adds updates from iris 0.2.7

This commit is contained in:
Todd Treece 2016-11-09 21:43:19 +00:00
parent e79b08d66e
commit 70896b3742
6 changed files with 281 additions and 129 deletions

View file

@ -1,4 +1,4 @@
// this file is part of the v0.2.1 easel local OS X install.
// this file is part of the v0.2.7 easel local OS X install.
// you can find out more about easel and the x-carve at
// inventables.com & easel.inventables.com

View file

@ -1,4 +1,4 @@
// this file is part of the v0.2.1 easel local OS X install.
// this file is part of the v0.2.7 easel local OS X install.
// you can find out more about easel and the x-carve at
// inventables.com & easel.inventables.com

View file

@ -1,4 +1,4 @@
// this file is part of the v0.2.1 easel local OS X install.
// this file is part of the v0.2.7 easel local OS X install.
// you can find out more about easel and the x-carve at
// inventables.com & easel.inventables.com

View file

@ -1,13 +1,11 @@
// this file is part of the v0.2.1 easel local OS X install.
// this file is part of the v0.2.7 easel local OS X install.
// you can find out more about easel and the x-carve at
// inventables.com & easel.inventables.com
var _ = require('underscore')._
, fs = require('fs')
var fs = require('fs')
, readline = require('readline')
, Debugger = require('./debugger')
, interval = require('./interval')
, onoff = require('onoff')
, eventDispatcher = require('./event_dispatcher');
@ -18,36 +16,31 @@ var Machine = function(port) {
var logger = Debugger.logger("Machine");
var commandStack; // Commands waiting to go to the machine
var sentCommands; // Commands in the machine's buffer
var lastRunCommand; // Last command completed by the machine
var queuedGcodeCommands; // Commands from gcode waiting to go to the machine
var queuedConsoleCommands; // Commands from sendInstruction waiting to go to the machine
var bufferedCommands; // Commands in the machine's buffer
var lastRunCommand; // Last command completed by the machine
var completedCommandCount;
var isRunning = false;
var isPaused = false;
var isStopping = false;
var isMachineConnected = false;
var machineIdentification = null;
var currentPosition = null;
var startRunTime = null;
var config = null;
var isGRBL = function() {
return (config && config.name.indexOf('GRBL') !== -1);
};
var runState = 'RUNNING';
var heartbeat = interval(function() {
if (!isRunning || isGRBL()) {
sendInstruction('status');
}
sendInstruction('status');
});
var startHeartbeat = function() {
logger.log('starting heartbeat');
heartbeat.start(500);
};
var stopHeartbeat = function() {
logger.log('stopping heartbeat');
heartbeat.stop();
};
@ -64,14 +57,18 @@ var Machine = function(port) {
};
var init = function() {
port.parser().addEventListener('ok', processedCommand);
port.parser().addEventListener('ready', machineConnected);
port.parser().addEventListener('state', state);
port.parser().addEventListener('position', position);
port.parser().addEventListener('settings', settings);
port.parser().addEventListener('portOpened', portOpened);
port.addEventListener('portOpened', onPortOpened);
port.parser().addEventListener('ok', onProcessCommand);
port.parser().addEventListener('ready', onMachineConnected);
port.parser().addEventListener('status', onStatus);
port.parser().addEventListener('position', onPosition);
port.parser().addEventListener('probe-status', onReceiveProbeStatus);
port.parser().addEventListener('probe-result', onReceiveProbeResult);
port.parser().addEventListener('settings', onSettings);
port.parser().addEventListener('grbl-alarm', onGrblAlarm);
port.parser().addEventListener('grbl-error', onGrblError);
port.parser().addEventListener('machine-type', onReceiveMachineType);
port.parser().addEventListener('serial-number', onReceiveSerialNumber);
port.addEventListener("close", portClosed);
};
@ -84,6 +81,22 @@ var Machine = function(port) {
that.dispatchEvent('grbl-error', message);
};
var onReceiveMachineType = function(machineType) {
that.dispatchEvent('machine-type', machineType);
};
var onReceiveProbeStatus = function(probeStatus) {
that.dispatchEvent('probe-status', probeStatus);
};
var onReceiveProbeResult = function(probeResult) {
that.dispatchEvent('probe-result', probeResult);
};
var onReceiveSerialNumber = function(serialNumber) {
that.dispatchEvent('serial-number', serialNumber);
};
var getMachineIdentification = function() {
if (isMachineConnected) {
return machineIdentification;
@ -92,22 +105,76 @@ var Machine = function(port) {
}
};
var portOpened = function() {
logger.log('port opened! waiting for identifier');
var onPortOpened = function() {
sendInstruction('flush');
};
var machineConnected = function(identification) {
var onMachineConnected = function(identification) {
machineIdentification = identification;
isMachineConnected = true;
startHeartbeat();
sendInstruction('readSerialNumber');
that.dispatchEvent('connected');
};
var state = function(state) {
that.dispatchEvent('state', state);
var statusTransitions = {
'PAUSING': {
'hold': 'PAUSED',
'door': 'PAUSED_DOOR_OPEN'
},
'PAUSED': {
'run': 'RUNNING',
'door': 'PAUSED_DOOR_OPEN'
},
'PAUSED_DOOR_OPEN': {
'hold': 'PAUSED',
'run': 'RUNNING'
},
'RESUMING': {
'run': 'RUNNING',
'door': 'PAUSED_DOOR_OPEN'
},
'RUNNING': {
'hold': 'PAUSED',
'door': 'PAUSED_DOOR_OPEN'
}
};
var position = function(position) {
var actionTransitions = {
'PAUSED': {
'resume': 'RESUMING'
},
'RUNNING': {
'pause': 'PAUSING'
},
'PAUSING': {
'resume': 'RESUMING'
},
'PAUSED_DOOR_OPEN': {},
'RESUMING': {
'pause': 'PAUSING'
}
};
var runStateEnteredCallbacks = function() {
return {
'PAUSING': paused,
'PAUSED_DOOR_OPEN': paused,
'PAUSED': paused,
'RESUMING': resumed,
'RUNNING': resumed
}
};
var onStatus = function(status) {
if (isRunning) {
transitionRunState(status, statusTransitions);
}
that.dispatchEvent('status', status);
};
var onPosition = function(position) {
currentPosition = position;
that.dispatchEvent('position', position);
};
@ -120,40 +187,43 @@ var Machine = function(port) {
sendInstruction('settings');
};
var settings = function(data) {
logger.log("machine settings: " + data);
var onSettings = function(data) {
that.dispatchEvent('settings', data);
};
var streamGcodeLines = function(lines) {
reset();
commandStack = lines.reverse()
queuedGcodeCommands = lines;
isRunning = true;
runState = 'RUNNING'; // TODO bring this under the easelAction umbrella
completedCommandCount = 0;
startRunTime = Date.now();
reportJobStatus();
fillCommandBuffer();
};
var clearStack = function() {
commandStack = [];
};
var nextCommand = function() {
if (commandStack.length > 0) {
return commandStack[commandStack.length - 1];
if (queuedConsoleCommands.length > 0) {
return queuedConsoleCommands[0];
} else if (isRunning && runState === 'RUNNING' && queuedGcodeCommands.length > 0) {
return queuedGcodeCommands[0];
} else {
return null;
}
};
var roomInBufferForNextCommand = function() {
var buffer = sentCommands.join("\n") + "\n";
var bytes = byteCount(buffer + nextCommand() + "\n");
return bytes <= MAX_BYTES;
var dequeueNextCommand = function() {
if (queuedConsoleCommands.length > 0) {
return queuedConsoleCommands.shift();
} else if (queuedGcodeCommands.length > 0) {
return queuedGcodeCommands.shift();
}
};
var popNextCommand = function() {
return commandStack.pop();
var roomInBufferForNextCommand = function() {
var potentialBufferedCommands = bufferedCommands.concat([nextCommand()]);
var bytes = byteCount(potentialBufferedCommands.join('\n') + '\n');
return bytes <= MAX_BYTES;
};
var sendLine = function(line) {
@ -162,31 +232,30 @@ var Machine = function(port) {
var fillCommandBuffer = function() {
while (nextCommand() && roomInBufferForNextCommand()) {
var line = popNextCommand();
sentCommands.unshift(line);
logger.log('Sending line: ' + line);
var line = dequeueNextCommand();
bufferedCommands.push(line);
sendLine(line);
}
};
var unprocessedCommandCount = function() {
return sentCommands.length + commandStack.length;
}
return bufferedCommands.length + queuedConsoleCommands.length + queuedGcodeCommands.length;
};
var percentComplete = function() {
return completedCommandCount / (completedCommandCount + unprocessedCommandCount()) * 100;
};
var processedCommand = function() {
lastRunCommand = sentCommands.pop();
var onProcessCommand = function() {
lastRunCommand = bufferedCommands.shift();
completedCommandCount++;
fillCommandBuffer();
if (isRunning && !isPaused) {
if (isRunning && runState === 'RUNNING') {
reportJobStatus();
if (unprocessedCommandCount() == 0) {
if (unprocessedCommandCount() === 0) {
isRunning = false;
} else {
fillCommandBuffer();
reportRunTime();
}
}
};
@ -194,17 +263,19 @@ var Machine = function(port) {
var currentState = function() {
return {
completedCommandCount: completedCommandCount,
pendingCommandCount: commandStack.length,
pendingCommandCount: queuedConsoleCommands.length + queuedGcodeCommands.length,
lastCommand: lastRunCommand,
machineBuffer: sentCommands.concat([]).reverse(), // reverse this thing to return the oldest commands first
machineBuffer: bufferedCommands,
running: isRunning,
paused: isPaused,
paused: runState === 'PAUSED',
stopping: isStopping
};
};
var portClosed = function() {
stopHeartbeat();
isMachineConnected = false;
reportRunTime();
that.dispatchEvent('port_lost', error("Machine disconnected"));
reset();
};
@ -220,10 +291,10 @@ var Machine = function(port) {
var error = function(message) {
return {
completed_command_count: completedCommandCount,
pending_command_count: commandStack.length,
pending_command_count: queuedConsoleCommands.length + queuedGcodeCommands.length,
current_position: currentPosition,
active_buffer: sentCommands.concat([]).reverse(), // reverse this thing to return the oldest commands first
last_instruction: lastRunCommand,
active_buffer: bufferedCommands,
sender_note: message
}
};
@ -231,22 +302,37 @@ var Machine = function(port) {
var reset = function() {
logger.log("Resetting");
isRunning = false;
isPaused = false;
commandStack = [];
sentCommands = [];
runState = 'RUNNING';
resetQueue();
completedCommandCount = 0;
};
var resetQueue = function() {
queuedGcodeCommands = [];
queuedConsoleCommands = [];
bufferedCommands = [];
};
var running = function() {
that.dispatchEvent("progress", percentComplete());
};
var reportJobStatus = function() {
if (isRunning) {
if (isPaused) {
paused();
} else {
running();
// Unified run-state reporting
reportRunState();
// For API compatibility, collapse intermediate pausing / resuming states
switch (runState) {
case 'RUNNING':
case 'RESUMING':
running();
break;
case 'PAUSED':
case 'PAUSING':
case 'PAUSING_DOOR_OPEN':
paused();
break;
}
} else if (isStopping) {
stopping();
@ -255,36 +341,79 @@ var Machine = function(port) {
}
};
var pause = function() {
if (isRunning) {
isPaused = true;
sendInstruction('pause')
paused();
var reportRunState = function() {
that.dispatchEvent("run-state", runState);
};
var reportRunTime = function() {
if (startRunTime !== null) {
that.dispatchEvent('run-time', {start: startRunTime, end: Date.now()});
startRunTime = null;
}
};
var pause = function() {
sendInstruction('pause');
easelAction('pause');
};
var paused = function() {
that.dispatchEvent("paused", percentComplete());
};
var resume = function() {
if (isPaused) {
isPaused = false;
sendInstruction('resume');
fillCommandBuffer();
that.dispatchEvent("resumed", percentComplete());
sendInstruction('resume');
easelAction('resume');
};
var resumed = function() {
fillCommandBuffer();
that.dispatchEvent("resumed", percentComplete());
};
var enteredRunState = function(state) {
if (runStateEnteredCallbacks()[state]) {
runStateEnteredCallbacks()[state]();
}
};
var transitionRunState = function(action, transitions) {
var nextState = transitions[runState][action];
if (nextState) {
if (isRunning && runState === 'RUNNING') {
reportRunTime();
} else if (isRunning && nextState === 'RUNNING') {
startRunTime = Date.now();
}
runState = nextState;
enteredRunState(runState);
}
};
var easelAction = function(action) {
transitionRunState(action, actionTransitions);
};
var REAL_TIME_COMMANDS = { pause: true, resume: true, flush: true, status: true };
var sendInstruction = function(instruction) {
if (instruction === 'flush') {
resetQueue();
}
var gcode = gcodeFor(instruction);
if (gcode === '?') {
if (REAL_TIME_COMMANDS[instruction]) {
port.write(gcode);
} else if (gcode !== undefined) {
sendLine(gcode);
enqueueCommand(gcode);
}
};
var enqueueCommand = function(line) {
queuedConsoleCommands.push(line);
fillCommandBuffer();
};
var stop = function(params) {
if (isRunning) {
isStopping = true;
@ -331,8 +460,7 @@ var Machine = function(port) {
that.requestSettings = requestSettings;
that.currentState = currentState;
that.streamGcodeLines = streamGcodeLines;
that.sendLine = sendLine;
that.clearStack = clearStack;
that.enqueueCommand = enqueueCommand;
that.disconnect = disconnect;
that.reportJobStatus = reportJobStatus;
that.pause = pause;
@ -347,6 +475,6 @@ var Machine = function(port) {
eventDispatcher(that);
return that;
}
};
module.exports = Machine;

View file

@ -1,9 +1,8 @@
// this file is part of the v0.2.1 easel local OS X install.
// this file is part of the v0.2.7 easel local OS X install.
// you can find out more about easel and the x-carve at
// inventables.com & easel.inventables.com
var _ = require('underscore')._
, Debugger = require('./debugger')
var Debugger = require('./debugger')
, eventDispatcher = require('./event_dispatcher');
var Parser = function(){
@ -27,11 +26,15 @@ var Parser = function(){
};
var isGrblReport = function (d) {
return d.match(/<(.*)>/);
return d.match(/^<.*>$/);
};
var isGrblBuildInfo = function(d) {
return d.match(/^\[.+:[\d-]+\]$/);
};
var isGrblSettings = function (d) {
return d.match(/\$\d+\s*=/);
return d.match(/^\$\d+\s*=/);
};
var isGrblError = function (d) {
@ -42,6 +45,10 @@ var Parser = function(){
return d.match(/ALARM:(.*)/);
};
var isGrblProbe = function (d) {
return d.match(/\[PRB:.+/);
};
var parseData = function (d, config) {
d = d.trim();
if (stringContainsAtLeastOne(d, config.readyResponses)) {
@ -52,6 +59,10 @@ var Parser = function(){
onGrblReport(d);
} else if (isGrblSettings(d)) {
onGrblSettings(d);
} else if (isGrblProbe(d)) {
onGrblProbe(d);
} else if (isGrblBuildInfo(d)) {
onGrblBuildInfo(d);
} else if (isGrblError(d)) {
that.dispatchEvent('grbl-error', d);
} else if (isGrblAlarm(d)) {
@ -61,36 +72,55 @@ var Parser = function(){
}
};
// format is <[status], MPos:[x],[y], [z] ... >
// format is <[status],MPos:[x],[y],[z],WPos:[x],[y],[z],Pin:|0|>
var onGrblReport = function (d) {
var numberRe = '([-+]?[0-9]*\\.?[0-9]+)';
var positionRe = numberRe + ',' + numberRe + ',' + numberRe;
var stateRe = '(\\w+)';
var statusRe = new RegExp(stateRe + ',MPos:' + positionRe + ',WPos:' + positionRe);
var statusMatch = d.match(statusRe);
var statusRe = '(\\w+)';
var probeRe = '(?:,Pin:(?:\\d{3})?\\|(\\d)\\|)?';
if (statusMatch) {
that.dispatchEvent('state', statusMatch[1].toLowerCase());
var match = d.match(new RegExp(statusRe + ',MPos:' + positionRe + ',WPos:' + positionRe + probeRe));
if (match) {
that.dispatchEvent('status', match[1].toLowerCase());
that.dispatchEvent('position', {
machine: {
x : parseFloat(statusMatch[2]),
y : parseFloat(statusMatch[3]),
z : parseFloat(statusMatch[4])
x : parseFloat(match[2]),
y : parseFloat(match[3]),
z : parseFloat(match[4])
},
work: {
x : parseFloat(statusMatch[5]),
y : parseFloat(statusMatch[6]),
z : parseFloat(statusMatch[7])
x : parseFloat(match[5]),
y : parseFloat(match[6]),
z : parseFloat(match[7])
}
});
if (match[8]) {
that.dispatchEvent('probe-status', parseInt(match[8]));
}
}
};
var onGrblBuildInfo = function(d) {
var match = d.match(/^\[(.+)\]$/);
if (match) {
var fields = match[1].split(':');
if (fields.length >= 4) {
that.dispatchEvent('machine-type', { product: fields[1], revision: fields[2] });
}
that.dispatchEvent('serial-number', fields[fields.length - 1]);
}
};
var onGrblSettings = function(d) {
logger.log("parsing settings data: " + d);
that.dispatchEvent('settings', d);
};
var onGrblProbe = function(d) { // sample string: [PRB:0.000,0.000,0.418:1]
var match = d.match(/\[PRB:.+:(0|1)\]/);
that.dispatchEvent('probe-result', match[1]);
};
that.parseData = parseData;
return that;

View file

@ -1,4 +1,4 @@
// this file is part of the v0.2.1 easel local OS X install.
// this file is part of the v0.2.7 easel local OS X install.
// you can find out more about easel and the x-carve at
// inventables.com & easel.inventables.com
@ -9,11 +9,11 @@ var errorHandler = require('./error_handler')
, Parser = require('./parser');
var SerialPortController = function() {
var that = {}
var that = {};
var port = null;
var isWindows = false;
var isWindows = !!process.platform.match(/^win/);
var parser = Parser();
var logger = Debugger.logger("Serial port controller");
@ -23,6 +23,7 @@ var SerialPortController = function() {
var write = function(data) {
if (connected()) {
that.dispatchEvent('write', data);
port.write(data);
}
};
@ -30,7 +31,6 @@ var SerialPortController = function() {
var close = function() {
if (connected()) {
if (port.fd) {
logger.log("Closing port");
port.close();
}
port = null;
@ -38,32 +38,25 @@ var SerialPortController = function() {
};
var listPorts = function (callback) {
// TODO: fix listing ports on windows
if(isWindows){
callback([]);
} else {
SP.list(function(err,ports) {
if (err) {
// TODO: catch and handle errors
errorHandler.trigger(err);
return null;
} else {
// TODO: scope for callback?
callback(ports);
}
});
}
SP.list(function(err, ports) {
if (err) {
// TODO: catch and handle errors
errorHandler.trigger(err);
return null;
} else {
// TODO: scope for callback?
callback(ports);
}
});
};
var initPortWithConfigs = function(comName, config) {
close();
logger.log("Opening port '" + comName + "'");
var thisPort = new SP.SerialPort(comName, {
baudrate: config.baud,
parser: SP.parsers.readline(config.separator),
errorCallback : function(err){
errorCallback: function(err){
logger.log("ERROR: " + err, Debugger.logger.RED);
return;
}
@ -74,14 +67,15 @@ var SerialPortController = function() {
return;
}
logger.log("Port opened");
parser.dispatchEvent('portOpened');
that.dispatchEvent('portOpened');
});
thisPort.on('data', function(d) {
thisPort.on('data', function(data) {
if (port !== thisPort) {
return;
}
parser.parseData(d, config);
that.dispatchEvent('read', data);
parser.parseData(data, config);
});
thisPort.on('error', function(d) {