bt-sensors-plugin-sk 1.3.6-3 → 1.3.6-beta5

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/diff.txt DELETED
@@ -1,2860 +0,0 @@
1
- diff --git a/BTSensor.js b/BTSensor.js
2
- index bcf4df6..16fcaa4 100644
3
- --- a/BTSensor.js
4
- +++ b/BTSensor.js
5
- @@ -74,7 +74,7 @@ function preparePath(obj, str) {
6
- evalResult= evalResult.call(obj)
7
- }
8
-
9
- - resultString += evalResult !== undefined ? evalResult : `${keyToAccess}_value_undefined`;
10
- + resultString += evalResult !== undefined ? evalResult.replace(/\s+/g,'_') : `${keyToAccess}_value_undefined`;
11
- } catch (error) {
12
- console.error(`Error accessing key '${keyToAccess}':`, error);
13
- resultString += fullMatch; // Keep the original curly braces on error
14
- @@ -107,6 +107,7 @@ class BTSensor extends EventEmitter {
15
- unknown: { name: "unknown", description: "Unknown sensor domain "},
16
- environmental: { name: "environmental", description: "Sensors that measure environmental conditions - air temperature, humidity etc."},
17
- electrical: { name: "electrical", description: "Electrical sensor - chargers, batteries, inverters etc."},
18
- + propulsion: { name: "propulsion", description: "Sensors that measure engine state"},
19
- tanks: { name: "tanks", description: "Sensors that measure level in tanks (gas, propane, water etc.) "}
20
- }
21
- static Domain = this.SensorDomains.unknown
22
- @@ -404,6 +405,12 @@ class BTSensor extends EventEmitter {
23
- if (!param.type)
24
- param.type="string"
25
-
26
- + if (param.isRequired) {
27
- + if (!Object.hasOwn(this._schema.properties.params, "required"))
28
- + this._schema.properties.params.required=[tag]
29
- + else
30
- + this._schema.properties.params.required.push(tag)
31
- + }
32
- this._schema.properties.params.properties[tag]=param
33
- return this._schema.properties.params.properties[tag]
34
- }
35
- @@ -412,6 +419,13 @@ class BTSensor extends EventEmitter {
36
- if (!path.type)
37
- path.type="string"
38
-
39
- + if (path.isRequired) {
40
- + if (!Object.hasOwn(this._schema.properties.paths, "required"))
41
- + this._schema.properties.paths.required=[tag]
42
- + else
43
- + this._schema.properties.paths.required.push(tag)
44
- + }
45
- +
46
- if (!path.pattern)
47
- path.pattern=//"^(?:[^{}\\s]*\\{[a-zA-Z0-9]+\\}[^{}\\s]*|[^{}\\s]*)$"
48
- "^((\\{[a-zA-Z0-9]+\\}|[a-zA-Z0-9]+))(\\.(\\{[a-zA-Z0-9]+\\}|[a-zA-Z0-9]+))*$"
49
- @@ -423,7 +437,13 @@ class BTSensor extends EventEmitter {
50
-
51
- if (!param.type)
52
- param.type="string"
53
- -
54
- +
55
- + if (param.isRequired) {
56
- + if (!Object.hasOwn(this._schema.properties.gattParams, "required"))
57
- + this._schema.properties.gattParams.required=[tag]
58
- + else
59
- + this._schema.properties.gattParams.required.push(tag)
60
- + }
61
- return this._schema.properties.gattParams.properties[tag]=param
62
- }
63
-
64
- @@ -432,8 +452,10 @@ class BTSensor extends EventEmitter {
65
- return this.addPath(tag,Object.assign({}, path))
66
- }
67
-
68
- - addDefaultParam(tag){
69
- - return this.addParameter(tag,Object.assign({}, BTSensor.DEFAULTS.params[tag]))
70
- + addDefaultParam(tag,required=false){
71
- + const param = Object.assign({}, BTSensor.DEFAULTS.params[tag])
72
- + param.isRequired=required
73
- + return this.addParameter(tag,param)
74
- }
75
-
76
- getJSONSchema(){
77
- @@ -465,13 +487,41 @@ class BTSensor extends EventEmitter {
78
- throw new Error("::initGATTNotifications() should be implemented by the BTSensor subclass")
79
- }
80
-
81
- - deviceConnect() {
82
- + deviceConnect(reconnect=false) {
83
-
84
-
85
- return connectQueue.enqueue( async ()=>{
86
- this.debug(`Connecting... ${this.getName()}`)
87
- - await this.device.connect()
88
- + await this.device.helper.callMethod('Connect')
89
- +
90
- this.debug(`Connected to ${this.getName()}`)
91
- + if (!reconnect) {
92
- + this.device.helper.on('PropertiesChanged', (propertiesChanged) => {
93
- + if ('Connected' in propertiesChanged) {
94
- + const { value } = propertiesChanged.Connected
95
- + if (value) {
96
- + this.device.emit('connect', { connected: true })
97
- + } else {
98
- + this.device.emit('disconnect', { connected: false })
99
- + }
100
- + }
101
- + })
102
- + this.device.on("disconnect", ()=>{
103
- + if (this.isActive()) {
104
- + this.debug(`Device disconnected. Attempting to reconnect to ${this.getName()}`)
105
- + try {
106
- + this.deviceConnect(true).then(()=>{
107
- + this.debug(`Device reconnected -- ${this.getName()}`)
108
- + })
109
- + }
110
- + catch (e) {
111
- + this.setPluginError( `Error while reconnecting to ${this.getName()}: ${e.message}`)
112
- + this.debug( `Error while reconnecting to ${this.getName()}: ${e.message}`)
113
- + this.debug(e)
114
- + }
115
- + }
116
- + })
117
- + }
118
-
119
- try {
120
-
121
- @@ -787,8 +837,6 @@ class BTSensor extends EventEmitter {
122
- /**
123
- * Listen to sensor.
124
- * ::listen() sets up listeners for property changes thru ::propertiesChanged(props)
125
- - * If GATT connections are available and active, function inits the GATT connection and
126
- - * optional GATT connection interval
127
- */
128
-
129
-
130
- @@ -839,13 +887,14 @@ class BTSensor extends EventEmitter {
131
- Object.keys(this.getPaths()).forEach((tag)=>{
132
- const pathMeta=this.getPath(tag)
133
- const path = config.paths[tag]
134
- - if (!(path===undefined))
135
- + if (!(path===undefined)) {
136
- + const preparedPath =
137
- this.app.handleMessage(id,
138
- {
139
- -// $source: source,
140
- updates:
141
- - [{ meta: [{path: preparePath(this, path), value: { units: pathMeta?.unit }}]}]
142
- + [{ meta: [{path: preparePath(this, path), value: { units: pathMeta?.unit }}]}]
143
- })
144
- + }
145
- })
146
- }
147
-
148
- @@ -855,11 +904,12 @@ class BTSensor extends EventEmitter {
149
- const pathMeta=this.getPath(tag)
150
- const path = deviceConfig.paths[tag];
151
- if (!(path === undefined)) {
152
- + let preparedPath = preparePath(this, path)
153
- this.on(tag, (val)=>{
154
- if (pathMeta.notify){
155
- this.app.notify(tag, val, id )
156
- } else {
157
- - this.updatePath(preparePath(this,path),val, id, source)
158
- + this.updatePath(preparedPath,val, id, source)
159
- }
160
- })
161
- }
162
- @@ -872,6 +922,9 @@ class BTSensor extends EventEmitter {
163
- return (Date.now()-this?._lastContact??Date.now())/1000
164
- }
165
-
166
- + prepareConfig(config){
167
- + config.params.sensorClass=this.constructor.name
168
- + }
169
-
170
- }
171
-
172
- diff --git a/README.md b/README.md
173
- index 5ed25cc..9f82afa 100644
174
- --- a/README.md
175
- +++ b/README.md
176
- @@ -1,7 +1,54 @@
177
- # Bluetooth Sensors for [Signal K](http://www.signalk.org)
178
- -# Version 1.2.0
179
-
180
- -## WHAT'S NEW SINCE VERSION 1.1.x
181
- +## WHAT'S NEW
182
- +
183
- +# Version 1.2.5
184
- +
185
- +- On initial startup, plugin saves default configuration. Fixing the "missing" configured devices after restart.
186
- +- Mopeka Tank Sensor configuration fix
187
- +- Added number of found devices in a domain in the configuration screen's domain tab
188
- +
189
- +## IMPORTANT NOTE TO NEW USERS OF VERSIONS OLDER THAN 1.2.5
190
- +
191
- +There's a known issue with saving the configuration after initial installation owing to current ServerAPI limitations.
192
- +
193
- +Your device config after you've saved it will appear to be "missing" after restarting. It's in fact saved in the plugin config directory. All you have to do is Submit the main configuration then enable and optionally disable Debug. This, believe it or not, ensures that the config is marked as enabled. You should see your data now and upon restart.
194
- +
195
- +# Version 1.2.4-4
196
- +
197
- +Junctek support (tested)
198
- +
199
- +# Version 1.2.4-3
200
- +
201
- +- Mercury Smartcraft fixes (working now!)
202
- +- Govee 510x regression errors fixed
203
- +
204
- +# Version 1.2.4-2
205
- +
206
- +- RenogyRoverClient fix to Battery SOC calculation
207
- +- VictronBatteryMonitor set default aux mode to secondary voltage
208
- +- SwitchBotTH and Meter Plus ::identify errors fixed
209
- +- MercurySmartcraft::identify method fix
210
- +
211
- +# Version 1.2.4-1
212
- +
213
- +- **NEW SENSOR** [Bank Manager](https://marinedcac.com/pages/bankmanager) (tested)
214
- +- **NEW SENSOR** [Mercury Smartcraft](https://www.mercurymarine.com/us/en/gauges-and-controls/displays/smartcraft-connect) (untested)
215
- +- Fixed Govee 5075 parsing
216
- +- Added defaults for Renogy Rover Client
217
- +- Automatic reconnect for GATT connected devices
218
- +
219
- +### Version 1.2.3
220
- +
221
- +Bug fixes Remoran Wave.3, JunctekBMS, and ShenzhenLiOn
222
- +
223
- +### Version 1.2.2
224
- +
225
- +- Junctek BMS and Remoran Wave 3 support (Note: both are not currently field tested)
226
- +
227
- +- Fixes to ShenzhenLiOn BMS, Victron Orion XS, Victron DC DC Converter, Victron Smart Lithium Classes
228
- +
229
- +### Version 1.2.1
230
-
231
- - Dynamic configuration screen. The Device list automatically updates when new devices are found by the BT scanner. (No more annoying screen refresh necessary!).
232
-
233
- @@ -13,6 +60,7 @@
234
-
235
- - Support for multiple simultaneous GATT connections.
236
-
237
- +
238
- ## WHAT IT IS
239
-
240
- 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>
241
- @@ -44,7 +92,9 @@ It's pretty easy to write and deploy your own sensor class for any currently uns
242
- |Redodo| Rebranded LiTime |
243
- |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) |
244
- |[Lancol](www.Lancol.com)| [Micro 10C 12V Car Battery Monitor](https://www.lancol.com/product/12v-bluetooth-4-0-battery-tester-micro-10-c/)|
245
- -
246
- +|[Junctek](https://www.junteks.com)|[Junctek BMS](https://www.junteks.com/pages/product/index) |
247
- +|[Remoran](https://remoran.eu)| [Remoran Wave.3](https://remoran.eu/wave.html)|
248
- +|[AC DC Systems](https://marinedcac.com) | [Bank Manager] hybrid (Pb and Li) charger(https://marinedcac.com/pages/bankmanager)|
249
-
250
- ### Environmental
251
- | Manufacturer | Devices |
252
- @@ -67,6 +117,13 @@ It's pretty easy to write and deploy your own sensor class for any currently uns
253
- | [Gobius](https://gobiusc.com/) | Gobius-C Tank level sensor|
254
- | [Mopeka](https://www.mopeka.com) | [Mopeka Pro Chek](https://mopeka.com/product-category/recreational-sensors-rv-bbq-etc/) ultrasonic tank level sensor |
255
-
256
- +### Propulsion
257
- +| Manufacturer | Devices |
258
- +|--------------|----------|
259
- +| [Mercury](https://www.mercurymarine.com)| [Mercury Smartcraft](https://www.mercurymarine.com/us/en/gauges-and-controls/displays/smartcraft-connect) connect engine sensor|
260
- +
261
- +
262
- +
263
-
264
- ## WHO IT'S FOR
265
-
266
- @@ -121,6 +178,35 @@ Finally, restart SK. Plugin should appear in your server plugins list.<br>
267
- > NOTE: "~/.signalk" is the default signalk home on Linux. If you're
268
- > getting permissions errors executing npm link, try executing "npm link" under sudo.
269
-
270
- +## KNOWN ISSUES
271
- +
272
- +### Connected Devices on Raspberry Pi platform (4/4b/5/CM400/CM500)
273
- +
274
- +Onboard Raspberry Pi WiFi can cause interference with the onboard Bluetooth resulting in lost connections to GATT connected devices (Renogy, JBD, etc. )
275
- +
276
- +One simple solution is to install a USB BT adapter like the [Tp-link UB500-Plus](https://www.tp-link.com/us/home-networking/usb-adapter/ub500-plus/)
277
- +
278
- +### USB 3.0 and HDMI interference
279
- +
280
- +Poorly shielded USB 3.0 and HDMI (5ghz) can interfere with BT transmission (2.4ghz).
281
- +
282
- +### Configuration Panel
283
- +
284
- +- 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.
285
- +- Unsaved sensor configuration changes are lost after selecting a different sensor. Be sure to Save changes for now.
286
- +- Victron GX, Victron Smart Battery Protect, and Victron VE Bus sensor classes have no default paths currently. Users will need to manually input.
287
- +
288
- +### Runtime
289
- +
290
- +- 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.
291
- +- 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.
292
- +- RPi 3/4/5/CM400s when running an Access Point on the on board Wifi can cause a problem connecting to devices through the onboard BT. The only known fix is to disable the onboard Bluetooth and use a USB BT adapter. Alternatively, you can use a USB WiFi adapter. NOTE: This only applies to _connected_ devices like Renogy devices, LiTime batteries etc.
293
- +
294
- +### Problems saving the configuration after installing plugin for first time (fixed as of Version 1.2.5)
295
- +
296
- +Device config after being saved will appear to be "missing" after restarting. The config is in fact saved in the plugin config directory. All you have to do is Submit the main configuration then enable and optionally disable Debug. This, believe it or not, ensures that the config is marked as enabled. You should see your data now and upon restart.
297
- +
298
- +
299
- ## CONFIGURATION
300
-
301
- After installing and restarting Signalk you should see a "BT Sensors Plugin" option in the Signalk->Server->Plugin Config page.<br><br>
302
- @@ -266,6 +352,7 @@ Many thanks to all those who contributed to the project either with code or test
303
- - Arjen
304
- - SDLee
305
- - Jordan
306
- +- Jan of SKipper App fame
307
-
308
- It takes a village. Or more appropriately, an armada. Okay, regatta. But you get the idea.
309
-
310
- diff --git a/index.js b/index.js
311
- index 90dbf06..85c912c 100644
312
- --- a/index.js
313
- +++ b/index.js
314
- @@ -1,5 +1,6 @@
315
- const packageInfo = require("./package.json")
316
-
317
- +
318
- const {createBluetooth} = require('node-ble')
319
- const {Variant} = require('dbus-next')
320
- const {bluetooth, destroy} = createBluetooth()
321
- @@ -138,10 +139,27 @@ module.exports = function (app) {
322
- var adapterID=options.adapter
323
- var foundConfiguredDevices=0
324
-
325
- + if (Object.keys(options).length==0){ //empty config means initial startup. save defaults and enabled=true.
326
- + let json = {configuration:{adapter:"hci0", transport:"le", discoveryTimeout:30, discoveryInterval:10}, enabled:true, enableDebug:false}
327
- + let appDataDirPath = app.getDataDirPath()
328
- + let jsonFile = appDataDirPath+'.json'
329
- + const fs = require("node:fs")
330
- + try {
331
- + fs.writeFileSync(jsonFile, JSON.stringify(json, null,2))
332
- + options=json
333
- + } catch(err){
334
- + console.log(`Error writing initial config: ${err.message} `)
335
- + console.log(err)
336
- + }
337
- +
338
- + }
339
- +
340
- plugin.registerWithRouter = function(router) {
341
-
342
- router.post('/updateSensorData', async (req, res) => {
343
- app.debug(req.body)
344
- + const sensor = sensorMap.get(req.body.mac_address)
345
- + sensor.prepareConfig(req.body)
346
- const i = deviceConfigs.findIndex((p)=>p.mac_address==req.body.mac_address)
347
- if (i<0){
348
- if (!options.peripherals){
349
- @@ -154,28 +172,36 @@ module.exports = function (app) {
350
- } else {
351
- options.peripherals[i] = req.body
352
- }
353
- + deviceConfigs=options.peripherals
354
- app.savePluginOptions(
355
- options, async () => {
356
- app.debug('Plugin options saved')
357
- res.status(200).json({message: "Sensor updated"})
358
- - const sensor = sensorMap.get(req.body.mac_address)
359
- if (sensor) {
360
- removeSensorFromList(sensor)
361
- if (sensor.isActive())
362
- await sensor.stopListening()
363
- - initConfiguredDevice(req.body)
364
- }
365
- -
366
- + initConfiguredDevice(req.body)
367
- }
368
- )
369
-
370
- });
371
- router.post('/removeSensorData', async (req, res) => {
372
- app.debug(req.body)
373
- + const sensor = sensorMap.get(req.body.mac_address)
374
- + if (!sensor) {
375
- + res.status(404).json({message: "Sensor not found"})
376
- + return
377
- + }
378
- const i = deviceConfigs.findIndex((p)=>p.mac_address==req.body.mac_address)
379
- if (i>=0){
380
- deviceConfigs.splice(i,1)
381
- }
382
- +
383
- + if (sensor.isActive())
384
- + await sensor.stopListening()
385
- +
386
- if (sensorMap.has(req.body.mac_address))
387
- sensorMap.delete(req.body.mac_address)
388
- app.savePluginOptions(
389
- @@ -201,12 +227,6 @@ module.exports = function (app) {
390
- }
391
- )
392
- });
393
- - router.get('/getDomains', (req, res) => {
394
- -
395
- - res.status(200).json(
396
- - BTSensor.SensorDomains
397
- - );
398
- - })
399
-
400
- router.get('/getBaseData', (req, res) => {
401
-
402
- @@ -231,9 +251,10 @@ module.exports = function (app) {
403
-
404
- router.get('/getProgress', (req, res) => {
405
- app.debug("Sending progress")
406
- - const json = {"progress":foundConfiguredDevices/deviceConfigs.length, "maxTimeout": 1,
407
- + let deviceCount = deviceConfigs.filter((dc)=>dc.active).length
408
- + const json = {"progress":foundConfiguredDevices/deviceCount, "maxTimeout": 1,
409
- "deviceCount":foundConfiguredDevices,
410
- - "totalDevices": deviceConfigs.length}
411
- + "totalDevices": deviceCount}
412
- res.status(200).json(json)
413
-
414
- });
415
- @@ -278,10 +299,12 @@ module.exports = function (app) {
416
- const config = getDeviceConfig(sensor.getMacAddress())
417
- const schema = sensor.getJSONSchema()
418
- schema.htmlDescription = sensor.getDescription()
419
- +
420
- return {
421
- info: getSensorInfo(sensor),
422
- schema: schema,
423
- - config: config?config:{}
424
- + config: config?config:{},
425
- + configCopy: JSON.parse(JSON.stringify(config?config:{}))
426
- }
427
- }
428
-
429
- @@ -400,8 +423,10 @@ module.exports = function (app) {
430
- try{
431
- const c = await getClassFor(device,config)
432
- c.debug=app.debug
433
- +
434
- const sensor = new c(device, config?.params, config?.gattParams)
435
- sensor.debug=app.debug
436
- + sensor.setPluginError=app.setPluginError
437
- sensor.app=app
438
- sensor._adapter=adapter //HACK!
439
- await sensor.init()
440
- @@ -411,7 +436,8 @@ module.exports = function (app) {
441
- const msg = `Unable to instantiate ${await BTSensor.getDeviceProp(device,"Address")}: ${error.message} `
442
- app.debug(msg)
443
- app.debug(error)
444
- - app.setPluginError(msg)
445
- + if (config.active)
446
- + app.setPluginError(msg)
447
- return null
448
- }
449
-
450
- @@ -564,12 +590,13 @@ module.exports = function (app) {
451
- }
452
- if (!(deviceConfigs===undefined)){
453
- const maxTimeout=Math.max(...deviceConfigs.map((dc)=>dc?.discoveryTimeout??options.discoveryTimeout))
454
- + const totalDevices = deviceConfigs.filter((dc)=>dc.active).length
455
- +
456
- var progress = 0
457
- if (progressID==null)
458
- progressID = setInterval(()=>{
459
- - channel.broadcast({"progress":++progress, "maxTimeout": maxTimeout, "deviceCount":foundConfiguredDevices, "totalDevices": deviceConfigs.length},"progress")
460
- - if ( foundConfiguredDevices==deviceConfigs.length){
461
- - app.debug("progress complete")
462
- + channel.broadcast({"progress":++progress, "maxTimeout": maxTimeout, "deviceCount":foundConfiguredDevices, "totalDevices": totalDevices},"progress")
463
- + if ( foundConfiguredDevices==totalDevices){
464
- progressID,progressTimeoutID = null
465
- clearTimeout(progressTimeoutID)
466
- clearInterval(progressID)
467
- @@ -578,12 +605,11 @@ module.exports = function (app) {
468
- },1000);
469
- if (progressTimeoutID==null)
470
- progressTimeoutID = setTimeout(()=> {
471
- - app.debug("progress timed out ")
472
- if (progressID) {
473
-
474
- clearInterval(progressID);
475
- progressID=null
476
- - channel.broadcast({"progress":maxTimeout, "maxTimeout": maxTimeout, "deviceCount":foundConfiguredDevices, "totalDevices": deviceConfigs.length},"progress")
477
- + channel.broadcast({"progress":maxTimeout, "maxTimeout": maxTimeout, "deviceCount":foundConfiguredDevices, "totalDevices": totalDevices},"progress")
478
- }
479
- }, (maxTimeout+1)*1000);
480
-
481
- diff --git a/package.json b/package.json
482
- index 8203e83..8eb2e4d 100644
483
- --- a/package.json
484
- +++ b/package.json
485
- @@ -1,9 +1,9 @@
486
- {
487
- "name": "bt-sensors-plugin-sk",
488
- - "version": "1.2.0",
489
- - "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",
490
- + "version": "1.2.5",
491
- + "description": "Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk#supported-sensors for a list of supported sensors",
492
- "main": "index.js",
493
- - "dependencies": {
494
- + "dependencies": {
495
- "@rjsf/bootstrap-4": "^5.24.11",
496
- "@rjsf/core": "^5.24.11",
497
- "@rjsf/utils": "^5.24.11",
498
- @@ -54,7 +54,7 @@
499
- "prepublishOnly": "npm run clean && npm run build",
500
- "dev": "webpack --watch --mode development",
501
- "build": "webpack --mode=production",
502
- - "clean": "rimraf ./public",
503
- + "clean": "rimraf ./public/*.js; rimraf ./public/*.txt",
504
- "bundle-analyzer": "webpack-bundle-analyzer --port 4200 public/stats.json"
505
- },
506
- "repository": {
507
- diff --git a/plugin_defaults.json b/plugin_defaults.json
508
- index 1430fd1..a2f7b37 100644
509
- --- a/plugin_defaults.json
510
- +++ b/plugin_defaults.json
511
- @@ -96,6 +96,17 @@
512
- "default": "electrical.batteries.{batteryID}.voltage"
513
- },
514
-
515
- + "power":
516
- + {
517
- + "unit": "W",
518
- + "default": "electrical.batteries.{batteryID}.power"
519
- + },
520
- +
521
- + "impedance":
522
- + {
523
- + "unit": "Ohm",
524
- + "default": "electrical.batteries.{batteryID}.impedance"
525
- + },
526
- "temperature":{
527
- "unit": "K",
528
- "default": "electrical.batteries.{batteryID}.temperature"
529
- @@ -107,13 +118,21 @@
530
-
531
- "capacity":{
532
- "remaining":{
533
- - "unit":"Ah",
534
- + "unit":"C",
535
- "default": "electrical.batteries.{batteryID}.capacity.remaining"
536
- },
537
- "actual":{
538
- - "unit":"Ah",
539
- + "unit":"C",
540
- "default": "electrical.batteries.{batteryID}.capacity.actual"
541
- },
542
- + "discharge":{
543
- + "unit":"KWh",
544
- + "default": "electrical.batteries.{batteryID}.capacity.dischargeSinceFull"
545
- + },
546
- + "charge":{
547
- + "unit":"KWh",
548
- + "default": "electrical.batteries.{batteryID}.capacity.totalCharge"
549
- + },
550
- "stateOfCharge":{
551
- "unit":"ratio",
552
- "default": "electrical.batteries.{batteryID}.capacity.stateOfCharge"
553
- diff --git a/public/images/Bank Manager All-in-onewc.webp b/public/images/Bank Manager All-in-onewc.webp
554
- new file mode 100644
555
- index 0000000..540ac19
556
- Binary files /dev/null and b/public/images/Bank Manager All-in-onewc.webp differ
557
- diff --git a/public/images/SmartShunt_500_nw.png b/public/images/SmartShunt_500_nw.png
558
- new file mode 100644
559
- index 0000000..5f60330
560
- Binary files /dev/null and b/public/images/SmartShunt_500_nw.png differ
561
- diff --git a/public/images/smartcraft.webp b/public/images/smartcraft.webp
562
- new file mode 100644
563
- index 0000000..f740827
564
- Binary files /dev/null and b/public/images/smartcraft.webp differ
565
- diff --git a/sensor_classes/BTHome/AbstractBTHomeSensor.js b/sensor_classes/BTHome/AbstractBTHomeSensor.js
566
- index 729fb18..d7db48b 100644
567
- --- a/sensor_classes/BTHome/AbstractBTHomeSensor.js
568
- +++ b/sensor_classes/BTHome/AbstractBTHomeSensor.js
569
- @@ -235,7 +235,7 @@ class AbstractBTHomeSensor extends BTSensor {
570
- * Extracts motion detection from the given BTHome data.
571
- *
572
- * @param btHomeData {BTHomeServiceData.BthomeServiceData} The BTHome data provided by the device.
573
- - * @returns {Boolean|null} The device's button press state.
574
- + * @returns {Boolean|null} Wether the device has detected motion.
575
- */
576
- parseMotion(btHomeData) {
577
- const motion = this.getSensorDataByObjectId(
578
- @@ -304,6 +304,31 @@ class AbstractBTHomeSensor extends BTSensor {
579
- return null;
580
- }
581
-
582
- + /**
583
- + * Parses the opening (window/door) state from BTHome service data.
584
- + * @param {BTHomeServiceData.BthomeServiceData} btHomeData - The parsed BTHome data object.
585
- + * @returns {string|null} "open", "closed", or null if not present.
586
- + */
587
- + parseWindowState(btHomeData) {
588
- + const state = this.getSensorDataByObjectId(btHomeData, BTHomeServiceData.BthomeObjectId.BINARY_WINDOW)?.window;
589
- + if (state.intValue === 1) return "open";
590
- + if (state.intValue === 0) return "closed";
591
- + return null;
592
- + }
593
- +
594
- + /**
595
- + * Parses the rotation from BTHome service data.
596
- + * @param {BTHomeServiceData.BthomeServiceData} btHomeData - The parsed BTHome data object.
597
- + * @returns {number|null} rotation in degrees from the closed position, or null if not present.
598
- + */
599
- + parseRotation(btHomeData) {
600
- + // Use the correct object ID for window event (0x2D, which is 45 in decimal, which is already specified in BthomeObjectId).
601
- + const sensorRotation = this.getSensorDataByObjectId(btHomeData, BTHomeServiceData.BthomeObjectId.SENSOR_ROTATION_0_1)?.rotation;
602
- + if (sensorRotation) {
603
- + return Number.parseFloat(sensorRotation.toFixed(2));
604
- + }
605
- + return null;
606
- + }
607
-
608
- propertiesChanged(props) {
609
- super.propertiesChanged(props);
610
- diff --git a/sensor_classes/BankManager.js b/sensor_classes/BankManager.js
611
- new file mode 100644
612
- index 0000000..d0303fc
613
- --- /dev/null
614
- +++ b/sensor_classes/BankManager.js
615
- @@ -0,0 +1,96 @@
616
- +const BTSensor = require("../BTSensor");
617
- +
618
- +class BankManager extends BTSensor{
619
- +
620
- + static Domain = BTSensor.SensorDomains.electrical
621
- + static async identify(device){
622
- +
623
- + const name = await this.getDeviceProp(device,"Name")
624
- + const regex = /^BankManager*[0-9]{2}/
625
- +
626
- + if (name && name.match(regex))
627
- + return this
628
- + else
629
- + return null
630
- +
631
- + }
632
- + initSchema(){
633
- + super.initSchema()
634
- +
635
- + this.addDefaultParam("id")
636
- + .default="bankManager"
637
- +
638
- + this.addMetadatum('liVoltage','V','Lithium Voltage')
639
- + .default="electrical.batteries.{id}.voltage.lithium"
640
- + this.addMetadatum('pbVoltage','V','Lead Voltage')
641
- + .default="electrical.batteries.{id}.voltage.lead"
642
- + this.addMetadatum('current','A','total current')
643
- + .default="electrical.batteries.{id}.current"
644
- + this.addMetadatum('soc','ratio','state of charge')
645
- + .default="electrical.batteries.{id}.soc"
646
- + this.addMetadatum('connectionStatus','','Connection Status')
647
- + .default="electrical.batteries.{id}.connectionStatus"
648
- +
649
- + }
650
- +
651
- + getManufacturer(){
652
- + return "Bank Manager"
653
- + }
654
- + hasGATT(){
655
- + return false
656
- + }
657
- + usingGATT(){
658
- + return true
659
- + }
660
- + emitDataFrom(buffer){
661
- + let data=buffer.toString().split(",")
662
- + this.emit("liVoltage", parseFloat(data[1]))
663
- + this.emit("pbVoltage", parseFloat(data[2]))
664
- + this.emit("current", parseFloat(data[3]))
665
- + this.emit("soc", parseFloat(data[4])/100)
666
- + this.emit("connectionStatus", parseInt(data[5]))
667
- +
668
- + }
669
- + emitGATT(){
670
- +
671
- + }
672
- +
673
- +
674
- + initGATTConnection(){
675
- + return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
676
- + if (!this.gattServer) {
677
- + this.gattServer = await this.device.gatt()
678
- + this.service = await this.gattServer.getPrimaryService("0000ffe0-0000-1000-8000-00805f9b34fb")
679
- + this.characteristic = await this.service.getCharacteristic("0000ffe1-0000-1000-8000-00805f9b34fb")
680
- + }
681
- + resolve(this)
682
- + }) .catch((e)=>{ reject(e.message) }) })
683
- + }
684
- +
685
- + initGATTNotifications() {
686
- + Promise.resolve(this.characteristic.startNotifications().then(()=>{
687
- + let data = null
688
- + this.characteristic.on('valuechanged', buffer => {
689
- + if (buffer.length>0 && buffer[0]==0x23) {
690
- + data = Buffer.from(buffer)
691
- + } else if (data && buffer.indexOf(0x0d0a)!==-1) {
692
- + data=Buffer.concat([data,buffer.subarray(0,buffer.indexOf(0x0d0a)-1)], data.length+buffer.indexOf(0x0d0a)-1)
693
- + this.emitDataFrom(data)
694
- + data=null
695
- + }
696
- + })
697
- + }))
698
- +
699
- + }
700
- +
701
- + async stopListening(){
702
- + super.stopListening()
703
- + if (this.characteristic && await this.characteristic.isNotifying()) {
704
- + await this.characteristic.stopNotifications()
705
- + }
706
- + if (await this.device.isConnected()){
707
- + await this.device.disconnect()
708
- + }
709
- + }
710
- +}
711
- +module.exports=BankManager
712
-
713
- diff --git a/sensor_classes/GobiusCTankMeter.js b/sensor_classes/GobiusCTankMeter.js
714
- index 6480535..318e621 100644
715
- --- a/sensor_classes/GobiusCTankMeter.js
716
- +++ b/sensor_classes/GobiusCTankMeter.js
717
- @@ -338,19 +338,7 @@ class GobiusCTankMeter extends BTSensor{
718
- this.service = await this.gattServer.getPrimaryService("0000ffe0-0000-1000-8000-00805f9b34fb")
719
- this.characteristic = await this.service.getCharacteristic("0000ffe9-0000-1000-8000-00805f9b34fb")
720
- }
721
- - this.device.on("disconnect", ()=>{
722
- - if (this.isActive()) {
723
- - this.debug(`Device disconnected. Attempting to reconnect to ${this.getName()}`)
724
- - try {
725
- - this.deviceConnect().then(()=>{
726
- - this.debug(`Device reconnected -- ${this.getName()}`)
727
- - })
728
- - }
729
- - catch (e) {
730
- - this.debug(`Error while reconnecting to ${this.getName()}`)
731
- - }
732
- - }
733
- - })
734
- +
735
-
736
- resolve(this)
737
- }).catch((e)=>{ reject(e.message) }) })
738
- diff --git a/sensor_classes/Govee/GoveeSensor.js b/sensor_classes/Govee/GoveeSensor.js
739
- new file mode 100644
740
- index 0000000..386ab98
741
- --- /dev/null
742
- +++ b/sensor_classes/Govee/GoveeSensor.js
743
- @@ -0,0 +1,48 @@
744
- +const BTSensor = require("../../BTSensor");
745
- +
746
- +class GoveeSensor extends BTSensor {
747
- + static Domain = this.SensorDomains.environmental
748
- + static ManufacturerUUID = '0000ec88-0000-1000-8000-00805f9b34fb'
749
- + static async identify(device){
750
- +
751
- + const regex = /^Govee_H5075_[a-f,A-F,0-9]{4}$/
752
- + const name = await this.getDeviceProp(device,"Name")
753
- + const uuids = await this.getDeviceProp(device,'UUIDs')
754
- +
755
- + if (name && name.match(this.getIDRegex()) &&
756
- + uuids && uuids.length > 0 &&
757
- + uuids[0] == this.ManufacturerUUID)
758
- + return this
759
- + else
760
- + return null
761
- +
762
- + }
763
- +
764
- + static DATA_ID=0xec88
765
- + getManufacturer(){
766
- + return "Govee"
767
- + }
768
- + getPackedTempAndHumidity(buffer, beg )
769
- + {
770
- + const negative=buffer[beg]&0x80
771
- + return {
772
- + packedValue: ((buffer.readIntBE(beg,3))&0xFFFFFF) ^ (negative?0x800000:0x000000),
773
- + tempIsNegative: negative
774
- + }
775
- + }
776
- + emitTemperatureAndHumidity(packedValue, tempIsNegative ){
777
- + this.emit("temp", 273.15+((((Math.trunc(packedValue/1000))/10))*(tempIsNegative?-1:1)))
778
- + this.emit("humidity", (packedValue % 1000) / 1000)
779
- +
780
- + }
781
- + async propertiesChanged(props){
782
- + super.propertiesChanged(props)
783
- + if (!props.hasOwnProperty("ManufacturerData")) return
784
- +
785
- + const buffer = this.getManufacturerData(this.constructor.DATA_ID)
786
- + if (buffer) {
787
- + this.emitValuesFrom(buffer)
788
- + }
789
- + }
790
- +}
791
- +module.exports=GoveeSensor
792
- diff --git a/sensor_classes/GoveeH5074.js b/sensor_classes/GoveeH5074.js
793
- new file mode 100644
794
- index 0000000..307c235
795
- --- /dev/null
796
- +++ b/sensor_classes/GoveeH5074.js
797
- @@ -0,0 +1,23 @@
798
- +const GoveeSensor = require("./Govee/GoveeSensor");
799
- +
800
- +class GoveeH5074 extends GoveeSensor {
801
- + static getIDRegex() {
802
- + return /^Govee_H5074_[a-f,A-F,0-9]{4}$/
803
- + }
804
- + initSchema(){
805
- + super.initSchema()
806
- + this.addDefaultParam("zone")
807
- +
808
- + this.addDefaultPath("temp","environment.temperature")
809
- + .read= (buffer)=>{return 273.15+(buffer.readUInt16LE(1)/100) }
810
- +
811
- + this.addDefaultPath("humidity", "environment.humidity")
812
- + .read = (buffer)=>{return buffer.readUInt16LE(3)/10000}
813
- +
814
- + this.addDefaultPath("battery","sensors.batteryStrength")
815
- + .read = (buffer)=>{return buffer.readUInt8(5)/100}
816
- + }
817
- +
818
- +
819
- +}
820
- +module.exports=GoveeH5074
821
- diff --git a/sensor_classes/GoveeH5075.js b/sensor_classes/GoveeH5075.js
822
- new file mode 100644
823
- index 0000000..6098893
824
- --- /dev/null
825
- +++ b/sensor_classes/GoveeH5075.js
826
- @@ -0,0 +1,37 @@
827
- +const GoveeSensor = require("./Govee/GoveeSensor");
828
- +
829
- +class GoveeH5075 extends GoveeSensor {
830
- + static getIDRegex(){
831
- + return /^Govee_H5075_[a-f,A-F,0-9]{4}$/
832
- + }
833
- + static test(){
834
- + const sensor = new GoveeH5075()
835
- + sensor.getName=()=>{return "Govee H5075 fake"}
836
- + sensor.initSchema()
837
- + sensor.on("temp", (t)=>{console.log(`temp => ${t}`)})
838
- + sensor.on("humidity", (t)=>{console.log(`humidity => ${t}`)})
839
- + sensor.on("battery", (t)=>{console.log(`battery => ${t}`)})
840
- + sensor.emitValuesFrom(Buffer.from([0x00, 0x81, 0xc2 ,0x89 ,0x64 ,0x00]))
841
- + sensor.emitValuesFrom(Buffer.from([0x00,0x03,0xbb,0x94,0x64,0x00]))
842
- +
843
- + }
844
- + initSchema(){
845
- + super.initSchema()
846
- + this.addDefaultParam("zone")
847
- +
848
- + this.addDefaultPath("temp","environment.temperature")
849
- + this.addDefaultPath("humidity", "environment.humidity")
850
- + this.addDefaultPath("battery","sensors.batteryStrength")
851
- + }
852
- +
853
- + emitValuesFrom(buffer){
854
- + if (buffer.length<6) {
855
- + this.debug(`Invalid buffer received. Cannot parse buffer ${buffer} for ${this.getMacAndName()}`)
856
- + return
857
- + }
858
- + const val = this.getPackedTempAndHumidity(buffer,1)
859
- + this.emitTemperatureAndHumidity(val.packedValue, val.tempIsNegative)
860
- + this.emit("battery", buffer[4]/100)
861
- + }
862
- +}
863
- +module.exports=GoveeH5075
864
- diff --git a/sensor_classes/GoveeH50xx.js b/sensor_classes/GoveeH50xx.js
865
- deleted file mode 100644
866
- index 1b49299..0000000
867
- --- a/sensor_classes/GoveeH50xx.js
868
- +++ /dev/null
869
- @@ -1,47 +0,0 @@
870
- -const BTSensor = require("../BTSensor");
871
- -
872
- -class GoveeH50xx extends BTSensor {
873
- - static Domain = this.SensorDomains.environmental
874
- - static async identify(device){
875
- - const regex = /^Govee_H50[0-9]{2}_[a-f,A-F,0-9]{4}$/
876
- - //this.getManufacturer()
877
- - const name = await this.getDeviceProp(device,"Name")
878
- - const uuids = await this.getDeviceProp(device,'UUIDs')
879
- -
880
- - if (name && name.match(regex) &&
881
- - uuids && uuids.length > 0 &&
882
- - uuids[0] == '0000ec88-0000-1000-8000-00805f9b34fb')
883
- - return this
884
- - else
885
- - return null
886
- - t
887
- - }
888
- -
889
- - initSchema(){
890
- - super.initSchema()
891
- - this.addDefaultParam("zone")
892
- -
893
- - this.addDefaultPath("temp","environment.temperature")
894
- - .read= (buffer)=>{return 273.15+(buffer.readUInt16LE(1)/100) }
895
- -
896
- - this.addDefaultPath("humidity", "environment.humidity")
897
- - .read = (buffer)=>{return buffer.readUInt16LE(3)/10000}
898
- -
899
- - this.addDefaultPath("battery","sensors.batteryStrength")
900
- - .read = (buffer)=>{return buffer.readUInt8(5)/100}
901
- - }
902
- -
903
- - getManufacturer(){
904
- - return "Govee"
905
- - }
906
- - async propertiesChanged(props){
907
- - super.propertiesChanged(props)
908
- - if (!props.hasOwnProperty("ManufacturerData")) return
909
- -
910
- - const buffer = this.getManufacturerData(0xec88)
911
- - if (buffer) {
912
- - this.emitValuesFrom(buffer)
913
- - }
914
- - }
915
- -}
916
- -module.exports=GoveeH50xx
917
- diff --git a/sensor_classes/GoveeH510x.js b/sensor_classes/GoveeH510x.js
918
- index aaf3ed6..1627c63 100644
919
- --- a/sensor_classes/GoveeH510x.js
920
- +++ b/sensor_classes/GoveeH510x.js
921
- @@ -1,39 +1,22 @@
922
- -const BTSensor = require("../BTSensor");
923
- -function decodeTempHumid(tempHumidBytes) {
924
- - // Convert the bytes to a 24-bit integer
925
- - const baseNum = (tempHumidBytes[0] << 16) + (tempHumidBytes[1] << 8) + tempHumidBytes[2];
926
- -
927
- - // Check if the temperature is negative
928
- - const isNegative = (baseNum & 0x800000) !== 0;
929
- -
930
- - // Extract the temperature and humidity values
931
- - const tempAsInt = baseNum & 0x7FFFFF;
932
- - const tempAsFloat = (tempAsInt / 1000) / 10.0;
933
- - const humid = (tempAsInt % 1000) / 10.0;
934
- -
935
- - // Apply the negative sign if necessary
936
- - if (isNegative) {
937
- - return {t:-tempAsFloat, h: humid};
938
- - } else {
939
- - return {t: tempAsFloat, h: humid};
940
- - }
941
- - }
942
- -class GoveeH510x extends BTSensor{
943
- - static Domain = this.SensorDomains.environmental
944
- - static async identify(device){
945
- - const regex = /^GVH510[0-9]_[a-f,A-F,0-9]{4}$/
946
- - const name = await this.getDeviceProp(device,"Name")
947
- - const uuids = await this.getDeviceProp(device,'UUIDs')
948
- +const GoveeSensor = require("./Govee/GoveeSensor");
949
-
950
- - if (name && name.match(regex) &&
951
- - uuids && uuids.length > 0 &&
952
- - uuids[0] == '0000ec88-0000-1000-8000-00805f9b34fb')
953
- - return this
954
- - else
955
- - return null
956
- -
957
- +class GoveeH510x extends GoveeSensor{
958
- +
959
- + static getIDRegex(){
960
- + return /^GVH510[0-9]_[a-f,A-F,0-9]{4}$/
961
- }
962
-
963
- + static test(){
964
- + const sensor = new GoveeH510x()
965
- + sensor.getName=()=>{return "Govee H510x fake"}
966
- + sensor.initSchema()
967
- + sensor.on("temp", (t)=>{console.log(`temp => ${t}`)})
968
- + sensor.on("humidity", (t)=>{console.log(`humidity => ${t}`)})
969
- + sensor.on("battery", (t)=>{console.log(`battery => ${t}`)})
970
- + sensor.emitValuesFrom(Buffer.from([0x01,0x01,0x03,0x6d,0xcc,0x5c]))
971
- +
972
- + }
973
- + static DATA_ID = 0x0001
974
- initSchema(){
975
- super.initSchema()
976
- this.addDefaultParam("zone")
977
- @@ -44,23 +27,16 @@ class GoveeH510x extends BTSensor{
978
- }
979
-
980
- emitValuesFrom(buffer){
981
- - const th = decodeTempHumid(buffer.subarray(2,5))
982
- - this.emit("temp", parseFloat((273.15+th.t).toFixed(2))) ;
983
- - this.emit("humidity", th.h/100 )
984
- - this.emit('battery', buffer[5]/100)
985
- - }
986
- + if (buffer.length<6) {
987
- + this.debug(`Invalid buffer received. Cannot parse buffer ${buffer} for ${this.getMacAndName()}`)
988
- + return
989
- + }
990
- + const val = this.getPackedTempAndHumidity(buffer,2)
991
- + this.emitTemperatureAndHumidity(val.packedValue, val.tempIsNegative)
992
- + this.emit("battery", buffer[5]/100)
993
-
994
- - getManufacturer(){
995
- - return "Govee"
996
- }
997
- - async propertiesChanged(props){
998
- - super.propertiesChanged(props)
999
- - if (!props.hasOwnProperty("ManufacturerData")) return
1000
-
1001
- - const buffer = this.getManufacturerData(0x0001)
1002
- - if (buffer) {
1003
- - this.emitValuesFrom(buffer)
1004
- - }
1005
- - }
1006
- +
1007
- }
1008
- module.exports=GoveeH510x
1009
- diff --git a/sensor_classes/JBDBMS.js b/sensor_classes/JBDBMS.js
1010
- index d25061a..0374a0c 100644
1011
- --- a/sensor_classes/JBDBMS.js
1012
- +++ b/sensor_classes/JBDBMS.js
1013
- @@ -52,10 +52,10 @@ class JBDBMS extends BTSensor {
1014
- (buffer)=>{return buffer.readInt16BE(6) / 100}
1015
-
1016
- this.addDefaultPath('remainingCapacity','electrical.batteries.capacity.remaining')
1017
- - .read=(buffer)=>{return buffer.readUInt16BE(8) / 100}
1018
- + .read=(buffer)=>{return (buffer.readUInt16BE(8) / 100)*3600}
1019
-
1020
- this.addDefaultPath('capacity','electrical.batteries.capacity.actual')
1021
- - .read=(buffer)=>{return buffer.readUInt16BE(10) / 100}
1022
- + .read=(buffer)=>{return (buffer.readUInt16BE(10) / 100)*3600}
1023
-
1024
- this.addDefaultPath('cycles','electrical.batteries.cycles' )
1025
- .read=(buffer)=>{return buffer.readUInt16BE(12)}
1026
- @@ -139,7 +139,7 @@ class JBDBMS extends BTSensor {
1027
- let datasize = -1
1028
- const timer = setTimeout(() => {
1029
- clearTimeout(timer)
1030
- - reject(new Error(`Response timed out from JBDBMS device ${this.getName()}. `));
1031
- + reject(new Error(`Response timed out (+30s) from JBDBMS device ${this.getName()}. `));
1032
- }, 30000);
1033
-
1034
- const valChanged = async (buffer) => {
1035
- @@ -147,13 +147,12 @@ class JBDBMS extends BTSensor {
1036
- if (buffer[0]!==0xDD || buffer.length < 5 || buffer[1] !== command)
1037
- reject(`Invalid buffer from ${this.getName()}, not processing.`)
1038
- else
1039
- - datasize=buffer[2]
1040
- + datasize=buffer[3]
1041
- }
1042
- buffer.copy(result,offset)
1043
- - if (buffer[buffer.length-1]==0x77 && offset+buffer.length-6==datasize){
1044
- + if (buffer[buffer.length-1]==0x77 && offset+buffer.length-7==datasize){
1045
-
1046
- result = Uint8Array.prototype.slice.call(result, 0, offset+buffer.length)
1047
- - this.debug(result)
1048
- this.rxChar.removeAllListeners()
1049
- clearTimeout(timer)
1050
- if (!checkSum(result))
1051
- @@ -196,6 +195,8 @@ async getAndEmitCellVoltages(){
1052
- }
1053
-
1054
- initGATTInterval(){
1055
- +
1056
- + this.emitGATT()
1057
- this.initGATTNotifications()
1058
- }
1059
-
1060
- diff --git a/sensor_classes/Junctek.js b/sensor_classes/Junctek.js
1061
- new file mode 100644
1062
- index 0000000..473e8f1
1063
- --- /dev/null
1064
- +++ b/sensor_classes/Junctek.js
1065
- @@ -0,0 +1,177 @@
1066
- +
1067
- +
1068
- +const BTSensor = require("../BTSensor");
1069
- +
1070
- +function bytesToBase10String(bytes){
1071
- + let s = ""
1072
- + for (let byte of bytes){
1073
- + s+=byte.toString(16).padStart(2,'0')
1074
- + }
1075
- + return s
1076
- + }
1077
- +
1078
- +class JunctekBMS extends BTSensor{
1079
- + static Domain = BTSensor.SensorDomains.electrical
1080
- +
1081
- + constructor(device, config, gattConfig) {
1082
- + super(device, config, gattConfig)
1083
- + }
1084
- +
1085
- + static async identify(device){
1086
- +
1087
- + return null
1088
- + }
1089
- +
1090
- + chargeDirection = 1
1091
- +
1092
- + hasGATT(){
1093
- + return true
1094
- + }
1095
- +
1096
- +
1097
- +
1098
- + async initSchema(){
1099
- + super.initSchema()
1100
- + this.addDefaultParam("batteryID")
1101
- +
1102
- + this.addDefaultPath("voltage","electrical.batteries.voltage")
1103
- +
1104
- + this.addDefaultPath("current","electrical.batteries.current")
1105
- +
1106
- + this.addDefaultPath("power","electrical.batteries.power")
1107
- +
1108
- + this.addDefaultPath("cycles",'electrical.batteries.cycles')
1109
- +
1110
- + this.addDefaultPath("soc",'electrical.batteries.capacity.stateOfCharge')
1111
- + this.addDefaultPath("remaining",'electrical.batteries.capacity.remaining')
1112
- + this.addDefaultPath("timeRemaining",'electrical.batteries.capacity.timeRemaining')
1113
- + this.addDefaultPath("discharge",'electrical.batteries.capacity.dischargeSinceFull')
1114
- + this.addDefaultPath("charge",'electrical.batteries.capacity.charge')
1115
- + this.addDefaultPath("temperature",'electrical.batteries.temperature')
1116
- + this.addDefaultPath("actualCapacity",'electrical.batteries.capacity.actual')
1117
- + this.addMetadatum('timeToCharged','s', 'time in seconds to battery is fully charged')
1118
- + .default='electrical.batteries.{batteryID}.timeToCharged'
1119
- + this.addMetadatum('impedance','mOhm', 'measured resistance')
1120
- + .default='electrical.batteries.{batteryID}.impedance'
1121
- + }
1122
- +
1123
- + emitFrom(buffer){
1124
- + let value=[], emitObject={}
1125
- + this.debug(buffer)
1126
- + for (let byte of buffer){
1127
- + if (byte==0xBB) {
1128
- + value=[]
1129
- + continue
1130
- + }
1131
- + if (byte==0xEE){
1132
- + continue
1133
- + }
1134
- +
1135
- + if (isNaN(parseInt(byte.toString(16)))){ //not a base-10 number. seriously. that's how Junctek does this.
1136
- + const v = parseInt(bytesToBase10String(value))
1137
- + //if (byte!==0xd5)
1138
- + // this.debug(`0x${(byte).toString(16)}: ${(value).map((v)=>'0x'+v.toString(16))} (${v})`)
1139
- + value=[]
1140
- + switch (byte){
1141
- + case 0xC0:
1142
- + emitObject["voltage"]=v/100
1143
- + break
1144
- +
1145
- + case 0xC1:
1146
- + emitObject["current"]=()=>{return (v/100)*this.chargeDirection}
1147
- + break
1148
- +
1149
- + case 0xD1:
1150
- + this.debug(v)
1151
- + if (v==0)
1152
- + this.chargeDirection=-1
1153
- + else
1154
- + this.chargeDirection= 1
1155
- + this.debug(this.chargeDirection)
1156
- + break
1157
- +
1158
- + case 0xD2:
1159
- +
1160
- + emitObject["remaining"]=v*3.6
1161
- + break
1162
- +
1163
- + case 0xD3:
1164
- + emitObject["discharge"]= v/100000
1165
- + break
1166
- + case 0xD4:
1167
- + emitObject["charge"]=v/100000
1168
- + break
1169
- + case 0xD6:
1170
- + if (chargeDirection==-1){
1171
- + emitObject["timeToCharged"] = NaN
1172
- + emitObject["timeRemaining"] = v*60
1173
- + }
1174
- + else {
1175
- + emitObject["timeRemaining"] = NaN
1176
- + emitObject["timeToCharged"] = v*60
1177
- + }
1178
- + break
1179
- + case 0xD7:
1180
- + emitObject["impedance"]=v/100
1181
- + break
1182
- + case 0xD8:
1183
- + emitObject["power"]=()=>{
1184
- + this.debug(this.chargeDirection)
1185
- + return (v/100)*this.chargeDirection
1186
- + }
1187
- + break
1188
- + case 0xD9:
1189
- + emitObject["temperature"]=v + 173.15 //assume C not F -- raw value is c - 100
1190
- + break
1191
- + case 0xB1:
1192
- + emitObject["capacityActual"]=v /10
1193
- + break
1194
- + }
1195
- + }
1196
- + else{
1197
- + value.push(byte)
1198
- + }
1199
- + }
1200
- + for (const [key, value] of Object.entries(emitObject)) {
1201
- + this.emit(key,value instanceof Function?value():value)
1202
- + }
1203
- + emitObject = {}
1204
- + }
1205
- + emitGATT(){
1206
- + /*this.battCharacteristic.readValue()
1207
- + .then((buffer)=>{
1208
- + this.emitFrom(buffer)
1209
- + })*/
1210
- + }
1211
- + initGATTConnection(){
1212
- + return new Promise((resolve,reject )=>{ this.).then(async ()=>{
1213
- + if (!this.gattServer) {
1214
- + this.gattServer = await this.device.gatt()
1215
- + this.battService = await this.gattServer.getPrimaryService("0000fff0-0000-1000-8000-00805f9b34fb")
1216
- + this.battCharacteristic = await this.battService.getCharacteristic("0000fff1-0000-1000-8000-00805f9b34fb")
1217
- +
1218
- + }
1219
- + resolve(this)
1220
- + }) .catch((e)=>{ reject(e.message) }) })
1221
- + }
1222
- +
1223
- + initGATTNotifications() {
1224
- + Promise.resolve(this.battCharacteristic.startNotifications().then(()=>{
1225
- + this.battCharacteristic.on('valuechanged', buffer => {
1226
- + this.emitFrom(buffer)
1227
- + })
1228
- + }))
1229
- + }
1230
- +
1231
- + async stopListening(){
1232
- + super.stopListening()
1233
- + if (this.battCharacteristic && await this.battCharacteristic.isNotifying()) {
1234
- + await this.battCharacteristic.stopNotifications()
1235
- + this.battCharacteristic=null
1236
- + }
1237
- + if (await this.device.isConnected()){
1238
- + await this.device.disconnect()
1239
- + }
1240
- + }
1241
- +}
1242
- +module.exports=JunctekBMS
1243
- diff --git a/sensor_classes/KilovaultHLXPlus.js b/sensor_classes/KilovaultHLXPlus.js
1244
- index 882feb9..e3a2723 100644
1245
- --- a/sensor_classes/KilovaultHLXPlus.js
1246
- +++ b/sensor_classes/KilovaultHLXPlus.js
1247
- @@ -78,7 +78,7 @@ class KilovaultHLXPlus extends BTSensor{
1248
- this.addDefaultPath("cycles",'electrical.batteries.cycles')
1249
- .read=(buffer)=>{return buffer.readInt16LE(12)}
1250
-
1251
- - this.addDefaultPath("soc",'electrical.batteries.capacity,stateOfCharge')
1252
- + this.addDefaultPath("soc",'electrical.batteries.capacity.stateOfCharge')
1253
- .read=(buffer)=>{return buffer.readInt16LE(14)}
1254
-
1255
- this.addDefaultPath("temperature",'electrical.batteries.temperature')
1256
- diff --git a/sensor_classes/MercurySmartcraft.js b/sensor_classes/MercurySmartcraft.js
1257
- new file mode 100644
1258
- index 0000000..577a483
1259
- --- /dev/null
1260
- +++ b/sensor_classes/MercurySmartcraft.js
1261
- @@ -0,0 +1,126 @@
1262
- +/*
1263
- +Service UUID Characteristic UUID Type Param Convertion Unit SignalK Path Comment
1264
- +00000000-0000-1000-8000-ec55f9f5b963 (Unknown) 00000001-0000-1000-8000-ec55f9f5b963 write / indicate SDP - - - Enable or disable data stream. To enable write 0x0D 0x01; To disable write 0x0D 0x00.
1265
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000101-0000-1000-8000-ec55f9f5b963 read ? - - ? Returns: Value: 1.1.0 (raw: 312e312e30)
1266
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000102-0000-1000-8000-ec55f9f5b963 write / notify ENGINE_RPM_UUID value / 60 Hz propulsion.p0.revolutions
1267
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000103-0000-1000-8000-ec55f9f5b963 write / notify COOLANT_TEMPERATURE_UUID value + 273.15 Kelvin propulsion.p0.temperature
1268
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000104-0000-1000-8000-ec55f9f5b963 write / notify BATTERY_VOLTAGE_UUID value / 1000 Volts propulsion.p0.alternatorVoltage
1269
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000105-0000-1000-8000-ec55f9f5b963 write / notify UNK_105_UUID ? ? ? Need longer data to figure out what is it. Value around 17384.
1270
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000106-0000-1000-8000-ec55f9f5b963 write / notify ENGINE_RUNTIME_UUID value * 60 Seconds propulsion.p0.runTime
1271
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000107-0000-1000-8000-ec55f9f5b963 write / notify CURRENT_FUEL_FLOW_UUID value / 100000 m3 / hour propulsion.p0.fuel.rate
1272
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000108-0000-1000-8000-ec55f9f5b963 write / notify FUEL_TANK_PCT_UUID value / 100 % propulsion.p0.fuel.tank Maybe there is a more appropriate name for the Signalk path
1273
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 00000109-0000-1000-8000-ec55f9f5b963 write / notify UNK_109_UUID ? ? ? Need longer data to figure out what is it. Raw data varied from 102701 to 102700.
1274
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 0000010a-0000-1000-8000-ec55f9f5b963 write / notify OIL_PRESSURE_UUID value / 100 kPascal propulsion.p0.oilPressure
1275
- +00000100-0000-1000-8000-ec55f9f5b963 (L2CAP) 0000010b-0000-1000-8000-ec55f9f5b963 write / notify UNK_10B_UUID ? ? ? Always zero. To investigate with more data.
1276
- +*/
1277
- +const BTSensor = require("../BTSensor");
1278
- +class MercurySmartcraft extends BTSensor{
1279
- + static Domain = BTSensor.SensorDomains.propulsion
1280
- +
1281
- + static async identify(device){
1282
- +
1283
- + const name = await this.getDeviceProp(device,"Name")
1284
- + const address = await this.getDeviceProp(device,"Address")
1285
- + if (name && address && name == `VVM_${address.replace(":","")}`)
1286
- + return this
1287
- + else
1288
- + return null
1289
- + }
1290
- +
1291
- +
1292
- +
1293
- + hasGATT(){
1294
- + return false
1295
- + }
1296
- + usingGATT(){
1297
- + return true
1298
- + }
1299
- + emitGATT(){
1300
- + }
1301
- + initSchema(){
1302
- + const bo = 2
1303
- +
1304
- + super.initSchema()
1305
- + this.addParameter(
1306
- + "id",
1307
- + {
1308
- + "title": "Engine ID",
1309
- + "examples": ["port","starboard","p0","p1"],
1310
- + "isRequired": true
1311
- + }
1312
- + )
1313
- +
1314
- + this.addMetadatum("rpm","Hz","engine revolutions per sec",
1315
- + (buffer)=>{return buffer.readUInt16LE(bo)/60}
1316
- + ).default='propulsion.{id}.revolutions'
1317
- +
1318
- + this.addMetadatum("coolant","K","temperature of engine coolant in K",
1319
- + (buffer)=>{return buffer.readUInt16LE(bo)+273.15}
1320
- + ).default='propulsion.{id}.coolantTemperature'
1321
- +
1322
- + this.addMetadatum("alternatorVoltage","V","voltage of alternator",
1323
- + (buffer)=>{return buffer.readUInt16LE(bo)/1000}
1324
- + ).default='propulsion.{id}.alternatorVoltage'
1325
- +
1326
- + this.addMetadatum("runtime","s","Total running time for engine (Engine Hours in seconds)",
1327
- + (buffer)=>{return buffer.readUInt16LE(bo)*60}
1328
- + ).default='propulsion.{id}.runTime'
1329
- +
1330
- + this.addMetadatum("rate","m3/s","Fuel rate of consumption (cubic meters per second)",
1331
- + (buffer)=>{return buffer.readUInt16LE(bo)/100000/3600}
1332
- + ).default='propulsion.{id}.fuel.rate'
1333
- +
1334
- + this.addMetadatum("pressure","Pa","Fuel pressure",
1335
- + (buffer)=>{return buffer.readUInt16LE(bo)*10}
1336
- + ).default='propulsion.{id}.pressure'
1337
- +
1338
- + this.addMetadatum("level","ratio","Level of fluid in tank 0-100%",
1339
- + (buffer)=>{return buffer.readUInt16LE(bo)/10000}
1340
- + ).default='tanks.petrol.currentLevel'
1341
- + }
1342
- +
1343
- + initGATTConnection(){
1344
- + return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
1345
- + if (!this.gattServer) {
1346
- + this.gattServer = await this.device.gatt()
1347
- + this.sdpService = await this.gattServer.getPrimaryService("00000000-0000-1000-8000-ec55f9f5b963")
1348
- + this.sdpCharacteristic = await this.sdpService.getCharacteristic("00000001-0000-1000-8000-ec55f9f5b963")
1349
- + this.dataService = await this.gattServer.getPrimaryService("00000100-0000-1000-8000-ec55f9f5b963")
1350
- + this.dataCharacteristics = {
1351
- + rpm: await this.dataService.getCharacteristic("00000102-0000-1000-8000-ec55f9f5b963"),
1352
- + coolant: await this.dataService.getCharacteristic("00000103-0000-1000-8000-ec55f9f5b963"),
1353
- + alternatorVoltage: await this.dataService.getCharacteristic("00000104-0000-1000-8000-ec55f9f5b963"),
1354
- + runtime: await this.dataService.getCharacteristic("00000106-0000-1000-8000-ec55f9f5b963"),
1355
- + rate: await this.dataService.getCharacteristic("00000107-0000-1000-8000-ec55f9f5b963"),
1356
- + level: await this.dataService.getCharacteristic("00000108-0000-1000-8000-ec55f9f5b963"),
1357
- + pressure: await this.dataService.getCharacteristic("0000010a-0000-1000-8000-ec55f9f5b963")
1358
- + }
1359
- + }
1360
- + resolve(this)
1361
- + }) .catch((e)=>{ reject(e.message) }) })
1362
- + }
1363
- + async initGATTNotifications() {
1364
- + await this.sdpCharacteristic.writeValue(Buffer.from([0x0D,0x01]))
1365
- + for (const c in this.dataCharacteristics){
1366
- + Promise.resolve(this.dataCharacteristics[c].startNotifications().then(()=>{
1367
- + this.dataCharacteristics[c].on('valuechanged', buffer => {
1368
- + this.emitData(c,buffer)
1369
- + })
1370
- + }))
1371
- + }
1372
- + }
1373
- +
1374
- + async stopListening(){
1375
- + super.stopListening()
1376
- + for (const c in this.dataCharacteristics){
1377
- + if (this.dataCharacteristics[c] && await this.dataCharacteristics[c].isNotifying()) {
1378
- + await this.dataCharacteristics[c].stopNotifications()
1379
- + }
1380
- + }
1381
- +
1382
- + if (await this.device.isConnected()){
1383
- + await this.device.disconnect()
1384
- + }
1385
- + }
1386
- +}
1387
- +module.exports=MercurySmartcraft
1388
-
1389
- diff --git a/sensor_classes/MopekaTankSensor.js b/sensor_classes/MopekaTankSensor.js
1390
- index 67ed0f6..9d41e7b 100644
1391
- --- a/sensor_classes/MopekaTankSensor.js
1392
- +++ b/sensor_classes/MopekaTankSensor.js
1393
- @@ -259,8 +259,8 @@ class MopekaTankSensor extends BTSensor{
1394
-
1395
- async init(){
1396
- await super.init()
1397
- - const md = this.valueIfVariant(this.getManufacturerData(this.constructor.manufacturerID))
1398
- - this.modelID = md[0]
1399
- + const md = this.getManufacturerData(this.constructor.manufacturerID)
1400
- + this.modelID = md?md[0]:0
1401
- }
1402
-
1403
- getMedium(){
1404
- @@ -280,17 +280,19 @@ class MopekaTankSensor extends BTSensor{
1405
- this.addParameter("medium",
1406
- {
1407
- title:"type of liquid in tank",
1408
- - enum: Object.keys(Media)
1409
- + enum: Object.keys(Media),
1410
- + isRequired: true
1411
- }
1412
- )
1413
- this.addParameter("tankHeight",
1414
- {
1415
- title:"height of tank (in mm)",
1416
- - type:"number",
1417
- - unit:"mm"
1418
- + type:"integer",
1419
- + unit:"mm",
1420
- + isRequired: true
1421
- }
1422
- )
1423
- - this.addDefaultParam("id")
1424
- + this.addDefaultParam("id", true)
1425
-
1426
- this.addDefaultPath("battVolt","sensors.batteryVoltage")
1427
- .read=((buffer)=>{
1428
- diff --git a/sensor_classes/RemoranWave3.js b/sensor_classes/RemoranWave3.js
1429
- new file mode 100644
1430
- index 0000000..e8448db
1431
- --- /dev/null
1432
- +++ b/sensor_classes/RemoranWave3.js
1433
- @@ -0,0 +1,225 @@
1434
- +
1435
- +function arduinoDateDecode (elapsedSeconds) {
1436
- + const date = new Date("2000-01-01")
1437
- + date.setTime(date.getTime() + 1000 * elapsedSeconds)
1438
- + return date
1439
- +}
1440
- +const errors= {
1441
- + 0: "Undefined",
1442
- + 1: "Invalid Battery",
1443
- + 2: "Overheat",
1444
- + 3: "Overheat Shutdown",
1445
- + 4: "Generator lead 1 disconnected",
1446
- + 5: "Generator lead 2 disconnected",
1447
- + 6: "Generator lead 3 disconnected",
1448
- + 7: "Short Circuit"
1449
- + }
1450
- +
1451
- +const eventTypes = {
1452
- + 0: "Reboot",
1453
- + 1: "Invalid Battery",
1454
- + 2: "Overheat",
1455
- + 3: "Overheat Shutdown",
1456
- + 4: "Generator lead 1 disconnected",
1457
- + 5: "Generator lead 2 disconnected",
1458
- + 6: "Generator lead 3 disconnected",
1459
- + 7: "Short Circuit",
1460
- + 255: "Debug"
1461
- + }
1462
- +
1463
- +const states= ["Charging Needed", "Charging", "Floating", "Idle"]
1464
- +
1465
- +
1466
- +const BTSensor = require("../BTSensor");
1467
- + class RemoranWave3 extends BTSensor{
1468
- + static Domain = BTSensor.SensorDomains.electrical
1469
- + serviceUUID = "81d08df0-c0f8-422a-9d9d-e4379bb1ea3b"
1470
- + info1CharUUID = "62c91222-fafe-4f6e-95f0-afc02bd19f2e"
1471
- + info2CharUUID = "f5d12d34-4390-486c-b906-24ea8906af71"
1472
- + eventUUID = "f12a8e25-59f7-42f2-b7ae-ba96fb25c13c"
1473
- +
1474
- + static async identify(device){
1475
- +
1476
- + const name = await this.getDeviceProp(device,"Name")
1477
- + if (name == 'Remoran Wave.3')
1478
- + return this
1479
- + else
1480
- + return null
1481
- + }
1482
- + hasGATT(){
1483
- + return true
1484
- + }
1485
- + usingGATT(){
1486
- + return true
1487
- + }
1488
- + emitInfo1Data(buffer){
1489
- + if (buffer.length < 20) {
1490
- + app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 20 bytes or more.`)
1491
- + return
1492
- + }
1493
- + this.emit("versionNumber", buffer.readUInt8(0))
1494
- + const errors = buffer.readUInt8(2)
1495
- + const errorState = []
1496
- + for (var i = 0; i < 8; ++i) {
1497
- + var c = 1 << i;
1498
- + errors & c && errorState.push(errors[i])
1499
- + }
1500
- + this.emit("errors", errorState)
1501
- + this.emit("state", states[buffer.readUInt8(3)])
1502
- + this.emit("rpm", buffer.readUInt32LE(4))
1503
- + this.emit( "voltage" , buffer.readFloatLE(8))
1504
- + this.emit("current", buffer.readFloatLE(12))
1505
- + this.emit( "power", buffer.readFloatLE(16))
1506
- +
1507
- + if (buffer.length > 23) {
1508
- + this.emit( "temp", ((buffer.readFloatLE(20))+273.15))
1509
- + this.emit( "uptime", buffer.readUInt32LE(24))
1510
- + if (versionNumber>1 && buffer.size > 31) {
1511
- + this.emit("energy", buffer.readFloatLE(32))
1512
- + }
1513
- + }
1514
- +
1515
- + }
1516
- + emitInfo2Data(buffer){
1517
- +
1518
- + if (buffer.size < 12) {
1519
- + app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 12 bytes or more.`)
1520
- + return
1521
- + }
1522
- + this.emit("versionNumber", buffer.readUInt8(0))
1523
- + this.emit("temp", ((buffer.readFloatLE(4))+273.15))
1524
- + this.emit("uptime", buffer.readUInt32LE(8))
1525
- + this.emit("lastBootTime", arduinoDateDecode(buffer.readUInt32LE(12)))
1526
- + this.emit("energy", buffer.readFloatLE(16))
1527
- + }
1528
- + emitEventData(buffer){
1529
- + if (buffer.length < 14) {
1530
- + this.debug(buffer)
1531
- + app.debug(`Bad buffer size ${buffer.length}. Buffer size must be 14 bytes or more.`)
1532
- + return
1533
- + }
1534
- + const eventType = buffer.readUInt16LE(8)
1535
- + var eventDesc = eventType.toString()
1536
- + if (Object.hasOwn(eventTypes,eventType))
1537
- + eventDesc = eventTypes[eventType]
1538
- +
1539
- +
1540
- + this.emit("event",
1541
- + {
1542
- + firstDate: arduinoDateDecode(buffer.readUInt32LE(0)),
1543
- + lastDate: arduinoDateDecode(buffer.readUInt32LE(4)),
1544
- + eventType: eventType,
1545
- + count: buffer.readUInt16LE(10),
1546
- + index: buffer.readUInt16LE(12),
1547
- + eventDesc: eventDesc
1548
- + }
1549
- + )
1550
- + }
1551
- + emitGATT(){
1552
- + this.info1Characteristic.readValue()
1553
- + .then((buffer)=>
1554
- + this.emitInfo1Data( buffer)
1555
- + )
1556
- + this.info2Characteristic.readValue()
1557
- + .then((buffer)=>
1558
- + this.emitInfo2Data(buffer)
1559
- + )
1560
- + this.eventCharacteristic.readValue()
1561
- + .then((buffer)=>
1562
- + this.emitEventData(buffer)
1563
- + )
1564
- +
1565
- + }
1566
- + initSchema(){
1567
- + super.initSchema()
1568
- + this.addDefaultParam("id")
1569
- + .default="RemoranWave3"
1570
- +
1571
- + this.getGATTParams()["useGATT"].default=true
1572
- +
1573
- + this.addMetadatum('errorCodes','', 'charger error codes (array)')
1574
- + .default= "electrical.chargers.{id}.errorCodes"
1575
- +
1576
- + this.addMetadatum('state','', 'charger state')
1577
- + .default= "electrical.chargers.{id}.state"
1578
- +
1579
- + this.addMetadatum('voltage','V', 'battery voltage')
1580
- + .default= "electrical.chargers.{id}.battery.voltage"
1581
- +
1582
- + this.addMetadatum('current','A', 'battery current')
1583
- + .default= "electrical.chargers.{id}.battery.current"
1584
- +
1585
- + this.addMetadatum('power','W', 'battery power')
1586
- + .default= "electrical.chargers.{id}.battery.power"
1587
- +
1588
- + this.addMetadatum('temp', 'K', 'charger temperature')
1589
- + .default= "electrical.chargers.{id}.temperature"
1590
- +
1591
- + this.addMetadatum('energy', 'wh', 'energy created today in Wh')
1592
- + .default= "electrical.chargers.{id}.energy"
1593
- +
1594
- + this.addMetadatum('event', '', 'charger event')
1595
- + .default= "electrical.chargers.{id}.event"
1596
- +
1597
- + this.addMetadatum('lastBootTime', 's', 'last boot time')
1598
- + .default= "electrical.chargers.{id}.lastBootTime"
1599
- +
1600
- + this.addMetadatum('rpm', '', 'revolutions per minute')
1601
- + .default= "sensors.{macAndName}.rpm"
1602
- +
1603
- + this.addMetadatum('uptime', 's', 'charger/sensor uptime')
1604
- + .default= "sensors.{macAndName}.uptime"
1605
- +
1606
- + this.addMetadatum('versionNumber', '', 'charger/sensor version number')
1607
- + .default= "sensors.{macAndName}.version"
1608
- +
1609
- + }
1610
- +
1611
- +
1612
- + initGATTConnection(){
1613
- + return new Promise((resolve,reject )=>{ this.deviceConnect().then(async ()=>{
1614
- + if (!this.gattServer) {
1615
- + this.gattServer = await this.device.gatt()
1616
- + this.service = await this.gattServer.getPrimaryService(this.serviceUUID)
1617
- + this.info1Characteristic = await this.service.getCharacteristic(this.info1CharUUID)
1618
- + this.info2Characteristic = await this.service.getCharacteristic(this.info2CharUUID)
1619
- + this.eventCharacteristic = await this.service.getCharacteristic(this.eventUUID)
1620
- + resolve(this)
1621
- + }}) .catch((e)=>{ this.debug(e); reject(e.message) }) })
1622
- + }
1623
- +
1624
- + initGATTNotifications() {
1625
- + Promise.resolve(this.info1Characteristic.startNotifications().then(()=>{
1626
- + this.info1Characteristic.on('valuechanged', buffer => {
1627
- + this.emitInfo1Data(buffer)
1628
- + })
1629
- + }))
1630
- + Promise.resolve(this.info2Characteristic.startNotifications().then(()=>{
1631
- + this.info2Characteristic.on('valuechanged', buffer => {
1632
- + this.emitInfo2Data(buffer)
1633
- + })
1634
- + }))
1635
- + Promise.resolve(this.eventCharacteristic.startNotifications().then(()=>{
1636
- + this.eventCharacteristic.on('valuechanged', buffer => {
1637
- + this.emitEventData(buffer)
1638
- + })
1639
- + }))
1640
- + }
1641
- +
1642
- + async stopNotifications(characteristic){
1643
- + if (characteristic && await characteristic.isNotifying()) {
1644
- + await characteristic.stopNotifications()
1645
- + }
1646
- + }
1647
- + async stopListening(){
1648
- + super.stopListening()
1649
- + await this.stopNotifications(this?.info1Characteristic)
1650
- + await this.stopNotifications(this?.info2Characteristic)
1651
- + await this.stopNotifications(this?.eventCharacteristic)
1652
- + if (await this.device.isConnected()){
1653
- + await this.device.disconnect()
1654
- + }
1655
- + }
1656
- + }
1657
- + module.exports=RemoranWave3
1658
- +
1659
-
1660
- diff --git a/sensor_classes/Renogy/RenogyConstants.js b/sensor_classes/Renogy/RenogyConstants.js
1661
- index 53bbf37..c35d8c8 100644
1662
- --- a/sensor_classes/Renogy/RenogyConstants.js
1663
- +++ b/sensor_classes/Renogy/RenogyConstants.js
1664
- @@ -13,7 +13,8 @@ CHARGING_STATE:
1665
- 3: 'Equalizing',
1666
- 4: 'Boost',
1667
- 5: 'Floating',
1668
- - 6: 'Current limiting'
1669
- + 6: 'Current limiting',
1670
- + 8: 'Not charging'
1671
- },
1672
-
1673
- LOAD_STATE: {
1674
- diff --git a/sensor_classes/Renogy/RenogySensor.js b/sensor_classes/Renogy/RenogySensor.js
1675
- index 6094b2d..c4719ae 100644
1676
- --- a/sensor_classes/Renogy/RenogySensor.js
1677
- +++ b/sensor_classes/Renogy/RenogySensor.js
1678
- @@ -42,13 +42,7 @@ class RenogySensor extends BTSensor{
1679
-
1680
- async initSchema(){
1681
- await super.initSchema()
1682
- - this.addParameter(
1683
- - "refreshInterval",
1684
- - {
1685
- - title: 'refresh interval',
1686
- - type: 'number'
1687
- - }
1688
- - )
1689
- +
1690
- this.addParameter(
1691
- "deviceID",
1692
- {
1693
- @@ -57,9 +51,6 @@ class RenogySensor extends BTSensor{
1694
- )
1695
- }
1696
-
1697
- - emitGATT(){
1698
- - }
1699
- -
1700
- getModelName(){
1701
- return this?.modelID??`${this.constructor.name} Unknown model`
1702
- }
1703
- @@ -86,7 +77,6 @@ class RenogySensor extends BTSensor{
1704
- }
1705
-
1706
- initGATTInterval(){
1707
- - this.emitGATT()
1708
- this.intervalID = setInterval(()=>{
1709
- this.emitGATT()
1710
- }, 1000*(this?.pollFreq??60) )
1711
- @@ -121,7 +111,8 @@ class RenogySensor extends BTSensor{
1712
- async stopListening(){
1713
- super.stopListening()
1714
-
1715
- - await this.readChar.stopNotifications()
1716
- + if (this.readChar)
1717
- + await this.readChar.stopNotifications()
1718
-
1719
- if (await this.device.isConnected()){
1720
- await this.device.disconnect()
1721
- diff --git a/sensor_classes/RenogyRoverClient.js b/sensor_classes/RenogyRoverClient.js
1722
- index 61d1787..36bcbff 100644
1723
- --- a/sensor_classes/RenogyRoverClient.js
1724
- +++ b/sensor_classes/RenogyRoverClient.js
1725
- @@ -6,53 +6,113 @@ const RenogySensor = require("./Renogy/RenogySensor.js");
1726
- const RC=require("./Renogy/RenogyConstants.js")
1727
-
1728
- class RenogyRoverClient extends RenogySensor {
1729
- -
1730
- +/*
1731
- + "batteryType": "electrical.charger.battery.type",
1732
- + "batteryPercentage": "electrical.charger.battery.charge",
1733
- + "batteryVoltage": "electrical.charger.battery.voltage",
1734
- + "batteryCurrent": "electrical.charger.battery.current",
1735
- + "controllerTemperature": "electrical.charger.temperature",
1736
- + "batteryTemperature": "electrical.charger.battery.temperature",
1737
- + "loadVoltage": "electrical.charger.load.voltage",
1738
- + "loadCurrent": "electrical.charger.load.current",
1739
- + "loadPower": "electrical.charger.load.power",
1740
- + "pvVoltage": "electrical.charger.solar.voltage",
1741
- + "pvCurrent": "electrical.charger.solar.current",
1742
- + "pvPower": "electrical.charger.solar.power",
1743
- + "maxChargingPowerToday": "electrical.charger.today.max",
1744
- + "maxDischargingPowerToday": "electrical.charger.discharging.maximum",
1745
- + "chargingAmpHoursToday": "electrical.charger.charged.today",
1746
- + "powerGenerationToday": "electrical.charger.power.today",
1747
- + "powerGenerationTotal": "electrical.charger.power.total",
1748
- + "loadStatus": "electrical.charger.load.status",
1749
- + "chargingStatus": "electrical.charger.status"
1750
- +*/
1751
-
1752
- initSchema(){
1753
- //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']
1754
- super.initSchema()
1755
- this.addMetadatum('batteryType', '', "battery type")
1756
- + .default="electrical.chargers.{id}.battery.type"
1757
- this.addMetadatum('batteryPercentage', 'ratio', "battery percentage",
1758
- - (buffer)=>{return buffer.readUInt16BE(3) })
1759
- + (buffer)=>{return buffer.readUInt16BE(3)/100 })
1760
- + .default="electrical.chargers.{id}.battery.soc"
1761
- +
1762
- this.addMetadatum('batteryVoltage', 'V', "battery voltage",
1763
- (buffer)=>{return buffer.readUInt16BE((5))/10})
1764
- + .default="electrical.chargers.{id}.battery.voltage"
1765
- +
1766
- this.addMetadatum('batteryCurrent', 'A', 'battery current',
1767
- (buffer)=>{return buffer.readUInt16BE((7))/100})
1768
- + .default="electrical.chargers.{id}.battery.current"
1769
- +
1770
- this.addMetadatum('controllerTemperature', 'K', 'controller temperature',
1771
- (buffer)=>{return buffer.readInt8((9))+273.15})
1772
- + .default="electrical.chargers.{id}.controller.temperature"
1773
- +
1774
- this.addMetadatum('batteryTemperature', 'K', 'battery temperature',
1775
- (buffer)=>{return buffer.readInt8((10))+273.15})
1776
- + .default="electrical.chargers.{id}.battery.temperature"
1777
- +
1778
- this.addMetadatum('loadVoltage', 'V', 'load voltage',
1779
- (buffer)=>{return buffer.readUInt16BE((11))/10})
1780
- + .default="electrical.chargers.{id}.load.voltage"
1781
- +
1782
- this.addMetadatum('loadCurrent', 'A', 'load current',
1783
- (buffer)=>{return buffer.readUInt16BE((13))/100})
1784
- + .default="electrical.chargers.{id}.load.current"
1785
- this.addMetadatum('loadPower', 'W', 'load power',
1786
- (buffer)=>{return buffer.readUInt16BE((15))})
1787
- + .default="electrical.chargers.{id}.load.power"
1788
- this.addMetadatum('pvVoltage', 'V', 'pv voltage',
1789
- (buffer)=>{return buffer.readUInt16BE((17))/10})
1790
- + .default="electrical.chargers.{id}.solar.voltage"
1791
- this.addMetadatum('pvCurrent', 'A', 'pv current',
1792
- (buffer)=>{return buffer.readUInt16BE((19))/100})
1793
- + .default="electrical.chargers.{id}.solar.current"
1794
- this.addMetadatum('pvPower', 'W', 'pv power',
1795
- (buffer)=>{return buffer.readUInt16BE(21)})
1796
- + .default="electrical.chargers.{id}.solar.power"
1797
- this.addMetadatum('maxChargingPowerToday', 'W', 'max charging power today',
1798
- (buffer)=>{return buffer.readUInt16BE(33)})
1799
- + .default="electrical.chargers.{id}.charge.max.today"
1800
- this.addMetadatum('maxDischargingPowerToday', 'W', 'max discharging power today',
1801
- (buffer)=>{return buffer.readUInt16BE(35)})
1802
- + .default="electrical.chargers.{id}.discharge.max.today"
1803
- this.addMetadatum('chargingAmpHoursToday', 'Ah', 'charging amp hours today',
1804
- (buffer)=>{return buffer.readUInt16BE(37)})
1805
- + .default="electrical.chargers.{id}.charge.ampHours.today"
1806
- +
1807
- this.addMetadatum('dischargingAmpHoursToday', 'Ah', 'discharging amp hours today',
1808
- (buffer)=>{return buffer.readUInt16BE(39)})
1809
- + .default="electrical.chargers.{id}.discharge.ampHours.today"
1810
- +
1811
- this.addMetadatum('powerGenerationToday', 'W', 'power generation today',
1812
- (buffer)=>{return buffer.readUInt16BE(41)})
1813
- + .default="electrical.chargers.{id}.power.generated.today"
1814
- +
1815
- this.addMetadatum('powerConsumptionToday', 'W', 'power consumption today',
1816
- (buffer)=>{return buffer.readUInt16BE(43)})
1817
- + .default="electrical.chargers.{id}.power.consumed.today"
1818
- +
1819
- this.addMetadatum('powerGenerationTotal', 'W', 'power generation total',
1820
- (buffer)=>{return buffer.readUInt32BE(59)})
1821
- + .default="electrical.chargers.{id}.power.generated.total"
1822
- +
1823
- this.addMetadatum('loadStatus', '', 'load status',
1824
- (buffer)=>{return RC.LOAD_STATE[buffer.readUInt8(67)>>7]})
1825
- + .default="electrical.chargers.{id}.load.status"
1826
-
1827
- this.addMetadatum('chargingStatus', '', 'charging status',
1828
- - (buffer)=>{return RC.CHARGING_STATE[buffer.readUInt8(68)]})
1829
- + (buffer)=>{
1830
- + const cs = buffer.readUInt8(68)
1831
- + if (Object.hasOwn(RC.CHARGING_STATE,cs))
1832
- + return RC.CHARGING_STATE[cs]
1833
- + else
1834
- + return null
1835
- + })
1836
- +
1837
- + .default="electrical.chargers.{id}.charge.status"
1838
- +
1839
- }
1840
-
1841
- retrieveDeviceID(){
1842
- diff --git a/sensor_classes/RuuviTag.js b/sensor_classes/RuuviTag.js
1843
- index 3648b98..555b3fc 100644
1844
- --- a/sensor_classes/RuuviTag.js
1845
- +++ b/sensor_classes/RuuviTag.js
1846
- @@ -50,15 +50,15 @@ class RuuviTag extends BTSensor{
1847
-
1848
- this.addMetadatum("accX","Mg","acceleration on X-axis",
1849
- (buffer)=>{ return buffer.readInt16BE(7)}
1850
- - ).default="sensors.{macAndName}.accX"
1851
- + ).examples=["sensors.{macAndName}.accX"]
1852
-
1853
- this.addMetadatum("accY","Mg","acceleration on Y-axis",
1854
- (buffer)=>{ return buffer.readInt16BE(9)}
1855
- - ) .default="sensors.{macAndName}.accY"
1856
- + ) .examples=["sensors.{macAndName}.accY"]
1857
-
1858
- this.addMetadatum("accZ","Mg","acceleration on Z-axis",
1859
- (buffer)=>{ return buffer.readInt16BE(11)}
1860
- - ) .default="sensors.{macAndName}.accZ"
1861
- + ) .examples=["sensors.{macAndName}.accZ"]
1862
-
1863
- this.addDefaultPath("battV","sensors.batteryVoltage")
1864
- .read=(buffer)=>{ return parseFloat((1.6+(buffer.readUInt16BE(13)>>5)/1000).toFixed(2))}
1865
- @@ -66,12 +66,12 @@ class RuuviTag extends BTSensor{
1866
- this.addMetadatum("mc","","movement counter",
1867
- (buffer)=>{ return buffer.readUInt16BE(13) && 0x1F}
1868
- )
1869
- - .default="sensors.{macAndName}.movementCounter"
1870
- + .examples=["sensors.{macAndName}.movementCounter"]
1871
-
1872
- this.addMetadatum("msc","","measurement sequence counter",
1873
- (buffer)=>{ return buffer.readUInt16BE(15)}
1874
- )
1875
- - .default="sensors.{macAndName}.measurementSequenceCounter"
1876
- + .examples=["sensors.{macAndName}.measurementSequenceCounter"]
1877
-
1878
- }
1879
-
1880
- diff --git a/sensor_classes/ShellySBDW002C.js b/sensor_classes/ShellySBDW002C.js
1881
- new file mode 100644
1882
- index 0000000..f9fe7a1
1883
- --- /dev/null
1884
- +++ b/sensor_classes/ShellySBDW002C.js
1885
- @@ -0,0 +1,51 @@
1886
- +const BTHomeServiceData = require("./BTHome/BTHomeServiceData");
1887
- +const AbstractBTHomeSensor = require("./BTHome/AbstractBTHomeSensor");
1888
- +
1889
- +/**
1890
- + * Sensor class representing the Shelly BLU Door/Window.
1891
- + * Specification on @see https://shelly-api-docs.shelly.cloud/docs-ble/Devices/dw
1892
- + * This sensor is publishing data utilising the BTHome format and inherits from {@link AbstractBTHomeSensor}.
1893
- + */
1894
- +class ShellySBDW002C extends AbstractBTHomeSensor {
1895
- + static Domain = this.SensorDomains.environmental;
1896
- +
1897
- + /**
1898
- + * The shortened local name as advertised by the Shelly BLU H&T.
1899
- + * @type {string}
1900
- + */
1901
- + static SHORTENED_LOCAL_NAME = "SBDW-002C";
1902
- + static LOCAL_NAME = "Shelly BLU Door/Window";
1903
- +
1904
- + getDescription() {
1905
- + return `For more information about the sensor go here: <a href=https://us.shelly.com/products/shelly-blu-door-window-white target="_blank">Shelly Blu Door/Window</a>.`;
1906
- + }
1907
- +
1908
- + initSchema() {
1909
- + super.initSchema();
1910
- + this.addDefaultParam("zone", true).default = "cabin";
1911
- +
1912
- + this.addParameter("opening", {
1913
- + title: "Name of opening",
1914
- + examples: ["companionWay", "porthole", "hatch"],
1915
- + isRequired: true,
1916
- + default: "companionWay",
1917
- + });
1918
- +
1919
- + this.addMetadatum("opening", "", "state of opening (door/window) as 'open' or 'closed'; null if not present. ", this.parseWindowState.bind(this)).default =
1920
- + "environment.{zone}.{opening}.state";
1921
- +
1922
- + // The second parameter is not the signalk path, but a JSON object in plugin_defaults.json; that provides the default signalk path
1923
- + this.addDefaultPath("battery", "sensors.batteryStrength").read = this.parseBatteryLevel.bind(this);
1924
- +
1925
- + this.addMetadatum("illuminance", "lx", "illuminance measured in lux, scaled 0.01", this.parseIlluminance.bind(this)).default =
1926
- + "sensors.{macAndName}.illuminance";
1927
- +
1928
- + this.addMetadatum("rotation", "deg", "rotation in degrees from the closed position; null if not present", this.parseRotation.bind(this)).default =
1929
- + "sensors.{macAndName}.rotation";
1930
- +
1931
- + this.addMetadatum("button", "", "button 'press' or 'hold_press'; null if not present", this.parseShellyButton.bind(this)).default =
1932
- + "sensors.{macAndName}.button";
1933
- + }
1934
- +}
1935
- +
1936
- +module.exports = ShellySBDW002C;
1937
- diff --git a/sensor_classes/ShellySBMO003Z.js b/sensor_classes/ShellySBMO003Z.js
1938
- index f1f16fb..7609db5 100644
1939
- --- a/sensor_classes/ShellySBMO003Z.js
1940
- +++ b/sensor_classes/ShellySBMO003Z.js
1941
- @@ -27,8 +27,7 @@ class ShellySBMO003Z extends AbstractBTHomeSensor {
1942
-
1943
- initSchema() {
1944
- super.initSchema()
1945
- - this.addDefaultParam("zone")
1946
- -
1947
- + this.addDefaultParam("zone", true)
1948
-
1949
- this.addMetadatum(
1950
- "motion",
1951
- diff --git a/sensor_classes/ShenzhenLiOnBMS.js b/sensor_classes/ShenzhenLiOnBMS.js
1952
- index bb6de7a..792aff0 100644
1953
- --- a/sensor_classes/ShenzhenLiOnBMS.js
1954
- +++ b/sensor_classes/ShenzhenLiOnBMS.js
1955
- @@ -1,6 +1,6 @@
1956
- /**
1957
- * Class to support Batteries with embedded Shenzhen Li-ion Battery Bodyguard Technology BMS
1958
- - * Brands include Redodo, Litime PowerQueen and possibly others
1959
- + * Brands include Redodo, Litime, PowerQueen and possibly others
1960
- */
1961
-
1962
-
1963
- @@ -34,19 +34,21 @@ class ShenzhenLiONBMS extends BTSensor{
1964
- static async identify(device){
1965
- return null
1966
- }
1967
- + async sendCommand(cmd){
1968
- + await this.txCharacteristic.writeValueWithResponse( Buffer.from(cmd))
1969
- + }
1970
- +
1971
- + async queryBatteryCommand(){
1972
- + await this.sendCommand(this.constructor.Commands.query_battery_status)
1973
- + }
1974
- +
1975
- hasGATT(){
1976
- return true
1977
- }
1978
- usingGATT(){
1979
- return true
1980
- }
1981
- - emitGATT(){
1982
- - this.characteristic.readValue()
1983
- - .then((buffer)=>
1984
- - this.emitValuesFrom( buffer)
1985
- - )
1986
-
1987
- - }
1988
- initSchema(){
1989
- super.initSchema()
1990
- this.getGATTParams()["useGATT"].default=true
1991
- @@ -58,6 +60,7 @@ class ShenzhenLiONBMS extends BTSensor{
1992
- {
1993
- title:"Number of cells",
1994
- type: "integer",
1995
- + isRequired: true,
1996
- default: 4,
1997
- minimum: 1,
1998
- maximum: 16
1999
- @@ -82,7 +85,7 @@ class ShenzhenLiONBMS extends BTSensor{
2000
-
2001
- for(let cellNum=0; cellNum < this?.numberOfCells??4; cellNum++) {
2002
- this.addMetadatum(`cell${cellNum+1}Voltage`,'V', `cell #${cellNum+1} voltage`,
2003
- - (buff)=>{return buff.readInt16LE(16+(cellNum*2)) })
2004
- + (buff)=>{return buff.readInt16LE(16+(cellNum*2)) /1000})
2005
- .default=`electrical.batteries.{batteryID}.cells.${cellNum+1}.voltage`
2006
- }
2007
-
2008
- @@ -97,11 +100,11 @@ class ShenzhenLiONBMS extends BTSensor{
2009
- (buff)=>{return buff.readInt16LE(54) + 273.15})
2010
- .default="electrical.batteries.{batteryID}.bms.temperature"
2011
-
2012
- - this.addDefaultPath('remainingAh','electrical.batteries.capacity.remaining')
2013
- - .read=(buff)=>{return this.buff.readUInt16LE(62)/100}
2014
- + this.addDefaultPath('remaining','electrical.batteries.capacity.remaining')
2015
- + .read=(buff)=>{return (buff.readUInt16LE(62)/100)*3600}
2016
-
2017
- - this.addDefaultPath('actualAh','electrical.batteries.capacity.actual')
2018
- - .read=(buff)=>{return this.buff.readUInt16LE(64)/100}
2019
- + this.addDefaultPath('actual','electrical.batteries.capacity.actual')
2020
- + .read=(buff)=>{return (buff.readUInt16LE(64)/100)*3600}
2021
-
2022
- this.addMetadatum('heat','', 'discharge disabled due to app button = 00000080, heater_error = 00000002',
2023
- (buff)=>{return buff.slice(68,72).reverse().join("")})
2024
- @@ -138,7 +141,6 @@ class ShenzhenLiONBMS extends BTSensor{
2025
- this.addDefaultPath( 'soc',"electrical.batteries.capacity.stateOfCharge")
2026
- .read=(buff)=>{return buff.readUInt16LE(90)/100}
2027
-
2028
- -
2029
- this.getJSONSchema().properties.params.required=["batteryID", "numberOfCells" ]
2030
- }
2031
-
2032
- @@ -155,16 +157,27 @@ class ShenzhenLiONBMS extends BTSensor{
2033
- resolve(this)
2034
- })})
2035
- }
2036
- +
2037
- + async initGATTInterval(){
2038
- + await this.initGATTNotifications()
2039
- + }
2040
- + emitGATT(){
2041
- +
2042
- + }
2043
- async initGATTNotifications() {
2044
- - await this.txCharacteristic.writeValue( Buffer.from(this.constructor.Commands.query_battery_status))
2045
- await this.rxCharacteristic.startNotifications()
2046
- this.rxCharacteristic.on('valuechanged', buffer => {
2047
- this.emitValuesFrom(buffer)
2048
- })
2049
- + this.intervalID=setInterval(
2050
- + async ()=>{
2051
- + await this.queryBatteryCommand()
2052
- + }, (this?.pollFreq??10)*1000
2053
- + )
2054
- }
2055
-
2056
- async stopListening(){
2057
- - super.stopListening()
2058
- + super.stopListening() //clears IntervalID as it happens
2059
- if (this.rxCharacteristic && await this.rxCharacteristic.isNotifying()) {
2060
- await this.rxCharacteristic.stopNotifications()
2061
- this.rxCharacteristic=null
2062
- @@ -174,4 +187,4 @@ class ShenzhenLiONBMS extends BTSensor{
2063
- }
2064
- }
2065
- }
2066
- -module.exports=ShenzhenLiONBMS
2067
-
2068
- +module.exports=ShenzhenLiONBMS
2069
- diff --git a/sensor_classes/SwitchBotMeterPlus.js b/sensor_classes/SwitchBotMeterPlus.js
2070
- index f611940..36eb9f3 100644
2071
- --- a/sensor_classes/SwitchBotMeterPlus.js
2072
- +++ b/sensor_classes/SwitchBotMeterPlus.js
2073
- @@ -24,7 +24,7 @@ class SwitchBotMeterPlus extends BTSensor{
2074
- const keys = Object.keys(md)
2075
- if ( (keys && keys.length>0) && (sdKeys && sdKeys.length >0) ){
2076
- const id = keys[keys.length-1]
2077
- - if (parseInt(id)==this.ID && md[id][0]==modelID && sdKeys[0] == this.serviceDataKey)
2078
- + if (parseInt(id)==this.ID && md[id][0]==this.modelID && sdKeys[0] == this.serviceDataKey)
2079
- return this
2080
- }
2081
- return null
2082
- @@ -63,7 +63,7 @@ class SwitchBotMeterPlus extends BTSensor{
2083
- }
2084
-
2085
- getName() {
2086
- - return "SwitchBotMeterPlus"
2087
- + return `SwitchBot Meter Plus (${this?.name??"Unnamed"})`
2088
- }
2089
-
2090
- }
2091
- diff --git a/sensor_classes/SwitchBotTH.js b/sensor_classes/SwitchBotTH.js
2092
- index 3444ab2..8dabba5 100644
2093
- --- a/sensor_classes/SwitchBotTH.js
2094
- +++ b/sensor_classes/SwitchBotTH.js
2095
- @@ -32,7 +32,7 @@ class SwitchBotTH extends BTSensor {
2096
- const keys = Object.keys(md)
2097
- if (keys && keys.length>0){
2098
- const id = keys[keys.length-1]
2099
- - if (parseInt(id)==this.ID && md[id].length==12 && md[id][0]==modelID)
2100
- + if (parseInt(id)==this.ID && md[id].length==12 && md[id][0]==this.modelID)
2101
- return this
2102
- }
2103
- return null
2104
- @@ -59,7 +59,7 @@ class SwitchBotTH extends BTSensor {
2105
- return "Wonder Labs"
2106
- }
2107
- getName() {
2108
- - "SwitchBotTH"
2109
- + return `SwitchBotTH (${this?.name??"Unnamed"})`
2110
- }
2111
- async propertiesChanged(props){
2112
- super.propertiesChanged(props)
2113
- diff --git a/sensor_classes/Victron/VictronImages.js b/sensor_classes/Victron/VictronImages.js
2114
- index bc624e4..8831cf5 100644
2115
- --- a/sensor_classes/Victron/VictronImages.js
2116
- +++ b/sensor_classes/Victron/VictronImages.js
2117
- @@ -1,6 +1,6 @@
2118
- Images= {
2119
- generic: "victron-min-1.jpg ",
2120
- - shunt: "Victron-SmartShunt.jpg",
2121
- + shunt: "SmartShunt_500_nw.png",
2122
- bmv: "BMV-712-Smart-Front.webp",
2123
- smartSolar: "smartsolarMPPT7515.png"
2124
- }
2125
- diff --git a/sensor_classes/Victron/VictronSensor.js b/sensor_classes/Victron/VictronSensor.js
2126
- index ae6612f..c112b14 100644
2127
- --- a/sensor_classes/Victron/VictronSensor.js
2128
- +++ b/sensor_classes/Victron/VictronSensor.js
2129
- @@ -15,9 +15,15 @@ function sleep(x) {
2130
-
2131
- constructor(device,config,gattConfig){
2132
- super(device,config,gattConfig)
2133
- - this.encryptionKey = config?.encryptionKey
2134
- +
2135
- + if(device.modelID)
2136
- + this.modelID=device.modelID
2137
- +
2138
- }
2139
-
2140
- + static getModelID(md) {
2141
- + return md[0x2e1]?.value.readUInt16LE(2)??-1
2142
- + }
2143
- static async identifyMode(device, mode){
2144
-
2145
- var md = await this.getDeviceProp(device,'ManufacturerData')
2146
- @@ -26,8 +32,10 @@ function sleep(x) {
2147
- else {
2148
-
2149
- if (md[0x2e1].value[0]==0x10) {
2150
- - if (md[0x2e1].value[4]==mode)
2151
- + if (md[0x2e1].value[4]==mode) {
2152
- + device.modelID=this.getModelID(md)
2153
- return this
2154
- + }
2155
- else
2156
- return null
2157
- }
2158
- @@ -45,8 +53,10 @@ function sleep(x) {
2159
- await sleep(500)
2160
- }
2161
- device.helper.removeListeners()
2162
- - if (md[0x2e1].value[4]==mode)
2163
- + if (md[0x2e1].value[4]==mode) {
2164
- + device.modelID=this.getModelID(md)
2165
- return this
2166
- + }
2167
- else
2168
- return null
2169
- }
2170
- @@ -60,14 +70,13 @@ function sleep(x) {
2171
- title:"Encryption Key"
2172
- }
2173
- )
2174
- - this.model_id=this.getManufacturerData(0x2e1)?.readUInt16LE(2)??"Unknown"
2175
- - this._schema.title = this.getName()
2176
- }
2177
- alarmReason(alarmValue){
2178
- return this.constructor.AlarmReason[alarmValue]
2179
- }
2180
- getModelName(){
2181
- - const m = VC.MODEL_ID_MAP[this.model_id]
2182
- + const mID = this.getModelID()
2183
- + const m = VC.MODEL_ID_MAP[mID]
2184
- if(m) {
2185
- if(typeof m == 'string' || m instanceof String ) {
2186
- return m
2187
- @@ -75,7 +84,7 @@ function sleep(x) {
2188
- return m.name
2189
- }
2190
- }
2191
- - return this.constructor.name+" (Model ID:"+this.model_id+")"
2192
- + return this.constructor.name+` (Model ID: ${mID==-1?"Unknown":mID})`
2193
- }
2194
-
2195
- decrypt(data){
2196
- @@ -98,6 +107,13 @@ function sleep(x) {
2197
- return Buffer.from(decData)
2198
-
2199
- }
2200
- + getModelID(){
2201
- + if (!this.modelID ||this.modelID==-1)
2202
- + this.modelID=this.getManufacturerData(0x2e1)?.readUInt16LE(2)??-1
2203
- +
2204
- + return this.modelID
2205
- + }
2206
- +
2207
- getName(){
2208
- return `Victron ${this.getModelName()}`
2209
- }
2210
- @@ -122,7 +138,7 @@ function sleep(x) {
2211
- }
2212
-
2213
- getImage(){
2214
- - const m = VC.MODEL_ID_MAP[this.model_id]
2215
- + const m = VC.MODEL_ID_MAP[this.getModelID()]
2216
- if (m && m.image)
2217
- return m.image
2218
- else
2219
- @@ -130,11 +146,18 @@ function sleep(x) {
2220
- }
2221
-
2222
- getDescription(){
2223
- - //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>`
2224
- + //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>`
2225
-
2226
-
2227
- - return `<img src="../bt-sensors-plugin-sk/images/${this.getImage()}" height="300" object-fit="cover" ></img>`
2228
- + return `<img src="../bt-sensors-plugin-sk/images/${this.getImage()}" width="200" style="float: left;
2229
- + margin: 20px;" ></img>
2230
- + 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>`
2231
- }
2232
-
2233
- + prepareConfig(config){
2234
- + super.prepareConfig(config)
2235
- + config.params.modelID=this.getModelID()
2236
- + }
2237
- +
2238
- }
2239
- module.exports=VictronSensor
2240
-
2241
- diff --git a/sensor_classes/VictronBatteryMonitor.js b/sensor_classes/VictronBatteryMonitor.js
2242
- index a16323e..038203e 100644
2243
- --- a/sensor_classes/VictronBatteryMonitor.js
2244
- +++ b/sensor_classes/VictronBatteryMonitor.js
2245
- @@ -22,7 +22,9 @@ class VictronBatteryMonitor extends VictronSensor{
2246
- d.currentProperties = {}
2247
- d.currentProperties.ManufacturerData={}
2248
- d.currentProperties.ManufacturerData[0x02e1]=b
2249
- - d.initMetadata()
2250
- + d.debug = (m)=>{console.log(m)}
2251
- + d.debug.bind(d)
2252
- + d.initSchema()
2253
- Object.keys(d.getPaths()).forEach((tag)=>{
2254
- d.on(tag,(v)=>console.log(`${tag}=${v}`))
2255
- })
2256
- @@ -56,7 +58,7 @@ class VictronBatteryMonitor extends VictronSensor{
2257
-
2258
- const alarmMD = this.addMetadatum('alarm','', 'alarm',
2259
- (buff,offset=0)=>{return buff.readInt16LE(offset)})
2260
- - alarmMD.default='"electrical.batteries.{batteryID}.alarm'
2261
- + alarmMD.default='electrical.batteries.{batteryID}.alarm'
2262
- alarmMD.notify=true
2263
-
2264
- this.addMetadatum( 'consumed','Ah', 'amp-hours consumed',
2265
- @@ -72,7 +74,7 @@ class VictronBatteryMonitor extends VictronSensor{
2266
- this.addDefaultPath( 'ttg',"electrical.batteries.capacity.timeRemaining")
2267
- .read=(buff,offset=0)=>{return this.NaNif(buff.readUInt16LE(offset),0xFFFF)*60}
2268
- this.getPath("ttg").gatt='65970ffe-4bda-4c1e-af4b-551c4cf74769'
2269
- -
2270
- + this.auxMode=VC.AuxMode.STARTER_VOLTAGE
2271
- try {
2272
- if (this.encryptionKey){
2273
- const decData = this.decrypt(this.getManufacturerData(0x02e1))
2274
- @@ -196,7 +198,7 @@ class VictronBatteryMonitor extends VictronSensor{
2275
- }
2276
-
2277
- getDescription(){
2278
- - return super.getDescription()
2279
- + 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)`
2280
- }
2281
-
2282
- async stopListening(){
2283
- diff --git a/sensor_classes/VictronDCDCConverter.js b/sensor_classes/VictronDCDCConverter.js
2284
- index 6bc634c..d90a1ea 100644
2285
- --- a/sensor_classes/VictronDCDCConverter.js
2286
- +++ b/sensor_classes/VictronDCDCConverter.js
2287
- @@ -10,7 +10,7 @@ class VictronDCDCConverter extends VictronSensor{
2288
-
2289
- initSchema(){
2290
- super.initSchema()
2291
- - this.addDefaultPath("id")
2292
- + this.addDefaultParam("id")
2293
-
2294
- this.addMetadatum('deviceState','', 'device state',
2295
- (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
2296
- diff --git a/sensor_classes/VictronLynxSmartBMS.js b/sensor_classes/VictronLynxSmartBMS.js
2297
- index 41711de..80a79a5 100644
2298
- --- a/sensor_classes/VictronLynxSmartBMS.js
2299
- +++ b/sensor_classes/VictronLynxSmartBMS.js
2300
- @@ -24,9 +24,11 @@ class VictronLynxSmartBMS extends VictronSensor{
2301
-
2302
- initSchema(){
2303
- super.initSchema()
2304
- + this.addDefaultParam("batteryID")
2305
- this.addMetadatum('error','', 'error code',
2306
- (buff)=>{return buff.readUInt8(0)})
2307
- -
2308
- + .default="electrical.batteries.{batteryID}.errorCode"
2309
- +
2310
- this.addDefaultPath('ttg','electrical.batteries.capacity.timeRemaining')
2311
- .read=(buff)=>{return this.NaNif(buff.readUInt16LE(1),0xFFFF)*60}
2312
- this.addDefaultPath('voltage','electrical.batteries.voltage')
2313
- @@ -36,10 +38,12 @@ class VictronLynxSmartBMS extends VictronSensor{
2314
-
2315
- this.addMetadatum('ioStatus','','IO Status', //TODO
2316
- (buff)=>{return buff.readUInt16LE(7)})
2317
- + .default="electrical.batteries.{batteryID}.IOStatus"
2318
-
2319
- this.addMetadatum('warningsAndAlarms','','warnings and alarms')
2320
-
2321
- this.addDefaultPath('soc','electrical.batteries.capacity.stateOfCharge')
2322
- +
2323
- this.addMetadatum('consumedAh','Ah','amp-hours consumed')
2324
- .default="electrical.batteries.{batteryID}.capacity.ampHoursConsumed"
2325
-
2326
- diff --git a/sensor_classes/VictronOrionXS.js b/sensor_classes/VictronOrionXS.js
2327
- index 3839180..4526b5e 100644
2328
- --- a/sensor_classes/VictronOrionXS.js
2329
- +++ b/sensor_classes/VictronOrionXS.js
2330
- @@ -34,7 +34,7 @@ class VictronOrionXS extends VictronSensor{
2331
- (buff)=>{return this.NaNif(buff.readUInt16LE(8),0xFFFF)/10})
2332
- .default="electrical.chargers.{id}.input.current"
2333
- this.addMetadatum('deviceOffReason','', 'device off reason',
2334
- - (buff)=>{return VC.OffReasons(buff.readUInt32BE(10))})
2335
- + (buff)=>{return VC.OffReasons.get(buff.readUInt32BE(10))})
2336
- .default="electrical.chargers.{id}.offReason"
2337
- }
2338
-
2339
- diff --git a/sensor_classes/VictronSmartLithium.js b/sensor_classes/VictronSmartLithium.js
2340
- index 0ed70ad..2988374 100644
2341
- --- a/sensor_classes/VictronSmartLithium.js
2342
- +++ b/sensor_classes/VictronSmartLithium.js
2343
- @@ -50,7 +50,7 @@ class VictronSmartLithium extends VictronSensor{
2344
- (buff)=>{return buff.readUInt16LE(4)})
2345
- .default="electrical.batteries.{batteryID}.errors"
2346
-
2347
- - for (let i=0;i++;i<8){
2348
- + for (let i=0;i<8; i++){
2349
- this.addMetadatum(`cell${i+1}Voltage`,'V', `cell ${i+1} voltage`)
2350
- .default=`electrical.batteries.{batteryID}.cell${i+1}.voltage`
2351
- }
2352
- diff --git a/sensor_classes/XiaomiMiBeacon.js b/sensor_classes/XiaomiMiBeacon.js
2353
- index 5517ab4..c7faac5 100644
2354
- --- a/sensor_classes/XiaomiMiBeacon.js
2355
- +++ b/sensor_classes/XiaomiMiBeacon.js
2356
- @@ -218,7 +218,8 @@ class XiaomiMiBeacon extends BTSensor{
2357
- this.addParameter(
2358
- "encryptionKey",
2359
- {
2360
- - title: "encryptionKey (AKA bindKey) for decryption"
2361
- + title: "encryptionKey (AKA bindKey) for decryption",
2362
- +
2363
- }
2364
- )
2365
- this.addDefaultPath('temp','environment.temperature')
2366
- diff --git a/src/components/PluginConfigurationPanel.js b/src/components/PluginConfigurationPanel.js
2367
- index 081d729..280e2b4 100644
2368
- --- a/src/components/PluginConfigurationPanel.js
2369
- +++ b/src/components/PluginConfigurationPanel.js
2370
- @@ -1,8 +1,7 @@
2371
- import Form from '@rjsf/core' ;
2372
- import validator from '@rjsf/validator-ajv8';
2373
- -import React from "react";
2374
- import ReactHtmlParser from 'react-html-parser';
2375
- -
2376
- +import React from 'react'
2377
- import {useEffect, useState} from 'react'
2378
-
2379
- import {Button, Grid } from '@material-ui/core';
2380
- @@ -20,9 +19,7 @@ import { ListGroupItem } from 'react-bootstrap';
2381
-
2382
- import ProgressBar from 'react-bootstrap/ProgressBar';
2383
-
2384
- -var _sensorMap, _sensorDomains={}, _sensorList={}
2385
- -
2386
- -export default function BTConfig (props) {
2387
- +export function BTConfig (props) {
2388
-
2389
- const _uiSchema= {
2390
- "ui:options": {label: false},
2391
- @@ -71,10 +68,9 @@ const useStyles = makeStyles((theme) => ({
2392
-
2393
- const [schema, setSchema] = useState({})
2394
- const [ uiSchema, setUISchema] = useState(_uiSchema )
2395
- - const [sensorList, setSensorList] = useState([])
2396
-
2397
- const [sensorData, setSensorData] = useState()
2398
- - const [sensorMap, setSensorMap ] = useState(new Map() )
2399
- + const [sensorMap, setSensorMap ] = useState(new Map())
2400
-
2401
- const [progress, setProgress ] = useState({
2402
- "progress":0, "maxTimeout": 100,
2403
- @@ -89,8 +85,6 @@ const useStyles = makeStyles((theme) => ({
2404
-
2405
- function sendJSONData(cmd, data){
2406
-
2407
- - console.log(`sending ${cmd}`)
2408
- - console.log(data)
2409
- const headers = new Headers();
2410
- headers.append("Content-Type", "application/json");
2411
- return fetch(`/plugins/bt-sensors-plugin-sk/${cmd}`, {
2412
- @@ -102,7 +96,6 @@ const useStyles = makeStyles((theme) => ({
2413
- }
2414
-
2415
- async function fetchJSONData(path){
2416
- - console.log(`fetching ${path}`)
2417
- var result
2418
- try {
2419
- result = fetch(`/plugins/bt-sensors-plugin-sk/${path}`, {
2420
- @@ -119,72 +112,53 @@ const useStyles = makeStyles((theme) => ({
2421
- }
2422
-
2423
- async function getSensors(){
2424
- - console.log("getSensors")
2425
- const response = await fetchJSONData("getSensors")
2426
- if (response.status!=200){
2427
- throw new Error(`Unable get sensor data: ${response.statusText} (${response.status}) `)
2428
- }
2429
- const json = await response.json()
2430
- - console.log(json)
2431
- - //for (let i=0;i<json.length;i++){
2432
- - // json[i].schema.htmlDescription=<div>{ReactHtmlParser(json[i].schema.htmlDescription)}<p></p></div>
2433
- - //}
2434
- - return json
2435
-
2436
- - }
2437
- - async function getDomains(){
2438
- - console.log("getDomains")
2439
- - const response = await fetchJSONData("getDomains")
2440
- - if (response.status!=200){
2441
- - throw new Error(`Unable get domain data: ${response.statusText} (${response.status}) `)
2442
- - }
2443
- - const json = await response.json()
2444
- - console.log(json)
2445
- return json
2446
-
2447
- }
2448
- +
2449
- async function getBaseData(){
2450
- - console.log("getBaseData")
2451
- const response = await fetchJSONData("getBaseData")
2452
- if (response.status!=200){
2453
- throw new Error(`Unable to get base data: ${response.statusText} (${response.status}) `)
2454
- }
2455
- const json = await response.json()
2456
- - console.log(json)
2457
- json.schema.htmlDescription=<div>{ReactHtmlParser(json.schema.htmlDescription)}<p></p></div>
2458
- return json
2459
- }
2460
- +
2461
- async function getProgress(){
2462
- - console.log("getProgress")
2463
- const response = await fetchJSONData("getProgress")
2464
- if (response.status!=200){
2465
- throw new Error(`Unable to get progress: ${response.statusText} (${response.status}) `)
2466
- }
2467
- const json = await response.json()
2468
- - console.log(json)
2469
- return json
2470
- }
2471
-
2472
- function updateSensorData(data){
2473
- - console.log("updateSensorData")
2474
- sendJSONData("updateSensorData", data).then((response)=>{
2475
- if (response.status != 200) {
2476
- throw new Error(response.statusText)
2477
- }
2478
- - sensorMap.get(data.mac_address)._changesMade=false
2479
- - sensorMap.get(data.mac_address).config = data
2480
- -
2481
- + setSensorMap((sm)=>{sm.delete(data.mac_address); return new Map(sm) })
2482
- + setSchema( {} )
2483
- +
2484
- })
2485
- }
2486
-
2487
- function undoChanges(mac) {
2488
- - console.log("undoChanges")
2489
- sensorMap.get(mac)._changesMade = false
2490
- + sensorMap.get(mac).config = JSON.parse(JSON.stringify(sensorMap.get(mac).configCopy))
2491
- setSensorData( sensorMap.get(mac).config )
2492
- }
2493
-
2494
- function removeSensorData(mac){
2495
- - console.log("removeSensorData")
2496
-
2497
- try{
2498
-
2499
- @@ -193,10 +167,7 @@ const useStyles = makeStyles((theme) => ({
2500
- throw new Error(response.statusText)
2501
- }
2502
- })
2503
- -
2504
- - _sensorMap.delete(mac)
2505
- -
2506
- - setSensorMap(new Map(_sensorMap))
2507
- + setSensorMap((sm)=>{sm.delete(mac); return new Map(sm) })
2508
- setSchema( {} )
2509
- } catch {(e)=>
2510
- setError( new Error(`Couldn't remove ${mac}: ${e}`))
2511
- @@ -206,88 +177,80 @@ const useStyles = makeStyles((theme) => ({
2512
-
2513
-
2514
- function updateBaseData(data){
2515
- - console.log("updateBaseData")
2516
- -
2517
- + setSensorMap(new Map())
2518
- + //setSensorList({})
2519
- sendJSONData("updateBaseData", data).then( (response )=>{
2520
- if (response.status != 200) {
2521
- setError(new Error(`Unable to update base data: ${response.statusText} (${response.status})`))
2522
- - } /*else {
2523
- - getProgress().then((json)=>{
2524
- - setProgress(json)
2525
- - }).catch((e)=>{
2526
- - setError(e)
2527
- - })
2528
- - }*/
2529
- + }
2530
- })
2531
- - }
2532
- -
2533
- -
2534
- - function refreshSensors(){
2535
- - console.log('refreshing sensor map')
2536
-
2537
- - getSensors().then((sensors)=>{
2538
- - setSensorMap (new Map(sensors.map((sensor)=>[sensor.info.mac,sensor])));
2539
- - })
2540
- - .catch((e)=>{
2541
- - setError(e)
2542
- - })
2543
- }
2544
- +
2545
- +
2546
-
2547
-
2548
- useEffect(()=>{
2549
- - console.log("useEffect([])")
2550
- let eventSource=null
2551
- -
2552
- fetchJSONData("getPluginState").then( async (response)=> {
2553
- +
2554
- + function newSensorEvent(event){
2555
- + let json = JSON.parse(event.data)
2556
- + console.log(`New sensor: ${json.info.mac}`)
2557
- + setSensorMap( (_sm)=> {
2558
- + //if (!_sm.has(json.info.mac))
2559
- + _sm.set(json.info.mac, json)
2560
- +
2561
- + return new Map(_sm)
2562
- + }
2563
- + )
2564
- + }
2565
- + function sensorChangedEvent(event){
2566
- + console.log("sensorchanged")
2567
- + const json = JSON.parse(event.data)
2568
- +
2569
- + setSensorMap( (_sm) => {
2570
- + const sensor = _sm.get(json.mac)
2571
- + if (sensor)
2572
- + Object.assign(sensor.info, json )
2573
- + return new Map(_sm)
2574
- + })
2575
- + }
2576
- +
2577
- +
2578
- if (response.status==404) {
2579
- setPluginState("unknown")
2580
- throw new Error("unable to get plugin state")
2581
- }
2582
- const json = await response.json()
2583
- - console.log("Setting up eventsource")
2584
- eventSource = new EventSource("/plugins/bt-sensors-plugin-sk/sse", { withCredentials: true })
2585
-
2586
- - setPluginState(json.state)
2587
- -
2588
- - _sensorDomains = await getDomains()
2589
- -
2590
- eventSource.addEventListener("newsensor", (event) => {
2591
- - console.log("newsensor")
2592
- - let json = JSON.parse(event.data)
2593
- -
2594
- - if (!_sensorMap.has(json.info.mac)) {
2595
- - console.log(`New sensor: ${json.info.mac}`)
2596
- - setSensorMap(new Map(_sensorMap.set(json.info.mac, json)))
2597
- - }
2598
- + newSensorEvent(event)
2599
- });
2600
-
2601
- eventSource.addEventListener("sensorchanged", (event) => {
2602
- - let json = JSON.parse(event.data)
2603
- - console.log("sensorchanged")
2604
- - console.log(json)
2605
- -
2606
- - if (_sensorMap.has(json.mac)) {
2607
- - let sensor = _sensorMap.get(json.mac)
2608
- -
2609
- - Object.assign(sensor.info, json )
2610
- - setSensorMap(new Map ( _sensorMap ))
2611
- - }
2612
- + sensorChangedEvent(event)
2613
- });
2614
- +
2615
- eventSource.addEventListener("progress", (event) => {
2616
- - console.log("progress")
2617
- const json = JSON.parse(event.data)
2618
- setProgress(json)
2619
- - console.log(json)
2620
- });
2621
-
2622
- eventSource.addEventListener("pluginstate", (event) => {
2623
- - console.log("pluginstate")
2624
- const json = JSON.parse(event.data)
2625
- setPluginState(json.state)
2626
- });
2627
- -
2628
- - })
2629
- +
2630
- + setPluginState(json.state);
2631
-
2632
- + (async ()=>{
2633
- + const sensors = await getSensors()
2634
- + setSensorMap ( new Map(sensors.map((sensor)=>[sensor.info.mac,sensor])) )
2635
- + })()
2636
- +
2637
- + })
2638
- .catch( (e) => {
2639
- setError(e)
2640
- }
2641
- @@ -296,13 +259,11 @@ const useStyles = makeStyles((theme) => ({
2642
- console.log("Closing connection to SSE")
2643
- eventSource.close()
2644
- };
2645
- +
2646
- },[])
2647
-
2648
- useEffect(()=>{
2649
- - console.log("useEffect([pluginState])")
2650
- - if (pluginState=="started"){
2651
- - refreshSensors()
2652
- -
2653
- + if (pluginState=="started") {
2654
- getBaseData().then((json) => {
2655
- setBaseSchema(json.schema);
2656
- setBaseData(json.data);
2657
- @@ -317,7 +278,6 @@ useEffect(()=>{
2658
- })
2659
-
2660
- } else{
2661
- - setSensorMap(new Map())
2662
- setBaseSchema({})
2663
- setBaseData({})
2664
- }
2665
- @@ -325,9 +285,10 @@ useEffect(()=>{
2666
- },[pluginState])
2667
-
2668
-
2669
- -
2670
- function confirmDelete(mac){
2671
- - const result = window.confirm(`Delete configuration for ${mac}?`)
2672
- +
2673
- + const sensor = sensorMap.get(mac)
2674
- + const result = !hasConfig(sensor) || window.confirm(`Delete configuration for ${sensor.info.name}?`)
2675
- if (result)
2676
- removeSensorData(mac)
2677
- }
2678
- @@ -352,7 +313,7 @@ function signalStrengthIcon(sensor){
2679
-
2680
- }
2681
- function hasConfig(sensor){
2682
- - return Object.keys(sensor.config).length>0;
2683
- + return Object.keys(sensor.configCopy).length>0;
2684
- }
2685
-
2686
- function createListGroupItem(sensor){
2687
- @@ -376,60 +337,43 @@ function createListGroupItem(sensor){
2688
- </ListGroupItem>
2689
- }
2690
-
2691
- -function configuredDevices(){
2692
- - return Array.from(sensorMap.entries()).filter((entry)=>hasConfig(entry[1]))
2693
- -}
2694
-
2695
- function devicesInDomain(domain){
2696
- +
2697
- return Array.from(sensorMap.entries()).filter((entry)=>entry[1].info.domain===domain)
2698
- }
2699
-
2700
- -
2701
- -useEffect(()=>{
2702
- - console.log("useEffect([sensorMap])")
2703
- -
2704
- - _sensorMap = sensorMap
2705
- -
2706
- - const _sensorDomains = new Set(sensorMap.entries().map((entry)=>{ return entry[1].info.domain}))
2707
- - const sl = {
2708
- - _configured: configuredDevices().map((entry) => {
2709
- - return createListGroupItem(sensorMap.get(entry[0]))
2710
- - })
2711
- - }
2712
- -
2713
- - _sensorDomains.forEach((d)=>{
2714
- - sl[d]=devicesInDomain(d).map((entry) => {
2715
- - return createListGroupItem(sensorMap.get(entry[0]))
2716
- - })
2717
- - })
2718
- - _sensorList=sl
2719
- -
2720
- -
2721
- - },[sensorMap]
2722
- - )
2723
- -
2724
- function ifNullNaN(value){
2725
- return value==null? NaN : value
2726
- }
2727
-
2728
- - function getSensorList(domain){
2729
- - return _sensorList[domain]
2730
- - }
2731
- -
2732
- function getTabs(){
2733
- + const sensorDomains = [... (new Set(sensorMap.entries().map((entry)=>{ return entry[1].info.domain})))].sort()
2734
- + const cd = Array.from(sensorMap.entries()).filter((entry)=>hasConfig(entry[1]))
2735
- + let sensorList={}
2736
- + sensorList["_configured"]=
2737
- + cd.length==0?
2738
- + "Select a device from its domain tab (Electrical etc.) and configure it.":
2739
- + cd.map((entry) => {
2740
- + return createListGroupItem(sensorMap.get(entry[0]))
2741
- + })
2742
- +
2743
- + sensorDomains.forEach((d)=>{
2744
- + sensorList[d]=devicesInDomain(d).map((entry) => {
2745
- + return createListGroupItem(sensorMap.get(entry[0]))
2746
- + })
2747
- + })
2748
-
2749
- - return Object.keys(_sensorList).map((domain)=> {return getTab(domain)})
2750
- + return Object.keys(sensorList).map((domain)=> {return getTab(domain, sensorList[domain])})
2751
- }
2752
- - // <div style={{paddingBottom: 20}} class="d-flex flex-wrap justify-content-start align-items-start">
2753
- - // <div class="d-flex flex-column" >
2754
-
2755
- - function getTab(key){
2756
- - var title = key.slice(key.charAt(0)==="_"?1:0)
2757
- + function getTab(key, sensorList){
2758
- + let title = key.slice(key.charAt(0)==="_"?1:0)
2759
-
2760
- - return <Tab eventKey={key} title={title.charAt(0).toUpperCase()+title.slice(1) }>
2761
- + return <Tab eventKey={key} title={`${title.charAt(0).toUpperCase()}${title.slice(1)}${typeof sensorList=='string'?'':' ('+sensorList.length+')'}` } >
2762
-
2763
- <ListGroup style={{ maxHeight: '300px', overflowY: 'auto' }}>
2764
- - {getSensorList(key)}
2765
- + {sensorList}
2766
- </ListGroup>
2767
-
2768
-
2769
- @@ -441,22 +385,6 @@ useEffect(()=>{
2770
- window.open(url, "_blank", "noreferrer");
2771
- }
2772
-
2773
- - function CustomFieldTemplate(props) {
2774
- - const { id, classNames, style, label, help, required, description, errors, children } = props;
2775
- - return (
2776
- - <div className={classNames} style={style}>
2777
- - <Grid container xs={12} direction="row" spacing="1">
2778
- - <Grid item xs={1} sm container direction="column">
2779
- - <Grid item ><label htmlFor={id}>{label}{required ? '*' : null}</label></Grid>
2780
- - <Grid item> {description}</Grid>
2781
- - </Grid>
2782
- - <Grid item>{children}</Grid>
2783
- - </Grid>
2784
- - {errors}
2785
- - {help}
2786
- - </div>
2787
- - );
2788
- -}
2789
-
2790
- if (pluginState=="stopped" || pluginState=="unknown")
2791
- return (<h3>Enable plugin to see configuration</h3>)
2792
- @@ -498,7 +426,7 @@ useEffect(()=>{
2793
- defaultActiveKey="_configured"
2794
- id="domain-tabs"
2795
- className="mb-3"
2796
- - onClick={()=>{setSchema({})}}
2797
- +
2798
- >
2799
- {getTabs()}
2800
- </Tabs>
2801
- @@ -514,13 +442,15 @@ useEffect(()=>{
2802
- uiSchema={uiSchema}
2803
- onChange={(e) => {
2804
- const s = sensorMap.get(e.formData.mac_address)
2805
- - s._changesMade=true
2806
- - setSensorData(e.formData)
2807
- + if(s) {
2808
- + s._changesMade=true
2809
- + s.config = e.formData
2810
- + setSensorData(e.formData)
2811
- + }
2812
- }
2813
- }
2814
- onSubmit={({ formData }, e) => {
2815
- updateSensorData(formData)
2816
- - setSchema({})
2817
- alert("Changes saved")
2818
- }}
2819
- onError={log('errors')}
2820
- @@ -538,4 +468,5 @@ useEffect(()=>{
2821
- )
2822
-
2823
-
2824
- - }
2825
-
2826
- + }
2827
- + export default BTConfig
2828
-
2829
- diff --git a/src/images/Aranet2_HOME_F_900x900_90OVA5J.original.webp b/src/images/Aranet2_HOME_F_900x900_90OVA5J.original.webp
2830
- new file mode 100644
2831
- index 0000000..db039cc
2832
- Binary files /dev/null and b/src/images/Aranet2_HOME_F_900x900_90OVA5J.original.webp differ
2833
- diff --git a/src/images/BMV-712-Smart-Front.webp b/src/images/BMV-712-Smart-Front.webp
2834
- new file mode 100644
2835
- index 0000000..9e8fd57
2836
- Binary files /dev/null and b/src/images/BMV-712-Smart-Front.webp differ
2837
- diff --git a/src/images/Gobius_C.png b/src/images/Gobius_C.png
2838
- new file mode 100644
2839
- index 0000000..e86a0dd
2840
- Binary files /dev/null and b/src/images/Gobius_C.png differ
2841
- diff --git a/src/images/LYWSD03MMC-Device.jpg b/src/images/LYWSD03MMC-Device.jpg
2842
- new file mode 100644
2843
- index 0000000..8670cbc
2844
- Binary files /dev/null and b/src/images/LYWSD03MMC-Device.jpg differ
2845
- diff --git a/src/images/Victron-SmartShunt.jpg b/src/images/Victron-SmartShunt.jpg
2846
- new file mode 100644
2847
- index 0000000..f7cea72
2848
- Binary files /dev/null and b/src/images/Victron-SmartShunt.jpg differ
2849
- diff --git a/src/images/bluetooth-logo.png b/src/images/bluetooth-logo.png
2850
- new file mode 100644
2851
- index 0000000..37fddd4
2852
- Binary files /dev/null and b/src/images/bluetooth-logo.png differ
2853
- diff --git a/src/images/smartsolarMPPT7515.png b/src/images/smartsolarMPPT7515.png
2854
- new file mode 100644
2855
- index 0000000..1bd55e4
2856
- Binary files /dev/null and b/src/images/smartsolarMPPT7515.png differ
2857
- diff --git a/src/images/victron-min-1.jpg b/src/images/victron-min-1.jpg
2858
- new file mode 100644
2859
- index 0000000..c59afba
2860
- Binary files /dev/null and b/src/images/victron-min-1.jpg differ