incyclist-ant-plus
Version:
Incyclist library for ANT+ - originally forked from longhorn/ant-plus
353 lines (352 loc) • 14.3 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());
});
};
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) {
AntDevice.devices[deviceNo].inUse = true;
}
releaseFromUsed(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)
return true;
return new Promise(resolve => {
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 });
}
}
this.iface = undefined;
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;