diff --git a/README.md b/README.md index 5fa0a45d00fc5aafcacfabcf1ef457ecb1cb1eba..b72a8ea34de2b49f38c0c4378cee93c9864dd1fd 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,39 @@ ## oy +- not yet disconnecting events + +- some styling + - state 'squishes' when module moved too far right + - state 'input' length / title length for long titles ? + +- state context menu or button - like 'fire this listener' on click, or get alt description + +- bridge should be able to look for devices, test devices, and do terminal stuff + +- right-click on title of module, reload + +- for crashes etc, how can we re-start / kick serverside from the browser? some other daemon program ? + +- probably shouldn't send *all* of the state every time ... ? + - althought it's nice and foolproof + +- still want more diverse html inputs ... i.e. text input with some size to it ? gcode parser for this ... + +- write in logger functions + - i.e. module.log('whatever') is wrapped at load into console.log() and sends serverside log message as well, with ID and name + - state variables good - final steps before writing gcode consumption program - - write ui position back to server - - nomenclature all over the place is spaget - - cleaner replace ? wholistic replace ? - - classes / pretty code ? find desires by using + - nomenclature all over the place is spaget + - rewriting this is a good reintroduction to project + - cleaner replace ? wholistic replace ? + - classes / pretty code ? find desires by using - any kind of save / load - still with script-type writable file ? + - but largely ready to write planner, serialport and packet parsing modules, do gcode stuff + - server modules are ground truth - to the client, we serve representations of these modules - on the client, we keep a copy of this rep , and build a UI for it, ``rep.ui`` @@ -35,6 +59,9 @@ http://backbonejs.org/#Events ## Desires - load / keep state + - run headless with view into + - collaborative program editing would be cool + - program save / load / edit ? ## Model Consistency diff --git a/client/client.js b/client/client.js index 861d6b04c46b5afd005a44e34c92c24e2466256d..0ed360dfa4e3ca1b8a2fcb314010e928215419d4 100644 --- a/client/client.js +++ b/client/client.js @@ -28,11 +28,7 @@ oncontextmenu = function(evt) { if (sckt) { lastPos.X = evt.pageX lastPos.Y = evt.pageY - var req = { - type: 'get menu', - data: '' - } - sckt.send(JSON.stringify(req)) + socketSend('get menu', '') } else { // socket brkn location.reload() @@ -86,6 +82,7 @@ function addReps(reps) { function addRep(rep) { // a div to locate it var domElem = document.createElement('div') + // dif. color for hardwares ? domElem.className = 'block' domElem.style.left = lastPos.X + 'px' domElem.style.top = lastPos.Y + 'px' diff --git a/client/divtools.js b/client/divtools.js index 0ef83161d8222965cb3823aac64c978f28907fe0..119212dc64efc68e051dd731b8b2920422dfb22f 100644 --- a/client/divtools.js +++ b/client/divtools.js @@ -1,37 +1,54 @@ // writing representations -function writeStateRep(container, rep, key){ +function writeStateRep(container, rep, key) { var variable = rep.state[key] - if(typeof variable == 'string'){ - var li = document.createElement('li') - li.appendChild(document.createTextNode(key)) - var input = document.createElement('input') - input.type = 'text' - input.size = 24 - input.value = variable - input.addEventListener('change', function(){ - rep.state[key] = input.value - putState(rep) - }) - li.appendChild(input) - container.appendChild(li) - return input - } else if (typeof variable == 'number'){ + if (variable.isButton) { + console.log('BUTTON!') var li = document.createElement('li') - li.appendChild(document.createTextNode(key)) - var input = document.createElement('input') - input.type = 'text' - input.size = 24 - input.value = variable.toString() - input.addEventListener('change', function(){ - rep.state[key] = parseFloat(input.value) + li.appendChild(document.createTextNode(variable.label)) + li.addEventListener('click', function() { + // invert + if(rep.state[key].isPressed){ + rep.state[key].isPressed = false + } else { + rep.state[key].isPressed = true + } putState(rep) }) - li.appendChild(input) container.appendChild(li) - return input + return li } else { - console.log("unui'd type:", typeof variable) + if (typeof variable == 'string') { + var li = document.createElement('li') + li.appendChild(document.createTextNode(key)) + var input = document.createElement('input') + input.type = 'text' + input.size = 24 + input.value = variable + input.addEventListener('change', function() { + rep.state[key] = input.value + putState(rep) + }) + li.appendChild(input) + container.appendChild(li) + return input + } else if (typeof variable == 'number') { + var li = document.createElement('li') + li.appendChild(document.createTextNode(key)) + var input = document.createElement('input') + input.type = 'text' + input.size = 24 + input.value = variable.toString() + input.addEventListener('change', function() { + rep.state[key] = parseFloat(input.value) + putState(rep) + }) + li.appendChild(input) + container.appendChild(li) + return input + } else { + console.log("unui'd type:", typeof variable) + } } } @@ -90,7 +107,7 @@ function modifyBezierTail(bz, x2, y2) { redrawBezier(bz) } -function getOutputArrow(div){ +function getOutputArrow(div) { var x = div.offsetParent.offsetLeft + div.offsetLeft + div.clientWidth var y = div.offsetParent.offsetTop + div.offsetTop + div.clientHeight / 2 var pos = { @@ -101,7 +118,7 @@ function getOutputArrow(div){ return pos } -function getInputArrow(div){ +function getInputArrow(div) { var x = div.offsetParent.offsetLeft var y = div.offsetParent.offsetTop + div.offsetTop + div.clientHeight / 2 var pos = { diff --git a/client/style.css b/client/style.css index 5d557999e50422bb15f9fb0755802666b5355669..0b06c0e286dc5c37b74873e16f8fa6e40447ddaa 100644 --- a/client/style.css +++ b/client/style.css @@ -34,9 +34,11 @@ body { color: #eee; } +/* .state li:hover { background-color: #303030; } +*/ .state input { background-color: #1a1a1a; @@ -82,7 +84,11 @@ li { } li:hover{ - background-color: #000; + background-color: #969696; +} + +li:active{ + background-color: #d1d1d1; } #menu { diff --git a/lib/inout.js b/lib/inout.js index e0d05f0d39288eb182db2994a7444c38106737ce..a24abf480bd63508894020345b4d376bb16071ab 100644 --- a/lib/inout.js +++ b/lib/inout.js @@ -54,10 +54,22 @@ function State(){ return state } +// within state ... or not ? +function Button(label){ + var button = { + isButton: true, + isPressed: false, + label: label + } + + return button +} + module.exports = { Input: Input, Output: Output, - State: State + State: State, + Button: Button } /* diff --git a/package-lock.json b/package-lock.json index f6b39aee788f79d0344d7a0983766c9a6f8d1b78..b46f78106464b85d6b3e9258ffe61a04d7d7c093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -356,6 +356,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, "bindings": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", @@ -483,6 +488,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" }, + "complex.js": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz", + "integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw==" + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -531,6 +541,11 @@ "ms": "2.0.0" } }, + "decimal.js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.1.tgz", + "integrity": "sha512-vklWB5C4Cj423xnaOtsUmAv0/7GqlXIgDv2ZKDyR64OV3OSzGHNx2mk4p/1EKnB5s70k73cIOOEcG9YzF0q4Lw==" + }, "decompress-response": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", @@ -600,6 +615,11 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "escape-latex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.1.1.tgz", + "integrity": "sha512-N2D6Z2kXh8x/pQNQH+natXDCwrzghhXMRII5dZ518mlTLeuba80NL0LCQyaahqOrAidoLivmmG6GKPnGhHse+A==" + }, "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -712,6 +732,11 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, + "fraction.js": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.9.tgz", + "integrity": "sha512-qP1sNwdrcA+Vs5TTvGETuaaUmz4Tm48V6Jc+8Oh/gqvkb1d42s99w5kvSrZkIATp/mz3rV4CTef6xINkCofu+A==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -876,6 +901,11 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -928,6 +958,21 @@ "object-visit": "1.0.1" } }, + "mathjs": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-5.1.2.tgz", + "integrity": "sha512-ewA6m6omkS+VPkU6Qd60T2oPVvACG4r8ajD6M6OaWbO0nMQ4wr0ZHFHRjfuzpTb96Y5HT82rTeNqGGktBa5G/w==", + "requires": { + "complex.js": "2.0.11", + "decimal.js": "10.0.1", + "escape-latex": "1.1.1", + "fraction.js": "4.0.9", + "javascript-natural-sort": "0.7.1", + "seed-random": "2.2.0", + "tiny-emitter": "2.0.2", + "typed-function": "1.1.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1526,6 +1571,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "seed-random": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=" + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -1833,6 +1883,11 @@ "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" }, + "tiny-emitter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", + "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" + }, "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", @@ -1871,6 +1926,11 @@ "mime-types": "2.1.19" } }, + "typed-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.1.0.tgz", + "integrity": "sha512-TuQzwiT4DDg19beHam3E66oRXhyqlyfgjHB/5fcvsRXbfmWPJfto9B4a0TBdTrQAPGlGmXh/k7iUI+WsObgORA==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1933,6 +1993,14 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "ws": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", + "requires": { + "async-limiter": "1.0.0" + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 2fb0e633c3b106b4c28e65247b879618a0f6e6e4..66dba07163d90e70adad0bb1fe82269e1feed12c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dependencies": { "express": "^4.16.3", "http": "0.0.0", + "mathjs": "^5.1.2", "readline": "^1.3.0", "serialport": "^6.2.2", "ws": "^6.0.0" diff --git a/server.js b/server.js index bd8b953c71d154aee65774fc7da736d7edd58d4b..8ae6379ddd6cab224de9061e25d2abe4145a9f3e 100644 --- a/server.js +++ b/server.js @@ -103,38 +103,62 @@ function socketRecv(evt) { } /* - +------------------------------------------------------ PROGRAM AS API - +------------------------------------------------------ */ var modules = new Array() // wrap require() up, appending path used to object, and giving ID // use the same to load from browser -var term = addModule('./src/ui/terminal.js') -var gcode = addModule('./src/parsing/gcode.js') +//var term = addModule('./src/ui/terminal.js') +//var gcode = addModule('./src/parsing/gcode.js') +//var planner = addModule('./src/motion/planner.js') +var stepper = addModule('./src/hardware/stepper.js') +var bridge = addModule('./src/hardware/bridge.js') // attaching an output to an input -term.outputs.lineOut.attach(gcode.inputs.lineIn) +// term.outputs.lineOut.attach(gcode.inputs.lineIn) // setting ui elements / state -term.state.uiInput = "G1 F100 X10 Y10" +// term.state.uiInput = "G1 F100 X10 Y10" // gcode.ui.G0.set(1255) +stepper.state.address = '0,2' +stepper.outputs.packet.attach(bridge.inputs.B) +bridge.outputs.B.attach(stepper.inputs.packet) + +// gcode.outputs.instructionOut.attach(planner.inputs.instruction) +// planner.outputs.move.attach(stepper.inputs.move) + // setting data w/r/t the representations they serve +/* term.ui = {} term.ui.left = 10 term.ui.top = 10 gcode.ui = {} -gcode.ui.left = 200 -gcode.ui.top = 400 +gcode.ui.left = 50 +gcode.ui.top = 100 -/* +planner.ui = {} +planner.ui.left = 100 +planner.ui.top = 250 +*/ -PROGRAM WRITING +stepper.ui = {} +stepper.ui.left = 150 +stepper.ui.top = 450 + +bridge.ui = {} +bridge.ui.left = 200 +bridge.ui.top = 650 +/* +------------------------------------------------------ +PROGRAM WRITING +------------------------------------------------------ */ function addModule(path) { @@ -149,7 +173,7 @@ function addModule(path) { mod.path = path // we need to add some top-level info to the inputs so that we can draw them - for (item in mod.inputs){ + for (item in mod.inputs) { mod.inputs[item].parentId = mod.id mod.inputs[item].key = item } @@ -211,7 +235,7 @@ function putReps() { socketSend('add reps', reps) } -function makeRep(mod){ +function makeRep(mod) { // scrape for only things we'll want to represent var rep = { id: mod.id, @@ -222,12 +246,12 @@ function makeRep(mod){ } // holds UI information - to load mods at location - if(mod.ui != null){ + if (mod.ui != null) { rep.ui = mod.ui } - for(var key in mod.state){ - if(isStateKey(key)){ + for (var key in mod.state) { + if (isStateKey(key)) { rep.state[key] = mod.state[key] } } @@ -239,7 +263,7 @@ function putRep(mod) { socketSend('add rep', makeRep(mod)) } -function changeRep(mod){ +function changeRep(mod) { socketSend('change rep', makeRep(mod)) } @@ -252,7 +276,12 @@ function changeState(data) { // rep only holds proper key-values w/o emitters, _values etc for (var key in newState) { if (isStateKey(key)) { - if (oldState[key] !== newState[key]) { + if (oldState[key].isButton) { + if (oldState[key].isPressed != newState[key].isPressed) { + console.log('CHANGE STATE', key, 'to', newState[key], 'in', data.id) + oldState[key] = newState[key] + } + } else if (oldState[key] !== newState[key]) { console.log('CHANGE STATE', key, 'to', newState[key], 'in', data.id) oldState[key] = newState[key] } @@ -260,9 +289,9 @@ function changeState(data) { } } -function changeUi(data){ +function changeUi(data) { var mod = modules[data.id] - if(mod.ui == null){ + if (mod.ui == null) { console.log("NO UI") mod.ui = {} } @@ -270,8 +299,8 @@ function changeUi(data){ console.log('CHANGE UI', mod.id, mod.ui) } -function isStateKey(key){ - if(key.indexOf('_') == 0 || key == 'emitters' || key == 'onChange' || key == 'emitChange'){ +function isStateKey(key) { + if (key.indexOf('_') == 0 || key == 'emitters' || key == 'onChange' || key == 'emitChange') { return false } else { return true @@ -283,8 +312,8 @@ function putState(mod) { // push just the state to the individual mod // copy only the keys we want to see var state = {} - for (var key in mod.state){ - if(isStateKey(key)){ + for (var key in mod.state) { + if (isStateKey(key)) { state[key] = mod.state[key] } } diff --git a/src/hardware/bridge.js b/src/hardware/bridge.js new file mode 100644 index 0000000000000000000000000000000000000000..40f889bb26cc0fa927ff5320b7b520c85ba961ed --- /dev/null +++ b/src/hardware/bridge.js @@ -0,0 +1,174 @@ +// boilerplate atkapi header +const InOut = require('../../lib/inout.js') +let Input = InOut.Input +let Output = InOut.Output + +let State = InOut.State +let Button = InOut.Button + +const serialport = require('serialport') + +function ATKBridge() { + var bridge = { + description: { + name: 'ATK Hardware Bridge', + alt: 'talks over serialport', + isHardware: true + } + } + + bridge.state = State() + var state = bridge.state + + state.rA = '0,0' + state.rB = '0,1' + + state.findPort = Button('click to find atk port') + state.portName = '---' + state.connect = Button('click to connect') + state.portStatus = 'closed' // or we hope it will be + state.terminal = 'address | key:values' + state.sendRawPacket = Button('sendRaw') + + /* + ------------------------------------------------------ + SERIALPORT MANAGEMENT + ------------------------------------------------------ + */ + + port = null + + state.onChange('findPort', findPort) + + function findPort() { + serialport.list(function(err, ports) { + ports.forEach(function(port) { + if (port.manufacturer == 'Silicon Labs') { + console.log('found cp2102 port') + state.portName = port.comName + openPort() + } + }) + }) + } + + state.onChange('connect', openPort) + + function openPort() { + if (state.portName == '---') { + findPort() + } else { + if (port == null) { + port = new serialport(state.portName, { + baudRate: 750000 + }) + port.on('open', function() { + state.portStatus = 'open' + }) + port.on('error', function(err) { + state.portStatus = err.message + }) + port.on('data', onPortData) + } + } + } + + function sendToHardware(pckt){ + if(port.writable){ + port.write(pckt) + return true + } else { + return false + } + } + + /* + ------------------------------------------------------ + PACKETS TO HARDWARE + ------------------------------------------------------ + */ + + // from software output to hardware ... + bridge.inputs = { + A: Input('headless packet', function() { + onPacketInput('rA') + }), + B: Input('headless packet', function() { + onPacketInput('rB') + }) + } + + state.onChange('sendRawPacket', function() { + if (parseTerminal(state.terminal)) { + state.sendRawPacket.isPressed = false + } else { + state.terminal = 'not parsed successfully' + state.sendRawPacket.isPressed = false + } + }) + + function parseTerminal(str){ + // + return false + } + + function onSoftwareInput(key) { + console.log('packet to send on address', state.key) + } + + function parseTerminal(str) { + console.log('ready to parse...', str, 'as packet') + } + + /* + ------------------------------------------------------ + PACKETS FROM HARDWARE + ------------------------------------------------------ + */ + + // from hardware outputs to software ... + bridge.outputs = { + A: Output('headless packet'), + B: Output('headless packet') + } + + var thisPacket = new Array() + + function onPortData(data) { + console.log('DATA IN', data) + /* + // not sure about this, from old code, but sure + var dataArray = new Array() + for (var i = 0; i < data.length; i ++){ + dataArray[i] = data[i] + } + */ + + thisPacket = thisPacket.concat(data) + if (thisPacket[0] <= 0) { + thisPacket = [] + console.log('throwing packet with leading zero') + } + + while (thisPacket.length >= thisPacket[0]) { + if (thisPacket.length == thisPacket[0]) { + var packetCopy = thisPacket.slice(0) // copy, deref + thisPacket = [] + onPacket(packetCopy) + } else { // rare case of two packets saddling break + var fullPacket = thisPacket.slice(0, thisPacket[0]) + onPacket(fullPacket) + thisPacket = thisPacket.slice(thisPacket[0]) + } + + } + } + + function onPacket(pckt) { + console.log('recv full packet', pckt) + } + + return bridge +} + +module.exports = ATKBridge \ No newline at end of file diff --git a/src/hardware/stepper.js b/src/hardware/stepper.js new file mode 100644 index 0000000000000000000000000000000000000000..32a68c3396a4396f42ac956a9eea14d4e05bd933 --- /dev/null +++ b/src/hardware/stepper.js @@ -0,0 +1,112 @@ +// boilerplate atkapi header +const InOut = require('../../lib/inout.js') +let Input = InOut.Input +let Output = InOut.Output + +let State = InOut.State +let Button = InOut.Button + +const math = require ('mathjs') + +function Stepper() { + var stepper = { + description: { + name: 'ATK Network Stepper Driver', + alt: 'software representation of stepper', + isHardware: true + } + } + + stepper.state = State() + state = stepper.state + + state.axis = 'X' + state.spu = 200 // steps per unit + state.rawMove = -10 + + state.makeMove = Button('test move') + + state.onChange('makeMove', function(){ + onRawMove() + state.makeMove.isPressed = false + }) + + stepper.inputs = { + move: Input('move instruction', onNewInstruction), + packet: Input('headless packet', onHardwareIn) + } + + stepper.outputs = { + heft: Output('number'), // how much juice used for last move + packet: Output('headless packet') + } + + function onHardwareIn(pckt){ + console.log('packet from stepper', pckt) + } + + function onRawMove(){ + console.log('raw move for', state.rawMove) + // finds type of ramp, calculates entry and exit ramp lengths + var length = Math.abs(state.rawMove) + var testTrapezoid = calcDiscreteMoves(state.rawMove, 10, 100, length / 2 - length/4, length / 2 + length / 4) + console.log(testTrapezoid) + /* + var machineTrap = calcDiscreteMoves(rawTrap) + var packet = makeTrapezoidPacket(machineTrap) + // send it + stepper.outputs.packet.emit(machineTrap) + */ + } + + function onNewInstruction(move){ + console.log('move to stepper', move) + // pick out axis (check if it's a wait move) + + // write trapezoid + + // machine trapezoid + + // send and setup for ack ? + } + + + function calcDiscreteMoves(delta, entry, accel, aLength, dLength){ + // steps, entryspeed, accel, accellength, deccellength + var dLength = watchRounding(Math.abs(delta * state.spu)) + var dDelta = 0 + if(delta < 0){ + dDelta = - dLength + } else { + dDelta = dLength + } + var dEntry = watchRounding(entry * state.spu) + var dAccel = watchRounding(accel * state.spu) + var dALength = watchRounding(aLength * state.spu) + var dDLength = watchRounding(dLength * state.spu) + + var discreteTrap = { + // HERE ! + } + // from float to step conversions, watching for corner (haha) cases + } + + function watchRounding(val){ + var rounded = Math.round(val) + if(rounded < 0){ + console.log('WATCH ZERO', val, rounded) + } + var error = Math.abs(val/rounded - 1) + if(error > 0.05){ + console.log('WATCH ROUNDING', val, rounded, error) + } + } + + function makeTrapezoidPacket(machineTrapezoid){ + // make a packit + } + + return stepper +} + +module.exports = Stepper \ No newline at end of file diff --git a/src/motion/planner.js b/src/motion/planner.js index ecf537333ed4ba701cc2e09fe57156754130c77c..328e5838bf3d7fab26b29f4d13e3e0a7b06c1120 100644 --- a/src/motion/planner.js +++ b/src/motion/planner.js @@ -1,88 +1,67 @@ // boilerplate atkapi header -const InOut = require('./lib/inout.js') +const InOut = require('../../lib/inout.js') let Input = InOut.Input let Output = InOut.Output +let State = InOut.State -function Gcode() { - - // state - this.state = { - mode: 'G0', - speeds: { - G0: 1200, - G1: 400 - } - } - - // local functions - var getKeyValues = (str) => { - var kv = {} - for (var i = 0; i < str.length; i++) { - if (str[i].match('[A-Za-z]')) { // regex to match upper case letters - var lastIndex = str.indexOf(' ', i) - if (lastIndex < 0) { - lastIndex = str.length - } - var key = str[i].toUpperCase() - kv[key] = parseFloat(str.slice(i + 1, lastIndex)) - } +function Planner() { + var planner = { + description: { + name: 'Lookahead Motion Planner', + alt: 'movements -> acceleration planned moves' } - return kv } - var parseGcode = (str) => { - var instruction = { - position: {}, - hasMove: false, - speed: 0 - } - - kv = getKeyValues(str) - // track modality - if(kv.G == 0 | kv.G == 1){ - this.state.mode = 'G' + kv.G.toString() - } else if (kv.G != null) { - // no arcs pls - console.log('unfriendly Gcode mode!', kv) - } - - for(key in kv){ - if(key.match('[A-EX-Z]')){ - instruction.position[key] = kv[key] - instruction.hasMove = true - } else if (key.match('[F]')){ - this.state.speeds[this.state.mode] = kv.F - } - } - - instruction.speed = this.state.speeds[this.state.mode] - // and this for help later? - instruction.kv = kv - - return instruction + planner.state = State() + var state = planner.state // reference pass attempt? + + state.axisIDs = 'X, Y, Z' + + state.accel = 100 // units/s/s + state.jd = 1 // units to arc about + state.ms = 10 // units/s + + planner.inputs = { + instruction: Input('move instruction', onNewInstruction) } - // input functions - var lineIn = (str) => { - var instruction = parseGcode(str) - if (instruction.hasMove){ - this.outputs.instructionOut.emit(instruction) - } else { - this.outputs.modeChange.emit(this.state.mode) - } + planner.outputs = { + move: Output('move instruction') } - // ins and outs - this.inputs = { - lineIn: new Input('string input', 'string', lineIn) + /* + ------------------------------------------------------ + ENTRY POINTS + ------------------------------------------------------ + */ + + function onNewInstruction(move){ + console.log('move to planner', move) } - this.outputs = { - instructionOut: new Output('move instruction', 'object'), - modeChange: new Output('mode change', 'string') + /* + ------------------------------------------------------ + JUNCTION DEVIATION + ------------------------------------------------------ + */ + + + + /* + ------------------------------------------------------ + TRAPEZOIDS FROM JUNCTION DEVIATION ENTRY / EXITS + ------------------------------------------------------ + */ + + function calcTrap(p1, p2, entry, accel, cruise, exit){ + // delta is signed ! important for writing direction later, we'll use delta for maths + var length = Math.abs(delta) + + // min and max exits / entries based on entry / exit speeds, accel, respectively + // actually we sould really do this in the planner ? } -} + return planner +} -// export the module -module.exports = Gcode \ No newline at end of file +module.exports = Planner \ No newline at end of file