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