diff --git a/src/devices/haritorax-wireless.ts b/src/devices/haritorax-wireless.ts index 66a8eff..428000a 100644 --- a/src/devices/haritorax-wireless.ts +++ b/src/devices/haritorax-wireless.ts @@ -76,6 +76,9 @@ let fpsModeCharacteristic: string; let correctionCharacteristic: string; let ankleCharacteristic: string; +let batteryDiscovered = false; +let settingsDiscovered = false; + let activeDevices: string[] = []; // JSDoc comments for events @@ -327,12 +330,14 @@ export default class HaritoraXWireless extends EventEmitter { if (trackerName.startsWith("HaritoraX")) { let sensorModeData; - if (sensorMode === 1) sensorModeData = 5; else sensorModeData = 8; const sensorModeBuffer = Buffer.from([sensorModeData]); - log(`Sending sensor mode to ${trackerName}: ${sensorModeData}`); + const sensorModeValue = new DataView( + sensorModeBuffer.buffer + ).getInt8(0); + log(`Sending sensor mode to ${trackerName}: ${sensorModeValue}`); await bluetooth.write( trackerName, settingsService, @@ -340,8 +345,9 @@ export default class HaritoraXWireless extends EventEmitter { sensorModeBuffer ); - const fpsModeBuffer = Buffer.from([fpsMode]); - log(`Sending FPS mode to ${trackerName}: ${fpsMode}`); + const fpsModeBuffer = Buffer.from([fpsMode === 50 ? 1 : 2]); + const fpsModeValue = new DataView(fpsModeBuffer.buffer).getInt8(0); + log(`Sending FPS mode to ${trackerName}: ${fpsModeValue}`); await bluetooth.write( trackerName, settingsService, @@ -349,9 +355,16 @@ export default class HaritoraXWireless extends EventEmitter { fpsModeBuffer ); - const correctionBuffer = Buffer.from([sensorAutoCorrectionBit]); + let correctionBit = 0; + if (sensorAutoCorrection.includes("accel")) correctionBit |= 0x01; + if (sensorAutoCorrection.includes("gyro")) correctionBit |= 0x02; + if (sensorAutoCorrection.includes("mag")) correctionBit |= 0x04; + const correctionBuffer = Buffer.from([correctionBit]); + const correctionValue = new DataView( + correctionBuffer.buffer + ).getInt8(0); log( - `Sending sensor auto correction to ${trackerName}: ${sensorAutoCorrectionBit}` + `Sending sensor auto correction to ${trackerName}: ${correctionValue}` ); await bluetooth.write( trackerName, @@ -361,16 +374,9 @@ export default class HaritoraXWireless extends EventEmitter { ); const ankleBuffer = Buffer.from([ankleMotionDetection ? 1 : 0]); + const ankleValue = new DataView(ankleBuffer.buffer).getInt8(0); log( - `Sending ankle motion detection to ${trackerName}: ${ - ankleMotionDetection ? 1 : 0 - }` - ); - await bluetooth.write( - trackerName, - settingsService, - ankleCharacteristic, - ankleBuffer + `Sending ankle motion detection to ${trackerName}: ${ankleValue}` ); await bluetooth.write( trackerName, @@ -379,13 +385,11 @@ export default class HaritoraXWireless extends EventEmitter { ankleBuffer ); - log( - `Setting the following settings onto tracker ${trackerName}: + log(`Setting the following settings onto tracker ${trackerName}: Sensor mode: ${sensorMode} FPS mode: ${fpsMode} Sensor auto correction: ${sensorAutoCorrection} -Ankle motion detection: ${ankleMotionDetection}` - ); +Ankle motion detection: ${ankleMotionDetection}`); trackerSettings.set(trackerName, [ sensorMode, @@ -702,7 +706,7 @@ Raw hex data calculated to be sent: ${hexValue}`); * @fires this#battery **/ - getBatteryInfo(trackerName: string) { + async getBatteryInfo(trackerName: string) { let batteryRemaining, batteryVoltage, chargeStatus = undefined; @@ -719,23 +723,10 @@ Raw hex data calculated to be sent: ${hexValue}`); return null; } else if (trackerName.startsWith("HaritoraX")) { log(`Reading battery info for ${trackerName}...`); - bluetooth.read( + batteryRemaining = await bluetooth.read( trackerName, batteryService, - batteryLevelCharacteristic, - (err, batteryLevelValue) => { - if (err) { - error(`Error reading batteryLevelCharacteristic: ${err}`); - return; - } - log("Read batteryLevelCharacteristic"); - - batteryRemaining = batteryLevelValue.readUInt8(0); - batteryVoltage = 0; - chargeStatus = ""; - - log(`Tracker ${trackerName} battery remaining: ${batteryRemaining}%`); - } + batteryLevelCharacteristic ); } } @@ -796,113 +787,88 @@ Raw hex data calculated to be sent: ${hexValue}`); trackerName: string, forceBluetoothRead?: boolean ) { - console.log(`forceBluetoothRead: ${forceBluetoothRead}`); - console.log(`bluetoothEnabled: ${bluetoothEnabled}`); - console.log( - `trackerName.startsWith("HaritoraX"): ${trackerName.startsWith( - "HaritoraX" - )}` - ); + log(`trackerName: ${trackerName}`); + log(`forceBluetoothRead: ${forceBluetoothRead}`); + if ( - forceBluetoothRead && - bluetoothEnabled && - trackerName.startsWith("HaritoraX") + (forceBluetoothRead && + bluetoothEnabled && + trackerName.startsWith("HaritoraX")) || + (bluetoothEnabled && trackerName.startsWith("HaritoraX")) || + !trackerSettings.has(trackerName) ) { - bluetooth.read( - trackerName, - settingsService, - sensorModeCharacteristic, - (err, sensorModeValue) => { - if (err) { - error(`Error reading sensorModeCharacteristic: ${err}`); - return; - } - log("Read sensorModeCharacteristic"); - - bluetooth.read( + log(`Forcing BLE reading for ${trackerName}`); + try { + const sensorModeValue = new DataView( + await bluetooth.read( trackerName, settingsService, - fpsModeCharacteristic, - (err, fpsModeValue) => { - if (err) { - error( - `Error reading fpsModeCharacteristic: ${err}` - ); - return; - } - log("Read fpsModeCharacteristic"); - - bluetooth.read( - trackerName, - settingsService, - correctionCharacteristic, - (err, correctionValue) => { - if (err) { - error( - `Error reading correctionCharacteristic: ${err}` - ); - return; - } - log("Read correctionCharacteristic"); - - bluetooth.read( - trackerName, - settingsService, - ankleCharacteristic, - (err, ankleValue) => { - if (err) { - error( - `Error reading ankleCharacteristic: ${err}` - ); - return; - } - log("Read ankleCharacteristic"); - - let sensorMode; - - if (sensorModeValue === 5) - sensorMode = 1; - else sensorMode = 2; - - let sensorAutoCorrection = []; - if (correctionValue & 0x01) - sensorAutoCorrection.push( - "accel" - ); - if (correctionValue & 0x02) - sensorAutoCorrection.push( - "gyro" - ); - if (correctionValue & 0x04) - sensorAutoCorrection.push( - "mag" - ); - - let ankleMotionDetection = - ankleValue === 1; - - log(`Tracker ${trackerName} settings: - Sensor mode: ${sensorMode} - FPS mode: ${fpsModeValue} - Sensor auto correction: ${sensorAutoCorrection} - Ankle motion detection: ${ankleMotionDetection}`); - - return { - sensorMode, - fpsMode: fpsModeValue, - sensorAutoCorrection, - ankleMotionDetection, - }; - } - ); - } - ); - } - ); - } - ); + sensorModeCharacteristic + ) + ).getInt8(0); + const fpsModeValue = new DataView( + await bluetooth.read( + trackerName, + settingsService, + fpsModeCharacteristic + ) + ).getInt8(0); + const correctionValue = new DataView( + await bluetooth.read( + trackerName, + settingsService, + correctionCharacteristic + ) + ).getInt8(0); + const ankleValue = new DataView( + await bluetooth.read( + trackerName, + settingsService, + ankleCharacteristic + ) + ).getInt8(0); + + let sensorMode; + if (sensorModeValue === 5) sensorMode = 1; + else sensorMode = 2; + + let fpsMode; + if (fpsModeValue === 1) fpsMode = 50; + else fpsMode = 100; + + let sensorAutoCorrection = []; + if (correctionValue & 0x01) sensorAutoCorrection.push("accel"); + if (correctionValue & 0x02) sensorAutoCorrection.push("gyro"); + if (correctionValue & 0x04) sensorAutoCorrection.push("mag"); + + let ankleMotionDetection = ankleValue === 1; + + log(`Tracker ${trackerName} raw settings: + Sensor mode: ${sensorModeValue} + FPS mode: ${fpsModeValue} + Sensor auto correction: ${correctionValue} + Ankle motion detection: ${ankleValue}`); + + log(`Tracker ${trackerName} settings: + Sensor mode: ${sensorMode} + FPS mode: ${fpsMode} + Sensor auto correction: ${sensorAutoCorrection} + Ankle motion detection: ${ankleMotionDetection}`); + + return { + sensorMode, + fpsMode, + sensorAutoCorrection, + ankleMotionDetection, + }; + } catch (err) { + error(`Error reading characteristic: ${err}`); + } } else { // GX trackers (or not forcing BLE reading) + log( + `Getting tracker settings for ${trackerName} (GX/no BLE reading)` + ); try { if (trackerSettings.has(trackerName)) { let [ @@ -959,37 +925,6 @@ Raw hex data calculated to be sent: ${hexValue}`); } } - /** - * Get the tracker's battery info. - * Support: GX6, GX2 - * - * @function getTrackerBattery - * @param {string} trackerName - * @returns {Map} - The tracker settings map - **/ - getTrackerBattery(trackerName: string) { - try { - if (trackerBattery.has(trackerName)) { - let [batteryRemaining, batteryVoltage, chargeStatus] = - trackerBattery.get(trackerName); - log( - `Tracker ${trackerName} battery remaining: ${batteryRemaining}%` - ); - log( - `Tracker ${trackerName} battery voltage: ${batteryVoltage}` - ); - log(`Tracker ${trackerName} charge status: ${chargeStatus}`); - return { batteryRemaining, batteryVoltage, chargeStatus }; - } else { - log(`Tracker ${trackerName} battery info not found`); - return null; - } - } catch (err) { - error(`Error getting battery info for ${trackerName}: ${err}`); - return null; - } - } - /** * Get the tracker's buttons. * Support: GX6, GX2, Bluetooth @@ -1141,14 +1076,6 @@ function listenToDeviceEvents() { haritora.emit("disconnect", peripheral.advertisement.localName); log(`Disconnected from ${peripheral.advertisement.localName}`); }); - - bluetooth.on("serviceDiscovered", (service: string) => { - haritora.emit("serviceDiscovered", service); - }); - - bluetooth.on("characteristicDiscovered", (characteristic: string) => { - haritora.emit("characteristicDiscovered", characteristic); - }); } /** @@ -1180,7 +1107,7 @@ function processIMUData(data: string, trackerName: string) { try { const { rotation, gravity, ankle } = decodeIMUPacket(data, trackerName); - log( + /*log( `Tracker ${trackerName} rotation: (${rotation.x.toFixed( 5 )}, ${rotation.y.toFixed(5)}, ${rotation.z.toFixed( @@ -1192,7 +1119,7 @@ function processIMUData(data: string, trackerName: string) { 5 )}, ${gravity.y.toFixed(5)}, ${gravity.z.toFixed(5)})` ); - if (ankle) log(`Tracker ${trackerName} ankle: ${ankle}`); + if (ankle) log(`Tracker ${trackerName} ankle: ${ankle}`);*/ haritora.emit("imu", trackerName, rotation, gravity, ankle); } catch (err) { diff --git a/src/mode/bluetooth.ts b/src/mode/bluetooth.ts index 7f5939a..c26ba28 100644 --- a/src/mode/bluetooth.ts +++ b/src/mode/bluetooth.ts @@ -136,7 +136,6 @@ export default class Bluetooth extends EventEmitter { }) => { //log(`Discovered service ${service.uuid}`); activeServices.push(service); - this.emit("serviceDiscovered", service.uuid); service.discoverCharacteristics( [], (err: any, characteristics: any[]) => { @@ -158,13 +157,10 @@ export default class Bluetooth extends EventEmitter { arg0: (err: any) => void ) => void; }) => { + //log(`Discovered characteristic: ${characteristic.uuid}`); activeCharacteristics.push( characteristic ); - this.emit( - "characteristicDiscovered", - characteristic.uuid - ); characteristic.on( "data", (data: any) => { @@ -232,23 +228,24 @@ export default class Bluetooth extends EventEmitter { return true; } - read( + async read( localName: any, service: string, - characteristic: string, - callback: (error: any, data: any) => void - ) { - log( - `Active services: ${activeServices - .map((s: { uuid: string }) => s.uuid) - .join(", ")}` - ); + characteristic: string + ): Promise { + while (!(await areAllBLEDiscovered())) { + log( + "Waiting for all services and characteristics to be discovered..." + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + log(`Reading characteristic ${characteristic} of service ${service}`); + const device = this.getDeviceInfo(localName); if (!device) { error(`Device ${localName} not found`); - callback(new Error(`Device ${localName} not found`), null); - return; + throw new Error(`Device ${localName} not found`); } const serviceInstance = activeServices.find( @@ -256,8 +253,7 @@ export default class Bluetooth extends EventEmitter { ); if (!serviceInstance) { error(`Service ${service} not found`); - callback(new Error(`Service ${service} not found`), null); - return; + throw new Error(`Service ${service} not found`); } const characteristicInstance = activeCharacteristics.find( @@ -265,22 +261,22 @@ export default class Bluetooth extends EventEmitter { ); if (!characteristicInstance) { error(`Characteristic ${characteristic} not found`); - callback( - new Error(`Characteristic ${characteristic} not found`), - null - ); - return; + throw new Error(`Characteristic ${characteristic} not found`); } - characteristicInstance.read((err: any, data: any) => { - if (err) { - error(`Error reading characteristic ${characteristic}: ${err}`); - callback(err, null); - return; - } + return new Promise((resolve, reject) => { + characteristicInstance.read((err: any, data: any) => { + const characteristicName = characteristics.get(characteristic); + if (err) { + error(`Error reading characteristic ${characteristicName}: ${err}`); + reject(err); + return; + } - log(`Read data from characteristic ${characteristic}`); - callback(null, data); + const buffer = new Uint8Array(data).buffer; + + resolve(buffer); + }); }); } @@ -290,68 +286,50 @@ export default class Bluetooth extends EventEmitter { characteristic: string, data: any ): Promise { - return new Promise((resolve, reject) => { - const device = this.getDeviceInfo(localName); - if (!device) { - error(`Device ${localName} not found`); - reject(new Error(`Device ${localName} not found`)); - return; - } + while (!(await areAllBLEDiscovered())) { + log( + "Waiting for all services and characteristics to be discovered..." + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + log( + `Writing to characteristic ${characteristic} of service ${service}` + ); + const device = this.getDeviceInfo(localName); + if (!device) { + error(`Device ${localName} not found`); + throw new Error(`Device ${localName} not found`); + } - device.discoverServices([], (err: any, services: any) => { + const serviceInstance = activeServices.find( + (s: { uuid: string }) => s.uuid === service + ); + if (!serviceInstance) { + error(`Service ${service} not found`); + throw new Error(`Service ${service} not found`); + } + + const characteristicInstance = activeCharacteristics.find( + (c: { uuid: string }) => c.uuid === characteristic + ); + if (!characteristicInstance) { + error(`Characteristic ${characteristic} not found`); + throw new Error(`Characteristic ${characteristic} not found`); + } + + return new Promise((resolve, reject) => { + characteristicInstance.write(data, false, (err: any) => { if (err) { - error(`Error discovering services: ${err}`); + error( + `Error writing to characteristic ${characteristic}: ${err}` + ); reject(err); return; } - const serviceInstance = services.find( - (s: { uuid: string }) => s.uuid === service - ); - if (!serviceInstance) { - error(`Service ${service} not found`); - reject(new Error(`Service ${service} not found`)); - return; - } - log(`Service instance: ${serviceInstance}`); - serviceInstance.discoverCharacteristics( - [], - (err: any, characteristics: any) => { - if (err) { - error(`Error discovering characteristics: ${err}`); - reject(err); - return; - } - - const characteristicInstance = characteristics.find( - (c: { uuid: string }) => c.uuid === characteristic - ); - if (!characteristicInstance) { - error(`Characteristic ${characteristic} not found`); - reject( - new Error( - `Characteristic ${characteristic} not found` - ) - ); - return; - } - - characteristicInstance.write( - data, - false, - (err: any) => { - if (err) { - error( - `Error writing to characteristic ${characteristic}: ${err}` - ); - reject(err); - return; - } - resolve(); - } - ); - } - ); + log(`Wrote data to characteristic ${characteristic}`); + resolve(); }); }); } @@ -412,6 +390,41 @@ export default class Bluetooth extends EventEmitter { } } +const importantServices = ["ef84369a90a911eda1eb0242ac120002", "180f"]; +const importantCharacteristics = [ + "2a19", + "ef84420290a911eda1eb0242ac120002", + "ef8443f690a911eda1eb0242ac120002", + "ef8445c290a911eda1eb0242ac120002", + "ef84c30090a911eda1eb0242ac120002", + "ef84c30590a911eda1eb0242ac120002", +]; + +async function areAllBLEDiscovered(): Promise { + for (const serviceUuid of importantServices) { + if ( + !activeServices.find((service: any) => service.uuid === serviceUuid) + ) { + return false; + } + } + + for (const characteristicUuid of importantCharacteristics) { + if ( + !activeCharacteristics.find( + (characteristic: any) => + characteristic.uuid === characteristicUuid + ) + ) { + return false; + } + } + + return true; +} + +// Proceed with the read or write operation + function emitData( classInstance: Bluetooth, localName: any, diff --git a/test/test.js b/test/test.js index 133f313..b7dcf46 100644 --- a/test/test.js +++ b/test/test.js @@ -15,6 +15,7 @@ if (mode === "bt" || mode === "bluetooth") { console.log("Active trackers for BT:", device.getActiveTrackers()); console.log("Device info:", await device.getDeviceInfo("HaritoraXW-(SERIAL)")); console.log("Device battery:", await device.getBatteryInfo("HaritoraXW-(SERIAL)")); + console.log("Device settings:", await device.getTrackerSettings("HaritoraXW-(SERIAL)")); } catch (error) { console.error("Error getting device data:", error); } @@ -42,7 +43,7 @@ if (mode === "bt" || mode === "bluetooth") { console.log(`Tracker settings map:`, device.getTrackerSettings("rightAnkle")); console.log(`Tracker raw hex settings map:`, device.getTrackerSettingsRaw("rightAnkle")); console.log(`Tracker buttons map:`, device.getTrackerButtons("rightAnkle")); - console.log(`Tracker battery map:`, device.getTrackerBattery("rightAnkle")); + console.log(`Tracker battery map:`, device.getBatteryInfo("rightAnkle")); }, 5000); /*setTimeout(() => {