@hangtime/grip-connect
Version:
Griptonite Motherboard, Tindeq Progressor, PitchSix Force Board, WHC-06, Entralpi, Climbro, mySmartBoard: Bluetooth API Force-Sensing strength analysis for climbers
334 lines (312 loc) • 11.6 kB
text/typescript
import { Device } from "../device.model.js"
import type { IForceBoard } from "../../interfaces/device/forceboard.interface.js"
/**
* Represents a PitchSix Force Board device.
* {@link https://pitchsix.com}
*/
export class ForceBoard extends Device implements IForceBoard {
constructor() {
super({
filters: [{ name: "Force Board" }],
services: [
{
name: "Device Information",
id: "device",
uuid: "0000180a-0000-1000-8000-00805f9b34fb",
characteristics: [
// {
// name: "Serial Number String (Blocked)",
// id: "serial",
// uuid: "00002a25-0000-1000-8000-00805f9b34fb",
// },
// {
// name: "Firmware Revision String (Blocked)",
// id: "firmware",
// uuid: "00002a26-0000-1000-8000-00805f9b34f",
// },
{
name: "Manufacturer Name String",
id: "manufacturer",
uuid: "00002a29-0000-1000-8000-00805f9b34fb",
},
],
},
{
name: "Battery Service",
id: "battery",
uuid: "0000180f-0000-1000-8000-00805f9b34fb",
characteristics: [
{
name: "Battery Level",
id: "level",
uuid: "00002a19-0000-1000-8000-00805f9b34fb",
},
],
},
{
name: "Nordic Device Firmware Update (DFU) Service",
id: "dfu",
uuid: "0000fe59-0000-1000-8000-00805f9b34fb",
characteristics: [
{
name: "Buttonless DFU",
id: "dfu",
uuid: "8ec90003-f315-4f60-9fb8-838830daea50",
},
],
},
{
name: "",
id: "",
uuid: "f3641400-00b0-4240-ba50-05ca45bf8abc",
characteristics: [
{
name: "Read + Indicate",
id: "",
uuid: "f3641401-00b0-4240-ba50-05ca45bf8abc",
},
],
},
{
name: "Humidity Service",
id: "humidity",
uuid: "cf194c6f-d0c1-47b2-aeff-dc610f09bd18",
characteristics: [
{
name: "Humidity Level",
id: "level",
uuid: "cf194c70-d0c1-47b2-aeff-dc610f09bd18",
},
],
},
{
name: "Temperature Serivce",
id: "temperature",
uuid: "3a90328c-c266-4c76-b05a-6af6104a0b13",
characteristics: [
{
name: "Read",
id: "level",
uuid: "3a90328d-c266-4c76-b05a-6af6104a0b13",
},
],
},
{
name: "Forceboard Service",
id: "forceboard",
uuid: "9a88d67f-8df2-4afe-9e0d-c2bbbe773dd0",
characteristics: [
{
name: "Write",
id: "",
uuid: "9a88d680-8df2-4afe-9e0d-c2bbbe773dd0",
},
{
name: "Read + Indicate",
id: "",
uuid: "9a88d681-8df2-4afe-9e0d-c2bbbe773dd0",
},
{
name: "Force Data",
id: "rx",
uuid: "9a88d682-8df2-4afe-9e0d-c2bbbe773dd0",
},
{
name: "Tare",
id: "tare",
uuid: "9a88d683-8df2-4afe-9e0d-c2bbbe773dd0",
},
{
name: "Read",
id: "",
uuid: "9a88d685-8df2-4afe-9e0d-c2bbbe773dd0",
},
{
name: "Threshold",
id: "threshold",
uuid: "9a88d686-8df2-4afe-9e0d-c2bbbe773dd0",
},
{
name: "Read + Write",
id: "",
uuid: "9a88d687-8df2-4afe-9e0d-c2bbbe773dd0",
},
{
name: "Serial / Read + Write",
id: "",
uuid: "9a88d688-8df2-4afe-9e0d-c2bbbe773dd0",
},
{
name: "Read + Write",
id: "",
uuid: "9a88d689-8df2-4afe-9e0d-c2bbbe773dd0",
},
],
},
{
name: "Weight Service",
id: "weight",
uuid: "467a8516-6e39-11eb-9439-0242ac130002",
characteristics: [
{
name: "Device Mode",
id: "tx",
uuid: "467a8517-6e39-11eb-9439-0242ac130002",
},
{
name: "Read + Write",
id: "",
uuid: "467a8518-6e39-11eb-9439-0242ac130002",
},
],
},
],
commands: {
START_WEIGHT_MEAS: String.fromCharCode(0x04), // Streaming Data Mode: continuously streams force data.
TARE_SCALE: String.fromCharCode(0x05), // Tare function: zeroes out the current load value
START_QUICK_MEAS: String.fromCharCode(0x06), // Quick Start Mode: Starts data transmission when a force value exceeds the Threshold and stops data transmission when the force data drops below the Threshold.
STOP_WEIGHT_MEAS: String.fromCharCode(0x07), // Idle Mode: Force Board is idle.
},
})
}
/**
* Retrieves battery or voltage information from the device.
* @returns {Promise<string | undefined>} A Promise that resolves with the battery or voltage information,
*/
battery = async (): Promise<string | undefined> => {
return await this.read("battery", "level", 250)
}
/**
* Handles data received from the device, processes weight measurements,
* and updates mass data including maximum and average values.
* It also handles command responses for retrieving device information.
*
* @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()
const dataArray = new Uint8Array(value.buffer)
// First two bytes contain the number of samples in the packet
const numSamples = (dataArray[0] << 8) | dataArray[1]
// Process each sample (3 bytes per sample)
for (let i = 0; i < numSamples; i++) {
const offset = 2 + i * 3 // Skip the first 2 bytes which indicate number of samples
if (offset + 2 < dataArray.length) {
// Sample = byte1*32768 + byte2*256 + byte3
const receivedData = dataArray[offset] * 32768 + dataArray[offset + 1] * 256 + dataArray[offset + 2]
// Convert from LBS to KG
const convertedReceivedData = receivedData * 0.453592
// Tare correction
const numericData = convertedReceivedData - this.applyTare(convertedReceivedData)
// Add data to downloadable Array
this.downloadPackets.push({
received: receivedTime,
sampleNum: this.dataPointCount,
battRaw: 0,
samples: [convertedReceivedData],
masses: [numericData],
})
// Update massMax
this.massMax = Math.max(Number(this.massMax), numericData).toFixed(1)
// Update running sum and count
const currentMassTotal = Math.max(-1000, 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)
// Notify with weight data
this.notifyCallback({
massMax: this.massMax,
massAverage: this.massAverage,
massTotal: Math.max(-1000, numericData).toFixed(1),
})
}
}
}
}
}
/**
* Retrieves humidity level from the device.
* @returns {Promise<string>} A Promise that resolves with the humidity level,
*/
humidity = async (): Promise<string | undefined> => {
return await this.read("humidity", "level", 250)
}
/**
* Retrieves manufacturer information from the device.
* @returns {Promise<string>} A Promise that resolves with the manufacturer information,
*/
manufacturer = async (): Promise<string | undefined> => {
return await this.read("device", "manufacturer", 250)
}
/**
* Stops the data stream on the specified device by setting it to Idle mode.
* @returns {Promise<void>} A promise that resolves when the stream is stopped.
*/
stop = async (): Promise<void> => {
await this.write("weight", "tx", this.commands.STOP_WEIGHT_MEAS, 0)
}
/**
* Starts streaming data from the specified device in Streaming Data Mode.
* @param {number} [duration=0] - The duration of the stream in milliseconds. If set to 0, stream will continue indefinitely.
* @returns {Promise<void>} A promise that resolves when the streaming operation is completed.
*/
stream = async (duration = 0): Promise<void> => {
// Start streaming data - Streaming Data Mode
await this.write("weight", "tx", this.commands.START_WEIGHT_MEAS, duration)
}
/**
* Sets the threshold in Lbs for the Quick Start mode.
* @param {number} thresholdLbs - The threshold value in pounds.
* @returns {Promise<void>} A promise that resolves when the threshold is set.
*/
threshold = async (thresholdLbs: number): Promise<void> => {
const thresholdHex = thresholdLbs.toString(16).padStart(6, "0")
// 3-byte array from the hex string
const bytes = new Uint8Array(3)
bytes[0] = parseInt(thresholdHex.substring(0, 2), 16)
bytes[1] = parseInt(thresholdHex.substring(2, 4), 16)
bytes[2] = parseInt(thresholdHex.substring(4, 6), 16)
await this.write("forceboard", "threshold", String.fromCharCode(...bytes), 0)
}
/**
* Tares the Force Board device using a characteristic to zero out the current load value.
* @returns {Promise<void>} A promise that resolves when the tare operation is completed.
*/
tareByCharacteristic = async (): Promise<void> => {
// Send tare command (0x01) to the tare characteristic
const tareValue = String.fromCharCode(0x01)
await this.write("forceboard", "tare", tareValue, 0)
}
/**
* Initiates a tare routine via the Device Mode characteristic.
* Writes 0x05 to the Device Mode characteristic to zero out the current load value.
* @returns {Promise<void>} A promise that resolves when the tare operation is completed.
*/
tareByMode = async (): Promise<void> => {
await this.write("weight", "tx", this.commands.TARE_SCALE, 0)
}
/**
* Retrieves temperature information from the device.
* @returns {Promise<string>} A Promise that resolves with the temperature information,
*/
temperature = async (): Promise<string | undefined> => {
return await this.read("temperature", "level", 250)
}
/**
* Starts the Force Board in Quick Start mode.
* Writes 0x06 to the Device Mode characteristic.
* @param {number} [duration=0] - The duration in milliseconds. If set to 0, mode will continue indefinitely.
* @returns {Promise<void>} A promise that resolves when the operation is completed.
*/
quick = async (duration = 0): Promise<void> => {
// Start in Quick Start mode
await this.write("weight", "tx", this.commands.START_QUICK_MEAS, duration)
}
}