@mschaeffler/node-red-bthome 1.0.0 → 1.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/btevent.js +50 -24
- package/bthome.html +8 -1
- package/bthome.js +103 -13
- package/package.json +1 -1
package/btevent.js
CHANGED
|
@@ -1,46 +1,72 @@
|
|
|
1
1
|
class BtEvent {
|
|
2
|
-
|
|
2
|
+
static eventLut = {
|
|
3
|
+
9: {
|
|
4
|
+
button: ["left","right"]
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
constructor(prefix,item)
|
|
3
8
|
{
|
|
4
9
|
this._events = {};
|
|
5
10
|
this._prefix = prefix;
|
|
11
|
+
this._item = item;
|
|
6
12
|
}
|
|
7
|
-
pushEvent(type,event)
|
|
13
|
+
pushEvent(type,event,data=null)
|
|
8
14
|
{
|
|
9
|
-
|
|
15
|
+
if( this._events[type] === undefined )
|
|
10
16
|
{
|
|
11
|
-
|
|
12
|
-
this._events[type] = event;
|
|
13
|
-
break;
|
|
14
|
-
case "string":
|
|
15
|
-
this._events[type] = [this._events[type]];
|
|
16
|
-
// fall through
|
|
17
|
-
case "object":
|
|
18
|
-
this._events[type].push( event );
|
|
19
|
-
break;
|
|
17
|
+
this._events[type] = [];
|
|
20
18
|
}
|
|
19
|
+
this._events[type].push( { event:event, data:data } );
|
|
21
20
|
}
|
|
22
|
-
eventMessages(name)
|
|
21
|
+
eventMessages(name,channel)
|
|
23
22
|
{
|
|
24
|
-
|
|
25
|
-
for( const t in this._events )
|
|
23
|
+
function pushResult(type,event,index=null)
|
|
26
24
|
{
|
|
27
|
-
|
|
28
|
-
if( typeof event == "string" )
|
|
25
|
+
if( event.event && event.data !== 0 )
|
|
29
26
|
{
|
|
30
|
-
|
|
27
|
+
let payload = { type: type, event: event.event };
|
|
28
|
+
let indexStr = "";;
|
|
29
|
+
if( channel !== null )
|
|
30
|
+
{
|
|
31
|
+
indexStr += "/"
|
|
32
|
+
indexStr += channel;
|
|
33
|
+
payload.channel = channel;
|
|
34
|
+
}
|
|
35
|
+
if( index !== null )
|
|
36
|
+
{
|
|
37
|
+
indexStr += "/";
|
|
38
|
+
indexStr += index;
|
|
39
|
+
payload.id = index;
|
|
40
|
+
}
|
|
41
|
+
if( event.data !== null )
|
|
31
42
|
{
|
|
32
|
-
|
|
43
|
+
payload.data = event.data;
|
|
33
44
|
}
|
|
45
|
+
result.push( {
|
|
46
|
+
topic: `${prefix}${name}${indexStr}/${event.event}`,
|
|
47
|
+
payload: payload
|
|
48
|
+
} );
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let result = [];
|
|
53
|
+
const prefix = this._prefix;
|
|
54
|
+
for( const t in this._events )
|
|
55
|
+
{
|
|
56
|
+
const event = this._events[t];
|
|
57
|
+
if( event.length == 1 )
|
|
58
|
+
{
|
|
59
|
+
pushResult( t, event[0] );
|
|
34
60
|
}
|
|
35
61
|
else
|
|
36
62
|
{
|
|
37
63
|
for( const i in event )
|
|
38
64
|
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
pushResult(
|
|
66
|
+
t,
|
|
67
|
+
event[i],
|
|
68
|
+
BtEvent.eventLut[this._item.typeId]?.[t]?.[i] ?? Number( i ) + 1
|
|
69
|
+
);
|
|
44
70
|
}
|
|
45
71
|
}
|
|
46
72
|
}
|
package/bthome.html
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
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,
|
|
@@ -90,6 +91,10 @@
|
|
|
90
91
|
<label for="node-input-contextVar"><i class="fa fa-database"></i> Context-Variable</label>
|
|
91
92
|
<input type="text" id="node-input-contextVar"></input>
|
|
92
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>
|
|
93
98
|
</script>
|
|
94
99
|
|
|
95
100
|
<script type="text/x-red" data-help-name="bthome">
|
|
@@ -181,6 +186,8 @@
|
|
|
181
186
|
<dd> name of the variable in flow context storage.</dd>
|
|
182
187
|
<dt>Contextstore <span class="property-type">string</span></dt>
|
|
183
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>
|
|
184
191
|
</dl>
|
|
185
192
|
|
|
186
193
|
<h4>Device-Configuration</h4>
|
package/bthome.js
CHANGED
|
@@ -3,6 +3,12 @@ const Tools = require( './tools.js' );
|
|
|
3
3
|
const Rawdata = require( "./rawdata.js" );
|
|
4
4
|
const BtEvent = require( "./btevent.js" );
|
|
5
5
|
|
|
6
|
+
class TypeIds {
|
|
7
|
+
static bluDW = 0x0202;
|
|
8
|
+
static bluRemote = 9;
|
|
9
|
+
}
|
|
10
|
+
Object.freeze( TypeIds );
|
|
11
|
+
|
|
6
12
|
module.exports = function(RED) {
|
|
7
13
|
|
|
8
14
|
function BtHomeNode(config) {
|
|
@@ -10,11 +16,12 @@ module.exports = function(RED) {
|
|
|
10
16
|
var node = this;
|
|
11
17
|
this.flowcontext = this.context().flow;
|
|
12
18
|
this.devices = JSON.parse( config.devices ?? "{}" );
|
|
13
|
-
this.counterMode = config.counterMode
|
|
19
|
+
this.counterMode = config.counterMode ?? "none";
|
|
14
20
|
this.statusPrefix = config.statusPrefix ? config.statusPrefix+'/' : "";
|
|
15
21
|
this.eventPrefix = config.eventPrefix ? config.eventPrefix +'/' : "";
|
|
16
22
|
this.contextVar = config.contextVar ?? "bthome";
|
|
17
23
|
this.contextStore = config.contextStore ?? "none";
|
|
24
|
+
this.batteryState = Boolean( config.batteryState );
|
|
18
25
|
this.data = {};
|
|
19
26
|
this.statistics = { ok:0, dup:0, old:0, err:0 };
|
|
20
27
|
node.status( "" );
|
|
@@ -113,17 +120,34 @@ module.exports = function(RED) {
|
|
|
113
120
|
decipher.final();
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
function
|
|
123
|
+
function decodeMsg()
|
|
117
124
|
{
|
|
118
|
-
|
|
125
|
+
let counter = {};
|
|
126
|
+
|
|
127
|
+
function setData(name,value)
|
|
119
128
|
{
|
|
120
|
-
item.data
|
|
129
|
+
if( item.data === undefined )
|
|
130
|
+
{
|
|
131
|
+
item.data = {};
|
|
132
|
+
}
|
|
133
|
+
switch( counter[name] )
|
|
134
|
+
{
|
|
135
|
+
case undefined:
|
|
136
|
+
counter[name] = 1;
|
|
137
|
+
item.data[name] = value;
|
|
138
|
+
break;
|
|
139
|
+
case 1:
|
|
140
|
+
item.data[name] = [ item.data[name] ];
|
|
141
|
+
// fall through
|
|
142
|
+
case 2:
|
|
143
|
+
case 3:
|
|
144
|
+
case 4:
|
|
145
|
+
counter[name]++;
|
|
146
|
+
item.data[name].push( value );
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
121
149
|
}
|
|
122
|
-
item.data[name] = value;
|
|
123
|
-
}
|
|
124
150
|
|
|
125
|
-
function decodeMsg()
|
|
126
|
-
{
|
|
127
151
|
rawdata = new Rawdata( rawdata );
|
|
128
152
|
while( rawdata.length() > 0 )
|
|
129
153
|
{
|
|
@@ -134,11 +158,41 @@ module.exports = function(RED) {
|
|
|
134
158
|
pid = rawdata.getUInt8();
|
|
135
159
|
break;
|
|
136
160
|
case 0x01:
|
|
137
|
-
|
|
161
|
+
if( node.batteryState )
|
|
162
|
+
{
|
|
163
|
+
setData( "battery", rawdata.getUInt8() );
|
|
164
|
+
delete item.battery;
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
{
|
|
168
|
+
item.battery = rawdata.getUInt8();
|
|
169
|
+
delete item.data?.battery;
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
case 0x04:
|
|
173
|
+
setData( "pressure", rawdata.getUInt24() * 0.01 );
|
|
138
174
|
break;
|
|
139
175
|
case 0x05:
|
|
140
176
|
setData( "lux", rawdata.getUInt24() * 0.01 );
|
|
141
177
|
break;
|
|
178
|
+
case 0x08:
|
|
179
|
+
setData( "dewpoint", rawdata.getInt16() * 0.01 );
|
|
180
|
+
break;
|
|
181
|
+
case 0x0C:
|
|
182
|
+
if( node.batteryState )
|
|
183
|
+
{
|
|
184
|
+
setData( "voltage", rawdata.getUInt16() * 0.001 );
|
|
185
|
+
delete item.voltage;
|
|
186
|
+
}
|
|
187
|
+
else
|
|
188
|
+
{
|
|
189
|
+
item.voltage = rawdata.getUInt16() * 0.001;
|
|
190
|
+
delete item.data?.voltage;
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case 0x20:
|
|
194
|
+
setData( "moisture", Boolean( rawdata.getUInt8() ) );
|
|
195
|
+
break;
|
|
142
196
|
case 0x21:
|
|
143
197
|
events.pushEvent( "motion", rawdata.getEnum( ["","motion"] ) );
|
|
144
198
|
break;
|
|
@@ -146,28 +200,58 @@ module.exports = function(RED) {
|
|
|
146
200
|
setData( "vibration", Boolean( rawdata.getUInt8() ) );
|
|
147
201
|
break;
|
|
148
202
|
case 0x2D:
|
|
203
|
+
{
|
|
149
204
|
let state = rawdata.getUInt8();
|
|
150
|
-
if( item.typeId ===
|
|
205
|
+
if( item.typeId === TypeIds.bluDW )
|
|
151
206
|
{
|
|
152
207
|
state = Boolean( state );
|
|
153
208
|
}
|
|
154
209
|
setData( "state", state );
|
|
155
210
|
break;
|
|
211
|
+
}
|
|
156
212
|
case 0x2E:
|
|
157
213
|
setData( "humidity", rawdata.getUInt8() );
|
|
158
214
|
break;
|
|
159
215
|
case 0x3A:
|
|
160
216
|
events.pushEvent( "button", rawdata.getEnum( ["","S","SS","SSS","L"] ) );
|
|
161
217
|
break;
|
|
218
|
+
case 0x3C:
|
|
219
|
+
{
|
|
220
|
+
const dimmer = rawdata.getUInt8();
|
|
221
|
+
const data = rawdata.getUInt8();
|
|
222
|
+
events.pushEvent( "dimmer", "dimmer", dimmer==1 ? data : -data );
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
162
225
|
case 0x3F:
|
|
163
226
|
setData( "tilt", rawdata.getInt16() * 0.1 );
|
|
164
227
|
break;
|
|
165
228
|
case 0x40:
|
|
166
|
-
|
|
229
|
+
{
|
|
230
|
+
const distance = rawdata.getUInt16();
|
|
231
|
+
setData( "distance", distance != 0 ? distance : null );
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case 0x44:
|
|
235
|
+
setData( "wind", rawdata.getUInt16() * 0.01 );
|
|
167
236
|
break;
|
|
168
237
|
case 0x45:
|
|
169
238
|
setData( "temperature", rawdata.getInt16() * 0.1 );
|
|
170
239
|
break;
|
|
240
|
+
case 0x46:
|
|
241
|
+
setData( "uv", rawdata.getUInt8() * 0.1 );
|
|
242
|
+
break;
|
|
243
|
+
case 0x59:
|
|
244
|
+
setData( "count", rawdata.getInt8() );
|
|
245
|
+
break;
|
|
246
|
+
case 0x5E:
|
|
247
|
+
setData( "direction", rawdata.getUInt16() * 0.01 );
|
|
248
|
+
break;
|
|
249
|
+
case 0x5F:
|
|
250
|
+
setData( "precipitation", rawdata.getUInt16() * 0.1 );
|
|
251
|
+
break;
|
|
252
|
+
case 0x60:
|
|
253
|
+
setData( "channel", rawdata.getUInt8() + 1 );
|
|
254
|
+
break;
|
|
171
255
|
case 0xF0:
|
|
172
256
|
item.typeId = rawdata.getUInt16();
|
|
173
257
|
break;
|
|
@@ -191,6 +275,11 @@ module.exports = function(RED) {
|
|
|
191
275
|
rawdata.reset();
|
|
192
276
|
}
|
|
193
277
|
}
|
|
278
|
+
if( item.typeId === TypeIds.bluRemote && item.data?.tilt )
|
|
279
|
+
{
|
|
280
|
+
events.pushEvent( "rotation", "rotation", item.data.tilt );
|
|
281
|
+
delete item.data.tilt;
|
|
282
|
+
}
|
|
194
283
|
}
|
|
195
284
|
|
|
196
285
|
function checkPid()
|
|
@@ -233,7 +322,7 @@ module.exports = function(RED) {
|
|
|
233
322
|
node.status( name );
|
|
234
323
|
send( [
|
|
235
324
|
item.data ? { topic:node.statusPrefix+name, payload:item.data } : null,
|
|
236
|
-
events.eventMessages( name )
|
|
325
|
+
events.eventMessages( name, item.data?.channel ?? null )
|
|
237
326
|
] );
|
|
238
327
|
}
|
|
239
328
|
|
|
@@ -244,8 +333,8 @@ module.exports = function(RED) {
|
|
|
244
333
|
const encrypted = Boolean( dib & 0x1 );
|
|
245
334
|
const version = dib >> 5;
|
|
246
335
|
let pid = null;
|
|
247
|
-
const events = new BtEvent( node.eventPrefix );
|
|
248
336
|
let item = node.data[name];
|
|
337
|
+
let events;
|
|
249
338
|
|
|
250
339
|
try
|
|
251
340
|
{
|
|
@@ -259,6 +348,7 @@ module.exports = function(RED) {
|
|
|
259
348
|
{
|
|
260
349
|
decryptMsg();
|
|
261
350
|
}
|
|
351
|
+
events = new BtEvent( node.eventPrefix, item );
|
|
262
352
|
decodeMsg();
|
|
263
353
|
if( checkPid() )
|
|
264
354
|
{
|