@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.
- package/README.md +28 -4
- package/bthome.html +105 -9
- package/bthome.js +29 -20
- 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
|
|
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
|
|
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>
|
|
137
|
+
<dd> <code>State-Prefix</code> + name of the device</dd>
|
|
85
138
|
<dt>payload <span class="property-type"></span></dt>
|
|
86
|
-
<dd>
|
|
139
|
+
<dd> decoded state data</dd>
|
|
87
140
|
</dl>
|
|
88
141
|
|
|
89
|
-
|
|
142
|
+
</h4>Events</h4>
|
|
90
143
|
<dl class="message-properties">
|
|
91
144
|
<dt>topic <span class="property-type">string</span></dt>
|
|
92
|
-
<dd>
|
|
145
|
+
<dd> <code>Event-Prefix</code> + name of the device</dd>
|
|
93
146
|
<dt>payload <span class="property-type"></span></dt>
|
|
94
|
-
<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>
|
|
100
|
-
<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
|
|
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
|
-
|
|
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
|
-
|
|
75
|
+
throw new Error( "unknown BT-Home " + msg.payload.addr );
|
|
78
76
|
}
|
|
79
77
|
else if( version !== 2 )
|
|
80
78
|
{
|
|
81
|
-
|
|
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
|
-
|
|
83
|
+
throw new Error( name + " encryption key needed" );
|
|
86
84
|
}
|
|
87
85
|
else if( (!encrypted) && node.devices[msg.payload.addr].key )
|
|
88
86
|
{
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|