adds server
This commit is contained in:
parent
5ac1bd7afd
commit
5b8ab3b12f
17 changed files with 1077 additions and 1 deletions
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
lib-cov
|
||||
lcov.info
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
.grunt
|
||||
|
||||
node_modules
|
||||
|
||||
installation/macinstall/iris/
|
||||
installation/macinstall/pkg/Scripts.d/node-*
|
||||
|
||||
46
README.md
46
README.md
|
|
@ -1,2 +1,46 @@
|
|||
# xcarve-server
|
||||
# Raspberry Pi XCarve Server
|
||||
A Node.js Raspberry Pi server for controlling an attached XCarve from a remote machine.
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have the latest stable version of Node.js installed on your Raspberry Pi. You can download
|
||||
it from the [node-arm](http://node-arm.herokuapp.com/) project.
|
||||
|
||||
```
|
||||
pi@xcarve ~ $ node -v
|
||||
v0.12.6
|
||||
```
|
||||
|
||||
Make sure the global `npm` folder on your Raspberry Pi is writable by the `pi` user.
|
||||
|
||||
```
|
||||
pi@xcarve ~ $ chown -R pi /usr/local
|
||||
```
|
||||
|
||||
Install `forever` and `xcarve-server` on your Raspberry Pi.
|
||||
|
||||
```
|
||||
pi@xcarve ~ $ npm install -g forever xcarve-server
|
||||
```
|
||||
|
||||
## Starting the Daemon
|
||||
|
||||
If everything has been installed, you can start the server daemon by running the following command:
|
||||
|
||||
```
|
||||
pi@xcarve ~ $ xcarve-server
|
||||
|
||||
██╗ ██╗ ██████╗ █████╗ ██████╗ ██╗ ██╗███████╗
|
||||
╚██╗██╔╝ ██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝
|
||||
╚███╔╝█████╗██║ ███████║██████╔╝██║ ██║█████╗
|
||||
██╔██╗╚════╝██║ ██╔══██║██╔══██╗╚██╗ ██╔╝██╔══╝
|
||||
██╔╝ ██╗ ╚██████╗██║ ██║██║ ██║ ╚████╔╝ ███████╗
|
||||
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝
|
||||
|
||||
starting server daemon on port 1338...
|
||||
```
|
||||
## Stopping the Daemon
|
||||
|
||||
```
|
||||
pi@xcarve ~ $ forever stopall
|
||||
```
|
||||
|
|
|
|||
18
cli
Executable file
18
cli
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var child,
|
||||
spawn = require('child_process').spawn,
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
var logo = fs.readFileSync(path.join(__dirname,'logo.txt'), 'utf8');
|
||||
|
||||
console.log(logo);
|
||||
|
||||
child = spawn('forever', ['start', 'index.js', '-s'], {
|
||||
cwd: __dirname,
|
||||
env: process.env,
|
||||
detached: true
|
||||
});
|
||||
child.on('error', console.log);
|
||||
|
||||
16
configs/carvin.json
Normal file
16
configs/carvin.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name" : "Carvin Configs",
|
||||
"baud" : 115200,
|
||||
"separator" : "\n",
|
||||
"readyResponse": "Marlin",
|
||||
"successResponse": "ok",
|
||||
"gcode": {
|
||||
"status": "M500",
|
||||
"settings": "M503",
|
||||
"liftToSafeHeight": "G0 Z10",
|
||||
"home": "M403",
|
||||
"park": "M403",
|
||||
"spindleOn": "M3",
|
||||
"spindleOff": "M5"
|
||||
}
|
||||
}
|
||||
25
configs/grbl_0.8.json
Normal file
25
configs/grbl_0.8.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name" : "GRBL 0.8 Default Configs",
|
||||
"baud" : 9600,
|
||||
"separator" : "\r\n",
|
||||
"readyResponse": "Grbl 0.8c",
|
||||
"successResponse": "ok",
|
||||
"gcode": {
|
||||
"flush": "\\u0x18",
|
||||
"home": "$H",
|
||||
"liftToSafeHeight": "G20 G90 Z0.25",
|
||||
"pause": "!",
|
||||
"park": "G20 G90 G0 X0 Y0",
|
||||
"recallCoordinateSystem": "G54",
|
||||
"resume": "~",
|
||||
"saveOrigin": "G10 L20 P0 X0 Y0 Z0",
|
||||
"settings": "$$",
|
||||
"status": "?",
|
||||
"unlock": "$X",
|
||||
"zProbe": "G21 G38.2 Z-100 F80",
|
||||
"spindleOn": "M3",
|
||||
"spindleOff": "M5",
|
||||
"lockMotors": "$7=255\nG91 X0 Y0",
|
||||
"unlockMotors": "$7=0\nG91 X0 Y0"
|
||||
}
|
||||
}
|
||||
25
configs/grbl_0.9.json
Normal file
25
configs/grbl_0.9.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name" : "GRBL 0.9 Default Configs",
|
||||
"baud" : 115200,
|
||||
"separator" : "\r\n",
|
||||
"readyResponse": "Grbl 0.9g",
|
||||
"successResponse": "ok",
|
||||
"gcode": {
|
||||
"flush": "\\u0x18",
|
||||
"home": "$H",
|
||||
"liftToSafeHeight": "G20 G90 Z0.25",
|
||||
"pause": "!",
|
||||
"park": "G20 G90 G0 X0 Y0",
|
||||
"recallCoordinateSystem": "G54",
|
||||
"resume": "~",
|
||||
"saveOrigin": "G10 L20 P0 X0 Y0 Z0",
|
||||
"settings": "$$",
|
||||
"status": "?",
|
||||
"unlock": "$X",
|
||||
"zProbe": "G21 G38.2 Z-100 F80",
|
||||
"spindleOn": "M3",
|
||||
"spindleOff": "M5",
|
||||
"lockMotors": "$1=255\nG91 X0 Y0",
|
||||
"unlockMotors": "$1=0\nG91 X0 Y0"
|
||||
}
|
||||
}
|
||||
17
index.js
Executable file
17
index.js
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
var io = require('socket.io')
|
||||
, http = require('http')
|
||||
, WebsocketController = require('./lib/websocket_controller')
|
||||
, fs = require('fs')
|
||||
, path = require('path');
|
||||
|
||||
var WEBSOCKET_PORT = 1338;
|
||||
|
||||
var app = http.createServer()
|
||||
io = io.listen(app);
|
||||
|
||||
var origins = "easel.inventables.com:80 easel.inventables.com:443"
|
||||
io.origins(origins);
|
||||
|
||||
app.listen(WEBSOCKET_PORT, '0.0.0.0');
|
||||
|
||||
var websocketController = new WebsocketController(io.sockets, '0.2.1');
|
||||
46
lib/debugger.js
Normal file
46
lib/debugger.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
exports.logger = function() {
|
||||
var logger = function(name) {
|
||||
var that = {};
|
||||
|
||||
var id = Date.now();
|
||||
var prefix = name + " [id=" + id + "] ";
|
||||
|
||||
var timestamp = function() {
|
||||
return "[time=" + Date.now() + "] ";
|
||||
};
|
||||
|
||||
var withColor = function(msg, color) {
|
||||
if (color) {
|
||||
if (Colors[color]) {
|
||||
color = Colors[color];
|
||||
}
|
||||
|
||||
msg = color + msg + Colors['RESET'];
|
||||
}
|
||||
|
||||
return msg;
|
||||
};
|
||||
|
||||
var log = function(msg, color) {
|
||||
console.log(prefix + timestamp() + withColor(msg, color));
|
||||
};
|
||||
|
||||
that.log = log;
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
logger.colors = {
|
||||
RED : '\033[31m',
|
||||
GREEN : '\033[32m',
|
||||
YELLOW : '\033[33m',
|
||||
BLUE : '\033[34m',
|
||||
MAGENTA : '\033[35m',
|
||||
CYAN : '\033[36m',
|
||||
RESET : '\033[0m'
|
||||
};
|
||||
|
||||
return logger;
|
||||
}();
|
||||
|
||||
11
lib/error_handler.js
Normal file
11
lib/error_handler.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
var ErrorHandler = (function () {
|
||||
|
||||
return {
|
||||
trigger : function (err) {
|
||||
// TODO: better error handling and recovery
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
module.exports = ErrorHandler;
|
||||
27
lib/event_dispatcher.js
Normal file
27
lib/event_dispatcher.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
module.exports = function(that) {
|
||||
var listeners = [];
|
||||
|
||||
var dispatchEvent = function(name, data) {
|
||||
listeners.forEach( function(listener) {
|
||||
if (listener.name === name) {
|
||||
listener.f.call(listener.context, data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var addEventListener = function(name, func, ctx) {
|
||||
listeners.push({name: name, f: func, context: ctx});
|
||||
};
|
||||
|
||||
var removeEventListener = function(name, func) {
|
||||
var i = listeners.indexOf({name: name, f: func});
|
||||
listeners = listeners.slice(i, i);
|
||||
};
|
||||
|
||||
that.dispatchEvent = dispatchEvent;
|
||||
that.addEventListener = addEventListener;
|
||||
that.removeEventListener = removeEventListener;
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
21
lib/interval.js
Normal file
21
lib/interval.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
var interval = function(callback) {
|
||||
var id = null;
|
||||
|
||||
var start = function(delay) {
|
||||
clearInterval(id);
|
||||
id = setInterval(callback, delay);
|
||||
}
|
||||
|
||||
var stop = function() {
|
||||
clearInterval(id);
|
||||
id = null;
|
||||
}
|
||||
|
||||
var that = {};
|
||||
that.start = start;
|
||||
that.stop = stop;
|
||||
return that;
|
||||
}
|
||||
|
||||
module.exports = interval;
|
||||
|
||||
348
lib/machine.js
Normal file
348
lib/machine.js
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
var _ = require('underscore')._
|
||||
, fs = require('fs')
|
||||
, readline = require('readline')
|
||||
, Debugger = require('./debugger')
|
||||
, interval = require('./interval')
|
||||
, onoff = require('onoff')
|
||||
, eventDispatcher = require('./event_dispatcher');
|
||||
|
||||
|
||||
var Machine = function(port) {
|
||||
var that = {};
|
||||
|
||||
var MAX_BYTES = 127;
|
||||
|
||||
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 completedCommandCount;
|
||||
var isRunning = false;
|
||||
var isPaused = false;
|
||||
var isStopping = false;
|
||||
var isMachineConnected = false;
|
||||
var machineIdentification = null;
|
||||
var currentPosition = null;
|
||||
|
||||
var config = null;
|
||||
|
||||
var isGRBL = function() {
|
||||
return (config && config.name.indexOf('GRBL') !== -1);
|
||||
};
|
||||
|
||||
var heartbeat = interval(function() {
|
||||
if (!isRunning || isGRBL()) {
|
||||
sendInstruction('status');
|
||||
}
|
||||
});
|
||||
|
||||
var startHeartbeat = function() {
|
||||
logger.log('starting heartbeat');
|
||||
heartbeat.start(500);
|
||||
};
|
||||
|
||||
var stopHeartbeat = function() {
|
||||
logger.log('stopping heartbeat');
|
||||
heartbeat.stop();
|
||||
};
|
||||
|
||||
var gcodeFor = function(instruction) {
|
||||
var gcode = config.gcode[instruction];
|
||||
if (gcode && gcode.indexOf('\\u') !== -1) {
|
||||
gcode = String.fromCharCode(gcode.replace('\\u', ''));
|
||||
}
|
||||
return gcode;
|
||||
};
|
||||
|
||||
var byteCount = function(s) {
|
||||
return encodeURI(s).split(/%..|./).length - 1;
|
||||
};
|
||||
|
||||
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.parser().addEventListener('grbl-alarm', onGrblAlarm);
|
||||
port.parser().addEventListener('grbl-error', onGrblError);
|
||||
|
||||
port.addEventListener("close", portClosed);
|
||||
};
|
||||
|
||||
var onGrblAlarm = function (message) {
|
||||
that.dispatchEvent('grbl-alarm', message);
|
||||
};
|
||||
|
||||
var onGrblError = function (message) {
|
||||
that.dispatchEvent('grbl-error', message);
|
||||
};
|
||||
|
||||
var getMachineIdentification = function() {
|
||||
if (isMachineConnected) {
|
||||
return machineIdentification;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
var portOpened = function() {
|
||||
logger.log('port opened! waiting for identifier');
|
||||
};
|
||||
|
||||
var machineConnected = function(identification) {
|
||||
machineIdentification = identification;
|
||||
isMachineConnected = true;
|
||||
startHeartbeat();
|
||||
that.dispatchEvent('connected');
|
||||
};
|
||||
|
||||
var state = function(state) {
|
||||
that.dispatchEvent('state', state);
|
||||
};
|
||||
|
||||
var position = function(position) {
|
||||
currentPosition = position;
|
||||
that.dispatchEvent('position', position);
|
||||
};
|
||||
|
||||
var ready = function() {
|
||||
that.dispatchEvent('ready');
|
||||
};
|
||||
|
||||
var requestSettings = function() {
|
||||
sendInstruction('settings');
|
||||
};
|
||||
|
||||
var settings = function(data) {
|
||||
logger.log("machine settings: " + data);
|
||||
that.dispatchEvent('settings', data);
|
||||
};
|
||||
|
||||
var streamGcodeLines = function(lines) {
|
||||
reset();
|
||||
commandStack = lines.reverse()
|
||||
isRunning = true;
|
||||
reportJobStatus();
|
||||
fillCommandBuffer();
|
||||
};
|
||||
|
||||
var clearStack = function() {
|
||||
commandStack = [];
|
||||
};
|
||||
|
||||
var nextCommand = function() {
|
||||
if (commandStack.length > 0) {
|
||||
return commandStack[commandStack.length - 1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
var roomInBufferForNextCommand = function() {
|
||||
var buffer = sentCommands.join("\n") + "\n";
|
||||
var bytes = byteCount(buffer + nextCommand() + "\n");
|
||||
|
||||
return bytes <= MAX_BYTES;
|
||||
};
|
||||
|
||||
var popNextCommand = function() {
|
||||
return commandStack.pop();
|
||||
};
|
||||
|
||||
var sendLine = function(line) {
|
||||
port.write(line + '\n');
|
||||
};
|
||||
|
||||
var fillCommandBuffer = function() {
|
||||
while (nextCommand() && roomInBufferForNextCommand()) {
|
||||
var line = popNextCommand();
|
||||
sentCommands.unshift(line);
|
||||
logger.log('Sending line: ' + line);
|
||||
sendLine(line);
|
||||
}
|
||||
};
|
||||
|
||||
var unprocessedCommandCount = function() {
|
||||
return sentCommands.length + commandStack.length;
|
||||
}
|
||||
|
||||
var percentComplete = function() {
|
||||
return completedCommandCount / (completedCommandCount + unprocessedCommandCount()) * 100;
|
||||
};
|
||||
|
||||
var processedCommand = function() {
|
||||
lastRunCommand = sentCommands.pop();
|
||||
completedCommandCount++;
|
||||
|
||||
if (isRunning && !isPaused) {
|
||||
reportJobStatus();
|
||||
if (unprocessedCommandCount() == 0) {
|
||||
isRunning = false;
|
||||
} else {
|
||||
fillCommandBuffer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var currentState = function() {
|
||||
return {
|
||||
completedCommandCount: completedCommandCount,
|
||||
pendingCommandCount: commandStack.length,
|
||||
lastCommand: lastRunCommand,
|
||||
machineBuffer: sentCommands.concat([]).reverse(), // reverse this thing to return the oldest commands first
|
||||
running: isRunning,
|
||||
paused: isPaused,
|
||||
stopping: isStopping
|
||||
};
|
||||
};
|
||||
|
||||
var portClosed = function() {
|
||||
isMachineConnected = false;
|
||||
that.dispatchEvent('port_lost', error("Machine disconnected"));
|
||||
reset();
|
||||
};
|
||||
|
||||
// Socket connection to Easel lost
|
||||
var disconnect = function() {
|
||||
stopHeartbeat();
|
||||
port.close();
|
||||
isMachineConnected = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
var error = function(message) {
|
||||
return {
|
||||
completed_command_count: completedCommandCount,
|
||||
pending_command_count: commandStack.length,
|
||||
current_position: currentPosition,
|
||||
active_buffer: sentCommands.concat([]).reverse(), // reverse this thing to return the oldest commands first
|
||||
last_instruction: lastRunCommand,
|
||||
sender_note: message
|
||||
}
|
||||
};
|
||||
|
||||
var reset = function() {
|
||||
logger.log("Resetting");
|
||||
isRunning = false;
|
||||
isPaused = false;
|
||||
commandStack = [];
|
||||
sentCommands = [];
|
||||
completedCommandCount = 0;
|
||||
};
|
||||
|
||||
var running = function() {
|
||||
that.dispatchEvent("progress", percentComplete());
|
||||
};
|
||||
|
||||
var reportJobStatus = function() {
|
||||
if (isRunning) {
|
||||
if (isPaused) {
|
||||
paused();
|
||||
} else {
|
||||
running();
|
||||
}
|
||||
} else if (isStopping) {
|
||||
stopping();
|
||||
} else if (isMachineConnected) {
|
||||
ready();
|
||||
}
|
||||
};
|
||||
|
||||
var pause = function() {
|
||||
if (isRunning) {
|
||||
isPaused = true;
|
||||
sendInstruction('pause')
|
||||
paused();
|
||||
}
|
||||
};
|
||||
|
||||
var paused = function() {
|
||||
that.dispatchEvent("paused", percentComplete());
|
||||
};
|
||||
|
||||
var resume = function() {
|
||||
if (isPaused) {
|
||||
isPaused = false;
|
||||
sendInstruction('resume');
|
||||
fillCommandBuffer();
|
||||
that.dispatchEvent("resumed", percentComplete());
|
||||
}
|
||||
};
|
||||
|
||||
var sendInstruction = function(instruction) {
|
||||
var gcode = gcodeFor(instruction);
|
||||
if (gcode === '?') {
|
||||
port.write(gcode);
|
||||
} else if (gcode !== undefined) {
|
||||
sendLine(gcode);
|
||||
}
|
||||
};
|
||||
|
||||
var stop = function(params) {
|
||||
if (isRunning) {
|
||||
isStopping = true;
|
||||
stopping();
|
||||
reset();
|
||||
sendInstruction('pause');
|
||||
setTimeout(function() {
|
||||
sendInstruction('flush');
|
||||
setTimeout(function() {
|
||||
sendInstruction('resume');
|
||||
setTimeout(function() {
|
||||
sendInstruction('liftToSafeHeight');
|
||||
sendInstruction('spindleOff');
|
||||
sendInstruction('park');
|
||||
isStopping = false;
|
||||
reportJobStatus();
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
var execute = function(instructions) {
|
||||
instructions.forEach(function(instruction) {
|
||||
sendInstruction(instruction);
|
||||
});
|
||||
};
|
||||
|
||||
var stopping = function() {
|
||||
that.dispatchEvent("stopping");
|
||||
};
|
||||
|
||||
var acquire = function(timestamp) {
|
||||
if (!isRunning) {
|
||||
that.dispatchEvent("release", timestamp);
|
||||
};
|
||||
};
|
||||
|
||||
var setConfig = function(_config) {
|
||||
config = _config;
|
||||
};
|
||||
|
||||
that.getMachineIdentification = getMachineIdentification;
|
||||
that.requestSettings = requestSettings;
|
||||
that.currentState = currentState;
|
||||
that.streamGcodeLines = streamGcodeLines;
|
||||
that.sendLine = sendLine;
|
||||
that.clearStack = clearStack;
|
||||
that.disconnect = disconnect;
|
||||
that.reportJobStatus = reportJobStatus;
|
||||
that.pause = pause;
|
||||
that.resume = resume;
|
||||
that.stop = stop;
|
||||
that.acquire = acquire;
|
||||
that.setConfig = setConfig;
|
||||
that.execute = execute;
|
||||
|
||||
init();
|
||||
reset();
|
||||
eventDispatcher(that);
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
module.exports = Machine;
|
||||
95
lib/parser.js
Normal file
95
lib/parser.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
var _ = require('underscore')._
|
||||
, Debugger = require('./debugger')
|
||||
, eventDispatcher = require('./event_dispatcher');
|
||||
|
||||
var Parser = function(){
|
||||
|
||||
var that = {};
|
||||
eventDispatcher(that);
|
||||
|
||||
var logger = Debugger.logger("Parser");
|
||||
|
||||
var stringContains = function(str, matcher) {
|
||||
return str.indexOf(matcher) !== -1;
|
||||
};
|
||||
|
||||
var stringContainsAtLeastOne = function(str, matchers) {
|
||||
for (var n = 0; n < matchers.length; n++) {
|
||||
if (stringContains(str, matchers[n])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var isGrblReport = function (d) {
|
||||
return d.match(/<(.*)>/);
|
||||
};
|
||||
|
||||
var isGrblSettings = function (d) {
|
||||
return d.match(/\$\d+\s*=/);
|
||||
};
|
||||
|
||||
var isGrblError = function (d) {
|
||||
return d.match(/error:(.*)/);
|
||||
};
|
||||
|
||||
var isGrblAlarm = function (d) {
|
||||
return d.match(/ALARM:(.*)/);
|
||||
};
|
||||
|
||||
var parseData = function (d, config) {
|
||||
d = d.trim();
|
||||
if (stringContainsAtLeastOne(d, config.readyResponses)) {
|
||||
that.dispatchEvent('ready', d);
|
||||
} else if (stringContains(d, config.successResponse)) {
|
||||
that.dispatchEvent('ok', d);
|
||||
} else if (isGrblReport(d)) {
|
||||
onGrblReport(d);
|
||||
} else if (isGrblSettings(d)) {
|
||||
onGrblSettings(d);
|
||||
} else if (isGrblError(d)) {
|
||||
that.dispatchEvent('grbl-error', d);
|
||||
} else if (isGrblAlarm(d)) {
|
||||
that.dispatchEvent('grbl-alarm', d);
|
||||
} else {
|
||||
that.dispatchEvent('unknown', d);
|
||||
}
|
||||
};
|
||||
|
||||
// format is <[status], MPos:[x],[y], [z] ... >
|
||||
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);
|
||||
|
||||
if (statusMatch) {
|
||||
that.dispatchEvent('state', statusMatch[1].toLowerCase());
|
||||
that.dispatchEvent('position', {
|
||||
machine: {
|
||||
x : parseFloat(statusMatch[2]),
|
||||
y : parseFloat(statusMatch[3]),
|
||||
z : parseFloat(statusMatch[4])
|
||||
},
|
||||
work: {
|
||||
x : parseFloat(statusMatch[5]),
|
||||
y : parseFloat(statusMatch[6]),
|
||||
z : parseFloat(statusMatch[7])
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var onGrblSettings = function(d) {
|
||||
logger.log("parsing settings data: " + d);
|
||||
that.dispatchEvent('settings', d);
|
||||
};
|
||||
|
||||
that.parseData = parseData;
|
||||
|
||||
return that;
|
||||
}
|
||||
|
||||
module.exports = Parser;
|
||||
122
lib/serial_port_controller.js
Normal file
122
lib/serial_port_controller.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
var errorHandler = require('./error_handler')
|
||||
, SP = require('serialport')
|
||||
, Debugger = require('./debugger')
|
||||
, eventDispatcher = require('./event_dispatcher')
|
||||
, Parser = require('./parser');
|
||||
|
||||
var SerialPortController = function() {
|
||||
var that = {}
|
||||
|
||||
var port = null;
|
||||
|
||||
var isWindows = !!process.platform.match(/^win/);
|
||||
var parser = Parser();
|
||||
var logger = Debugger.logger("Serial port controller");
|
||||
|
||||
var connected = function() {
|
||||
return (port !== null);
|
||||
};
|
||||
|
||||
var write = function(data) {
|
||||
if (connected()) {
|
||||
port.write(data);
|
||||
}
|
||||
};
|
||||
|
||||
var close = function() {
|
||||
if (connected()) {
|
||||
if (port.fd) {
|
||||
logger.log("Closing port");
|
||||
port.close();
|
||||
}
|
||||
port = null;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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){
|
||||
logger.log("ERROR: " + err, Debugger.logger.RED);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
thisPort.on('open', function() {
|
||||
if (port !== thisPort) {
|
||||
return;
|
||||
}
|
||||
logger.log("Port opened");
|
||||
parser.dispatchEvent('portOpened');
|
||||
});
|
||||
|
||||
thisPort.on('data', function(d) {
|
||||
if (port !== thisPort) {
|
||||
return;
|
||||
}
|
||||
parser.parseData(d, config);
|
||||
});
|
||||
|
||||
thisPort.on('error', function(d) {
|
||||
if (port !== thisPort) {
|
||||
return;
|
||||
}
|
||||
if (port !== null) {
|
||||
logger.log('On error');
|
||||
logger.log('error: ' + d);
|
||||
logger.log('CODE: ' + d.code);
|
||||
if (d.code === 'UNKNOWN' || d.code === 'ENXIO' || d.code === undefined) {
|
||||
close();
|
||||
that.dispatchEvent("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
thisPort.on('close', function() {
|
||||
if (port !== thisPort) {
|
||||
return;
|
||||
}
|
||||
logger.log('On close');
|
||||
port = null;
|
||||
that.dispatchEvent("close");
|
||||
});
|
||||
|
||||
port = thisPort;
|
||||
};
|
||||
|
||||
that.listPorts = listPorts;
|
||||
that.initPortWithConfigs = initPortWithConfigs;
|
||||
that.write = write;
|
||||
that.parser = function() { return parser; };
|
||||
that.connected = connected;
|
||||
that.close = close;
|
||||
|
||||
eventDispatcher(that);
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
module.exports = SerialPortController;
|
||||
220
lib/websocket_controller.js
Normal file
220
lib/websocket_controller.js
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
var Machine = require('./machine')
|
||||
, SerialPortController = require('./serial_port_controller')
|
||||
, path = require('path')
|
||||
, Debugger = require('./debugger')
|
||||
, power = require('onoff').Gpio(17, 'out')
|
||||
, fs = require('fs');
|
||||
|
||||
|
||||
var WebsocketController = function(sockets, version) {
|
||||
var that = {};
|
||||
var logger = Debugger.logger("Websocket Controller");
|
||||
var connectedClients = 0;
|
||||
var sp_controller = new SerialPortController();
|
||||
var machine = Machine(sp_controller);
|
||||
var minimumTimeBetweenUpdates = 500;
|
||||
var lastUpdateTime = Date.now();
|
||||
var config = null;
|
||||
var projectName = "Unknown";
|
||||
|
||||
var setUpMachineListeners = function() {
|
||||
machine.addEventListener('connected', function() {
|
||||
reportConnectionStatus();
|
||||
});
|
||||
|
||||
machine.addEventListener('ready', function() {
|
||||
sockets.emit('ready');
|
||||
});
|
||||
|
||||
machine.addEventListener('resumed', function(percentComplete) {
|
||||
sockets.emit('running', projectName, percentComplete);
|
||||
});
|
||||
|
||||
machine.addEventListener('progress', function(percentComplete) {
|
||||
if (Date.now() - lastUpdateTime > minimumTimeBetweenUpdates || percentComplete === 100) {
|
||||
lastUpdateTime = Date.now();
|
||||
sockets.emit('running', projectName, percentComplete);
|
||||
}
|
||||
});
|
||||
|
||||
machine.addEventListener('done', function() {
|
||||
sockets.emit('done');
|
||||
});
|
||||
|
||||
machine.addEventListener('error', function(d) {
|
||||
sockets.emit('error', d);
|
||||
});
|
||||
|
||||
machine.addEventListener('port_lost', function(data) {
|
||||
sockets.emit('port_lost', data);
|
||||
});
|
||||
|
||||
machine.addEventListener('position', function(position) {
|
||||
sockets.emit('position', position);
|
||||
});
|
||||
|
||||
machine.addEventListener('state', function(state) {
|
||||
sockets.emit('state', state);
|
||||
});
|
||||
|
||||
machine.addEventListener('settings', function(settings) {
|
||||
sockets.emit('machine-settings', settings);
|
||||
});
|
||||
|
||||
machine.addEventListener('paused', function(percentComplete) {
|
||||
sockets.emit('paused', projectName, percentComplete);
|
||||
});
|
||||
|
||||
machine.addEventListener('release', function(timestamp) {
|
||||
sockets.emit('release', timestamp);
|
||||
});
|
||||
|
||||
machine.addEventListener('stopping', function() {
|
||||
sockets.emit('stopping');
|
||||
});
|
||||
|
||||
machine.addEventListener('grbl-error', function(message) {
|
||||
sockets.emit('grbl-error', message);
|
||||
});
|
||||
|
||||
machine.addEventListener('grbl-alarm', function(message) {
|
||||
sockets.emit('grbl-alarm', message);
|
||||
});
|
||||
};
|
||||
|
||||
setUpMachineListeners();
|
||||
|
||||
var reportJobStatus = function() {
|
||||
machine.reportJobStatus();
|
||||
};
|
||||
|
||||
var reportConnectionStatus = function() {
|
||||
sockets.emit('connection_status', machine.getMachineIdentification());
|
||||
};
|
||||
|
||||
var onGcode = function(job) {
|
||||
var gcode = job.gcode;
|
||||
var lines = gcode.split('\n');
|
||||
projectName = job.name;
|
||||
if (!machine) {
|
||||
console.error("Machine not initialized");
|
||||
} else {
|
||||
logger.log('got ' + lines.length + ' lines of gcode');
|
||||
machine.streamGcodeLines(lines);
|
||||
}
|
||||
};
|
||||
|
||||
var onMachineSettings = function() {
|
||||
machine.requestSettings();
|
||||
};
|
||||
|
||||
var onGetPorts = function() {
|
||||
sp_controller.listPorts(function (ports) {
|
||||
sockets.emit('ports', ports)
|
||||
});
|
||||
};
|
||||
|
||||
var onConsole = function(line) {
|
||||
logger.log('sending line from console: '+line)
|
||||
// TODO : using machine protected function
|
||||
machine.sendLine(line);
|
||||
};
|
||||
|
||||
var onSenderState = function() {
|
||||
if (machine) {
|
||||
socket.emit('iris-state', machine.currentState());
|
||||
} else {
|
||||
socket.emit('iris-state', 'offline');
|
||||
}
|
||||
};
|
||||
|
||||
var onSetConfig = function(_config) {
|
||||
config = _config;
|
||||
|
||||
logger.log('Setting config to: ' + config.name);
|
||||
machine.disconnect();
|
||||
|
||||
logger.log("initing using config: " + config.name)
|
||||
machine.setConfig(config);
|
||||
};
|
||||
|
||||
var onClear = function() {
|
||||
machine.clearStack();
|
||||
};
|
||||
|
||||
var onDisconnect = function() {
|
||||
connectedClients -= 1;
|
||||
|
||||
if (connectedClients !== 0)
|
||||
return;
|
||||
|
||||
machine.disconnect();
|
||||
|
||||
// turn off power
|
||||
setTimeout(function() {
|
||||
power.writeSync(0);
|
||||
}, 2000);
|
||||
|
||||
};
|
||||
|
||||
var onPause = function() {
|
||||
machine.pause();
|
||||
};
|
||||
|
||||
var onResume = function() {
|
||||
machine.resume();
|
||||
};
|
||||
|
||||
var onAcquire = function(timestamp) {
|
||||
machine.acquire(timestamp);
|
||||
};
|
||||
|
||||
var onStop = function(params) {
|
||||
machine.stop(params);
|
||||
};
|
||||
|
||||
var onExecute = function(instructions) {
|
||||
machine.execute(instructions);
|
||||
};
|
||||
|
||||
var onInitPort = function(comName) {
|
||||
if (config === null) {
|
||||
logger.log('ERROR: trying to connect without setting a configuration!');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log("trying to init port: " + comName);
|
||||
sp_controller.initPortWithConfigs(comName, config);
|
||||
};
|
||||
|
||||
sockets.on('connection', function(socket) {
|
||||
|
||||
// turn on powerswitchtail
|
||||
power.writeSync(1);
|
||||
|
||||
socket.emit('version', version);
|
||||
|
||||
socket.on('get_connection', reportConnectionStatus);
|
||||
socket.on('get_job_status', reportJobStatus);
|
||||
socket.on('gcode', onGcode);
|
||||
socket.on('get_ports', onGetPorts);
|
||||
socket.on('console', onConsole);
|
||||
socket.on('execute', onExecute);
|
||||
socket.on('state', onSenderState);
|
||||
socket.on('set_config', onSetConfig)
|
||||
socket.on('disconnect', onDisconnect);
|
||||
socket.on('init_port', onInitPort);
|
||||
socket.on('pause', onPause);
|
||||
socket.on('acquire', onAcquire);
|
||||
socket.on('resume', onResume);
|
||||
socket.on('stop', onStop);
|
||||
socket.on('machine-settings', onMachineSettings);
|
||||
socket.on('sent_feedback', function() { socket.broadcast.emit("sent_feedback"); });
|
||||
|
||||
connectedClients += 1;
|
||||
});
|
||||
|
||||
return that;
|
||||
};
|
||||
|
||||
module.exports = WebsocketController;
|
||||
9
logo.txt
Normal file
9
logo.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
██╗ ██╗ ██████╗ █████╗ ██████╗ ██╗ ██╗███████╗
|
||||
╚██╗██╔╝ ██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝
|
||||
╚███╔╝█████╗██║ ███████║██████╔╝██║ ██║█████╗
|
||||
██╔██╗╚════╝██║ ██╔══██║██╔══██╗╚██╗ ██╔╝██╔══╝
|
||||
██╔╝ ██╗ ╚██████╗██║ ██║██║ ██║ ╚████╔╝ ███████╗
|
||||
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝
|
||||
|
||||
starting server daemon on port 1338...
|
||||
|
||||
11
package.json
Normal file
11
package.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "xcarve-server",
|
||||
"version": "0.1.0",
|
||||
"bin": "./cli",
|
||||
"dependencies": {
|
||||
"onoff": "^1.0.2",
|
||||
"serialport": "1.7.x",
|
||||
"socket.io": "1.3.x",
|
||||
"underscore": "1.4.x"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue