UNPKG

incyclist-ant-plus

Version:

Incyclist library for ANT+ - originally forked from longhorn/ant-plus

247 lines (246 loc) 10.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const stream_1 = require("stream"); const ant_device_1 = require("../ant-device"); const DEFAULT_BINARY_PATH = 'antserver.exe'; const MAX_PING_TIMEOUT = 3000; class AntServerBinding extends ant_device_1.AntDevice { constructor(props) { super(props); this.props = props || {}; this.server = undefined; this.requests = {}; this.serverData = ''; } getDevices() { return []; } launchServer(path) { return (0, child_process_1.spawn)(path, []); } startServer() { return __awaiter(this, void 0, void 0, function* () { if (this.server) return; return new Promise(resolve => { this.logEvent({ message: 'starting ANT+ Server' }); const path = this.props.binaryPath || DEFAULT_BINARY_PATH; let error; try { this.server = this.launchServer(path); if (!this.server) { this.logEvent({ message: 'ANT+ Server could not be started' }); return resolve(false); } this.server.once('error', (err) => { this.logEvent({ message: 'ANT+ Server could not be started', error: err.message }); return resolve(false); }); this.server.on('spawn', () => { this.logEvent({ message: 'ANT+ Server spawned' }); this.server.on('error', this.onServerError.bind(this)); }); } catch (err) { error = err.message; } if (!this.server) { this.logEvent({ message: 'ANT+ Server could not be started', error }); return resolve(false); } this.server.on('close', this.onServerStopped.bind(this)); this.server.stdout.on('error', (err) => { this.logEvent({ message: 'ANT+ output error', error: err.message }); }); this.server.stdin.on('error', (err) => { this.logEvent({ message: 'ANT+ input error', error: err.message }); }); this.server.stdout.pipe(new stream_1.PassThrough()) .on('data', this.onServerData.bind(this)); return resolve(true); }); }); } stopServer() { if (!this.server) return; this.server.removeAllListeners(); this.server.kill(); this.logEvent({ message: 'stopping ANT+ Server' }); } onServerError(err) { if (!this.server) return; this.logEvent({ message: 'ANT+ Server error', error: err.message }); } onServerStopped() { this.server.removeAllListeners(); this.server = undefined; } onServerData(data) { const str = data.toString(); const lines = str.split(/\r?\n/); if (this.serverData) { lines[0] = this.serverData + lines[0]; } if (str.charAt[str.length - 1] !== '\r' && str.charAt[str.length - 1] !== '\n') { const removed = lines.splice(-1); if (removed) this.serverData = removed[0]; } lines.forEach(line => this.onServerMessage(line)); } onServerMessage(str) { try { if (this.props.serverDebug && !str.startsWith('debug/ping')) this.logEvent({ message: 'Ant+ Server [IN]:', msg: str }); const parts = str.split('/'); if (parts[0] === 'response') { const requestId = parts[1]; const request = this.requests[requestId]; if (request) { parts.splice(0, 2); request.resolve(...parts); } } else if (parts[0] === 'message') { const data = Buffer.from(parts[1], 'hex'); this.onMessage(data); } else if (parts[0] === 'error') { const message = parts[2]; this.logEvent({ message: 'ANT+Server error', error: message }); } else if (parts[0] === 'debug') { if (parts[2] === 'ping') this.lastPingTS = Date.now(); } } catch (err) { this.logEvent({ message: 'error', fn: 'onServerMessage()', error: err.message || err, stack: err.stack }); } } sendServerMessage(message) { try { const output = `message/${message}`; if (this.props.serverDebug) this.logEvent({ message: 'Ant+ Server [OUT]:', msg: output }); this.server.stdin.write(`${output}\n`); return true; } catch (err) { this.logEvent({ message: 'Ant+ Server out error:', error: err.message }); return false; } } sendServerPing() { try { if (this.lastPingTS !== undefined) { const ts = Date.now() - this.lastPingTS; if (ts > MAX_PING_TIMEOUT) { this.logEvent({ message: 'ping timeout' }); this.lastPingTS = undefined; } } const output = `ping/${Date.now()}`; this.server.stdin.write(`${output}\n`); return true; } catch (err) { this.logEvent({ message: 'Ant+ Server out error:', error: err.message }); return false; } return true; } sendRequest(command, ...args) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { const requestId = Date.now(); let message = command; if (args) message += args.reduce((p, c) => `${p}/${c}`, ''); this.requests[requestId] = { message, resolve, reject }; try { const output = `request/${requestId}/${message}`; if (this.props.serverDebug) this.logEvent({ message: 'Ant+ Server [OUT]:', msg: output }); this.server.stdin.write(`${output}\n`); } catch (err) { this.logEvent({ message: 'Ant+ Server request out error:', error: err.message, requestId }); reject(err); } }); }); } open() { return __awaiter(this, void 0, void 0, function* () { yield this.startServer(); if (this.server) { try { const { deviceNo = 0 } = this.props; this.logEvent({ message: 'opening ANT+ device', deviceNo }); const res = yield this.sendRequest('open', deviceNo); if (res === true || res === 'true') { if (!this.pingIv) this.pingIv = setInterval(() => { this.sendServerPing(); }, 100); const started = yield this.startup(this.props.startupTimeout); if (!started) { yield this.close(); this.logEvent({ message: 'ANT+ device could not be opened', reason: 'startup failed' }); return false; } this.deviceNo = deviceNo; this.channels = []; for (let i = 0; i < this.maxChannels; i++) this.channels.push(null); this.logEvent({ message: 'ANT+ device opened', deviceNo }); return true; return true; } else { this.logEvent({ message: 'ANT+ device could not be opened', reason: 'open failed' }); this.close(); return false; } } catch (err) { this.logEvent({ message: 'ANT+ device could not be opened', reason: 'error', error: err.message }); return false; } } }); } close() { return __awaiter(this, void 0, void 0, function* () { if (this.server) { if (this.pingIv) { clearInterval(this.pingIv); this.pingIv = undefined; } const res = yield this.sendRequest('close'); if (res) { this.stopServer(); return true; } return false; } return false; }); } write(data) { if (this.props.debug) { const logger = this.props.logger || console; this.logEvent({ message: 'ANT+ SEND', data: data.toString('hex') }); } const payload = data.slice(2); this.sendServerMessage(payload.toString('hex')); } } exports.default = AntServerBinding;