bt-sensors-plugin-sk 1.2.6-beta-3 → 1.2.6-beta-5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BTSensor.js +165 -62
- package/README.md +10 -1
- package/classLoader.js +9 -2
- package/development/FakeBTDevice.js +4 -1
- package/index.js +116 -59
- package/package.json +1 -1
- package/plugin_defaults.json +9 -0
- package/public/847.js +1 -1
- package/public/images/EcoWorthyBW2.webp +0 -0
- package/public/images/JikongBMS.jpg +0 -0
- package/sensor_classes/{EcoWorthy.js → EcoWorthyBW02.js} +20 -8
- package/sensor_classes/Inkbird.js +1 -1
- package/sensor_classes/JBDBMS.js +3 -0
- package/sensor_classes/JikongBMS.js +506 -0
- package/sensor_classes/RemoranWave3.js +3 -4
- package/sensor_classes/RuuviTag.js +1 -0
- package/sensor_classes/ShellySBMO003Z.js +5 -0
- package/sensor_classes/TestData/Jikong.json +36 -0
- package/sensor_classes/XiaomiMiBeacon.js +12 -14
- package/src/components/PluginConfigurationPanel.js +6 -1
package/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const OutOfRangeDevice = require("./OutOfRangeDevice.js")
|
|
|
11
11
|
const { createChannel, createSession } = require("better-sse");
|
|
12
12
|
const { clearTimeout } = require('timers')
|
|
13
13
|
const loadClassMap = require('./classLoader.js')
|
|
14
|
+
const { debug } = require("node:console")
|
|
14
15
|
class MissingSensor {
|
|
15
16
|
|
|
16
17
|
constructor(config){
|
|
@@ -76,11 +77,17 @@ class MissingSensor {
|
|
|
76
77
|
getRSSI(){
|
|
77
78
|
return NaN
|
|
78
79
|
}
|
|
80
|
+
getState(){
|
|
81
|
+
return "ERROR"
|
|
82
|
+
}
|
|
79
83
|
stopListening(){}
|
|
80
84
|
listen(){}
|
|
81
85
|
isActive(){
|
|
82
86
|
return false
|
|
83
87
|
}
|
|
88
|
+
isConnected(){return false}
|
|
89
|
+
|
|
90
|
+
|
|
84
91
|
elapsedTimeSinceLastContact(){
|
|
85
92
|
return NaN
|
|
86
93
|
}
|
|
@@ -90,18 +97,58 @@ class MissingSensor {
|
|
|
90
97
|
prepareConfig(){
|
|
91
98
|
|
|
92
99
|
}
|
|
100
|
+
getErrorLog(){
|
|
101
|
+
return []
|
|
102
|
+
}
|
|
103
|
+
getDebugLog(){
|
|
104
|
+
return []
|
|
105
|
+
}
|
|
93
106
|
|
|
94
107
|
}
|
|
95
108
|
module.exports = function (app) {
|
|
96
109
|
var deviceConfigs=[]
|
|
97
110
|
var starts=0
|
|
98
111
|
|
|
99
|
-
var plugin = {
|
|
112
|
+
var plugin = {
|
|
113
|
+
debug (message) {
|
|
114
|
+
app.debug(message)
|
|
115
|
+
const logEntry = {
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
message: message
|
|
118
|
+
}
|
|
119
|
+
this.log.push(logEntry)
|
|
120
|
+
if (channel)
|
|
121
|
+
channel.broadcast(logEntry,"pluginDebug")
|
|
122
|
+
},
|
|
123
|
+
setError( error ){
|
|
124
|
+
app.setPluginError(error)
|
|
125
|
+
app.debug(error)
|
|
126
|
+
const logEntry = {
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
message: error
|
|
129
|
+
}
|
|
130
|
+
this.errorLog.push(logEntry)
|
|
131
|
+
if (channel)
|
|
132
|
+
channel.broadcast( logEntry, "pluginError" )
|
|
133
|
+
},
|
|
134
|
+
setStatusText( status ){
|
|
135
|
+
app.setPluginStatus(status)
|
|
136
|
+
const logEntry = {timestamp: Date.now(), message: status}
|
|
137
|
+
this.log.push(logEntry)
|
|
138
|
+
if (channel)
|
|
139
|
+
channel.broadcast( logEntry, "pluginStatusText" )
|
|
140
|
+
},
|
|
141
|
+
setFatalError( error ){
|
|
142
|
+
this.setError(`FATAL ERROR: ${error}`)
|
|
143
|
+
this.stop()
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
}
|
|
100
147
|
plugin.id = 'bt-sensors-plugin-sk';
|
|
101
148
|
plugin.name = 'BT Sensors plugin';
|
|
102
149
|
plugin.description = 'Plugin to communicate with and update paths to BLE Sensors in Signalk';
|
|
103
|
-
|
|
104
|
-
|
|
150
|
+
plugin.log = []
|
|
151
|
+
plugin.errorLog = []
|
|
105
152
|
|
|
106
153
|
plugin.schema = {
|
|
107
154
|
type: "object",
|
|
@@ -134,6 +181,8 @@ module.exports = function (app) {
|
|
|
134
181
|
var discoveryIntervalID, progressID, progressTimeoutID, deviceHealthID
|
|
135
182
|
var adapter
|
|
136
183
|
const channel = createChannel()
|
|
184
|
+
|
|
185
|
+
plugin.debug(`Loading plugin ${packageInfo.version}`)
|
|
137
186
|
|
|
138
187
|
const sensorMap=new Map()
|
|
139
188
|
|
|
@@ -169,7 +218,7 @@ module.exports = function (app) {
|
|
|
169
218
|
|
|
170
219
|
_tempSensor = new _class ( _sensor.device )
|
|
171
220
|
_tempSensor.currentProperties=_sensor.currentProperties
|
|
172
|
-
_tempSensor.
|
|
221
|
+
_tempSensor._app = app
|
|
173
222
|
_tempSensor._adapter=adapter
|
|
174
223
|
await _tempSensor.init()
|
|
175
224
|
const _json = sensorToJSON(_tempSensor)
|
|
@@ -188,7 +237,6 @@ module.exports = function (app) {
|
|
|
188
237
|
})
|
|
189
238
|
|
|
190
239
|
router.post('/updateSensorData', async (req, res) => {
|
|
191
|
-
app.debug(req.body)
|
|
192
240
|
const sensor = sensorMap.get(req.body.mac_address)
|
|
193
241
|
sensor.prepareConfig(req.body)
|
|
194
242
|
const i = deviceConfigs.findIndex((p)=>p.mac_address==req.body.mac_address)
|
|
@@ -206,7 +254,6 @@ module.exports = function (app) {
|
|
|
206
254
|
deviceConfigs=options.peripherals
|
|
207
255
|
app.savePluginOptions(
|
|
208
256
|
options, async () => {
|
|
209
|
-
app.debug('Plugin options saved')
|
|
210
257
|
res.status(200).json({message: "Sensor updated"})
|
|
211
258
|
if (sensor) {
|
|
212
259
|
removeSensorFromList(sensor)
|
|
@@ -219,7 +266,6 @@ module.exports = function (app) {
|
|
|
219
266
|
|
|
220
267
|
});
|
|
221
268
|
router.post('/removeSensorData', async (req, res) => {
|
|
222
|
-
app.debug(req.body)
|
|
223
269
|
const sensor = sensorMap.get(req.body.mac_address)
|
|
224
270
|
if (!sensor) {
|
|
225
271
|
res.status(404).json({message: "Sensor not found"})
|
|
@@ -237,7 +283,6 @@ module.exports = function (app) {
|
|
|
237
283
|
sensorMap.delete(req.body.mac_address)
|
|
238
284
|
app.savePluginOptions(
|
|
239
285
|
options, () => {
|
|
240
|
-
app.debug('Plugin options saved')
|
|
241
286
|
res.status(200).json({message: "Sensor updated"})
|
|
242
287
|
channel.broadcast({},"resetSensors")
|
|
243
288
|
}
|
|
@@ -247,11 +292,9 @@ module.exports = function (app) {
|
|
|
247
292
|
|
|
248
293
|
router.post('/updateBaseData', async (req, res) => {
|
|
249
294
|
|
|
250
|
-
app.debug(req.body)
|
|
251
295
|
Object.assign(options,req.body)
|
|
252
296
|
app.savePluginOptions(
|
|
253
297
|
options, () => {
|
|
254
|
-
app.debug('Plugin options saved')
|
|
255
298
|
res.status(200).json({message: "Plugin updated"})
|
|
256
299
|
channel.broadcast({},"pluginRestarted")
|
|
257
300
|
restartPlugin(options)
|
|
@@ -277,13 +320,11 @@ module.exports = function (app) {
|
|
|
277
320
|
|
|
278
321
|
|
|
279
322
|
router.get('/getSensors', (req, res) => {
|
|
280
|
-
app.debug("Sending sensors")
|
|
281
323
|
const t = sensorsToJSON()
|
|
282
324
|
res.status(200).json(t)
|
|
283
325
|
});
|
|
284
326
|
|
|
285
327
|
router.get('/getProgress', (req, res) => {
|
|
286
|
-
app.debug("Sending progress")
|
|
287
328
|
let deviceCount = deviceConfigs.filter((dc)=>dc.active).length
|
|
288
329
|
const json = {"progress":foundConfiguredDevices/deviceCount, "maxTimeout": 1,
|
|
289
330
|
"deviceCount":foundConfiguredDevices,
|
|
@@ -302,7 +343,6 @@ module.exports = function (app) {
|
|
|
302
343
|
const session = await createSession(req,res)
|
|
303
344
|
channel.register(session)
|
|
304
345
|
req.on("close", ()=>{
|
|
305
|
-
app.debug("deregistering session")
|
|
306
346
|
channel.deregister(session)
|
|
307
347
|
})
|
|
308
348
|
});
|
|
@@ -318,14 +358,18 @@ module.exports = function (app) {
|
|
|
318
358
|
|
|
319
359
|
function getSensorInfo(sensor){
|
|
320
360
|
|
|
321
|
-
|
|
361
|
+
const s = sensor.getState()
|
|
322
362
|
return { mac: sensor.getMacAddress(),
|
|
323
363
|
name: sensor.getName(),
|
|
324
364
|
class: sensor.constructor.name,
|
|
325
365
|
domain: sensor.getDomain().name,
|
|
366
|
+
state: sensor.getState(),
|
|
367
|
+
errorLog: sensor.getErrorLog(),
|
|
368
|
+
debugLog: sensor.getDebugLog(),
|
|
326
369
|
RSSI: sensor.getRSSI(),
|
|
327
370
|
signalStrength: sensor.getSignalStrength(),
|
|
328
|
-
|
|
371
|
+
connected: sensor.isConnected(),
|
|
372
|
+
lastContactDelta: sensor.elapsedTimeSinceLastContact()
|
|
329
373
|
}
|
|
330
374
|
}
|
|
331
375
|
|
|
@@ -346,14 +390,14 @@ module.exports = function (app) {
|
|
|
346
390
|
|
|
347
391
|
const transport = options?.transport??"le"
|
|
348
392
|
const duplicateData = options?.duplicateData??false
|
|
349
|
-
|
|
393
|
+
plugin.debug("Starting scan...");
|
|
350
394
|
//Use adapter.helper directly to get around Adapter::startDiscovery()
|
|
351
395
|
//filter options which can cause issues with Device::Connect()
|
|
352
396
|
//turning off Discovery
|
|
353
397
|
//try {await adapter.startDiscovery()}
|
|
354
398
|
try{
|
|
355
399
|
if (transport) {
|
|
356
|
-
|
|
400
|
+
plugin.debug(`Setting Bluetooth transport option to ${transport}. DuplicateData to ${duplicateData}`)
|
|
357
401
|
await adapter.helper.callMethod('SetDiscoveryFilter', {
|
|
358
402
|
Transport: new Variant('s', transport),
|
|
359
403
|
DuplicateData: new Variant('b', duplicateData)
|
|
@@ -362,7 +406,7 @@ module.exports = function (app) {
|
|
|
362
406
|
await adapter.helper.callMethod('StartDiscovery')
|
|
363
407
|
}
|
|
364
408
|
catch (error){
|
|
365
|
-
|
|
409
|
+
plugin.debug(error)
|
|
366
410
|
}
|
|
367
411
|
|
|
368
412
|
}
|
|
@@ -376,8 +420,6 @@ module.exports = function (app) {
|
|
|
376
420
|
}
|
|
377
421
|
|
|
378
422
|
function addSensorToList(sensor){
|
|
379
|
-
app.debug(`adding sensor to list ${sensor.getMacAddress()}`)
|
|
380
|
-
|
|
381
423
|
sensorMap.set(sensor.getMacAddress(),sensor)
|
|
382
424
|
channel.broadcast(sensorToJSON(sensor),"newsensor");
|
|
383
425
|
}
|
|
@@ -389,7 +431,6 @@ module.exports = function (app) {
|
|
|
389
431
|
return new Promise( ( resolve, reject )=>{
|
|
390
432
|
var s
|
|
391
433
|
const startNumber=starts
|
|
392
|
-
//app.debug(`Waiting on ${deviceNameAndAddress(config)}`)
|
|
393
434
|
adapter.waitDevice(config.mac_address,(config?.discoveryTimeout??30)*1000)
|
|
394
435
|
.then(async (device)=> {
|
|
395
436
|
if (startNumber != starts ) {
|
|
@@ -402,13 +443,11 @@ module.exports = function (app) {
|
|
|
402
443
|
if (s instanceof BLACKLISTED)
|
|
403
444
|
reject ( `Device is blacklisted (${s.reasonForBlacklisting()}).`)
|
|
404
445
|
else{
|
|
405
|
-
//app.debug(`Adding sensor to list ${config.mac_address}`)
|
|
406
446
|
|
|
407
447
|
addSensorToList(s)
|
|
408
448
|
s._lastRSSI=-1*Infinity
|
|
409
449
|
s.on("RSSI",(()=>{
|
|
410
450
|
if (Date.now()-s._lastRSSI > 30000) { //only update RSSI on client every 30 seconds
|
|
411
|
-
//app.debug(`Updating ${s.getMacAddress()} RSSI after ${Date.now()-s._lastRSSI} ms`)
|
|
412
451
|
|
|
413
452
|
s._lastRSSI=Date.now()
|
|
414
453
|
|
|
@@ -434,13 +473,29 @@ module.exports = function (app) {
|
|
|
434
473
|
removeSensorFromList(s)
|
|
435
474
|
addSensorToList(s)
|
|
436
475
|
})
|
|
476
|
+
s.on("state", (state)=>{
|
|
477
|
+
channel.broadcast(getSensorInfo(s), "sensorchanged")
|
|
478
|
+
})
|
|
479
|
+
s.on("error",(error)=>{
|
|
480
|
+
channel.broadcast(getSensorInfo(s), "sensorchanged")
|
|
481
|
+
})
|
|
482
|
+
s.on("debug", ()=>{
|
|
483
|
+
channel.broadcast(getSensorInfo(s), "sensorchanged")
|
|
484
|
+
})
|
|
437
485
|
addSensorToList(s)
|
|
438
486
|
resolve(s)
|
|
439
487
|
}
|
|
440
488
|
}
|
|
441
489
|
if (startNumber == starts ) {
|
|
442
|
-
|
|
443
|
-
|
|
490
|
+
const errorTxt = `Unable to communicate with device ${deviceNameAndAddress(config)} Reason: ${e?.message??e}`
|
|
491
|
+
if(s)
|
|
492
|
+
{
|
|
493
|
+
s.setError(errorTxt)
|
|
494
|
+
} else {
|
|
495
|
+
plugin.setError(errorTxt)
|
|
496
|
+
}
|
|
497
|
+
plugin.debug(e)
|
|
498
|
+
|
|
444
499
|
reject( e?.message??e )
|
|
445
500
|
}
|
|
446
501
|
})})
|
|
@@ -476,20 +531,17 @@ module.exports = function (app) {
|
|
|
476
531
|
|
|
477
532
|
const sensor = new c(device, config?.params, config?.gattParams)
|
|
478
533
|
sensor._paths=config.paths //this might be a good candidate for refactoring
|
|
479
|
-
sensor.
|
|
480
|
-
sensor.setPluginError=app.setPluginError
|
|
481
|
-
sensor.app=app
|
|
482
|
-
if (!adapter) debugger
|
|
534
|
+
sensor._app=app
|
|
483
535
|
sensor._adapter=adapter //HACK!
|
|
484
536
|
await sensor.init()
|
|
485
537
|
return sensor
|
|
486
538
|
}
|
|
487
539
|
catch(error){
|
|
488
540
|
const msg = `Unable to instantiate ${await BTSensor.getDeviceProp(device,"Address")}: ${error.message} `
|
|
489
|
-
|
|
490
|
-
|
|
541
|
+
plugin.debug(msg)
|
|
542
|
+
plugin.debug(error)
|
|
491
543
|
if (config.active)
|
|
492
|
-
|
|
544
|
+
plugin.setError(msg)
|
|
493
545
|
return null
|
|
494
546
|
}
|
|
495
547
|
|
|
@@ -497,7 +549,7 @@ module.exports = function (app) {
|
|
|
497
549
|
|
|
498
550
|
function initConfiguredDevice(deviceConfig){
|
|
499
551
|
const startNumber=starts
|
|
500
|
-
|
|
552
|
+
plugin.setStatusText(`Initializing ${deviceNameAndAddress(deviceConfig)}`);
|
|
501
553
|
if (!deviceConfig.discoveryTimeout)
|
|
502
554
|
deviceConfig.discoveryTimeout = options.discoveryTimeout
|
|
503
555
|
createSensor(adapter, deviceConfig).then((sensor)=>{
|
|
@@ -505,7 +557,7 @@ module.exports = function (app) {
|
|
|
505
557
|
return
|
|
506
558
|
}
|
|
507
559
|
if (deviceConfig.active && !(sensor.device instanceof OutOfRangeDevice) ) {
|
|
508
|
-
|
|
560
|
+
plugin.setStatusText(`Listening to ${++foundConfiguredDevices} sensors.`);
|
|
509
561
|
sensor.activate(deviceConfig, plugin)
|
|
510
562
|
}
|
|
511
563
|
|
|
@@ -517,10 +569,10 @@ module.exports = function (app) {
|
|
|
517
569
|
return
|
|
518
570
|
}
|
|
519
571
|
const msg =`Sensor at ${deviceConfig.mac_address} unavailable. Reason: ${error}`
|
|
520
|
-
|
|
521
|
-
|
|
572
|
+
plugin.debug(msg)
|
|
573
|
+
|
|
522
574
|
if (deviceConfig.active)
|
|
523
|
-
|
|
575
|
+
plugin.setError(msg)
|
|
524
576
|
const sensor=new MissingSensor(deviceConfig)
|
|
525
577
|
++foundConfiguredDevices
|
|
526
578
|
|
|
@@ -530,7 +582,7 @@ module.exports = function (app) {
|
|
|
530
582
|
}
|
|
531
583
|
function findDevices (discoveryTimeout) {
|
|
532
584
|
const startNumber = starts
|
|
533
|
-
|
|
585
|
+
plugin.setStatusText("Scanning for new Bluetooth devices...");
|
|
534
586
|
|
|
535
587
|
adapter.devices().then( (macs)=>{
|
|
536
588
|
if (startNumber != starts ) {
|
|
@@ -580,7 +632,7 @@ module.exports = function (app) {
|
|
|
580
632
|
//Connect to adapter
|
|
581
633
|
|
|
582
634
|
if (!adapter){
|
|
583
|
-
|
|
635
|
+
plugin.debug(`Connecting to bluetooth adapter ${adapterID}`);
|
|
584
636
|
|
|
585
637
|
adapter = await bluetooth.getAdapter(adapterID)
|
|
586
638
|
|
|
@@ -588,11 +640,10 @@ module.exports = function (app) {
|
|
|
588
640
|
|
|
589
641
|
await adapter.helper._prepare()
|
|
590
642
|
adapter.helper._propsProxy.on('PropertiesChanged', async (iface,changedProps,invalidated) => {
|
|
591
|
-
app.debug(changedProps)
|
|
592
643
|
if (Object.hasOwn(changedProps,"Powered")){
|
|
593
644
|
if (changedProps.Powered.value==false) {
|
|
594
645
|
if (plugin.started){ //only call stop() if plugin is started
|
|
595
|
-
|
|
646
|
+
plugin.setStatusText(`Bluetooth Adapter ${adapterID} turned off. Plugin disabled.`)
|
|
596
647
|
await plugin.stop()
|
|
597
648
|
}
|
|
598
649
|
} else {
|
|
@@ -601,8 +652,8 @@ module.exports = function (app) {
|
|
|
601
652
|
}
|
|
602
653
|
})
|
|
603
654
|
if (!await adapter.isPowered()) {
|
|
604
|
-
|
|
605
|
-
|
|
655
|
+
plugin.debug(`Bluetooth Adapter ${adapterID} not powered on.`)
|
|
656
|
+
plugin.setError(`Bluetooth Adapter ${adapterID} not powered on.`)
|
|
606
657
|
await plugin.stop()
|
|
607
658
|
return
|
|
608
659
|
}
|
|
@@ -618,19 +669,26 @@ module.exports = function (app) {
|
|
|
618
669
|
plugin.stopped=false
|
|
619
670
|
}
|
|
620
671
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
plugin.schema.properties.adapter.
|
|
672
|
+
try{
|
|
673
|
+
const activeAdapters = await bluetooth.activeAdapters()
|
|
674
|
+
if (activeAdapters.length==0){
|
|
675
|
+
plugin.setError("No active Bluetooth adapters found.")
|
|
676
|
+
}
|
|
677
|
+
plugin.schema.properties.adapter.enum=[]
|
|
678
|
+
plugin.schema.properties.adapter.enumNames=[]
|
|
679
|
+
for (a of activeAdapters){
|
|
680
|
+
plugin.schema.properties.adapter.enum.push(a.adapter)
|
|
681
|
+
plugin.schema.properties.adapter.enumNames.push(`${a.adapter} @ ${ await a.getAddress()} (${await a.getName()})`)
|
|
682
|
+
}}
|
|
683
|
+
catch(e){
|
|
684
|
+
plugin.setError(`Unable to get adapters: ${e.message}`)
|
|
627
685
|
}
|
|
628
686
|
|
|
629
687
|
await startScanner(options)
|
|
630
688
|
if (starts>0){
|
|
631
|
-
|
|
689
|
+
plugin.debug(`Plugin ${packageInfo.version} restarting...`);
|
|
632
690
|
} else {
|
|
633
|
-
|
|
691
|
+
plugin.debug(`Plugin ${packageInfo.version} started` )
|
|
634
692
|
|
|
635
693
|
}
|
|
636
694
|
starts++
|
|
@@ -638,7 +696,7 @@ module.exports = function (app) {
|
|
|
638
696
|
try{
|
|
639
697
|
await startScanner(options)
|
|
640
698
|
} catch (e){
|
|
641
|
-
|
|
699
|
+
plugin.setError(`Error starting scan: ${e.message}`)
|
|
642
700
|
}
|
|
643
701
|
if (!(deviceConfigs===undefined)){
|
|
644
702
|
const maxTimeout=Math.max(...deviceConfigs.map((dc)=>dc?.discoveryTimeout??options.discoveryTimeout))
|
|
@@ -677,7 +735,6 @@ module.exports = function (app) {
|
|
|
677
735
|
const dt = config?.discoveryTimeout??options.discoveryTimeout
|
|
678
736
|
const lc=sensor.elapsedTimeSinceLastContact()
|
|
679
737
|
if (lc > dt) {
|
|
680
|
-
//app.debug(`${sensor.getMacAddress()} not heard from in ${lc} seconds`)
|
|
681
738
|
channel.broadcast(getSensorInfo(sensor), "sensorchanged")
|
|
682
739
|
}
|
|
683
740
|
})
|
|
@@ -691,7 +748,7 @@ module.exports = function (app) {
|
|
|
691
748
|
options.discoveryInterval)
|
|
692
749
|
}
|
|
693
750
|
plugin.stop = async function () {
|
|
694
|
-
|
|
751
|
+
plugin.debug("Stopping plugin")
|
|
695
752
|
plugin.stopped=true
|
|
696
753
|
plugin.started=false
|
|
697
754
|
channel.broadcast({state:"stopped"},"pluginstate")
|
|
@@ -712,10 +769,10 @@ module.exports = function (app) {
|
|
|
712
769
|
for await (const sensorEntry of sensorMap.entries()) {
|
|
713
770
|
try{
|
|
714
771
|
await sensorEntry[1].stopListening()
|
|
715
|
-
|
|
772
|
+
plugin.debug(`No longer listening to ${sensorEntry[0]}`)
|
|
716
773
|
}
|
|
717
774
|
catch (e){
|
|
718
|
-
|
|
775
|
+
plugin.setError(`Error stopping listening to ${sensorEntry[0]}: ${e.message}`)
|
|
719
776
|
}
|
|
720
777
|
}
|
|
721
778
|
}
|
|
@@ -726,12 +783,12 @@ module.exports = function (app) {
|
|
|
726
783
|
if( await adapter.isDiscovering())
|
|
727
784
|
try{
|
|
728
785
|
await adapter.stopDiscovery()
|
|
729
|
-
|
|
786
|
+
plugin.debug('Scan stopped')
|
|
730
787
|
} catch (e){
|
|
731
|
-
|
|
788
|
+
plugin.setError(`Error stopping scan: ${e.message}`)
|
|
732
789
|
}
|
|
733
790
|
}
|
|
734
|
-
|
|
791
|
+
plugin.debug('BT Sensors plugin stopped')
|
|
735
792
|
|
|
736
793
|
}
|
|
737
794
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bt-sensors-plugin-sk",
|
|
3
|
-
"version": "1.2.6-beta-
|
|
3
|
+
"version": "1.2.6-beta-5",
|
|
4
4
|
"description": "Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk#supported-sensors for a list of supported sensors",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
package/plugin_defaults.json
CHANGED
|
@@ -134,10 +134,19 @@
|
|
|
134
134
|
"default": "electrical.batteries.{batteryID}.capacity.totalCharge"
|
|
135
135
|
},
|
|
136
136
|
"stateOfCharge":{
|
|
137
|
+
"description": "Battery's state of charge as ratio",
|
|
137
138
|
"unit":"ratio",
|
|
138
139
|
"default": "electrical.batteries.{batteryID}.capacity.stateOfCharge"
|
|
139
140
|
},
|
|
141
|
+
"stateOfHealth":{
|
|
142
|
+
"description": "Battery's state of health as ratio",
|
|
143
|
+
"unit":"ratio",
|
|
144
|
+
"default": "electrical.batteries.{batteryID}.capacity.stateOfHealth"
|
|
145
|
+
},
|
|
146
|
+
|
|
140
147
|
"timeRemaining":{
|
|
148
|
+
"description": "Time in seconds until battery reaches its discharge floor at current usage",
|
|
149
|
+
|
|
141
150
|
"unit":"s",
|
|
142
151
|
"default": "electrical.batteries.{batteryID}.capacity.timeRemaining"
|
|
143
152
|
|
package/public/847.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(self.webpackChunkbt_sensors_plugin_sk=self.webpackChunkbt_sensors_plugin_sk||[]).push([[847],{9337:()=>{},62995:(e,t,n)=>{"use strict";n.r(t),n.d(t,{BTConfig:()=>k,default:()=>C});var a=n(73490),s=n(74810),r=n(40334),o=n(86528),c=n.n(o),i=n(11363),l=n(97493),u=n(27606),m=n(82096),d=n(3768),g=n(71431),f=n(39676),h=n(47041),p=n(86038),E=n(95027),w=n(43540),y=n(38250),S=n(31008),b=n(20455),v=n(23399);const A=e=>console.log.bind(console,e);function k(e){const t=(0,m.A)((e=>({root:{"& > *":{margin:e.spacing(1)}}}))),[n,k]=(0,o.useState)({}),[C,D]=(0,o.useState)({}),[_,$]=(0,o.useState)({}),[N
|
|
1
|
+
(self.webpackChunkbt_sensors_plugin_sk=self.webpackChunkbt_sensors_plugin_sk||[]).push([[847],{9337:()=>{},62995:(e,t,n)=>{"use strict";n.r(t),n.d(t,{BTConfig:()=>k,default:()=>C});var a=n(73490),s=n(74810),r=n(40334),o=n(86528),c=n.n(o),i=n(11363),l=n(97493),u=n(27606),m=n(82096),d=n(3768),g=n(71431),f=n(39676),h=n(47041),p=n(86038),E=n(95027),w=n(43540),y=n(38250),S=n(31008),b=n(20455),v=n(23399);const A=e=>console.log.bind(console,e);function k(e){const t=(0,m.A)((e=>({root:{"& > *":{margin:e.spacing(1)}}}))),[n,k]=(0,o.useState)({}),[C,D]=(0,o.useState)({}),[_,$]=(0,o.useState)({}),[x,N]=(0,o.useState)({"ui:options":{label:!1},paths:{enableMarkdownInDescription:!0},title:{"ui:widget":"hidden"}}),[O,T]=(0,o.useState)(),[j,M]=(0,o.useState)(!1),[U,J]=(0,o.useState)(!0),[L,I]=(0,o.useState)(new Map),[P,K]=(0,o.useState)({progress:0,maxTimeout:100,deviceCount:0,totalDevices:0}),[R,B]=(0,o.useState)("unknown"),[G,W]=(0,o.useState)(),[F,H]=(0,o.useState)(!1),[z,Y]=(0,o.useState)(""),q=t();function Q(e,t){const n=new Headers;return n.append("Content-Type","application/json"),fetch(`/plugins/bt-sensors-plugin-sk/${e}`,{credentials:"include",method:"POST",body:JSON.stringify(t),headers:n})}async function V(e,t={}){let n;try{const a=Object.keys(t).length?"?"+new URLSearchParams(t).toString():"";n=await fetch(`/plugins/bt-sensors-plugin-sk/${e}${a}`,{credentials:"include",method:"GET"})}catch(e){n={status:500,statusText:e.toString()}}return n}function X(e){return Object.keys(e.configCopy).length>0}function Z(e){const t=X(e);return c().createElement(b.A,{action:!0,onClick:()=>{e.config.mac_address=e.info.mac,$(e.schema),T(e.config)}},c().createElement("div",{class:"d-flex justify-content-between align-items-center",style:t?{fontWeight:"normal"}:{fontStyle:"italic"}},`${e._changesMade?"*":""}${e.info.name} MAC: ${e.info.mac} RSSI: ${n=e.info.RSSI,null==n?NaN:n}`,c().createElement("div",{class:"d-flex justify-content-between "},e.info.state,c().createElement("div",{class:"d-flex justify-content-between "},function(e){return null==e.info.lastContactDelta||e.info.lastContactDelta>e.config.discoveryTimeout?c().createElement(d.A,null):e.info.signalStrength>80?c().createElement(g.A,null):e.info.signalStrength>60?c().createElement(f.A,null):e.info.signalStrength>40?c().createElement(h.A,null):e.info.signalStrength>20?c().createElement(p.A,null):c().createElement(E.A,null)}(e)))));var n}function ee(e){window.open(e,"_blank","noreferrer")}return(0,o.useEffect)((()=>{let e=null;return V("getPluginState").then((async t=>{if(404==t.status)throw B("unknown"),new Error("unable to get plugin state");const n=await t.json();e=new EventSource("/plugins/bt-sensors-plugin-sk/sse",{withCredentials:!0}),e.addEventListener("newsensor",(e=>{!function(e){let t=JSON.parse(e.data);console.log(`New sensor: ${t.info.mac}`),I((e=>(e.set(t.info.mac,t),new Map(e))))}(e)})),e.addEventListener("sensorchanged",(e=>{!function(e){console.log("sensorchanged");const t=JSON.parse(e.data);console.log(t),I((e=>{const n=e.get(t.mac);return n&&Object.assign(n.info,t),new Map(e)}))}(e)})),e.addEventListener("progress",(e=>{const t=JSON.parse(e.data);K(t)})),e.addEventListener("pluginstate",(e=>{const t=JSON.parse(e.data);B(t.state)})),B(n.state),(async()=>{const e=await async function(){const e=await V("getSensors");if(200!=e.status)throw new Error(`Unable get sensor data: ${e.statusText} (${e.status}) `);return await e.json()}();I(new Map(e.map((e=>[e.info.mac,e]))))})()})).catch((e=>{W(e.message)})),()=>{console.log("Closing connection to SSE"),e.close()}}),[]),(0,o.useEffect)((()=>{if(!j)return;if(!O||!L)return;const e=L.get(O.mac_address);e&&_&&O&&Object.hasOwn(O,"params")&&"UNKNOWN"==e.info.class&&O.params.sensorClass&&"UNKNOWN"!=O.params.sensorClass&&(J(!1),Y(`Please wait. Fetching schema for ${O.params.sensorClass}...`),async function(e,t){const n=await V("getSensorInfo",{mac_address:e,class:t});if(200!=n.status)throw new Error(`Unable get sensor info: ${n.statusText} (${n.status}) `);return await n.json()}(O.mac_address,O.params.sensorClass).then((e=>{$(e.schema)})).catch((e=>{alert(e.message)})).finally((()=>{Y(""),J(!0),M(!1)})))}),[j]),(0,o.useEffect)((()=>{H(""!=z)}),[z]),(0,o.useEffect)((()=>{"started"==R?(async function(){const e=await V("getBaseData");if(200!=e.status)throw new Error(`Unable to get base data: ${e.statusText} (${e.status}) `);const t=await e.json();return t.schema.htmlDescription=c().createElement("div",null,(0,r.Ay)(t.schema.htmlDescription),c().createElement("p",null)),t}().then((e=>{k(e.schema),D(e.data)})).catch((e=>{W(e.message)})),async function(){const e=await V("getProgress");if(200!=e.status)throw new Error(`Unable to get progress: ${e.statusText} (${e.status}) `);return await e.json()}().then((e=>{K(e)})).catch((e=>{W(e.message)}))):(k({}),D({}))}),[R]),"stopped"==R||"unknown"==R?c().createElement("h3",null,"Enable plugin to see configuration"):c().createElement("div",null,c().createElement(i.A,{anchorOrigin:{horizontal:"center",vertical:"bottom"},onClose:()=>H(!1),open:F,message:z,key:"snackbar"}),c().createElement("div",{className:q.root},c().createElement(l.A,{variant:"contained",onClick:()=>{ee("https://github.com/naugehyde/bt-sensors-plugin-sk/tree/1.2.0-beta#configuration")}},"Documentation"),c().createElement(l.A,{variant:"contained",onClick:()=>{ee("https://github.com/naugehyde/bt-sensors-plugin-sk/issues/new/choose")}},"Report Issue"),c().createElement(l.A,{variant:"contained",onClick:()=>{ee("https://discord.com/channels/1170433917761892493/1295425963466952725")}},"Discord Thread"),c().createElement("p",null),c().createElement("p",null)),c().createElement("hr",{style:{width:"100%",height:"1px",color:"gray","background-color":"gray","text-align":"left","margin-left":0}}),G?c().createElement("h2",{style:{color:"red"}},G):"",c().createElement(a.Ay,{schema:n,validator:s.Ay,uiSchema:{"ui:field":"LayoutGridField","ui:layoutGrid":{"ui:row":[{"ui:row":{className:"row",children:[{"ui:columns":{className:"col-xs-4",children:["adapter","transport","duplicateData","discoveryTimeout","discoveryInterval"]}}]}}]}},onChange:e=>D(e.formData),onSubmit:({formData:e},t)=>{var n;n=e,I(new Map),Q("updateBaseData",n).then((e=>{200!=e.status&&W(`Unable to update base data: ${e.statusText} (${e.status})`)})),$({})},onError:A("errors"),formData:C}),c().createElement("hr",{style:{width:"100%",height:"1px",color:"gray","background-color":"gray","text-align":"left","margin-left":0}}),c().createElement("p",null),c().createElement("p",null),P.deviceCount<P.totalDevices?c().createElement(v.A,{max:P.maxTimeout,now:P.progress}):"",c().createElement("p",null),c().createElement(y.A,{defaultActiveKey:"_configured",id:"domain-tabs",className:"mb-3"},function(){const e=[...new Set(L.entries().map((e=>e[1].info.domain)))].sort(),t=Array.from(L.entries()).filter((e=>X(e[1])));let n={};return n._configured=0==t.length?"Select a device from its domain tab (Electrical etc.) and configure it.":t.map((e=>Z(L.get(e[0])))),e.forEach((e=>{var t;n[e]=(t=e,Array.from(L.entries()).filter((e=>e[1].info.domain===t))).map((e=>Z(L.get(e[0]))))})),Object.keys(n).map((e=>function(e,t){let n=e.slice("_"===e.charAt(0)?1:0);return c().createElement(S.A,{eventKey:e,title:`${n.charAt(0).toUpperCase()}${n.slice(1)}${"string"==typeof t?"":" ("+t.length+")"}`},c().createElement(w.A,{style:{maxHeight:"300px",overflowY:"auto"}},t))}(e,n[e])))}()),c().createElement("div",{style:{paddingLeft:10,paddingTop:10,display:0==Object.keys(_).length?"none":""}},c().createElement(u.A,{container:!0,direction:"column",style:{spacing:5}},c().createElement(u.A,{item:!0},c().createElement("h2",null,_?.title),c().createElement("p",null)),c().createElement(u.A,{item:!0},(0,r.Ay)(_?.htmlDescription))),c().createElement("fieldset",{disabled:!U},c().createElement(a.Ay,{schema:_,validator:s.Ay,uiSchema:x,onChange:(e,t)=>{const n=L.get(e.formData.mac_address);n&&(n._changesMade=!0,n.config=e.formData,T(e.formData)),"root_params_sensorClass"==t&&M(!0)},onSubmit:({formData:e},t)=>{var n;Q("updateSensorData",n=e).then((e=>{if(200!=e.status)throw new Error(e.statusText);I((e=>(e.delete(n.mac_address),new Map(e)))),$({})})),alert("Changes saved")},onError:A("errors"),formData:O},c().createElement("div",{className:q.root},c().createElement(l.A,{type:"submit",color:"primary",variant:"contained"},"Save"),c().createElement(l.A,{variant:"contained",onClick:()=>{var e;e=O.mac_address,L.get(e)._changesMade=!1,L.get(e).config=JSON.parse(JSON.stringify(L.get(e).configCopy)),T(L.get(e).config)}},"Undo"),c().createElement(l.A,{variant:"contained",color:"secondary",onClick:e=>function(e){const t=L.get(e);(!X(t)||window.confirm(`Delete configuration for ${t.info.name}?`))&&function(e){try{Q("removeSensorData",{mac_address:e}).then((e=>{if(200!=e.status)throw new Error(e.statusText)})),I((t=>(t.delete(e),new Map(t)))),$({})}catch{}}(e)}(O.mac_address)},"Delete"))))))}const C=k}}]);
|
|
Binary file
|
|
Binary file
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
const BTSensor = require("../BTSensor");
|
|
2
|
-
|
|
1
|
+
const BTSensor = require("../BTSensor.js");
|
|
2
|
+
let FakeDevice,FakeGATTService,FakeGATTCharacteristic;
|
|
3
|
+
|
|
4
|
+
// Dynamically import FakeBTDevice.js for node<= 20
|
|
5
|
+
import('../development/FakeBTDevice.js')
|
|
6
|
+
.then(module => {
|
|
7
|
+
FakeDevice = module.FakeDevice;
|
|
8
|
+
FakeGATTService= module.FakeGATTService
|
|
9
|
+
FakeGATTCharacteristic=module.FakeGATTCharacteristic
|
|
10
|
+
|
|
11
|
+
})
|
|
12
|
+
.catch(error => {
|
|
13
|
+
console.error('Error loading FakeBTDevice:', error);
|
|
14
|
+
});
|
|
15
|
+
//({FakeDevice,FakeGATTService,FakeGATTCharacteristic }=require( "../development/FakeBTDevice.js"))
|
|
3
16
|
//a1000000650000000000180103440018004800640531ff8000002710000100010000000000000000000100020000ffff00000000000000000000000000000000000000000000000000000000000000000000418b
|
|
4
17
|
//a20000006500000000001801035600040cfb0cfd0cfb0cfaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000300cd00c000befc18fc18fc18fc18fc18fc18976a
|
|
5
18
|
|
|
@@ -29,7 +42,7 @@ function waitForVariable(obj, variableName, interval = 100) {
|
|
|
29
42
|
});
|
|
30
43
|
}
|
|
31
44
|
|
|
32
|
-
class
|
|
45
|
+
class EcoWorthyBW02 extends BTSensor {
|
|
33
46
|
static Domain = BTSensor.SensorDomains.electrical
|
|
34
47
|
|
|
35
48
|
static TX_RX_SERVICE = "0000fff0-0000-1000-8000-00805f9b34fb"
|
|
@@ -45,7 +58,7 @@ class EcoWorthy extends BTSensor {
|
|
|
45
58
|
)]
|
|
46
59
|
)]
|
|
47
60
|
)
|
|
48
|
-
const obj = new
|
|
61
|
+
const obj = new EcoWorthyBW02(device)
|
|
49
62
|
obj.currentProperties={Name:"Fake EcoWorthy", Address:"<mac>"}
|
|
50
63
|
obj.debug=(m)=>{console.log(m)}
|
|
51
64
|
obj.deviceConnect=()=>{}
|
|
@@ -59,9 +72,8 @@ class EcoWorthy extends BTSensor {
|
|
|
59
72
|
static identify(device){
|
|
60
73
|
return null
|
|
61
74
|
}
|
|
62
|
-
static ImageFile = "
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
static ImageFile = "EcoWorthyBW02.webp"
|
|
76
|
+
|
|
65
77
|
async initSchema(){
|
|
66
78
|
super.initSchema()
|
|
67
79
|
this.addDefaultParam("batteryID")
|
|
@@ -157,4 +169,4 @@ async stopListening(){
|
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
}
|
|
160
|
-
module.exports =
|
|
172
|
+
module.exports = EcoWorthyBW02;
|
package/sensor_classes/JBDBMS.js
CHANGED