adds server

This commit is contained in:
Todd Treece 2015-08-14 17:22:57 -04:00
parent 5ac1bd7afd
commit 5b8ab3b12f
17 changed files with 1077 additions and 1 deletions

21
.gitignore vendored Normal file
View 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-*

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;

View 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
View 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
View file

@ -0,0 +1,9 @@
██╗ ██╗ ██████╗ █████╗ ██████╗ ██╗ ██╗███████╗
╚██╗██╔╝ ██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝
╚███╔╝█████╗██║ ███████║██████╔╝██║ ██║█████╗
██╔██╗╚════╝██║ ██╔══██║██╔══██╗╚██╗ ██╔╝██╔══╝
██╔╝ ██╗ ╚██████╗██║ ██║██║ ██║ ╚████╔╝ ███████╗
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝
starting server daemon on port 1338...

11
package.json Normal file
View 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"
}
}