UNPKG

incyclist-ant-plus

Version:

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

363 lines (362 loc) 14.8 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AntDevice = void 0; const usb_1 = require("usb"); const consts_1 = require("./consts"); const messages_1 = require("./messages"); const ant_channel_1 = __importDefault(require("./ant-channel")); const supportedDevices = [ { vendor: 0x0fcf, product: 0x1008, name: 'GarminStick2' }, { vendor: 0x0fcf, product: 0x1009, name: 'GarminStick3' }, ]; class AntDevice { constructor(props) { this.detachedKernelDriver = false; this.canScan = false; this.props = props || {}; this.maxChannels = undefined; this.channels = []; this.getDevices(); } logEvent(event) { if (this.props && this.props.logger) { const logger = this.props.logger; if (logger.logEvent) logger.logEvent(event); else if (logger.log) { const str = Object.keys(event).map(k => ({ key: k, data: event[k] })) .reduce((p, d, i) => i === 0 ? `${d.key}:${d.data}` : `${p}, ${d.key}:${d.data}`, ''); logger.log(str); } } } getMaxChannels() { return this.maxChannels; } isScanEnabled() { return this.canScan; } getDeviceNumber() { return this.deviceNo; } getDevices() { if (AntDevice.devices === undefined) { try { const allDevices = (0, usb_1.getDeviceList)(); if (!allDevices || allDevices.length === 0) return []; const available = allDevices .filter((d) => { const { idVendor, idProduct } = d.deviceDescriptor; return supportedDevices.find(sd => sd.vendor === idVendor && sd.product === idProduct) !== undefined; }); AntDevice.devices = available.map(device => ({ device, inUse: false })); if (AntDevice.devices.length == 0) { AntDevice.devices = undefined; } } catch (_a) { } } return AntDevice.devices; } markAsUsed(deviceNo) { var _a; if ((_a = AntDevice.devices) === null || _a === void 0 ? void 0 : _a[deviceNo]) AntDevice.devices[deviceNo].inUse = true; } releaseFromUsed(deviceNo) { var _a; if ((_a = AntDevice.devices) === null || _a === void 0 ? void 0 : _a[deviceNo]) AntDevice.devices[deviceNo].inUse = false; } open() { return __awaiter(this, void 0, void 0, function* () { const available = this.getDevices(); if (!available || available.length === 0) return this.props.detailedStartReport ? 'NoStick' : false; let found = -1; const { deviceNo, startupTimeout } = this.props; if (deviceNo !== undefined && deviceNo >= 0) { if (available.length <= deviceNo || available[deviceNo].inUse) return this.props.detailedStartReport ? 'NoStick' : false; const opened = yield this.openUSBDevice(available[deviceNo].device); if (opened) found = deviceNo; } else { let i = 0; while (found === -1 && i < available.length) { const current = i; const deviceInfo = available[i++]; if (!deviceInfo.inUse) { const opened = yield this.openUSBDevice(deviceInfo.device); if (opened) found = current; } } } if (found !== -1) { const started = yield this.startup(startupTimeout); if (!started) { yield this.close(); return this.props.detailedStartReport ? 'StartupError' : false; } this.deviceNo = found; this.markAsUsed(found); this.channels = []; for (let i = 0; i < this.maxChannels; i++) this.channels.push(null); return this.props.detailedStartReport ? 'Success' : true; } }); } close() { return __awaiter(this, void 0, void 0, function* () { for (let i = 0; i < this.maxChannels; i++) { if (this.channels[i] !== null) yield this.channels[i].stopScanner(); if (this.channels[i] !== null) yield this.channels[i].stopAllSensors(); } const closed = yield this.closeUSBDevice(); if (!closed) return false; this.releaseFromUsed(this.deviceNo); this.deviceNo = undefined; this.maxChannels = undefined; this.channels = []; AntDevice.devices = undefined; return true; }); } openUSBDevice(device) { return __awaiter(this, void 0, void 0, function* () { this.device = device; if (!this.device) return false; try { this.device.open(); this.iface = this.device.interfaces[0]; try { if (this.iface.isKernelDriverActive()) { this.detachedKernelDriver = true; this.iface.detachKernelDriver(); } } catch (_a) { } this.iface.claim(); } catch (_b) { this.device.close(); this.device = undefined; this.iface = undefined; return false; } this.inEp = this.iface.endpoints[0]; this.inEp.on('data', (data) => { if (!data.length) { return; } if (this.leftover) { data = Buffer.concat([this.leftover, data]); this.leftover = undefined; } if (data.readUInt8(0) !== 0xA4) { this.onError('SYNC missing', data); } const len = data.length; let beginBlock = 0; while (beginBlock < len) { if (beginBlock + 1 === len) { this.leftover = data.slice(beginBlock); break; } const blockLen = data.readUInt8(beginBlock + 1); const endBlock = beginBlock + blockLen + 4; if (endBlock > len) { this.leftover = data.slice(beginBlock); break; } const readData = data.slice(beginBlock, endBlock); this.onMessage(readData); beginBlock = endBlock; } }); this.inEp.on('error', (err) => { if (this.props.debug) { const logger = this.props.logger || console; logger.log('ERROR RECV: ', err); } }); this.inEp.on('end', () => { if (this.props.debug) { const logger = this.props.logger || console; logger.log('STOP RECV: '); } }); this.inEp.startPoll(); this.outEp = this.iface.endpoints[1]; this.outEp.on('error', (err) => { if (this.props.debug) { const logger = this.props.logger || console; logger.log('ERROR OUTP: ', err); } }); if (this.iface.endpoints.length > 2) { this.iface.endpoints.forEach(ep => ep.on('error', () => { })); } return true; }); } startup(timeout) { return __awaiter(this, void 0, void 0, function* () { const sleep = (ms) => { return new Promise(resolve => setTimeout(resolve, ms)); }; return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { let to = undefined; if (timeout) { to = setTimeout(() => { resolve(false); }, timeout); } yield this.sendMessage(messages_1.Messages.resetSystem(), { msgId: consts_1.Constants.MESSAGE_STARTUP }); yield sleep(1000); const data = yield this.sendMessage(messages_1.Messages.requestMessage(0, consts_1.Constants.MESSAGE_CAPABILITIES), { msgId: consts_1.Constants.MESSAGE_CAPABILITIES }); this.maxChannels = data.readUInt8(3); this.canScan = (data.readUInt8(7) & 0x06) === 0x06; yield this.sendMessage(messages_1.Messages.setNetworkKey(), { event: consts_1.Constants.MESSAGE_NETWORK_KEY }); if (to) clearTimeout(to); resolve(true); })); }); } closeUSBDevice() { return __awaiter(this, void 0, void 0, function* () { if (!this.device || !this.inEp || !this.inEp || !this.outEp) return true; return new Promise(resolve => { this.inEp.on('error', console.log); this.outEp.on('error', console.log); this.inEp.stopPoll(() => { this.iface.release(true, (error) => { if (error) { return resolve(false); } if (this.detachedKernelDriver) { this.detachedKernelDriver = false; try { this.iface.attachKernelDriver(); } catch (err) { this.logEvent({ message: 'error closing USBDevice', reason: err.message }); } resolve(true); this.device = undefined; this.inEp = undefined; this.outEp = undefined; return; } this.device.reset((error) => { if (error) { return resolve(false); } this.device.close(); resolve(true); this.device = undefined; this.inEp = undefined; this.outEp = undefined; }); }); }); }); }); } getChannel() { const freeChanneldIdx = this.channels.findIndex(c => c === null); if (freeChanneldIdx === -1) return null; const channel = new ant_channel_1.default(freeChanneldIdx, this); this.channels[freeChanneldIdx] = channel; return channel; } freeChannel(channel) { this.channels[channel.getChannelNo()] = null; } write(data) { if (!data) return; if (this.props.debug) { const logger = this.props.logger || console; this.logEvent({ message: 'ANT+ SEND', data: data.toString('hex') }); } this.outEp.transfer(data, (error) => { if (error) this.logEvent({ message: 'ANT+ SEND ERROR', data: data.toString('hex'), error }); }); } sendMessage(data, waitFor) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => { const { msgId, event } = waitFor; this.waitingFor = { msgId, event, resolve }; this.write(data); }); }); } onMessage(data) { if (this.props.debug) { this.logEvent({ message: 'ANT+ RECV', data: data.toString('hex') }); } if (data.length < 4 || data.readUInt8(0) !== consts_1.Constants.MESSAGE_TX_SYNC) { this.logEvent({ message: 'ANT+ RECV ERROR', data: data.toString('hex'), error: 'Illegal message' }); return; } const msgLength = data.readUInt8(1); if (data.length < msgLength + 3) { this.logEvent({ message: 'ANT+ RECV ERROR', data: data.toString('hex'), error: 'Illegal message' }); return; } const messageID = data.readUInt8(2); if (this.waitingFor !== undefined && this.waitingFor.msgId) { if (messageID === this.waitingFor.msgId) { this.waitingFor.resolve(data); this.waitingFor = undefined; return; } } else if (this.waitingFor !== undefined && this.waitingFor.event) { const event = data.readUInt8(4); if (messageID === consts_1.Constants.MESSAGE_CHANNEL_EVENT && event === this.waitingFor.event) { this.waitingFor.resolve(data); this.waitingFor = undefined; return; } } this.channels.forEach((channel, channelNo) => { if (!channel) return; const msgChannel = data.readUInt8(messages_1.Messages.BUFFER_INDEX_CHANNEL_NUM); if (msgChannel === channelNo) { channel.onMessage(data); } }); } onError(error, message) { this.logEvent({ message: 'ANT+ ERROR', error, msg: message.toString('hex') }); } } exports.AntDevice = AntDevice;