UNPKG

@hangtime/grip-connect

Version:

Griptonite Motherboard, Tindeq Progressor, PitchSix Force Board, WHC-06, Entralpi, Climbro, mySmartBoard: Bluetooth API Force-Sensing strength analysis for climbers

164 lines (142 loc) 4.78 kB
import { Device } from "../device.model.js" import type { IClimbro } from "../../interfaces/device/climbro.interface.js" /** * Climbro protocol constants */ enum ClimbroResponses { /** * 240 - Battery data marker */ BAT_DAT = 0xf0, /** * 245 - Sensor data marker */ SENS_DAT = 0xf5, /** * 246 - 36kg value */ DAT_36KG = 0xf6, } /** * Represents a Climbro device. * {@link https://climbro.com/} */ export class Climbro extends Device implements IClimbro { /** * Battery constants */ private static readonly minBatteryDisc: number = 112 private static readonly maxBatteryDisc: number = 230 private static readonly batRangeDisc: number = this.maxBatteryDisc - this.minBatteryDisc private static readonly batLevelCoef: number = 100 / this.batRangeDisc /** * Synchronization flag used to track the current data type being processed. * Set to BAT_DAT when processing battery data, SENS_DAT when processing sensor data. * @type {number} * @private */ private flagSynchro = 0 /** * Current battery level percentage calculated from the device's battery voltage. * @type {number} * @private */ private batteryLevel = 0 constructor() { super({ filters: [{ namePrefix: "Climbro" }], services: [ { name: "UART Transparent Service", id: "uart", uuid: "49535343-fe7d-4ae5-8fa9-9fafd205e455", characteristics: [ { name: "Read/Notify", id: "rx", uuid: "49535343-1e4d-4bd9-ba61-23c647249616", }, ], }, ], }) } /** * Retrieves battery level from the device. * @returns {Promise<string | undefined>} A Promise that resolves with the battery level. */ battery = async (): Promise<string | undefined> => { // Battery level is continuously updated via notifications return this.batteryLevel.toString() } /** * Handles data received from the device, processes force measurements and battery data * according to the Climbro protocol. * * @param {DataView} value - The notification event. */ override handleNotifications = (value: DataView): void => { if (value) { // Update timestamp this.updateTimestamp() if (value.buffer) { const receivedTime: number = Date.now() // Convert DataView to Uint8Array for easier processing const buffer = new Uint8Array(value.buffer) const byteCount = buffer.length for (let i = 0; i < byteCount; i++) { let signalValue = buffer[i] // Check for battery data marker if (signalValue === ClimbroResponses.BAT_DAT) { this.flagSynchro = ClimbroResponses.BAT_DAT continue } // Check for sensor data marker if (signalValue === ClimbroResponses.SENS_DAT) { this.flagSynchro = ClimbroResponses.SENS_DAT continue } // Check if signal is the reserved word for 36kg and convert it if (signalValue === ClimbroResponses.DAT_36KG) { signalValue = 36 } // Process battery level signal if (this.flagSynchro === ClimbroResponses.BAT_DAT) { this.batteryLevel = Climbro.batLevelCoef * (signalValue - Climbro.minBatteryDisc) continue } // Process force signal if (this.flagSynchro === ClimbroResponses.SENS_DAT) { // Process force data inline const forceValue = signalValue const numericData = forceValue - this.applyTare(forceValue) // Add data to downloadable array this.downloadPackets.push({ received: receivedTime, sampleNum: this.dataPointCount, battRaw: this.batteryLevel, samples: [forceValue], masses: [numericData], }) // Check for max weight this.massMax = Math.max(Number(this.massMax), Number(numericData)).toFixed(1) // Update running sum and count const currentMassTotal = Math.max(-1000, Number(numericData)) this.massTotalSum += currentMassTotal this.dataPointCount++ // Calculate the average dynamically this.massAverage = (this.massTotalSum / this.dataPointCount).toFixed(1) // Check if device is being used this.activityCheck(numericData) this.notifyCallback({ massMax: this.massMax, massAverage: this.massAverage, massTotal: Math.max(-1000, numericData).toFixed(1), }) continue } } } } } }