UNPKG

ant-plus

Version:
744 lines (656 loc) 25.4 kB
/* * ANT+ profile: https://www.thisisant.com/developer/ant-plus/device-profiles/#521_tab * Spec sheet: https://www.thisisant.com/resources/bicycle-power/ */ import { Messages, SendCallback, AntPlusSensor, AntPlusScanner } from './ant'; class FitnessEquipmentSensorState { constructor(deviceID: number) { this.DeviceID = deviceID; } _EventCount0x19?: number; _EventCount0x1A?: number; DeviceID: number; Temperature?: number; ZeroOffset?: number; SpinDownTime?: number; EquipmentType?: 'Treadmill' | 'Elliptical' | 'Reserved' | 'Rower' | 'Climber' | 'NordicSkier' | 'Trainer/StationaryBike' | 'General'; ElapsedTime?: number; Distance?: number; RealSpeed?: number; VirtualSpeed?: number; HeartRate?: number; HeartRateSource?: 'HandContact' | 'EM' | 'ANT+'; State?: 'OFF' | 'READY' | 'IN_USE' | 'FINISHED'; CycleLength?: number; Incline?: number; Resistance?: number; METs?: number; CaloricBurnRate?: number; Calories?: number; AscendedDistance?: number; DescendedDistance?: number; Strides?: number; Strokes?: number; Cadence?: number; AccumulatedPower?: number; InstantaneousPower?: number; AveragePower?: number; TrainerStatus?: number; TargetStatus?: 'OnTarget' | 'LowSpeed' | 'HighSpeed'; WheelTicks?: number; WheelPeriod?: number; Torque?: number; HwVersion?: number; ManId?: number; ModelNum?: number; SwVersion?: number; SerialNumber?: number; PairedDevices: any[] = []; } class FitnessEquipmentScanState extends FitnessEquipmentSensorState { Rssi: number; Threshold: number; } export class FitnessEquipmentSensor extends AntPlusSensor { static deviceType = 0x11; public attach(channel, deviceID): void { super.attach(channel, 'receive', deviceID, FitnessEquipmentSensor.deviceType, 0, 255, 8192); this.state = new FitnessEquipmentSensorState(deviceID); } private state: FitnessEquipmentSensorState; protected updateState(deviceId, data) { this.state.DeviceID = deviceId; updateState(this, this.state, data); } private _setUserConfiguration(userWeight?: number, bikeWeight?: number, wheelDiameter?: number, gearRatio?: number, cbk?: SendCallback) { const m = userWeight === undefined ? 0xFFFF : Math.max(0, Math.min(65534, Math.round(userWeight * 100))); const df = wheelDiameter === undefined ? 0xFF : Math.round(wheelDiameter * 10) % 10; const mb = bikeWeight === undefined ? 0xFFF : Math.max(0, Math.min(1000, Math.round(bikeWeight * 20))); const d = wheelDiameter === undefined ? 0xFF : Math.max(0, Math.min(254, Math.round(wheelDiameter))); const gr = gearRatio === undefined ? 0x00 : Math.max(1, Math.min(255, Math.round(gearRatio / .03))); const payload = [0x37, m & 0xFF, (m >> 8) & 0xFF, 0xFF, (df & 0xF) | ((mb & 0xF) << 4), (mb >> 4) & 0xF, d & 0xFF, gr & 0xFF]; const msg = Messages.acknowledgedData(this.channel, payload); this.send(msg, cbk); } public setUserConfiguration(cbk: SendCallback); public setUserConfiguration(userWeight: number, cbk?: SendCallback); public setUserConfiguration(userWeight: number, bikeWeight: number, cbk?: SendCallback); public setUserConfiguration(userWeight: number, bikeWeight: number, wheelDiameter: number, cbk?: SendCallback); public setUserConfiguration(userWeight: number, bikeWeight: number, wheelDiameter: number, gearRatio: number, cbk?: SendCallback); public setUserConfiguration(userWeight?: number | SendCallback, bikeWeight?: number | SendCallback, wheelDiameter?: number | SendCallback, gearRatio?: number | SendCallback, cbk?: SendCallback) { if (typeof (userWeight) === 'function') { return this._setUserConfiguration(undefined, undefined, undefined, undefined, userWeight); } else if (typeof (bikeWeight) === 'function') { return this._setUserConfiguration(userWeight, undefined, undefined, undefined, bikeWeight); } else if (typeof (wheelDiameter) === 'function') { return this._setUserConfiguration(userWeight, bikeWeight, undefined, undefined, wheelDiameter); } else if (typeof (gearRatio) === 'function') { return this._setUserConfiguration(userWeight, bikeWeight, wheelDiameter, undefined, gearRatio); } else { return this._setUserConfiguration(userWeight, bikeWeight, wheelDiameter, gearRatio, cbk); } } public setBasicResistance(resistance: number, cbk?: SendCallback) { const res = Math.max(0, Math.min(200, Math.round(resistance * 2))); const payload = [0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, res & 0xFF]; const msg = Messages.acknowledgedData(this.channel, payload); this.send(msg, cbk); } public setTargetPower(power: number, cbk?: SendCallback) { const p = Math.max(0, Math.min(4000, Math.round(power * 4))); const payload = [0x31, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, p & 0xFF, (p >> 8) & 0xFF]; const msg = Messages.acknowledgedData(this.channel, payload); this.send(msg, cbk); } private _setWindResistance(windCoeff?: number, windSpeed?: number, draftFactor?: number, cbk?: SendCallback) { const wc = windCoeff === undefined ? 0xFF : Math.max(0, Math.min(186, Math.round(windCoeff * 100))); const ws = windSpeed === undefined ? 0xFF : Math.max(0, Math.min(254, Math.round(windSpeed + 127))); const df = draftFactor === undefined ? 0xFF : Math.max(0, Math.min(100, Math.round(draftFactor * 100))); const payload = [0x32, 0xFF, 0xFF, 0xFF, 0xFF, wc & 0xFF, ws & 0xFF, df & 0xFF]; const msg = Messages.acknowledgedData(this.channel, payload); this.send(msg, cbk); } public setWindResistance(cbk: SendCallback); public setWindResistance(windCoeff: number, cbk?: SendCallback); public setWindResistance(windCoeff: number, windSpeed: number, cbk?: SendCallback); public setWindResistance(windCoeff: number, windSpeed: number, draftFactor: number, cbk?: SendCallback); public setWindResistance(windCoeff?: number | SendCallback, windSpeed?: number | SendCallback, draftFactor?: number | SendCallback, cbk?: SendCallback) { if (typeof (windCoeff) === 'function') { return this._setWindResistance(undefined, undefined, undefined, windCoeff); } else if (typeof (windSpeed) === 'function') { return this._setWindResistance(windCoeff, undefined, undefined, windSpeed); } else if (typeof (draftFactor) === 'function') { return this._setWindResistance(windCoeff, windSpeed, undefined, draftFactor); } else { return this._setWindResistance(windCoeff, windSpeed, draftFactor, cbk); } } private _setTrackResistance(slope?: number, rollingResistanceCoeff?: number, cbk?: SendCallback) { const s = slope === undefined ? 0xFFFF : Math.max(0, Math.min(40000, Math.round((slope + 200) * 100))); const rr = rollingResistanceCoeff === undefined ? 0xFF : Math.max(0, Math.min(254, Math.round(rollingResistanceCoeff * 20000))); const payload = [0x33, 0xFF, 0xFF, 0xFF, 0xFF, s & 0xFF, (s >> 8) & 0xFF, rr & 0xFF]; const msg = Messages.acknowledgedData(this.channel, payload); this.send(msg, cbk); } public setTrackResistance(cbk: SendCallback); public setTrackResistance(slope: number, cbk?: SendCallback); public setTrackResistance(slope: number, rollingResistanceCoeff: number, cbk?: SendCallback); public setTrackResistance(slope?: number | SendCallback, rollingResistanceCoeff?: number | SendCallback, cbk?: SendCallback) { if (typeof (slope) === 'function') { return this._setTrackResistance(undefined, undefined, slope); } else if (typeof (rollingResistanceCoeff) === 'function') { return this._setTrackResistance(slope, undefined, rollingResistanceCoeff); } else { return this._setTrackResistance(slope, rollingResistanceCoeff, cbk); } } } export class FitnessEquipmentScanner extends AntPlusScanner { protected deviceType() { return FitnessEquipmentSensor.deviceType; } private states: { [id: number]: FitnessEquipmentScanState } = {}; protected createStateIfNew(deviceId) { if (!this.states[deviceId]) { this.states[deviceId] = new FitnessEquipmentScanState(deviceId); } } protected updateRssiAndThreshold(deviceId, rssi, threshold) { this.states[deviceId].Rssi = rssi; this.states[deviceId].Threshold = threshold; } protected updateState(deviceId, data) { updateState(this, this.states[deviceId], data); } } function resetState(state: FitnessEquipmentSensorState | FitnessEquipmentScanState) { 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._EventCount0x1A; delete state.Cadence; delete state.AccumulatedPower; delete state.InstantaneousPower; delete state.AveragePower; delete state.TrainerStatus; delete state.TargetStatus; delete state.AscendedDistance; delete state.DescendedDistance; delete state.Strides; delete state.Strokes; delete state.WheelTicks; delete state.WheelPeriod; delete state.Torque; } function updateState( sensor: FitnessEquipmentSensor | FitnessEquipmentScanner, state: FitnessEquipmentSensorState | FitnessEquipmentScanState, data: Buffer) { const page = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA); switch (page) { case 0x01: { const temperature = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); if (temperature !== 0xFF) { state.Temperature = -25 + temperature * 0.5; } const calBF = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 1); if (calBF & 0x40) { state.ZeroOffset = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 4); } if (calBF & 0x80) { state.SpinDownTime = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 6); } break; } case 0x10: { const equipmentTypeBF = data.readUInt8(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 = 'Reserved'; break; case 22: state.EquipmentType = 'Rower'; break; case 23: state.EquipmentType = 'Climber'; break; case 24: state.EquipmentType = 'NordicSkier'; break; case 25: state.EquipmentType = 'Trainer/StationaryBike'; break; default: state.EquipmentType = 'General'; break; } let elapsedTime = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 2); let distance = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); const speed = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 4); const heartRate = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 6); const capStateBF = data.readUInt8(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) { //Hit rollover value elapsedTime += 64; } } state.ElapsedTime = (state.ElapsedTime || 0) + elapsedTime - oldElapsedTime; if (capStateBF & 0x04) { const oldDistance = (state.Distance || 0) % 256; if (distance !== oldDistance) { if (oldDistance > distance) { //Hit rollover value 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; } switch ((capStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (capStateBF & 0x80) { // lap } break; } case 0x11: { const cycleLen = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); const incline = data.readInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 4); const resistance = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 6); const capStateBF = data.readUInt8(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; } switch ((capStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (capStateBF & 0x80) { // lap } break; } case 0x12: { const mets = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 2); const caloricbr = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 4); const calories = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 6); const capStateBF = data.readUInt8(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; } switch ((capStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (capStateBF & 0x80) { // lap } break; } case 0x13: { const cadence = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 4); let negDistance = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 5); let posDistance = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 6); const flagStateBF = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 7); if (cadence !== 0xFF) { state.Cadence = cadence; } if (flagStateBF & 0x02) { const oldNegDistance = (state.DescendedDistance || 0) % 256; if (negDistance !== oldNegDistance) { if (oldNegDistance > negDistance) { negDistance += 256; } } state.DescendedDistance = (state.DescendedDistance || 0) + negDistance - oldNegDistance; } if (flagStateBF & 0x01) { const oldPosDistance = (state.AscendedDistance || 0) % 256; if (posDistance !== oldPosDistance) { if (oldPosDistance > posDistance) { posDistance += 256; } } state.AscendedDistance = (state.AscendedDistance || 0) + posDistance - oldPosDistance; } switch ((flagStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (flagStateBF & 0x80) { // lap } break; } case 0x14: { let posDistance = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 2); let strides = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); const cadence = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 4); const power = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 5); const flagStateBF = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 7); if (cadence !== 0xFF) { state.Cadence = cadence; } if (power !== 0xFFFF) { state.InstantaneousPower = power; } if (flagStateBF & 0x02) { const oldPosDistance = (state.AscendedDistance || 0) % 256; if (posDistance !== oldPosDistance) { if (oldPosDistance > posDistance) { posDistance += 256; } } state.AscendedDistance = (state.AscendedDistance || 0) + posDistance - oldPosDistance; } if (flagStateBF & 0x01) { const oldStrides = (state.Strides || 0) % 256; if (strides !== oldStrides) { if (oldStrides > strides) { strides += 256; } } state.Strides = (state.Strides || 0) + strides - oldStrides; } switch ((flagStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (flagStateBF & 0x80) { // lap } break; } case 0x16: { let strokes = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); const cadence = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 4); const power = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 5); const flagStateBF = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 7); if (cadence !== 0xFF) { state.Cadence = cadence; } if (power !== 0xFFFF) { state.InstantaneousPower = power; } if (flagStateBF & 0x01) { const oldStrokes = (state.Strokes || 0) % 256; if (strokes !== oldStrokes) { if (oldStrokes > strokes) { strokes += 256; } } state.Strokes = (state.Strokes || 0) + strokes - oldStrokes; } switch ((flagStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (flagStateBF & 0x80) { // lap } break; } case 0x17: { let strides = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); const cadence = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 4); const power = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 5); const flagStateBF = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 7); if (cadence !== 0xFF) { state.Cadence = cadence; } if (power !== 0xFFFF) { state.InstantaneousPower = power; } if (flagStateBF & 0x01) { const oldStrides = (state.Strides || 0) % 256; if (strides !== oldStrides) { if (oldStrides > strides) { strides += 256; } } state.Strides = (state.Strides || 0) + strides - oldStrides; } switch ((flagStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (flagStateBF & 0x80) { // lap } break; } case 0x18: { let strides = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); const cadence = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 4); const power = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 5); const flagStateBF = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 7); if (cadence !== 0xFF) { state.Cadence = cadence; } if (power !== 0xFFFF) { state.InstantaneousPower = power; } if (flagStateBF & 0x01) { const oldStrides = (state.Strides || 0) % 256; if (strides !== oldStrides) { if (oldStrides > strides) { strides += 256; } } state.Strides = (state.Strides || 0) + strides - oldStrides; } switch ((flagStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (flagStateBF & 0x80) { // lap } break; } case 0x19: { const oldEventCount = state._EventCount0x19 || 0; let eventCount = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 1); const cadence = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 2); let accPower = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 3); const power = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 5) & 0xFFF; const trainerStatus = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 6) >> 4; const flagStateBF = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 7); if (eventCount !== oldEventCount) { state._EventCount0x19 = eventCount; if (oldEventCount > eventCount) { //Hit rollover value 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; } switch ((flagStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (flagStateBF & 0x80) { // lap } break; } case 0x1A: { const oldEventCount = state._EventCount0x1A || 0; let eventCount = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 1); let wheelTicks = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 2); let accWheelPeriod = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 3); let accTorque = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 5); const flagStateBF = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 7); if (eventCount !== oldEventCount) { state._EventCount0x1A = eventCount; if (oldEventCount > eventCount) { //Hit rollover value eventCount += 255; } } const oldWheelTicks = (state.WheelTicks || 0) % 256; if (wheelTicks !== oldWheelTicks) { if (oldWheelTicks > wheelTicks) { wheelTicks += 65536; } } state.WheelTicks = (state.WheelTicks || 0) + wheelTicks - oldWheelTicks; const oldWheelPeriod = (state.WheelPeriod || 0) % 256; if (accWheelPeriod !== oldWheelPeriod) { if (oldWheelPeriod > accWheelPeriod) { accWheelPeriod += 65536; } } state.WheelPeriod = (state.WheelPeriod || 0) + accWheelPeriod - oldWheelPeriod; const oldTorque = (state.Torque || 0) % 256; if (accTorque !== oldTorque) { if (oldTorque > accTorque) { accTorque += 65536; } } state.Torque = (state.Torque || 0) + accTorque - oldTorque; switch ((flagStateBF & 0x70) >> 4) { case 1: state.State = 'OFF'; break; case 2: state.State = 'READY'; resetState(state); break; case 3: state.State = 'IN_USE'; break; case 4: state.State = 'FINISHED'; break; default: delete state.State; break; } if (flagStateBF & 0x80) { // lap } break; } case 0x50: { state.HwVersion = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); state.ManId = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 4); state.ModelNum = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 6); break; } case 0x51: { const swRevSup = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 2); const swRevMain = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); const serial = data.readInt32LE(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.BUFFER_INDEX_MSG_DATA + 1); const tot = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 2); const chState = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 3); const devId = data.readUInt16LE(Messages.BUFFER_INDEX_MSG_DATA + 4); const trType = data.readUInt8(Messages.BUFFER_INDEX_MSG_DATA + 6); const devType = data.readUInt8(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; } sensor.emit('fitnessData', state); }