bt-sensors-plugin-sk 1.2.0 → 1.2.2

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.
Files changed (35) hide show
  1. package/BTSensor.js +0 -3
  2. package/README.md +33 -10
  3. package/index.js +9 -2
  4. package/package.json +3 -3
  5. package/plugin_defaults.json +21 -2
  6. package/public/images/Aranet2_HOME_F_900x900_90OVA5J.original.webp +0 -0
  7. package/public/images/BMV-712-Smart-Front.webp +0 -0
  8. package/public/images/Gobius_C.png +0 -0
  9. package/public/images/LYWSD03MMC-Device.jpg +0 -0
  10. package/public/images/SmartShunt_500_nw.png +0 -0
  11. package/public/images/Victron-SmartShunt.jpg +0 -0
  12. package/public/images/bluetooth-logo.png +0 -0
  13. package/public/images/smartsolarMPPT7515.png +0 -0
  14. package/public/images/victron-min-1.jpg +0 -0
  15. package/sensor_classes/Junctek.js +148 -0
  16. package/sensor_classes/KilovaultHLXPlus.js +1 -1
  17. package/sensor_classes/RemoranWave3.js +217 -0
  18. package/sensor_classes/Renogy/RenogySensor.js +3 -2
  19. package/sensor_classes/ShenzhenLiOnBMS.js +26 -16
  20. package/sensor_classes/Victron/VictronImages.js +1 -1
  21. package/sensor_classes/Victron/VictronSensor.js +4 -2
  22. package/sensor_classes/VictronBatteryMonitor.js +2 -2
  23. package/sensor_classes/VictronDCDCConverter.js +1 -1
  24. package/sensor_classes/VictronLynxSmartBMS.js +5 -1
  25. package/sensor_classes/VictronOrionXS.js +1 -1
  26. package/sensor_classes/VictronSmartLithium.js +1 -1
  27. package/src/images/Aranet2_HOME_F_900x900_90OVA5J.original.webp +0 -0
  28. package/src/images/BMV-712-Smart-Front.webp +0 -0
  29. package/src/images/Gobius_C.png +0 -0
  30. package/src/images/LYWSD03MMC-Device.jpg +0 -0
  31. package/src/images/Victron-SmartShunt.jpg +0 -0
  32. package/src/images/bluetooth-logo.png +0 -0
  33. package/src/images/smartsolarMPPT7515.png +0 -0
  34. package/src/images/victron-min-1.jpg +0 -0
  35. package/vsl_patch_17_06_25.patch +13 -0
package/BTSensor.js CHANGED
@@ -787,8 +787,6 @@ class BTSensor extends EventEmitter {
787
787
  /**
788
788
  * Listen to sensor.
789
789
  * ::listen() sets up listeners for property changes thru ::propertiesChanged(props)
790
- * If GATT connections are available and active, function inits the GATT connection and
791
- * optional GATT connection interval
792
790
  */
793
791
 
794
792
 
@@ -842,7 +840,6 @@ class BTSensor extends EventEmitter {
842
840
  if (!(path===undefined))
843
841
  this.app.handleMessage(id,
844
842
  {
845
- // $source: source,
846
843
  updates:
847
844
  [{ meta: [{path: preparePath(this, path), value: { units: pathMeta?.unit }}]}]
848
845
  })
package/README.md CHANGED
@@ -1,17 +1,11 @@
1
1
  # Bluetooth Sensors for [Signal K](http://www.signalk.org)
2
- # Version 1.2.0
2
+ # Version 1.2.2
3
3
 
4
- ## WHAT IT IS
5
-
6
- BT Sensors Plugin for Signalk is a lightweight BLE (Bluetooth Low Energy) framework for listening and connecting to Bluetooth sensors on your boat. After discovery and configuration the plugin sends deltas to Signalk paths with values your sensor reports. <br>
7
-
8
- It runs on any 2.0 or greater SignalK installation but on Linux only. It's been tested on Desktop and headless RPis, OpenPlotter, and Cerbo GX/Ekrano.
9
-
10
- A typical use case is a Bluetooth thermometer like the Xiaomi LYWSD03MMC, an inexpensive Bluetooth thermometer that runs on a 3V watch battery that can report the current temperature and humidity in your refrigerator or cabin or wherever you want to stick it (no judgement.) <br>
4
+ ## WHAT'S NEW SINCE VERSION 1.2.1
11
5
 
12
- The reported temperature can then be displayed on a Signalk app like Kip, WilhelmSK or, with appropriate mapping to NMEA-2000, a NMEA 2000 Multi-function display.
6
+ - Junctek BMS and Remoran Wave 3 support (Note: both are not currently field tested)
13
7
 
14
- It's pretty easy to write and deploy your own sensor class for any currently unsupported sensor. More on that in [the development README](./sensor_classes/DEVELOPMENT.md).
8
+ - Fixes to ShenzhenLiOn BMS, Victron Orion XS, Victron DC DC Converter, Victron Smart Lithium Classes
15
9
 
16
10
  ## WHAT'S NEW SINCE VERSION 1.1.x
17
11
 
@@ -25,6 +19,19 @@ It's pretty easy to write and deploy your own sensor class for any currently uns
25
19
 
26
20
  - Support for multiple simultaneous GATT connections.
27
21
 
22
+
23
+ ## WHAT IT IS
24
+
25
+ BT Sensors Plugin for Signalk is a lightweight BLE (Bluetooth Low Energy) framework for listening and connecting to Bluetooth sensors on your boat. After discovery and configuration the plugin sends deltas to Signalk paths with values your sensor reports. <br>
26
+
27
+ It runs on any 2.0 or greater SignalK installation but on Linux only. It's been tested on Desktop and headless RPis, OpenPlotter, and Cerbo GX/Ekrano.
28
+
29
+ A typical use case is a Bluetooth thermometer like the Xiaomi LYWSD03MMC, an inexpensive Bluetooth thermometer that runs on a 3V watch battery that can report the current temperature and humidity in your refrigerator or cabin or wherever you want to stick it (no judgement.) <br>
30
+
31
+ The reported temperature can then be displayed on a Signalk app like Kip, WilhelmSK or, with appropriate mapping to NMEA-2000, a NMEA 2000 Multi-function display.
32
+
33
+ It's pretty easy to write and deploy your own sensor class for any currently unsupported sensor. More on that in [the development README](./sensor_classes/DEVELOPMENT.md).
34
+
28
35
  ## SUPPORTED SENSORS
29
36
 
30
37
  ### NOTE
@@ -44,6 +51,8 @@ It's pretty easy to write and deploy your own sensor class for any currently uns
44
51
  |Redodo| Rebranded LiTime |
45
52
  |Kilovault| [Kilovault HLX+ smart batteries ](https://sunwatts.com/content/manual/KiloVault_HLX_PLUS_Datasheet_06252021%20%281%29.pdf?srsltid=AfmBOooY-cGnC_Qm6V1T9Vg5oZzBCJurS0AOGoWqWeyy-dwz2vA-l1Jb) (Note: Kilovault appears to be out of business as of March 2024) |
46
53
  |[Lancol](www.Lancol.com)| [Micro 10C 12V Car Battery Monitor](https://www.lancol.com/product/12v-bluetooth-4-0-battery-tester-micro-10-c/)|
54
+ |[Junctek](https://www.junteks.com)|[Junctek BMS](https://www.junteks.com/pages/product/index) |
55
+ |[Remoran](https://remoran.eu)| [Remoran Wave.3](https://remoran.eu/wave.html)|
47
56
 
48
57
 
49
58
  ### Environmental
@@ -121,6 +130,19 @@ Finally, restart SK. Plugin should appear in your server plugins list.<br>
121
130
  > NOTE: "~/.signalk" is the default signalk home on Linux. If you're
122
131
  > getting permissions errors executing npm link, try executing "npm link" under sudo.
123
132
 
133
+ ## KNOWN ISSUES
134
+
135
+ ### Configuration Panel
136
+
137
+ - Safari 18.1 on OsX produces errors on load that kill the configuration screen. No known cause. Upgrade to most recent Safari or use Chrome.
138
+ - Unsaved sensor configuration changes are lost after selecting a different sensor. Be sure to Save changes for now.
139
+ - Renogy Rover Client, Victron GX, Victron Smart Battery Protect, and Victron VE Bus sensor classes have no default paths currently. Users will need to manually input.
140
+
141
+ ### Runtime
142
+
143
+ - IMPORTANT Set `Scan for new devices interval` to `0` after configuration is complete. The plugin will run but in Bluetooth-rich environments, or if you have a long range BT 5.3 device, the system Bluetooth stack may fail after 4 hours or so.
144
+ - There's no way that I know of to remove a SK Path without restarting the server. So if any active paths are changed by the plugin, you'll still see them hanging around in the data browser growing stale until you restart the server.
145
+
124
146
  ## CONFIGURATION
125
147
 
126
148
  After installing and restarting Signalk you should see a "BT Sensors Plugin" option in the Signalk->Server->Plugin Config page.<br><br>
@@ -266,6 +288,7 @@ Many thanks to all those who contributed to the project either with code or test
266
288
  - Arjen
267
289
  - SDLee
268
290
  - Jordan
291
+ - Jan of SKipper App fame
269
292
 
270
293
  It takes a village. Or more appropriately, an armada. Okay, regatta. But you get the idea.
271
294
 
package/index.js CHANGED
@@ -172,10 +172,19 @@ module.exports = function (app) {
172
172
  });
173
173
  router.post('/removeSensorData', async (req, res) => {
174
174
  app.debug(req.body)
175
+ const sensor = sensorMap.get(req.body.mac_address)
176
+ if (!sensor) {
177
+ res.status(404).json({message: "Sensor not found"})
178
+ return
179
+ }
175
180
  const i = deviceConfigs.findIndex((p)=>p.mac_address==req.body.mac_address)
176
181
  if (i>=0){
177
182
  deviceConfigs.splice(i,1)
178
183
  }
184
+
185
+ if (sensor.isActive())
186
+ await sensor.stopListening()
187
+
179
188
  if (sensorMap.has(req.body.mac_address))
180
189
  sensorMap.delete(req.body.mac_address)
181
190
  app.savePluginOptions(
@@ -569,7 +578,6 @@ module.exports = function (app) {
569
578
  progressID = setInterval(()=>{
570
579
  channel.broadcast({"progress":++progress, "maxTimeout": maxTimeout, "deviceCount":foundConfiguredDevices, "totalDevices": deviceConfigs.length},"progress")
571
580
  if ( foundConfiguredDevices==deviceConfigs.length){
572
- app.debug("progress complete")
573
581
  progressID,progressTimeoutID = null
574
582
  clearTimeout(progressTimeoutID)
575
583
  clearInterval(progressID)
@@ -578,7 +586,6 @@ module.exports = function (app) {
578
586
  },1000);
579
587
  if (progressTimeoutID==null)
580
588
  progressTimeoutID = setTimeout(()=> {
581
- app.debug("progress timed out ")
582
589
  if (progressID) {
583
590
 
584
591
  clearInterval(progressID);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bt-sensors-plugin-sk",
3
- "version": "1.2.0",
4
- "description": "Bluetooth Sensors for Signalk -- support for Victron devices, RuuviTag, Xiaomi, ATC and Inkbird, Ultrasonic wind meters, Mopeka tank readers, Renogy Battery and Solar Controllers, Aranet4 environment sensors, SwitchBot temp and humidity sensors, KilovaultHLXPlus smart batteries, and Govee GVH51xx temp sensors",
3
+ "version": "1.2.2",
4
+ "description": "Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk for a list of supported classes",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
7
  "@rjsf/bootstrap-4": "^5.24.11",
@@ -54,7 +54,7 @@
54
54
  "prepublishOnly": "npm run clean && npm run build",
55
55
  "dev": "webpack --watch --mode development",
56
56
  "build": "webpack --mode=production",
57
- "clean": "rimraf ./public",
57
+ "clean": "rimraf ./public/*.js; rimraf ./public/*.txt",
58
58
  "bundle-analyzer": "webpack-bundle-analyzer --port 4200 public/stats.json"
59
59
  },
60
60
  "repository": {
@@ -96,6 +96,17 @@
96
96
  "default": "electrical.batteries.{batteryID}.voltage"
97
97
  },
98
98
 
99
+ "power":
100
+ {
101
+ "unit": "W",
102
+ "default": "electrical.batteries.{batteryID}.power"
103
+ },
104
+
105
+ "impedance":
106
+ {
107
+ "unit": "Ohm",
108
+ "default": "electrical.batteries.{batteryID}.impedance"
109
+ },
99
110
  "temperature":{
100
111
  "unit": "K",
101
112
  "default": "electrical.batteries.{batteryID}.temperature"
@@ -107,13 +118,21 @@
107
118
 
108
119
  "capacity":{
109
120
  "remaining":{
110
- "unit":"Ah",
121
+ "unit":"C",
111
122
  "default": "electrical.batteries.{batteryID}.capacity.remaining"
112
123
  },
113
124
  "actual":{
114
- "unit":"Ah",
125
+ "unit":"C",
115
126
  "default": "electrical.batteries.{batteryID}.capacity.actual"
116
127
  },
128
+ "discharge":{
129
+ "unit":"KWh",
130
+ "default": "electrical.batteries.{batteryID}.capacity.dischargeSinceFull"
131
+ },
132
+ "charge":{
133
+ "unit":"KWh",
134
+ "default": "electrical.batteries.{batteryID}.capacity.totalCharge"
135
+ },
117
136
  "stateOfCharge":{
118
137
  "unit":"ratio",
119
138
  "default": "electrical.batteries.{batteryID}.capacity.stateOfCharge"
Binary file
Binary file
Binary file
@@ -0,0 +1,148 @@
1
+
2
+
3
+ const BTSensor = require("../BTSensor");
4
+
5
+ function bytesToBase10String(bytes){
6
+ let s = ""
7
+ for (let byte of bytes){
8
+ s+=byte.toString(16)
9
+ }
10
+ return s
11
+ }
12
+
13
+ class JunctekBMS extends BTSensor{
14
+ static Domain = BTSensor.SensorDomains.electrical
15
+
16
+ constructor(device, config, gattConfig) {
17
+ super(device, config, gattConfig)
18
+ }
19
+
20
+ static async identify(device){
21
+
22
+ return null
23
+ }
24
+
25
+ hasGATT(){
26
+ return true
27
+ }
28
+
29
+
30
+
31
+ async initSchema(){
32
+ super.initSchema()
33
+ this.addDefaultParam("batteryID")
34
+
35
+ this.addDefaultPath("voltage","electrical.batteries.voltage")
36
+
37
+ this.addDefaultPath("current","electrical.batteries.current")
38
+
39
+ this.addDefaultPath("power","electrical.batteries.power")
40
+
41
+ this.addDefaultPath("cycles",'electrical.batteries.cycles')
42
+
43
+ this.addDefaultPath("soc",'electrical.batteries.capacity.stateOfCharge')
44
+ this.addDefaultPath("remainingAh",'electrical.batteries.capacity.remaining')
45
+ this.addDefaultPath("timeRemaining",'electrical.batteries.capacity.timeRemaining')
46
+ this.addDefaultPath("discharge",'electrical.batteries.capacity.dischargeSinceFull')
47
+ this.addDefaultPath("charge",'electrical.batteries.capacity.charge')
48
+ this.addDefaultPath("temperature",'electrical.batteries.temperature')
49
+ this.addDefaultPath("actualCapacity",'electrical.batteries.capacity.actual')
50
+ this.addMetadatum('impedance','mOhm', 'measured resistance')
51
+ .default='electrical.batteries.{batteryID}.impedance'
52
+ }
53
+
54
+ emitFrom(buffer){
55
+ var value=[], chargeDirection = 1
56
+
57
+ for (let byte of buffer){
58
+ if (byte==0xBB) {
59
+ value=[]
60
+ continue
61
+ }
62
+ if (byte==0xEE){
63
+ continue
64
+ }
65
+
66
+ value.push[byte]
67
+
68
+ if (parseInt(byte.toString(16))==NaN){ //not a base-10 number. seriously. that's how Junctek does this.
69
+ const v = parseInt(bytesToBase10String(value))
70
+ switch (byte){
71
+ case 0xC0:{
72
+ emitData("voltage",v/100)
73
+ }
74
+ case 0xC1:{
75
+ emitData("current",(v/100)*chargeDirection)
76
+ }
77
+
78
+ case 0xD1:{
79
+ if (byte==0)
80
+ chargeDirection=-1
81
+ }
82
+
83
+ case 0xD2:{
84
+ emitData("remainingAh",v/1000)
85
+ }
86
+
87
+ case 0xD3:{
88
+ emitData("discharge",v/100000)
89
+ }
90
+ case 0xD4:{
91
+ emitData("charge",v/100000)
92
+ }
93
+ case 0xD6:{
94
+ emitData("timeRemaining",v*60)
95
+ }
96
+ case 0xD7:{
97
+ emitData("impedance",v/100)
98
+ }
99
+ case 0xD8:{
100
+ emitData("power",(v/100)*chargeDirection)
101
+ }
102
+ case 0xD9:{
103
+ emitData("temperature",v + 173.15) //assume C not F
104
+ }
105
+ case 0xB1:{
106
+ emitData("capacityActual",v /10 )
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ emitGATT(){
113
+ this.battCharacteristic.readValue()
114
+ .then((buffer)=>{
115
+ this.emitFrom(buffer)
116
+ })
117
+ }
118
+ initGATTConnection(){
119
+ return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
120
+ if (!this.gattServer) {
121
+ this.gattServer = await this.device.gatt()
122
+ this.battService = await this.gattServer.getPrimaryService("0000fff0-0000-1000-8000-00805f9b34fb")
123
+ this.battCharacteristic = await this.battService.getCharacteristic("0000ffe1-0000-1000-8000-00805f9b34fb")
124
+ }
125
+ resolve(this)
126
+ }) .catch((e)=>{ reject(e.message) }) })
127
+ }
128
+
129
+ initGATTNotifications() {
130
+ Promise.resolve(this.battCharacteristic.startNotifications().then(()=>{
131
+ this.battCharacteristic.on('valuechanged', buffer => {
132
+ this.emitFrom(buffer)
133
+ })
134
+ }))
135
+ }
136
+
137
+ async stopListening(){
138
+ super.stopListening()
139
+ if (this.battCharacteristic && await this.battCharacteristic.isNotifying()) {
140
+ await this.battCharacteristic.stopNotifications()
141
+ this.battCharacteristic=null
142
+ }
143
+ if (await this.device.isConnected()){
144
+ await this.device.disconnect()
145
+ }
146
+ }
147
+ }
148
+ module.exports=JunctekBMS
@@ -78,7 +78,7 @@ class KilovaultHLXPlus extends BTSensor{
78
78
  this.addDefaultPath("cycles",'electrical.batteries.cycles')
79
79
  .read=(buffer)=>{return buffer.readInt16LE(12)}
80
80
 
81
- this.addDefaultPath("soc",'electrical.batteries.capacity,stateOfCharge')
81
+ this.addDefaultPath("soc",'electrical.batteries.capacity.stateOfCharge')
82
82
  .read=(buffer)=>{return buffer.readInt16LE(14)}
83
83
 
84
84
  this.addDefaultPath("temperature",'electrical.batteries.temperature')
@@ -0,0 +1,217 @@
1
+
2
+ function arduinoDateDecode (elapsedSeconds) {
3
+ const date = new Date("2000-01-01")
4
+ date.setTime(date.getTime() + 1000 * elapsedSeconds)
5
+ return date
6
+ }
7
+ const errors= {
8
+ 0: "Undefined",
9
+ 1: "Invalid Battery",
10
+ 2: "Overheat",
11
+ 3: "Overheat Shutdown",
12
+ 4: "Generator lead 1 disconnected",
13
+ 5: "Generator lead 2 disconnected",
14
+ 6: "Generator lead 3 disconnected",
15
+ 7: "Short Circuit"
16
+ }
17
+
18
+ const eventTypes = {
19
+ 0: "Reboot",
20
+ 1: "Invalid Battery",
21
+ 2: "Overheat",
22
+ 3: "Overheat Shutdown",
23
+ 4: "Generator lead 1 disconnected",
24
+ 5: "Generator lead 2 disconnected",
25
+ 6: "Generator lead 3 disconnected",
26
+ 7: "Short Circuit",
27
+ 255: "Debug"
28
+ }
29
+
30
+ const states= ["Charging Needed", "Charging", "Floating", "Idle"]
31
+
32
+
33
+ const BTSensor = require("../BTSensor");
34
+ class RemoranWave3 extends BTSensor{
35
+ static Domain = BTSensor.SensorDomains.electrical
36
+ serviceUUID = "81D08DF0-C0F8-422A-9D9D-E4379BB1EA3B"
37
+ info1CharUUID = "62C91222-FAFE-4F6E-95F0-AFC02BD19F2E"
38
+ info2CharUUID = "f5d12d34-4390-486c-b906-24ea8906af71"
39
+ eventUuid = "f12a8e25-59f7-42f2-b7ae-ba96fb25c13c"
40
+
41
+ static async identify(device){
42
+
43
+ const name = await this.getDeviceProp(device,"Name")
44
+ if (name == 'Remoran Wave.3')
45
+ return this
46
+ else
47
+ return null
48
+ }
49
+ hasGATT(){
50
+ return true
51
+ }
52
+ usingGATT(){
53
+ return true
54
+ }
55
+ emitInfo1Data(buffer){
56
+ if (buffer.length < 20) {
57
+ app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 20 bytes or more.`)
58
+ return
59
+ }
60
+ emitData("versionNumber", buffer.readUInt8(0))
61
+ const errors = buffer.readUInt8(2)
62
+ const errorState = []
63
+ for (var i = 0; i < 8; ++i) {
64
+ var c = 1 << i;
65
+ r & c && errorState.push(errors[i])
66
+ }
67
+ emitData("errors", errorState)
68
+ emitData("state", states[buffer.readUInt8(3)])
69
+ emitData("rpm", buffer.readUInt32LE(4))
70
+ emitData( "voltage" , buffer.readFloatLE(8))
71
+ emitData("current", buffer.readFloatLE(12))
72
+ emitData( "power", buffer.readFloatLE(16))
73
+
74
+ if (buffer.length > 23) {
75
+ emitData( "temp", buffer.readFloatLE(20).toFixed(1))
76
+ emitData( "uptime", buffer.readUInt32(24))
77
+ if (versionNumber>1 && buffer.size > 31) {
78
+ emitData("energy", buffer.readFloatLE(32).toFixed(1))
79
+ }
80
+ }
81
+
82
+ }
83
+ emitInfo2Data(buffer){
84
+ if (buffer.size < 12) {
85
+ app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 12 bytes or more.`)
86
+ return
87
+ }
88
+ emitData("versionNumber", buffer.getUint8(0))
89
+ emitData("temp", buffer.readFloat32LE(4))
90
+ emitData("uptime", buffer.readUInt32(8))
91
+ emitData("lastBootTime", arduinoDateDecode(buffer.getUInt32LE(12)))
92
+ emitData("energy", buffer.readFloat32LE(16))
93
+ }
94
+ emitEventData(buffer){
95
+ if (buffer.size < 14) {
96
+ app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 14 bytes or more.`)
97
+ return
98
+ }
99
+ this.emitData("event",
100
+ {
101
+ firstDate: arduinoDateDecode(buffer.readUInt32LE(0)),
102
+ lastDate: arduinoDateDecode(buffer.readUInt32LE(4)),
103
+ eventType: buffer.readUInt16LE(8),
104
+ count: buffer.readUInt16LE(10),
105
+ index: buffer.readUInt16LE(12),
106
+ eventDesc: eventTypes[i]
107
+ }
108
+ )
109
+ }
110
+ emitGATT(){
111
+ this.info1Characteristic.readValue()
112
+ .then((buffer)=>
113
+ this.emitInfo1Data( buffer)
114
+ )
115
+ this.info2Characteristic.readValue()
116
+ .then((buffer)=>
117
+ this.emitInfo2Data(buffer)
118
+ )
119
+ this.eventCharacteristic.readValue()
120
+ .then((buffer)=>
121
+ this.emitEventData(buffer)
122
+ )
123
+
124
+ }
125
+ initSchema(){
126
+ super.initSchema()
127
+ this.addDefaultParam("id")
128
+ .default="RemoranWave3"
129
+
130
+ this.getGATTParams()["useGATT"].default=true
131
+
132
+ this.addMetadatum('errorCodes','', 'charger error codes (array)')
133
+ .default= "electrical.chargers.{id}.errorCodes"
134
+
135
+ this.addMetadatum('state','', 'charger state')
136
+ .default= "electrical.chargers.{id}.state"
137
+
138
+ this.addMetadatum('voltage','V', 'battery voltage')
139
+ .default= "electrical.chargers.{id}.battery.voltage"
140
+
141
+ this.addMetadatum('current','A', 'battery current')
142
+ .default= "electrical.chargers.{id}.battery.current"
143
+
144
+ this.addMetadatum('power','W', 'battery power')
145
+ .default= "electrical.chargers.{id}.battery.power"
146
+
147
+ this.addMetadatum('temp', 'K', 'charger temperature')
148
+ .default= "electrical.chargers.{id}.temperature"
149
+
150
+ this.addMetadatum('energy', 'wh', 'energy created today in Wh')
151
+ .default= "electrical.chargers.{id}.energy"
152
+
153
+ this.addMetadatum('event', '', 'charger event')
154
+ .default= "electrical.chargers.{id}.event"
155
+
156
+ this.addMetadatum('lastBootTime', 's', 'last boot time')
157
+ .default= "electrical.chargers.{id}.lastBootTime"
158
+
159
+ this.addMetadatum('rpm', '', 'revolutions per minute')
160
+ .default= "sensors.{macAndName}.rpm"
161
+
162
+ this.addMetadatum('uptime', 's', 'charger/sensor uptime')
163
+ .default= "sensors.{macAndName}.uptime"
164
+
165
+ this.addMetadatum('versionNumber', '', 'charger/sensor version number')
166
+ .default= "sensors.{macAndName}.version"
167
+
168
+ }
169
+
170
+
171
+ initGATTConnection(){
172
+ return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
173
+ if (!this.gattServer) {
174
+ this.gattServer = await this.device.gatt()
175
+ this.service = await this.gattServer.getPrimaryService(this.serviceUUID)
176
+ this.info1Characteristic = await this.service.getCharacteristic(this.info1CharUUID)
177
+ this.info2Characteristic = await this.service.getPrimaryService(this.info2CharUUID)
178
+ this.eventCharacteristic = await this.service.getCharacteristic(this.eventUUID)
179
+ resolve(this)
180
+ }}) .catch((e)=>{ reject(e.message) }) })
181
+ }
182
+
183
+ initGATTNotifications() {
184
+ Promise.resolve(this.info1Characteristic.startNotifications().then(()=>{
185
+ this.info1Characteristic.on('valuechanged', buffer => {
186
+ this.emitInfo1Data(buffer)
187
+ })
188
+ }))
189
+ Promise.resolve(this.info2Characteristic.startNotifications().then(()=>{
190
+ this.info2Characteristic.on('valuechanged', buffer => {
191
+ this.emitInfo2Data(buffer)
192
+ })
193
+ }))
194
+ Promise.resolve(this.eventCharacteristic.startNotifications().then(()=>{
195
+ this.eventCharacteristic.on('valuechanged', buffer => {
196
+ this.emitEventData(buffer)
197
+ })
198
+ }))
199
+ }
200
+
201
+ async stopNotifications(characteristic){
202
+ if (characteristic && await characteristic.isNotifying()) {
203
+ await characteristic.stopNotifications()
204
+ }
205
+ }
206
+ async stopListening(){
207
+ super.stopListening()
208
+ await stopNotifations(this.info1Characteristic)
209
+ await stopNotifations(this.info2Characteristic)
210
+ await stopNotifations(this.eventCharacteristic)
211
+ if (await this.device.isConnected()){
212
+ await this.device.disconnect()
213
+ }
214
+ }
215
+ }
216
+ module.exports=RemoranWave3
217
+
@@ -86,7 +86,7 @@ class RenogySensor extends BTSensor{
86
86
  }
87
87
 
88
88
  initGATTInterval(){
89
- this.emitGATT()
89
+ this.emitGATT()
90
90
  this.intervalID = setInterval(()=>{
91
91
  this.emitGATT()
92
92
  }, 1000*(this?.pollFreq??60) )
@@ -121,7 +121,8 @@ class RenogySensor extends BTSensor{
121
121
  async stopListening(){
122
122
  super.stopListening()
123
123
 
124
- await this.readChar.stopNotifications()
124
+ if (this.readChar)
125
+ await this.readChar.stopNotifications()
125
126
 
126
127
  if (await this.device.isConnected()){
127
128
  await this.device.disconnect()
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Class to support Batteries with embedded Shenzhen Li-ion Battery Bodyguard Technology BMS
3
- * Brands include Redodo, Litime PowerQueen and possibly others
3
+ * Brands include Redodo, Litime, PowerQueen and possibly others
4
4
  */
5
5
 
6
6
 
@@ -34,19 +34,21 @@ class ShenzhenLiONBMS extends BTSensor{
34
34
  static async identify(device){
35
35
  return null
36
36
  }
37
+ async sendCommand(cmd){
38
+ await this.txCharacteristic.writeValueWithResponse( Buffer.from(cmd))
39
+ }
40
+
41
+ async queryBatteryCommand(){
42
+ await this.sendCommand(this.constructor.Commands.query_battery_status)
43
+ }
44
+
37
45
  hasGATT(){
38
46
  return true
39
47
  }
40
48
  usingGATT(){
41
49
  return true
42
50
  }
43
- emitGATT(){
44
- this.characteristic.readValue()
45
- .then((buffer)=>
46
- this.emitValuesFrom( buffer)
47
- )
48
51
 
49
- }
50
52
  initSchema(){
51
53
  super.initSchema()
52
54
  this.getGATTParams()["useGATT"].default=true
@@ -82,7 +84,7 @@ class ShenzhenLiONBMS extends BTSensor{
82
84
 
83
85
  for(let cellNum=0; cellNum < this?.numberOfCells??4; cellNum++) {
84
86
  this.addMetadatum(`cell${cellNum+1}Voltage`,'V', `cell #${cellNum+1} voltage`,
85
- (buff)=>{return buff.readInt16LE(16+(cellNum*2)) })
87
+ (buff)=>{return buff.readInt16LE(16+(cellNum*2)) /1000})
86
88
  .default=`electrical.batteries.{batteryID}.cells.${cellNum+1}.voltage`
87
89
  }
88
90
 
@@ -97,11 +99,11 @@ class ShenzhenLiONBMS extends BTSensor{
97
99
  (buff)=>{return buff.readInt16LE(54) + 273.15})
98
100
  .default="electrical.batteries.{batteryID}.bms.temperature"
99
101
 
100
- this.addDefaultPath('remainingAh','electrical.batteries.capacity.remaining')
101
- .read=(buff)=>{return this.buff.readUInt16LE(62)/100}
102
+ this.addDefaultPath('remaining','electrical.batteries.capacity.remaining')
103
+ .read=(buff)=>{return (buff.readUInt16LE(62)/100)*3600}
102
104
 
103
- this.addDefaultPath('actualAh','electrical.batteries.capacity.actual')
104
- .read=(buff)=>{return this.buff.readUInt16LE(64)/100}
105
+ this.addDefaultPath('actual','electrical.batteries.capacity.actual')
106
+ .read=(buff)=>{return (buff.readUInt16LE(64)/100)*3600}
105
107
 
106
108
  this.addMetadatum('heat','', 'discharge disabled due to app button = 00000080, heater_error = 00000002',
107
109
  (buff)=>{return buff.slice(68,72).reverse().join("")})
@@ -138,7 +140,6 @@ class ShenzhenLiONBMS extends BTSensor{
138
140
  this.addDefaultPath( 'soc',"electrical.batteries.capacity.stateOfCharge")
139
141
  .read=(buff)=>{return buff.readUInt16LE(90)/100}
140
142
 
141
-
142
143
  this.getJSONSchema().properties.params.required=["batteryID", "numberOfCells" ]
143
144
  }
144
145
 
@@ -155,16 +156,25 @@ class ShenzhenLiONBMS extends BTSensor{
155
156
  resolve(this)
156
157
  })})
157
158
  }
159
+
160
+ async initGATTInterval(){
161
+ await this.initGATTNotifications()
162
+ }
163
+
158
164
  async initGATTNotifications() {
159
- await this.txCharacteristic.writeValue( Buffer.from(this.constructor.Commands.query_battery_status))
160
165
  await this.rxCharacteristic.startNotifications()
161
166
  this.rxCharacteristic.on('valuechanged', buffer => {
162
167
  this.emitValuesFrom(buffer)
163
168
  })
169
+ this.intervalID=setInterval(
170
+ async ()=>{
171
+ await this.queryBatteryCommand()
172
+ }, (this?.pollFreq??10)*1000
173
+ )
164
174
  }
165
175
 
166
176
  async stopListening(){
167
- super.stopListening()
177
+ super.stopListening() //clears IntervalID as it happens
168
178
  if (this.rxCharacteristic && await this.rxCharacteristic.isNotifying()) {
169
179
  await this.rxCharacteristic.stopNotifications()
170
180
  this.rxCharacteristic=null
@@ -174,4 +184,4 @@ class ShenzhenLiONBMS extends BTSensor{
174
184
  }
175
185
  }
176
186
  }
177
- module.exports=ShenzhenLiONBMS
187
+ module.exports=ShenzhenLiONBMS
@@ -1,6 +1,6 @@
1
1
  Images= {
2
2
  generic: "victron-min-1.jpg ",
3
- shunt: "Victron-SmartShunt.jpg",
3
+ shunt: "SmartShunt_500_nw.png",
4
4
  bmv: "BMV-712-Smart-Front.webp",
5
5
  smartSolar: "smartsolarMPPT7515.png"
6
6
  }
@@ -130,10 +130,12 @@ function sleep(x) {
130
130
  }
131
131
 
132
132
  getDescription(){
133
- //return `<img src="https://www.victronenergy.com/_next/image?url=https%3A%2F%2Fwww.victronenergy.com%2Fupload%2Fproducts%2FSmartShunt%2520500_nw.png&w=1080&q=70"" height="150" object-fit="cover" ></img>`
133
+ //return `<img src="https://www.victronenergy.com/_next/image?url=https%3A%2F%2Fwww.victronenergy.com%2Fupload%2Fproducts%2FSmartShunt%2520500_nw.png&w=1080&q=70"" height="150" align=”top” ></img>`
134
134
 
135
135
 
136
- return `<img src="../bt-sensors-plugin-sk/images/${this.getImage()}" height="300" object-fit="cover" ></img>`
136
+ return `<img src="../bt-sensors-plugin-sk/images/${this.getImage()}" width="200" style="float: left;
137
+ margin: 20px;" ></img>
138
+ To get the encryption key for your device, follow the instructions <a href=https://communityarchive.victronenergy.com/questions/187303/victron-bluetooth-advertising-protocol.html target="_victron_encrypt">here</a>`
137
139
  }
138
140
 
139
141
  }
@@ -56,7 +56,7 @@ class VictronBatteryMonitor extends VictronSensor{
56
56
 
57
57
  const alarmMD = this.addMetadatum('alarm','', 'alarm',
58
58
  (buff,offset=0)=>{return buff.readInt16LE(offset)})
59
- alarmMD.default='"electrical.batteries.{batteryID}.alarm'
59
+ alarmMD.default='electrical.batteries.{batteryID}.alarm'
60
60
  alarmMD.notify=true
61
61
 
62
62
  this.addMetadatum( 'consumed','Ah', 'amp-hours consumed',
@@ -196,7 +196,7 @@ class VictronBatteryMonitor extends VictronSensor{
196
196
  }
197
197
 
198
198
  getDescription(){
199
- return super.getDescription()
199
+ 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)`
200
200
  }
201
201
 
202
202
  async stopListening(){
@@ -10,7 +10,7 @@ class VictronDCDCConverter extends VictronSensor{
10
10
 
11
11
  initSchema(){
12
12
  super.initSchema()
13
- this.addDefaultPath("id")
13
+ this.addDefaultParam("id")
14
14
 
15
15
  this.addMetadatum('deviceState','', 'device state',
16
16
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
@@ -24,9 +24,11 @@ class VictronLynxSmartBMS extends VictronSensor{
24
24
 
25
25
  initSchema(){
26
26
  super.initSchema()
27
+ this.addDefaultParam("batteryID")
27
28
  this.addMetadatum('error','', 'error code',
28
29
  (buff)=>{return buff.readUInt8(0)})
29
-
30
+ .default="electrical.batteries.{batteryID}.errorCode"
31
+
30
32
  this.addDefaultPath('ttg','electrical.batteries.capacity.timeRemaining')
31
33
  .read=(buff)=>{return this.NaNif(buff.readUInt16LE(1),0xFFFF)*60}
32
34
  this.addDefaultPath('voltage','electrical.batteries.voltage')
@@ -36,10 +38,12 @@ class VictronLynxSmartBMS extends VictronSensor{
36
38
 
37
39
  this.addMetadatum('ioStatus','','IO Status', //TODO
38
40
  (buff)=>{return buff.readUInt16LE(7)})
41
+ .default="electrical.batteries.{batteryID}.IOStatus"
39
42
 
40
43
  this.addMetadatum('warningsAndAlarms','','warnings and alarms')
41
44
 
42
45
  this.addDefaultPath('soc','electrical.batteries.capacity.stateOfCharge')
46
+
43
47
  this.addMetadatum('consumedAh','Ah','amp-hours consumed')
44
48
  .default="electrical.batteries.{batteryID}.capacity.ampHoursConsumed"
45
49
 
@@ -34,7 +34,7 @@ class VictronOrionXS extends VictronSensor{
34
34
  (buff)=>{return this.NaNif(buff.readUInt16LE(8),0xFFFF)/10})
35
35
  .default="electrical.chargers.{id}.input.current"
36
36
  this.addMetadatum('deviceOffReason','', 'device off reason',
37
- (buff)=>{return VC.OffReasons(buff.readUInt32BE(10))})
37
+ (buff)=>{return VC.OffReasons.get(buff.readUInt32BE(10))})
38
38
  .default="electrical.chargers.{id}.offReason"
39
39
  }
40
40
 
@@ -50,7 +50,7 @@ class VictronSmartLithium extends VictronSensor{
50
50
  (buff)=>{return buff.readUInt16LE(4)})
51
51
  .default="electrical.batteries.{batteryID}.errors"
52
52
 
53
- for (let i=0;i++;i<8){
53
+ for (let i=0;i<8; i++){
54
54
  this.addMetadatum(`cell${i+1}Voltage`,'V', `cell ${i+1} voltage`)
55
55
  .default=`electrical.batteries.{batteryID}.cell${i+1}.voltage`
56
56
  }
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,13 @@
1
+ diff --git a/sensor_classes/VictronSmartLithium.js b/sensor_classes/VictronSmartLithium.js
2
+ index 0ed70ad..2988374 100644
3
+ --- a/sensor_classes/VictronSmartLithium.js
4
+ +++ b/sensor_classes/VictronSmartLithium.js
5
+ @@ -50,7 +50,7 @@ class VictronSmartLithium extends VictronSensor{
6
+ (buff)=>{return buff.readUInt16LE(4)})
7
+ .default="electrical.batteries.{batteryID}.errors"
8
+
9
+ - for (let i=0;i++;i<8){
10
+ + for (let i=0;i<8; i++){
11
+ this.addMetadatum(`cell${i+1}Voltage`,'V', `cell ${i+1} voltage`)
12
+ .default=`electrical.batteries.{batteryID}.cell${i+1}.voltage`
13
+ }