incyclist-ant-plus
Version:
Incyclist library for ANT+ - originally forked from longhorn/ant-plus
339 lines (338 loc) • 12 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.getSticks = void 0;
const events_1 = __importDefault(require("events"));
const usb_1 = require("usb");
const consts_1 = require("./consts");
const messages_1 = require("./messages");
const types_1 = require("./types");
function getSticks(filterFn) {
const info = usb_1.getDeviceList();
if (filterFn === undefined)
return info;
return info
.filter(filterFn);
}
exports.getSticks = getSticks;
class Stick extends events_1.default {
constructor(idVendor, idProduct, props = {}) {
super();
this.idVendor = idVendor;
this.idProduct = idProduct;
this.detachedKernelDriver = false;
this.usedChannels = 0;
this.attachedSensors = [];
this.maxChannels = 0;
this.canScan = false;
this.waitingForStartup = false;
this.setMaxListeners(50);
this.props = props;
}
getDevices() {
try {
const allDevices = usb_1.getDeviceList();
return allDevices
.filter((d) => d.deviceDescriptor.idVendor === this.idVendor && d.deviceDescriptor.idProduct === this.idProduct)
.filter(d => Stick.deviceInUse.indexOf(d) === -1);
}
catch (err) {
const logger = this.props.logger;
logger.log('getDevices ERROR :', err);
return [];
}
}
static listDevices(filterFn) {
const allDevices = usb_1.getDeviceList();
const info = [...allDevices];
info.forEach(d => d.inUse = Stick.deviceInUse.indexOf(d) !== -1);
if (filterFn === undefined)
return info;
return info
.filter(filterFn);
}
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);
}
}
}
blockDevice(device) {
Stick.deviceInUse.push(device);
}
unblockDevice(device) {
let deviceIdx = undefined;
for (var i = 0; i < Stick.deviceInUse.length; i++) {
let blocked = Stick.deviceInUse[i];
if (blocked.deviceAddress == device.deviceAddress) {
deviceIdx = i;
}
}
if (deviceIdx != undefined) {
Stick.deviceInUse.splice(deviceIdx, 1);
}
}
is_present() {
return this.getDevices().length > 0;
}
open() {
return __awaiter(this, void 0, void 0, function* () {
const device = usb_1.findByIds(this.idVendor, this.idProduct);
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;
}
Stick.deviceInUse.push(this.device);
this.waitingForStartup = true;
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.emit('error', { error: 'SYNC missing', context: 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.read(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];
let startupCnt = 0;
let tsStart = Date.now();
let tsTimeout = tsStart + 1000;
this.reset();
let startupTimer = setInterval(() => {
if (!this.waitingForStartup) {
clearInterval(startupTimer);
return;
}
if (Date.now() > tsTimeout) {
startupCnt++;
if (this.props.maxAttempts && startupCnt > this.props.maxAttempts) {
clearInterval(startupTimer);
this.waitingForStartup = false;
this.emit('error', { error: 'Startup timeout', context: 'timeout' });
return;
}
else {
tsStart = Date.now();
tsTimeout = tsStart + 1000;
this.reset();
}
}
}, 100);
return true;
});
}
openAsync(cb) {
let ct;
const doOpen = () => {
try {
const result = this.open();
if (result) {
ct._completed = true;
try {
cb(undefined);
}
catch (_a) {
}
}
else {
return false;
}
}
catch (err) {
cb(err);
}
return true;
};
const fn = (d) => {
if (!d || (d.deviceDescriptor.idVendor === this.idVendor && d.deviceDescriptor.idProduct === this.idProduct)) {
if (doOpen()) {
usb_1.usb.removeListener('attach', fn);
}
}
};
usb_1.usb.on('attach', fn);
if (this.is_present()) {
setImmediate(() => usb_1.usb.emit('attach', this.device));
}
return ct = new types_1.CancellationTokenListener(fn, cb);
}
close() {
this.detach_all();
this.inEp.stopPoll(() => {
this.iface.release(true, () => {
if (this.detachedKernelDriver) {
this.detachedKernelDriver = false;
try {
this.iface.attachKernelDriver();
}
catch (_a) {
}
}
this.iface = undefined;
this.device.reset(() => {
this.device.close();
this.emit('shutdown');
const devIdx = Stick.deviceInUse.indexOf(this.device);
if (devIdx >= 0) {
Stick.deviceInUse.splice(devIdx, 1);
}
if (usb_1.usb.listenerCount('attach')) {
usb_1.usb.emit('attach', this.device);
}
this.device = undefined;
});
});
});
}
reset() {
this.detach_all();
this.maxChannels = 0;
this.usedChannels = 0;
this.write(messages_1.Messages.resetSystem());
}
isScanning() {
return this.usedChannels === -1;
}
attach(sensor, forScan) {
if (this.usedChannels < 0) {
return false;
}
if (forScan) {
if (this.usedChannels !== 0) {
return false;
}
this.usedChannels = -1;
}
else {
if (this.maxChannels <= this.usedChannels) {
return false;
}
++this.usedChannels;
}
this.attachedSensors.push(sensor);
return true;
}
stopScan() {
if (this.usedChannels === -1)
this.usedChannels = 0;
}
detach(sensor) {
const idx = this.attachedSensors.indexOf(sensor);
if (idx < 0) {
return false;
}
if (this.usedChannels < 0) {
this.usedChannels = 0;
}
else {
--this.usedChannels;
}
this.attachedSensors.splice(idx, 1);
return true;
}
detach_all() {
const copy = [...this.attachedSensors];
copy.forEach((sensor) => sensor.detach());
}
write(data) {
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) {
}
});
}
read(data) {
if (this.props.debug) {
this.logEvent({ message: 'ANT+ RECV', data: data.toString('hex'), handlers: this.listenerCount('read') });
}
const messageID = data.readUInt8(2);
if (messageID === consts_1.Constants.MESSAGE_STARTUP) {
this.waitingForStartup = false;
this.write(messages_1.Messages.requestMessage(0, consts_1.Constants.MESSAGE_CAPABILITIES));
}
else if (messageID === consts_1.Constants.MESSAGE_CAPABILITIES) {
this.maxChannels = data.readUInt8(3);
this.canScan = (data.readUInt8(7) & 0x06) === 0x06;
this.write(messages_1.Messages.setNetworkKey());
}
else if (messageID === consts_1.Constants.MESSAGE_CHANNEL_EVENT && data.readUInt8(4) === consts_1.Constants.MESSAGE_NETWORK_KEY) {
this.emit('startup', data);
}
else {
this.emit('read', data);
}
}
}
exports.default = Stick;
Stick.deviceInUse = [];