UNPKG

incyclist-ant-plus

Version:

Incyclist library for ANT+ - originally forked from longhorn/ant-plus

578 lines (577 loc) 23.8 kB
"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; } }