bt-sensors-plugin-sk 1.3.1 → 1.3.2-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BTSensor.js CHANGED
@@ -856,6 +856,10 @@ class BTSensor extends EventEmitter {
856
856
  return this.currentProperties?.RSSI??NaN
857
857
  }
858
858
 
859
+ isPaired(){
860
+ return this.currentProperties?.Paired??false
861
+ }
862
+
859
863
  getSignalStrength(){
860
864
  const rssi = this.getRSSI()
861
865
  if (!rssi)
package/README.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # Bluetooth Sensors for [Signal K](http://www.signalk.org)
2
2
 
3
3
  ## WHAT'S NEW
4
+ # Version 1.3.2-1
4
5
 
6
+ - VictronSmartBatteryProtect fix
7
+ - RenogyRoverClient deviceID clarity
8
+
9
+ # Version 1.3.2
10
+
11
+ - Victron Alarm Reason improvements
12
+ - VictronOrionXS offReason text implementation
13
+ - Shelly Blu H&T description changes
5
14
 
6
15
  # Version 1.3.1
7
16
 
@@ -0,0 +1,54 @@
1
+ /*
2
+ Smart Battery Protect
3
+ Start
4
+ bit
5
+ Nr of
6
+ bits Meaning Units Range NA
7
+ value Remark
8
+ 8 8 Device state 0 .. 0xFE 0xFF VE_REG_DEVICE_STATE
9
+ 16 8 Output state 0 .. 0xFE 0xFF VE_REG_DC_OUTPUT_STATUS
10
+ 24 8 Error code 0 .. 0xFE 0xFF VE_REG_CHR_ERROR_CODE
11
+ 32 16 Alarm reason 0 .. 0xFFFF - VE_REG_ALARM_REASON
12
+ 48 16 Warning reason 0 .. 0xFFFF - VE_REG_WARNING_REASON
13
+ 64 16 Input voltage 0.01 V 327.68 .. 327.66 V 0x7FFF VE_REG_DC_CHANNEL1_VOLTAGE
14
+ 80 16 Output voltage 0.01 V 0 .. 655.34 V 0xFFFF VE_REG_DC_OUTPUT_VOLTAGE
15
+ 96 32 Off reason 0 .. 0xFFFFFFFF - VE_REG_DEVICE_OFF_REASON_2
16
+ */
17
+
18
+ const VictronSensor = require ("./Victron/VictronSensor.js")
19
+ const VC = require("./Victron/VictronConstants.js")
20
+ class VictronSmartBatteryProtect extends VictronSensor{
21
+
22
+ static ImageFile="VictronSmartBatteryProte.jpg"
23
+
24
+ initSchema(){
25
+ super.initSchema()
26
+ this.addDefaultParam("id")
27
+ this.addMetadatum('deviceState','', 'device state',
28
+ (buff)=>{return VC.OperationMode.get(buff.readUInt8(1))})
29
+ this.addMetadatum('outputStatus','', 'output status', //TODO
30
+ (buff)=>{return (buff.readUInt8(2))})
31
+
32
+ this.addMetadatum('chargerError','', 'charger error',
33
+ (buff)=>{return VC.ChargerError.get(buff.readUInt8(3))})
34
+ this.addMetadatum('alarmReason','', 'alarm reason',
35
+ (buff)=>{return buff.readUInt16LE(4)})
36
+ this.getPath("alarmReason").notify=true
37
+ this.addMetadatum('warningReason','', 'warning reason', //TODO
38
+ (buff)=>{return (buff.readUInt16LE(5))})
39
+ this.addMetadatum('channel1Voltage','V', 'channel one voltage',
40
+ (buff)=>{return this.NaNif(buff.readInt16LE(7),0x7FFF)/100})
41
+ this.addMetadatum('outputVoltage','V', 'output voltage',
42
+ (buff)=>{return this.NaNif(buff.readUInt16LE(9),0xFFFF)/100})
43
+ this.addMetadatum('offReason','', 'off reason',
44
+ (buff)=>{return this.offReasonText(buff.readUInt32LE(11))})
45
+ }
46
+ emitValuesFrom(decData){
47
+ super.emitValuesFrom(decData)
48
+ const alarm = this.getPath("alarmReason").read(decData)
49
+ if (alarm>0)
50
+ this.emitAlarm("alarmReason",alarm)
51
+ }
52
+
53
+ }
54
+ module.exports=VictronSmartBatteryProtect
@@ -0,0 +1,54 @@
1
+ /*
2
+ Smart Battery Protect
3
+ Start
4
+ bit
5
+ Nr of
6
+ bits Meaning Units Range NA
7
+ value Remark
8
+ 8 8 Device state 0 .. 0xFE 0xFF VE_REG_DEVICE_STATE
9
+ 16 8 Output state 0 .. 0xFE 0xFF VE_REG_DC_OUTPUT_STATUS
10
+ 24 8 Error code 0 .. 0xFE 0xFF VE_REG_CHR_ERROR_CODE
11
+ 32 16 Alarm reason 0 .. 0xFFFF - VE_REG_ALARM_REASON
12
+ 48 16 Warning reason 0 .. 0xFFFF - VE_REG_WARNING_REASON
13
+ 64 16 Input voltage 0.01 V 327.68 .. 327.66 V 0x7FFF VE_REG_DC_CHANNEL1_VOLTAGE
14
+ 80 16 Output voltage 0.01 V 0 .. 655.34 V 0xFFFF VE_REG_DC_OUTPUT_VOLTAGE
15
+ 96 32 Off reason 0 .. 0xFFFFFFFF - VE_REG_DEVICE_OFF_REASON_2
16
+ */
17
+
18
+ const VictronSensor = require ("./Victron/VictronSensor.js")
19
+ const VC = require("./Victron/VictronConstants.js")
20
+ class VictronSmartBatteryProtect extends VictronSensor{
21
+
22
+ static ImageFile="VictronSmartBatteryProte.jpg"
23
+
24
+ initSchema(){
25
+ super.initSchema()
26
+ this.addDefaultParam("id")
27
+ this.addMetadatum('deviceState','', 'device state',
28
+ (buff)=>{return VC.OperationMode.get(buff.readUInt8(1))})
29
+ this.addMetadatum('outputStatus','', 'output status', //TODO
30
+ (buff)=>{return (buff.readUInt8(2))})
31
+
32
+ this.addMetadatum('chargerError','', 'charger error',
33
+ (buff)=>{return VC.ChargerError.get(buff.readUInt8(3))})
34
+ this.addMetadatum('alarmReason','', 'alarm reason',
35
+ (buff)=>{return buff.readUInt16LE(4)})
36
+ this.getPath("alarmReason").notify=true
37
+ this.addMetadatum('warningReason','', 'warning reason', //TODO
38
+ (buff)=>{return (buff.readUInt16LE(5))})
39
+ this.addMetadatum('channel1Voltage','V', 'channel one voltage',
40
+ (buff)=>{return this.NaNif(buff.readInt16LE(7),0x7FFF)/100})
41
+ this.addMetadatum('outputVoltage','V', 'output voltage',
42
+ (buff)=>{return this.NaNif(buff.readUInt16LE(9),0xFFFF)/100})
43
+ this.addMetadatum('offReason','', 'off reason',
44
+ (buff)=>{return this.offReasonText(buff.readUInt32LE(11))})
45
+ }
46
+ emitValuesFrom(decData){
47
+ super.emitValuesFrom(decData)
48
+ const alarm = this.getPath("alarmReason").read(decData)
49
+ if (alarm>0)
50
+ this.emitAlarm("alarmReason",alarm)
51
+ }
52
+
53
+ }
54
+ module.exports=VictronSmartBatteryProtect
package/index.js CHANGED
@@ -110,7 +110,7 @@ class MissingSensor {
110
110
  on(){
111
111
 
112
112
  }
113
-
113
+ stopNotifications(){}
114
114
  isError(){
115
115
  return true
116
116
  }
@@ -550,7 +550,6 @@ module.exports = function (app) {
550
550
  c.debug=app.debug
551
551
 
552
552
  const sensor = new c(device, config?.params, config?.gattParams)
553
-
554
553
  sensor._paths=config.paths //this might be a good candidate for refactoring
555
554
  sensor._app=app
556
555
  sensor._adapter=adapter //HACK!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bt-sensors-plugin-sk",
3
- "version": "1.3.1",
3
+ "version": "1.3.2-1",
4
4
  "description": "Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk#supported-sensors for a list of supported sensors",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -348,7 +348,7 @@ class GobiusCTankMeter extends BTSensor{
348
348
  }
349
349
 
350
350
  async deactivateGATT(){
351
- await this.stopNotifications(this.characteristic)
351
+ await this.stopGATTNotifications(this.characteristic)
352
352
  await super.deactivateGATT()
353
353
  }
354
354
 
@@ -84,10 +84,19 @@ class JBDBMS extends BTSensor {
84
84
 
85
85
  this.addDefaultPath('SOC','electrical.batteries.capacity.stateOfCharge')
86
86
  .read=(buffer)=>{return buffer.readUInt8(23)/100}
87
-
88
- this.addMetadatum('FET', '', 'FET Control',
89
- (buffer)=>{return buffer.readUInt8(24)} )
90
- .default="electrical.batteries.{batteryID}.FETControl"
87
+
88
+
89
+ this.addMetadatum('FET', '', 'FET On/Off Status',
90
+ (buffer)=>{return buffer.readUInt8(24) !=0 } )
91
+ .default="electrical.batteries.{batteryID}.FETStatus"
92
+
93
+ this.addMetadatum('FETCharging', '', 'FET Status Charging',
94
+ (buffer)=>{return (buffer.readUInt8(24) & 0x1) !=0 })
95
+ .default="electrical.batteries.{batteryID}.FETStatus.charging"
96
+
97
+ this.addMetadatum('FETDischarging', '', 'FET Status Discharging',
98
+ (buffer)=>{return (buffer.readUInt8(24) & 0x2) !=0 })
99
+ .default="electrical.batteries.{batteryID}.FETStatus.discharging"
91
100
 
92
101
  await this.deviceConnect()
93
102
  const gattServer = await this.device.gatt()
@@ -188,6 +197,7 @@ class JBDBMS extends BTSensor {
188
197
  }
189
198
 
190
199
  async initGATTConnection() {
200
+ this.setConnected(await this.device.isConnected())
191
201
  return this
192
202
  }
193
203
 
@@ -3,7 +3,7 @@ ported from https://github.com/cyrils/renogy-bt
3
3
  */
4
4
  const BTSensor = require("../../BTSensor.js");
5
5
  const VC = require('./RenogyConstants.js');
6
- const crc16Modbus = require('./CRC.js')
6
+ const crc16Modbus = require('./CRC.js');
7
7
  class RenogySensor extends BTSensor{
8
8
  static Domain=BTSensor.SensorDomains.electrical
9
9
  static ALIAS_PREFIX = 'BT-TH'
@@ -34,20 +34,27 @@ class RenogySensor extends BTSensor{
34
34
  b.writeUInt16BE(words,4)
35
35
  b.writeUInt16BE(crc16Modbus(b.subarray(0,6)),6)
36
36
 
37
- await writeCharacteristic.writeValueWithResponse(b, 0)
37
+ await writeCharacteristic.writeValueWithoutResponse(b, 0)
38
38
 
39
39
  }
40
40
  static identify(device){
41
41
  return null
42
42
  }
43
-
43
+ pollFreq=30
44
44
  async initSchema(){
45
45
  await super.initSchema()
46
-
46
+ this.getGATTParams().pollFreq.default=this.pollFreq
47
47
  this.addParameter(
48
48
  "deviceID",
49
49
  {
50
- title: 'ID of device'
50
+ title: 'ID of device',
51
+ description: 'only modify if device is in hub mode or daisy chained',
52
+ default:255,
53
+ type: 'number',
54
+ minimum: 0,
55
+ maximum: 255,
56
+ multipleOf:1,
57
+ isRequired: true
51
58
  }
52
59
  )
53
60
  }
@@ -73,15 +80,17 @@ class RenogySensor extends BTSensor{
73
80
  }
74
81
 
75
82
  async sendReadFunctionRequest(writeReq, words){
76
- this.constructor.sendReadFunctionRequest(
83
+ await this.constructor.sendReadFunctionRequest(
77
84
  this.writeChar, this.getDeviceID(), writeReq, words)
78
85
  }
79
86
 
80
87
  initGATTInterval(){
88
+ this.emitGATT()
81
89
  this.intervalID = setInterval(()=>{
82
90
  this.emitGATT()
83
91
  }, 1000*(this?.pollFreq??60) )
84
92
  }
93
+
85
94
  emitGATT(){
86
95
  this.getAllEmitterFunctions().forEach(async (emitter)=>
87
96
  await emitter()
@@ -12,9 +12,21 @@ class RenogyBattery extends RenogySensor {
12
12
  this.getAndEmitCellTemperatures.bind(this),
13
13
  this.getAndEmitCellVoltages.bind(this)]
14
14
  }
15
+ numberOfCells=4
15
16
  initSchema(){
16
-
17
- this.addMetadatum('numberOfCells','', 'number of cells')
17
+ this.addDefaultParam("batteryID").default="house"
18
+ this.addParameter(
19
+ "numberOfCells",
20
+ {
21
+ title:"Number of cells",
22
+ type: "number",
23
+ isRequired: true,
24
+ default: this.numberOfCells,
25
+ minimum: 1,
26
+ maximum: 16,
27
+ multipleOf:1
28
+ }
29
+ )
18
30
  this.addDefaultPath('current','electrical.batteries.current')
19
31
  .read=(buffer)=>{return buffer.readInt16BE(3)/100}
20
32
 
@@ -41,31 +53,33 @@ class RenogyBattery extends RenogySensor {
41
53
  async initGATTConnection() {
42
54
  await super.initGATTConnection()
43
55
  this.numberOfCells = await this.retrieveNumberOfCells()
44
- this.deviceID = await this.retrieveDeviceID()
45
- this.emit('numberOfCells', this.numberOfCells)
46
56
  }
47
57
 
48
- retrieveNumberOfCells(){
49
-
58
+ retrieveModelID(){
50
59
  return new Promise( async ( resolve, reject )=>{
51
- await this.sendReadFunctionRequest(0x1388,0x11)
52
60
 
53
- const valChanged = async (buffer) => {
54
- resolve(buffer.readUInt16(3))
55
- }
56
- this.readChar.once('valuechanged', valChanged )
61
+ await this.sendReadFunctionRequest(0x1402,0x08)
62
+
63
+ this.readChar.once('valuechanged', async (buffer) => {
64
+ if (buffer[2]!=0x10)
65
+ reject("Unknown error retrieving model ID") //???
66
+ const model = buffer.subarray(3,17).toString().trim()
67
+ resolve(model)
57
68
  })
69
+ })
58
70
  }
59
- retrieveDeviceID(){
71
+ retrieveNumberOfCells(){
72
+
60
73
  return new Promise( async ( resolve, reject )=>{
61
- this.sendFunctionRequest(0x14, 0x67)
74
+ await this.sendReadFunctionRequest(0x1388,0x11)
62
75
 
63
76
  const valChanged = async (buffer) => {
64
- resolve((buffer.readUInt8(4)))
77
+ resolve(buffer.readUInt16(3))
65
78
  }
66
79
  this.readChar.once('valuechanged', valChanged )
67
80
  })
68
81
  }
82
+
69
83
 
70
84
  getAndEmitBatteryInfo(){
71
85
  return new Promise( async ( resolve, reject )=>{
@@ -54,10 +54,24 @@ class RenogyInverter extends RenogySensor {
54
54
  })
55
55
  }
56
56
 
57
+ retrieveModelID(){
58
+ return new Promise( async ( resolve, reject )=>{
59
+
60
+ await this.sendReadFunctionRequest(0x10d7,0x08)
61
+
62
+ this.readChar.once('valuechanged', async (buffer) => {
63
+ if (buffer[2]!=0x10)
64
+ reject("Unknown error retrieving model ID") //???
65
+ const model = buffer.subarray(3,17).toString().trim()
66
+ resolve(model)
67
+ })
68
+ })
69
+ }
70
+
57
71
  getAndEmitInverterStats(){
58
72
  return new Promise( async ( resolve, reject )=>{
59
73
 
60
- await this.sendReadFunctionRequest(0xfa0, 0x8)
74
+ await this.sendReadFunctionRequest(0xfa0, 0xA)
61
75
 
62
76
  this.readChar.once('valuechanged', (buffer) => {
63
77
  ["ueiVoltage","ueiCurrent", "voltage", "loadCurrent", "frequency","temperature"].forEach((tag)=>
@@ -71,7 +85,7 @@ class RenogyInverter extends RenogySensor {
71
85
  getAndEmitSolarCharging(){
72
86
  return new Promise( async ( resolve, reject )=>{
73
87
 
74
- await this.sendReadFunctionRequest(0x10e9, 0x5)
88
+ await this.sendReadFunctionRequest(0x10e9, 0x7)
75
89
 
76
90
  this.readChar.once('valuechanged', (buffer) => {
77
91
  ["solarVoltage","solarCurrent", "solarPower", "solarChargingStatus", "solarChargingPower"].forEach((tag)=>
@@ -85,7 +99,7 @@ class RenogyInverter extends RenogySensor {
85
99
  getAndEmitInverterLoad(){
86
100
  return new Promise( async ( resolve, reject )=>{
87
101
 
88
- await this.sendReadFunctionRequest(0x113a, 0x2)
102
+ await this.sendReadFunctionRequest(0x113a, 0x6)
89
103
 
90
104
  this.readChar.once('valuechanged', (buffer) => {
91
105
  ["loadPower", "chargingCurrent"].forEach((tag)=>
@@ -34,6 +34,10 @@ class RenogyRoverClient extends RenogySensor {
34
34
  initSchema(){
35
35
  //Buffer(73) [1, 3, 68, 32, 32, 82, 78, 71, 45, 67, 84, 82, 76, 45, 87, 78, 68, 51, 48, 7, 140, 0, 132, 0, 126, 0, 120, 0, 111, 0, 106, 100, 50, 0, 5, 0, 120, 0, 120, 0, 28, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 5, 0, 5, 2, 148, 0, 5, 206, 143, 34, 228, buffer: ArrayBuffer(8192), byteLength: 73, byteOffset: 6144, length: 73, Symbol(Symbol.toStringTag): 'Uint8Array']
36
36
  super.initSchema()
37
+
38
+ this.addDefaultParam("id")
39
+ .default="solar"
40
+
37
41
  this.addMetadatum('batteryType', '', "battery type")
38
42
  .default="electrical.chargers.{id}.battery.type"
39
43
  this.addMetadatum('batteryPercentage', 'ratio', "battery percentage",
@@ -118,22 +122,12 @@ class RenogyRoverClient extends RenogySensor {
118
122
 
119
123
  }
120
124
 
121
- retrieveDeviceID(){
122
- return new Promise( async ( resolve, reject )=>{
123
- this.sendReadFunctionRequest(0x1A, 0x1)
124
-
125
- const valChanged = async (buffer) => {
126
- resolve((buffer.readUInt8(4)))
127
- }
128
- this.readChar.once('valuechanged', valChanged )
129
- })
130
- }
131
-
125
+
132
126
  retrieveBatteryType(){
133
127
  return new Promise( async ( resolve, reject )=>{
134
128
  //Buffer(7) [255, 3, 2, 0, 1, 80, 80, buffer: ArrayBuffer(8192), byteLength: 7, byteOffset: 864, length: 7, Symbol(Symbol.toStringTag): 'Uint8Array']
135
129
 
136
- this.sendReadFunctionRequest(0xe004, 0x01)
130
+ await this.sendReadFunctionRequest(0xe004, 0x01)
137
131
 
138
132
  const valChanged = async (buffer) => {
139
133
  resolve(RC.BATTERY_TYPE[(buffer.readUInt8(4))])
@@ -157,13 +151,10 @@ class RenogyRoverClient extends RenogySensor {
157
151
  }
158
152
  async initGATTConnection() {
159
153
  await super.initGATTConnection()
160
- if (!this.deviceID)
161
- this.deviceID = await this.retrieveDeviceID()
162
- this.modelID=await this.retrieveModelID()
163
154
 
155
+ this.modelID=await this.retrieveModelID()
164
156
  this.batteryType = await this.retrieveBatteryType()
165
- this.emit('batteryType', this.batteryType)
166
-
157
+ this.emit('batteryType', this.batteryType)
167
158
 
168
159
  }
169
160
 
@@ -175,7 +166,7 @@ class RenogyRoverClient extends RenogySensor {
175
166
  async getAndEmitChargeInfo(){
176
167
  return new Promise( async ( resolve, reject )=>{
177
168
  try {
178
- this.sendReadFunctionRequest(0x100, 0x22)
169
+ await this.sendReadFunctionRequest(0x100, 0x22)
179
170
 
180
171
  this.readChar.once('valuechanged', buffer => {
181
172
  this.emitValuesFrom(buffer)
@@ -20,10 +20,10 @@ class ShellySBHT003C extends AbstractBTHomeSensor {
20
20
 
21
21
 
22
22
  getTextDescription(){
23
- return `NOTE: Device must be paired with SignalK server machine to operate properly. For more information about the sensor go here: <a href=https://us.shelly.com/products/shelly-blu-h-t-black target="_blank">Shelly Blu H&T</a>.`
23
+ return `NOTE: Device must be paired with SignalK server machine or you won't see updates (see: <a href=https://shelly-api-docs.shelly.cloud/docs-ble/common#pairing target="_blank">https://shelly-api-docs.shelly.cloud/docs-ble/common#pairing</a> ). For more information about the sensor go here: <a href=https://us.shelly.com/blogs/documentation/shelly-blu-h-t target="_blank">Shelly Blu H&T</a>.<br><br>Alternatively you can edit your /etc/bluetooth/main.conf file and set "TemporaryTimeout = 90" in the [General] section, then restart the bluetooth service.`
24
24
  }
25
25
 
26
- initSchema() {
26
+ initSchema() {
27
27
  super.initSchema()
28
28
  this.addDefaultParam("zone")
29
29
 
@@ -20,11 +20,11 @@ class ShellySBMO003Z extends AbstractBTHomeSensor {
20
20
  * @type {string}
21
21
  */
22
22
  static LOCAL_NAME="Shelly BLU Motion";
23
- static ImageFile="ShellyBLUMotion.webp"
23
+ static ImageFile="ShellyBLUMotion.webp"
24
24
 
25
25
  getTextDescription(){
26
- return `NOTE: Device must be paired with SignalK server machine to operate properly. For more information about the sensor go here: <a href=https://us.shelly.com/products/shelly-blu-motion target="_blank">Shelly Blu Motion</a>.`
27
- }
26
+ return `${!this.isPaired()?"NOTE: Device must be paired with SignalK server machine to operate properly (see: <a href=https://shelly-api-docs.shelly.cloud/docs-ble/common#pairing target=\"_blank\">https://shelly-api-docs.shelly.cloud/docs-ble/common#pairing</a> ":"Device is paired."}).<br><br>For more information about the sensor click here: <a href=https://us.shelly.com/products/shelly-blu-motion target="_blank">Shelly Blu Motion</a>.`
27
+ }
28
28
 
29
29
  initSchema() {
30
30
  super.initSchema()
@@ -62,20 +62,6 @@ class ShellySBMO003Z extends AbstractBTHomeSensor {
62
62
  .default="sensors.{macAndName}.button"
63
63
 
64
64
 
65
- /*
66
- this.addMetadatum(
67
- "packetID",
68
- null,
69
- "packetID from sensor",
70
- this.parsePacketID.bind(this)
71
- )
72
- .default="sensors.{macAndName}.packetID"
73
- */
74
-
75
- }
76
- getState() {
77
-
78
- return super.getState()
79
65
  }
80
66
  }
81
67
 
@@ -18,6 +18,14 @@ const ProtectionStatus = {
18
18
  0x4000: "Short Circuit Protection"
19
19
  }
20
20
 
21
+ const BatteryState = {
22
+ 0: "Discharging/Idle",
23
+ 1: "Charging",
24
+ 2: "Discharging",
25
+ 4: "Charge Disabled"
26
+
27
+ }
28
+
21
29
  class ShenzhenLiONBMS extends BTSensor{
22
30
  static Domain = BTSensor.SensorDomains.electrical
23
31
  static ImageFile = "LiTimeLiFePo4Battery.avif"
@@ -63,11 +71,12 @@ class ShenzhenLiONBMS extends BTSensor{
63
71
  "numberOfCells",
64
72
  {
65
73
  title:"Number of cells",
66
- type: "integer",
74
+ type: "number",
67
75
  isRequired: true,
68
76
  default: 4,
69
77
  minimum: 1,
70
- maximum: 16
78
+ maximum: 16,
79
+ multipleOf:1
71
80
  }
72
81
  )
73
82
 
@@ -91,6 +100,9 @@ class ShenzhenLiONBMS extends BTSensor{
91
100
  this.addMetadatum(`cell${cellNum+1}Voltage`,'V', `cell #${cellNum+1} voltage`,
92
101
  (buff)=>{return buff.readInt16LE(16+(cellNum*2)) /1000})
93
102
  .default=`electrical.batteries.{batteryID}.cells.${cellNum+1}.voltage`
103
+
104
+ this.addMetadatum(`cell${cellNum+1}Balancing`,'', `cell #${cellNum+1} balance state (true=balancing)`)
105
+ .default=`electrical.batteries.{batteryID}.cells.${cellNum+1}.balancing`
94
106
  }
95
107
 
96
108
  this.addDefaultPath('current','electrical.batteries.current')
@@ -119,19 +131,15 @@ class ShenzhenLiONBMS extends BTSensor{
119
131
  .default="electrical.batteries.{batteryID}.balanceMemoryActive"
120
132
 
121
133
  this.addMetadatum('protectionState','', 'protection state',
122
- (buff)=>{return buff.slice(76,80).reverse().join("")})
134
+ (buff)=>{return ProtectionStatus[buff.readUInt16LE(76)]??"Normal"})
123
135
  .default="electrical.batteries.{batteryID}.protectionState"
124
136
 
125
137
  this.addMetadatum('failureState','', 'failure state',
126
138
  (buff)=>{return buff.slice(80,84).reverse().join("").slice(-3)})
127
139
  .default="electrical.batteries.{batteryID}.failureState"
128
-
129
- this.addMetadatum('balanceState','', '1 = cell at offset is balancing',
130
- (buff)=>{return buff.slice(84,88).reverse().join("")})
131
- .default="electrical.batteries.{batteryID}.balanceState"
132
140
 
133
141
  this.addMetadatum('batteryState','', 'charge disabled = "0004", charging = "0001" (when charging active app will show estimated time untill fully charged), discharging/idle: "0000", unkown = "0002"',
134
- (buff)=>{return buff.slice(88,90).reverse().join("")})
142
+ (buff)=>{return (BatteryState[buff.readUInt16LE(88)])??"Unknown"})
135
143
  .default="electrical.batteries.{batteryID}.batteryState"
136
144
 
137
145
  this.addMetadatum('dischargeCount','', 'discharge count',
@@ -148,6 +156,16 @@ class ShenzhenLiONBMS extends BTSensor{
148
156
  this.getJSONSchema().properties.params.required=["batteryID", "numberOfCells" ]
149
157
  }
150
158
 
159
+ emitValuesFrom(buffer){
160
+ super.emitValuesFrom(buffer)
161
+ const balanceState= buffer.slice(84,88).reverse().join("")
162
+
163
+ for(let cellNum=0; cellNum < this?.numberOfCells??4; cellNum++) {
164
+ this.emit(`cell${cellNum+1}Balancing`, balanceState[cellNum]==='1')
165
+ }
166
+
167
+ }
168
+
151
169
  async initGATTConnection(isReconnecting){
152
170
 
153
171
  await super.initGATTConnection(isReconnecting)