@jcoreio/iron-pi-device-client
Version:
Client library for reading and writing Iron Pi input and output states
190 lines (157 loc) • 6.84 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.IronPiDeviceClient = exports.EVENT_DEVICES_DETECTED = exports.EVENT_DEVICE_INPUT_STATES = undefined;
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');
var _verror2 = _interopRequireDefault(_verror);
var _ipcTypes = require('./ipcTypes');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const log = (0, _log4jcore2.default)('iron-pi-device-client');
const EVENT_DEVICE_INPUT_STATES = exports.EVENT_DEVICE_INPUT_STATES = 'deviceInputStates';
const EVENT_DEVICES_DETECTED = exports.EVENT_DEVICES_DETECTED = 'devicesDetected';
class IronPiDeviceClient extends _typedEventEmitter2.default {
/**
* @param unixSocketPath Optional override of default UNIX socket path, which is '/tmp/socket-iron-pi'.
*/
/** mapping from request serial number to request state */
constructor({ unixSocketPath, host, port }) {
super();
this._inFlightRequests = new Map();
this._curMethodSerial = 0;
this.connected = false;
this.deviceInputStates = undefined;
this._onIPCMessage = event => {
try {
const msg = JSON.parse(event.data);
log.trace('rx from driver:', msg);
const { hardwareInfo, deviceInputStates, methodSerial, methodResult, methodError } = msg;
if (methodSerial) {
const request = this._inFlightRequests.get(methodSerial);
if (request) {
clearTimeout(request.timeout);
if (methodError) {
request.reject(Error(`method error: ${methodError}`));
} else {
request.resolve(methodResult);
}
} else {
log.error(`could not find method serial: ${methodSerial}`);
}
}
if (hardwareInfo) {
this._hardwareInfo = hardwareInfo;
this.connected = true;
this.emit(EVENT_DEVICES_DETECTED, hardwareInfo);
}
if (deviceInputStates) {
this.deviceInputStates = deviceInputStates;
this.emit(EVENT_DEVICE_INPUT_STATES, deviceInputStates);
}
} catch (err) {
log.error('could not process an incoming IPC message', err);
}
};
const socketOpts = port ? { host, port } : { path: unixSocketPath || _ipcTypes.UNIX_SOCKET_PATH };
const ipcClient = this._ipcClient = new _socketIpc.MessageClient(socketOpts);
ipcClient.on('message', this._onIPCMessage);
ipcClient.on('error', err => this.emit('error', new _verror2.default(err, 'IronPiDeviceClient socket error')));
ipcClient.on('connection', () => this.emit('connection'));
ipcClient.on('close', () => {
this.connected = false;
this.emit('close');
});
}
start() {
this._ipcClient.start().catch(err => {
log.error('could not connect to iron pi hardware agent', err);
});
}
hardwareInfo() {
return this._hardwareInfo;
}
setOutputs(setOutputs) {
const { outputs } = setOutputs;
if (!Array.isArray(outputs) || outputs.find(out => typeof out !== 'object')) throw Error('outputs property must be an array of Objects with the format {address: number, levels: Array<boolean>}');
this._send({ setOutputs });
}
setLEDs(setLEDs) {
const { leds } = setLEDs;
if (!Array.isArray(leds) || leds.find(cmd => typeof cmd !== 'object')) throw Error('leds property must be an array of Objects with the format {address: number, colors: string, onTime: number, offTime: number, idleTime: number}');
this._send({ setLEDs });
}
async getNetworkState() {
return await this._callMethod(_ipcTypes.METHOD_GET_NETWORK_STATE);
}
async getNetworkSettings() {
return await this._callMethod(_ipcTypes.METHOD_GET_NETWORK_SETTINGS);
}
async setNetworkSettings(settings) {
await this._callMethod(_ipcTypes.METHOD_SET_NETWORK_SETTINGS, settings);
}
async isSSHEnabled() {
return await this._callMethod(_ipcTypes.METHOD_IS_SSH_ENABLED);
}
async setSSHEnabled(enabled) {
await this._callMethod(_ipcTypes.METHOD_SET_SSH_ENABLED, enabled);
}
async setSystemPassword(password) {
await this._callMethod(_ipcTypes.METHOD_SET_SYSTEM_PASSWORD, password);
}
/**
* Checks that a unit has no issues that need to be resolved before shipping
* @return {string[]|[]} list of problems, or an empty array if there are no problems to report
*/
checkDeviceForShipping() {
if (!this.connected) return ['not connected to the Iron Pi hardware agent'];
const { deviceInputStates } = this;
if (!deviceInputStates) return ['Iron Pi hardware agent has not reported input states'];
const { hasError, inputStates } = deviceInputStates;
const issues = [];
if (hasError !== false) issues.push('Iron Pi hardware agent has errors');
const [mainBoardInputStates] = inputStates;
if (mainBoardInputStates) {
const { digitalInputs, analogInputs, connectButtonPressed } = mainBoardInputStates;
for (let inputIdx = 0; inputIdx < 8; ++inputIdx) {
const inputName = `Input ${inputIdx + 1}`;
const digitalValue = digitalInputs[inputIdx];
const analogValue = analogInputs[inputIdx];
if (typeof digitalValue !== 'boolean') issues.push(`${inputName} digital input is missing`);else if (digitalValue) issues.push(`${inputName} digital input is high`);
if (!Number.isFinite(analogValue)) issues.push(`${inputName} analog input is missing`);else if (Math.abs(analogValue) > 0.1) issues.push(`${inputName} analog reading is out of range: ${analogValue.toFixed(2)}V`);
}
if (connectButtonPressed) issues.push('Connect button is pressed');
} else {
issues.push('Missing input states for main board');
}
return issues;
}
_callMethod(methodName, methodArg = {}) {
return new Promise((resolve, reject) => {
if (this._ipcClient.isConnected()) {
const methodSerial = ++this._curMethodSerial;
const timeout = setTimeout(() => {
this._inFlightRequests.delete(methodSerial);
reject(Error('method call timed out'));
}, 20000);
this._inFlightRequests.set(methodSerial, { timeout, resolve, reject });
this._send({
methodName,
methodSerial,
methodArg
});
} else {
reject(Error('not connected to iron pi hardware agent'));
}
});
}
_send(msg) {
this._ipcClient.send(JSON.stringify(msg));
}
}
exports.IronPiDeviceClient = IronPiDeviceClient;
exports.default = IronPiDeviceClient;