From 64276bd8817b92577b36d7e8fa051f067c3b3137 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Fri, 20 Oct 2017 00:38:18 +0800 Subject: [PATCH 01/16] Prepare for v2 --- package.json | 6 +- src/Controller.js | 514 ++++++++++++++++++++++++++++++++++++++++++++++ src/constants.js | 49 +++++ src/controller.js | 293 -------------------------- src/index.js | 2 +- src/units.js | 10 + 6 files changed, 578 insertions(+), 296 deletions(-) create mode 100644 src/Controller.js create mode 100644 src/constants.js delete mode 100644 src/controller.js create mode 100644 src/units.js diff --git a/package.json b/package.json index b200490..b65e40e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cncjs-controller", - "version": "1.3.0", + "version": "2.0.0", "description": "A controller library for event-based communication between client and CNCjs server.", "main": "lib/index.js", "scripts": { @@ -33,7 +33,9 @@ "events", "client" ], - "dependencies": {}, + "dependencies": { + "lodash.mapvalues": "^4.6.0" + }, "devDependencies": { "babel-cli": "~6.24.1", "babel-core": "~6.24.1", diff --git a/src/Controller.js b/src/Controller.js new file mode 100644 index 0000000..1df29ca --- /dev/null +++ b/src/Controller.js @@ -0,0 +1,514 @@ +import mapValues from 'lodash.mapvalues'; +import { in2mm } from './units'; +import ensureArray from './ensure-array'; +import { + // Units + IMPERIAL_UNITS, + METRIC_UNITS, + // Controller + GRBL, + SMOOTHIE, + TINYG +} from './constants'; + +const noop = () => {}; + +class Controller { + io = null; + socket = null; + + listeners = { + // Socket.IO Events + // Fired upon a connection including a successful reconnection. + 'connect': [], + // Fired upon a connection error. + 'connect_error': [], + // Fired upon a connection timeout. + 'connect_timeout': [], + // Fired when an error occurs. + 'error': [], + // Fired upon a disconnection. + 'disconnect': [], + // Fired upon a successful reconnection. + 'reconnect': [], + // Fired upon an attempt to reconnect. + 'reconnect_attempt': [], + // Fired upon an attempt to reconnect. + 'reconnecting': [], + // Fired upon a reconnection attempt error. + 'reconnect_error': [], + // Fired when couldn't reconnect within reconnectionAttempts. + 'reconnect_failed': [], + + // System Events + 'startup': [], + 'ports': [], + 'config:change': [], + 'task:start': [], + 'task:finish': [], + 'task:error': [], + 'controller:type': [], + 'controller:settings': [], + 'controller:state': [], + 'connection:open': [], + 'connection:close': [], + 'connection:change': [], + 'connection:error': [], + 'connection:read': [], + 'connection:write': [], + 'gcode:load': [], + 'gcode:unload': [], + 'feeder:status': [], + 'sender:status': [], + 'workflow:state': [], + 'message': [] + }; + + context = { + xmin: 0, + xmax: 0, + ymin: 0, + ymax: 0, + zmin: 0, + zmax: 0 + }; + + // User-defined baud rates + baudRates = []; + + // Available controllers + loadedControllers = []; + + // Controller + type = ''; // Grbl|Smoothie|TinyG + settings = {}; + state = {}; + + // Connection + connection = { + ident: '', + type: '', // serial|socket + settings: {} + }; + + workflow = { + state: 'idle' // running|paused|idle + }; + + // Whether the client is connected to the server. + // @return {boolean} Returns true if the client is connected to the server, false otherwise. + get connected() { + return !!(this.socket && this.socket.connected); + } + + // @param {object} io The socket.io-client module. + constructor(io) { + if (!io) { + throw new Error(`Expected the socket.io-client module, but got: ${io}`); + } + + this.io = io; + } + // Establish a connection to the server. + // @param {string} host + // @param {object} options + // @param {function} next + connect(host = '', options = {}, next = noop) { + if (typeof next !== 'function') { + next = noop; + } + + this.socket && this.socket.destroy(); + this.socket = this.io.connect(host, options); + + Object.keys(this.listeners).forEach((eventName) => { + if (!this.socket) { + return; + } + + this.socket.on(eventName, (...args) => { + if (eventName === 'controller:type') { + this.type = args[0]; + } + if (eventName === 'controller:settings') { + this.type = args[0]; + this.settings = { ...args[1] }; + } + if (eventName === 'controller:state') { + this.type = args[0]; + this.state = { ...args[1] }; + } + if (eventName === 'connection:open') { + const { ident, type, settings } = { ...args[0] }; + this.connection.ident = ident; + this.connection.type = type; + this.connection.settings = settings; + } + if (eventName === 'connection:close') { + this.type = ''; + this.settings = {}; + this.state = {}; + this.connection.ident = ''; + this.connection.type = ''; + this.connection.setting = {}; + this.workflow.state = 'idle'; + } + if (eventName === 'workflow:state') { + this.workflow.state = args[0]; + } + + const listeners = ensureArray(this.listeners[eventName]); + listeners.forEach(listener => { + listener(...args); + }); + }); + }); + + this.socket.on('startup', (data) => { + const { loadedControllers, baudRates } = { ...data }; + + this.loadedControllers = ensureArray(loadedControllers); + + // User-defined baud rates + this.baudRates = ensureArray(baudRates); + + if (next) { + next(null); + + // The callback can only be called once + next = null; + } + }); + } + // Disconnect from the server. + disconnect() { + this.socket && this.socket.destroy(); + this.socket = null; + } + // Adds the `listener` function to the end of the listeners array for the event named `eventName`. + // @param {string} eventName The name of the event. + // @param {function} listener The listener function. + addListener(eventName, listener) { + const listeners = this.listeners[eventName]; + if (!listeners || typeof listener !== 'function') { + return false; + } + listeners.push(listener); + return true; + } + // Removes the specified `listener` from the listener array for the event named `eventName`. + // @param {string} eventName The name of the event. + // @param {function} listener The listener function. + removeListener(eventName, listener) { + const listeners = this.listeners[eventName]; + if (!listeners || typeof listener !== 'function') { + return false; + } + listeners.splice(listeners.indexOf(listener), 1); + return true; + } + // Opens a connection. + // @param {string} controllerType One of: 'Grbl', 'Smoothe', 'TinyG'. Defaults to 'Grbl'. + // @param {string} connectionType One of: 'serial', 'socket'. Defaults to 'serial'. + // @param {object} options The options object. + // @param {string} options.path (serial only) The serial port referenced by the path. + // @param {number} [options.baudRate] (serial only) Defaults to 115200. + // @param {string} options.host (socket only) The host address. + // @param {number} [options.port] (socket only) The port number. Defaults to 23. () + // @param {function} [callback] Called after a connection is opened. + open(controllerType = 'Grbl', connectionType = 'serial', options, callback) { + if (typeof options !== 'object') { + options = {}; + callback = options; + } + if (typeof callback !== 'function') { + callback = noop; + } + if (!this.socket) { + return; + } + this.socket.emit('open', controllerType, connectionType, options, (err, ...args) => { + if (!err) { + this.connection.ident = args[0]; + } + + callback(err, ...args); + }); + } + // Closes an open connection. + // @param {function} [callback] Called once a connection is closed. + close(callback) { + if (typeof callback !== 'function') { + callback = noop; + } + if (!this.socket) { + return; + } + if (!this.connection.ident) { + return; + } + this.socket.emit('close', this.connection.ident, (err, ...args) => { + this.connection.ident = ''; + callback(err, ...args); + }); + } + // Executes a command on the server. + // @param {string} cmd The command string + // @example Example Usage + // - Load G-code + // controller.command('gcode:load', name, gcode, context /* optional */, callback) + // - Unload G-code + // controller.command('gcode:unload') + // - Start sending G-code + // controller.command('gcode:start') + // - Stop sending G-code + // controller.command('gcode:stop', { force: true }) + // - Pause + // controller.command('gcode:pause') + // - Resume + // controller.command('gcode:resume') + // - Feeder + // controller.command('feeder:feed') + // controller.command('feeder:start') + // controller.command('feeder:stop') + // - Feed Hold + // controller.command('feedhold') + // - Cycle Start + // controller.command('cyclestart') + // - Status Report + // controller.command('statusreport') + // - Homing + // controller.command('homing') + // - Sleep + // controller.command('sleep') + // - Unlock + // controller.command('unlock') + // - Reset + // controller.command('reset') + // - Feed Override + // controller.command('feedOverride') + // - Spindle Override + // controller.command('spindleOverride') + // - Rapid Override + // controller.command('rapidOverride') + // - Energize Motors + // controller.command('energizeMotors:on') + // controller.command('energizeMotors:off') + // - G-code + // controller.command('gcode', 'G0X0Y0', context /* optional */) + // - Load a macro + // controller.command('macro:load', '', context /* optional */, callback) + // - Run a macro + // controller.command('macro:run', '', context /* optional */, callback) + // - Load file from a watch directory + // controller.command('watchdir:load', '/path/to/file', callback) + command(cmd, ...args) { + if (!this.socket) { + return; + } + if (!this.connection.ident) { + return; + } + this.socket.emit('command', this.connection.ident, cmd, ...args); + } + // Writes data to the open connection. + // @param {string} data The data to write. + // @param {object} [context] The associated context information. + write(data, context) { + if (!this.socket) { + return; + } + if (!this.connection.ident) { + return; + } + this.socket.emit('write', this.connection.ident, data, context); + } + // Writes data and a newline character to the open connection. + // @param {string} data The data to write. + // @param {object} [context] The associated context information. + writeln(data, context) { + if (!this.socket) { + return; + } + if (!this.connection.ident) { + return; + } + this.socket.emit('writeln', this.connection.ident, data, context); + } + // Gets a list of available serial ports. + // @param {function} [callback] Called once completed. + getPorts(callback) { + if (!this.socket) { + return; + } + this.socket.emit('getPorts', callback); + } + // Gets the machine state. + // @return {string|number} The machine state. + getMachineState() { + if ([GRBL, SMOOTHIE, TINYG].indexOf(this.type) < 0) { + return ''; + } + + if (!this.connection.ident) { + return ''; + } + + let machineState; + + if (this.type === GRBL) { + machineState = this.state.machineState; + } else if (this.type === SMOOTHIE) { + machineState = this.state.machineState; + } else if (this.type === TINYG) { + machineState = this.state.machineState; + } + + return machineState || ''; + } + // Gets the machine position. + // @return {object} A position object which contains x, y, z, a, b, and c properties. + getMachinePosition() { + const defaultMachinePosition = { + x: '0.000', + y: '0.000', + z: '0.000', + a: '0.000', + b: '0.000', + c: '0.000' + }; + + // Grbl + if (this.type === GRBL) { + const { mpos } = this.state; + let { $13 = 0 } = { ...this.settings.settings }; + $13 = Number($13) || 0; + + // Machine position are reported in mm ($13=0) or inches ($13=1) + return mapValues({ + ...defaultMachinePosition, + ...mpos + }, (val) => { + return ($13 > 0) ? in2mm(val) : val; + }); + } + + // Smoothieware + if (this.type === SMOOTHIE) { + const { mpos, modal = {} } = this.state; + const units = { + 'G20': IMPERIAL_UNITS, + 'G21': METRIC_UNITS + }[modal.units]; + + // Machine position are reported in current units + return mapValues({ + ...defaultMachinePosition, + ...mpos + }, (val) => { + return (units === IMPERIAL_UNITS) ? in2mm(val) : val; + }); + } + + // TinyG + if (this.type === TINYG) { + const { mpos } = this.state; + + // https://github.com/synthetos/g2/wiki/Status-Reports + // Canonical machine position are always reported in millimeters with no offsets. + return { + ...defaultMachinePosition, + ...mpos + }; + } + + return defaultMachinePosition; + } + // Gets the work position. + // @return {object} A position object which contains x, y, z, a, b, and c properties. + getWorkPosition() { + const defaultWorkPosition = { + x: '0.000', + y: '0.000', + z: '0.000', + a: '0.000', + b: '0.000', + c: '0.000' + }; + + // Grbl + if (this.type === GRBL) { + const { wpos } = this.state; + let { $13 = 0 } = { ...this.settings.settings }; + $13 = Number($13) || 0; + + // Work position are reported in mm ($13=0) or inches ($13=1) + return mapValues({ + ...defaultWorkPosition, + ...wpos + }, val => { + return ($13 > 0) ? in2mm(val) : val; + }); + } + + // Smoothieware + if (this.type === SMOOTHIE) { + const { wpos, modal = {} } = this.state; + const units = { + 'G20': IMPERIAL_UNITS, + 'G21': METRIC_UNITS + }[modal.units]; + + // Work position are reported in current units + return mapValues({ + ...defaultWorkPosition, + ...wpos + }, (val) => { + return (units === IMPERIAL_UNITS) ? in2mm(val) : val; + }); + } + + // TinyG + if (this.type === TINYG) { + const { wpos, modal = {} } = this.state; + const units = { + 'G20': IMPERIAL_UNITS, + 'G21': METRIC_UNITS + }[modal.units]; + + // Work position are reported in current units, and also apply any offsets. + return mapValues({ + ...defaultWorkPosition, + ...wpos + }, (val) => { + return (units === IMPERIAL_UNITS) ? in2mm(val) : val; + }); + } + + return defaultWorkPosition; + } + // Gets work coordinate system. + // @return {string} Returns work coordinate system (G54-G59). + getWorkCoordinateSystem() { + const defaultWCS = 'G54'; + + if (this.type === GRBL) { + const { wcs } = { ...this.state.modal }; + return wcs || defaultWCS; + } + + if (this.type === SMOOTHIE) { + const { wcs } = { ...this.state.modal }; + return wcs || defaultWCS; + } + + if (this.type === TINYG) { + const { wcs } = { ...this.state.modal }; + return wcs || defaultWCS; + } + + return defaultWCS; + } +} + +export default Controller; diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..562ab76 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,49 @@ +// Metric and Imperial units +export const IMPERIAL_UNITS = 'in'; +export const METRIC_UNITS = 'mm'; + +// Controller +export const GRBL = 'Grbl'; +export const SMOOTHIE = 'Smoothie'; +export const TINYG = 'TinyG'; + +// Workflow State +export const WORKFLOW_STATE_IDLE = 'idle'; +export const WORKFLOW_STATE_PAUSED = 'paused'; +export const WORKFLOW_STATE_RUNNING = 'running'; + +// Grbl Machine State +export const GRBL_MACHINE_STATE_IDLE = 'Idle'; +export const GRBL_MACHINE_STATE_RUN = 'Run'; +export const GRBL_MACHINE_STATE_HOLD = 'Hold'; +export const GRBL_MACHINE_STATE_DOOR = 'Door'; +export const GRBL_MACHINE_STATE_HOME = 'Home'; +export const GRBL_MACHINE_STATE_SLEEP = 'Sleep'; +export const GRBL_MACHINE_STATE_ALARM = 'Alarm'; +export const GRBL_MACHINE_STATE_CHECK = 'Check'; + +// Smoothie Machine State +export const SMOOTHIE_MACHINE_STATE_IDLE = 'Idle'; +export const SMOOTHIE_MACHINE_STATE_RUN = 'Run'; +export const SMOOTHIE_MACHINE_STATE_HOLD = 'Hold'; +export const SMOOTHIE_MACHINE_STATE_DOOR = 'Door'; +export const SMOOTHIE_MACHINE_STATE_HOME = 'Home'; +export const SMOOTHIE_MACHINE_STATE_ALARM = 'Alarm'; +export const SMOOTHIE_MACHINE_STATE_CHECK = 'Check'; + +// TinyG Machine State +// https://github.com/synthetos/g2/wiki/Status-Reports#stat-values +export const TINYG_MACHINE_STATE_INITIALIZING = 0; // Machine is initializing +export const TINYG_MACHINE_STATE_READY = 1; // Machine is ready for use +export const TINYG_MACHINE_STATE_ALARM = 2; // Machine is in alarm state +export const TINYG_MACHINE_STATE_STOP = 3; // Machine has encountered program stop +export const TINYG_MACHINE_STATE_END = 4; // Machine has encountered program end +export const TINYG_MACHINE_STATE_RUN = 5; // Machine is running +export const TINYG_MACHINE_STATE_HOLD = 6; // Machine is holding +export const TINYG_MACHINE_STATE_PROBE = 7; // Machine is in probing operation +export const TINYG_MACHINE_STATE_CYCLE = 8; // Reserved for canned cycles (not used) +export const TINYG_MACHINE_STATE_HOMING = 9; // Machine is in a homing cycle +export const TINYG_MACHINE_STATE_JOG = 10; // Machine is in a jogging cycle +export const TINYG_MACHINE_STATE_INTERLOCK = 11; // Machine is in safety interlock hold +export const TINYG_MACHINE_STATE_SHUTDOWN = 12; // Machine is in shutdown state. Will not process commands +export const TINYG_MACHINE_STATE_PANIC = 13; // Machine is in panic state. Needs to be physically reset diff --git a/src/controller.js b/src/controller.js deleted file mode 100644 index c32eb7b..0000000 --- a/src/controller.js +++ /dev/null @@ -1,293 +0,0 @@ -import ensureArray from './ensure-array'; - -const noop = () => {}; - -class Controller { - io = null; - socket = null; - - listeners = { - // Socket.IO Events - // Fired upon a connection including a successful reconnection. - 'connect': [], - // Fired upon a connection error. - 'connect_error': [], - // Fired upon a connection timeout. - 'connect_timeout': [], - // Fired when an error occurs. - 'error': [], - // Fired upon a disconnection. - 'disconnect': [], - // Fired upon a successful reconnection. - 'reconnect': [], - // Fired upon an attempt to reconnect. - 'reconnect_attempt': [], - // Fired upon an attempt to reconnect. - 'reconnecting': [], - // Fired upon a reconnection attempt error. - 'reconnect_error': [], - // Fired when couldn't reconnect within reconnectionAttempts. - 'reconnect_failed': [], - - // System Events - 'startup': [], - 'config:change': [], - 'task:start': [], - 'task:finish': [], - 'task:error': [], - 'serialport:list': [], - 'serialport:change': [], - 'serialport:open': [], - 'serialport:close': [], - 'serialport:error': [], - 'serialport:read': [], - 'serialport:write': [], - 'gcode:load': [], - 'gcode:unload': [], - 'feeder:status': [], - 'sender:status': [], - 'workflow:state': [], - 'controller:settings': [], - 'controller:state': [], - 'message': [] - }; - - context = { - xmin: 0, - xmax: 0, - ymin: 0, - ymax: 0, - zmin: 0, - zmax: 0 - }; - - // User-defined baud rates and ports - baudrates = []; - ports = []; - - loadedControllers = []; - port = ''; - type = ''; - settings = {}; - state = {}; - workflow = { - state: 'idle' // running|paused|idle - }; - - // @param {object} io The socket.io-client module. - constructor(io) { - if (!io) { - throw new Error(`Expected the socket.io-client module, but got: ${io}`); - } - - this.io = io; - } - // Whether or not the client is connected. - // @return {boolean} Returns true if the client is connected, false otherwise. - get connected() { - return !!(this.socket && this.socket.connected); - } - // Establish a connection to the server. - // @param {string} host - // @param {object} options - // @param {function} next - connect(host = '', options = {}, next = noop) { - if (typeof next !== 'function') { - next = noop; - } - - this.socket && this.socket.destroy(); - this.socket = this.io.connect(host, options); - - Object.keys(this.listeners).forEach((eventName) => { - if (!this.socket) { - return; - } - - this.socket.on(eventName, (...args) => { - if (eventName === 'serialport:open') { - const { controllerType, port } = { ...args[0] }; - this.port = port; - this.type = controllerType; - } - if (eventName === 'serialport:close') { - this.port = ''; - this.type = ''; - this.state = {}; - this.settings = {}; - this.workflow.state = 'idle'; - } - if (eventName === 'workflow:state') { - this.workflow.state = args[0]; - } - if (eventName === 'controller:settings') { - this.type = args[0]; - this.settings = { ...args[1] }; - } - if (eventName === 'controller:state') { - this.type = args[0]; - this.state = { ...args[1] }; - } - - const listeners = ensureArray(this.listeners[eventName]); - listeners.forEach(listener => { - listener(...args); - }); - }); - }); - - this.socket.on('startup', (data) => { - const { loadedControllers, baudrates, ports } = { ...data }; - - this.loadedControllers = ensureArray(loadedControllers); - - // User-defined baud rates and ports - this.baudrates = ensureArray(baudrates); - this.ports = ensureArray(ports); - - if (next) { - next(null); - - // The callback can only be called once - next = null; - } - }); - } - // Disconnect from the server. - disconnect() { - this.socket && this.socket.destroy(); - this.socket = null; - } - // Adds the `listener` function to the end of the listeners array for the event named `eventName`. - // @param {string} eventName The name of the event. - // @param {function} listener The listener function. - addListener(eventName, listener) { - const listeners = this.listeners[eventName]; - if (!listeners || typeof listener !== 'function') { - return false; - } - listeners.push(listener); - return true; - } - // Removes the specified `listener` from the listener array for the event named `eventName`. - // @param {string} eventName The name of the event. - // @param {function} listener The listener function. - removeListener(eventName, listener) { - const listeners = this.listeners[eventName]; - if (!listeners || typeof listener !== 'function') { - return false; - } - listeners.splice(listeners.indexOf(listener), 1); - return true; - } - // Opens a connection to the given serial port. - // @param {string} port The path of the serial port you want to open. For example, `dev/tty.XXX` on Mac and Linux, or `COM1` on Windows. - // @param {object} [options] The options object. - // @param {string} [options.controllerType] One of: 'Grbl', 'Smoothe', 'TinyG'. Defaults to 'Grbl'. - // @param {number} [options.baudrate] Defaults to 115200. - // @param {function} [callback] Called after a connection is opened. - openPort(port, options, callback) { - if (typeof options !== 'object') { - options = {}; - callback = options; - } - if (typeof callback !== 'function') { - callback = noop; - } - this.socket && this.socket.emit('open', port, options, callback); - } - // Closes an open connection. - // @param {string} port The path of the serial port you want to close. For example, `dev/tty.XXX` on Mac and Linux, or `COM1` on Windows. - // @param {function} [callback] Called once a connection is closed. - closePort(port, callback) { - if (typeof callback !== 'function') { - callback = noop; - } - this.socket && this.socket.emit('close', port, callback); - } - // Retrieves a list of available serial ports with metadata. - // @param {function} [callback] Called once completed. - listPorts(callback) { - this.socket && this.socket.emit('list', callback); - } - // Executes a command on the server. - // @param {string} cmd The command string - // @example Example Usage - // - Load G-code - // controller.command('gcode:load', name, gcode, context /* optional */, callback) - // - Unload G-code - // controller.command('gcode:unload') - // - Start sending G-code - // controller.command('gcode:start') - // - Stop sending G-code - // controller.command('gcode:stop', { force: true }) - // - Pause - // controller.command('gcode:pause') - // - Resume - // controller.command('gcode:resume') - // - Feeder - // controller.command('feeder:feed') - // controller.command('feeder:start') - // controller.command('feeder:stop') - // controller.command('feeder:clear') - // - Feed Hold - // controller.command('feedhold') - // - Cycle Start - // controller.command('cyclestart') - // - Status Report - // controller.command('statusreport') - // - Homing - // controller.command('homing') - // - Sleep - // controller.command('sleep') - // - Unlock - // controller.command('unlock') - // - Reset - // controller.command('reset') - // - Feed Override - // controller.command('feedOverride') - // - Spindle Override - // controller.command('spindleOverride') - // - Rapid Override - // controller.command('rapidOverride') - // - Energize Motors - // controller.command('energizeMotors:on') - // controller.command('energizeMotors:off') - // - G-code - // controller.command('gcode', 'G0X0Y0', context /* optional */) - // - Load a macro - // controller.command('macro:load', '', context /* optional */, callback) - // - Run a macro - // controller.command('macro:run', '', context /* optional */, callback) - // - Load file from a watch directory - // controller.command('watchdir:load', '/path/to/file', callback) - command(cmd, ...args) { - const { port } = this; - if (!port) { - return; - } - this.socket && this.socket.emit.apply(this.socket, ['command', port, cmd].concat(args)); - } - // Writes data to the serial port. - // @param {string} data The data to write. - // @param {object} [context] The associated context information. - write(data, context) { - const { port } = this; - if (!port) { - return; - } - this.socket && this.socket.emit('write', port, data, context); - } - // Writes data and a newline character to the serial port. - // @param {string} data The data to write. - // @param {object} [context] The associated context information. - writeln(data, context) { - const { port } = this; - if (!port) { - return; - } - this.socket && this.socket.emit('writeln', port, data, context); - } -} - -export default Controller; diff --git a/src/index.js b/src/index.js index e641a4e..2dc8422 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ -import Controller from './controller'; +import Controller from './Controller'; module.exports = Controller; diff --git a/src/units.js b/src/units.js new file mode 100644 index 0000000..1e2de04 --- /dev/null +++ b/src/units.js @@ -0,0 +1,10 @@ +// from mm to in +const mm2in = (val = 0) => val / 25.4; + +// from in to mm +const in2mm = (val = 0) => val * 25.4; + +export { + mm2in, + in2mm +}; From 80650d9725effa03a36f6aa0074ac25bded9fcae Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Fri, 20 Oct 2017 16:05:53 +0800 Subject: [PATCH 02/16] Revert "Prepare for v2" This reverts commit 64276bd8817b92577b36d7e8fa051f067c3b3137. --- package.json | 6 +- src/Controller.js | 514 ---------------------------------------------- src/constants.js | 49 ----- src/controller.js | 293 ++++++++++++++++++++++++++ src/index.js | 2 +- src/units.js | 10 - 6 files changed, 296 insertions(+), 578 deletions(-) delete mode 100644 src/Controller.js delete mode 100644 src/constants.js create mode 100644 src/controller.js delete mode 100644 src/units.js diff --git a/package.json b/package.json index b65e40e..b200490 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cncjs-controller", - "version": "2.0.0", + "version": "1.3.0", "description": "A controller library for event-based communication between client and CNCjs server.", "main": "lib/index.js", "scripts": { @@ -33,9 +33,7 @@ "events", "client" ], - "dependencies": { - "lodash.mapvalues": "^4.6.0" - }, + "dependencies": {}, "devDependencies": { "babel-cli": "~6.24.1", "babel-core": "~6.24.1", diff --git a/src/Controller.js b/src/Controller.js deleted file mode 100644 index 1df29ca..0000000 --- a/src/Controller.js +++ /dev/null @@ -1,514 +0,0 @@ -import mapValues from 'lodash.mapvalues'; -import { in2mm } from './units'; -import ensureArray from './ensure-array'; -import { - // Units - IMPERIAL_UNITS, - METRIC_UNITS, - // Controller - GRBL, - SMOOTHIE, - TINYG -} from './constants'; - -const noop = () => {}; - -class Controller { - io = null; - socket = null; - - listeners = { - // Socket.IO Events - // Fired upon a connection including a successful reconnection. - 'connect': [], - // Fired upon a connection error. - 'connect_error': [], - // Fired upon a connection timeout. - 'connect_timeout': [], - // Fired when an error occurs. - 'error': [], - // Fired upon a disconnection. - 'disconnect': [], - // Fired upon a successful reconnection. - 'reconnect': [], - // Fired upon an attempt to reconnect. - 'reconnect_attempt': [], - // Fired upon an attempt to reconnect. - 'reconnecting': [], - // Fired upon a reconnection attempt error. - 'reconnect_error': [], - // Fired when couldn't reconnect within reconnectionAttempts. - 'reconnect_failed': [], - - // System Events - 'startup': [], - 'ports': [], - 'config:change': [], - 'task:start': [], - 'task:finish': [], - 'task:error': [], - 'controller:type': [], - 'controller:settings': [], - 'controller:state': [], - 'connection:open': [], - 'connection:close': [], - 'connection:change': [], - 'connection:error': [], - 'connection:read': [], - 'connection:write': [], - 'gcode:load': [], - 'gcode:unload': [], - 'feeder:status': [], - 'sender:status': [], - 'workflow:state': [], - 'message': [] - }; - - context = { - xmin: 0, - xmax: 0, - ymin: 0, - ymax: 0, - zmin: 0, - zmax: 0 - }; - - // User-defined baud rates - baudRates = []; - - // Available controllers - loadedControllers = []; - - // Controller - type = ''; // Grbl|Smoothie|TinyG - settings = {}; - state = {}; - - // Connection - connection = { - ident: '', - type: '', // serial|socket - settings: {} - }; - - workflow = { - state: 'idle' // running|paused|idle - }; - - // Whether the client is connected to the server. - // @return {boolean} Returns true if the client is connected to the server, false otherwise. - get connected() { - return !!(this.socket && this.socket.connected); - } - - // @param {object} io The socket.io-client module. - constructor(io) { - if (!io) { - throw new Error(`Expected the socket.io-client module, but got: ${io}`); - } - - this.io = io; - } - // Establish a connection to the server. - // @param {string} host - // @param {object} options - // @param {function} next - connect(host = '', options = {}, next = noop) { - if (typeof next !== 'function') { - next = noop; - } - - this.socket && this.socket.destroy(); - this.socket = this.io.connect(host, options); - - Object.keys(this.listeners).forEach((eventName) => { - if (!this.socket) { - return; - } - - this.socket.on(eventName, (...args) => { - if (eventName === 'controller:type') { - this.type = args[0]; - } - if (eventName === 'controller:settings') { - this.type = args[0]; - this.settings = { ...args[1] }; - } - if (eventName === 'controller:state') { - this.type = args[0]; - this.state = { ...args[1] }; - } - if (eventName === 'connection:open') { - const { ident, type, settings } = { ...args[0] }; - this.connection.ident = ident; - this.connection.type = type; - this.connection.settings = settings; - } - if (eventName === 'connection:close') { - this.type = ''; - this.settings = {}; - this.state = {}; - this.connection.ident = ''; - this.connection.type = ''; - this.connection.setting = {}; - this.workflow.state = 'idle'; - } - if (eventName === 'workflow:state') { - this.workflow.state = args[0]; - } - - const listeners = ensureArray(this.listeners[eventName]); - listeners.forEach(listener => { - listener(...args); - }); - }); - }); - - this.socket.on('startup', (data) => { - const { loadedControllers, baudRates } = { ...data }; - - this.loadedControllers = ensureArray(loadedControllers); - - // User-defined baud rates - this.baudRates = ensureArray(baudRates); - - if (next) { - next(null); - - // The callback can only be called once - next = null; - } - }); - } - // Disconnect from the server. - disconnect() { - this.socket && this.socket.destroy(); - this.socket = null; - } - // Adds the `listener` function to the end of the listeners array for the event named `eventName`. - // @param {string} eventName The name of the event. - // @param {function} listener The listener function. - addListener(eventName, listener) { - const listeners = this.listeners[eventName]; - if (!listeners || typeof listener !== 'function') { - return false; - } - listeners.push(listener); - return true; - } - // Removes the specified `listener` from the listener array for the event named `eventName`. - // @param {string} eventName The name of the event. - // @param {function} listener The listener function. - removeListener(eventName, listener) { - const listeners = this.listeners[eventName]; - if (!listeners || typeof listener !== 'function') { - return false; - } - listeners.splice(listeners.indexOf(listener), 1); - return true; - } - // Opens a connection. - // @param {string} controllerType One of: 'Grbl', 'Smoothe', 'TinyG'. Defaults to 'Grbl'. - // @param {string} connectionType One of: 'serial', 'socket'. Defaults to 'serial'. - // @param {object} options The options object. - // @param {string} options.path (serial only) The serial port referenced by the path. - // @param {number} [options.baudRate] (serial only) Defaults to 115200. - // @param {string} options.host (socket only) The host address. - // @param {number} [options.port] (socket only) The port number. Defaults to 23. () - // @param {function} [callback] Called after a connection is opened. - open(controllerType = 'Grbl', connectionType = 'serial', options, callback) { - if (typeof options !== 'object') { - options = {}; - callback = options; - } - if (typeof callback !== 'function') { - callback = noop; - } - if (!this.socket) { - return; - } - this.socket.emit('open', controllerType, connectionType, options, (err, ...args) => { - if (!err) { - this.connection.ident = args[0]; - } - - callback(err, ...args); - }); - } - // Closes an open connection. - // @param {function} [callback] Called once a connection is closed. - close(callback) { - if (typeof callback !== 'function') { - callback = noop; - } - if (!this.socket) { - return; - } - if (!this.connection.ident) { - return; - } - this.socket.emit('close', this.connection.ident, (err, ...args) => { - this.connection.ident = ''; - callback(err, ...args); - }); - } - // Executes a command on the server. - // @param {string} cmd The command string - // @example Example Usage - // - Load G-code - // controller.command('gcode:load', name, gcode, context /* optional */, callback) - // - Unload G-code - // controller.command('gcode:unload') - // - Start sending G-code - // controller.command('gcode:start') - // - Stop sending G-code - // controller.command('gcode:stop', { force: true }) - // - Pause - // controller.command('gcode:pause') - // - Resume - // controller.command('gcode:resume') - // - Feeder - // controller.command('feeder:feed') - // controller.command('feeder:start') - // controller.command('feeder:stop') - // - Feed Hold - // controller.command('feedhold') - // - Cycle Start - // controller.command('cyclestart') - // - Status Report - // controller.command('statusreport') - // - Homing - // controller.command('homing') - // - Sleep - // controller.command('sleep') - // - Unlock - // controller.command('unlock') - // - Reset - // controller.command('reset') - // - Feed Override - // controller.command('feedOverride') - // - Spindle Override - // controller.command('spindleOverride') - // - Rapid Override - // controller.command('rapidOverride') - // - Energize Motors - // controller.command('energizeMotors:on') - // controller.command('energizeMotors:off') - // - G-code - // controller.command('gcode', 'G0X0Y0', context /* optional */) - // - Load a macro - // controller.command('macro:load', '', context /* optional */, callback) - // - Run a macro - // controller.command('macro:run', '', context /* optional */, callback) - // - Load file from a watch directory - // controller.command('watchdir:load', '/path/to/file', callback) - command(cmd, ...args) { - if (!this.socket) { - return; - } - if (!this.connection.ident) { - return; - } - this.socket.emit('command', this.connection.ident, cmd, ...args); - } - // Writes data to the open connection. - // @param {string} data The data to write. - // @param {object} [context] The associated context information. - write(data, context) { - if (!this.socket) { - return; - } - if (!this.connection.ident) { - return; - } - this.socket.emit('write', this.connection.ident, data, context); - } - // Writes data and a newline character to the open connection. - // @param {string} data The data to write. - // @param {object} [context] The associated context information. - writeln(data, context) { - if (!this.socket) { - return; - } - if (!this.connection.ident) { - return; - } - this.socket.emit('writeln', this.connection.ident, data, context); - } - // Gets a list of available serial ports. - // @param {function} [callback] Called once completed. - getPorts(callback) { - if (!this.socket) { - return; - } - this.socket.emit('getPorts', callback); - } - // Gets the machine state. - // @return {string|number} The machine state. - getMachineState() { - if ([GRBL, SMOOTHIE, TINYG].indexOf(this.type) < 0) { - return ''; - } - - if (!this.connection.ident) { - return ''; - } - - let machineState; - - if (this.type === GRBL) { - machineState = this.state.machineState; - } else if (this.type === SMOOTHIE) { - machineState = this.state.machineState; - } else if (this.type === TINYG) { - machineState = this.state.machineState; - } - - return machineState || ''; - } - // Gets the machine position. - // @return {object} A position object which contains x, y, z, a, b, and c properties. - getMachinePosition() { - const defaultMachinePosition = { - x: '0.000', - y: '0.000', - z: '0.000', - a: '0.000', - b: '0.000', - c: '0.000' - }; - - // Grbl - if (this.type === GRBL) { - const { mpos } = this.state; - let { $13 = 0 } = { ...this.settings.settings }; - $13 = Number($13) || 0; - - // Machine position are reported in mm ($13=0) or inches ($13=1) - return mapValues({ - ...defaultMachinePosition, - ...mpos - }, (val) => { - return ($13 > 0) ? in2mm(val) : val; - }); - } - - // Smoothieware - if (this.type === SMOOTHIE) { - const { mpos, modal = {} } = this.state; - const units = { - 'G20': IMPERIAL_UNITS, - 'G21': METRIC_UNITS - }[modal.units]; - - // Machine position are reported in current units - return mapValues({ - ...defaultMachinePosition, - ...mpos - }, (val) => { - return (units === IMPERIAL_UNITS) ? in2mm(val) : val; - }); - } - - // TinyG - if (this.type === TINYG) { - const { mpos } = this.state; - - // https://github.com/synthetos/g2/wiki/Status-Reports - // Canonical machine position are always reported in millimeters with no offsets. - return { - ...defaultMachinePosition, - ...mpos - }; - } - - return defaultMachinePosition; - } - // Gets the work position. - // @return {object} A position object which contains x, y, z, a, b, and c properties. - getWorkPosition() { - const defaultWorkPosition = { - x: '0.000', - y: '0.000', - z: '0.000', - a: '0.000', - b: '0.000', - c: '0.000' - }; - - // Grbl - if (this.type === GRBL) { - const { wpos } = this.state; - let { $13 = 0 } = { ...this.settings.settings }; - $13 = Number($13) || 0; - - // Work position are reported in mm ($13=0) or inches ($13=1) - return mapValues({ - ...defaultWorkPosition, - ...wpos - }, val => { - return ($13 > 0) ? in2mm(val) : val; - }); - } - - // Smoothieware - if (this.type === SMOOTHIE) { - const { wpos, modal = {} } = this.state; - const units = { - 'G20': IMPERIAL_UNITS, - 'G21': METRIC_UNITS - }[modal.units]; - - // Work position are reported in current units - return mapValues({ - ...defaultWorkPosition, - ...wpos - }, (val) => { - return (units === IMPERIAL_UNITS) ? in2mm(val) : val; - }); - } - - // TinyG - if (this.type === TINYG) { - const { wpos, modal = {} } = this.state; - const units = { - 'G20': IMPERIAL_UNITS, - 'G21': METRIC_UNITS - }[modal.units]; - - // Work position are reported in current units, and also apply any offsets. - return mapValues({ - ...defaultWorkPosition, - ...wpos - }, (val) => { - return (units === IMPERIAL_UNITS) ? in2mm(val) : val; - }); - } - - return defaultWorkPosition; - } - // Gets work coordinate system. - // @return {string} Returns work coordinate system (G54-G59). - getWorkCoordinateSystem() { - const defaultWCS = 'G54'; - - if (this.type === GRBL) { - const { wcs } = { ...this.state.modal }; - return wcs || defaultWCS; - } - - if (this.type === SMOOTHIE) { - const { wcs } = { ...this.state.modal }; - return wcs || defaultWCS; - } - - if (this.type === TINYG) { - const { wcs } = { ...this.state.modal }; - return wcs || defaultWCS; - } - - return defaultWCS; - } -} - -export default Controller; diff --git a/src/constants.js b/src/constants.js deleted file mode 100644 index 562ab76..0000000 --- a/src/constants.js +++ /dev/null @@ -1,49 +0,0 @@ -// Metric and Imperial units -export const IMPERIAL_UNITS = 'in'; -export const METRIC_UNITS = 'mm'; - -// Controller -export const GRBL = 'Grbl'; -export const SMOOTHIE = 'Smoothie'; -export const TINYG = 'TinyG'; - -// Workflow State -export const WORKFLOW_STATE_IDLE = 'idle'; -export const WORKFLOW_STATE_PAUSED = 'paused'; -export const WORKFLOW_STATE_RUNNING = 'running'; - -// Grbl Machine State -export const GRBL_MACHINE_STATE_IDLE = 'Idle'; -export const GRBL_MACHINE_STATE_RUN = 'Run'; -export const GRBL_MACHINE_STATE_HOLD = 'Hold'; -export const GRBL_MACHINE_STATE_DOOR = 'Door'; -export const GRBL_MACHINE_STATE_HOME = 'Home'; -export const GRBL_MACHINE_STATE_SLEEP = 'Sleep'; -export const GRBL_MACHINE_STATE_ALARM = 'Alarm'; -export const GRBL_MACHINE_STATE_CHECK = 'Check'; - -// Smoothie Machine State -export const SMOOTHIE_MACHINE_STATE_IDLE = 'Idle'; -export const SMOOTHIE_MACHINE_STATE_RUN = 'Run'; -export const SMOOTHIE_MACHINE_STATE_HOLD = 'Hold'; -export const SMOOTHIE_MACHINE_STATE_DOOR = 'Door'; -export const SMOOTHIE_MACHINE_STATE_HOME = 'Home'; -export const SMOOTHIE_MACHINE_STATE_ALARM = 'Alarm'; -export const SMOOTHIE_MACHINE_STATE_CHECK = 'Check'; - -// TinyG Machine State -// https://github.com/synthetos/g2/wiki/Status-Reports#stat-values -export const TINYG_MACHINE_STATE_INITIALIZING = 0; // Machine is initializing -export const TINYG_MACHINE_STATE_READY = 1; // Machine is ready for use -export const TINYG_MACHINE_STATE_ALARM = 2; // Machine is in alarm state -export const TINYG_MACHINE_STATE_STOP = 3; // Machine has encountered program stop -export const TINYG_MACHINE_STATE_END = 4; // Machine has encountered program end -export const TINYG_MACHINE_STATE_RUN = 5; // Machine is running -export const TINYG_MACHINE_STATE_HOLD = 6; // Machine is holding -export const TINYG_MACHINE_STATE_PROBE = 7; // Machine is in probing operation -export const TINYG_MACHINE_STATE_CYCLE = 8; // Reserved for canned cycles (not used) -export const TINYG_MACHINE_STATE_HOMING = 9; // Machine is in a homing cycle -export const TINYG_MACHINE_STATE_JOG = 10; // Machine is in a jogging cycle -export const TINYG_MACHINE_STATE_INTERLOCK = 11; // Machine is in safety interlock hold -export const TINYG_MACHINE_STATE_SHUTDOWN = 12; // Machine is in shutdown state. Will not process commands -export const TINYG_MACHINE_STATE_PANIC = 13; // Machine is in panic state. Needs to be physically reset diff --git a/src/controller.js b/src/controller.js new file mode 100644 index 0000000..c32eb7b --- /dev/null +++ b/src/controller.js @@ -0,0 +1,293 @@ +import ensureArray from './ensure-array'; + +const noop = () => {}; + +class Controller { + io = null; + socket = null; + + listeners = { + // Socket.IO Events + // Fired upon a connection including a successful reconnection. + 'connect': [], + // Fired upon a connection error. + 'connect_error': [], + // Fired upon a connection timeout. + 'connect_timeout': [], + // Fired when an error occurs. + 'error': [], + // Fired upon a disconnection. + 'disconnect': [], + // Fired upon a successful reconnection. + 'reconnect': [], + // Fired upon an attempt to reconnect. + 'reconnect_attempt': [], + // Fired upon an attempt to reconnect. + 'reconnecting': [], + // Fired upon a reconnection attempt error. + 'reconnect_error': [], + // Fired when couldn't reconnect within reconnectionAttempts. + 'reconnect_failed': [], + + // System Events + 'startup': [], + 'config:change': [], + 'task:start': [], + 'task:finish': [], + 'task:error': [], + 'serialport:list': [], + 'serialport:change': [], + 'serialport:open': [], + 'serialport:close': [], + 'serialport:error': [], + 'serialport:read': [], + 'serialport:write': [], + 'gcode:load': [], + 'gcode:unload': [], + 'feeder:status': [], + 'sender:status': [], + 'workflow:state': [], + 'controller:settings': [], + 'controller:state': [], + 'message': [] + }; + + context = { + xmin: 0, + xmax: 0, + ymin: 0, + ymax: 0, + zmin: 0, + zmax: 0 + }; + + // User-defined baud rates and ports + baudrates = []; + ports = []; + + loadedControllers = []; + port = ''; + type = ''; + settings = {}; + state = {}; + workflow = { + state: 'idle' // running|paused|idle + }; + + // @param {object} io The socket.io-client module. + constructor(io) { + if (!io) { + throw new Error(`Expected the socket.io-client module, but got: ${io}`); + } + + this.io = io; + } + // Whether or not the client is connected. + // @return {boolean} Returns true if the client is connected, false otherwise. + get connected() { + return !!(this.socket && this.socket.connected); + } + // Establish a connection to the server. + // @param {string} host + // @param {object} options + // @param {function} next + connect(host = '', options = {}, next = noop) { + if (typeof next !== 'function') { + next = noop; + } + + this.socket && this.socket.destroy(); + this.socket = this.io.connect(host, options); + + Object.keys(this.listeners).forEach((eventName) => { + if (!this.socket) { + return; + } + + this.socket.on(eventName, (...args) => { + if (eventName === 'serialport:open') { + const { controllerType, port } = { ...args[0] }; + this.port = port; + this.type = controllerType; + } + if (eventName === 'serialport:close') { + this.port = ''; + this.type = ''; + this.state = {}; + this.settings = {}; + this.workflow.state = 'idle'; + } + if (eventName === 'workflow:state') { + this.workflow.state = args[0]; + } + if (eventName === 'controller:settings') { + this.type = args[0]; + this.settings = { ...args[1] }; + } + if (eventName === 'controller:state') { + this.type = args[0]; + this.state = { ...args[1] }; + } + + const listeners = ensureArray(this.listeners[eventName]); + listeners.forEach(listener => { + listener(...args); + }); + }); + }); + + this.socket.on('startup', (data) => { + const { loadedControllers, baudrates, ports } = { ...data }; + + this.loadedControllers = ensureArray(loadedControllers); + + // User-defined baud rates and ports + this.baudrates = ensureArray(baudrates); + this.ports = ensureArray(ports); + + if (next) { + next(null); + + // The callback can only be called once + next = null; + } + }); + } + // Disconnect from the server. + disconnect() { + this.socket && this.socket.destroy(); + this.socket = null; + } + // Adds the `listener` function to the end of the listeners array for the event named `eventName`. + // @param {string} eventName The name of the event. + // @param {function} listener The listener function. + addListener(eventName, listener) { + const listeners = this.listeners[eventName]; + if (!listeners || typeof listener !== 'function') { + return false; + } + listeners.push(listener); + return true; + } + // Removes the specified `listener` from the listener array for the event named `eventName`. + // @param {string} eventName The name of the event. + // @param {function} listener The listener function. + removeListener(eventName, listener) { + const listeners = this.listeners[eventName]; + if (!listeners || typeof listener !== 'function') { + return false; + } + listeners.splice(listeners.indexOf(listener), 1); + return true; + } + // Opens a connection to the given serial port. + // @param {string} port The path of the serial port you want to open. For example, `dev/tty.XXX` on Mac and Linux, or `COM1` on Windows. + // @param {object} [options] The options object. + // @param {string} [options.controllerType] One of: 'Grbl', 'Smoothe', 'TinyG'. Defaults to 'Grbl'. + // @param {number} [options.baudrate] Defaults to 115200. + // @param {function} [callback] Called after a connection is opened. + openPort(port, options, callback) { + if (typeof options !== 'object') { + options = {}; + callback = options; + } + if (typeof callback !== 'function') { + callback = noop; + } + this.socket && this.socket.emit('open', port, options, callback); + } + // Closes an open connection. + // @param {string} port The path of the serial port you want to close. For example, `dev/tty.XXX` on Mac and Linux, or `COM1` on Windows. + // @param {function} [callback] Called once a connection is closed. + closePort(port, callback) { + if (typeof callback !== 'function') { + callback = noop; + } + this.socket && this.socket.emit('close', port, callback); + } + // Retrieves a list of available serial ports with metadata. + // @param {function} [callback] Called once completed. + listPorts(callback) { + this.socket && this.socket.emit('list', callback); + } + // Executes a command on the server. + // @param {string} cmd The command string + // @example Example Usage + // - Load G-code + // controller.command('gcode:load', name, gcode, context /* optional */, callback) + // - Unload G-code + // controller.command('gcode:unload') + // - Start sending G-code + // controller.command('gcode:start') + // - Stop sending G-code + // controller.command('gcode:stop', { force: true }) + // - Pause + // controller.command('gcode:pause') + // - Resume + // controller.command('gcode:resume') + // - Feeder + // controller.command('feeder:feed') + // controller.command('feeder:start') + // controller.command('feeder:stop') + // controller.command('feeder:clear') + // - Feed Hold + // controller.command('feedhold') + // - Cycle Start + // controller.command('cyclestart') + // - Status Report + // controller.command('statusreport') + // - Homing + // controller.command('homing') + // - Sleep + // controller.command('sleep') + // - Unlock + // controller.command('unlock') + // - Reset + // controller.command('reset') + // - Feed Override + // controller.command('feedOverride') + // - Spindle Override + // controller.command('spindleOverride') + // - Rapid Override + // controller.command('rapidOverride') + // - Energize Motors + // controller.command('energizeMotors:on') + // controller.command('energizeMotors:off') + // - G-code + // controller.command('gcode', 'G0X0Y0', context /* optional */) + // - Load a macro + // controller.command('macro:load', '', context /* optional */, callback) + // - Run a macro + // controller.command('macro:run', '', context /* optional */, callback) + // - Load file from a watch directory + // controller.command('watchdir:load', '/path/to/file', callback) + command(cmd, ...args) { + const { port } = this; + if (!port) { + return; + } + this.socket && this.socket.emit.apply(this.socket, ['command', port, cmd].concat(args)); + } + // Writes data to the serial port. + // @param {string} data The data to write. + // @param {object} [context] The associated context information. + write(data, context) { + const { port } = this; + if (!port) { + return; + } + this.socket && this.socket.emit('write', port, data, context); + } + // Writes data and a newline character to the serial port. + // @param {string} data The data to write. + // @param {object} [context] The associated context information. + writeln(data, context) { + const { port } = this; + if (!port) { + return; + } + this.socket && this.socket.emit('writeln', port, data, context); + } +} + +export default Controller; diff --git a/src/index.js b/src/index.js index 2dc8422..e641a4e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ -import Controller from './Controller'; +import Controller from './controller'; module.exports = Controller; diff --git a/src/units.js b/src/units.js deleted file mode 100644 index 1e2de04..0000000 --- a/src/units.js +++ /dev/null @@ -1,10 +0,0 @@ -// from mm to in -const mm2in = (val = 0) => val / 25.4; - -// from in to mm -const in2mm = (val = 0) => val * 25.4; - -export { - mm2in, - in2mm -}; From 55429e5f07ae5e79c1d47e808a8d2f7c6d4cc175 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 21 Oct 2017 00:40:47 +0800 Subject: [PATCH 03/16] Prepare for v2 --- README.md | 175 ++++++++++++--- package.json | 2 +- src/Controller.js | 526 ++++++++++++++++++++++++++++++++++++++++++++++ src/constants.js | 49 +++++ src/controller.js | 293 -------------------------- src/index.js | 2 +- src/mapvalues.js | 12 ++ src/noop.js | 3 + src/units.js | 10 + 9 files changed, 751 insertions(+), 321 deletions(-) create mode 100644 src/Controller.js create mode 100644 src/constants.js delete mode 100644 src/controller.js create mode 100644 src/mapvalues.js create mode 100644 src/noop.js create mode 100644 src/units.js diff --git a/README.md b/README.md index 420554e..713eae5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Installation ```sh -npm install --save cncjs-controller +npm install --save cncjs-controller@latest npm install --save socket.io-client@1.7 # socket.io-client 1.7 is recommended ``` @@ -18,19 +18,22 @@ import io from 'socket.io-client'; import Controller from 'cncjs-controller'; const controller = new Controller(io); -const host = ''; // e.g. http://127.0.0.1:8000 -const token = ''; +const host = ''; // or 'http://127.0.0.1:8000' +const token = '...'; // Authorization token const options = { query: 'token=' + token }; controller.connect(host, options, () => { - const port = '/dev/cu.wchusbserialfa130'; - - controller.openPort(port, { - controllerType: 'Grbl', // Grbl|Smoothie|TinyG - baudrate: 115200 - }, (err) => { + const controllerType = 'Grbl'; // Grbl|Smoothie|TinyG + const connectionType = 'serial'; // serial|socket + const serialOptions = { + path: '/dev/cu.wchusbserialfa130', + baudRate: 115200 + }; + + // Open a serial connection + controller.open(controllerType, connectionType, serialOptions, (err) => { if (err) { console.error(err); return; @@ -41,37 +44,157 @@ controller.connect(host, options, () => { // Disconnect after 60 seconds setTimeout(() => { - // Close port - controller.closePort(); - - // Close connection - controller.disconnect(); + controller.close((err) => { + controller.disconnect(); + }); }, 60 * 1000); }); -controller.addListener('serialport:open', (options) => { - const { - port, - baudrate, - controllerType - } = options; - console.log(`Connected to the port "${port}" with a baud rate of ${baudrate}.`, { port, baudrate }); +controller.addListener('connection:open', (options) => { + const { ident, type, settings } = options; + + if (type === 'serial') { + const { path, baudRate } = { ...settings }; + console.log(`Connected to ${path} with a baud rate of ${baudRate}`); + } else if (type === 'socket') { + const { host, port } = { ...settings }; + console.log(`Connected to ${host}:${port}'); + } }); -controller.addListener('serialport:close', (options) => { - const { port } = options; - console.log(`The port "${port}" is disconnected.`); +controller.addListener('connection:close', (options) => { + const { type, settings } = options; + + console.log(`Connection closed: type=${type}, settings=${JSON.stringify(settings)}`); }); -controller.addListener('serialport:write', (data, context) => { +controller.addListener('connection:write', (data, context) => { console.log('>', data); }); -controller.addListener('serialport:read', (data) => { +controller.addListener('connection:read', (data) => { console.log('<', data); }); ``` +## API + +### Properties + +Name | Type | Default | Description +:--- | :--- | :------ | :---------- +baudRates | array | | A list of user-defined baud rates. +availableControllers | array | | A list of available controllers. +type | string | | The controller type. +settings | object | | The controller settings. +state | object | | The controller state. +connection | object | | The connection object. +connection.ident | string | | The connection identifier. +connection.type | string | | The connection type. +connection.settings | object | | The connection settings. +workflow | object | | The workflow object. +workflow.state | string | | The workflow state. +connected | boolean | | Whether the client is connected to the server. + +### Methods + +#### connect(host = '', options, [callback]) +Establish a connection to the server. + +##### Arguments +1. host (string): +2. options (object): +3. [callback] (function): The callback function. + +#### disconnect([callback]) +Disconnect from the server. + +##### Arguments +1. [callback] (function): The callback function. + +#### addListener(eventName, listener) +Adds the `listener` function to the end of the listeners array for the event named `eventName`. + +##### Arguments +1. eventName (string): The name of the event. +2. listener (function): The listener function. + +#### removeListener(eventName, listener) +Removes the specified `listener` from the listener array for the event named `eventName`. + +##### Arguments +1. eventName (string): The name of the event. +2. listener (function): The listener function. + +#### open(controllerType, connectionType, options, [callback]) +Opens a connection. + +##### Arguments +1. controllerType (string): One of: 'Grbl', 'Smoothe', 'TinyG'. +2. connectionType (string): One of: 'serial', 'socket'. +3. options (object): The options object. +4. options.path (string): `serial` The serial port referenced by the path. +5. [options.baudRate=115200] (string): `serial` The baud rate. +6. options.host (string): `socket` The host address to connect. +7. [options.port=23] (string): `socket` The port number. +8. [callback] (string): Called after a connection is opened. + +#### close([callback]) +Closes an open connection. + +##### Arguments +1. [callback] (string): Called once a connection is closed. + +#### command(cmd, ...args) +Executes a command on the server. + +##### Arguments +1. cmd (string): The command to execute. + +#### write(data, [context) +Writes data to the open connection. + +##### Arguments +1. data (string): The data to write. +2. [context] (object): The associated context information. + +#### writeln(data, [context]) +Writes data and a newline character to the open connection. + +##### Arguments +1. data (string): The data to write. +2. [context] (object): The associated context information. + +#### getPorts([callback]) +Gets a list of available serial ports. + +##### Arguments +1. [callback] (object): Called once completed. + +#### getMachineState() +Gets the machine state. + +##### Return +(string|number): Returns the machine state. + +#### getMachinePosition() +Gets the machine position. + +##### Return +(object): Returns a position object which contains x, y, z, a, b, and c properties. + +#### getWorkPosition() +Gets the work position. + +##### Return +(object): Returns a position object which contains x, y, z, a, b, and c properties. + +#### getModalState() +Gets modal state. + +##### Return +(object): Returns the modal state. + ## License MIT diff --git a/package.json b/package.json index b200490..a014162 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cncjs-controller", - "version": "1.3.0", + "version": "2.0.0-alpha", "description": "A controller library for event-based communication between client and CNCjs server.", "main": "lib/index.js", "scripts": { diff --git a/src/Controller.js b/src/Controller.js new file mode 100644 index 0000000..0a11f5f --- /dev/null +++ b/src/Controller.js @@ -0,0 +1,526 @@ +import ensureArray from './ensure-array'; +import mapValues from './mapvalues'; +import noop from './noop'; +import { in2mm } from './units'; +import { + // Units + IMPERIAL_UNITS, + METRIC_UNITS, + // Controller + GRBL, + SMOOTHIE, + TINYG +} from './constants'; + +class Controller { + io = null; + socket = null; + + listeners = { + // Socket.IO Events + // Fired upon a connection including a successful reconnection. + 'connect': [], + // Fired upon a connection error. + 'connect_error': [], + // Fired upon a connection timeout. + 'connect_timeout': [], + // Fired when an error occurs. + 'error': [], + // Fired upon a disconnection. + 'disconnect': [], + // Fired upon a successful reconnection. + 'reconnect': [], + // Fired upon an attempt to reconnect. + 'reconnect_attempt': [], + // Fired upon an attempt to reconnect. + 'reconnecting': [], + // Fired upon a reconnection attempt error. + 'reconnect_error': [], + // Fired when couldn't reconnect within reconnectionAttempts. + 'reconnect_failed': [], + + // System Events + 'startup': [], + 'ports': [], + 'config:change': [], + 'task:start': [], + 'task:finish': [], + 'task:error': [], + 'controller:type': [], + 'controller:settings': [], + 'controller:state': [], + 'connection:open': [], + 'connection:close': [], + 'connection:change': [], + 'connection:error': [], + 'connection:read': [], + 'connection:write': [], + 'feeder:status': [], + 'sender:status': [], + 'sender:load': [], + 'sender:unload': [], + 'workflow:state': [], + 'message': [] + }; + + context = { + xmin: 0, + xmax: 0, + ymin: 0, + ymax: 0, + zmin: 0, + zmax: 0 + }; + + // User-defined baud rates + baudRates = []; + + // Available controllers + availableControllers = []; + + // Controller + type = ''; // Grbl|Smoothie|TinyG + settings = {}; + state = {}; + + // Connection + connection = { + ident: '', + type: '', // serial|socket + settings: {} + }; + + workflow = { + state: 'idle' // running|paused|idle + }; + + // Whether the client is connected to the server. + // @return {boolean} Returns true if the client is connected to the server, false otherwise. + get connected() { + return !!(this.socket && this.socket.connected); + } + + // @param {object} io The socket.io-client module. + constructor(io) { + if (!io) { + throw new Error(`Expected the socket.io-client module, but got: ${io}`); + } + + this.io = io; + } + // Establish a connection to the server. + // @param {string} host + // @param {object} options + // @param {function} callback + connect(host = '', options = {}, callback = noop) { + if (typeof callback !== 'function') { + callback = noop; + } + + this.socket && this.socket.destroy(); + this.socket = this.io.connect(host, options); + + Object.keys(this.listeners).forEach((eventName) => { + if (!this.socket) { + return; + } + + this.socket.on(eventName, (...args) => { + if (eventName === 'controller:type') { + this.type = args[0]; + } + if (eventName === 'controller:settings') { + this.type = args[0]; + this.settings = { ...args[1] }; + } + if (eventName === 'controller:state') { + this.type = args[0]; + this.state = { ...args[1] }; + } + if (eventName === 'connection:open') { + const { ident, type, settings } = { ...args[0] }; + this.connection.ident = ident; + this.connection.type = type; + this.connection.settings = settings; + } + if (eventName === 'connection:close') { + this.type = ''; + this.settings = {}; + this.state = {}; + this.connection.ident = ''; + this.connection.type = ''; + this.connection.setting = {}; + this.workflow.state = 'idle'; + } + if (eventName === 'workflow:state') { + this.workflow.state = args[0]; + } + + const listeners = ensureArray(this.listeners[eventName]); + listeners.forEach(listener => { + listener(...args); + }); + }); + }); + + this.socket.on('startup', (data) => { + const { availableControllers, baudRates } = { ...data }; + + this.availableControllers = ensureArray(availableControllers); + + // User-defined baud rates + this.baudRates = ensureArray(baudRates); + + if (callback) { + callback(null); + + // The callback can only be called once + callback = null; + } + }); + } + // Disconnect from the server. + disconnect() { + this.socket && this.socket.destroy(); + this.socket = null; + } + // Adds the `listener` function to the end of the listeners array for the event named `eventName`. + // @param {string} eventName The name of the event. + // @param {function} listener The listener function. + addListener(eventName, listener) { + const listeners = this.listeners[eventName]; + if (!listeners || typeof listener !== 'function') { + return false; + } + listeners.push(listener); + return true; + } + // Removes the specified `listener` from the listener array for the event named `eventName`. + // @param {string} eventName The name of the event. + // @param {function} listener The listener function. + removeListener(eventName, listener) { + const listeners = this.listeners[eventName]; + if (!listeners || typeof listener !== 'function') { + return false; + } + listeners.splice(listeners.indexOf(listener), 1); + return true; + } + // Opens a connection. + // @param {string} controllerType One of: 'Grbl', 'Smoothe', 'TinyG'. + // @param {string} connectionType One of: 'serial', 'socket'. + // @param {object} options The options object. + // @param {string} options.path `serial` The serial port referenced by the path. + // @param {number} [options.baudRate=115200] `serial` The baud rate. + // @param {string} options.host `socket` The host address to connect. + // @param {number} [options.port=23] `socket` The port number. + // @param {function} [callback] Called after a connection is opened. + open(controllerType, connectionType, options, callback) { + if (typeof options !== 'object') { + options = {}; + callback = options; + } + if (typeof callback !== 'function') { + callback = noop; + } + if (!this.socket) { + return; + } + this.socket.emit('open', controllerType, connectionType, options, (err, ...args) => { + if (!err) { + this.connection.ident = args[0]; + } + + callback(err, ...args); + }); + } + // Closes an open connection. + // @param {function} [callback] Called once a connection is closed. + close(callback) { + if (typeof callback !== 'function') { + callback = noop; + } + if (!this.socket) { + return; + } + if (!this.connection.ident) { + return; + } + this.socket.emit('close', this.connection.ident, (err, ...args) => { + this.connection.ident = ''; + callback(err, ...args); + }); + } + // Executes a command on the server. + // @param {string} cmd The command to execute. + // @example Example Usage + // - Load G-code + // controller.command('sender:load', name, gcode, [context], [callback]) + // - Unload G-code + // controller.command('sender:unload') + // - Start sending G-code + // controller.command('sender:start') + // - Stop sending G-code + // controller.command('sender:stop', { force: true }) + // - Pause + // controller.command('sender:pause') + // - Resume + // controller.command('sender:resume') + // - Feeder + // controller.command('feeder:start') + // controller.command('feeder:stop') + // - Feed Hold + // controller.command('feedhold') + // - Cycle Start + // controller.command('cyclestart') + // - Homing + // controller.command('homing') + // - Sleep + // controller.command('sleep') + // - Unlock + // controller.command('unlock') + // - Reset + // controller.command('reset') + // - Feed Override + // controller.command('override:feed') + // - Spindle Override + // controller.command('override:spindle') + // - Rapid Override + // controller.command('override:rapid') + // - Laser Test + // controller.command('lasertest', [power=0], [duration=0], [maxS=1000]) + // - G-code + // controller.command('gcode', gcode, [context]) + // - Load a macro + // controller.command('macro:load', id, [context], [callback]) + // - Run a macro + // controller.command('macro:run', id, [context], [callback]) + // - Load file from a watch directory + // controller.command('watchdir:load', '/path/to/file', callback) + command(cmd, ...args) { + if (!this.socket) { + return; + } + if (!this.connection.ident) { + return; + } + this.socket.emit('command', this.connection.ident, cmd, ...args); + } + // Writes data to the open connection. + // @param {string} data The data to write. + // @param {object} [context] The associated context information. + write(data, context) { + if (!this.socket) { + return; + } + if (!this.connection.ident) { + return; + } + this.socket.emit('write', this.connection.ident, data, context); + } + // Writes data and a newline character to the open connection. + // @param {string} data The data to write. + // @param {object} [context] The associated context information. + writeln(data, context) { + if (!this.socket) { + return; + } + if (!this.connection.ident) { + return; + } + this.socket.emit('writeln', this.connection.ident, data, context); + } + // Gets a list of available serial ports. + // @param {function} [callback] Called once completed. + getPorts(callback) { + if (!this.socket) { + return; + } + this.socket.emit('getPorts', callback); + } + // Gets the machine state. + // @return {string|number} Returns the machine state. + getMachineState() { + if ([GRBL, SMOOTHIE, TINYG].indexOf(this.type) < 0) { + return ''; + } + + if (!this.connection.ident) { + return ''; + } + + let machineState; + + if (this.type === GRBL) { + machineState = this.state.machineState; + } else if (this.type === SMOOTHIE) { + machineState = this.state.machineState; + } else if (this.type === TINYG) { + machineState = this.state.machineState; + } + + return machineState || ''; + } + // Gets the machine position. + // @return {object} Returns a position object which contains x, y, z, a, b, and c properties. + getMachinePosition() { + const defaultMachinePosition = { + x: '0.000', + y: '0.000', + z: '0.000', + a: '0.000', + b: '0.000', + c: '0.000' + }; + + // Grbl + if (this.type === GRBL) { + const { mpos } = this.state; + let { $13 = 0 } = { ...this.settings.settings }; + $13 = Number($13) || 0; + + // Machine position are reported in mm ($13=0) or inches ($13=1) + return mapValues({ + ...defaultMachinePosition, + ...mpos + }, (val) => { + return ($13 > 0) ? in2mm(val) : val; + }); + } + + // Smoothieware + if (this.type === SMOOTHIE) { + const { mpos, modal = {} } = this.state; + const units = { + 'G20': IMPERIAL_UNITS, + 'G21': METRIC_UNITS + }[modal.units]; + + // Machine position are reported in current units + return mapValues({ + ...defaultMachinePosition, + ...mpos + }, (val) => { + return (units === IMPERIAL_UNITS) ? in2mm(val) : val; + }); + } + + // TinyG + if (this.type === TINYG) { + const { mpos } = this.state; + + // https://github.com/synthetos/g2/wiki/Status-Reports + // Canonical machine position are always reported in millimeters with no offsets. + return { + ...defaultMachinePosition, + ...mpos + }; + } + + return defaultMachinePosition; + } + // Gets the work position. + // @return {object} Returns a position object which contains x, y, z, a, b, and c properties. + getWorkPosition() { + const defaultWorkPosition = { + x: '0.000', + y: '0.000', + z: '0.000', + a: '0.000', + b: '0.000', + c: '0.000' + }; + + // Grbl + if (this.type === GRBL) { + const { wpos } = this.state; + let { $13 = 0 } = { ...this.settings.settings }; + $13 = Number($13) || 0; + + // Work position are reported in mm ($13=0) or inches ($13=1) + return mapValues({ + ...defaultWorkPosition, + ...wpos + }, val => { + return ($13 > 0) ? in2mm(val) : val; + }); + } + + // Smoothieware + if (this.type === SMOOTHIE) { + const { wpos, modal = {} } = this.state; + const units = { + 'G20': IMPERIAL_UNITS, + 'G21': METRIC_UNITS + }[modal.units]; + + // Work position are reported in current units + return mapValues({ + ...defaultWorkPosition, + ...wpos + }, (val) => { + return (units === IMPERIAL_UNITS) ? in2mm(val) : val; + }); + } + + // TinyG + if (this.type === TINYG) { + const { wpos, modal = {} } = this.state; + const units = { + 'G20': IMPERIAL_UNITS, + 'G21': METRIC_UNITS + }[modal.units]; + + // Work position are reported in current units, and also apply any offsets. + return mapValues({ + ...defaultWorkPosition, + ...wpos + }, (val) => { + return (units === IMPERIAL_UNITS) ? in2mm(val) : val; + }); + } + + return defaultWorkPosition; + } + // Gets modal state. + // @return {object} Returns the modal state. + getModalState() { + const defaultModalState = { + motion: '', // G0, G1, G2, G3, G38.2, G38.3, G38.4, G38.5, G80 + plane: '', // G17: xy-plane, G18: xz-plane, G19: yz-plane + units: '', // G20: Inches, G21: Millimeters + wcs: '', // G54, G55, G56, G57, G58, G59 + path: '', // G61: Exact path mode, G61.1: Exact stop mode, G64: Continuous mode + distance: '', // G90: Absolute, G91: Relative + feedrate: '', // G93: Inverse time mode, G94: Units per minute + program: '', // M0, M1, M2, M30 + spindle: '', // M3: Spindle (cw), M4: Spindle (ccw), M5: Spindle off + coolant: '' // M7: Mist coolant, M8: Flood coolant, M9: Coolant off, [M7,M8]: Both on + }; + + if (this.type === GRBL) { + return { + ...defaultModalState, + ...this.state.modal + }; + } + + if (this.type === SMOOTHIE) { + return { + ...defaultModalState, + ...this.state.modal + }; + } + + if (this.type === TINYG) { + return { + ...defaultModalState, + ...this.state.modal + }; + } + + return defaultModalState; + } +} + +export default Controller; diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..562ab76 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,49 @@ +// Metric and Imperial units +export const IMPERIAL_UNITS = 'in'; +export const METRIC_UNITS = 'mm'; + +// Controller +export const GRBL = 'Grbl'; +export const SMOOTHIE = 'Smoothie'; +export const TINYG = 'TinyG'; + +// Workflow State +export const WORKFLOW_STATE_IDLE = 'idle'; +export const WORKFLOW_STATE_PAUSED = 'paused'; +export const WORKFLOW_STATE_RUNNING = 'running'; + +// Grbl Machine State +export const GRBL_MACHINE_STATE_IDLE = 'Idle'; +export const GRBL_MACHINE_STATE_RUN = 'Run'; +export const GRBL_MACHINE_STATE_HOLD = 'Hold'; +export const GRBL_MACHINE_STATE_DOOR = 'Door'; +export const GRBL_MACHINE_STATE_HOME = 'Home'; +export const GRBL_MACHINE_STATE_SLEEP = 'Sleep'; +export const GRBL_MACHINE_STATE_ALARM = 'Alarm'; +export const GRBL_MACHINE_STATE_CHECK = 'Check'; + +// Smoothie Machine State +export const SMOOTHIE_MACHINE_STATE_IDLE = 'Idle'; +export const SMOOTHIE_MACHINE_STATE_RUN = 'Run'; +export const SMOOTHIE_MACHINE_STATE_HOLD = 'Hold'; +export const SMOOTHIE_MACHINE_STATE_DOOR = 'Door'; +export const SMOOTHIE_MACHINE_STATE_HOME = 'Home'; +export const SMOOTHIE_MACHINE_STATE_ALARM = 'Alarm'; +export const SMOOTHIE_MACHINE_STATE_CHECK = 'Check'; + +// TinyG Machine State +// https://github.com/synthetos/g2/wiki/Status-Reports#stat-values +export const TINYG_MACHINE_STATE_INITIALIZING = 0; // Machine is initializing +export const TINYG_MACHINE_STATE_READY = 1; // Machine is ready for use +export const TINYG_MACHINE_STATE_ALARM = 2; // Machine is in alarm state +export const TINYG_MACHINE_STATE_STOP = 3; // Machine has encountered program stop +export const TINYG_MACHINE_STATE_END = 4; // Machine has encountered program end +export const TINYG_MACHINE_STATE_RUN = 5; // Machine is running +export const TINYG_MACHINE_STATE_HOLD = 6; // Machine is holding +export const TINYG_MACHINE_STATE_PROBE = 7; // Machine is in probing operation +export const TINYG_MACHINE_STATE_CYCLE = 8; // Reserved for canned cycles (not used) +export const TINYG_MACHINE_STATE_HOMING = 9; // Machine is in a homing cycle +export const TINYG_MACHINE_STATE_JOG = 10; // Machine is in a jogging cycle +export const TINYG_MACHINE_STATE_INTERLOCK = 11; // Machine is in safety interlock hold +export const TINYG_MACHINE_STATE_SHUTDOWN = 12; // Machine is in shutdown state. Will not process commands +export const TINYG_MACHINE_STATE_PANIC = 13; // Machine is in panic state. Needs to be physically reset diff --git a/src/controller.js b/src/controller.js deleted file mode 100644 index c32eb7b..0000000 --- a/src/controller.js +++ /dev/null @@ -1,293 +0,0 @@ -import ensureArray from './ensure-array'; - -const noop = () => {}; - -class Controller { - io = null; - socket = null; - - listeners = { - // Socket.IO Events - // Fired upon a connection including a successful reconnection. - 'connect': [], - // Fired upon a connection error. - 'connect_error': [], - // Fired upon a connection timeout. - 'connect_timeout': [], - // Fired when an error occurs. - 'error': [], - // Fired upon a disconnection. - 'disconnect': [], - // Fired upon a successful reconnection. - 'reconnect': [], - // Fired upon an attempt to reconnect. - 'reconnect_attempt': [], - // Fired upon an attempt to reconnect. - 'reconnecting': [], - // Fired upon a reconnection attempt error. - 'reconnect_error': [], - // Fired when couldn't reconnect within reconnectionAttempts. - 'reconnect_failed': [], - - // System Events - 'startup': [], - 'config:change': [], - 'task:start': [], - 'task:finish': [], - 'task:error': [], - 'serialport:list': [], - 'serialport:change': [], - 'serialport:open': [], - 'serialport:close': [], - 'serialport:error': [], - 'serialport:read': [], - 'serialport:write': [], - 'gcode:load': [], - 'gcode:unload': [], - 'feeder:status': [], - 'sender:status': [], - 'workflow:state': [], - 'controller:settings': [], - 'controller:state': [], - 'message': [] - }; - - context = { - xmin: 0, - xmax: 0, - ymin: 0, - ymax: 0, - zmin: 0, - zmax: 0 - }; - - // User-defined baud rates and ports - baudrates = []; - ports = []; - - loadedControllers = []; - port = ''; - type = ''; - settings = {}; - state = {}; - workflow = { - state: 'idle' // running|paused|idle - }; - - // @param {object} io The socket.io-client module. - constructor(io) { - if (!io) { - throw new Error(`Expected the socket.io-client module, but got: ${io}`); - } - - this.io = io; - } - // Whether or not the client is connected. - // @return {boolean} Returns true if the client is connected, false otherwise. - get connected() { - return !!(this.socket && this.socket.connected); - } - // Establish a connection to the server. - // @param {string} host - // @param {object} options - // @param {function} next - connect(host = '', options = {}, next = noop) { - if (typeof next !== 'function') { - next = noop; - } - - this.socket && this.socket.destroy(); - this.socket = this.io.connect(host, options); - - Object.keys(this.listeners).forEach((eventName) => { - if (!this.socket) { - return; - } - - this.socket.on(eventName, (...args) => { - if (eventName === 'serialport:open') { - const { controllerType, port } = { ...args[0] }; - this.port = port; - this.type = controllerType; - } - if (eventName === 'serialport:close') { - this.port = ''; - this.type = ''; - this.state = {}; - this.settings = {}; - this.workflow.state = 'idle'; - } - if (eventName === 'workflow:state') { - this.workflow.state = args[0]; - } - if (eventName === 'controller:settings') { - this.type = args[0]; - this.settings = { ...args[1] }; - } - if (eventName === 'controller:state') { - this.type = args[0]; - this.state = { ...args[1] }; - } - - const listeners = ensureArray(this.listeners[eventName]); - listeners.forEach(listener => { - listener(...args); - }); - }); - }); - - this.socket.on('startup', (data) => { - const { loadedControllers, baudrates, ports } = { ...data }; - - this.loadedControllers = ensureArray(loadedControllers); - - // User-defined baud rates and ports - this.baudrates = ensureArray(baudrates); - this.ports = ensureArray(ports); - - if (next) { - next(null); - - // The callback can only be called once - next = null; - } - }); - } - // Disconnect from the server. - disconnect() { - this.socket && this.socket.destroy(); - this.socket = null; - } - // Adds the `listener` function to the end of the listeners array for the event named `eventName`. - // @param {string} eventName The name of the event. - // @param {function} listener The listener function. - addListener(eventName, listener) { - const listeners = this.listeners[eventName]; - if (!listeners || typeof listener !== 'function') { - return false; - } - listeners.push(listener); - return true; - } - // Removes the specified `listener` from the listener array for the event named `eventName`. - // @param {string} eventName The name of the event. - // @param {function} listener The listener function. - removeListener(eventName, listener) { - const listeners = this.listeners[eventName]; - if (!listeners || typeof listener !== 'function') { - return false; - } - listeners.splice(listeners.indexOf(listener), 1); - return true; - } - // Opens a connection to the given serial port. - // @param {string} port The path of the serial port you want to open. For example, `dev/tty.XXX` on Mac and Linux, or `COM1` on Windows. - // @param {object} [options] The options object. - // @param {string} [options.controllerType] One of: 'Grbl', 'Smoothe', 'TinyG'. Defaults to 'Grbl'. - // @param {number} [options.baudrate] Defaults to 115200. - // @param {function} [callback] Called after a connection is opened. - openPort(port, options, callback) { - if (typeof options !== 'object') { - options = {}; - callback = options; - } - if (typeof callback !== 'function') { - callback = noop; - } - this.socket && this.socket.emit('open', port, options, callback); - } - // Closes an open connection. - // @param {string} port The path of the serial port you want to close. For example, `dev/tty.XXX` on Mac and Linux, or `COM1` on Windows. - // @param {function} [callback] Called once a connection is closed. - closePort(port, callback) { - if (typeof callback !== 'function') { - callback = noop; - } - this.socket && this.socket.emit('close', port, callback); - } - // Retrieves a list of available serial ports with metadata. - // @param {function} [callback] Called once completed. - listPorts(callback) { - this.socket && this.socket.emit('list', callback); - } - // Executes a command on the server. - // @param {string} cmd The command string - // @example Example Usage - // - Load G-code - // controller.command('gcode:load', name, gcode, context /* optional */, callback) - // - Unload G-code - // controller.command('gcode:unload') - // - Start sending G-code - // controller.command('gcode:start') - // - Stop sending G-code - // controller.command('gcode:stop', { force: true }) - // - Pause - // controller.command('gcode:pause') - // - Resume - // controller.command('gcode:resume') - // - Feeder - // controller.command('feeder:feed') - // controller.command('feeder:start') - // controller.command('feeder:stop') - // controller.command('feeder:clear') - // - Feed Hold - // controller.command('feedhold') - // - Cycle Start - // controller.command('cyclestart') - // - Status Report - // controller.command('statusreport') - // - Homing - // controller.command('homing') - // - Sleep - // controller.command('sleep') - // - Unlock - // controller.command('unlock') - // - Reset - // controller.command('reset') - // - Feed Override - // controller.command('feedOverride') - // - Spindle Override - // controller.command('spindleOverride') - // - Rapid Override - // controller.command('rapidOverride') - // - Energize Motors - // controller.command('energizeMotors:on') - // controller.command('energizeMotors:off') - // - G-code - // controller.command('gcode', 'G0X0Y0', context /* optional */) - // - Load a macro - // controller.command('macro:load', '', context /* optional */, callback) - // - Run a macro - // controller.command('macro:run', '', context /* optional */, callback) - // - Load file from a watch directory - // controller.command('watchdir:load', '/path/to/file', callback) - command(cmd, ...args) { - const { port } = this; - if (!port) { - return; - } - this.socket && this.socket.emit.apply(this.socket, ['command', port, cmd].concat(args)); - } - // Writes data to the serial port. - // @param {string} data The data to write. - // @param {object} [context] The associated context information. - write(data, context) { - const { port } = this; - if (!port) { - return; - } - this.socket && this.socket.emit('write', port, data, context); - } - // Writes data and a newline character to the serial port. - // @param {string} data The data to write. - // @param {object} [context] The associated context information. - writeln(data, context) { - const { port } = this; - if (!port) { - return; - } - this.socket && this.socket.emit('writeln', port, data, context); - } -} - -export default Controller; diff --git a/src/index.js b/src/index.js index e641a4e..2dc8422 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ -import Controller from './controller'; +import Controller from './Controller'; module.exports = Controller; diff --git a/src/mapvalues.js b/src/mapvalues.js new file mode 100644 index 0000000..67bd623 --- /dev/null +++ b/src/mapvalues.js @@ -0,0 +1,12 @@ +import noop from './noop'; + +const mapValues = (obj, fn = noop) => { + const data = { ...obj }; + Object.keys(data).forEach(key => { + const val = data[key]; + data[key] = fn && fn(val); + }); + return data; +}; + +export default mapValues; diff --git a/src/noop.js b/src/noop.js new file mode 100644 index 0000000..77787df --- /dev/null +++ b/src/noop.js @@ -0,0 +1,3 @@ +const noop = () => {}; + +export default noop; diff --git a/src/units.js b/src/units.js new file mode 100644 index 0000000..1e2de04 --- /dev/null +++ b/src/units.js @@ -0,0 +1,10 @@ +// from mm to in +const mm2in = (val = 0) => val / 25.4; + +// from in to mm +const in2mm = (val = 0) => val * 25.4; + +export { + mm2in, + in2mm +}; From 1a77cdb54f3fc40f7fef21718476f824b5381801 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Fri, 20 Oct 2017 20:06:03 -0500 Subject: [PATCH 04/16] Update README.md --- README.md | 137 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 713eae5..178908e 100644 --- a/README.md +++ b/README.md @@ -77,59 +77,83 @@ controller.addListener('connection:read', (data) => { }); ``` -## API - -### Properties - -Name | Type | Default | Description -:--- | :--- | :------ | :---------- -baudRates | array | | A list of user-defined baud rates. -availableControllers | array | | A list of available controllers. -type | string | | The controller type. -settings | object | | The controller settings. -state | object | | The controller state. -connection | object | | The connection object. -connection.ident | string | | The connection identifier. -connection.type | string | | The connection type. -connection.settings | object | | The connection settings. -workflow | object | | The workflow object. -workflow.state | string | | The workflow state. -connected | boolean | | Whether the client is connected to the server. - -### Methods - -#### connect(host = '', options, [callback]) +## API Events + +### Socket.IO Events + +Name | Description +:--- | :---------- +connect | Fired upon a connection including a successful reconnection. +connect_error | Fired upon a connection error. +connect_timeout | Fired upon a connection timeout. +error | Fired when an error occurs. +disconnect | Fired upon a disconnection. +reconnect | Fired upon a successful reconnection. +reconnect_attempt | Fired upon an attempt to reconnect. +reconnecting | Fired upon an attempt to reconnect. +reconnect_error | Fired upon a reconnection attempt error. +reconnect_failed | Fired when couldn't reconnect within reconnectionAttempts. + +### CNCjs Events + +Name | Description +:--- | :---------- +startup | +ports | +config:change | +task:start | +task:finish | +task:error | +controller:type | +controller:settings | +controller:state | +connection:open | +connection:close | +connection:change | +connection:error | +connection:read | +connection:write | +feeder:status | +sender:status | +sender:load | +sender:unload | +workflow:state | +message | + +## API Methods + +### connect(host = '', options, [callback]) Establish a connection to the server. -##### Arguments +#### Arguments 1. host (string): 2. options (object): 3. [callback] (function): The callback function. -#### disconnect([callback]) +### disconnect([callback]) Disconnect from the server. -##### Arguments +#### Arguments 1. [callback] (function): The callback function. -#### addListener(eventName, listener) +### addListener(eventName, listener) Adds the `listener` function to the end of the listeners array for the event named `eventName`. -##### Arguments +#### Arguments 1. eventName (string): The name of the event. 2. listener (function): The listener function. -#### removeListener(eventName, listener) +### removeListener(eventName, listener) Removes the specified `listener` from the listener array for the event named `eventName`. -##### Arguments +#### Arguments 1. eventName (string): The name of the event. 2. listener (function): The listener function. -#### open(controllerType, connectionType, options, [callback]) +### open(controllerType, connectionType, options, [callback]) Opens a connection. -##### Arguments +#### Arguments 1. controllerType (string): One of: 'Grbl', 'Smoothe', 'TinyG'. 2. connectionType (string): One of: 'serial', 'socket'. 3. options (object): The options object. @@ -139,62 +163,79 @@ Opens a connection. 7. [options.port=23] (string): `socket` The port number. 8. [callback] (string): Called after a connection is opened. -#### close([callback]) +### close([callback]) Closes an open connection. -##### Arguments +#### Arguments 1. [callback] (string): Called once a connection is closed. -#### command(cmd, ...args) +### command(cmd, ...args) Executes a command on the server. -##### Arguments +#### Arguments 1. cmd (string): The command to execute. -#### write(data, [context) +### write(data, [context) Writes data to the open connection. -##### Arguments +#### Arguments 1. data (string): The data to write. 2. [context] (object): The associated context information. #### writeln(data, [context]) Writes data and a newline character to the open connection. -##### Arguments +#### Arguments 1. data (string): The data to write. 2. [context] (object): The associated context information. -#### getPorts([callback]) +### getPorts([callback]) Gets a list of available serial ports. -##### Arguments +#### Arguments 1. [callback] (object): Called once completed. -#### getMachineState() +### getMachineState() Gets the machine state. -##### Return +#### Return (string|number): Returns the machine state. -#### getMachinePosition() +### getMachinePosition() Gets the machine position. -##### Return +#### Return (object): Returns a position object which contains x, y, z, a, b, and c properties. -#### getWorkPosition() +### getWorkPosition() Gets the work position. -##### Return +#### Return (object): Returns a position object which contains x, y, z, a, b, and c properties. -#### getModalState() +### getModalState() Gets modal state. -##### Return +#### Return (object): Returns the modal state. +## API Properties + +Name | Type | Default | Description +:--- | :--- | :------ | :---------- +baudRates | array | | A list of user-defined baud rates. +availableControllers | array | | A list of available controllers. +type | string | | The controller type. +settings | object | | The controller settings. +state | object | | The controller state. +connection | object | | The connection object. +connection.ident | string | | The connection identifier. +connection.type | string | | The connection type. +connection.settings | object | | The connection settings. +workflow | object | | The workflow object. +workflow.state | string | | The workflow state. +connected | boolean | | Whether the client is connected to the server. + ## License MIT From 88d33f10ae356b810e4a61f683e317d0c27568ef Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Fri, 20 Oct 2017 21:23:53 -0500 Subject: [PATCH 05/16] Update README.md --- README.md | 59 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 178908e..78e9750 100644 --- a/README.md +++ b/README.md @@ -98,27 +98,27 @@ reconnect_failed | Fired when couldn't reconnect within reconnectionAttempts. Name | Description :--- | :---------- -startup | -ports | -config:change | -task:start | -task:finish | -task:error | -controller:type | -controller:settings | -controller:state | -connection:open | -connection:close | -connection:change | -connection:error | -connection:read | -connection:write | -feeder:status | -sender:status | -sender:load | -sender:unload | -workflow:state | -message | +startup(data) | +ports(ports) | +config:change() | +task:start(taskId) | +task:finish(taskId, code) | +task:error(taskId, err) | +controller:type(type) | +controller:settings(type, settings) | +controller:state(type, state) | +connection:open(options) | +connection:close(options) | +connection:change(options, isOpen) | +connection:error(options, err) | +connection:read(options, data) | +connection:write(options, data, context) | +feeder:status(status) | +sender:status(status) | +sender:load(name, gcode, context) | +sender:unload() | +workflow:state(state) | +message(message) | ## API Methods @@ -195,6 +195,12 @@ Gets a list of available serial ports. #### Arguments 1. [callback] (object): Called once completed. +### getBaudRates([callback]) +Gets a list of supported baud rates. + +#### Arguments +1. [callback] (object): Called once completed. + ### getMachineState() Gets the machine state. @@ -223,18 +229,17 @@ Gets modal state. Name | Type | Default | Description :--- | :--- | :------ | :---------- -baudRates | array | | A list of user-defined baud rates. +connected | boolean | | Whether the client is connected to the server. availableControllers | array | | A list of available controllers. -type | string | | The controller type. +type | string | | The controller type. One of: Grbl, Smoothie, TinyG. settings | object | | The controller settings. state | object | | The controller state. connection | object | | The connection object. connection.ident | string | | The connection identifier. -connection.type | string | | The connection type. -connection.settings | object | | The connection settings. +connection.type | string | | The connection type. One of: serial, socket. +connection.settings | object | | The connection settings.
serial: `{ path, baudRate }`
socket: `{ host, port }` workflow | object | | The workflow object. -workflow.state | string | | The workflow state. -connected | boolean | | Whether the client is connected to the server. +workflow.state | string | | The workflow state. One of: idle, paused, running. ## License From 95876615d023ef3cd9b2623410cf978fe07f43e0 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Fri, 20 Oct 2017 22:38:42 -0500 Subject: [PATCH 06/16] Update README.md --- README.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 78e9750..e1f393b 100644 --- a/README.md +++ b/README.md @@ -83,23 +83,22 @@ controller.addListener('connection:read', (data) => { Name | Description :--- | :---------- -connect | Fired upon a connection including a successful reconnection. -connect_error | Fired upon a connection error. -connect_timeout | Fired upon a connection timeout. -error | Fired when an error occurs. -disconnect | Fired upon a disconnection. -reconnect | Fired upon a successful reconnection. -reconnect_attempt | Fired upon an attempt to reconnect. -reconnecting | Fired upon an attempt to reconnect. -reconnect_error | Fired upon a reconnection attempt error. -reconnect_failed | Fired when couldn't reconnect within reconnectionAttempts. +connect() | Fired upon a connection including a successful reconnection. +connect_error(error) | Fired upon a connection error. +connect_timeout() | Fired upon a connection timeout. +error() | Fired when an error occurs. +disconnect() | Fired upon a disconnection. +reconnect(attempt) | Fired upon a successful reconnection. +reconnect_attempt() | Fired upon an attempt to reconnect. +reconnecting(attempt) | Fired upon a successful reconnection. +reconnect_error(error) | Fired upon a reconnection attempt error. +reconnect_failed() | Fired when couldn't reconnect within `reconnectionAttempts`. ### CNCjs Events Name | Description :--- | :---------- startup(data) | -ports(ports) | config:change() | task:start(taskId) | task:finish(taskId, code) | @@ -115,7 +114,7 @@ connection:read(options, data) | connection:write(options, data, context) | feeder:status(status) | sender:status(status) | -sender:load(name, gcode, context) | +sender:load(data, context) | sender:unload() | workflow:state(state) | message(message) | @@ -193,13 +192,13 @@ Writes data and a newline character to the open connection. Gets a list of available serial ports. #### Arguments -1. [callback] (object): Called once completed. +1. [callback] (function): The error-first callback. ### getBaudRates([callback]) Gets a list of supported baud rates. #### Arguments -1. [callback] (object): Called once completed. +1. [callback] (function): The error-first callback. ### getMachineState() Gets the machine state. From 2fdc7918bdf1cd904e7d329ed6c35669e9f470b8 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 21 Oct 2017 10:16:57 +0800 Subject: [PATCH 07/16] Add a getBaudRates() API to get a list of supported baud rates --- src/Controller.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Controller.js b/src/Controller.js index 0a11f5f..96e4d95 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -72,9 +72,6 @@ class Controller { zmax: 0 }; - // User-defined baud rates - baudRates = []; - // Available controllers availableControllers = []; @@ -164,13 +161,10 @@ class Controller { }); this.socket.on('startup', (data) => { - const { availableControllers, baudRates } = { ...data }; + const { availableControllers } = { ...data }; this.availableControllers = ensureArray(availableControllers); - // User-defined baud rates - this.baudRates = ensureArray(baudRates); - if (callback) { callback(null); @@ -338,6 +332,14 @@ class Controller { } this.socket.emit('getPorts', callback); } + // Gets a list of supported baud rates. + // @param {function} [callback] Called once completed. + getBaudRates(callback) { + if (!this.socket) { + return; + } + this.socket.emit('getBaudRates', callback); + } // Gets the machine state. // @return {string|number} Returns the machine state. getMachineState() { From 9088407e50b18e994982aac99180bd1f083d511f Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 21 Oct 2017 11:40:09 +0800 Subject: [PATCH 08/16] Use the error-first callback for getPorts() and getBaudRates() --- src/Controller.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Controller.js b/src/Controller.js index 96e4d95..5e6bc5b 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -209,7 +209,7 @@ class Controller { // @param {string} options.host `socket` The host address to connect. // @param {number} [options.port=23] `socket` The port number. // @param {function} [callback] Called after a connection is opened. - open(controllerType, connectionType, options, callback) { + open(controllerType, connectionType, options, callback = noop) { if (typeof options !== 'object') { options = {}; callback = options; @@ -230,7 +230,7 @@ class Controller { } // Closes an open connection. // @param {function} [callback] Called once a connection is closed. - close(callback) { + close(callback = noop) { if (typeof callback !== 'function') { callback = noop; } @@ -325,17 +325,25 @@ class Controller { this.socket.emit('writeln', this.connection.ident, data, context); } // Gets a list of available serial ports. - // @param {function} [callback] Called once completed. - getPorts(callback) { + // @param {function} [callback] The error-first callback. + getPorts(callback = noop) { + if (typeof callback !== 'function') { + callback = noop; + } if (!this.socket) { + callback(new Error('The socket is not connected')); return; } this.socket.emit('getPorts', callback); } // Gets a list of supported baud rates. - // @param {function} [callback] Called once completed. - getBaudRates(callback) { + // @param {function} [callback] The error-first callback. + getBaudRates(callback = noop) { + if (typeof callback !== 'function') { + callback = noop; + } if (!this.socket) { + callback(new Error('The socket is not connected')); return; } this.socket.emit('getBaudRates', callback); From d6a121030fef57d30fcde589bc4b14e81c3711d4 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 21 Oct 2017 00:10:20 -0500 Subject: [PATCH 09/16] Update README.md --- README.md | 140 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 104 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index e1f393b..f4b1e7e 100644 --- a/README.md +++ b/README.md @@ -77,47 +77,115 @@ controller.addListener('connection:read', (data) => { }); ``` -## API Events +## API Events + +### CNCjs Events + +#### Event: 'startup' +* `data` *(Object)* data object +* `data.availableControllers` *(Array)* A list of available controllers + +#### Event: 'config:change' + +#### Event: 'task:start' +* `taskId` *(String)* task id + +#### Event: 'task:finish' +* `taskId` *(String)* task id +* `code` *(Number)* exit code + +#### Event: 'task:error' +* `taskId` *(String)* task id +* `error` *(Object)* error object + +#### Event: 'controller:type' +* `type` *(String)* controller type + +#### Event: 'controller:settings' +* `type` *(String)* controller type +* `settings` *(Object)* controller settings + +#### Event: 'controller:state' +* `type` *(String)* controller type +* `state` *(Object)* controller state + +#### Event: 'connection:open' +* `options` *(Object)* connection options + +#### Event: 'connection:close' +* `options` *(Object)* connection options + +#### Event: 'connection:change' +* `options` *(Object)* connection options +* `isOpen` *(Boolean)* True if the connection is open, flase otherwise. + +#### Event: 'connection:error' +* `options` *(Object)* connection options +* `error`*(Object)* error object + +#### Event: 'connection:read' +* `options` *(Object)* connection options +* `data` *(String)* data to read + +#### Event: 'connection:write' +* `options` *(Object)* connection options +* `data` *(String)* data to write +* `context` *(Object)* associated context information + +#### Event: 'feeder:status' +* `status` *(Object)* feeder status object + +#### Event: 'sender:status' +* `status` *(Object)* sender status object + +#### Event: 'sender:load' +* `data` *(String)* data to load +* `context` *(Object)* associated context information + +#### Event: 'sender:unload' + +#### Event: 'workflow:state' +* `state` *(String)* workflow state + +#### Event: 'message' +* `message` *(String)* message string ### Socket.IO Events -Name | Description -:--- | :---------- -connect() | Fired upon a connection including a successful reconnection. -connect_error(error) | Fired upon a connection error. -connect_timeout() | Fired upon a connection timeout. -error() | Fired when an error occurs. -disconnect() | Fired upon a disconnection. -reconnect(attempt) | Fired upon a successful reconnection. -reconnect_attempt() | Fired upon an attempt to reconnect. -reconnecting(attempt) | Fired upon a successful reconnection. -reconnect_error(error) | Fired upon a reconnection attempt error. -reconnect_failed() | Fired when couldn't reconnect within `reconnectionAttempts`. +#### Event: 'connect' +Fired upon a connection including a successful reconnection. -### CNCjs Events +#### Event: 'connect_error' +* `error` *(Object)* error object +Fired upon a connection error. + +#### Event: 'connect_timeout' +Fired upon a connection timeout. + +#### Event: 'error' +* `error` *(Object)* error object +Fired when an error occurs. + +#### Event: 'disconnect' +* `reason` *(String)* either 'io server disconnect' or 'io client disconnect' +Fired upon a disconnection. + +#### Event: 'reconnect' +* `attempt` *(Number)* reconnection attempt number +Fired upon a successful reconnection. + +#### Event: 'reconnect_attempt' +Fired upon an attempt to reconnect. + +#### Event: 'reconnecting' +* `attempt` *(Number)* reconnection attempt number +Fired upon a successful reconnection. + +#### Event: 'reconnect_error' +Fired upon a reconnection attempt error. -Name | Description -:--- | :---------- -startup(data) | -config:change() | -task:start(taskId) | -task:finish(taskId, code) | -task:error(taskId, err) | -controller:type(type) | -controller:settings(type, settings) | -controller:state(type, state) | -connection:open(options) | -connection:close(options) | -connection:change(options, isOpen) | -connection:error(options, err) | -connection:read(options, data) | -connection:write(options, data, context) | -feeder:status(status) | -sender:status(status) | -sender:load(data, context) | -sender:unload() | -workflow:state(state) | -message(message) | +#### Event: 'reconnect_failed' +Fired when couldn't reconnect within `reconnectionAttempts`. ## API Methods From b67fe6f9e5ee36bdbee7be656890a022ceffa595 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 21 Oct 2017 00:46:45 -0500 Subject: [PATCH 10/16] Update README.md --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f4b1e7e..bb57c4b 100644 --- a/README.md +++ b/README.md @@ -83,108 +83,158 @@ controller.addListener('connection:read', (data) => { #### Event: 'startup' * `data` *(Object)* data object -* `data.availableControllers` *(Array)* A list of available controllers +* `data.availableControllers` *(Array)* a list of all available controllers + +Fired upon system startup. #### Event: 'config:change' +Fired whenever config changes. + #### Event: 'task:start' * `taskId` *(String)* task id +Fired when a task is started. + #### Event: 'task:finish' * `taskId` *(String)* task id * `code` *(Number)* exit code +Fired when a task is finished. + #### Event: 'task:error' * `taskId` *(String)* task id * `error` *(Object)* error object +Fired when an error occurred. + #### Event: 'controller:type' * `type` *(String)* controller type +Fired when the controller type changes. + #### Event: 'controller:settings' * `type` *(String)* controller type * `settings` *(Object)* controller settings +Fired when the controller settings changes. + #### Event: 'controller:state' * `type` *(String)* controller type * `state` *(Object)* controller state +Fired when the controller state changes. + #### Event: 'connection:open' * `options` *(Object)* connection options +Fired upon a connection open. + #### Event: 'connection:close' * `options` *(Object)* connection options +Fired upon a connection close. + #### Event: 'connection:change' * `options` *(Object)* connection options * `isOpen` *(Boolean)* True if the connection is open, flase otherwise. +Fired upon a connection change. + #### Event: 'connection:error' * `options` *(Object)* connection options * `error`*(Object)* error object +Fired upon a connection error. + #### Event: 'connection:read' * `options` *(Object)* connection options * `data` *(String)* data to read +Fired once a line is received from the connection. + #### Event: 'connection:write' * `options` *(Object)* connection options * `data` *(String)* data to write * `context` *(Object)* associated context information +Fired when writing data to the connection. + #### Event: 'feeder:status' -* `status` *(Object)* feeder status object +* `status` *(Object)* feeder status + +Fired when the feeder status changes. #### Event: 'sender:status' -* `status` *(Object)* sender status object +* `status` *(Object)* sender status + +Fired when the sender status changes. #### Event: 'sender:load' * `data` *(String)* data to load * `context` *(Object)* associated context information +Fired when a G-code program is loaded. + #### Event: 'sender:unload' +Fired when a G-code program is unloaded. + #### Event: 'workflow:state' * `state` *(String)* workflow state +Fired when the workflow state changes. + #### Event: 'message' * `message` *(String)* message string +Fired when the server sends message to the client. + ### Socket.IO Events #### Event: 'connect' + Fired upon a connection including a successful reconnection. #### Event: 'connect_error' * `error` *(Object)* error object + Fired upon a connection error. #### Event: 'connect_timeout' + Fired upon a connection timeout. #### Event: 'error' * `error` *(Object)* error object + Fired when an error occurs. #### Event: 'disconnect' * `reason` *(String)* either 'io server disconnect' or 'io client disconnect' + Fired upon a disconnection. #### Event: 'reconnect' * `attempt` *(Number)* reconnection attempt number + Fired upon a successful reconnection. #### Event: 'reconnect_attempt' + Fired upon an attempt to reconnect. #### Event: 'reconnecting' * `attempt` *(Number)* reconnection attempt number + Fired upon a successful reconnection. #### Event: 'reconnect_error' + Fired upon a reconnection attempt error. #### Event: 'reconnect_failed' + Fired when couldn't reconnect within `reconnectionAttempts`. ## API Methods From 2d3bf62938fbb7f65369ed476e505b49dc643abf Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 21 Oct 2017 14:33:30 +0800 Subject: [PATCH 11/16] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb57c4b..57e208b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ [![NPM](https://nodei.co/npm/cncjs-controller.png?downloads=true&stars=true)](https://www.npmjs.com/package/cncjs-controller) **A controller library for event-based communication between client and CNCjs server** - + +This branch is for CNCjs 2.0.0 or later versions. If you're looking for the previous version (<= 1.9), please visit the [v1](https://github.com/cncjs/cncjs-controller/tree/v1) branch. + ## Installation ```sh From fea400da8192c3484ee8f04d9b4ecd1706e4b009 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 21 Oct 2017 14:50:15 +0800 Subject: [PATCH 12/16] Clean dist --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a014162..23fafa2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "prepublish": "npm run eslint && npm test && npm run clean && npm run build && npm run dist", "build": "babel --out-dir ./lib ./src", "dist": "webpack", - "clean": "rm -rf lib/*", + "clean": "rm -rf lib/* dist/*", "eslint": "eslint ./src", "test": "tap test/*.js --node-arg=--require --node-arg=babel-register --node-arg=--require --node-arg=babel-polyfill", "coveralls": "tap test/*.js --coverage --coverage-report=text-lcov --nyc-arg=--require --nyc-arg=babel-register --nyc-arg=--require --nyc-arg=babel-polyfill | coveralls" From 72f909f0c063b17113da0949705357cbfe94d0e1 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sat, 21 Oct 2017 15:01:26 +0800 Subject: [PATCH 13/16] Renamed CNCController to CNCJSController --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index df801e9..1ffbc6b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,7 +11,7 @@ module.exports = [ path: path.join(__dirname, 'dist'), filename: `${pkg.name}-${pkg.version}.js`, libraryTarget: 'umd', - library: 'CNCController' + library: 'CNCJSController' }, plugins: [ new webpack.BannerPlugin(banner) From 58b181d2b47009e2db2f4fc97b3f02a317170470 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Sun, 22 Oct 2017 11:12:15 +0800 Subject: [PATCH 14/16] The 'ports' event is no longer available --- src/Controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Controller.js b/src/Controller.js index 5e6bc5b..8989bd1 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -41,7 +41,6 @@ class Controller { // System Events 'startup': [], - 'ports': [], 'config:change': [], 'task:start': [], 'task:finish': [], From 5596176b627d307e6fdb46cc81c1df29c80268da Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Thu, 9 Nov 2017 13:03:34 +0800 Subject: [PATCH 15/16] Preliminary support for Marlin 3D printer firmware --- src/Controller.js | 46 +++++++++++++++++++++++++++++++++++++++++++++- src/constants.js | 1 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Controller.js b/src/Controller.js index 8989bd1..35f9389 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -8,6 +8,7 @@ import { METRIC_UNITS, // Controller GRBL, + MARLIN, SMOOTHIE, TINYG } from './constants'; @@ -350,7 +351,7 @@ class Controller { // Gets the machine state. // @return {string|number} Returns the machine state. getMachineState() { - if ([GRBL, SMOOTHIE, TINYG].indexOf(this.type) < 0) { + if ([GRBL, MARLIN, SMOOTHIE, TINYG].indexOf(this.type) < 0) { return ''; } @@ -362,6 +363,8 @@ class Controller { if (this.type === GRBL) { machineState = this.state.machineState; + } else if (this.type === MARLIN) { + machineState = this.state.machineState; } else if (this.type === SMOOTHIE) { machineState = this.state.machineState; } else if (this.type === TINYG) { @@ -397,6 +400,23 @@ class Controller { }); } + // Marlin + if (this.type === MARLIN) { + const { pos, modal = {} } = this.state; + const units = { + 'G20': IMPERIAL_UNITS, + 'G21': METRIC_UNITS + }[modal.units]; + + // Machine position are reported in current units + return mapValues({ + ...defaultMachinePosition, + ...pos + }, (val) => { + return (units === IMPERIAL_UNITS) ? in2mm(val) : val; + }); + } + // Smoothieware if (this.type === SMOOTHIE) { const { mpos, modal = {} } = this.state; @@ -455,6 +475,23 @@ class Controller { }); } + // Marlin + if (this.type === MARLIN) { + const { pos, modal = {} } = this.state; + const units = { + 'G20': IMPERIAL_UNITS, + 'G21': METRIC_UNITS + }[modal.units]; + + // Work position are reported in current units + return mapValues({ + ...defaultWorkPosition, + ...pos + }, (val) => { + return (units === IMPERIAL_UNITS) ? in2mm(val) : val; + }); + } + // Smoothieware if (this.type === SMOOTHIE) { const { wpos, modal = {} } = this.state; @@ -514,6 +551,13 @@ class Controller { }; } + if (this.type === MARLIN) { + return { + ...defaultModalState, + ...this.state.modal + }; + } + if (this.type === SMOOTHIE) { return { ...defaultModalState, diff --git a/src/constants.js b/src/constants.js index 562ab76..01d850e 100644 --- a/src/constants.js +++ b/src/constants.js @@ -4,6 +4,7 @@ export const METRIC_UNITS = 'mm'; // Controller export const GRBL = 'Grbl'; +export const MARLIN = 'Marlin'; export const SMOOTHIE = 'Smoothie'; export const TINYG = 'TinyG'; From c08f8891bbfc7d0a7ee55108be4e29afe18c41a6 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Thu, 9 Nov 2017 13:04:58 +0800 Subject: [PATCH 16/16] v2.0.0-alpha.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23fafa2..d314a0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cncjs-controller", - "version": "2.0.0-alpha", + "version": "2.0.0-alpha.1", "description": "A controller library for event-based communication between client and CNCjs server.", "main": "lib/index.js", "scripts": {