bt-sensors-plugin-sk 1.1.0-beta.1 → 1.1.0-beta.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BTSensor.js CHANGED
@@ -10,10 +10,15 @@ const BTCompanies = require('./bt_co.json')
10
10
  */
11
11
 
12
12
  const BTCompanyMap=new Map()
13
+
13
14
  BTCompanies.company_identifiers.forEach( (v) =>{
14
15
  BTCompanyMap.set(v.value, v.name)
15
16
  })
16
17
 
18
+ /**
19
+ * https://www.intuitibits.com/2016/03/23/dbm-to-percent-conversion/
20
+ */
21
+
17
22
  function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
18
23
  const nominal_rssi=(perfect_rssi - worst_rssi);
19
24
  var signal_quality =
@@ -31,6 +36,7 @@ function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
31
36
  }
32
37
  return Math.ceil(signal_quality);
33
38
  }
39
+
34
40
  class BTSensor extends EventEmitter {
35
41
  static metadata=new Map()
36
42
  constructor(device, config={}, gattConfig={}) {
@@ -84,35 +90,58 @@ class BTSensor extends EventEmitter {
84
90
  }
85
91
  }
86
92
 
87
- static getMetadata(){
88
- return this.metadata
89
- }
90
-
91
- static {
92
- var md = this.addMetadatum("name", "string","Name of sensor" )
93
- md.isParam=true
94
-
95
-
93
+
94
+ static async getPropsProxy(device){
95
+
96
+ if (!device._propsProxy) {
97
+ const objectProxy = await device.helper.dbus.getProxyObject(device.helper.service, device.helper.object)
98
+ device._propsProxy = await objectProxy.getInterface('org.freedesktop.DBus.Properties')
99
+ }
100
+ return device._propsProxy
101
+ }
102
+ static async getDeviceProps(device, propNames=[]){
103
+ const _propsProxy = await this.getPropsProxy(device)
104
+ const rawProps = await _propsProxy.GetAll(device.helper.iface)
105
+ const props = {}
106
+ for (const propKey in rawProps) {
107
+ if (propNames.length==0 || propNames.indexOf(propKey)>=0)
108
+ props[propKey] = rawProps[propKey].value
109
+ }
110
+ return props
111
+ }
112
+ static async getDeviceProp(device, prop){
113
+ const _propsProxy = await this.getPropsProxy(device)
114
+ try{
115
+ const rawProps = await _propsProxy.Get(device.helper.iface,prop)
116
+ return rawProps?.value
117
+ }
118
+ catch(e){
119
+ return null //Property $prop (probably) doesn't exist in $device
120
+ }
121
+ }
96
122
 
97
- }
98
- static addMetadatum(tag, ...args){
99
- var metadatum = new this.Metadatum(tag, ...args)
100
- this.getMetadata().set(tag,metadatum)
101
- return metadatum
102
- }
103
123
 
104
- emitData(tag, buffer, ...args){
105
- this.emit(tag, this.getMetadatum(tag).read(buffer, ...args))
124
+ static async getManufacturerID(device){
125
+ const md = await this.getDeviceProp(device,'ManufacturerData')
126
+ if (!md) return null
127
+ const keys = Object.keys(md)
128
+ if (keys && keys.length>0){
129
+ return parseInt(keys[0])
130
+ }
131
+ return null
106
132
  }
133
+ static NaNif(v1,v2) { return (v1==v2)?NaN:v1 }
107
134
 
108
135
  async init(){
136
+ var md = this.addMetadatum("name", "string","Name of sensor" )
137
+ md.isParam=true
109
138
  this.currentProperties = await this.constructor.getDeviceProps(this.device)
110
139
  this.addMetadatum("RSSI","db","Signal strength in db")
111
140
  this.getMetadatum("RSSI").default=`sensors.${this.getMacAddress().replaceAll(':', '')}.rssi`
112
141
  this.getMetadatum("RSSI").read=()=>{return this.getRSSI()}
113
142
  this.getMetadatum("RSSI").read.bind(this)
114
- if (this.canUseGATT()) {
115
- var md = this.addMetadatum("useGATT", "boolean", "Use GATT connection")
143
+ if (this.hasGATT()) {
144
+ md = this.addMetadatum("useGATT", "boolean", "Use GATT connection")
116
145
  md.type="boolean"
117
146
  md.isParam=true
118
147
  md.isGATT=true
@@ -123,21 +152,9 @@ class BTSensor extends EventEmitter {
123
152
  md.isGATT=true
124
153
  }
125
154
  }
126
-
127
- static async getManufacturerID(device){
128
- const md = await this.getDeviceProp(device,'ManufacturerData')
129
- if (!md) return null
130
- const keys = Object.keys(md)
131
- if (keys && keys.length>0){
132
- return parseInt(keys[0])
133
- }
134
- return null
135
- }
136
- static NaNif(v1,v2) { return (v1==v2)?NaN:v1 }
137
155
 
138
156
  NaNif(v1,v2) { return this.constructor.NaNif(v1,v2) }
139
157
 
140
-
141
158
  addMetadatum(tag, ...args){
142
159
  var metadatum = new this.Metadatum(tag, ...args)
143
160
  this.getMetadata().set(tag, metadatum)
@@ -193,6 +210,8 @@ class BTSensor extends EventEmitter {
193
210
  return bars
194
211
 
195
212
  }
213
+
214
+
196
215
  getDescription(){
197
216
  return `${this.getName()} from ${this.getManufacturer()}`
198
217
  }
@@ -213,7 +232,7 @@ class BTSensor extends EventEmitter {
213
232
  getRSSI(){
214
233
  return this.currentProperties?.RSSI??NaN
215
234
  }
216
- canUseGATT(){
235
+ hasGATT(){
217
236
  return false
218
237
  }
219
238
  usingGATT(){
@@ -226,35 +245,10 @@ class BTSensor extends EventEmitter {
226
245
  return obj
227
246
 
228
247
  }
229
-
230
- static async getPropsProxy(device){
231
-
232
- const objectProxy = await device.helper.dbus.getProxyObject(device.helper.service, device.helper.object)
233
- if (!device._propsProxy)
234
- device._propsProxy = await objectProxy.getInterface('org.freedesktop.DBus.Properties')
235
- return device._propsProxy
236
- }
237
- static async getDeviceProps(device, propNames=[]){
238
- const _propsProxy = await this.getPropsProxy(device)
239
- const rawProps = await _propsProxy.GetAll(device.helper.iface)
240
- const props = {}
241
- for (const propKey in rawProps) {
242
- if (propNames.length==0 || propNames.indexOf(propKey)>=0)
243
- props[propKey] = rawProps[propKey].value
244
- }
245
- return props
248
+ emitData(tag, buffer, ...args){
249
+ this.emit(tag, this.getMetadatum(tag).read(buffer, ...args))
246
250
  }
247
- static async getDeviceProp(device, prop){
248
- const _propsProxy = await this.getPropsProxy(device)
249
- try{
250
- const rawProps = await _propsProxy.Get(device.helper.iface,prop)
251
- return rawProps?.value
252
- }
253
- catch(e){
254
- return null //Property $prop (probably) doesn't exist in $device
255
- }
256
- }
257
-
251
+
258
252
  /**
259
253
  * callback function on device properties changing
260
254
  */
@@ -294,9 +288,13 @@ class BTSensor extends EventEmitter {
294
288
  const id = this.getManufacturerID()
295
289
  return (id==null)?"Unknown manufacturer":BTCompanyMap.get(parseInt(id))
296
290
  }
297
- getManufacturerData(key){
291
+
292
+ getManufacturerData(key=null){
298
293
  if (this.currentProperties.ManufacturerData)
299
- return this.valueIfVariant (this.currentProperties.ManufacturerData[key])
294
+ if (key)
295
+ return this.valueIfVariant (this.currentProperties.ManufacturerData[key])
296
+ else
297
+ return(this.valueIfVariant (this.currentProperties.ManufacturerData))
300
298
  else
301
299
  return null
302
300
  }
package/README.md CHANGED
@@ -5,49 +5,45 @@
5
5
 
6
6
  BT Sensors Plugin for Signalk is a lightweight BLE (Bluetooth Low Energy) framework for connecting to Bluetooth sensors on your boat and sending deltas to Signalk paths with the values the sensors reports. <br>
7
7
 
8
- A typical use case is a Bluetooth thermometer like the Xiaomi LYWSD03MMC, an inexpensive Bluetooth thermometer that runs on a 3V watch battery that can report the current temperature and humidity in your refrigerator or cabin or wherever you want to stick it (no judgement.) <br>
8
+ The Plugin currently supports every documented Victron device (AC Charger, Battery Monitor, DC-DC Converter, DC Energy Meter, GX Device, Inverter, Inverter RS, Lynx Smart BMS, Orion XS, Smart Battery Protect, Smart Lithium and VE Bus), Xiaomi devices, [ATC devices](https://github.com/atc1441/ATC_MiThermometer), RuuviTags and Inkbird thermometers.
9
9
 
10
- The reported temperature can then be displayed on a Signalk app like Kip or, with appropiate mapping to NMEA-2000, a NMEA 2000 Multi-function display.
10
+ A typical use case is a Bluetooth thermometer like the Xiaomi LYWSD03MMC, an inexpensive Bluetooth thermometer that runs on a 3V watch battery that can report the current temperature and humidity in your refrigerator or cabin or wherever you want to stick it (no judgement.) <br>
11
11
 
12
- The Plugin currently supports the Xiaomi LYWSD03MMC, [ATC](https://github.com/atc1441/ATC_MiThermometer) flashed LYWSD03MMCs, Victron SmartShunt and the Inkbird IBS-TH2 thermometer.
12
+ The reported temperature can then be displayed on a Signalk app like Kip, WilhelmSK or, with appropiate mapping to NMEA-2000, a NMEA 2000 Multi-function display.
13
13
 
14
- Sounds like meager offerings but it's pretty easy to write and deploy your own sensor class for any currently unsupported sensor. More on that in [the development section](#development).
14
+ It's pretty easy to write and deploy your own sensor class for any currently unsupported sensor. More on that in [the development README](./sensor_classes/DEVELOPMENT.md).
15
15
 
16
16
  ## WHO IS IT FOR
17
17
 
18
- Signalk users with a Linux boat-puter (Windows and MacOS are NOT supported) and Bluetooth sensors they'd like to integrate into their Signalk datastream.
19
-
20
- ## ALTERNATIVES
21
-
22
- An [MQTT](https://mqtt.org/) server with an appropriate SK client plugin. There are several MQTT plugin clients in the Signalk appstore.
23
-
24
- Advantages of this plugin over an MQTT server and client plugin are:
25
- * simplicity of setup
26
- * reduced overhead on server
27
- * one less piece of software to maintain on your boat-puter
28
- * ease of rolling your own sensor classes
29
-
30
- The key advantages of an MQTT setup is comprehensive support for BT devices and non-Linux platforms.
18
+ Signalk users with a Linux boat-puter (Windows and MacOS are NOT currently supported) and Bluetooth sensors they'd like to integrate into their Signalk datastream.
31
19
 
32
20
  ## REQUIREMENTS
33
21
 
34
- * A Linux Signalk boat-puter with System-D (NOTE: Most Linux installations support System-D)
22
+ * A Linux Signalk boat-puter with System-D (NOTE: Most Linux installations support System-D)
35
23
  * A Bluetooth adapter
36
24
  * [Bluez](https://www.bluez.org) installed
37
25
  (Go here for [Snap installation instructions](https://snapcraft.io/bluez))
38
26
  * [Node-ble](https://www.npmjs.com/package/node-ble) (installs with the plugin)
39
- * [utilities-sk](https://github.com/naugehyde/utilities-sk)
40
27
 
41
28
  ## INSTALLATION
29
+
30
+ NOTE: If you're running the 1.0.3 release, you will have to reconfigure your devices.<br>
31
+
42
32
  ### Signalk Appstore
43
- This will be the recommended installation when the code is ready for wider sharing. In the meantime, use the platform-specific developer install instructions below.
33
+ The plugin is currently available in the Signalk Appstore. <br>
34
+
35
+ ### NPM
36
+
37
+ See https://www.npmjs.com/package/bt-sensors-plugin-sk/v/1.1.0-beta.2 for more information.
44
38
 
45
39
  ### Linux
46
- From a command prompt:<br>
40
+
41
+ If you want to install directly from source (this is mostly of interest to custom sensor class developers) execute the following from a command prompt:<br>
47
42
 
48
43
  <pre> cd ~/[some_dir]
49
44
  git clone https://github.com/naugehyde/bt-sensors-plugin-sk
50
45
  cd bt-sensors-plugin-sk
46
+ git switch '1.1.0'
51
47
  npm i
52
48
  [sudo] npm link
53
49
  cd [signalk_home]
@@ -62,37 +58,38 @@ Finally, restart SK. Plugin should appear in your server plugins list.<br>
62
58
 
63
59
  After installing and restarting Signalk you should see a "BT Sensors Plugin" option in the Signalk->Server->Plugin Config page.<br><br>
64
60
 
65
- <img width="1135" alt="Screenshot 2024-09-01 at 8 35 34 PM" src="https://github.com/user-attachments/assets/7e5b7d87-92e3-4fcd-8971-95ef6799c62f"><br><br>
66
-
67
- On initial configuration, wait 45 seconds (by default) for the Bluetooth device to complete its scan of nearby devices. Until the scan is complete, the Sensors section will be disabled. When the scan is complete your screen should look something like this:<br><br>
61
+ <img width="1130" alt="Screenshot 2024-10-13 at 6 50 09 PM" src="https://github.com/user-attachments/assets/4180504e-1ca8-48af-babf-899486fb0a24"><br><br>
68
62
 
69
- <img width="378" alt="Screenshot 2024-08-30 at 11 21 39 AM" src="https://github.com/user-attachments/assets/6e24b880-7ee7-4deb-9608-fb42d9db74f7"><br><br>
63
+ On initial configuration, wait for your Bluetooth adapter to scan devices. The plugin will scan for new devices at whatever you set the "scan for new devices interval" value to. <br><br>
70
64
 
71
- > TIP: If after 45 seconds (or whatever your Initial Scan Timeout is set to) you don't see "Scan completed. Found x Bluetooth devices." atop the config screen and the Sensors section is still disabled, close and re-open the config screen to refresh the screen. The config screen isn't as <i>reactive</i> as it oughtta be.<br><br>
65
+ > TIP: Close and re-open the config screen to refresh the screen. The config screen isn't as <i>reactive</i> as it oughtta be.<br><br>
72
66
 
73
- Then press the + button to add a sensor. Your screen should look like this:<br><br>
67
+ Then press the + button to add a sensor. Your screen should look something like this:<br><br>
68
+ <img width="1122" alt="Screenshot 2024-10-13 at 6 52 52 PM" src="https://github.com/user-attachments/assets/0487b8d0-4bc0-4358-85c6-a507bc3c97d2">
74
69
 
75
- <img width="1116" alt="Screenshot 2024-09-01 at 8 47 53 PM" src="https://github.com/user-attachments/assets/6fdab5cc-ab01-4441-a88f-2753a49aadbd">
76
70
  <br><br>
77
71
 
78
- Then select the sensor you want to connect to from the drop down.<br>
72
+ Select the sensor you want to connect to from the drop down.<br>
79
73
 
80
- >TIP: If you need to rescan, disable and re-enable the plugin or restart Signalk.<br><br>
74
+ If you don't see your device and you know that it's on and nearby to the server, it may not be currently supported. But fear not, you can add custom sensor classes yourself. (Check out [the development section](#development).). <br><br>
81
75
 
82
- <img width="1109" alt="Screenshot 2024-09-01 at 8 48 04 PM" src="https://github.com/user-attachments/assets/264a8737-8c0e-4b34-a737-e0d834b54b99">
83
- <br><br>
76
+ Now it's a simple matter of associating the data emitted by the sensor with the Signalk path you want it to update. (Also, you can name your sensor so when it appears in logs its easy to recognize.) <br><br>
77
+
78
+ <img width="1125" alt="Screenshot 2024-10-13 at 9 30 53 PM" src="https://github.com/user-attachments/assets/5284539e-d5ee-488f-bbab-901258eb1c0b">
79
+
80
+ The plugin doesn't need for Signalk to restart after submitting your config but restart if that makes you more comfortable. <br><br>
84
81
 
85
- Then select the class of bluetooth device. The class should have a similar name to the device. If you don't see the class for your device, you can develop your own (check out [the development section](#development).). <br><br>
82
+ ### ADVERTISED DATA
86
83
 
87
- <img width="1104" alt="Screenshot 2024-09-01 at 8 48 19 PM" src="https://github.com/user-attachments/assets/b55ac065-6c57-48eb-8321-e40138d1ca99"><br><br>
84
+ Most supported Bluetooth sensors broadcast (or advertise) their device's data without an explicit connection. Some devices broadcast their data encrypted. Supported devices with encrypted advertised data will allow you to input the encryption key as a config parameter. NOTE: the encryption key is currently stored on the server in plain text.
88
85
 
89
- Then it's a simple matter of associating the data emitted by the sensor with the Signalk path you want it to update. In the example pictured here there are three data points (temperature, humidity and sensor voltage). Other classes will expose different data.
86
+ ### GATT CONNECTIONS
90
87
 
91
- <img width="1107" alt="Screenshot 2024-09-01 at 8 48 38 PM" src="https://github.com/user-attachments/assets/cb5aeb01-2e9a-44b3-ac3a-401cd7d6c3e7"><br><br>
88
+ If you see something like this on your device config:
92
89
 
93
- Remember to hit the submit button.
90
+ <img width="727" alt="Screenshot 2024-10-13 at 9 42 26 PM" src="https://github.com/user-attachments/assets/65fe1aa7-99a5-41ac-85dc-cfde00e95932">
94
91
 
95
- The plugin doesn't need for Signalk to restart but restart if that makes you more comfortable.
92
+ You have the option of making a GATT Connection to your device. A GATT Connection is energy-inefficient but serves data from your device in more or less real-time. To learn more about GATT check out: https://learn.adafruit.com/introduction-to-bluetooth-low-energy/gatt
96
93
 
97
94
  ## NOW WHAT?
98
95
 
@@ -100,161 +97,8 @@ You should see data appear in your data browser. Here's a screenshot of Signalk
100
97
 
101
98
  <img width="1142" alt="Screenshot 2024-09-01 at 9 14 27 PM" src="https://github.com/user-attachments/assets/80abbc1c-e01e-4908-aa1a-eec83679cd7c"><br><br>
102
99
 
103
- You can now take the data and display it using Kip, or route it to NMEA-2K and display it on a N2K MFD, or use it to create and respond to alerts in Node-Red. Life is good. So good.
100
+ You can now take the data and display it using Kip, WilhelmSK or route it to NMEA-2K and display it on a N2K MFD, or use it to create and respond to alerts in Node-Red. Isn't life grand?
104
101
 
105
- # <a name="development"></a>BLUETOOTH SENSOR CLASS DEVELOPMENT
106
-
107
- The goal of this project is to support as many mariner-useful sensors as possible. If there's anything we can do to make sensor class development easier, please let us know.<br><br>
108
-
109
- ## REQUIREMENTS
110
-
111
- * programming knowledge, preferably class programming in Nodejs
112
- * familiarity with [Node-ble](https://www.npmjs.com/package/node-ble)
113
- * ideally, the device manufacturer's specification for their bluetooth API
114
- * failing the above, a willingness to hack or at the very least google aggressively
115
-
116
- ## PROGRAMMING PROCESS
117
-
118
- ### Discovery
119
-
120
- You'll first need to know what data your sensor produces and the means by which it provides data (GATT Server or advertisement) and the details thereof.<br><br>
121
-
122
- The first approach is to see if the device manufacturer has documented this. Not all do. Don't worry if you can't find OEM docs, it's likely someone on the internet has figured out your device's whys and wherefores for you already. Google that thang. <br><br>
123
-
124
- If you're still coming up empty, there are any number of tools you can use to examine the Bluetooth data stream of your device. <br><br>
125
-
126
- * bluetoothctl (installed with Bluez on Linux)
127
- * [NRF Connect](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=en_US)
128
-
129
- With these tools you can see what data your device advertises, and what data it provides via a connection to its GATT Server. <br><br>
130
-
131
- ### Coding
132
-
133
- #### Get the code
134
-
135
- To get the code you'll first need to clone this repository then create a branch. That's git talk. Google it if you don't know what that means.<br>
136
-
137
- Once you've done that you're ready for...
138
-
139
- #### Actual coding
140
-
141
- Below is a simple Device class for the Xiaomi thermometer with stock firmware. The code demonstrates the core responsibilities of a Bluetooth sensor device class in the BT-Sensor-plugin's framework:
142
-
143
- * resides in the sensor_classes subdirectory
144
- * extends the BTSensor class
145
- * provides metadata for the device's various data points (the static metadata class member)
146
- * overrides the BTSensor::connect() and disconnect() methods
147
- * emits values for each data point
148
- * exports the class
149
-
150
- <pre>const BTSensor = require("../BTSensor");
151
-
152
- class LYWSD03MMC extends BTSensor{
153
-
154
- static needsScannerOn(){
155
- return false
156
- }
157
- static metadata = new Map()
158
- .set('temp',{unit:'K', description: 'temperature'})
159
- .set('humidity',{unit:'ratio', description: 'humidity'})
160
- .set('voltage',{unit:'V', description: 'sensor battery voltage'})
161
-
162
- constructor(device){
163
- super(device)
164
- }
165
-
166
- emitValues(buffer){
167
- this.emit("temp",((buffer.readInt16LE(0))/100) + 273.1);
168
- this.emit("humidity",buffer.readUInt8(2)/100);
169
- this.emit("voltage",buffer.readUInt16LE(3)/1000);
170
- }
171
-
172
- async connect() {
173
- await this.device.connect()
174
- var gattServer = await this.device.gatt()
175
- var gattService = await gattServer.getPrimaryService("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")
176
- var gattCharacteristic = await gattService.getCharacteristic("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6")
177
- this.emitValues(await gattCharacteristic.readValue())
178
- await gattCharacteristic.startNotifications();
179
- gattCharacteristic.on('valuechanged', buffer => {
180
- this.emitValues(buffer)
181
- })
182
- }
183
- async disconnect(){
184
- super.disconnect()
185
- await this.device.disconnect()
186
- }
187
- }
188
- module.exports=LYWSD03MMC</pre>
189
-
190
- Most of the work is done in the connect() method. In this case, the connect() method creates a connection to the device:
191
- <pre>await this.device.connect()</pre>
192
- Then it gets the device's gattServer and primary service:
193
- <pre>
194
- var gattServer = await this.device.gatt()
195
- var gattService = await gattServer.getPrimaryService("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")
196
- </pre>
197
- Then, it requests the device's "characteristic" that will send us data.
198
- <pre>var gattCharacteristic = await gattService.getCharacteristic("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6")</pre>
199
- Then it asks the characteristic for notifications when its value changes.
200
- <pre>await gattCharacteristic.startNotifications();</pre>
201
- Then most importantly it emits the values when the data has changed:
202
- <pre>gattCharacteristic.on('valuechanged', buffer => {
203
- this.emitValues(buffer)
204
- })</pre>
205
- In this implementation, the emitValues member function does the work of parsing the buffer and emitting the values.
206
- <pre>
207
- emitValues(buffer){
208
- this.emit("temp",((buffer.readInt16LE(0))/100) + 273.1);
209
- this.emit("humidity",buffer.readUInt8(2)/100);
210
- this.emit("voltage",buffer.readUInt16LE(3)/1000);
211
- }
212
- </pre>
213
- NOTE: If you guessed that the plugin listens to changes to device objects and then publishes the deltas, you guessed right.
214
-
215
- ### All that said
216
- The problem with Gatt Server devices is they stay connected and eat up a lot of energy, draining your device's batteries. You can deactivate the device from the config screen when it's not in use or in this case you can flash the device with custom firmware that changes the device to a broadcast device that advertises its data obviating the need for a battery-draining connection. In the case of Xiaomi LYWSD03MMC you can flash it with some very useful software called [ATC](https://github.com/atc1441/ATC_MiThermometer?tab=readme-ov-file) </pre>
217
-
218
- Below is an example of a BTSensor subclass that uses the advertising protocol to get the data from a flashed Xiaomi thermometer.
219
-
220
- <pre>const BTSensor = require("../BTSensor");
221
- const LYWSD03MMC = require('./LYWSD03MMC.js')
222
- class ATC extends BTSensor{
223
-
224
- constructor(device){
225
- super(device)
226
- }
227
-
228
- static metadata = LYWSD03MMC.metadata
229
-
230
- connect() {
231
- const cb = async (propertiesChanged) => {
232
- try{
233
- this.device.getServiceData().then((data)=>{
234
- //TBD Check if the service ID is universal across ATC variants
235
- const buff=data['0000181a-0000-1000-8000-00805f9b34fb'];
236
- this.emit("temp",((buff.readInt16LE(6))/100) + 273.1);
237
- this.emit("humidity",buff.readUInt16LE(8)/10000);
238
- this.emit("voltage",buff.readUInt16LE(10)/1000);
239
- })
240
- }
241
- catch (error) {
242
- throw new Error(`Unable to read data from ${util.inspect(device)}: ${error}` )
243
- }
244
- }
245
- cb();
246
- this.device.helper.on('PropertiesChanged', cb)
247
- }
248
- }
249
- module.exports=ATC</pre>
250
-
251
- The big difference here is in the connect() method. All it does is wait on propertiesChanged and when that event occurs, the device object parses the buffer and emits the data. NOTE: Both classes have the same metadata, so the ATC class "borrows" the metadata from the LYWSD03MMC class.<br>
252
-
253
- ## LET US KNOW
254
-
255
- When you're done working on your class and satisified that it's functioning properly, commit and request a merge (more git talk).<br>
256
-
257
- We love to see new sensor classes!
258
102
 
259
103
 
260
104
 
package/bt_co.json CHANGED
@@ -14517,4 +14517,4 @@
14517
14517
  "name": "Ericsson AB"
14518
14518
  }
14519
14519
  ]
14520
- }
14520
+ }
package/index.js CHANGED
@@ -30,7 +30,7 @@ class MissingSensor {
30
30
  this.mac_address = config.mac_address
31
31
 
32
32
  }
33
- canUseGATT(){
33
+ hasGATT(){
34
34
  return false
35
35
  }
36
36
  getMetadata(){
@@ -171,7 +171,7 @@ module.exports = function (app) {
171
171
  },
172
172
  discoveryInterval: {title: "Scan for new devices interval (in seconds-- 0 for no new device scanning)",
173
173
  type: "integer",
174
- default: 60,
174
+ default: 10,
175
175
  minimum: 0,
176
176
  multipleOf: 10
177
177
  },
@@ -257,7 +257,7 @@ module.exports = function (app) {
257
257
  oneOf.properties.params.properties[tag]=metadatum.asJSONSchema()
258
258
  })
259
259
 
260
- if (sensor.canUseGATT()){
260
+ if (sensor.hasGATT()){
261
261
 
262
262
  oneOf.properties.gattParams={
263
263
  title:`GATT Specific device parameters`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bt-sensors-plugin-sk",
3
- "version": "1.1.0-beta.1",
4
- "description": "Bluetooth Sensors for Signalk - Beta 20241012",
3
+ "version": "1.1.0-beta.2.1",
4
+ "description": "Bluetooth Sensors for Signalk -- support for Victron devices, RuuviTag, Xiaomi, ATC and Inkbird",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
7
  "dbus-next": "^0.10.2",
@@ -14,8 +14,8 @@ class ATC extends BTSensor{
14
14
  return null
15
15
  }
16
16
 
17
- static {
18
- this.metadata = new Map(super.getMetadata())
17
+ async init() {
18
+ await super.init()
19
19
  this.addMetadatum('temp','K', 'temperature',
20
20
  (buff,offset)=>{return ((buff.readInt16LE(offset))/100) + 273.1})
21
21
  this.addMetadatum('humidity','ratio', 'humidity',
@@ -0,0 +1,153 @@
1
+ # BLUETOOTH SENSOR CLASS DEVELOPMENT (OUT OF DATE AS OF 1.1.0 Beta release NEW VERSION COMING SOON)
2
+
3
+ The goal of this project is to support as many mariner-useful sensors as possible. If there's anything we can do to make sensor class development easier, please let us know.<br><br>
4
+
5
+ ## REQUIREMENTS
6
+
7
+ * programming knowledge, preferably class programming in Nodejs
8
+ * familiarity with [Node-ble](https://www.npmjs.com/package/node-ble)
9
+ * ideally, the device manufacturer's specification for their bluetooth API
10
+ * failing the above, a willingness to hack or at the very least google aggressively
11
+
12
+ ## PROGRAMMING PROCESS
13
+
14
+ ### Discovery
15
+
16
+ You'll first need to know what data your sensor produces and the means by which it provides data (GATT Server or advertisement) and the details thereof.<br><br>
17
+
18
+ The first approach is to see if the device manufacturer has documented this. Not all do. Don't worry if you can't find OEM docs, it's likely someone on the internet has figured out your device's whys and wherefores for you already. Google that thang. <br><br>
19
+
20
+ If you're still coming up empty, there are any number of tools you can use to examine the Bluetooth data stream of your device. <br><br>
21
+
22
+ * bluetoothctl (installed with Bluez on Linux)
23
+ * [NRF Connect](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=en_US)
24
+
25
+ With these tools you can see what data your device advertises, and what data it provides via a connection to its GATT Server. <br><br>
26
+
27
+ ### Coding
28
+
29
+ #### Get the code
30
+
31
+ To get the code you'll first need to clone this repository then create a branch. That's git talk. Google it if you don't know what that means.<br>
32
+
33
+ Once you've done that you're ready for...
34
+
35
+ #### Actual coding
36
+
37
+ Below is a simple Device class for the Xiaomi thermometer with stock firmware. The code demonstrates the core responsibilities of a Bluetooth sensor device class in the BT-Sensor-plugin's framework:
38
+
39
+ * resides in the sensor_classes subdirectory
40
+ * extends the BTSensor class
41
+ * provides metadata for the device's various data points (the static metadata class member)
42
+ * overrides the BTSensor::connect() and disconnect() methods
43
+ * emits values for each data point
44
+ * exports the class
45
+
46
+ <pre>const BTSensor = require("../BTSensor");
47
+
48
+ class LYWSD03MMC extends BTSensor{
49
+
50
+ static needsScannerOn(){
51
+ return false
52
+ }
53
+ static metadata = new Map()
54
+ .set('temp',{unit:'K', description: 'temperature'})
55
+ .set('humidity',{unit:'ratio', description: 'humidity'})
56
+ .set('voltage',{unit:'V', description: 'sensor battery voltage'})
57
+
58
+ constructor(device){
59
+ super(device)
60
+ }
61
+
62
+ emitValues(buffer){
63
+ this.emit("temp",((buffer.readInt16LE(0))/100) + 273.1);
64
+ this.emit("humidity",buffer.readUInt8(2)/100);
65
+ this.emit("voltage",buffer.readUInt16LE(3)/1000);
66
+ }
67
+
68
+ async connect() {
69
+ await this.device.connect()
70
+ var gattServer = await this.device.gatt()
71
+ var gattService = await gattServer.getPrimaryService("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")
72
+ var gattCharacteristic = await gattService.getCharacteristic("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6")
73
+ this.emitValues(await gattCharacteristic.readValue())
74
+ await gattCharacteristic.startNotifications();
75
+ gattCharacteristic.on('valuechanged', buffer => {
76
+ this.emitValues(buffer)
77
+ })
78
+ }
79
+ async disconnect(){
80
+ super.disconnect()
81
+ await this.device.disconnect()
82
+ }
83
+ }
84
+ module.exports=LYWSD03MMC</pre>
85
+
86
+ Most of the work is done in the connect() method. In this case, the connect() method creates a connection to the device:
87
+ <pre>await this.device.connect()</pre>
88
+ Then it gets the device's gattServer and primary service:
89
+ <pre>
90
+ var gattServer = await this.device.gatt()
91
+ var gattService = await gattServer.getPrimaryService("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")
92
+ </pre>
93
+ Then, it requests the device's "characteristic" that will send us data.
94
+ <pre>var gattCharacteristic = await gattService.getCharacteristic("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6")</pre>
95
+ Then it asks the characteristic for notifications when its value changes.
96
+ <pre>await gattCharacteristic.startNotifications();</pre>
97
+ Then most importantly it emits the values when the data has changed:
98
+ <pre>gattCharacteristic.on('valuechanged', buffer => {
99
+ this.emitValues(buffer)
100
+ })</pre>
101
+ In this implementation, the emitValues member function does the work of parsing the buffer and emitting the values.
102
+ <pre>
103
+ emitValues(buffer){
104
+ this.emit("temp",((buffer.readInt16LE(0))/100) + 273.1);
105
+ this.emit("humidity",buffer.readUInt8(2)/100);
106
+ this.emit("voltage",buffer.readUInt16LE(3)/1000);
107
+ }
108
+ </pre>
109
+ NOTE: If you guessed that the plugin listens to changes to device objects and then publishes the deltas, you guessed right.
110
+
111
+ ### All that said
112
+ The problem with Gatt Server devices is they stay connected and eat up a lot of energy, draining your device's batteries. You can deactivate the device from the config screen when it's not in use or in this case you can flash the device with custom firmware that changes the device to a broadcast device that advertises its data obviating the need for a battery-draining connection. In the case of Xiaomi LYWSD03MMC you can flash it with some very useful software called [ATC](https://github.com/atc1441/ATC_MiThermometer?tab=readme-ov-file) </pre>
113
+
114
+ Below is an example of a BTSensor subclass that uses the advertising protocol to get the data from a flashed Xiaomi thermometer.
115
+
116
+ <pre>const BTSensor = require("../BTSensor");
117
+ const LYWSD03MMC = require('./LYWSD03MMC.js')
118
+ class ATC extends BTSensor{
119
+
120
+ constructor(device){
121
+ super(device)
122
+ }
123
+
124
+ static metadata = LYWSD03MMC.metadata
125
+
126
+ connect() {
127
+ const cb = async (propertiesChanged) => {
128
+ try{
129
+ this.device.getServiceData().then((data)=>{
130
+ //TBD Check if the service ID is universal across ATC variants
131
+ const buff=data['0000181a-0000-1000-8000-00805f9b34fb'];
132
+ this.emit("temp",((buff.readInt16LE(6))/100) + 273.1);
133
+ this.emit("humidity",buff.readUInt16LE(8)/10000);
134
+ this.emit("voltage",buff.readUInt16LE(10)/1000);
135
+ })
136
+ }
137
+ catch (error) {
138
+ throw new Error(`Unable to read data from ${util.inspect(device)}: ${error}` )
139
+ }
140
+ }
141
+ cb();
142
+ this.device.helper.on('PropertiesChanged', cb)
143
+ }
144
+ }
145
+ module.exports=ATC</pre>
146
+
147
+ The big difference here is in the connect() method. All it does is wait on propertiesChanged and when that event occurs, the device object parses the buffer and emits the data. NOTE: Both classes have the same metadata, so the ATC class "borrows" the metadata from the LYWSD03MMC class.<br>
148
+
149
+ ## LET US KNOW
150
+
151
+ When you're done working on your class and satisified that it's functioning properly, commit and request a merge (more git talk).<br>
152
+
153
+ We love to see new sensor classes!
@@ -16,14 +16,10 @@ class Inkbird extends BTSensor{
16
16
  return null
17
17
  }
18
18
 
19
- static {
20
- this.metadata = new Map(super.getMetadata())
21
- this.addMetadatum('temp','K', 'temperature')
22
- this.addMetadatum('battery','ratio', 'battery strength')
23
- }
24
-
25
19
  async init(){
26
20
  await super.init()
21
+ this.addMetadatum('temp','K', 'temperature')
22
+ this.addMetadatum('battery','ratio', 'battery strength')
27
23
  if (this.getName() == 'sps'){
28
24
  this.addMetadatum('humidity','ratio', 'humidity')
29
25
  }
@@ -11,11 +11,7 @@ class RuuviTag extends BTSensor{
11
11
  }
12
12
  return null
13
13
  }
14
-
15
- static {
16
- this.metadata = new Map(super.getMetadata())
17
- }
18
-
14
+
19
15
  async init(){
20
16
  await super.init()
21
17
  const md = this.valueIfVariant(this.getManufacturerData(this.constructor.manufacturerID))
@@ -209,6 +209,7 @@ module.exports = {
209
209
  0xA38B: "SmartShunt 2000A/50mV",
210
210
  0xA3A4: "Smart Battery Sense",
211
211
  0xA3A5: "Smart Battery Sense",
212
+ 0xA3B0: "Smart Battery Protect",
212
213
  0xA3C0: "Orion Smart 12V|12V-18A Isolated DC-DC Charger",
213
214
  0xA3C8: "Orion Smart 12V|12V-30A Isolated DC-DC Charger",
214
215
  0xA3D0: "Orion Smart 12V|12V-30A Non-isolated DC-DC Charger",
@@ -11,12 +11,6 @@ const VC = require('./VictronConstants.js')
11
11
  this.encryptionKey = config?.encryptionKey
12
12
  }
13
13
 
14
- static {
15
- this.metadata = new Map(super.getMetadata())
16
- const md = this.addMetadatum('encryptionKey','', "Encryption Key")
17
- md.isParam = true
18
- }
19
-
20
14
  static async identifyMode(device, mode){
21
15
 
22
16
  try{
@@ -51,13 +45,18 @@ const VC = require('./VictronConstants.js')
51
45
 
52
46
  async init(){
53
47
  await super.init()
48
+ var md = this.addMetadatum('encryptionKey','', "Encryption Key")
49
+ md.isParam = true
50
+ this.metadata = new Map(super.getMetadata())
51
+ md = this.addMetadatum('encryptionKey','', "Encryption Key")
52
+ md.isParam = true
54
53
  this.model_id=this.getManufacturerData(0x2e1).readUInt16LE(2)
55
54
  }
56
55
  alarmReason(alarmValue){
57
56
  return this.constructor.AlarmReason[alarmValue]
58
57
  }
59
58
  getModelName(){
60
- return VC.MODEL_ID_MAP[this.model_id]
59
+ return VC?.MODEL_ID_MAP[this.model_id]??this.constructor.name+" (Model ID:"+this.model_id+")"
61
60
  }
62
61
 
63
62
  decrypt(data){
@@ -28,9 +28,8 @@ class VictronACCharger extends VictronSensor{
28
28
  return await this.identifyMode(device, 0x0A)
29
29
  }
30
30
 
31
- static {
32
- this.metadata = new Map(super.getMetadata())
33
-
31
+ async init(){
32
+ await super.init()
34
33
  this.addMetadatum('state','', 'device state',
35
34
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
36
35
  this.addMetadatum('error','', 'error code',
@@ -48,8 +47,8 @@ class VictronACCharger extends VictronSensor{
48
47
  this.addMetadatum('curr2','A', 'battery 2 current',
49
48
  (buff)=>{return this.NaNif(buff.readUInt16BE(7)&0x7FF,0x7FF)/10})
50
49
 
51
- this.addMetadatum('batt3','V', 'battery 3 voltage',
52
- (buff)=>{return this.NaNif((buff.readUInt16BE(8)>>3), 0x1FFF)/100})
50
+ this.addMetadatum('batt3','V', 'battery 3 voltage',
51
+ (buff)=>{return this.NaNif((buff.readUInt16BE(8)>>3), 0x1FFF)/100})
53
52
 
54
53
  this.addMetadatum('curr3','A', 'battery 3 current',
55
54
  (buff)=>{return this.NaNif(buff.readUInt16BE(9)&0x7FF,0x7FF)/10})
@@ -1,11 +1,18 @@
1
1
  const VictronSensor = require("./Victron/VictronSensor.js");
2
2
  const VC=require("./Victron/VictronConstants.js")
3
3
  class VictronBatteryMonitor extends VictronSensor{
4
- static {
5
- this.metadata = new Map(super.getMetadata())
4
+
5
+
6
+ static async identify(device){
7
+ return await this.identifyMode(device, 0x02)
8
+ }
9
+
10
+ characteristics=[]
11
+ async init(){
12
+ await super.init()
6
13
  this.addMetadatum('current', 'A', 'house battery amperage',
7
- (buff,offset=0)=>{return buff.readInt32LE(offset)/1000},
8
- '6597ed8c-4bda-4c1e-af4b-551c4cf74769')
14
+ (buff,offset=0)=>{return buff.readInt32LE(offset)/1000},
15
+ '6597ed8c-4bda-4c1e-af4b-551c4cf74769')
9
16
  this.addMetadatum('power','W', 'house battery wattage',
10
17
  (buff,offset=0)=>{return buff.readInt16LE(offset)},
11
18
  '6597ed8e-4bda-4c1e-af4b-551c4cf74769')
@@ -27,15 +34,6 @@ class VictronBatteryMonitor extends VictronSensor{
27
34
  this.addMetadatum( 'ttg','s','time to go',
28
35
  (buff,offset=0)=>{return this.NaNif(buff.readUInt16LE(offset),0xFFFF)*60},
29
36
  '65970ffe-4bda-4c1e-af4b-551c4cf74769')
30
- }
31
-
32
- static async identify(device){
33
- return await this.identifyMode(device, 0x02)
34
- }
35
-
36
- characteristics=[]
37
- async init(){
38
- await super.init()
39
37
  const modecurrent = this.getAuxModeAndCurrent()
40
38
  this.auxMode= modecurrent.auxMode
41
39
  switch(this.auxMode){
@@ -138,7 +136,7 @@ class VictronBatteryMonitor extends VictronSensor{
138
136
  propertiesChanged(props){
139
137
  super.propertiesChanged(props)
140
138
  }
141
- canUseGATT(){
139
+ hasGATT(){
142
140
  return true
143
141
  }
144
142
 
@@ -7,8 +7,8 @@ class VictronDCDCConverter extends VictronSensor{
7
7
  return await this.identifyMode(device,0x04)
8
8
  }
9
9
 
10
- static {
11
- this.metadata= new Map(super.getMetadata())
10
+ async init(){
11
+ await super.init()
12
12
  this.addMetadatum('deviceState','', 'device state',
13
13
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
14
14
  this.addMetadatum('chargerError','', 'charger error',
@@ -8,6 +8,13 @@ class VictronDCEnergyMeter extends VictronSensor{
8
8
  }
9
9
  async init(){
10
10
  await super.init()
11
+ this.addMetadatum('meterType','', 'meter type',
12
+ (buff)=>{return VC.MeterType.get( buff.readInt16LE(0))})
13
+ this.addMetadatum('voltage','','voltage',
14
+ (buff)=>{return buff.readInt16LE(2)/100})
15
+ this.addMetadatum('alarm','', 'alarm',
16
+ (buff)=>{return buff.readUInt16LE(4)})
17
+ this.addMetadatum('current','A', 'current')
11
18
  const modeCurrent = this.getAuxModeAndCurrent()
12
19
  this.auxMode= modeCurrent.auxMode
13
20
  switch(this.auxMode){
@@ -30,16 +37,7 @@ class VictronDCEnergyMeter extends VictronSensor{
30
37
  break
31
38
  }
32
39
  }
33
- static {
34
- this.metadata = new Map(super.getMetadata())
35
- this.addMetadatum('meterType','', 'meter type',
36
- (buff)=>{return VC.MeterType.get( buff.readInt16LE(0))})
37
- this.addMetadatum('voltage','','voltage',
38
- (buff)=>{return buff.readInt16LE(2)/100})
39
- this.addMetadatum('alarm','', 'alarm',
40
- (buff)=>{return buff.readUInt16LE(4)})
41
- this.addMetadatum('current','A', 'current')
42
- }
40
+
43
41
  emitValuesFrom(decData){
44
42
  this.emitData("meterType",decData,0)
45
43
  this.emitData("voltage",decData,2);
@@ -26,8 +26,8 @@ TBD
26
26
  return await this.identifyMode(device, 0x07)
27
27
  }
28
28
 
29
- static {
30
- this.metadata = new Map(super.getMetadata())
29
+ async init() {
30
+ super.init()
31
31
  this.addMetadatum('voltage','V', 'channel #1 voltage',
32
32
  (buff)=>{return this.NaNif(buff.readInt16LE(0),0xFFFF)/100})
33
33
  this.addMetadatum('pvPower','W','DC input power in watts',
@@ -7,12 +7,11 @@ class VictronInverter extends VictronSensor{
7
7
  return await this.identifyMode(device, 0x03)
8
8
  }
9
9
 
10
- static {
11
- var md
12
- this.metadata= new Map(super.getMetadata())
10
+ async init() {
11
+ await super.init()
13
12
  this.addMetadatum('deviceState','', 'inverter device state',
14
13
  (buff)=>{return VC.OperationMode.get(buff.readIntU8(0))})
15
- md = this.addMetadatum('alarmReason','', 'reason for alarm',
14
+ const md = this.addMetadatum('alarmReason','', 'reason for alarm',
16
15
  (buff)=>{return buff.readIntU16LE(1)})
17
16
  md.notify=true
18
17
 
@@ -10,12 +10,12 @@ class VictronInverterRS extends VictronSensor{
10
10
  }
11
11
 
12
12
 
13
- static {
14
- var md
15
- this.metadata= new Map(super.getMetadata())
13
+ async init() {
14
+ await super.init()
15
+
16
16
  this.addMetadatum('deviceState','', 'inverter device state',
17
17
  (buff)=>{return VC.OperationMode.get(buff.readIntU8(0))})
18
- md = this.addMetadatum('chargerError','', 'charger error',
18
+ const md = this.addMetadatum('chargerError','', 'charger error',
19
19
  (buff)=>{return VC.ChargerError(buff.readIntU8(1))})
20
20
  md.notify=true
21
21
 
@@ -23,9 +23,8 @@ class VictronLynxSmartBMS extends VictronSensor{
23
23
  return await this.identifyMode(device, 0x0A)
24
24
  }
25
25
 
26
- static {
27
- this.metadata = new Map(super.getMetadata())
28
-
26
+ async init() {
27
+ await super.init()
29
28
  this.addMetadatum('error','', 'error code',
30
29
  (buff)=>{return buff.readUInt8(0)})
31
30
 
@@ -8,9 +8,8 @@ class VictronOrionXS extends VictronSensor{
8
8
  static async identify(device){
9
9
  return await this.identifyMode(device, 0x0F)
10
10
  }
11
- static {
12
- this.metadata = new Map(super.getMetadata())
13
-
11
+ async init() {
12
+ await super.init()
14
13
  this.addMetadatum('deviceState','', 'device state',
15
14
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
16
15
  this.addMetadatum('chargerError','', 'charger error',
@@ -23,9 +23,8 @@ class VictronSmartBatteryProtect extends VictronSensor{
23
23
  return await this.identifyMode(device, 0x09)
24
24
  }
25
25
 
26
- static {
27
- this.metadata = new Map(super.getMetadata())
28
-
26
+ async init() {
27
+ await super.init()
29
28
  this.addMetadatum('deviceState','', 'device state',
30
29
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(1))})
31
30
  this.addMetadatum('outputStatus','', 'output status', //TODO
@@ -25,16 +25,14 @@ class VictronSmartLithium extends VictronSensor{
25
25
  return await this.identifyMode(device, 0x05)
26
26
  }
27
27
 
28
- static {
29
-
28
+ async init() {
29
+ await super.init()
30
30
  function _toCellVoltage(val){
31
31
  return val==0x7F?NaN:2.6+(val/100)
32
32
  }
33
- this.metadata = new Map(super.getMetadata())
34
-
35
33
  this.addMetadatum('bmsFlags','', 'BMS Flags',
36
34
  (buff)=>{return buff.readUInt32BE(0)})
37
- /
35
+
38
36
  this.addMetadatum('smartLithiumErrors','', 'Smart Lithium Errors Flags',
39
37
  (buff)=>{return buff.readUInt16BE(3)})
40
38
  this.addMetadatum('cell1Voltage','V', 'cell #1 voltage',
@@ -58,9 +56,7 @@ class VictronSmartLithium extends VictronSensor{
58
56
  this.addMetadatum('balancerStatus','', 'balancer status', //TODO
59
57
  (buff)=>{return this.NaNif((buff.readUInt16BE(14)&0xf),0xF)})
60
58
  this.addMetadatum('batteryTemp','K', 'battery temperature',
61
- (buff)=>{return this.NaNif((buff.readInt8(15)>>1),0x7F)+233.15})
62
-
63
-
59
+ (buff)=>{return this.NaNif((buff.readInt8(15)>>1),0x7F)+233.15})
64
60
  }
65
61
  }
66
62
  module.exports=VictronSmartLithium
@@ -6,8 +6,8 @@ class VictronSolarCharger extends VictronSensor{
6
6
  return await this.identifyMode(device, 0x01)
7
7
  }
8
8
 
9
- static {
10
- this.metadata = new Map(super.getMetadata())
9
+ async init() {
10
+ await super.init()
11
11
 
12
12
  this.addMetadatum('chargeState','', 'charge state',
13
13
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
@@ -9,8 +9,8 @@ class VictronVEBus extends VictronSensor{
9
9
  return await this.identifyMode(device, 0x0C)
10
10
  }
11
11
 
12
- static {
13
- this.metadata = new Map(super.getMetadata())
12
+ async init() {
13
+ await super.init()
14
14
 
15
15
  this.addMetadatum('chargeState','', 'charge state',
16
16
  (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
@@ -91,21 +91,7 @@ class XiaomiMiBeacon extends BTSensor{
91
91
  }
92
92
  return null
93
93
  }
94
- static {
95
- this.metadata = new Map(super.getMetadata())
96
- //3985f4ebc032f276cc316f1f6ecea085
97
- //8a1dadfa832fef54e9c1d190
98
94
 
99
- const md = this.addMetadatum("encryptionKey", "", "encryptionKey (AKA bindKey) for decryption")
100
- md.isParam=true
101
- this.addMetadatum('temp','K', 'temperature',
102
- (buff,offset)=>{return ((buff.readInt16LE(offset))/100) + 273.1})
103
- this.addMetadatum('humidity','ratio', 'humidity',
104
- (buff,offset)=>{return ((buff.readUInt8(offset))/100)})
105
- this.addMetadatum('voltage', 'V', 'sensor battery voltage',
106
- (buff,offset)=>{return ((buff.readUInt16LE(offset))/1000)})
107
-
108
- }
109
95
  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."
110
96
 
111
97
  emitValues(buffer){
@@ -120,7 +106,10 @@ class XiaomiMiBeacon extends BTSensor{
120
106
  return this.GATTwarning
121
107
  }
122
108
  initGATT(){
123
- this.debug(this.GATTwarning.toUpperCase())
109
+ if (!this?.gattWarningDelivered) {
110
+ this.debug(this.GATTwarning.toUpperCase())
111
+ this.gattWarningDelivered=true
112
+ }
124
113
  return new Promise((resolve,reject )=>{
125
114
  this.device.connect().then(async ()=>{
126
115
  if (!this.gattServer) {
@@ -168,7 +157,7 @@ class XiaomiMiBeacon extends BTSensor{
168
157
  return cipher.update(encryptedPayload)
169
158
  }
170
159
 
171
- canUseGATT(){
160
+ hasGATT(){
172
161
  return true
173
162
  }
174
163
 
@@ -202,6 +191,15 @@ class XiaomiMiBeacon extends BTSensor{
202
191
 
203
192
  async init(){
204
193
  await super.init()
194
+ const md = this.addMetadatum("encryptionKey", "", "encryptionKey (AKA bindKey) for decryption")
195
+ md.isParam=true
196
+ this.addMetadatum('temp','K', 'temperature',
197
+ (buff,offset)=>{return ((buff.readInt16LE(offset))/100) + 273.1})
198
+ this.addMetadatum('humidity','ratio', 'humidity',
199
+ (buff,offset)=>{return ((buff.readUInt8(offset))/100)})
200
+ this.addMetadatum('voltage', 'V', 'sensor battery voltage',
201
+ (buff,offset)=>{return ((buff.readUInt16LE(offset))/1000)})
202
+
205
203
  const data = this.getServiceData(this.constructor.SERVICE_MIBEACON)
206
204
  const frameControl = data[0] + (data[1] << 8)
207
205
  this.deviceID = data[2] + (data[3] << 8)
package/test.js DELETED
@@ -1,32 +0,0 @@
1
- const {createBluetooth} = require('node-ble')
2
- const {bluetooth, destroy} = createBluetooth()
3
- const { argv } = require('node:process');
4
-
5
-
6
- async function main(){
7
-
8
- const adapter = await bluetooth.defaultAdapter()
9
-
10
- try{await adapter.startDiscovery()} catch{}
11
- const device = await adapter.waitDevice(argv[2])
12
- var trials=0
13
- await device.connect()
14
- const gatt = await device.gatt()
15
- await device.disconnect()
16
-
17
- async function search(){
18
- await device.connect()
19
-
20
- console.log(`GATT trial #${++trials}...`)
21
- const s = await gatt.getPrimaryService("65970000-4bda-4c1e-af4b-551c4cf74769")
22
- const c = await s.getCharacteristic('6597ffff-4bda-4c1e-af4b-551c4cf74769')
23
- await c.readValue()
24
- await device.disconnect()
25
- }
26
-
27
- setInterval(() => {
28
- search()
29
- },argv[3]);
30
- search()
31
- }
32
- main()