UNPKG

spi-hub-client

Version:

node.js client for SPI bus communications using spi-hub

163 lines (132 loc) 5.92 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.SPI_HUB_EVENT_DEVICES_CHANGED = exports.SPI_HUB_EVENT_MESSAGE = undefined; var _assert = require('assert'); var _assert2 = _interopRequireDefault(_assert); var _log4jcore = require('log4jcore'); var _log4jcore2 = _interopRequireDefault(_log4jcore); var _typedEventEmitter = require('@jcoreio/typed-event-emitter'); var _typedEventEmitter2 = _interopRequireDefault(_typedEventEmitter); var _socketIpc = require('socket-ipc'); var _verror = require('verror'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const log = (0, _log4jcore2.default)('spi-hub-client'); const SPI_HUB_SOCKET_PATH = '/tmp/socket-spi-hub'; const IPC_PROTO_VERSION = 2; // Commands that are valid both on the SPI bus and on the IPC socket const SPI_HUB_CMD_MSG_TO_DEVICE = 1; const SPI_HUB_CMD_MSG_FROM_DEVICE = 2; // Commands that are only valid on the IPC socket const SPI_HUB_CMD_DEVICES_LIST = 100; const IPC_MESSAGE_FROM_DEVICE_OVERHEAD = 7; const IPC_MESSAGES_TO_DEVICE_OVERHEAD = 4; // Overhead at the beginning = proto version + message type + 2 byte length const IPC_OVERHEAD_PER_MESSAGE_TO_DEVICE = 8; // Overhead per message const IPC_MESSAGE_TO_DEVICE_PREAMBLE = 0xA3; const SPI_HUB_EVENT_MESSAGE = exports.SPI_HUB_EVENT_MESSAGE = 'message'; const SPI_HUB_EVENT_DEVICES_CHANGED = exports.SPI_HUB_EVENT_DEVICES_CHANGED = 'devicesChanged'; class SPIHubClient extends _typedEventEmitter2.default { constructor(opts = {}) { super(); this._binary = !!opts.binary; const ipcClient = this._ipcClient = new _socketIpc.MessageClient(SPI_HUB_SOCKET_PATH, { binary: true }); ipcClient.on('message', message => this._onIPCMessage(message)); ipcClient.on('error', err => this.emit('error', new _verror.VError(err, 'SPIHubClient socket error'))); } start() { this._ipcClient.start(); } send(txMessages) { (0, _assert2.default)(txMessages); const txMessagesArr = Array.isArray(txMessages) ? txMessages : [txMessages]; // Encode payloads to binary buffers const messageStates = txMessagesArr.map(txMessage => { const { message } = txMessage; return { txMessage, payload: typeof message === 'string' ? Buffer.from(message) : message }; }); // Calculate required size and allocate buffer const messagesLenTotal = messageStates.reduce((sum, entry) => sum + IPC_OVERHEAD_PER_MESSAGE_TO_DEVICE + entry.payload.length, 0); const ipcMsgBuf = Buffer.alloc(IPC_MESSAGES_TO_DEVICE_OVERHEAD + messagesLenTotal); // Write IPC message preamble let pos = 0; ipcMsgBuf.writeUInt8(IPC_PROTO_VERSION, pos++); ipcMsgBuf.writeUInt8(SPI_HUB_CMD_MSG_TO_DEVICE, pos++); ipcMsgBuf.writeUInt16LE(messageStates.length, pos); pos += 2; // Write messages to IPC message buffer for (const messageState of messageStates) { const { txMessage: { busId, deviceId, channel, deDupeId }, payload } = messageState; const deDupeIdFinal = deDupeId || 0; validateUInt8(busId, 'busId'); validateUInt8(deviceId, 'deviceId'); validateUInt8(channel, 'channel'); validateUInt16(deDupeIdFinal, 'deDupeId'); ipcMsgBuf.writeUInt8(IPC_MESSAGE_TO_DEVICE_PREAMBLE, pos++); ipcMsgBuf.writeUInt8(busId, pos++); ipcMsgBuf.writeUInt8(deviceId, pos++); ipcMsgBuf.writeUInt8(channel, pos++); ipcMsgBuf.writeUInt16LE(deDupeIdFinal, pos); pos += 2; ipcMsgBuf.writeUInt16LE(payload.length, pos); pos += 2; payload.copy(ipcMsgBuf, pos); pos += payload.length; } // Send the IPC message this._ipcClient.send(ipcMsgBuf); } _onIPCMessage(event) { const ipcMsgBuf = event.data; try { (0, _assert2.default)(ipcMsgBuf.length > 2, 'ipc message is too short'); let pos = 0; const version = ipcMsgBuf.readUInt8(pos++); const cmd = ipcMsgBuf.readUInt8(pos++); _assert2.default.strictEqual(version, IPC_PROTO_VERSION, 'unexpected protocol version'); switch (cmd) { case SPI_HUB_CMD_DEVICES_LIST: { const strMessage = ipcMsgBuf.toString('utf8', pos); const { devices, serialNumber, accessCode } = JSON.parse(strMessage); this.emit(SPI_HUB_EVENT_DEVICES_CHANGED, { devices, serialNumber, accessCode }); }break; case SPI_HUB_CMD_MSG_FROM_DEVICE: { (0, _assert2.default)(ipcMsgBuf.length >= IPC_MESSAGE_FROM_DEVICE_OVERHEAD, 'message from device is too short'); const busId = ipcMsgBuf.readUInt8(pos++); const deviceId = ipcMsgBuf.readUInt8(pos++); const channel = ipcMsgBuf.readUInt8(pos++); pos += 2; // Throw away the de-dupe ID const messageFromSPI = { busId, deviceId, channel }; if (this._binary) { const messageBuffer = messageFromSPI.messageBuffer = Buffer.alloc(ipcMsgBuf.length - pos); ipcMsgBuf.copy(messageBuffer, 0, pos); } else { messageFromSPI.message = ipcMsgBuf.toString('utf8', pos); } this.emit(SPI_HUB_EVENT_MESSAGE, messageFromSPI); }break; default: throw Error('unexpected IPC message ID: ' + cmd); } } catch (err) { log.error('could not process an incoming IPC message', err); } } } exports.default = SPIHubClient; function validateUInt8(val, fieldName) { validateUInt(val, fieldName, 255); } function validateUInt16(val, fieldName) { validateUInt(val, fieldName, 65535); } function validateUInt(val, fieldName, maxValue) { if (!Number.isInteger(val)) throw Error(`${fieldName} must be a valid integer`); if (val < 0) throw Error(`${fieldName} must be at least 0`); if (val > maxValue) throw Error(`${fieldName} must be ${maxValue} or less`); }