bt-sensors-plugin-sk 1.2.0-beta.0.0.1.test → 1.2.0-beta.0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.vscode/launch.json +7 -0
  2. package/BTSensor.js +57 -11
  3. package/Queue.js +48 -0
  4. package/README.md +34 -12
  5. package/classLoader.js +38 -0
  6. package/index.js +71 -105
  7. package/package.json +2 -1
  8. package/plugin_defaults.json +99 -10
  9. package/public/893.js +1 -1
  10. package/sensor_classes/ATC.js +5 -3
  11. package/sensor_classes/Aranet/AranetSensor.js +4 -0
  12. package/sensor_classes/Aranet2.js +16 -15
  13. package/sensor_classes/Aranet4.js +23 -17
  14. package/sensor_classes/GoveeH50xx.js +12 -12
  15. package/sensor_classes/GoveeH510x.js +4 -5
  16. package/sensor_classes/IBeacon.js +13 -9
  17. package/sensor_classes/Inkbird.js +4 -2
  18. package/sensor_classes/JBDBMS.js +33 -25
  19. package/sensor_classes/KilovaultHLXPlus.js +33 -16
  20. package/sensor_classes/LancolVoltageMeter.js +4 -3
  21. package/sensor_classes/MopekaTankSensor.js +19 -10
  22. package/sensor_classes/Renogy/RenogySensor.js +1 -1
  23. package/sensor_classes/RenogyBattery.js +15 -13
  24. package/sensor_classes/RenogyRoverClient.js +2 -3
  25. package/sensor_classes/RuuviTag.js +36 -27
  26. package/sensor_classes/ShellySBHT003C.js +19 -22
  27. package/sensor_classes/SwitchBotMeterPlus.js +10 -12
  28. package/sensor_classes/SwitchBotTH.js +13 -16
  29. package/sensor_classes/UltrasonicWindMeter.js +13 -5
  30. package/sensor_classes/VictronACCharger.js +19 -8
  31. package/sensor_classes/VictronBatteryMonitor.js +41 -24
  32. package/sensor_classes/VictronDCDCConverter.js +14 -5
  33. package/sensor_classes/VictronDCEnergyMeter.js +26 -7
  34. package/sensor_classes/VictronGXDevice.js +2 -5
  35. package/sensor_classes/VictronInverter.js +18 -14
  36. package/sensor_classes/VictronInverterRS.js +18 -7
  37. package/sensor_classes/VictronLynxSmartBMS.js +15 -13
  38. package/sensor_classes/VictronOrionXS.js +15 -3
  39. package/sensor_classes/VictronSmartBatteryProtect.js +5 -6
  40. package/sensor_classes/VictronSmartLithium.js +18 -18
  41. package/sensor_classes/VictronSolarCharger.js +31 -18
  42. package/sensor_classes/VictronVEBus.js +2 -5
  43. package/sensor_classes/XiaomiMiBeacon.js +20 -19
  44. package/src/components/PluginConfigurationPanel.js +52 -27
  45. package/testqueue.js +64 -0
@@ -6,25 +6,38 @@ class VictronSolarCharger extends VictronSensor{
6
6
  return await this.identifyMode(device, 0x01)
7
7
  }
8
8
 
9
- async init() {
10
- await super.init()
11
- this.initMetadata()
12
- }
13
- initMetadata() {
14
- this.addMetadatum('chargeState','', 'charge state',
9
+ initSchema() {
10
+ super.initSchema()
11
+ this.addDefaultParam("id")
12
+
13
+ this.addMetadatum('chargeState','', 'charge state',
15
14
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
16
- this.addMetadatum('chargerError','', 'charger error',
17
- (buff)=>{return VC.ChargerError.get(buff.readUInt8(1))})
18
- this.addMetadatum('voltage','V', 'charger battery voltage',
19
- (buff)=>{return this.NaNif(buff.readInt16LE(2),0x7FFF)/100})
20
- this.addMetadatum('current','A','charger battery current',
21
- (buff)=>{return this.NaNif(buff.readInt16LE(4),0x7FFF)/10})
22
- this.addMetadatum('yield','Wh', 'yield today in Watt-hours',
23
- (buff)=>{return this.NaNif(buff.readUInt16LE(6),0xFFFF)*10})
24
- this.addMetadatum('solarPower','W', 'solar power',
25
- (buff)=>{return this.NaNif(buff.readUInt16LE(8),0xFFFF)})
26
- this.addMetadatum('externalDeviceLoad','A', 'external device load',
27
- (buff)=>{return this.NaNif(buff.readUInt16LE(10)&0x1FF,0x1FF)/10})
15
+ .default="electrical.solar.{id}.state"
16
+
17
+ this.addMetadatum('chargerError','', 'charger error',
18
+ (buff)=>{return VC.ChargerError.get(buff.readUInt8(1))})
19
+ .default="electrical.solar.{id}.error"
20
+
21
+ this.addMetadatum('voltage','V', 'charger battery voltage',
22
+ (buff)=>{return this.NaNif(buff.readInt16LE(2),0x7FFF)/100})
23
+ .default="electrical.solar.{id}.dc.voltage"
24
+
25
+ this.addMetadatum('current','A','charger battery current',
26
+ (buff)=>{return this.NaNif(buff.readInt16LE(4),0x7FFF)/10})
27
+ .default="electrical.solar.{id}.dc.current"
28
+
29
+ this.addMetadatum('yield','Wh', 'yield today in Watt-hours',
30
+ (buff)=>{return this.NaNif(buff.readUInt16LE(6),0xFFFF)*10})
31
+ .default="electrical.solar.{id}.yieldToday"
32
+
33
+ this.addMetadatum('solarPower','W', 'solar power',
34
+ (buff)=>{return this.NaNif(buff.readUInt16LE(8),0xFFFF)})
35
+ .default="electrical.solar.{id}.panelPower"
36
+
37
+ this.addMetadatum('externalDeviceLoad','A', 'external device load',
38
+ (buff)=>{return this.NaNif(buff.readUInt16LE(10)&0x1FF,0x1FF)/10})
39
+ .default="electrical.solar.{id}.loadCurrent"
40
+
28
41
  }
29
42
  }
30
43
  module.exports=VictronSolarCharger
@@ -9,11 +9,8 @@ class VictronVEBus extends VictronSensor{
9
9
  return await this.identifyMode(device, 0x0C)
10
10
  }
11
11
 
12
- async init() {
13
- await super.init()
14
- this.initMetadata()
15
- }
16
- initMetadata(){
12
+ initSchema(){
13
+ super.initSchema()
17
14
  this.addMetadatum('chargeState','', 'charge state',
18
15
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
19
16
 
@@ -90,8 +90,6 @@ class XiaomiMiBeacon extends BTSensor{
90
90
  return null
91
91
  }
92
92
  }
93
-
94
- GATTwarning = "WARNING: Xiaomi GATT connections are known to be unreliable on Debian distributions with Bluez 5.55 and up (earlier BlueZ versions are untested). Using GATT on the Xiaomi may put the system Bluetooth stack into an inconsistent state disrupting and disabling other plugin Bluetooth connections. If by some chance you're successful using GATT with the Xiaomi, let the plugin developer know your configuration. Refer to the plugin documentation for getting the Xiamoi bindKey for non-GATT connections and more information on Xiaomi GATT issues."
95
93
 
96
94
  emitValues(buffer){
97
95
  this.emitData("temp", buffer, 0)
@@ -102,15 +100,12 @@ class XiaomiMiBeacon extends BTSensor{
102
100
  return "Xiaomi Inc."
103
101
  }
104
102
  getGATTDescription() {
105
- return this.GATTwarning
103
+ return ""
106
104
  }
105
+
107
106
  initGATTConnection(){
108
- if (!this?.gattWarningDelivered) {
109
- this.debug(this.GATTwarning.toUpperCase())
110
- this.gattWarningDelivered=true
111
- }
112
107
  return new Promise((resolve,reject )=>{
113
- this.device.connect().then(async ()=>{
108
+ this.deviceConnect().then(async ()=>{
114
109
  if (!this.gattServer) {
115
110
  this.gattServer = await this.device.gatt()
116
111
  this.gattService = await this.gattServer.getPrimaryService("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")
@@ -202,30 +197,36 @@ class XiaomiMiBeacon extends BTSensor{
202
197
  }
203
198
 
204
199
  async init(){
200
+
205
201
  await super.init()
206
202
  const data = this.getServiceData(this.constructor.SERVICE_MIBEACON)
207
203
  const frameControl = data[0] + (data[1] << 8)
208
204
  this.deviceID = data[2] + (data[3] << 8)
209
205
  this.isEncrypted = (frameControl >> 3) & 1
210
206
  this.encryptionVersion = frameControl >> 12
207
+ }
208
+ initSchema(){
209
+ super.initSchema()
210
+ this.addDefaultParam("zone")
211
+
211
212
  this.addParameter(
212
213
  "encryptionKey",
213
214
  {
214
215
  title: "encryptionKey (AKA bindKey) for decryption"
215
216
  }
216
217
  )
217
- this.addMetadatum('temp','K', 'temperature',
218
- (buff,offset)=>{return ((buff.readInt16LE(offset))/10) + 273.15})
219
- this.addMetadatum('humidity','ratio', 'humidity',
220
- (buff,offset)=>{return buff.readInt16LE(offset)/1000})
218
+ this.addDefaultPath('temp','environment.temperature')
219
+ .read=(buff,offset)=>{return ((buff.readInt16LE(offset))/10) + 273.15}
221
220
 
222
- this.addMetadatum('batteryStrength', 'ratio', 'sensor battery strength',
223
- (buff,offset)=>{return ((buff.readUInt8(offset))/100)})
224
- this.addMetadatum('voltage', 'V', 'sensor battery voltage',
225
- (buff,offset)=>{return ((buff.readUInt16LE(offset))/1000)})
226
-
227
-
228
- }
221
+ this.addDefaultPath('humidity','environment.humidity')
222
+ .read=(buff,offset)=>{return buff.readInt16LE(offset)/1000}
223
+
224
+ this.addDefaultPath("batteryStrength", "sensors.batteryStrength")
225
+ .read= (buff,offset)=>{return ((buff.readUInt8(offset))/100)}
226
+
227
+ this.addDefaultPath("voltage", "sensors.batteryVoltage")
228
+ .read=(buff,offset)=>{return ((buff.readUInt16LE(offset))/1000)}
229
+ }
229
230
 
230
231
  getName(){
231
232
  const dt = DEVICE_TYPES.get(this.deviceID)
@@ -5,8 +5,7 @@ import {useEffect, useState} from 'react'
5
5
 
6
6
  import {Button, Grid} from '@material-ui/core';
7
7
 
8
-
9
- import { SignalCellular0Bar, SignalCellular1Bar, SignalCellular2Bar, SignalCellular3Bar, SignalCellular4Bar, SignalCellularConnectedNoInternet0Bar } from '@material-ui/icons';
8
+ import { SignalCellular0Bar, SignalCellular1Bar, SignalCellular2Bar, SignalCellular3Bar, SignalCellular4Bar, SignalCellularConnectedNoInternet0Bar } from '@material-ui/icons';
10
9
 
11
10
  const log = (type) => console.log.bind(console, type);
12
11
 
@@ -66,16 +65,16 @@ export default (props) => {
66
65
 
67
66
  async function fetchJSONData(path){
68
67
  console.log(`fetching ${path}`)
69
-
68
+
70
69
  return fetch(`/plugins/bt-sensors-plugin-sk/${path}`, {
71
70
  credentials: 'include'
72
71
  })
73
72
 
74
73
  }
75
74
 
76
- async function getSensorData(){
77
-
78
- const response = await fetchJSONData("sensors")
75
+ async function getSensors(){
76
+ console.log("getSensors")
77
+ const response = await fetchJSONData("getSensors")
79
78
  if (response.status!=200){
80
79
  throw new Error(`Unable get sensor data: ${response.statusText} (${response.status}) `)
81
80
  }
@@ -86,8 +85,8 @@ export default (props) => {
86
85
  }
87
86
 
88
87
  async function getBaseData(){
89
-
90
- const response = await fetchJSONData("base")
88
+ console.log("getBaseData")
89
+ const response = await fetchJSONData("getBaseData")
91
90
  if (response.status!=200){
92
91
  throw new Error(`Unable get base data: ${response.statusText} (${response.status}) `)
93
92
  }
@@ -96,8 +95,8 @@ export default (props) => {
96
95
  return json
97
96
  }
98
97
  async function getProgress(){
99
-
100
- const response = await fetchJSONData("progress")
98
+ console.log("getProgress")
99
+ const response = await fetchJSONData("getProgress")
101
100
  if (response.status!=200){
102
101
  throw new Error(`Unable get progres: ${response.statusText} (${response.status}) `)
103
102
  }
@@ -107,21 +106,26 @@ export default (props) => {
107
106
  }
108
107
 
109
108
  function updateSensorData(data){
110
- sendJSONData("sendSensorData", data).then((response)=>{
109
+ console.log("updateSensorData")
110
+ sendJSONData("updateSensorData", data).then((response)=>{
111
111
  if (response.status != 200) {
112
112
  throw new Error(response.statusText)
113
113
  }
114
- debugger
114
+ sensorMap.get(data.mac_address)._changesMade=false
115
115
  sensorMap.get(data.mac_address).config = data
116
116
 
117
117
  })
118
- }
118
+ }
119
119
 
120
120
  function undoChanges(mac) {
121
+ console.log("undoChanges")
122
+ sensorMap.get(mac)._changesMade = false
121
123
  setSensorData( sensorMap.get(mac).config )
122
124
  }
123
125
 
124
126
  function removeSensorData(mac){
127
+ console.log("removeSensorData")
128
+
125
129
  try{
126
130
 
127
131
  sendJSONData("removeSensorData", {mac_address:mac} ).then((response)=>{
@@ -142,7 +146,9 @@ export default (props) => {
142
146
 
143
147
 
144
148
  function updateBaseData(data){
145
- sendJSONData("sendBaseData", data).then( (response )=>{
149
+ console.log("updateBaseData")
150
+
151
+ sendJSONData("updateBaseData", data).then( (response )=>{
146
152
  if (response.status != 200) {
147
153
  setError(new Error(`Unable to update base data: ${response.statusText} (${response.status})`))
148
154
  } else {
@@ -160,7 +166,7 @@ export default (props) => {
160
166
  function refreshSensors(){
161
167
  console.log('refreshing sensor map')
162
168
 
163
- getSensorData().then((sensors)=>{
169
+ getSensors().then((sensors)=>{
164
170
  setSensorMap (new Map(sensors.map((sensor)=>[sensor.info.mac,sensor])));
165
171
  })
166
172
  .catch((e)=>{
@@ -170,14 +176,19 @@ export default (props) => {
170
176
 
171
177
 
172
178
  useEffect(()=>{
173
-
174
- fetchJSONData("sendPluginState").then( async (response)=> {
179
+ console.log("useEffect([])")
180
+ fetchJSONData("getPluginState").then( async (response)=> {
181
+ if (response.status==404) {
182
+ setPluginState("unknown")
183
+ throw new Error("unable to get plugin state")
184
+ }
175
185
  const json = await response.json()
176
186
  setPluginState(json.state)
177
187
  console.log("Setting up eventsource")
178
188
  const eventSource = new EventSource("/plugins/bt-sensors-plugin-sk/sse")
179
189
 
180
190
  eventSource.addEventListener("newsensor", (event) => {
191
+ console.log("newsensor")
181
192
  let json = JSON.parse(event.data)
182
193
 
183
194
  if (!_sensorMap.has(json.info.mac)) {
@@ -187,7 +198,9 @@ export default (props) => {
187
198
  });
188
199
 
189
200
  eventSource.addEventListener("sensorchanged", (event) => {
190
- let json = JSON.parse(event.data)
201
+ let json = JSON.parse(event.data)
202
+ console.log("sensorchanged")
203
+ console.log(json)
191
204
 
192
205
  if (_sensorMap.has(json.mac)) {
193
206
  let sensor = _sensorMap.get(json.mac)
@@ -196,12 +209,14 @@ export default (props) => {
196
209
  }
197
210
  });
198
211
  eventSource.addEventListener("progress", (event) => {
212
+ console.log("progress")
199
213
  const json = JSON.parse(event.data)
200
214
  setProgress(json)
201
215
  console.log(json)
202
216
  });
203
217
 
204
218
  eventSource.addEventListener("pluginstate", (event) => {
219
+ console.log("pluginstate")
205
220
  const json = JSON.parse(event.data)
206
221
  setPluginState(json.state)
207
222
  });
@@ -215,6 +230,7 @@ export default (props) => {
215
230
  },[])
216
231
 
217
232
  useEffect(()=>{
233
+ console.log("useEffect([pluginState])")
218
234
  if (pluginState=="started"){
219
235
  refreshSensors()
220
236
 
@@ -239,9 +255,7 @@ useEffect(()=>{
239
255
 
240
256
  },[pluginState])
241
257
 
242
- useEffect(()=>{
243
- console.log(error)
244
- },[error])
258
+
245
259
 
246
260
  function confirmDelete(mac){
247
261
  const result = window.confirm(`Delete configuration for ${mac}?`)
@@ -269,6 +283,8 @@ function signalStrengthIcon(sensor){
269
283
 
270
284
  }
271
285
  useEffect(()=>{
286
+ console.log("useEffect([sensorMap])")
287
+
272
288
  _sensorMap = sensorMap
273
289
 
274
290
  setSensorList(
@@ -289,7 +305,7 @@ useEffect(()=>{
289
305
  }
290
306
  }>
291
307
  <div class="d-flex justify-content-between align-items-center" style={hasConfig?{fontWeight:"normal"}:{fontStyle:"italic"}}>
292
- {`${sensor.info.name} MAC: ${sensor.info.mac} RSSI: ${ifNullNaN(sensor.info.RSSI)}` }
308
+ {`${sensor._changesMade?"*":""}${sensor.info.name} MAC: ${sensor.info.mac} RSSI: ${ifNullNaN(sensor.info.RSSI)}` }
293
309
  <div class="d-flex justify-content-between ">
294
310
  {
295
311
  signalStrengthIcon(sensor)
@@ -308,12 +324,13 @@ useEffect(()=>{
308
324
  }
309
325
 
310
326
 
311
- if (pluginState=="stopped")
312
- return (<h1 >Enable plugin to see configuration</h1>)
327
+ if (pluginState=="stopped" || pluginState=="unknown")
328
+ return (<h1 >Enable plugin to see configuration (if plugin is Enabled and you're seeing this message, restart SignalK)</h1>)
313
329
  else
314
330
  return(
331
+
315
332
  <div>
316
- {error?<h2 style="color: red;">{error.message}</h2>:""}
333
+ {error?<h2 style="color: red;">{error}</h2>:""}
317
334
  <Form
318
335
  schema={baseSchema}
319
336
  validator={validator}
@@ -329,7 +346,8 @@ useEffect(()=>{
329
346
  now={progress.progress}
330
347
  />:""
331
348
  }
332
- <h2>{`${sensorMap.size>0?" Bluetooth Devices click to configure":""}`}</h2>
349
+ <h2>{`${sensorMap.size>0?"Bluetooth Devices - Select to configure" :""}`}</h2>
350
+ <h2>{`${sensorMap.size>0?"(* = sensor has unsaved changes)" :""}`}</h2>
333
351
  <p></p>
334
352
  <div style={{paddingBottom: 20}} class="d-flex flex-wrap justify-content-start align-items-start">
335
353
  <ListGroup style={{ maxHeight: '300px', overflowY: 'auto' }}>
@@ -340,7 +358,14 @@ useEffect(()=>{
340
358
  schema={schema}
341
359
  validator={validator}
342
360
  uiSchema={uiSchema}
343
- onChange={(e) => setSensorData(e.formData)}
361
+ onChange={(e) => {
362
+ const s = sensorMap.get(e.formData.mac_address)
363
+ s._changesMade=true
364
+ //s.config = e.formData;
365
+
366
+ setSensorData(e.formData)
367
+ }
368
+ }
344
369
  onSubmit={({ formData }, e) => {
345
370
  console.log(formData)
346
371
  updateSensorData(formData)
package/testqueue.js ADDED
@@ -0,0 +1,64 @@
1
+
2
+
3
+ import {createBluetooth} from 'node-ble'
4
+
5
+ import {AutoQueue} from "./Queue.js"
6
+ import {Variant} from 'dbus-next'
7
+
8
+ const {bluetooth, destroy} = createBluetooth()
9
+ const connectQueue = new AutoQueue()
10
+ const adapter = await bluetooth.getAdapter("hci0")
11
+ await adapter.startDiscovery()
12
+
13
+ function deviceConnect(mac) {
14
+
15
+ /* CAUTION: HACK AHEAD
16
+
17
+ Bluez for some cockamamie reason (It's 2025 for chrissake.
18
+ BLUETOOTH ISN'T JUST FOR DESKTOPS ANYMORE, BLUEZ DEVS!)
19
+ SUSPENDS scanning while connected to a device.
20
+
21
+ The next line of code gives the scanner a kick in the arse,
22
+ starting it up again so, I dunno, another device might be able
23
+ to connect and sensor classes could maybe get beacon updates.
24
+
25
+ You know, the little things.
26
+ */
27
+ adapter.waitDevice(mac,(30000)).then((device) =>{
28
+
29
+ return connectQueue.enqueue( async ()=>{
30
+ console.log("Connecting to "+mac)
31
+ try {await device.connect()} catch {(e)=>console.log(e)}
32
+ try {
33
+ console.log("Connected to "+mac)
34
+ console.log("Stopping discovery for "+mac)
35
+
36
+ await adapter.helper.callMethod('StopDiscovery')
37
+ console.log("Discovery stopped for "+mac)
38
+ await adapter.helper.callMethod('SetDiscoveryFilter', {
39
+ Transport: new Variant('s', "le")
40
+ })
41
+ console.log("Starting discovery for "+mac)
42
+ await adapter.helper.callMethod('StartDiscovery')
43
+ console.log("Discovery started for "+mac)
44
+
45
+ } catch (e){
46
+ //probably ignorable error. probably.
47
+ console.log(e)
48
+ }
49
+ })
50
+ })
51
+ /* END HACK*/
52
+ }
53
+ setInterval( ()=>{
54
+ deviceConnect("D1:06:00:C6:16:4A")
55
+ }, 5000)
56
+ setInterval( ()=>{
57
+
58
+ for (const mac of (["D1:06:01:46:49:39","A4:C1:38:3E:7E:94"])){
59
+ try { deviceConnect(mac) } catch { (e)=>console.log(e) }
60
+ }
61
+ }, 10000)
62
+
63
+
64
+