@hangtime/grip-connect
Version:
Griptonite Motherboard, Tindeq Progressor, PitchSix Force Board, WHC-06, Entralpi, Climbro, mySmartBoard: Bluetooth API Force-Sensing strength analysis for climbers
167 lines • 7.42 kB
JavaScript
import { Device } from "../device.model.js";
/**
* Represents a Weiheng - WH-C06 (or MAT Muscle Meter) device.
* To use this device enable: `chrome://flags/#enable-experimental-web-platform-features`.
* {@link https://googlechrome.github.io/samples/web-bluetooth/scan.html| Web Bluetooth}
* {@link https://weihengmanufacturer.com}
*/
export class WHC06 extends Device {
/**
* Offset for the byte location in the manufacturer data to extract the weight.
* @type {number}
* @static
* @readonly
* @constant
*/
static weightOffset = 10;
/**
* Company identifier for WH-C06, also used by 'TomTom International BV': https://www.bluetooth.com/specifications/assigned-numbers/
* @type {number}
* @static
* @readonly
* @constant
*/
static manufacturerId = 256;
/**
* To track disconnection timeout.
* @type {number|null}
* @private
*/
advertisementTimeout = null;
/**
* The limit in seconds when timeout is triggered
* @type {number}
* @private
* @readonly
*/
advertisementTimeoutTime = 10;
// /**
// * Offset for the byte location in the manufacturer data to determine weight stability.
// * @type {number}
// * @static
// * @readonly
// * @constant
// */
// private static readonly stableOffset: number = 14
constructor() {
super({
filters: [
{
// name: "IF_B7",
manufacturerData: [
{
companyIdentifier: 0x0100, // 256
},
],
},
],
services: [],
});
}
/**
* Connects to a Bluetooth device.
* @param {Function} [onSuccess] - Optional callback function to execute on successful connection. Default logs success.
* @param {Function} [onError] - Optional callback function to execute on error. Default logs the error.
*/
connect = async (onSuccess = () => console.log("Connected successfully"), onError = (error) => console.error(error)) => {
try {
// Only data matching the optionalManufacturerData parameter to requestDevice is included in the advertisement event: https://github.com/WebBluetoothCG/web-bluetooth/issues/598
const optionalManufacturerData = this.filters.flatMap((filter) => filter.manufacturerData?.map((data) => data.companyIdentifier) || []);
const bluetooth = await this.getBluetooth();
this.bluetooth = await bluetooth.requestDevice({
filters: this.filters,
optionalManufacturerData,
});
if (!this.bluetooth.gatt) {
throw new Error("GATT is not available on this device");
}
// Update timestamp
this.updateTimestamp();
// Device has no services / characteristics, so we directly call onSuccess
onSuccess();
this.bluetooth.addEventListener("advertisementreceived", (event) => {
const data = event.manufacturerData.get(WHC06.manufacturerId);
if (data) {
// Handle recieved data
const weight = (data.getUint8(WHC06.weightOffset) << 8) | data.getUint8(WHC06.weightOffset + 1);
// const stable = (data.getUint8(STABLE_OFFSET) & 0xf0) >> 4
// const unit = data.getUint8(STABLE_OFFSET) & 0x0f
const receivedTime = Date.now();
const receivedData = weight / 100;
const numericData = receivedData - this.applyTare(receivedData) * -1;
// Add data to downloadable Array
this.downloadPackets.push({
received: receivedTime,
sampleNum: this.dataPointCount,
battRaw: 0,
samples: [numericData],
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),
});
}
// Reset "still advertising" counter
this.resetAdvertisementTimeout();
});
// When the companyIdentifier is provided we want to get manufacturerData using watchAdvertisements.
if (optionalManufacturerData.length) {
// Receive events when the system receives an advertisement packet from a watched device.
// To use this function in Chrome: chrome://flags/#enable-experimental-web-platform-features has to be enabled.
// More info: https://chromestatus.com/feature/5180688812736512
if (typeof this.bluetooth.watchAdvertisements === "function") {
await this.bluetooth.watchAdvertisements();
}
else {
throw new Error("watchAdvertisements isn't supported. For Chrome, enable it at chrome://flags/#enable-experimental-web-platform-features.");
}
}
}
catch (error) {
onError(error);
}
};
/**
* Custom check if a Bluetooth device is connected.
* For the WH-C06 device, the `gatt.connected` property remains `false` even after the device is connected.
* @returns {boolean} A boolean indicating whether the device is connected.
*/
isConnected = () => {
return !!this.bluetooth;
};
/**
* Resets the timeout that checks if the device is still advertising.
*/
resetAdvertisementTimeout = () => {
// Clear the previous timeout
if (this.advertisementTimeout) {
clearTimeout(this.advertisementTimeout);
}
// Set a new timeout to stop tracking if no advertisement is received
this.advertisementTimeout = globalThis.setTimeout(() => {
// Mimic a disconnect
const disconnectedEvent = new Event("gattserverdisconnected");
Object.defineProperty(disconnectedEvent, "target", {
value: this.bluetooth,
writable: false,
});
// Print error to the console
console.error(`No advertisement received for ${this.advertisementTimeoutTime} seconds, stopping tracking..`);
this.onDisconnected(disconnectedEvent);
}, this.advertisementTimeoutTime * 1000); // 10 seconds
};
}
//# sourceMappingURL=wh-c06.model.js.map