bt-sensors-plugin-sk 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BTSensor.js CHANGED
@@ -3,7 +3,7 @@ const { log } = require('node:console');
3
3
  const EventEmitter = require('node:events');
4
4
  const AutoQueue = require("./Queue.js")
5
5
  const DistanceManager = require("./DistanceManager")
6
-
6
+ const OutOfRangeDevice = require("./OutOfRangeDevice.js")
7
7
  /**
8
8
  * @author Andrew Gerngross <oh.that.andy@gmail.com>
9
9
  */
@@ -278,7 +278,7 @@ class BTSensor extends EventEmitter {
278
278
 
279
279
  unsetError(){
280
280
  this._error=false
281
- this.emit("error", false)
281
+ this.emit("errorDetected", false)
282
282
  }
283
283
 
284
284
  setError(message){
@@ -292,7 +292,7 @@ class BTSensor extends EventEmitter {
292
292
 
293
293
  this._errorLog.push({timestamp:Date.now(), message:message})
294
294
  this._error=true
295
- this.emit("error", true)
295
+ this.emit("errorDetected", true)
296
296
  }
297
297
 
298
298
  //Instance Initialization functions
@@ -365,7 +365,10 @@ class BTSensor extends EventEmitter {
365
365
  await this.initSchema()
366
366
 
367
367
  this.initListen()
368
- this.setState("DORMANT")
368
+ if ( this.device instanceof OutOfRangeDevice)
369
+ this.setState("OUT OF RANGE")
370
+ else
371
+ this.setState("DORMANT")
369
372
 
370
373
  }
371
374
 
@@ -1059,11 +1062,11 @@ class BTSensor extends EventEmitter {
1059
1062
  if (!(path === undefined)) {
1060
1063
  let preparedPath = this.preparePath(path)
1061
1064
  this.on(tag, (val)=>{
1062
- if (pathMeta.notify){
1065
+ /* if (pathMeta.notify){
1063
1066
  this._app.notify(tag, val, id )
1064
- } else {
1067
+ } else {*/
1065
1068
  this.updatePath(preparedPath,val, id, source)
1066
- }
1069
+ //}
1067
1070
  })
1068
1071
  }
1069
1072
  })
package/README.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Bluetooth Sensors for [Signal K](http://www.signalk.org)
2
2
 
3
3
  ## WHAT'S NEW
4
+
5
+ # Version 1.3.3
6
+
7
+ - Support for additional Xiaomi environmental sensors
8
+ - Out Of Range device automatic retry
9
+ - Pairing guide
10
+
11
+ # Version 1.3.2-1
12
+
13
+ - VictronSmartBatteryProtect fix
14
+ - RenogyRoverClient deviceID clarity
15
+
4
16
  # Version 1.3.2
5
17
 
6
18
  - Victron Alarm Reason improvements
package/index.js CHANGED
@@ -445,7 +445,7 @@ module.exports = function (app) {
445
445
  sensor.on("connected", (state)=>{
446
446
  updateSensor(sensor)
447
447
  })
448
- sensor.on("error",(error)=>{
448
+ sensor.on("errorDetected",(error)=>{
449
449
  updateSensor(sensor)
450
450
  })
451
451
  sensor.on("debug", ()=>{
@@ -492,19 +492,17 @@ module.exports = function (app) {
492
492
  s.stopListening()
493
493
  else{
494
494
  const device = new OutOfRangeDevice(adapter, config)
495
- const c = await getClassFor(device,config)
496
- if (c.domain==BTSensor.SensorDomains.beacons || c.IsRoaming){
497
- s = await instantiateSensor(device,config)
498
- device.once("deviceFound",async (device)=>{
499
- s.device=device
500
- s.listen()
501
- await s.activate(config, plugin)
502
- removeSensorFromList(s)
503
- addSensorToList(s)
504
- })
495
+ s = await instantiateSensor(device,config)
496
+ device.once("deviceFound",async (device)=>{
497
+ s.device=device
498
+ s.listen()
499
+ if (config.active)
500
+ await s.activate(config, plugin)
501
+ removeSensorFromList(s)
505
502
  addSensorToList(s)
506
- resolve(s)
507
- }
503
+ })
504
+ addSensorToList(s)
505
+ resolve(s)
508
506
  }
509
507
  if (startNumber == starts ) {
510
508
  const errorTxt = `Unable to communicate with device ${deviceNameAndAddress(config)} Reason: ${e?.message??e}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bt-sensors-plugin-sk",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk#supported-sensors for a list of supported sensors",
5
5
  "main": "index.js",
6
6
  "dependencies": {
package/pairing.md ADDED
@@ -0,0 +1,147 @@
1
+ # Bluetooth Device Pairing using bluetoothctl
2
+
3
+ This guide provides a step-by-step procedure for pairing, trusting, and connecting a new Bluetooth device using the bluetoothctl command-line utility, which is the official Bluetooth client for BlueZ (the Linux Bluetooth stack).
4
+
5
+ ## Prerequisites
6
+
7
+ - A running Linux system (e.g., Debian, Ubuntu, Fedora, Arch).
8
+ - The bluez package installed (which includes bluetoothctl).
9
+ - The Bluetooth service (bluetooth.service) is running.
10
+ - Your local Bluetooth adapter is enabled and powered on.
11
+
12
+ ## The Pairing Process
13
+
14
+ Follow these steps within your terminal:
15
+
16
+ ### Step 1: Launch bluetoothctl
17
+
18
+ Open your terminal and run the command to enter the interactive bluetoothctl prompt.
19
+
20
+ ```
21
+
22
+ bluetoothctl
23
+
24
+ ```
25
+
26
+ (The prompt should change to [bluetooth]#)
27
+
28
+ ### Step 2: Check Controller Status (Optional but recommended)Ensure your adapter is powered on and set to discoverable/pairable mode.
29
+
30
+ ```
31
+
32
+ [bluetooth]# show
33
+
34
+ ```
35
+
36
+ If the Powered field is listed as no, power the adapter on:
37
+
38
+ ```
39
+
40
+ [bluetooth]# power on
41
+
42
+ ```
43
+
44
+ ### Step 3: Start Scanning for Devices
45
+
46
+ Put your target Bluetooth device into pairing mode (see device instructions). Then, start the scanning process:
47
+
48
+ ```
49
+
50
+ [bluetooth]# scan on
51
+
52
+ ```
53
+
54
+
55
+ You will see a list of discovered devices scrolling by with their MAC addresses and names.
56
+
57
+ ```
58
+ Discovery started
59
+ [CHG] Device 12:34:56:78:90:AB Name: My Bluetooth Headphones
60
+ [NEW] Device AB:CD:EF:12:34:56 Name: Wireless Mouse
61
+ ...
62
+ ```
63
+
64
+
65
+ ### Step 4: Identify and Stop Scanning
66
+
67
+ Once you see the name of your device, copy its MAC Address (e.g., AB:CD:EF:12:34:56) and stop the scan to save battery and reduce clutter:
68
+
69
+ ```
70
+
71
+ [bluetooth]# scan off
72
+
73
+ ```
74
+
75
+
76
+ Step 5: Trust the Device
77
+
78
+ Trusting the device is crucial. It allows the device to reconnect automatically after a reboot or disconnection without needing to re-pair. Replace [MAC_ADDRESS] with your device's MAC address.
79
+
80
+ ```
81
+
82
+ [bluetooth]# trust [MAC_ADDRESS]
83
+
84
+ Attempting to trust AB:CD:EF:12:34:56
85
+ [CHG] Device AB:CD:EF:12:34:56 Trusted: yes
86
+ ...
87
+
88
+ ```
89
+
90
+ ### Step 6: Connect the Device
91
+
92
+ This step attempts to establish the active connection.
93
+
94
+ ```
95
+
96
+ [bluetooth]# connect [MAC_ADDRESS]
97
+
98
+ Attempting to connect to AB:CD:EF:12:34:56
99
+ [CHG] Device AB:CD:EF:12:34:56 Connected: yes
100
+ Connection successful
101
+ ...
102
+ ```
103
+
104
+ ### Step 7: Pair the Device
105
+
106
+ If the connection attempt in Step 6 did not automatically handle the key exchange, or if you prefer to explicitly perform the key exchange first, use the pair command. This initiates the actual pairing sequence. If your device requires a PIN or Passkey, bluetoothctl will prompt you to enter it (or confirm it). NOTE: Check your device's manual for setting the device's pairing mode.
107
+
108
+ ```
109
+
110
+ [bluetooth]# pair [MAC_ADDRESS]
111
+ Attempting to pair with AB:CD:EF:12:34:56
112
+ [CHG] Device AB:CD:EF:12:34:56 Paired: yes
113
+ Pairing successful
114
+ ...
115
+
116
+ ```
117
+
118
+ (If prompted, enter or confirm the Passkey. If this command succeeds, you may need to run connect (Step 6) again to establish the active link.)
119
+
120
+ ### Step 8: Exit
121
+
122
+ Once the connection is established, you can exit the interactive utility.
123
+
124
+ ```
125
+
126
+ [bluetooth]# exit
127
+
128
+ ```
129
+
130
+ ## Summary of Essential Commands
131
+
132
+ | **Command** | **Purpose** |
133
+ | :--- | :--- |
134
+ | `bluetoothctl` | Enter the interactive prompt. |
135
+ | `power on` | Power on the local Bluetooth adapter. |
136
+ | `scan on` | Start discovering nearby devices. |
137
+ | `scan off` | Stop device discovery. |
138
+ | `trust [MAC]` | Mark the device as trusted for auto-reconnection. **(Crucial)** |
139
+ | `connect [MAC]` | Establish a connection to the device (may implicitly trigger pairing). |
140
+ | `pair [MAC]` | Explicitly initiate the pairing process (key exchange). |
141
+ | `exit` | Exit the `bluetoothctl` utility. |
142
+
143
+ ### Troubleshooting Tips
144
+
145
+ * **Adapter Issues:** If `show` or `list` commands don't show an adapter, ensure your Bluetooth hardware is properly recognized by the system (check kernel logs or `rfkill list`).
146
+
147
+ * **Device Out of Range:** Ensure the device is close to the computer and is fully charged.
@@ -197,6 +197,7 @@ class JBDBMS extends BTSensor {
197
197
  }
198
198
 
199
199
  async initGATTConnection() {
200
+ this.setConnected(await this.device.isConnected())
200
201
  return this
201
202
  }
202
203
 
@@ -257,7 +257,7 @@ class JikongBMS extends BTSensor {
257
257
 
258
258
  this.addMetadatum("cycleCapacity", "number", "Cycle capacity", (buffer) => {
259
259
  return buffer.readUInt32LE(154 + this.offset * 2) / 1000;
260
- }).default = "electrical.batteries.{batteryID}.discharging";
260
+ }).default = "electrical.batteries.{batteryID}.cycleCapacity";
261
261
 
262
262
  this.addMetadatum(
263
263
  "balanceAction",
@@ -3,7 +3,7 @@ ported from https://github.com/cyrils/renogy-bt
3
3
  */
4
4
  const BTSensor = require("../../BTSensor.js");
5
5
  const VC = require('./RenogyConstants.js');
6
- const crc16Modbus = require('./CRC.js')
6
+ const crc16Modbus = require('./CRC.js');
7
7
  class RenogySensor extends BTSensor{
8
8
  static Domain=BTSensor.SensorDomains.electrical
9
9
  static ALIAS_PREFIX = 'BT-TH'
@@ -34,20 +34,27 @@ class RenogySensor extends BTSensor{
34
34
  b.writeUInt16BE(words,4)
35
35
  b.writeUInt16BE(crc16Modbus(b.subarray(0,6)),6)
36
36
 
37
- await writeCharacteristic.writeValueWithResponse(b, 0)
37
+ await writeCharacteristic.writeValueWithoutResponse(b, 0)
38
38
 
39
39
  }
40
40
  static identify(device){
41
41
  return null
42
42
  }
43
-
43
+ pollFreq=30
44
44
  async initSchema(){
45
45
  await super.initSchema()
46
-
46
+ this.getGATTParams().pollFreq.default=this.pollFreq
47
47
  this.addParameter(
48
48
  "deviceID",
49
49
  {
50
- title: 'ID of device'
50
+ title: 'ID of device',
51
+ description: 'only modify if device is in hub mode or daisy chained',
52
+ default:255,
53
+ type: 'number',
54
+ minimum: 0,
55
+ maximum: 255,
56
+ multipleOf:1,
57
+ isRequired: true
51
58
  }
52
59
  )
53
60
  }
@@ -73,15 +80,17 @@ class RenogySensor extends BTSensor{
73
80
  }
74
81
 
75
82
  async sendReadFunctionRequest(writeReq, words){
76
- this.constructor.sendReadFunctionRequest(
83
+ await this.constructor.sendReadFunctionRequest(
77
84
  this.writeChar, this.getDeviceID(), writeReq, words)
78
85
  }
79
86
 
80
87
  initGATTInterval(){
88
+ this.emitGATT()
81
89
  this.intervalID = setInterval(()=>{
82
90
  this.emitGATT()
83
91
  }, 1000*(this?.pollFreq??60) )
84
92
  }
93
+
85
94
  emitGATT(){
86
95
  this.getAllEmitterFunctions().forEach(async (emitter)=>
87
96
  await emitter()
@@ -12,9 +12,21 @@ class RenogyBattery extends RenogySensor {
12
12
  this.getAndEmitCellTemperatures.bind(this),
13
13
  this.getAndEmitCellVoltages.bind(this)]
14
14
  }
15
+ numberOfCells=4
15
16
  initSchema(){
16
-
17
- this.addMetadatum('numberOfCells','', 'number of cells')
17
+ this.addDefaultParam("batteryID").default="house"
18
+ this.addParameter(
19
+ "numberOfCells",
20
+ {
21
+ title:"Number of cells",
22
+ type: "number",
23
+ isRequired: true,
24
+ default: this.numberOfCells,
25
+ minimum: 1,
26
+ maximum: 16,
27
+ multipleOf:1
28
+ }
29
+ )
18
30
  this.addDefaultPath('current','electrical.batteries.current')
19
31
  .read=(buffer)=>{return buffer.readInt16BE(3)/100}
20
32
 
@@ -41,31 +53,33 @@ class RenogyBattery extends RenogySensor {
41
53
  async initGATTConnection() {
42
54
  await super.initGATTConnection()
43
55
  this.numberOfCells = await this.retrieveNumberOfCells()
44
- this.deviceID = await this.retrieveDeviceID()
45
- this.emit('numberOfCells', this.numberOfCells)
46
56
  }
47
57
 
48
- retrieveNumberOfCells(){
49
-
58
+ retrieveModelID(){
50
59
  return new Promise( async ( resolve, reject )=>{
51
- await this.sendReadFunctionRequest(0x1388,0x11)
52
60
 
53
- const valChanged = async (buffer) => {
54
- resolve(buffer.readUInt16(3))
55
- }
56
- this.readChar.once('valuechanged', valChanged )
61
+ await this.sendReadFunctionRequest(0x1402,0x08)
62
+
63
+ this.readChar.once('valuechanged', async (buffer) => {
64
+ if (buffer[2]!=0x10)
65
+ reject("Unknown error retrieving model ID") //???
66
+ const model = buffer.subarray(3,17).toString().trim()
67
+ resolve(model)
57
68
  })
69
+ })
58
70
  }
59
- retrieveDeviceID(){
71
+ retrieveNumberOfCells(){
72
+
60
73
  return new Promise( async ( resolve, reject )=>{
61
- this.sendFunctionRequest(0x14, 0x67)
74
+ await this.sendReadFunctionRequest(0x1388,0x11)
62
75
 
63
76
  const valChanged = async (buffer) => {
64
- resolve((buffer.readUInt8(4)))
77
+ resolve(buffer.readUInt16(3))
65
78
  }
66
79
  this.readChar.once('valuechanged', valChanged )
67
80
  })
68
81
  }
82
+
69
83
 
70
84
  getAndEmitBatteryInfo(){
71
85
  return new Promise( async ( resolve, reject )=>{
@@ -54,10 +54,24 @@ class RenogyInverter extends RenogySensor {
54
54
  })
55
55
  }
56
56
 
57
+ retrieveModelID(){
58
+ return new Promise( async ( resolve, reject )=>{
59
+
60
+ await this.sendReadFunctionRequest(0x10d7,0x08)
61
+
62
+ this.readChar.once('valuechanged', async (buffer) => {
63
+ if (buffer[2]!=0x10)
64
+ reject("Unknown error retrieving model ID") //???
65
+ const model = buffer.subarray(3,17).toString().trim()
66
+ resolve(model)
67
+ })
68
+ })
69
+ }
70
+
57
71
  getAndEmitInverterStats(){
58
72
  return new Promise( async ( resolve, reject )=>{
59
73
 
60
- await this.sendReadFunctionRequest(0xfa0, 0x8)
74
+ await this.sendReadFunctionRequest(0xfa0, 0xA)
61
75
 
62
76
  this.readChar.once('valuechanged', (buffer) => {
63
77
  ["ueiVoltage","ueiCurrent", "voltage", "loadCurrent", "frequency","temperature"].forEach((tag)=>
@@ -71,7 +85,7 @@ class RenogyInverter extends RenogySensor {
71
85
  getAndEmitSolarCharging(){
72
86
  return new Promise( async ( resolve, reject )=>{
73
87
 
74
- await this.sendReadFunctionRequest(0x10e9, 0x5)
88
+ await this.sendReadFunctionRequest(0x10e9, 0x7)
75
89
 
76
90
  this.readChar.once('valuechanged', (buffer) => {
77
91
  ["solarVoltage","solarCurrent", "solarPower", "solarChargingStatus", "solarChargingPower"].forEach((tag)=>
@@ -85,7 +99,7 @@ class RenogyInverter extends RenogySensor {
85
99
  getAndEmitInverterLoad(){
86
100
  return new Promise( async ( resolve, reject )=>{
87
101
 
88
- await this.sendReadFunctionRequest(0x113a, 0x2)
102
+ await this.sendReadFunctionRequest(0x113a, 0x6)
89
103
 
90
104
  this.readChar.once('valuechanged', (buffer) => {
91
105
  ["loadPower", "chargingCurrent"].forEach((tag)=>
@@ -34,6 +34,10 @@ class RenogyRoverClient extends RenogySensor {
34
34
  initSchema(){
35
35
  //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']
36
36
  super.initSchema()
37
+
38
+ this.addDefaultParam("id")
39
+ .default="solar"
40
+
37
41
  this.addMetadatum('batteryType', '', "battery type")
38
42
  .default="electrical.chargers.{id}.battery.type"
39
43
  this.addMetadatum('batteryPercentage', 'ratio', "battery percentage",
@@ -118,22 +122,12 @@ class RenogyRoverClient extends RenogySensor {
118
122
 
119
123
  }
120
124
 
121
- retrieveDeviceID(){
122
- return new Promise( async ( resolve, reject )=>{
123
- this.sendReadFunctionRequest(0x1A, 0x1)
124
-
125
- const valChanged = async (buffer) => {
126
- resolve((buffer.readUInt8(4)))
127
- }
128
- this.readChar.once('valuechanged', valChanged )
129
- })
130
- }
131
-
125
+
132
126
  retrieveBatteryType(){
133
127
  return new Promise( async ( resolve, reject )=>{
134
128
  //Buffer(7) [255, 3, 2, 0, 1, 80, 80, buffer: ArrayBuffer(8192), byteLength: 7, byteOffset: 864, length: 7, Symbol(Symbol.toStringTag): 'Uint8Array']
135
129
 
136
- this.sendReadFunctionRequest(0xe004, 0x01)
130
+ await this.sendReadFunctionRequest(0xe004, 0x01)
137
131
 
138
132
  const valChanged = async (buffer) => {
139
133
  resolve(RC.BATTERY_TYPE[(buffer.readUInt8(4))])
@@ -157,13 +151,10 @@ class RenogyRoverClient extends RenogySensor {
157
151
  }
158
152
  async initGATTConnection() {
159
153
  await super.initGATTConnection()
160
- if (!this.deviceID)
161
- this.deviceID = await this.retrieveDeviceID()
162
- this.modelID=await this.retrieveModelID()
163
154
 
155
+ this.modelID=await this.retrieveModelID()
164
156
  this.batteryType = await this.retrieveBatteryType()
165
- this.emit('batteryType', this.batteryType)
166
-
157
+ this.emit('batteryType', this.batteryType)
167
158
 
168
159
  }
169
160
 
@@ -175,7 +166,7 @@ class RenogyRoverClient extends RenogySensor {
175
166
  async getAndEmitChargeInfo(){
176
167
  return new Promise( async ( resolve, reject )=>{
177
168
  try {
178
- this.sendReadFunctionRequest(0x100, 0x22)
169
+ await this.sendReadFunctionRequest(0x100, 0x22)
179
170
 
180
171
  this.readChar.once('valuechanged', buffer => {
181
172
  this.emitValuesFrom(buffer)
@@ -71,11 +71,12 @@ class ShenzhenLiONBMS extends BTSensor{
71
71
  "numberOfCells",
72
72
  {
73
73
  title:"Number of cells",
74
- type: "integer",
74
+ type: "number",
75
75
  isRequired: true,
76
76
  default: 4,
77
77
  minimum: 1,
78
- maximum: 16
78
+ maximum: 16,
79
+ multipleOf:1
79
80
  }
80
81
  )
81
82
 
@@ -26,7 +26,7 @@ class VictronBatteryMonitor extends VictronSensor{
26
26
  d.debug.bind(d)
27
27
  d.initSchema()
28
28
  Object.keys(d.getPaths()).forEach((tag)=>{
29
- d.on(tag,(v)=>console.log(`${tag}=${v}`))
29
+ d.on(tag,(v)=>console.log(`${tag}=${JSON.stringify(v)}`))
30
30
  })
31
31
  if (key)
32
32
  b = d.decrypt(b)
@@ -62,7 +62,6 @@ class VictronBatteryMonitor extends VictronSensor{
62
62
  const alarmMD = this.addMetadatum('alarm','', 'alarm',
63
63
  (buff,offset=0)=>{return buff.readInt16LE(offset)})
64
64
  alarmMD.default='electrical.batteries.{batteryID}.alarm'
65
- alarmMD.notify=true
66
65
 
67
66
  this.addMetadatum( 'consumed','Ah', 'amp-hours consumed',
68
67
  (buff,offset=0)=>{return buff.readInt32LE(offset)/10},
@@ -78,8 +77,6 @@ class VictronBatteryMonitor extends VictronSensor{
78
77
  .read=(buff,offset=0)=>{return this.NaNif(buff.readUInt16LE(offset),0xFFFF)*60}
79
78
  this.getPath("ttg").gatt='65970ffe-4bda-4c1e-af4b-551c4cf74769';
80
79
 
81
- this.auxMode=VC.AuxMode.STARTER_VOLTAGE
82
-
83
80
  if (this.auxMode==undefined){
84
81
  const md=await this.constructor.getDataPacket(this.device, this.getManufacturerData(this.constructor.ManufacturerID))
85
82
  try {
@@ -55,7 +55,6 @@ class VictronDCEnergyMeter extends VictronSensor{
55
55
  this.addMetadatum('alarm','', 'alarm',
56
56
  (buff)=>{return buff.readUInt16LE(4)})
57
57
  .default="electrical.meters.{id}.alarm"
58
- this.getPath("alarm").notify=true
59
58
  this.addMetadatum('current','A', 'current')
60
59
  .default="electrical.meters.{id}.current"
61
60
 
@@ -17,7 +17,6 @@ class VictronInverter extends VictronSensor{
17
17
  const md = this.addMetadatum('alarmReason','', 'reason for alarm',
18
18
  (buff)=>{return buff.readIntU16LE(1)})
19
19
  .default="electrical.inverters.{id}.alarm"
20
- md.notify=true
21
20
 
22
21
  this.addDefaultPath('dcVoltage','electrical.inverters.dc.voltage')
23
22
  .read=(buff)=>{return this.NaNif(buff.readInt16LE(3),0x7FFF)/100}
@@ -18,7 +18,6 @@ class VictronInverterRS extends VictronSensor{
18
18
  const md = this.addMetadatum('chargerError','', 'charger error',
19
19
  (buff)=>{return VC.ChargerError(buff.readIntU8(1))})
20
20
  md.default='electrical.inverters.{id}.error'
21
- md.notify=true
22
21
 
23
22
  this.addMetadatum('batteryVoltage','V', 'battery voltage',
24
23
  (buff)=>{return this.NaNif(buff.readInt16LE(2),0x7FFF)/100})
@@ -33,7 +33,6 @@ class VictronSmartBatteryProtect extends VictronSensor{
33
33
  (buff)=>{return VC.ChargerError.get(buff.readUInt8(3))})
34
34
  this.addMetadatum('alarmReason','', 'alarm reason',
35
35
  (buff)=>{return buff.readUInt16LE(4)})
36
- this.getPath("alarmReason").notify=true
37
36
  this.addMetadatum('warningReason','', 'warning reason', //TODO
38
37
  (buff)=>{return (buff.readUInt16LE(5))})
39
38
  this.addMetadatum('channel1Voltage','V', 'channel one voltage',
@@ -45,9 +44,9 @@ class VictronSmartBatteryProtect extends VictronSensor{
45
44
  }
46
45
  emitValuesFrom(decData){
47
46
  super.emitValuesFrom(decData)
48
- const alarm = this.getPath("alarm").read(decData)
47
+ const alarm = this.getPath("alarmReason").read(decData)
49
48
  if (alarm>0)
50
- this.emitAlarm("alarm",alarm)
49
+ this.emitAlarm("alarmReason",alarm)
51
50
  }
52
51
 
53
52
  }
@@ -100,7 +100,7 @@ class XiaomiMiBeacon extends BTSensor{
100
100
  }
101
101
 
102
102
  getDescription(){
103
- return `<div><p><img src="../bt-sensors-plugin-sk/images/LYWSD03MMC-Device.jpg" alt=LYWSD03MMC image" style="float: left; margin-right: 10px;" /> The LYWSD03MMC temperature and humidity sensor is an inexpensive environmental sensor from Xiaomi Inc. <p> WARNING: If you use the GATT connection, you won't need an encrypytion//bind key to get your data but the energy cost of maintaining a GATT connection is high and will drain your battery in about 2 weeks of persistent use. Instead follow the instructions <a href=https://github.com/PiotrMachowski/Xiaomi-cloud-tokens-extractor/?tab=readme-ov-file#linux--home-assistant-in-ssh--web-terminal target="_blank">here</a> to get your device's encryption key.<div>`
103
+ return `<div><p><img src="../bt-sensors-plugin-sk/images/LYWSD03MMC-Device.jpg" alt=LYWSD03MMC image" style="float: left; margin-right: 10px;" /> The LYWSD03MMC temperature and humidity sensor is an inexpensive environmental sensor from Xiaomi Inc. <p> Follow the instructions <a href=https://github.com/PiotrMachowski/Xiaomi-cloud-tokens-extractor/?tab=readme-ov-file#linux--home-assistant-in-ssh--web-terminal target="_blank">here</a> to get your device's encryption key.<div>`
104
104
  }
105
105
 
106
106
  getManufacturer(){
@@ -131,19 +131,18 @@ class XiaomiMiBeacon extends BTSensor{
131
131
  })
132
132
  }
133
133
 
134
- decryptV2and3(data){
134
+ decryptV2and3(data, index=11){
135
135
  const encryptedPayload = data.subarray(-4);
136
- const xiaomi_mac = data.subarray(5,11)
137
- const nonce = Buffer.concat([data.subarray(0, 5), data.subarray(-4,-1), xiaomi_mac.subarray(-1)]);
136
+ const nonce = Buffer.concat([data.subarray(0, 5), data.subarray(-4,-1), (this._mac_reversed)]);
138
137
  const cipher = crypto.createDecipheriv('aes-128-ccm', Buffer.from(this.encryptionKey,"hex"), nonce, { authTagLength: 4});
139
138
  cipher.setAAD(Buffer.from('11', 'hex'), { plaintextLength: encryptedPayload.length });
140
139
 
141
140
  return cipher.update(encryptedPayload)
142
141
  }
143
- decryptV4and5(data){
144
- const encryptedPayload = data.subarray(11,-7);
145
- const xiaomi_mac = data.subarray(5,11)
146
- const nonce = Buffer.concat([xiaomi_mac, data.subarray(2, 5), data.subarray(-7,-4)]);
142
+ decryptV4and5(data, index=11){
143
+
144
+ const encryptedPayload = data.subarray(index,-7);
145
+ const nonce = Buffer.concat([(this._mac_reversed), data.subarray(2, 5), data.subarray(-7,-4)]);
147
146
  const cipher = crypto.createDecipheriv('aes-128-ccm', Buffer.from(this.encryptionKey,"hex"), nonce, { authTagLength: 4});
148
147
  cipher.setAAD(Buffer.from('11', 'hex'), { plaintextLength: encryptedPayload.length });
149
148
  cipher.setAuthTag(data.subarray(-4))
@@ -151,7 +150,7 @@ class XiaomiMiBeacon extends BTSensor{
151
150
  }
152
151
 
153
152
  hasGATT(){
154
- return true
153
+ return false
155
154
  }
156
155
 
157
156
  propertiesChanged(props){
@@ -160,35 +159,64 @@ class XiaomiMiBeacon extends BTSensor{
160
159
  if (!props.hasOwnProperty("ServiceData")) return
161
160
 
162
161
  const data = this.getServiceData(this.constructor.SERVICE_MIBEACON)
163
- var dec
162
+ if (data.length<12) return
163
+
164
+ let dec=[]
165
+ let dataIndex=5
166
+
167
+ const frameControl = data.readUInt16LE(0)
168
+ const isEncrypted = (frameControl >> 3) & 1
169
+ const encryptionVersion = frameControl >> 12
170
+ const mesh = (frameControl >> 7) & 1; // mesh device
171
+ const authMode = (frameControl >> 10) & 3;
172
+ const solicited = (frameControl >> 9) & 1;
173
+ const registered = (frameControl >> 8) & 1;
174
+ const objectInclude = (frameControl >> 6) & 1; // object/payload data present
175
+ const capabilityInclude = (frameControl >> 5) & 1; // capability byte present
176
+ const macInclude = (frameControl >> 4) & 1; // MAC address included in payload
177
+ const requestTiming = frameControl & 1;
178
+
164
179
  if (!this.encryptionKey){
165
180
  throw new Error(`${this.getNameAndAddress()} requires an encryption key.`)
166
181
  }
167
- if (this.encryptionVersion >= 4) {
168
- dec = this.decryptV4and5(data)
182
+ if (encryptionVersion >= 4) {
183
+ dec = this.decryptV4and5(data, macInclude?11:5)
169
184
  } else {
170
- if(this.encryptionVersion>=2){
171
- dec=this.decryptV2and3(data)
185
+ if(encryptionVersion>=2){
186
+ dec=this.decryptV2and3(data, macInclude?11:5)
172
187
  }
173
188
  }
174
189
  if (dec.length==0)
175
190
  throw new Error(`${this.getNameAndAddress()} received empty decrypted packet. Check that the bind/encryption key in config is correct.`)
176
191
 
177
- switch(dec[0]){
178
- case 0x0D:
192
+ const objCode = dec.readUInt16LE(0)
193
+
194
+ switch(objCode){
195
+ case 0x100D:
179
196
  this.emitData("temp",dec,3)
180
197
  this.emitData("humidity",dec,5)
181
198
  break
182
199
 
183
- case 0x0A:
200
+ case 0x100A:
184
201
  this.emitData("batteryStrength",dec,3)
185
202
  break
186
- case 0x04:
203
+ case 0x1004:
187
204
  this.emitData("temp",dec,3)
188
205
  break
189
- case 0x06:
206
+ case 0x1006:
190
207
  this.emitData("humidity",dec,3)
191
208
  break
209
+ case 0x4C01:
210
+ this.emit("temp",dec.readFloatLE(3)+273.15)
211
+ break
212
+
213
+ case 0x4C02:
214
+ this.emit("humidity",dec[3]/100)
215
+ break
216
+ case 0x4C03:
217
+ this.emit("batteryStrength",dec[3]/100)
218
+ break
219
+
192
220
  default:
193
221
  throw new Error(`${this.getNameAndAddress()} unable to parse decrypted service data (${util.inspect(dec)})`)
194
222
 
@@ -198,13 +226,13 @@ class XiaomiMiBeacon extends BTSensor{
198
226
  async init(){
199
227
 
200
228
  await super.init()
229
+ this._mac_reversed = (Buffer.from(this.getMacAddress().replaceAll(":",""), "hex")).reverse()
201
230
  const data = this.getServiceData(this.constructor.SERVICE_MIBEACON)
202
231
  if (!data || data.length<4)
203
- throw new Error(`Service Data ${this.constructor.SERVICE_MIBEACON} not available for ${this.getName()}`)
204
- const frameControl = data[0] + (data[1] << 8)
205
- this.deviceID = data[2] + (data[3] << 8)
206
- this.isEncrypted = (frameControl >> 3) & 1
207
- this.encryptionVersion = frameControl >> 12
232
+ this.setError(`Service Data ${this.constructor.SERVICE_MIBEACON} not available for ${this.getName()}`)
233
+ else
234
+ this.deviceID = data[2] + (data[3] << 8)
235
+
208
236
  }
209
237
  initSchema(){
210
238
  super.initSchema()