incyclist-ant-plus
Version:
Incyclist library for ANT+ - originally forked from longhorn/ant-plus
247 lines (246 loc) • 10.1 kB
JavaScript
"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;