@mschaeffler/node-red-bthome 0.5.1 → 1.1.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/btevent.js +37 -12
- package/bthome.html +15 -4
- package/bthome.js +90 -20
- package/package.json +1 -1
package/btevent.js
CHANGED
|
@@ -4,15 +4,19 @@ class BtEvent {
|
|
|
4
4
|
this._events = {};
|
|
5
5
|
this._prefix = prefix;
|
|
6
6
|
}
|
|
7
|
-
pushEvent(type,event)
|
|
7
|
+
pushEvent(type,event,data=null)
|
|
8
8
|
{
|
|
9
|
+
if( event && data !== null )
|
|
10
|
+
{
|
|
11
|
+
event = `${event}|${data}`;
|
|
12
|
+
}
|
|
9
13
|
switch( typeof this._events[type] )
|
|
10
14
|
{
|
|
11
15
|
case "undefined":
|
|
12
16
|
this._events[type] = event;
|
|
13
17
|
break;
|
|
14
18
|
case "string":
|
|
15
|
-
this._events[type] = [this._events[type]];
|
|
19
|
+
this._events[type] = [ this._events[type] ];
|
|
16
20
|
// fall through
|
|
17
21
|
case "object":
|
|
18
22
|
this._events[type].push( event );
|
|
@@ -21,26 +25,47 @@ class BtEvent {
|
|
|
21
25
|
}
|
|
22
26
|
eventMessages(name)
|
|
23
27
|
{
|
|
24
|
-
|
|
28
|
+
function pushResult(type,event,index=null)
|
|
29
|
+
{
|
|
30
|
+
if( event )
|
|
31
|
+
{
|
|
32
|
+
const help = event.split( '|' );
|
|
33
|
+
let payload = { type: type, event: help[0] };
|
|
34
|
+
let topic;
|
|
35
|
+
if( index === null )
|
|
36
|
+
{
|
|
37
|
+
topic = `${prefix}${name}/${help[0]}`;
|
|
38
|
+
}
|
|
39
|
+
else
|
|
40
|
+
{
|
|
41
|
+
topic = `${prefix}${name}/${index}/${help[0]}`;
|
|
42
|
+
payload.id = index;
|
|
43
|
+
}
|
|
44
|
+
if( help[1] !== undefined )
|
|
45
|
+
{
|
|
46
|
+
payload.data = help[1];
|
|
47
|
+
}
|
|
48
|
+
result.push( {
|
|
49
|
+
topic: topic,
|
|
50
|
+
payload: payload
|
|
51
|
+
} );
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let result = [];
|
|
56
|
+
const prefix = this._prefix;
|
|
25
57
|
for( const t in this._events )
|
|
26
58
|
{
|
|
27
59
|
const event = this._events[t];
|
|
28
60
|
if( typeof event == "string" )
|
|
29
61
|
{
|
|
30
|
-
|
|
31
|
-
{
|
|
32
|
-
result.push( { topic: `${this._prefix}${name}/${event}`, payload: { type: t, event: event } } );
|
|
33
|
-
}
|
|
62
|
+
pushResult( t, event );
|
|
34
63
|
}
|
|
35
64
|
else
|
|
36
65
|
{
|
|
37
66
|
for( const i in event )
|
|
38
67
|
{
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
const index = Number( i ) + 1;
|
|
42
|
-
result.push( { topic: `${this._prefix}${name}/${index}/${event[i]}`, payload: { type: t, id: index, event: event[i] } } );
|
|
43
|
-
}
|
|
68
|
+
pushResult( t, event[i], Number( i ) + 1 );
|
|
44
69
|
}
|
|
45
70
|
}
|
|
46
71
|
}
|
package/bthome.html
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
defaults: {
|
|
6
6
|
name: {value:""},
|
|
7
7
|
devices:{value:"[]",required:true},
|
|
8
|
-
|
|
8
|
+
counterMode:{value:"none",required:true},
|
|
9
9
|
statusPrefix:{value:""},
|
|
10
10
|
eventPrefix:{value:""},
|
|
11
11
|
contextVar:{value:"bthome",required:true},
|
|
12
|
-
contextStore:{value:"none",required:true}
|
|
12
|
+
contextStore:{value:"none",required:true},
|
|
13
|
+
batteryState:{value:true,required:true}
|
|
13
14
|
},
|
|
14
15
|
inputs:1,
|
|
15
16
|
outputs:2,
|
|
@@ -67,8 +68,12 @@
|
|
|
67
68
|
<input type="text" id="node-input-devices">
|
|
68
69
|
</div>
|
|
69
70
|
<div class="form-row">
|
|
70
|
-
<label for="node-input-
|
|
71
|
-
<
|
|
71
|
+
<label for="node-input-counterMode"><i class="fa fa-clock-o"></i> Countermode</label>
|
|
72
|
+
<select type="text" id="node-input-counterMode">
|
|
73
|
+
<option value="none">no check</option>
|
|
74
|
+
<option value="rising">always rising</option>
|
|
75
|
+
<option value="time">time stamp</option>
|
|
76
|
+
</select>
|
|
72
77
|
</div>
|
|
73
78
|
<div class="form-row">
|
|
74
79
|
<label for="node-input-statusPrefix"><i class="fa fa-star"></i> Status-Prefix</label>
|
|
@@ -86,6 +91,10 @@
|
|
|
86
91
|
<label for="node-input-contextVar"><i class="fa fa-database"></i> Context-Variable</label>
|
|
87
92
|
<input type="text" id="node-input-contextVar"></input>
|
|
88
93
|
</div>
|
|
94
|
+
<div class="form-row">
|
|
95
|
+
<label for="node-input-batteryState"><i class="fa fa-"></i> battery is state</label>
|
|
96
|
+
<input type="checkbox" id="node-input-batteryState" style="display:inline-block; width:20px; vertical-align:baseline;">
|
|
97
|
+
</div>
|
|
89
98
|
</script>
|
|
90
99
|
|
|
91
100
|
<script type="text/x-red" data-help-name="bthome">
|
|
@@ -177,6 +186,8 @@
|
|
|
177
186
|
<dd> name of the variable in flow context storage.</dd>
|
|
178
187
|
<dt>Contextstore <span class="property-type">string</span></dt>
|
|
179
188
|
<dd> context store to be used.</dd>
|
|
189
|
+
<dt>battery is state <span class="property-type">Boolean</span></dt>
|
|
190
|
+
<dd> battery level is included in state mesage.</dt>
|
|
180
191
|
</dl>
|
|
181
192
|
|
|
182
193
|
<h4>Device-Configuration</h4>
|
package/bthome.js
CHANGED
|
@@ -10,11 +10,12 @@ module.exports = function(RED) {
|
|
|
10
10
|
var node = this;
|
|
11
11
|
this.flowcontext = this.context().flow;
|
|
12
12
|
this.devices = JSON.parse( config.devices ?? "{}" );
|
|
13
|
-
this.
|
|
13
|
+
this.counterMode = config.counterMode ?? "none";
|
|
14
14
|
this.statusPrefix = config.statusPrefix ? config.statusPrefix+'/' : "";
|
|
15
15
|
this.eventPrefix = config.eventPrefix ? config.eventPrefix +'/' : "";
|
|
16
16
|
this.contextVar = config.contextVar ?? "bthome";
|
|
17
17
|
this.contextStore = config.contextStore ?? "none";
|
|
18
|
+
this.batteryState = Boolean( config.batteryState );
|
|
18
19
|
this.data = {};
|
|
19
20
|
this.statistics = { ok:0, dup:0, old:0, err:0 };
|
|
20
21
|
node.status( "" );
|
|
@@ -84,14 +85,26 @@ module.exports = function(RED) {
|
|
|
84
85
|
const mac = Tools.mac2bytes( msg.payload.addr );
|
|
85
86
|
const ciphertext = Buffer.from( rawdata.slice( 0, -8 ) );
|
|
86
87
|
const counter = rawdata.slice( -8, -4 );
|
|
87
|
-
|
|
88
|
+
const counterInt = counter[0] | (counter[1]<<8) | (counter[2]<<16) | (counter[3]<<24);
|
|
89
|
+
switch( node.counterMode )
|
|
88
90
|
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
case "rising":
|
|
92
|
+
if( counterInt > ( item.lastCounter ?? -1 ) )
|
|
93
|
+
{
|
|
94
|
+
item.lastCounter = counterInt;
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
{
|
|
98
|
+
throw new Error( "bthome "+msg.payload.gateway+" "+name+" "+counterInt+" <= "+item.lastCounter );
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
case "time":
|
|
102
|
+
const deltaTime = msgTime - counterInt*1000;
|
|
103
|
+
if( deltaTime > 30000 || deltaTime < -15000 )
|
|
104
|
+
{
|
|
105
|
+
throw new Error( "bthome "+msg.payload.gateway+" "+name+" "+(new Date(counterInt*1000))+" "+deltaTime );
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
95
108
|
}
|
|
96
109
|
const mic = Buffer.from( rawdata.slice( -4 ) );
|
|
97
110
|
const nonce = Buffer.from( mac.concat( Tools.uuid16, dib, counter ) );
|
|
@@ -101,17 +114,34 @@ module.exports = function(RED) {
|
|
|
101
114
|
decipher.final();
|
|
102
115
|
}
|
|
103
116
|
|
|
104
|
-
function
|
|
117
|
+
function decodeMsg()
|
|
105
118
|
{
|
|
106
|
-
|
|
119
|
+
let counter = {};
|
|
120
|
+
|
|
121
|
+
function setData(name,value)
|
|
107
122
|
{
|
|
108
|
-
item.data
|
|
123
|
+
if( item.data === undefined )
|
|
124
|
+
{
|
|
125
|
+
item.data = {};
|
|
126
|
+
}
|
|
127
|
+
switch( typeof counter[name] )
|
|
128
|
+
{
|
|
129
|
+
case "undefined":
|
|
130
|
+
counter[name] = 1;
|
|
131
|
+
item.data[name] = value;
|
|
132
|
+
break;
|
|
133
|
+
case "boolean":
|
|
134
|
+
case "number":
|
|
135
|
+
case "string":
|
|
136
|
+
item.data[name] = [ item.data[name] ];
|
|
137
|
+
// fall through
|
|
138
|
+
case "object":
|
|
139
|
+
counter[name]++;
|
|
140
|
+
item.data[name].push( value );
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
109
143
|
}
|
|
110
|
-
item.data[name] = value;
|
|
111
|
-
}
|
|
112
144
|
|
|
113
|
-
function decodeMsg()
|
|
114
|
-
{
|
|
115
145
|
rawdata = new Rawdata( rawdata );
|
|
116
146
|
while( rawdata.length() > 0 )
|
|
117
147
|
{
|
|
@@ -122,11 +152,32 @@ module.exports = function(RED) {
|
|
|
122
152
|
pid = rawdata.getUInt8();
|
|
123
153
|
break;
|
|
124
154
|
case 0x01:
|
|
125
|
-
|
|
155
|
+
if( node.batteryState )
|
|
156
|
+
{
|
|
157
|
+
setData( "battery", rawdata.getUInt8() );
|
|
158
|
+
delete item.battery;
|
|
159
|
+
}
|
|
160
|
+
else
|
|
161
|
+
{
|
|
162
|
+
item.battery = rawdata.getUInt8();
|
|
163
|
+
delete item.data?.battery;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
case 0x04:
|
|
167
|
+
setData( "pressure", rawdata.getUInt24() * 0.01 );
|
|
126
168
|
break;
|
|
127
169
|
case 0x05:
|
|
128
170
|
setData( "lux", rawdata.getUInt24() * 0.01 );
|
|
129
171
|
break;
|
|
172
|
+
case 0x08:
|
|
173
|
+
setData( "dewpoint", rawdata.getInt16() * 0.01 );
|
|
174
|
+
break;
|
|
175
|
+
case 0x0C:
|
|
176
|
+
setData( "voltage", rawdata.getUInt16() * 0.001 );
|
|
177
|
+
break;
|
|
178
|
+
case 0x20:
|
|
179
|
+
setData( "rain", Boolean( rawdata.getUInt8() ) );
|
|
180
|
+
break;
|
|
130
181
|
case 0x21:
|
|
131
182
|
events.pushEvent( "motion", rawdata.getEnum( ["","motion"] ) );
|
|
132
183
|
break;
|
|
@@ -147,15 +198,33 @@ module.exports = function(RED) {
|
|
|
147
198
|
case 0x3A:
|
|
148
199
|
events.pushEvent( "button", rawdata.getEnum( ["","S","SS","SSS","L"] ) );
|
|
149
200
|
break;
|
|
201
|
+
case 0x3C:
|
|
202
|
+
events.pushEvent( "dimmer", rawdata.getEnum( ["","Left","Right"] ), rawdata.getUInt8() );
|
|
203
|
+
break;
|
|
150
204
|
case 0x3F:
|
|
151
205
|
setData( "tilt", rawdata.getInt16() * 0.1 );
|
|
152
206
|
break;
|
|
153
207
|
case 0x40:
|
|
154
208
|
setData( "distance", rawdata.getUInt16() );
|
|
155
209
|
break;
|
|
210
|
+
case 0x44:
|
|
211
|
+
setData( "wind", rawdata.getUInt16() * 0.01 );
|
|
212
|
+
break;
|
|
156
213
|
case 0x45:
|
|
157
214
|
setData( "temperature", rawdata.getInt16() * 0.1 );
|
|
158
215
|
break;
|
|
216
|
+
case 0x46:
|
|
217
|
+
setData( "uv", rawdata.getUInt8() * 0.1 );
|
|
218
|
+
break;
|
|
219
|
+
case 0x5E:
|
|
220
|
+
setData( "direction", rawdata.getUInt16() * 0.01 );
|
|
221
|
+
break;
|
|
222
|
+
case 0x5F:
|
|
223
|
+
setData( "precipitation", rawdata.getUInt16() * 0.1 );
|
|
224
|
+
break;
|
|
225
|
+
case 0x60:
|
|
226
|
+
setData( "channel", rawdata.getUInt8() );
|
|
227
|
+
break;
|
|
159
228
|
case 0xF0:
|
|
160
229
|
item.typeId = rawdata.getUInt16();
|
|
161
230
|
break;
|
|
@@ -179,6 +248,7 @@ module.exports = function(RED) {
|
|
|
179
248
|
rawdata.reset();
|
|
180
249
|
}
|
|
181
250
|
}
|
|
251
|
+
//console.log(counter)
|
|
182
252
|
}
|
|
183
253
|
|
|
184
254
|
function checkPid()
|
|
@@ -238,15 +308,15 @@ module.exports = function(RED) {
|
|
|
238
308
|
try
|
|
239
309
|
{
|
|
240
310
|
checkMsg();
|
|
241
|
-
if( encrypted )
|
|
242
|
-
{
|
|
243
|
-
decryptMsg();
|
|
244
|
-
}
|
|
245
311
|
if( item == undefined )
|
|
246
312
|
{
|
|
247
313
|
item = { pid: null, typeId: null, gw: {} };
|
|
248
314
|
node.data[name] = item;
|
|
249
315
|
}
|
|
316
|
+
if( encrypted )
|
|
317
|
+
{
|
|
318
|
+
decryptMsg();
|
|
319
|
+
}
|
|
250
320
|
decodeMsg();
|
|
251
321
|
if( checkPid() )
|
|
252
322
|
{
|