incyclist-ant-plus
Version:
Incyclist library for ANT+ - originally forked from longhorn/ant-plus
379 lines (378 loc) • 16.4 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.MAX_CHANNEL_COLLISION = exports.CLOSE_TIMEOUT = exports.START_TIMEOUT = void 0;
const consts_1 = require("./consts");
const events_1 = __importDefault(require("events"));
const messages_1 = require("./messages");
exports.START_TIMEOUT = 5000;
exports.CLOSE_TIMEOUT = 2000;
exports.MAX_CHANNEL_COLLISION = 10;
class Channel extends events_1.default {
constructor(channelNo, device, props) {
super();
this.isScanner = false;
this.isSensor = false;
this.isWriting = false;
this.messageQueue = [];
this.ackErrorCount = 0;
this.channelNo = channelNo;
this.device = device;
this.props = props || {};
}
onDeviceData(profile, deviceID, deviceState) {
if (this.isScanner) {
this.emit('detected', profile, deviceID);
}
this.emit('data', profile, deviceID, deviceState);
}
getChannelNo() {
return this.channelNo;
}
getDevice() {
return this.device;
}
setProps(props) {
this.props = props || {};
}
getProps() {
return this.props;
}
startScanner() {
return __awaiter(this, void 0, void 0, function* () {
if (this.isSensor)
yield this.stopAllSensors();
return new Promise((done) => __awaiter(this, void 0, void 0, function* () {
let to;
let starting = true;
try {
const send = (data, opts) => __awaiter(this, void 0, void 0, function* () {
if (!starting)
return;
return yield this.sendMessage(data, opts);
});
to = setTimeout(() => {
done(false);
starting = false;
}, exports.START_TIMEOUT);
yield send(messages_1.Messages.assignChannel(this.channelNo, 'receive'));
yield send(messages_1.Messages.setDevice(this.channelNo, 0, 0, 0));
yield send(messages_1.Messages.setFrequency(this.channelNo, 57));
yield send(messages_1.Messages.setRxExt());
yield send(messages_1.Messages.libConfig(this.channelNo, 0xE0));
yield send(messages_1.Messages.openRxScan());
if (to)
clearTimeout(to);
this.isScanner = true;
done(true);
}
catch (err) {
if (to)
clearTimeout(to);
done(false);
}
}));
});
}
stopScanner() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.isScanner)
return true;
yield this.closeChannel();
this.isScanner = false;
});
}
startSensor(sensor) {
return __awaiter(this, void 0, void 0, function* () {
if (this.isScanner)
yield this.stopScanner();
return new Promise((done) => __awaiter(this, void 0, void 0, function* () {
let to;
try {
let isStarting = true;
const send = (data, opts) => __awaiter(this, void 0, void 0, function* () {
if (!isStarting)
return;
return yield this.sendMessage(data, opts);
});
to = setTimeout(() => {
if (isStarting) {
isStarting = false;
done(false);
}
}, exports.START_TIMEOUT);
const { type, transmissionType, timeout, frequency, period } = sensor.getChannelConfiguration();
const deviceID = sensor.getDeviceID();
const deviceType = sensor.getDeviceType();
yield send(messages_1.Messages.assignChannel(this.channelNo, type));
yield send(messages_1.Messages.setDevice(this.channelNo, deviceID, deviceType, transmissionType));
yield send(messages_1.Messages.searchChannel(this.channelNo, timeout));
yield send(messages_1.Messages.setFrequency(this.channelNo, frequency));
yield send(messages_1.Messages.setPeriod(this.channelNo, period));
yield send(messages_1.Messages.libConfig(this.channelNo, 0xE0));
yield send(messages_1.Messages.openChannel(this.channelNo));
isStarting = false;
if (to)
clearTimeout(to);
this.attach(sensor);
this.isSensor = true;
done(this.isSensor);
}
catch (err) {
if (to)
clearTimeout(to);
done(false);
}
}));
});
}
stopAllSensors() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.isSensor)
return true;
this.detach(this.attachedSensor);
yield this.closeChannel();
this.isSensor = false;
});
}
stopSensor(sensor) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.stopAllSensors();
});
}
restartSensor() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.isSensor || !this.attachedSensor)
return true;
this.flush();
yield this.closeChannel({ restart: true });
const sensor = this.attachedSensor;
return new Promise((done) => __awaiter(this, void 0, void 0, function* () {
let to;
let isStarting = true;
try {
const send = (data, opts) => __awaiter(this, void 0, void 0, function* () {
if (!isStarting)
return;
return yield this.sendMessage(data, opts);
});
to = setTimeout(() => {
if (isStarting) {
isStarting = false;
done(false);
}
}, exports.START_TIMEOUT);
const { type, transmissionType, timeout, frequency, period } = sensor.getChannelConfiguration();
const deviceID = sensor.getDeviceID();
const deviceType = sensor.getDeviceType();
yield this.sendMessage(messages_1.Messages.assignChannel(this.channelNo, type));
yield this.sendMessage(messages_1.Messages.setDevice(this.channelNo, deviceID, deviceType, transmissionType));
yield this.sendMessage(messages_1.Messages.searchChannel(this.channelNo, timeout));
yield this.sendMessage(messages_1.Messages.setFrequency(this.channelNo, frequency));
yield this.sendMessage(messages_1.Messages.setPeriod(this.channelNo, period));
yield this.sendMessage(messages_1.Messages.libConfig(this.channelNo, 0xE0));
yield this.sendMessage(messages_1.Messages.openChannel(this.channelNo));
isStarting = false;
if (to)
clearTimeout(to);
this.attach(sensor);
return done(true);
}
catch (err) {
if (to)
clearTimeout(to);
return done(false);
}
}));
});
}
closeChannel() {
return __awaiter(this, arguments, void 0, function* (props = {}) {
const { restart } = props || {};
return new Promise(resolve => {
let isClosing = true;
const close = () => {
isClosing = false;
if (!restart)
this.device.freeChannel(this);
this.off('status', onStatusUpdate);
resolve();
};
const onStatusUpdate = (status) => __awaiter(this, void 0, void 0, function* () {
if (status.msg === 1 && status.code === consts_1.Constants.EVENT_CHANNEL_CLOSED) {
yield this.sendMessage(messages_1.Messages.unassignChannel(this.channelNo));
close();
}
});
this.removeAllListeners();
this.on('status', onStatusUpdate);
setTimeout(() => {
if (isClosing) {
close();
}
}, exports.CLOSE_TIMEOUT);
this.device.write(messages_1.Messages.closeChannel(this.channelNo));
});
});
}
attach(sensor) {
this.attachedSensor = sensor;
sensor.setChannel(this);
this.on('message', (data) => { sensor.onMessage(data); });
this.on('status', (status, data) => { sensor.onEvent(data); });
}
detach(sensor) {
this.attachedSensor = null;
sensor.setChannel(null);
this.off('message', (data) => { sensor.onMessage(data); });
this.off('status', (status, data) => { sensor.onEvent(data); });
}
onMessage(data) {
try {
const messageID = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_TYPE);
const channel = data.readUInt8(messages_1.Messages.BUFFER_INDEX_CHANNEL_NUM);
const prevMsgId = this.messageQueue.length > 0 ? this.messageQueue[0].msgId : undefined;
const resolve = (value) => {
if (this.messageQueue.length === 0)
return;
const msg = this.messageQueue[0];
this.messageQueue.splice(0, 1);
this.isWriting = false;
this.ackErrorCount = 0;
msg.resolve(value);
};
const next = () => {
if (this.messageQueue.length === 0)
return;
const msg = this.messageQueue[0];
if (msg.timeout) {
setTimeout(() => {
resolve(false);
next();
}, msg.timeout);
}
this.isWriting = true;
this.ackErrorCount = 0;
this.device.write(msg.data);
msg.data = undefined;
};
if (messageID === consts_1.Constants.MESSAGE_CHANNEL_EVENT && channel === this.channelNo) {
const status = {
msg: data.readUInt8(4),
code: data.readUInt8(5),
};
this.emit('status', status, data);
if (status.msg !== 1) {
if (prevMsgId === status.msg) {
const success = status.code === 0x00 || status.code === consts_1.Constants.EVENT_TRANSFER_TX_COMPLETED;
resolve(success);
next();
return;
}
}
else {
switch (status.code) {
case consts_1.Constants.EVENT_TRANSFER_TX_COMPLETED:
resolve(true);
next();
return;
case consts_1.Constants.EVENT_TRANSFER_TX_FAILED:
case consts_1.Constants.TRANSFER_IN_PROGRESS:
case consts_1.Constants.TRANSFER_SEQUENCE_NUMBER_ERROR:
case consts_1.Constants.TRANSFER_IN_ERROR:
case consts_1.Constants.MESSAGE_SIZE_EXCEEDS_LIMIT:
case consts_1.Constants.INVALID_MESSAGE:
case consts_1.Constants.EVENT_CHANNEL_CLOSED:
resolve(false);
next();
return;
case consts_1.Constants.EVENT_RX_SEARCH_TIMEOUT:
this.stopScanner();
return;
case consts_1.Constants.EVENT_RX_FAIL:
break;
case consts_1.Constants.EVENT_RX_FAIL_GO_TO_SEARCH:
break;
case consts_1.Constants.EVENT_CHANNEL_COLLISION:
if (this.isWriting) {
this.ackErrorCount++;
}
break;
}
}
}
else if (channel === this.channelNo)
this.emit('message', data);
}
catch (err) {
console.log(err);
}
}
sendMessage(data, opts) {
const props = opts || {};
const msgId = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_TYPE);
const channel = data.readUInt8(messages_1.Messages.BUFFER_INDEX_CHANNEL_NUM);
const { timeout } = props;
return new Promise((resolve, reject) => {
if (channel !== this.channelNo)
reject(new Error('invalid channel'));
let to;
const done = (res) => {
resolve(res);
clearTimeout(to);
to = undefined;
this.isWriting = false;
};
const error = (err) => {
reject(err);
clearTimeout(to);
to = undefined;
this.isWriting = false;
};
if (this.isWriting) {
let found = -1;
do {
found = this.messageQueue.findIndex(qi => qi.msgId === msgId && qi.data);
if (found !== -1) {
const message = this.messageQueue[found];
this.messageQueue.splice(found, 1);
message.resolve(false);
}
} while (found !== -1);
this.messageQueue.push({ msgId, resolve: done, reject: error, data, timeout });
}
else {
this.messageQueue.push({ msgId, resolve: done, reject: error });
this.isWriting = true;
this.device.write(data);
if (timeout && !to)
to = setTimeout(() => {
if (!this.isWriting)
return;
const found = this.messageQueue.findIndex(qi => qi.msgId === msgId);
if (found !== -1) {
this.messageQueue.splice(found, 1);
}
error(new Error('timeout'));
}, timeout);
}
});
}
flush() {
this.messageQueue.forEach(msg => { msg.resolve(false); });
this.messageQueue = [];
this.isWriting = false;
}
}
exports.default = Channel;