gd-ant-plus
Version:
A node module for ANT+ - forked from longhorn/ant-plus
882 lines (779 loc) • 23.8 kB
text/typescript
import events = require('events');
import usb = require('usb');
export enum Constants {
MESSAGE_TX_SYNC = 0xA4,
DEFAULT_NETWORK_NUMBER = 0x00,
// Configuration messages
MESSAGE_CHANNEL_UNASSIGN = 0x41,
MESSAGE_CHANNEL_ASSIGN = 0x42,
MESSAGE_CHANNEL_ID = 0x51,
MESSAGE_CHANNEL_PERIOD = 0x43,
MESSAGE_CHANNEL_SEARCH_TIMEOUT = 0x44,
MESSAGE_CHANNEL_FREQUENCY = 0x45,
MESSAGE_CHANNEL_TX_POWER = 0x60,
MESSAGE_NETWORK_KEY = 0x46,
MESSAGE_TX_POWER = 0x47,
MESSAGE_PROXIMITY_SEARCH = 0x71,
MESSAGE_ENABLE_RX_EXT = 0x66,
MESSAGE_LIB_CONFIG = 0x6E,
MESSAGE_CHANNEL_OPEN_RX_SCAN = 0x5B,
// Notification messages
MESSAGE_STARTUP = 0x6F,
// Control messages
MESSAGE_SYSTEM_RESET = 0x4A,
MESSAGE_CHANNEL_OPEN = 0x4B,
MESSAGE_CHANNEL_CLOSE = 0x4C,
MESSAGE_CHANNEL_REQUEST = 0x4D,
// Data messages
MESSAGE_CHANNEL_BROADCAST_DATA = 0x4E,
MESSAGE_CHANNEL_ACKNOWLEDGED_DATA = 0x4F,
MESSAGE_CHANNEL_BURST_DATA = 0x50,
// Channel event messages
MESSAGE_CHANNEL_EVENT = 0x40,
// Requested response messages
MESSAGE_CHANNEL_STATUS = 0x52,
//MESSAGE_CHANNEL_ID = 0x51,
MESSAGE_VERSION = 0x3E,
MESSAGE_CAPABILITIES = 0x54,
MESSAGE_SERIAL_NUMBER = 0x61,
// Message parameters
CHANNEL_TYPE_TWOWAY_RECEIVE = 0x00,
CHANNEL_TYPE_TWOWAY_TRANSMIT = 0x10,
CHANNEL_TYPE_SHARED_RECEIVE = 0x20,
CHANNEL_TYPE_SHARED_TRANSMIT = 0x30,
CHANNEL_TYPE_ONEWAY_RECEIVE = 0x40,
CHANNEL_TYPE_ONEWAY_TRANSMIT = 0x50,
RADIO_TX_POWER_MINUS20DB = 0x00,
RADIO_TX_POWER_MINUS10DB = 0x01,
RADIO_TX_POWER_0DB = 0x02,
RADIO_TX_POWER_PLUS4DB = 0x03,
RESPONSE_NO_ERROR = 0x00,
EVENT_RX_SEARCH_TIMEOUT = 0x01,
EVENT_RX_FAIL = 0x02,
EVENT_TX = 0x03,
EVENT_TRANSFER_RX_FAILED = 0x04,
EVENT_TRANSFER_TX_COMPLETED = 0x05,
EVENT_TRANSFER_TX_FAILED = 0x06,
EVENT_CHANNEL_CLOSED = 0x07,
EVENT_RX_FAIL_GO_TO_SEARCH = 0x08,
EVENT_CHANNEL_COLLISION = 0x09,
EVENT_TRANSFER_TX_START = 0x0A,
CHANNEL_IN_WRONG_STATE = 0x15,
CHANNEL_NOT_OPENED = 0x16,
CHANNEL_ID_NOT_SET = 0x18,
CLOSE_ALL_CHANNELS = 0x19,
TRANSFER_IN_PROGRESS = 0x1F,
TRANSFER_SEQUENCE_NUMBER_ERROR = 0x20,
TRANSFER_IN_ERROR = 0x21,
MESSAGE_SIZE_EXCEEDS_LIMIT = 0x27,
INVALID_MESSAGE = 0x28,
INVALID_NETWORK_NUMBER = 0x29,
INVALID_LIST_ID = 0x30,
INVALID_SCAN_TX_CHANNEL = 0x31,
INVALID_PARAMETER_PROVIDED = 0x33,
EVENT_QUEUE_OVERFLOW = 0x35,
USB_STRING_WRITE_FAIL = 0x70,
CHANNEL_STATE_UNASSIGNED = 0x00,
CHANNEL_STATE_ASSIGNED = 0x01,
CHANNEL_STATE_SEARCHING = 0x02,
CHANNEL_STATE_TRACKING = 0x03,
CAPABILITIES_NO_RECEIVE_CHANNELS = 0x01,
CAPABILITIES_NO_TRANSMIT_CHANNELS = 0x02,
CAPABILITIES_NO_RECEIVE_MESSAGES = 0x04,
CAPABILITIES_NO_TRANSMIT_MESSAGES = 0x08,
CAPABILITIES_NO_ACKNOWLEDGED_MESSAGES = 0x10,
CAPABILITIES_NO_BURST_MESSAGES = 0x20,
CAPABILITIES_NETWORK_ENABLED = 0x02,
CAPABILITIES_SERIAL_NUMBER_ENABLED = 0x08,
CAPABILITIES_PER_CHANNEL_TX_POWER_ENABLED = 0x10,
CAPABILITIES_LOW_PRIORITY_SEARCH_ENABLED = 0x20,
CAPABILITIES_SCRIPT_ENABLED = 0x40,
CAPABILITIES_SEARCH_LIST_ENABLED = 0x80,
CAPABILITIES_LED_ENABLED = 0x01,
CAPABILITIES_EXT_MESSAGE_ENABLED = 0x02,
CAPABILITIES_SCAN_MODE_ENABLED = 0x04,
CAPABILITIES_PROX_SEARCH_ENABLED = 0x10,
CAPABILITIES_EXT_ASSIGN_ENABLED = 0x20,
CAPABILITIES_FS_ANTFS_ENABLED = 0x40,
TIMEOUT_NEVER = 0xFF,
}
export class Messages {
static BUFFER_INDEX_MSG_LEN: number = 1;
static BUFFER_INDEX_MSG_TYPE: number = 2;
static BUFFER_INDEX_CHANNEL_NUM: number = 3;
static BUFFER_INDEX_MSG_DATA: number = 4;
static BUFFER_INDEX_EXT_MSG_BEGIN: number = 12;
static resetSystem(): Buffer {
const payload: number[] = [];
payload.push(0x00);
return this.buildMessage(payload, Constants.MESSAGE_SYSTEM_RESET);
}
static requestMessage(channel: number, messageID: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
payload.push(messageID);
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_REQUEST);
}
static setNetworkKey(): Buffer {
const payload: number[] = [];
payload.push(Constants.DEFAULT_NETWORK_NUMBER);
payload.push(0xB9);
payload.push(0xA5);
payload.push(0x21);
payload.push(0xFB);
payload.push(0xBD);
payload.push(0x72);
payload.push(0xC3);
payload.push(0x45);
return this.buildMessage(payload, Constants.MESSAGE_NETWORK_KEY);
}
static assignChannel(channel: number, type = 'receive'): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
if (type === 'receive') {
payload.push(Constants.CHANNEL_TYPE_TWOWAY_RECEIVE);
} else if (type === 'receive_only') {
payload.push(Constants.CHANNEL_TYPE_ONEWAY_RECEIVE);
} else if (type === 'receive_shared') {
payload.push(Constants.CHANNEL_TYPE_SHARED_RECEIVE);
} else if (type === 'transmit') {
payload.push(Constants.CHANNEL_TYPE_TWOWAY_TRANSMIT);
} else if (type === 'transmit_only') {
payload.push(Constants.CHANNEL_TYPE_ONEWAY_TRANSMIT);
} else if (type === 'transmit_shared') {
payload.push(Constants.CHANNEL_TYPE_SHARED_TRANSMIT);
} else {
throw 'type not allowed';
}
payload.push(Constants.DEFAULT_NETWORK_NUMBER);
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_ASSIGN);
}
static setDevice(channel: number, deviceID: number, deviceType: number, transmissionType: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
payload = payload.concat(this.intToLEHexArray(deviceID, 2));
payload = payload.concat(this.intToLEHexArray(deviceType));
payload = payload.concat(this.intToLEHexArray(transmissionType));
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_ID);
}
static searchChannel(channel: number, timeout: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
payload = payload.concat(this.intToLEHexArray(timeout));
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_SEARCH_TIMEOUT);
}
static setPeriod(channel: number, period: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
payload = payload.concat(this.intToLEHexArray(period));
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_PERIOD);
}
static setFrequency(channel: number, frequency: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
payload = payload.concat(this.intToLEHexArray(frequency));
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_FREQUENCY);
}
static setRxExt(): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(0));
payload = payload.concat(this.intToLEHexArray(1));
return this.buildMessage(payload, Constants.MESSAGE_ENABLE_RX_EXT);
}
static libConfig(channel: number, how: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
payload = payload.concat(this.intToLEHexArray(how));
return this.buildMessage(payload, Constants.MESSAGE_LIB_CONFIG);
}
static openRxScan(): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(0));
payload = payload.concat(this.intToLEHexArray(1));
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_OPEN_RX_SCAN);
}
static openChannel(channel: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_OPEN);
}
static closeChannel(channel: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_CLOSE);
}
static unassignChannel(channel: number): Buffer {
let payload: number[] = [];
payload = payload.concat(this.intToLEHexArray(channel));
return this.buildMessage(payload, Constants.MESSAGE_CHANNEL_UNASSIGN);
}
static acknowledgedData ( payload : number[] ) {
return Messages.buildMessage( payload, Constants.MESSAGE_CHANNEL_ACKNOWLEDGED_DATA )
}
static broadcastData ( payload : number[] ) {
return Messages.buildMessage( payload, Constants.MESSAGE_CHANNEL_BROADCAST_DATA )
}
static buildMessage(payload: number[] = [], msgID = 0x00): Buffer {
const m: number[] = [];
m.push(Constants.MESSAGE_TX_SYNC);
m.push(payload.length);
m.push(msgID);
payload.forEach((byte) => {
m.push(byte);
});
m.push(this.getChecksum(m));
return Buffer.from(m);
}
static intToLEHexArray(int: number, numBytes = 1): number[] {
numBytes = numBytes || 1;
const a: number[] = [];
const b = Buffer.from(this.decimalToHex(int, numBytes * 2), 'hex');
let i = b.length - 1;
while (i >= 0) {
a.push(b[i]);
i--;
}
return a;
}
static decimalToHex(d: number, numDigits: number): string {
let hex = Number(d).toString(16);
numDigits = numDigits || 2;
while (hex.length < numDigits) {
hex = '0' + hex;
}
// console.log(hex);
return hex;
}
static getChecksum(message: any[]): number {
let checksum = 0;
message.forEach((byte) => {
checksum = (checksum ^ byte) % 0xFF;
});
return checksum;
}
}
export interface ICancellationToken {
cancel(): void;
}
class CancellationTokenListener {
_completed = false;
constructor(private fn: (d: any) => void, private cb: (err: Error) => void) { }
cancel() {
if (!this._completed) {
this._completed = true;
// @ts-ignore
usb.removeListener('attach', this.fn);
this.cb(new Error('Canceled'));
}
}
}
export class DeviceInfo extends usb.Device {
inUse : boolean
}
export class USBDriver extends events.EventEmitter {
private static deviceInUse: usb.Device[] = [];
private device: usb.Device;
private iface: usb.Interface;
private detachedKernelDriver = false;
private inEp: usb.InEndpoint & events.EventEmitter;
private outEp: usb.OutEndpoint & events.EventEmitter;
private leftover: Buffer;
private usedChannels: number = 0;
private attachedSensors: BaseSensor[] = [];
maxChannels: number = 0;
canScan: boolean = false;
props: any;
waitingForStartup: boolean = false;
constructor(private idVendor: number, private idProduct: number, dbgLevel=0, props={}) {
super();
this.setMaxListeners(50);
this.props = props;
usb.setDebugLevel(dbgLevel);
}
private getDevices() {
try {
const allDevices = usb.getDeviceList();
return allDevices
.filter((d) => d.deviceDescriptor.idVendor === this.idVendor && d.deviceDescriptor.idProduct === this.idProduct)
.filter(d => USBDriver.deviceInUse.indexOf(d) === -1);
}
catch (err) {
const logger = this.props.logger || console;
logger.log('getDevices ERROR :', err);
return [];
}
}
static listDevices( filterFn?: (d:usb.Device)=>boolean) {
const allDevices = usb.getDeviceList();
const info = [...allDevices];
info.forEach( d => (d as DeviceInfo).inUse = USBDriver.deviceInUse.indexOf(d)!==-1 )
if ( filterFn===undefined)
return info;
return info
.filter( filterFn)
}
blockDevice( device : usb.Device ) {
USBDriver.deviceInUse.push(device);
}
unblockDevice( device : usb.Device ) {
let deviceIdx = undefined;
for ( var i=0; i<USBDriver.deviceInUse.length; i++) {
let blocked = USBDriver.deviceInUse[i];
if (blocked.deviceAddress==device.deviceAddress) {
deviceIdx = i;
}
}
if (deviceIdx!=undefined) {
USBDriver.deviceInUse.splice(deviceIdx,1);
}
}
is_present(): boolean {
return this.getDevices().length > 0;
}
open(): boolean {
const devices = this.getDevices();
while (devices.length) {
try {
this.device = devices.shift();
this.device.open();
this.iface = this.device.interfaces[0];
try {
if (this.iface.isKernelDriverActive()) {
this.detachedKernelDriver = true;
this.iface.detachKernelDriver();
}
} catch {
// Ignore kernel driver errors;
}
this.iface.claim();
break;
} catch {
// Ignore the error and try with the next device, if present
this.device.close();
this.device = undefined;
this.iface = undefined;
}
}
if (!this.device) {
return false;
}
USBDriver.deviceInUse.push(this.device);
this.waitingForStartup = true;
this.inEp = this.iface.endpoints[0] as usb.InEndpoint;
this.inEp.on('data', (data: Buffer) => {
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: any) => {
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] as usb.OutEndpoint;
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: (err: Error) => void): ICancellationToken {
let ct: CancellationTokenListener;
const doOpen = () => {
try {
const result = this.open();
if (result) {
ct._completed = true;
try {
cb(undefined);
} catch {
// ignore errors
}
} 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()) {
// @ts-ignore
usb.removeListener('attach', fn);
}
}
};
usb.on('attach', fn);
if (this.is_present()) {
// @ts-ignore
setImmediate(() => usb.emit('attach', this.device));
}
return ct = new CancellationTokenListener(fn, cb);
}
close() {
this.detach_all();
this.inEp.stopPoll(() => {
// @ts-ignore
this.iface.release(true, () => {
if (this.detachedKernelDriver) {
this.detachedKernelDriver = false;
try {
this.iface.attachKernelDriver();
} catch {
// Ignore kernel driver errors;
}
}
this.iface = undefined;
this.device.reset(() => {
this.device.close();
this.emit('shutdown');
const devIdx = USBDriver.deviceInUse.indexOf(this.device);
if (devIdx >= 0) {
USBDriver.deviceInUse.splice(devIdx, 1);
}
// @ts-ignore
if (usb.listenerCount('attach')) {
// @ts-ignore
usb.emit('attach', this.device);
}
this.device = undefined;
});
});
});
}
reset() {
this.detach_all();
this.maxChannels = 0;
this.usedChannels = 0;
this.write(Messages.resetSystem());
}
isScanning(): boolean {
return this.usedChannels === -1;
}
attach(sensor: BaseSensor, forScan: boolean): boolean {
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;
}
detach(sensor: BaseSensor): boolean {
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: BaseSensor) => sensor.detach());
}
write(data: Buffer) {
if (this.props.debug) {
const logger = this.props.logger || console;
logger.log('DATA SEND: ', data);
}
this.outEp.transfer(data, (error) => {
if (error) {
//console.log('ERROR SEND: ', error);
}
});
}
read(data: Buffer) {
if (this.props.debug) {
const logger = this.props.logger || console;
logger.log('DATA RECV: ', data);
}
const messageID = data.readUInt8(2);
if (messageID === Constants.MESSAGE_STARTUP) {
this.waitingForStartup = false;
this.write(Messages.requestMessage(0, Constants.MESSAGE_CAPABILITIES));
} else if (messageID === Constants.MESSAGE_CAPABILITIES) {
this.maxChannels = data.readUInt8(3);
this.canScan = (data.readUInt8(7) & 0x06) === 0x06;
this.write(Messages.setNetworkKey());
} else if (messageID === Constants.MESSAGE_CHANNEL_EVENT && data.readUInt8(4) === Constants.MESSAGE_NETWORK_KEY) {
this.emit('startup', data);
} else {
this.emit('read', data);
}
}
}
export class GarminStick2 extends USBDriver {
constructor(dbgLevel = 0) {
super(0x0fcf, 0x1008, dbgLevel);
}
}
export class GarminStick3 extends USBDriver {
constructor(dbgLevel = 0) {
super(0x0fcf, 0x1009, dbgLevel);
}
}
export interface IDecodeDataCallback {
(data: Buffer): void;
}
export class BaseSensor extends events.EventEmitter {
channel: number;
deviceID: number;
transmissionType: number;
protected decodeDataCbk: IDecodeDataCallback;
constructor(private stick: USBDriver) {
super();
stick.on('read', this.handleEventMessages.bind(this));
}
protected scan(type: string, frequency: number) {
if (this.channel !== undefined) {
throw 'already attached';
}
if (!this.stick.canScan) {
throw 'stick cannot scan';
}
const channel = 0;
const onStatus = (status) => {
switch (status.msg) {
case 0x01:
switch (status.code) {
case Constants.EVENT_CHANNEL_CLOSED:
this.write(Messages.unassignChannel(channel));
break;
default:
break;
}
break;
case Constants.MESSAGE_CHANNEL_ASSIGN:
this.write(Messages.setDevice(channel, 0, 0, 0));
break;
case Constants.MESSAGE_CHANNEL_ID:
this.write(Messages.setFrequency(channel, frequency));
break;
case Constants.MESSAGE_CHANNEL_FREQUENCY:
this.write(Messages.setRxExt());
break;
case Constants.MESSAGE_ENABLE_RX_EXT:
this.write(Messages.libConfig(channel, 0xE0));
break;
case Constants.MESSAGE_LIB_CONFIG:
this.write(Messages.openRxScan());
break;
case Constants.MESSAGE_CHANNEL_OPEN_RX_SCAN:
if (this.decodeDataCbk) {
this.stick.on('read', this.decodeDataCbk);
}
process.nextTick(() => this.emit('attached'));
break;
case Constants.MESSAGE_CHANNEL_CLOSE:
break;
case Constants.MESSAGE_CHANNEL_UNASSIGN:
this.removeListener('status', onStatus);
this.channel = undefined;
if (this.decodeDataCbk) {
this.stick.removeListener('read', this.decodeDataCbk);
}
process.nextTick(() => this.emit('detached'));
break;
default:
break;
}
};
if (this.stick.isScanning()) {
this.channel = channel;
this.deviceID = 0;
this.transmissionType = 0;
this.on('status', onStatus);
if (this.decodeDataCbk) {
this.stick.on('read', this.decodeDataCbk);
}
process.nextTick(() => this.emit('attached'));
} else if (this.stick.attach(this, true)) {
this.channel = channel;
this.deviceID = 0;
this.transmissionType = 0;
this.on('status', onStatus);
this.write(Messages.assignChannel(channel, type));
} else {
throw 'cannot attach';
}
}
protected attach(channel: number, type: string, deviceID: number, deviceType: number, transmissionType: number,
timeout: number, period: number, frequency: number) {
if (this.channel !== undefined) {
throw 'already attached';
}
if (!this.stick.attach(this, false)) {
throw 'cannot attach';
}
this.channel = channel;
this.deviceID = deviceID;
this.transmissionType = transmissionType;
const onStatus = (status) => {
switch (status.msg) {
case 0x01:
switch (status.code) {
case Constants.EVENT_CHANNEL_CLOSED:
this.write(Messages.unassignChannel(channel));
break;
default:
break;
}
break;
case Constants.MESSAGE_CHANNEL_ASSIGN:
this.write(Messages.setDevice(channel, deviceID, deviceType, transmissionType));
break;
case Constants.MESSAGE_CHANNEL_ID:
this.write(Messages.searchChannel(channel, timeout));
break;
case Constants.MESSAGE_CHANNEL_SEARCH_TIMEOUT:
this.write(Messages.setFrequency(channel, frequency));
break;
case Constants.MESSAGE_CHANNEL_FREQUENCY:
this.write(Messages.setPeriod(channel, period));
break;
case Constants.MESSAGE_CHANNEL_PERIOD:
this.write(Messages.libConfig(channel, 0xE0));
break;
case Constants.MESSAGE_LIB_CONFIG:
this.write(Messages.openChannel(channel));
break;
case Constants.MESSAGE_CHANNEL_OPEN:
if (this.decodeDataCbk) {
this.stick.on('read', this.decodeDataCbk);
}
process.nextTick(() => this.emit('attached'));
break;
case Constants.MESSAGE_CHANNEL_CLOSE:
break;
case Constants.MESSAGE_CHANNEL_UNASSIGN:
this.removeListener('status', onStatus);
this.channel = undefined;
if (this.decodeDataCbk) {
this.stick.removeListener('read', this.decodeDataCbk);
}
process.nextTick(() => this.emit('detached'));
break;
default:
break;
}
};
this.on('status', onStatus);
this.write(Messages.assignChannel(channel, type));
}
detach() {
if (this.channel === undefined) {
return;
}
this.write(Messages.closeChannel(this.channel));
if (!this.stick.detach(this)) {
throw 'error detaching';
}
}
protected write(data: Buffer) {
this.stick.write(data);
}
private handleEventMessages(data: Buffer) {
const messageID = data.readUInt8(2);
if (messageID === Constants.MESSAGE_CHANNEL_EVENT && data.readUInt8(3) === this.channel) {
const status = {
msg: data.readUInt8(4),
code: data.readUInt8(5),
};
this.emit('status', status);
}
}
}
export class AntPlusBaseSensor extends BaseSensor {
protected scan(type: string) {
return super.scan(type, 57);
}
protected attach(channel: number, type: string, deviceID: number, deviceType: number, transmissionType: number,
timeout: number, period: number) {
return super.attach(channel, type, deviceID, deviceType, transmissionType, timeout, period, 57);
}
}
export class AntPlusSensor extends AntPlusBaseSensor {
protected scan() {
throw 'scanning unsupported';
}
protected attach(channel: number, type: string, deviceID: number, deviceType: number, transmissionType: number,
timeout: number, period: number) {
return super.attach(channel, type, deviceID, deviceType, transmissionType, timeout, period);
}
}
export class AntPlusScanner extends AntPlusBaseSensor {
protected scan(type: string) {
return super.scan(type);
}
protected attach() {
throw 'attach unsupported';
}
}