@mschaeffler/node-red-bthome 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +28 -4
  2. package/bthome.html +105 -9
  3. package/bthome.js +29 -20
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -8,10 +8,10 @@ At the moment these sensors are implemented and tested:
8
8
  - Shelly BLU Door/Window
9
9
  - Shelly BLU H&T
10
10
  - Shelly BLU Button 1
11
- - Shelly BLU Button Tough 1
11
+ - Shelly BLU Button Tough 1
12
12
  - Shelly BLU RC Button 4
13
13
  - Shelly BLU Wall Switch 4
14
- - Shelly BLU Motion
14
+ - Shelly BLU Motion
15
15
 
16
16
  ## Capture of Raw Frames
17
17
 
@@ -19,6 +19,10 @@ The raw data frames are captured by Shelly devices with Bluetooth (Gen2 up to Ge
19
19
 
20
20
  [This is the script to be used.](https://raw.githubusercontent.com/m-schaeffler/ShellyScripts/refs/heads/main/ShellyBlu.js)
21
21
 
22
+ ## Encryption
23
+
24
+ is at the moment not supported, an update will follow shortly.
25
+
22
26
  ## Install
23
27
 
24
28
  ```
@@ -64,14 +68,14 @@ There are two output ports:
64
68
 
65
69
  |msg. | type | description |
66
70
  |:-------|:-------|:----------------------------------|
67
- |topic | string | State-Prefix + name of the device|
71
+ |topic | string | `State-Prefix` + name of the device|
68
72
  |payload | object | decoded state data|
69
73
 
70
74
  ### Events
71
75
 
72
76
  |msg. | type | description |
73
77
  |:-------|:-------|:----------------------------------|
74
- |topic | string | Event-Prefix + name of the device|
78
+ |topic | string | `Event-Prefix` + name of the device|
75
79
  |payload | object | data of the decoded event|
76
80
 
77
81
  ## Parameters
@@ -105,6 +109,26 @@ An example for such a config from the unit tests:
105
109
 
106
110
  ### Context storage
107
111
 
112
+ All recorded data can be stored in a flow context variable for
113
+ - initialisation
114
+ - statistics
115
+ - visualisation
116
+
117
+ Example:
118
+ ```
119
+ {
120
+ "dev_unencrypted_1":
121
+ {
122
+ "pid": 164,
123
+ "time": 1745395033113,
124
+ "encrypted": false,
125
+ "battery": 100,
126
+ "gw": { "Shelly Gateway": { "time": 1745395033113, "rssi":-85 } },
127
+ data": { "humidity":56, "temperature":-21.3 }
128
+ }
129
+ }
130
+ ```
131
+
108
132
  ## Example Flow
109
133
 
110
134
  [example flow](https://github.com/m-schaeffler/node-red-my-nodes/raw/main/node-red-bthome/examples/bthome.json)
package/bthome.html CHANGED
@@ -74,30 +74,126 @@
74
74
 
75
75
  <script type="text/x-red" data-help-name="bthome">
76
76
 
77
- <p>A Node Red node to decrypt and decode bthome frames.</p>
77
+ <p>A Node Red node to decrypt and decode raw data frames from <a href="https://bthome.io">BT-Home</a> sensors.</p>
78
78
 
79
- <p>.</p>
79
+ <p>At the moment these sensors are implemented and tested:</p>
80
+ <ul>
81
+ <li>Shelly BLU Door/Window</li>
82
+ <li>Shelly BLU H&T</li>
83
+ <li>Shelly BLU Button 1</li>
84
+ <li>Shelly BLU Button Tough 1</li>
85
+ <li>Shelly BLU RC Button 4</li>
86
+ <li>Shelly BLU Wall Switch 4</li>
87
+ <li>Shelly BLU Motion</li>
88
+ </ul>
80
89
 
90
+ <h3>Capture of Raw Frames</h3>
91
+ <p>The raw data frames are captured by Shelly devices with Bluetooth (Gen2 up to Gen4) and then sent via MQTT to Node-Red.</p>
92
+ <a href="https://raw.githubusercontent.com/m-schaeffler/ShellyScripts/refs/heads/main/ShellyBlu.js">This is the script to be used.</a>
93
+
94
+ <h3>Encryption</h3>
95
+ <p>is at the moment not supported, an update will follow shortly.</p>
96
+
81
97
  <h3>Input</h3>
98
+ <dl class="message-properties">
99
+ <dt>payload <span class="property-type">object</span></dt>
100
+ <dd> data from Shelly script</dd>
101
+ </dl>
102
+
103
+ <h4>msg.payload</h4>
104
+ <p>Only the first two values are needed, the others are optional.</p>
105
+ <dl class="message-properties">
106
+ <dt>payload.addr <span class="property-type">string</span></dt>
107
+ <dd> mac of the BT-Home device (needed)</dd>
108
+ <dt>payload.data <span class="property-type">array of bytes</span></dt>
109
+ <dd> raw BT-Home message (needed)</dd>
110
+ <dt>payload.rssi <span class="property-type">number</span></dt>
111
+ <dd> signal strength</dd>
112
+ <dt>payload.time <span class="property-type">number</span></dt>
113
+ <dd> Javscript timestamp of the reception</dd>
114
+ <dt>payload.gateway <span class="property-type">string</span></dt>
115
+ <dd> name of the geteway</dd>
116
+ </dl>
117
+
118
+ <p>This is an example of such a message payload:</p>
119
+ <code>{<br>
120
+ "addr": "11:22:33:44:55:66",<br>
121
+ "rssi": -85,<br>
122
+ "time": 1745395033113,<br>
123
+ "gateway": "Shelly Gateway",<br>
124
+ "data": [68,0,164,1,100,46,56,69,43,255]<br>
125
+ }</code>
126
+
127
+ <h3>Outputs</h3>
128
+ <p>There are two output ports:</p>
129
+ <ol>
130
+ <li>one for meassurement values (states)</li>
131
+ <li>one for actions done with the devices (events)</li>
132
+ </ol>
133
+
134
+ <h4>State</h4>
82
135
  <dl class="message-properties">
83
136
  <dt>topic <span class="property-type">string</span></dt>
84
- <dd> topic for the output message</dd>
137
+ <dd> <code>State-Prefix</code> + name of the device</dd>
85
138
  <dt>payload <span class="property-type"></span></dt>
86
- <dd> payload for the output message</dd>
139
+ <dd> decoded state data</dd>
87
140
  </dl>
88
141
 
89
- <h3>Outputs</h3>
142
+ </h4>Events</h4>
90
143
  <dl class="message-properties">
91
144
  <dt>topic <span class="property-type">string</span></dt>
92
- <dd> same is in corresponding input message</dd>
145
+ <dd> <code>Event-Prefix</code> + name of the device</dd>
93
146
  <dt>payload <span class="property-type"></span></dt>
94
- <dd> same is in corresponding input message</dd>
147
+ <dd> data of the decoded event</dd>
95
148
  </dl>
96
149
 
97
150
  <h3>Parameters</h3>
98
151
  <dl class="message-properties">
99
- <dt>devices <span class="property-type">JSON</span></dt>
100
- <dd> data of the connected BT-Home devices.</dd>
152
+ <dt>Devices <span class="property-type">JSON</span></dt>
153
+ <dd> configuration of the BT-Home devices.</dd>
154
+ <dt>Status-Prefix <span class="property-type">string</span></dt>
155
+ <dd> prefix for the topic for state output.</dd>
156
+ <dt>Event-Prefix <span class="property-type">string</span></dt>
157
+ <dd> prefix for the topic for event output.</dd>
158
+ <dt>Context-Variable <span class="property-type">string</span></dt>
159
+ <dd> name of the variable in flow context storage.</dd>
160
+ <dt>Contextstore <span class="property-type">string</span></dt>
161
+ <dd> context store to be used.</dd>
101
162
  </dl>
102
163
 
164
+ <h4>Device-Configuration</h4>
165
+ <p>With this JSON string the installed <a href="https://bthome.io">BT-Home</a> devices are configured:</p>
166
+ <code>{<br>
167
+ "<mac address of the device>": { "topic": "<name of the device>", "key": "<encryption key, if device is encrypted>" }<br>
168
+ }</code>
169
+
170
+ <p>An example for such a config from the unit tests:</p>
171
+ <code>{<br>
172
+ "11:22:33:44:55:66": { "topic": "dev_unencrypted_1" },<br>
173
+ "00:01:02:03:04:05": { "topic": "dev_unencrypted_2" },<br>
174
+ "00:10:20:30:40:50": { "topic": "dev_encrypted_1", "key": "00112233445566778899AABBCCDDEEFF" },<br>
175
+ "00:00:00:00:00:00": { "topic": "dev_encrypted_2", "key": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] }<br>
176
+ }</code>
177
+
178
+ <h4>Context storage</h4>
179
+ <p>All recorded data can be stored in a flow context variable for</p>
180
+ <ul>
181
+ <li>initialisation</li>
182
+ <li>statistics</li>
183
+ <li>visualisation</li>
184
+ </ul>
185
+
186
+ <p>Example:</p>
187
+ <code>{<br>
188
+ "dev_unencrypted_1":<br>
189
+ {<br>
190
+ "pid": 164,<br>
191
+ "time": 1745395033113,<br>
192
+ "encrypted": false,<br>
193
+ "battery": 100,<br>
194
+ "gw": { "Shelly Gateway": { "time": 1745395033113, "rssi":-85 } },<br>
195
+ data": { "humidity":56, "temperature":-21.3 }<br>
196
+ }
197
+ }<code>
198
+
103
199
  </script>
package/bthome.js CHANGED
@@ -9,8 +9,8 @@ module.exports = function(RED) {
9
9
  var node = this;
10
10
  this.flowcontext = this.context().flow;
11
11
  this.devices = JSON.parse( config.devices ?? "{}" );
12
- this.statusPrefix = config.statusPrefix ? config.statusPrefix+'/' :"";
13
- this.eventPrefix = config.eventPrefix ? config.eventPrefix+'/' : "";
12
+ this.statusPrefix = config.statusPrefix ? config.statusPrefix+'/' : "";
13
+ this.eventPrefix = config.eventPrefix ? config.eventPrefix +'/' : "";
14
14
  this.contextVar = config.contextVar ?? "bthome";
15
15
  this.contextStore = config.contextStore ?? "none";
16
16
  this.data = {};
@@ -25,7 +25,7 @@ module.exports = function(RED) {
25
25
  }
26
26
  else
27
27
  {
28
- console.log( "context read", value );
28
+ //console.log( "context read", value );
29
29
  if( value !== undefined )
30
30
  {
31
31
  node.data = value;
@@ -64,9 +64,7 @@ module.exports = function(RED) {
64
64
  node.on('input', function(msg,send,done) {
65
65
  if( ! Array.isArray( msg.payload.data ) )
66
66
  {
67
- node.error( "msg.payload.data must be an Array!" );
68
- node.trace( "msg processed" );
69
- done();
67
+ done( "msg.payload.data must be an Array!" );
70
68
  return;
71
69
  }
72
70
 
@@ -74,30 +72,38 @@ module.exports = function(RED) {
74
72
  {
75
73
  if( name === undefined )
76
74
  {
77
- node.error( "unknown BT-Home " + msg.payload.addr );
75
+ throw new Error( "unknown BT-Home " + msg.payload.addr );
78
76
  }
79
77
  else if( version !== 2 )
80
78
  {
81
- node.error( "wrong BT-Home version " + version );
79
+ throw new Error( "wrong BT-Home version " + version );
82
80
  }
83
81
  else if( encrypted && !node.devices[msg.payload.addr].key )
84
82
  {
85
- node.error( name + " encryption key needed" );
83
+ throw new Error( name + " encryption key needed" );
86
84
  }
87
85
  else if( (!encrypted) && node.devices[msg.payload.addr].key )
88
86
  {
89
- node.error( name + " encrypted messages needed" );
87
+ throw new Error( name + " encrypted messages needed" );
90
88
  }
91
- else
92
- {
93
- return true;
94
- }
95
- return false;
96
89
  }
97
90
 
98
91
  function decryptMsg()
99
92
  {
100
- console.log("encrypted");
93
+ let mac = [];
94
+ for( const help of msg.payload.addr.split( ":" ) )
95
+ {
96
+ mac.push( Number.parseInt( help, 16 ) );
97
+ }
98
+ const uuid16 = [0xD2,0xFC];
99
+ const ciphertext = Buffer.from( rawdata.slice( 0, -8 ) );
100
+ const counter = rawdata.slice( -8, -4 );
101
+ const mic = Buffer.from( rawdata.slice( -4 ) );
102
+ const nonce = Buffer.from( mac.concat( uuid16, dib, counter ) );
103
+ const decipher = Crypto.createDecipheriv( "aes-128-ccm", node.devices[msg.payload.addr].key, nonce, { authTagLength: 4 } );
104
+ decipher.setAuthTag( mic );
105
+ rawdata = Array.from( decipher.update( ciphertext ) );
106
+ decipher.final();
101
107
  }
102
108
 
103
109
  function setData(name,value)
@@ -212,12 +218,12 @@ module.exports = function(RED) {
212
218
  const events = new BtEvent( node.eventPrefix );
213
219
  let item = node.data[name];
214
220
 
215
- if( checkMsg() )
221
+ try
216
222
  {
223
+ checkMsg();
217
224
  if( encrypted )
218
225
  {
219
226
  decryptMsg();
220
- done();return;
221
227
  }
222
228
  if( item == undefined )
223
229
  {
@@ -229,9 +235,12 @@ module.exports = function(RED) {
229
235
  {
230
236
  newMessage();
231
237
  }
238
+ done();
239
+ }
240
+ catch( e )
241
+ {
242
+ done( e.message );
232
243
  }
233
- node.trace( "msg processed" );
234
- done();
235
244
  });
236
245
  }
237
246
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mschaeffler/node-red-bthome",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "A Node Red node to decrypt and decode BT-Home frames",
5
5
  "author": {
6
6
  "name": "Mathias Schäffler",