diff --git a/README.md b/README.md index 420554e..57e208b 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,13 @@ [![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 -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 +20,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'; + const controllerType = 'Grbl'; // Grbl|Smoothie|TinyG + const connectionType = 'serial'; // serial|socket + const serialOptions = { + path: '/dev/cu.wchusbserialfa130', + baudRate: 115200 + }; - controller.openPort(port, { - controllerType: 'Grbl', // Grbl|Smoothie|TinyG - baudrate: 115200 - }, (err) => { + // Open a serial connection + controller.open(controllerType, connectionType, serialOptions, (err) => { if (err) { console.error(err); return; @@ -41,37 +46,320 @@ 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 Events + +### CNCjs Events + +#### Event: 'startup' +* `data` *(Object)* data object +* `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 + +Fired when the feeder status changes. + +#### Event: 'sender:status' +* `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 + +### 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] (function): The error-first callback. + +### getBaudRates([callback]) +Gets a list of supported baud rates. + +#### Arguments +1. [callback] (function): The error-first callback. + +### 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. + +## API Properties + +Name | Type | Default | Description +:--- | :--- | :------ | :---------- +connected | boolean | | Whether the client is connected to the server. +availableControllers | array | | A list of available controllers. +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. 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. One of: idle, paused, running. + ## License MIT diff --git a/package.json b/package.json index b200490..d314a0c 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "cncjs-controller", - "version": "1.3.0", + "version": "2.0.0-alpha.1", "description": "A controller library for event-based communication between client and CNCjs server.", "main": "lib/index.js", "scripts": { "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" diff --git a/src/Controller.js b/src/Controller.js new file mode 100644 index 0000000..35f9389 --- /dev/null +++ b/src/Controller.js @@ -0,0 +1,579 @@ +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, + MARLIN, + 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': [], + '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 + }; + + // 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 } = { ...data }; + + this.availableControllers = ensureArray(availableControllers); + + 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 = noop) { + 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 = noop) { + 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] 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] 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); + } + // Gets the machine state. + // @return {string|number} Returns the machine state. + getMachineState() { + if ([GRBL, MARLIN, 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 === MARLIN) { + 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; + }); + } + + // 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; + 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; + }); + } + + // 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; + 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 === MARLIN) { + 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..01d850e --- /dev/null +++ b/src/constants.js @@ -0,0 +1,50 @@ +// Metric and Imperial units +export const IMPERIAL_UNITS = 'in'; +export const METRIC_UNITS = 'mm'; + +// Controller +export const GRBL = 'Grbl'; +export const MARLIN = 'Marlin'; +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 +}; 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)