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

221 lines (189 loc) 8.68 kB
/** */ const VictronSensor = require("./Victron/VictronSensor.js"); const VC=require("./Victron/VictronConstants.js") const int24 = require('int24') class VictronBatteryMonitor extends VictronSensor{ static _test(data, key){ var b if (data instanceof String) b = Buffer.from(data.replaceAll(" ",""),"hex") else b = data const d = new this() if (key) { d.encryptionKey = key } else d.auxMode=b.readInt8(8)&0x3 d.currentProperties = {} d.currentProperties.ManufacturerData={} d.currentProperties.ManufacturerData[0x02e1]=b d.debug = (m)=>{console.log(m)} d.debug.bind(d) d.initSchema() Object.keys(d.getPaths()).forEach((tag)=>{ d.on(tag,(v)=>console.log(`${tag}=${JSON.stringify(v)}`)) }) if (key) b = d.decrypt(b) else console.log(b) d.emitValuesFrom(b) d.removeAllListeners() } static ImageFile = "VictronSmartShunt.jpg" characteristics=[] async initSchema(){ await super.initSchema() this.addDefaultParam("batteryID").default="house" //"default": "electrical.batteries.{batteryID}.voltage" this.addDefaultPath('current','electrical.batteries.current') .read=(buff,offset=0)=>{return buff.readInt32LE(offset)/1000} this.getPath("current").gatt='6597ed8c-4bda-4c1e-af4b-551c4cf74769' this.addMetadatum('power','W', 'house battery wattage', (buff,offset=0)=>{return buff.readInt16LE(offset)}, '6597ed8e-4bda-4c1e-af4b-551c4cf74769') .default="electrical.batteries.{batteryID}.power" this.addDefaultPath('voltage', "electrical.batteries.voltage") .read=(buff,offset=0)=>{return this.NaNif(buff.readInt16LE(offset), 0x7FFF)/100} this.getPath("voltage").gatt='6597ed8d-4bda-4c1e-af4b-551c4cf74769' const alarmMD = this.addMetadatum('alarm','', 'alarm', (buff,offset=0)=>{return buff.readInt16LE(offset)}) alarmMD.default='electrical.batteries.{batteryID}.alarm' this.addMetadatum( 'consumed','Ah', 'amp-hours consumed', (buff,offset=0)=>{return buff.readInt32LE(offset)/10}, '6597eeff-4bda-4c1e-af4b-551c4cf74769',) .default="electrical.batteries.{batteryID}.capacity.ampHoursConsumed" this.addDefaultPath( 'soc',"electrical.batteries.capacity.stateOfCharge") .read=(buff,offset=0)=>{return buff.readUInt16LE(offset)/10000} this.getPath("soc").gatt='65970fff-4bda-4c1e-af4b-551c4cf74769' this.addDefaultPath( 'ttg',"electrical.batteries.capacity.timeRemaining") .read=(buff,offset=0)=>{return this.NaNif(buff.readUInt16LE(offset),0xFFFF)*60} this.getPath("ttg").gatt='65970ffe-4bda-4c1e-af4b-551c4cf74769'; if (this.auxMode==undefined){ const md=await this.constructor.getDataPacket(this.device, this.getManufacturerData(this.constructor.ManufacturerID)) try { if (this.encryptionKey){ const decData = this.decrypt(md) if (decData) this.auxMode=decData.readInt8(8)&0x3 } } catch(e){ this.debug(`Unable to determine device AuxMode. ${e.message}`) this._app.debug(e) this.auxMode=VC.AuxMode.DISABLED } } switch(this.auxMode){ case VC.AuxMode.STARTER_VOLTAGE: this.addMetadatum('starterVoltage','V', 'starter battery voltage', (buff,offset=0)=>{return buff.readInt16LE(offset)/100}, '6597ed7d-4bda-4c1e-af4b-551c4cf74769') .default="electrical.batteries.starter.voltage" break; case VC.AuxMode.MIDPOINT_VOLTAGE: this.addMetadatum('midpointVoltage','V', 'midpoint battery voltage', (buff,offset=0)=>{return buff.readUInt16LE(offset)/100}, '6597ed7d-4bda-4c1e-af4b-551c4cf74769') .default="electrical.batteries.midpoint.voltage" break; case VC.AuxMode.TEMPERATURE: this.addDefaultPath('temperature','electrical.batteries.temperature') .read=(buff,offset=0)=>{return (buff.readUInt16LE(offset)/100)} this.getPath('temperature').gatt='6597ed7d-4bda-4c1e-af4b-551c4cf74769' break; default: break } } emitValuesFrom(decData){ this.emitData("ttg",decData,0) this.emitData("voltage",decData,2); const alarm = this.getPath("alarm").read(decData,4) if (alarm>0) this.emitAlarm("alarm",alarm) switch(this.auxMode){ case VC.AuxMode.STARTER_VOLTAGE: this.emitData("starterVoltage",decData,6); break; case VC.AuxMode.MIDPOINT_VOLTAGE: this.emitData("midpointVoltage",decData,6); break; case VC.AuxMode.TEMPERATURE: this.emitData("temperature",decData,6); break; default: break } this.emit("current", (this.NaNif(int24.readInt24LE(decData, 8)>>2,0x1FFFFF))/1000) this.emit("consumed",(this.NaNif(int24.readInt24LE(decData, 11)&0xFFFFF,0xFFFFF)) / 10) ; this.emit("soc", this.NaNif(((decData.readUInt16LE(13)& 0x3FFF)>>4),0x3FF)/1000) } initGATTConnection() { return new Promise((resolve,reject )=>{ if (!this.valueIfVariant(this.currentProperties.Paired)) reject(`${this.getName()} must be paired with the Signalk server to use GATT protocol`) this.deviceConnect().then(async ()=>{ this.debug(`${this.getName()} connected.`) if (!this.gattServer) { this.gattServer = await this.device.gatt() this.gattService= await this.gattServer.getPrimaryService("65970000-4bda-4c1e-af4b-551c4cf74769") const keepAlive = await this.gattService.getCharacteristic('6597ffff-4bda-4c1e-af4b-551c4cf74769') await keepAlive.writeValue(Buffer.from([0xFF,0xFF]), { offset: 0, type: 'request' }) } resolve(this) }).catch((e)=>reject(e.message)) }) } emitGATT(){ Object.keys(this.getPaths()).forEach( (tag)=> { const datum = this.getPaths()[tag] if (datum.gatt) { this.gattService.getCharacteristic(datum.gatt).then((gattCharacteristic)=>{ gattCharacteristic.readValue().then((buffer)=>{ this.emitData(tag, buffer) }) }).catch((e)=>{ throw new Error(e) })} }) } initGATTNotifications(){ return new Promise((resolve,reject )=>{ Object.keys(this.getPaths()).forEach( (tag)=> { const datum = this.getPaths()[tag] if (datum.gatt) { this.gattService.getCharacteristic(datum.gatt).then(async (gattCharacteristic)=>{ //const buffer = await gattCharacteristic.readValue() //this.emitData(tag, buffer) gattCharacteristic.startNotifications().then(()=>{ gattCharacteristic.on('valuechanged', buffer => { this.emitData(tag, buffer) }) this.characteristics.push(gattCharacteristic) }) }) }}) resolve(this)}) } hasGATT(){ return true } getGATTDescription(){ return "To use the GATT connection the SignalK server computer and the Smart Shunt must first be paired." } getDescription(){ return `${super.getDescription()}.<p><p>After setting the encryption key, Save and reselect to configure the value of the Aux field (Secondary Battery, Midpoint or Battery Temperature)` } async stopListening(){ super.stopListening() for (var c of this.characteristics){ await c.stopNotifications() } if (await this.device.isConnected()){ await this.device.disconnect() this.debug(`Disconnected`) } } } module.exports=VictronBatteryMonitor