UNPKG

bt-sensors-plugin-sk

Version:

Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk#supported-sensors for a list of supported sensors

222 lines (198 loc) 5.42 kB
const { buffer } = require("stream/consumers"); const BTSensor = require("../BTSensor"); const crypto = require("crypto"); //69 7e a0 b5 d5 4c f0 24 e7 94 77 23 55 55 41 14 class BMBatteryMonitor extends BTSensor { static Domain = BTSensor.SensorDomains.electrical; static GATT_SERVICE_UUID = "0000fff0-0000-1000-8000-00805f9b34fb"; static GATT_READ_UUID = "0000fff4-0000-1000-8000-00805f9b34fb"; static GATT_WRITE_UUID = "0000fff3-0000-1000-8000-00805f9b34fb"; static cryptKeys = { 2: Buffer.from([ 0x6c, 0x65, 0x61, 0x67, 0x65, 0x6e, 0x64, 0xff, 0xfe, 0x31, 0x38, 0x38, 0x32, 0x34, 0x36, 0x36, ]), 6: Buffer.from([ 0x6c, 0x65, 0x61, 0x67, 0x65, 0x6e, 0x64, 0xff, 0xfe, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x39, ]), 7: Buffer.from([ 0x6c, 0x65, 0x61, 0x67, 0x65, 0x6e, 0x64, 0xff, 0xfe, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x40, ]), }; static BatteryState = { 0: "Normal", 1: "Low voltage", 2: "Charging", }; static COMMAND_HEX = "d1550700000000000000000000000000"; static async identify(device) { const name = await this.getDeviceProp(device, "Name"); if (name && /BM[2|6|7]$/.test(name)) return this; else return null; } static ImageFile = "bm6.webp"; createCipher(encrypt = true) { const iv = Buffer.alloc(16, 0); const cipher = encrypt ? crypto.createCipheriv("aes-128-cbc", this._getCryptKey(), iv) : crypto.createDecipheriv("aes-128-cbc", this._getCryptKey(), iv); cipher.setAutoPadding(false); return cipher; } encryptCommand() { const cipher = this.createCipher(); const plaintext = Buffer.from(this.constructor.COMMAND_HEX, "hex"); return Buffer.concat([cipher.update(plaintext), cipher.final()]); } decryptPayload(payload) { const cipher = this.createCipher(false); const decrypted = Buffer.concat([cipher.update(payload), cipher.final()]); return decrypted; } hasGATT() { return true; } usingGATT() { return true; } async emitGATT() { return new Promise(async (resolve, reject) => { await this.read.startNotifications(); await this.write.writeValueWithResponse( this.encryptCommand(this._getCryptKey()) ); const timer = setTimeout(() => { clearTimeout(timer); this.stopNotifications(this.read); reject( new Error( `Response timed out (+30s) getting results from BM device ${this.getName()}.` ) ); }, 30000); this.read.once("valuechanged", (buffer) => { clearTimeout(timer); this.stopNotifications(this.read); this.emitValuesFrom(buffer); resolve(this); }); }) } _getCryptKey() { return this.constructor.cryptKeys[this.monitorVersion]; } initSchema() { super.initSchema(); this.getGATTParams()["useGATT"].default = true; this.addParameter("monitorVersion", { title: "monitor version", //unclear if version 2 has same GATT characteristics and data format enum: [2, 6, 7], isRequired: true, type: "number", default: 6, }); this.addDefaultParam("batteryID"); this.addDefaultPath("voltage", "electrical.batteries.voltage").read = ( buffer ) => { return buffer.readUInt16BE(7) / 100; }; this.addDefaultPath( "soc", "electrical.batteries.capacity.stateOfCharge" ).read = (buffer) => { return buffer.readUInt8(6) / 100; }; this.addMetadatum("batteryState", "", "battery state", (buff) => { return this.constructor.BatteryState[buff.readUInt8(5)]; }).default = "electrical.batteries.{batteryID}.state"; this.addDefaultPath( "temperature", "electrical.batteries.temperature" ).read = (buffer) => { return (buffer.readUInt8(3) ? -1 : 1) * buffer.readUInt8(4) + 273.15; }; } async initGATTConnection(isReconnecting) { await super.initGATTConnection(isReconnecting); const gattServer = await this.getGATTServer(); const service = await gattServer.getPrimaryService( this.constructor.GATT_SERVICE_UUID ); this.write = await service.getCharacteristic( this.constructor.GATT_WRITE_UUID ); this.read = await service.getCharacteristic( this.constructor.GATT_READ_UUID ); } emitValuesFrom(buffer) { if (buffer.length < 16) { this.debug(`Received invalid buffer ${JSON.stringify(buffer)}`); return; } const b = this.decryptPayload(buffer); if (Buffer.compare(b.subarray(0, 3), Buffer.from([0xd1, 0x55, 0x07])) == 0) super.emitValuesFrom(b); else this.debug(`Received invalid decrypted buffer ${JSON.stringify(b)}`); } async initGATTNotifications() { await this.read.startNotifications(); await this.write.writeValueWithResponse( this.encryptCommand(this._getCryptKey()) ); this.read.on("valuechanged", (buffer) => { this.emitValuesFrom(buffer); }); } async deactivateGATT() { await this.stopGATTNotifications(this.read); await super.deactivateGATT(); } } module.exports = BMBatteryMonitor;