incyclist-ant-plus
Version:
Incyclist library for ANT+ - originally forked from longhorn/ant-plus
578 lines (577 loc) • 23.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FitnessEquipmentSensorState = void 0;
const consts_1 = require("../consts");
const messages_1 = require("../messages");
const base_sensor_1 = __importStar(require("./base-sensor"));
class FitnessEquipmentSensorState extends base_sensor_1.SensorState {
constructor() {
super(...arguments);
this.PairedDevices = [];
}
}
exports.FitnessEquipmentSensorState = FitnessEquipmentSensorState;
const DEVICE_TYPE = 0x11;
const PROFILE = 'FE';
const PERIOD = 8192;
class FitnessEquipmentSensor extends base_sensor_1.default {
constructor() {
super(...arguments);
this.states = {};
}
getDeviceType() {
return DEVICE_TYPE;
}
getProfile() {
return PROFILE;
}
getDeviceID() {
return this.deviceID;
}
getChannelConfiguration() {
return { type: 'receive', transmissionType: 0, timeout: consts_1.Constants.TIMEOUT_NEVER, period: PERIOD, frequency: 57 };
}
onMessage(data) {
const channel = this.getChannel();
if (!channel)
return;
const channelNo = channel.getChannelNo();
const deviceID = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_EXT_MSG_BEGIN + 1);
const deviceType = data.readUInt8(messages_1.Messages.BUFFER_INDEX_EXT_MSG_BEGIN + 3);
if (data.readUInt8(messages_1.Messages.BUFFER_INDEX_CHANNEL_NUM) !== channelNo || deviceType !== this.getDeviceType()) {
return;
}
if (!this.states[deviceID]) {
this.states[deviceID] = new FitnessEquipmentSensorState(deviceID);
this.states[deviceID].Channel = channelNo;
}
if (data.readUInt8(messages_1.Messages.BUFFER_INDEX_EXT_MSG_BEGIN) & 0x40) {
if (data.readUInt8(messages_1.Messages.BUFFER_INDEX_EXT_MSG_BEGIN + 5) === 0x20) {
this.states[deviceID].Rssi = data.readInt8(messages_1.Messages.BUFFER_INDEX_EXT_MSG_BEGIN + 6);
this.states[deviceID].Threshold = data.readInt8(messages_1.Messages.BUFFER_INDEX_EXT_MSG_BEGIN + 7);
}
}
switch (data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_TYPE)) {
case consts_1.Constants.MESSAGE_CHANNEL_BROADCAST_DATA:
case consts_1.Constants.MESSAGE_CHANNEL_ACKNOWLEDGED_DATA:
case consts_1.Constants.MESSAGE_CHANNEL_BURST_DATA:
updateState(this.states[deviceID], data);
if (this.deviceID === 0 || this.deviceID === deviceID) {
channel.onDeviceData(this.getProfile(), deviceID, this.states[deviceID]);
}
break;
default:
break;
}
}
logEvent(event) {
const channel = this.getChannel();
if (channel && channel.getProps().logger && channel.getProps().logger.logEvent !== undefined) {
try {
channel.getProps().logger.logEvent(event);
}
catch (_a) { }
}
}
waitForRestart() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise(done => {
if (!this.isRestarting)
return done();
const iv = setInterval(() => {
if (!this.isRestarting) {
clearInterval(iv);
done();
}
}, 100);
});
});
}
onEvent(data) {
return __awaiter(this, void 0, void 0, function* () {
const msg = data.readUInt8(4);
const code = data.readUInt8(5);
const event = {
msg: msg.toString(16),
code: code.toString(16),
};
if (event.msg === '1' && code in [0, 3, 4, 5, 6])
return;
this.logEvent({ message: 'channel event', channelNo: this.channel.getChannelNo(), deviceID: this.getDeviceID(), event });
return;
});
}
send(data, props) {
return __awaiter(this, void 0, void 0, function* () {
const { logStr, timeout, args } = props || {};
const channel = this.getChannel();
if (!channel)
return false;
const logEvent = (logStr && channel.getProps().logger && channel.getProps().logger.logEvent !== undefined) ?
(e) => { try {
channel.getProps().logger.logEvent(e);
}
catch (_a) { } } :
(_e) => { };
if (this.isRestarting)
yield this.waitForRestart();
const tsStart = Date.now();
logEvent({ message: 'sending FE message', command: logStr, args, timeout });
const res = yield channel.sendMessage(data, { timeout });
if (this.isRestarting)
yield this.waitForRestart();
const duration = Date.now() - tsStart;
logEvent({ message: 'FE message response', command: logStr, args, response: res, duration });
return res;
});
}
sendUserConfiguration(userWeight, bikeWeight, wheelDiameter, gearRatio) {
return __awaiter(this, void 0, void 0, function* () {
var payload = [];
payload.push(this.channel.getChannelNo());
const logStr = 'setUserConfiguration';
const args = { userWeight, bikeWeight, wheelDiameter, gearRatio };
var m = userWeight === undefined ? 0xFFFF : userWeight;
var mb = bikeWeight === undefined ? 0xFFF : bikeWeight;
var d = wheelDiameter === undefined ? 0xFF : wheelDiameter;
var gr = gearRatio === undefined ? 0x00 : gearRatio;
var dOffset = 0xFF;
if (m !== 0xFFFF)
m = Math.trunc(m * 100);
if (mb !== 0xFFF)
mb = Math.trunc(mb * 20);
if (d !== 0xFF) {
d = d * 1000;
dOffset = d % 10;
d = Math.trunc(d / 10);
}
if (gr !== 0x00) {
gr = Math.trunc(gr / 0.03);
}
payload.push(0x37);
payload.push(m & 0xFF);
payload.push((m >> 8) & 0xFF);
payload.push(0xFF);
payload.push(((mb & 0xF) << 4) | (dOffset & 0xF));
payload.push((mb >> 4) & 0xF);
payload.push(d & 0xFF);
payload.push(gr & 0xFF);
let msg = messages_1.Messages.acknowledgedData(payload);
return yield this.send(msg, { logStr, timeout: this.sendTimeout, args });
});
}
sendBasicResistance(resistance) {
return __awaiter(this, void 0, void 0, function* () {
var payload = [];
payload.push(this.channel.getChannelNo());
const logStr = 'setBasicResistance';
const args = { resistance };
var res = resistance === undefined ? 0 : resistance;
res = res / 0.5;
payload.push(0x30);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(res & 0xFF);
let msg = messages_1.Messages.acknowledgedData(payload);
return yield this.send(msg, { logStr, timeout: this.sendTimeout, args });
});
}
sendTargetPower(power) {
return __awaiter(this, void 0, void 0, function* () {
var payload = [];
payload.push(this.channel.getChannelNo());
const logStr = 'setTargetPower';
const args = { power };
var p = power === undefined ? 0x00 : power;
p = p * 4;
payload.push(0x31);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(p & 0xFF);
payload.push((p >> 8) & 0xFF);
let msg = messages_1.Messages.acknowledgedData(payload);
return yield this.send(msg, { logStr, args, timeout: this.sendTimeout });
});
}
sendWindResistance(windCoeff, windSpeed, draftFactor) {
return __awaiter(this, void 0, void 0, function* () {
var payload = [];
payload.push(this.channel.getChannelNo());
const logStr = 'setWindResistance';
const args = { windCoeff, windSpeed, draftFactor };
var wc = windCoeff === undefined ? 0xFF : windCoeff;
var ws = windSpeed === undefined ? 0xFF : windSpeed;
var df = draftFactor === undefined ? 0xFF : draftFactor;
if (wc !== 0xFF) {
wc = Math.trunc(wc / 0.01);
}
if (ws !== 0xFF) {
ws = Math.trunc(ws + 127);
}
if (df !== 0xFF) {
df = Math.trunc(df / 0.01);
}
payload.push(0x32);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(wc & 0xFF);
payload.push(ws & 0xFF);
payload.push(df & 0xFF);
let msg = messages_1.Messages.acknowledgedData(payload);
return yield this.send(msg, { logStr, args, timeout: this.sendTimeout });
});
}
sendTrackResistance(slope, rrCoeff) {
return __awaiter(this, void 0, void 0, function* () {
var payload = [];
payload.push(this.channel.getChannelNo());
const logStr = 'setTrackResistance';
const args = { slope, rrCoeff };
var s = slope === undefined ? 0xFFFF : slope;
var rr = rrCoeff === undefined ? 0xFF : rrCoeff;
if (s !== 0xFFFF) {
s = Math.trunc((s + 200) / 0.01);
}
if (rr !== 0xFF) {
rr = Math.trunc(rr / 0.00005);
}
payload.push(0x33);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(0xFF);
payload.push(s & 0xFF);
payload.push((s >> 8) & 0xFF);
payload.push(rr & 0xFF);
let msg = messages_1.Messages.acknowledgedData(payload);
return yield this.send(msg, { logStr, args, timeout: this.sendTimeout });
});
}
}
exports.default = FitnessEquipmentSensor;
function resetState(state) {
delete state.ElapsedTime;
delete state.Distance;
delete state.RealSpeed;
delete state.VirtualSpeed;
delete state.HeartRate;
delete state.HeartRateSource;
delete state.CycleLength;
delete state.Incline;
delete state.Resistance;
delete state.METs;
delete state.CaloricBurnRate;
delete state.Calories;
delete state._EventCount0x19;
delete state.Cadence;
delete state.AccumulatedPower;
delete state.InstantaneousPower;
delete state.AveragePower;
delete state.TrainerStatus;
delete state.TargetStatus;
}
function parseFEStateValue(value, state, prev) {
switch (value) {
case 1:
state.State = 'OFF';
break;
case 2:
state.State = 'READY';
if (prev.State !== 'READY')
resetState(state);
break;
case 3:
state.State = 'IN_USE';
break;
case 4:
state.State = 'FINISHED';
break;
default:
delete state.State;
break;
}
}
function updateState(state, data) {
const prev = Object.assign({}, state);
state._RawData = data;
const page = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA);
switch (page) {
case 0x01: {
const temperature = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 3);
if (temperature !== 0xFF) {
state.Temperature = -25 + temperature * 0.5;
}
const calBF = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 1);
if (calBF & 0x40) {
state.ZeroOffset = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 4);
}
if (calBF & 0x80) {
state.SpinDownTime = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 6);
}
break;
}
case 0x10: {
const equipmentTypeBF = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 1);
switch (equipmentTypeBF & 0x1F) {
case 19:
state.EquipmentType = 'Treadmill';
break;
case 20:
state.EquipmentType = 'Elliptical';
break;
case 21:
state.EquipmentType = 'StationaryBike';
break;
case 22:
state.EquipmentType = 'Rower';
break;
case 23:
state.EquipmentType = 'Climber';
break;
case 24:
state.EquipmentType = 'NordicSkier';
break;
case 25:
state.EquipmentType = 'Trainer';
break;
default:
state.EquipmentType = 'General';
break;
}
let elapsedTime = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 2);
let distance = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 3);
const speed = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 4);
const heartRate = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 6);
const capStateBF = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 7);
if (heartRate !== 0xFF) {
switch (capStateBF & 0x03) {
case 3: {
state.HeartRate = heartRate;
state.HeartRateSource = 'HandContact';
break;
}
case 2: {
state.HeartRate = heartRate;
state.HeartRateSource = 'EM';
break;
}
case 1: {
state.HeartRate = heartRate;
state.HeartRateSource = 'ANT+';
break;
}
default: {
delete state.HeartRate;
delete state.HeartRateSource;
break;
}
}
}
elapsedTime /= 4;
const oldElapsedTime = (state.ElapsedTime || 0) % 64;
if (elapsedTime !== oldElapsedTime) {
if (oldElapsedTime > elapsedTime) {
elapsedTime += 64;
}
}
state.ElapsedTime = (state.ElapsedTime || 0) + elapsedTime - oldElapsedTime;
if (capStateBF & 0x04) {
const oldDistance = (state.Distance || 0) % 256;
if (distance !== oldDistance) {
if (oldDistance > distance) {
distance += 256;
}
}
state.Distance = (state.Distance || 0) + distance - oldDistance;
}
else {
delete state.Distance;
}
if (capStateBF & 0x08) {
state.VirtualSpeed = speed / 1000;
delete state.RealSpeed;
}
else {
delete state.VirtualSpeed;
state.RealSpeed = speed / 1000;
}
parseFEStateValue((capStateBF & 0x70) >> 4, state, prev);
if (capStateBF & 0x80) {
}
break;
}
case 0x11: {
const cycleLen = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 3);
const incline = data.readInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 4);
const resistance = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 6);
const capStateBF = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 7);
if (cycleLen !== 0xFF) {
state.CycleLength = cycleLen / 100;
}
if (incline >= -10000 && incline <= 10000) {
state.Incline = incline / 100;
}
if (resistance !== 0xFF) {
state.Resistance = resistance;
}
parseFEStateValue((capStateBF & 0x70) >> 4, state, prev);
if (capStateBF & 0x80) {
}
break;
}
case 0x12: {
const mets = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 2);
const caloricbr = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 4);
const calories = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 6);
const capStateBF = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 7);
if (mets !== 0xFFFF) {
state.METs = mets / 100;
}
if (caloricbr !== 0xFFFF) {
state.CaloricBurnRate = caloricbr / 10;
}
if (capStateBF & 0x01) {
state.Calories = calories;
}
parseFEStateValue((capStateBF & 0x70) >> 4, state, prev);
if (capStateBF & 0x80) {
}
break;
}
case 0x19: {
const oldEventCount = state._EventCount0x19 || 0;
let eventCount = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 1);
const cadence = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 2);
let accPower = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 3);
const power = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 5) & 0xFFF;
const trainerStatus = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 6) >> 4;
const flagStateBF = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 7);
if (eventCount !== oldEventCount) {
state._EventCount0x19 = eventCount;
if (oldEventCount > eventCount) {
eventCount += 255;
}
}
if (cadence !== 0xFF) {
state.Cadence = cadence;
}
if (power !== 0xFFF) {
state.InstantaneousPower = power;
const oldAccPower = (state.AccumulatedPower || 0) % 65536;
if (accPower !== oldAccPower) {
if (oldAccPower > accPower) {
accPower += 65536;
}
}
state.AccumulatedPower = (state.AccumulatedPower || 0) + accPower - oldAccPower;
state.AveragePower = (accPower - oldAccPower) / (eventCount - oldEventCount);
}
state.TrainerStatus = trainerStatus;
switch (flagStateBF & 0x03) {
case 0:
state.TargetStatus = 'OnTarget';
break;
case 1:
state.TargetStatus = 'LowSpeed';
break;
case 2:
state.TargetStatus = 'HighSpeed';
break;
default:
delete state.TargetStatus;
break;
}
parseFEStateValue((flagStateBF & 0x70) >> 4, state, prev);
if (flagStateBF & 0x80) {
}
break;
}
case 0x50: {
state.HwVersion = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 3);
state.ManId = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 4);
state.ModelNum = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 6);
break;
}
case 0x51: {
const swRevSup = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 2);
const swRevMain = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 3);
const serial = data.readInt32LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 4);
state.SwVersion = swRevMain;
if (swRevSup !== 0xFF) {
state.SwVersion += swRevSup / 1000;
}
if (serial !== 0xFFFFFFFF) {
state.SerialNumber = serial;
}
break;
}
case 0x56: {
const idx = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 1);
const tot = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 2);
const chState = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 3);
const devId = data.readUInt16LE(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 4);
const trType = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 6);
const devType = data.readUInt8(messages_1.Messages.BUFFER_INDEX_MSG_DATA + 7);
if (idx === 0) {
state.PairedDevices = [];
}
if (tot > 0) {
state.PairedDevices.push({ id: devId, type: devType, paired: (chState & 0x80) ? true : false });
}
break;
}
default:
return;
}
}