node-red-contrib-homebridge-automation 0.1.12-beta.9 → 0.2.1-beta.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 (41) hide show
  1. package/.github/npm-version-script.js +35 -43
  2. package/.github/workflows/Build and Publish.yml +81 -75
  3. package/README.md +7 -4
  4. package/eslint.config.mjs +34 -0
  5. package/package.json +34 -25
  6. package/src/HAP-NodeRed.html +71 -71
  7. package/src/HAP-NodeRed.js +32 -1082
  8. package/src/HapDeviceRoutes.js +59 -0
  9. package/src/hbBaseNode.js +94 -0
  10. package/src/hbConfigNode.js +239 -0
  11. package/src/hbConfigNode.test.js +2179 -0
  12. package/src/hbControlNode.js +77 -0
  13. package/src/hbEventNode.js +23 -0
  14. package/src/hbResumeNode.js +63 -0
  15. package/src/hbStatusNode.js +37 -0
  16. package/test/node-red/.config.nodes.json +453 -0
  17. package/test/node-red/.config.nodes.json.backup +453 -0
  18. package/test/node-red/.config.runtime.json +4 -0
  19. package/test/node-red/.config.runtime.json.backup +3 -0
  20. package/test/node-red/.config.users.json +23 -0
  21. package/test/node-red/.config.users.json.backup +20 -0
  22. package/test/node-red/.flows.json.backup +2452 -0
  23. package/test/node-red/flows.json +2453 -0
  24. package/test/node-red/package.json +6 -0
  25. package/test/node-red/settings.js +593 -0
  26. package/test/node-red/test/node-red/.config.nodes.json +430 -0
  27. package/test/node-red/test/node-red/.config.runtime.json +4 -0
  28. package/test/node-red/test/node-red/.config.runtime.json.backup +3 -0
  29. package/test/node-red/test/node-red/.config.users.json +20 -0
  30. package/test/node-red/test/node-red/.config.users.json.backup +17 -0
  31. package/test/node-red/test/node-red/package.json +6 -0
  32. package/test/node-red/test/node-red/settings.js +593 -0
  33. package/.eslintrc.js +0 -24
  34. package/.nycrc.json +0 -11
  35. package/src/lib/Accessory.js +0 -126
  36. package/src/lib/Characteristic.js +0 -30
  37. package/src/lib/HbAccessories.js +0 -167
  38. package/src/lib/Homebridge.js +0 -71
  39. package/src/lib/Homebridges.js +0 -68
  40. package/src/lib/Service.js +0 -307
  41. package/src/lib/register.js +0 -156
@@ -1,154 +1,24 @@
1
1
  var debug = require('debug')('hapNodeRed');
2
- var Queue = require('better-queue');
3
- // var register = require('./lib/register.js');
4
- var Homebridges = require('./lib/Homebridges.js').Homebridges;
5
- var HAPNodeJSClient = require('hap-node-client').HAPNodeJSClient;
6
2
 
7
- module.exports = function (RED) {
8
- var evDevices = [];
9
- var ctDevices = [];
10
- var hbDevices;
11
- var homebridge;
12
- var reqisterQueue = new Queue(function (node, cb) {
13
- _register.call(node.that, node, cb);
14
- }, {
15
- concurrent: 1,
16
- autoResume: false,
17
- maxRetries: 1000,
18
- retryDelay: 30000
19
- });
20
- reqisterQueue.pause();
21
-
22
- /**
23
- * hbConf - Configuration
24
- *
25
- * @param {type} n description
26
- * @return {type} description
27
- */
28
-
29
- function hbConf(n) {
30
- RED.nodes.createNode(this, n);
31
- this.username = n.username;
32
- this.macAddress = n.macAddress || '';
33
- this.password = this.credentials.password;
34
-
35
- this.users = {};
36
-
37
- if (homebridge) {
38
- if (this.macAddress) {
39
- // register additional PIN on existing instance
40
- homebridge.RegisterPin(this.macAddress, n.username);
41
- }
42
- } else {
43
- homebridge = new HAPNodeJSClient({
44
- "pin": n.username,
45
- "refresh": 900,
46
- "debug": false,
47
- "timeout": 20,
48
- "reqTimeout": 7000
49
- });
50
- reqisterQueue.pause();
51
- homebridge.on('Ready', function (accessories) {
52
- // evDevices = register.registerEv(homebridge, accessories);
53
- // ctDevices = register.registerCt(homebridge, accessories);
54
- hbDevices = new Homebridges(accessories);
55
- // debug("output", JSON.stringify(hbDevices.toList({ perms: 'ev'}), null, 4));
56
- // debug("evDevices", evDevices);
57
- // debug('Discovered %s evDevices', evDevices.length);
58
- debug('Discovered %s new evDevices', hbDevices.toList({
59
- perms: 'ev'
60
- }).length);
61
- // debug(hbDevices.toList({perms: 'pw'}));
3
+ const HBConfigNode = require('./hbConfigNode.js');
4
+ const HbEventNode = require('./hbEventNode'); // Import the class
5
+ const HbResumeNode = require('./hbResumeNode'); // Import the class
6
+ const HbControlNode = require('./hbControlNode');
7
+ const HbStatusNode = require('./hbStatusNode');
62
8
 
63
- var list = hbDevices.toList({
64
- perms: 'ev'
65
- });
9
+ const HapDeviceRoutes = require('./HapDeviceRoutes');
66
10
 
67
- var deleteSeen = [];
68
-
69
- for (var i = 0; i < list.length; i++) {
70
- var endpoint = list[i];
71
- // console.log("Checking", endpoint.fullName);
72
- if (deleteSeen[endpoint.fullName]) {
73
- console.log("WARNING: Duplicate device name", endpoint.fullName);
74
- // debug('Duplicate', endpoint);
75
- // response.event.payload.endpoints.splice(i, 1);
76
- } else {
77
- deleteSeen[endpoint.fullName] = true;
78
- }
79
- }
80
-
81
- deleteSeen = [];
82
-
83
- for (i = 0; i < list.length; i++) {
84
- endpoint = list[i];
85
- // console.log("Checking uniqueId", endpoint.uniqueId);
86
- if (deleteSeen[endpoint.uniqueId]) {
87
- console.log("ERROR: Parsing failed, duplicate uniqueID.", endpoint.fullName);
88
- // response.event.payload.endpoints.splice(i, 1);
89
- } else {
90
- deleteSeen[endpoint.uniqueId] = true;
91
- }
92
- }
93
- // evDevices.sort((a, b) => (a.sortName > b.sortName) ? 1 : ((b.sortName > a.sortName) ? -1 : 0));
94
- // ctDevices.sort((a, b) => (a.sortName > b.sortName) ? 1 : ((b.sortName > a.sortName) ? -1 : 0));
11
+ module.exports = function (RED) {
12
+ var hbDevices;
95
13
 
96
- // debug('Discovered %s ctDevices', ctDevices.length);
97
- debug('Discovered %s new ctDevices', hbDevices.toList({
98
- perms: 'pw'
99
- }).length);
100
- // debug('Discovered %s new ctDevices', hbDevices.toList({ perms: 'pw'}).length);
101
- // debug("Register Queue", reqisterQueue.getStats());
102
- reqisterQueue.resume();
103
- });
14
+ class hbConfigNode extends HBConfigNode {
15
+ constructor(config) {
16
+ debug('hbConfigNode', JSON.stringify(config));
17
+ super(config, RED);
104
18
  }
105
-
106
- var node = this;
107
-
108
- this.connect = function (callback) {
109
- callback();
110
- };
111
-
112
- this.register = function (deviceNode, callback) {
113
- debug("hbConf.register", deviceNode.fullName);
114
- node.users[deviceNode.id] = deviceNode;
115
- debug("Register %s -> %s", deviceNode.type, deviceNode.fullName);
116
- reqisterQueue.push({
117
- that: this,
118
- device: deviceNode.device,
119
- type: deviceNode.type,
120
- name: deviceNode.name,
121
- fullName: deviceNode.fullName,
122
- node: node
123
- }, callback);
124
- // debug("Register Queue - push", reqisterQueue.getStats());
125
- };
126
-
127
- this.deregister = function (deviceNode, callback) {
128
- deviceNode.status({
129
- text: 'disconnected',
130
- shape: 'ring',
131
- fill: 'red'
132
- });
133
- // Should this also remove the homebridge registered event?
134
- //
135
- // debug("hbEvent deregistered:", deviceNode.name);
136
- // if (homebridge.listenerCount(deviceNode.eventName)) {
137
- deviceNode.eventName.forEach(function (event) {
138
- homebridge.removeListener(event, deviceNode.listener);
139
- });
140
- // }
141
- callback();
142
- };
143
-
144
- this.on('close', function () {
145
- if (node.client && node.client.connected) {
146
- node.client.end();
147
- }
148
- });
149
19
  }
150
20
 
151
- RED.nodes.registerType("hb-conf", hbConf, {
21
+ RED.nodes.registerType("hb-conf", hbConfigNode, {
152
22
  credentials: {
153
23
  password: {
154
24
  type: "password"
@@ -156,959 +26,39 @@ module.exports = function (RED) {
156
26
  }
157
27
  });
158
28
 
159
- /**
160
- * hbEvent - Node that listens to HomeKit Events, and sends message into NodeRED
161
- *
162
- * @param {type} n description
163
- * @return {type} description
164
- */
165
-
166
- function hbEvent(n) {
167
- // debug("hbEvent", n);
168
- RED.nodes.createNode(this, n);
169
- this.conf = RED.nodes.getNode(n.conf);
170
- this.confId = n.conf;
171
- this.device = n.device;
172
- this.service = n.Service;
173
- this.name = n.name;
174
- this.fullName = n.name + ' - ' + n.Service;
175
- this.sendInitialState = n.sendInitialState === true;
176
- this.state = {};
177
-
178
- var node = this;
179
-
180
- node.command = function (event) {
181
- // False messages can be received from accessories with multiple services
182
- // if (Object.keys(_convertHBcharactericToNode(event, node)).length > 0) {
183
- // debug("hbEvent", node.name, event);
184
- if (event.status === true && event.value !== undefined) {
185
- node.state = Object.assign(node.state, _convertHBcharactericToNode([event], node));
186
- var msg = {
187
- name: node.name,
188
- payload: node.state,
189
- Homebridge: node.hbDevice.homebridge,
190
- Manufacturer: node.hbDevice.manufacturer,
191
- Service: node.hbDevice.deviceType,
192
- _device: node.device,
193
- _confId: node.confId,
194
- _rawEvent: event
195
- };
196
- node.status({
197
- text: JSON.stringify(msg.payload).slice(0, 30) + '...',
198
- shape: 'dot',
199
- fill: 'green'
200
- });
201
- clearTimeout(node.timeout);
202
- node.timeout = setTimeout(function () {
203
- node.status({});
204
- }, 10 * 1000);
205
- node.send(msg);
206
- } else if (event.status === true) {
207
- node.status({
208
- text: 'connected',
209
- shape: 'dot',
210
- fill: 'green'
211
- });
212
- } else {
213
- node.status({
214
- text: 'disconnected: ' + event.status,
215
- shape: 'ring',
216
- fill: 'red'
217
- });
218
- }
219
- };
220
- // };
221
-
222
- node.conf.register(node, function () {
223
- debug("hbEvent.register", node.fullName);
224
- this.hbDevice = hbDevices.findDevice(node.device, {
225
- perms: 'pr'
226
- });
227
- if (this.hbDevice) {
228
- node.hbDevice = this.hbDevice;
229
- node.deviceType = this.hbDevice.deviceType;
230
-
231
- _status(node.device, node, {
232
- perms: 'ev'
233
- }, function (err, message) {
234
- if (!err) {
235
- node.state = _convertHBcharactericToNode(message.characteristics, node);
236
- debug("hbEvent received: %s = %s", node.fullName, JSON.stringify(message.characteristics).slice(0, 80) + '...');
237
- if (node.sendInitialState) {
238
- var msg = {
239
- name: node.name,
240
- payload: node.state,
241
- Homebridge: node.hbDevice.homebridge,
242
- Manufacturer: node.hbDevice.manufacturer,
243
- Service: node.hbDevice.deviceType,
244
- _device: node.device,
245
- _confId: node.confId,
246
- _rawMessage: message,
247
- };
248
- node.status({
249
- text: JSON.stringify(msg.payload).slice(0, 30) + '...',
250
- shape: 'dot',
251
- fill: 'green'
252
- });
253
- clearTimeout(node.timeout);
254
- node.timeout = setTimeout(function () {
255
- node.status({});
256
- }, 10 * 1000);
257
- node.send(msg);
258
- }
259
- } else {
260
- node.error("hbEvent _status: error", node.fullName, err);
261
- }
262
- });
263
- // Register for events
264
- node.listener = node.command;
265
- node.eventName = [];
266
- // node.eventName = this.hbDevice.host + this.hbDevice.port + this.hbDevice.aid;
267
- // debug("DEVICE", this.hbDevice);
268
- this.hbDevice.eventRegisters.forEach(function (event) {
269
- homebridge.on(node.hbDevice.id + event.aid + event.iid, node.command);
270
- node.eventName.push(node.hbDevice.id + event.aid + event.iid);
271
- });
272
- // homebridge.on(this.hbDevice.host + this.hbDevice.port + this.hbDevice.aid, node.command);
273
- node.status({
274
- text: 'connected',
275
- shape: 'dot',
276
- fill: 'green'
277
- });
278
- } else {
279
- node.error("197:Can't find device " + node.device, null);
280
- }
281
- }.bind(this));
282
-
283
- node.on('close', function (callback) {
284
- node.conf.deregister(node, callback);
285
- });
286
- }
287
-
288
- RED.nodes.registerType("hb-event", hbEvent);
289
-
290
- /**
291
- * hbResume - description
292
- *
293
- * State operating model
294
- * - Store msg into node.lastPayload
295
- * - Store device state into node.state on events
296
- *
297
- * Turn on message just passes thru
298
- * - if msg = on
299
- *
300
- * First turn off message restores state from Turn on
301
- * - if msg = off and node.lastPayload === on
302
- *
303
- * Second turn off message just passes thru
304
- * - if msg = off and node.lastPayload === off
305
- * - Update stored device state to off
306
- *
307
- * @param {type} n description
308
- * @return {type} description
309
- */
310
-
311
- function hbResume(n) {
312
- RED.nodes.createNode(this, n);
313
- this.conf = RED.nodes.getNode(n.conf);
314
- this.confId = n.conf;
315
- this.device = n.device;
316
- this.service = n.Service;
317
- this.name = n.name;
318
- this.fullName = n.name + ' - ' + n.Service;
319
- var node = this;
320
-
321
- node.state = null;
322
- node.lastMessageTime = null;
323
- node.lastMessageValue = null;
324
- node.lastPayload = {
325
- On: false
326
- };
327
-
328
- node.on('input', function (msg) {
329
- this.msg = msg;
330
- debug("hbResume.input: %s input", node.fullName, JSON.stringify(msg));
331
- if (typeof msg.payload === "object") {
332
- // Using this to validate input message contains valid Accessory Characteristics
333
- if (node.hbDevice) { // not populated until initialization is complete
334
- var message = _createControlMessage.call(this, msg.payload, node, node.hbDevice);
335
-
336
- if (message.characteristics.length > 0) {
337
- var newMsg;
338
- if (!msg.payload.On) {
339
- // false / Turn Off
340
- // debug("hbResume-Node lastPayload %s", JSON.stringify(node.lastPayload));
341
- if (node.lastPayload.On) {
342
- // last msg was on, restore previous state
343
- newMsg = {
344
- name: node.name,
345
- _device: node.device,
346
- _confId: node.confId
347
- };
348
- if (node.hbDevice) {
349
- newMsg.Homebridge = node.hbDevice.homebridge;
350
- newMsg.Manufacturer = node.hbDevice.manufacturer;
351
- newMsg.Type = node.hbDevice.deviceType;
352
- }
353
- newMsg.payload = node.state;
354
- } else {
355
- // last msg was off, pass thru
356
- node.state = JSON.parse(JSON.stringify(msg.payload));
357
- newMsg = msg;
358
- }
359
- } else {
360
- // True / Turn on
361
- newMsg = msg;
362
- }
363
- // Off messages should not include brightness
364
- node.send((newMsg.payload.On ? newMsg : newMsg.payload = {
365
- On: false
366
- }, newMsg));
367
- debug("hbResume.input: %s output", node.fullName, JSON.stringify(newMsg));
368
- node.status({
369
- text: JSON.stringify(newMsg.payload).slice(0, 30) + '...',
370
- shape: 'dot',
371
- fill: 'green'
372
- });
373
- clearTimeout(node.timeout);
374
- node.timeout = setTimeout(function () {
375
- node.status({});
376
- }, 10 * 1000);
377
- node.lastMessageValue = newMsg.payload;
378
- node.lastMessageTime = Date.now();
379
- // debug("hbResume.input: %s updating lastPayload %s", node.fullName, JSON.stringify(msg.payload));
380
- node.lastPayload = JSON.parse(JSON.stringify(msg.payload)); // store value not reference
381
- }
382
- } else {
383
- node.error("Homebridge not initialized - 1", this.msg);
384
- node.status({
385
- text: 'Homebridge not initialized -1',
386
- shape: 'ring',
387
- fill: 'red'
388
- });
389
- }
390
- } else {
391
- node.error("Payload should be an JSON object containing device characteristics and values, ie {\"On\":false, \"Brightness\":0 }\nValid values include: " + node.hbDevice.descriptions, this.msg);
392
- node.status({
393
- text: 'Invalid payload',
394
- shape: 'ring',
395
- fill: 'red'
396
- });
397
- }
398
- });
399
-
400
- node.command = function (event) {
401
- // debug("hbResume received event: %s ->", node.fullName, event);
402
- // debug("hbResume - internals %s millis, old %s, event %s, previous %s", Date.now() - node.lastMessageTime, node.lastMessageValue, event.status, node.state);
403
- // Don't update for events originating from here
404
- // if Elapsed is greater than 5 seconds, update stored state
405
- // if Elapsed is less then 5, and lastMessage doesn't match event update stored state
406
-
407
- var payload = Object.assign({}, node.state);
408
-
409
- // debug("should be true", _getObjectDiff(payload, node.state).length);
410
-
411
- payload = Object.assign(payload, _convertHBcharactericToNode([event], node));
412
-
413
- // debug("should be false", _getObjectDiff(payload, node.state).length);
414
-
415
- debug("hbResume.event: %s %s -> %s", node.fullName, JSON.stringify(node.state), JSON.stringify(payload));
416
-
417
- if (event.status === true && event.value !== undefined) {
418
- if ((Date.now() - node.lastMessageTime) > 5000) {
419
- debug("hbResume.update: %s - updating stored event >5", node.fullName, payload);
420
- node.state = JSON.parse(JSON.stringify(payload));
421
- } else if (_getObjectDiff(payload, node.lastMessageValue).length > 0) {
422
- // debug("hbResume - updating stored event !=", payload, node.lastMessageValue);
423
- // node.state = payload;
424
- }
425
- } else if (event.status === true) {
426
- node.status({
427
- text: 'connected',
428
- shape: 'dot',
429
- fill: 'green'
430
- });
431
- } else {
432
- node.status({
433
- text: 'disconnected: ' + event.status,
434
- shape: 'ring',
435
- fill: 'red'
436
- });
437
- }
438
- };
439
-
440
- node.conf.register(node, function () {
441
- debug("hbResume.register:", node.fullName);
442
- this.hbDevice = hbDevices.findDevice(node.device, {
443
- perms: 'pw'
444
- });
445
- if (this.hbDevice) {
446
- _status(node.device, node, {
447
- perms: 'pw'
448
- }, function (err, message) {
449
- if (!err) {
450
- node.state = _convertHBcharactericToNode(message.characteristics, node);
451
- debug("hbResume received: %s = %s", node.fullName, JSON.stringify(message.characteristics).slice(0, 80) + '...');
452
- } else {
453
- node.error(err);
454
- }
455
- });
456
- node.hbDevice = this.hbDevice;
457
- node.deviceType = this.hbDevice.deviceType;
458
- // Register for events
459
- node.listener = node.command;
460
- node.eventName = [];
461
- // node.eventName = this.hbDevice.host + this.hbDevice.port + this.hbDevice.aid;
462
- // homebridge.on(this.hbDevice.host + this.hbDevice.port + this.hbDevice.aid, node.command);
463
- this.hbDevice.eventRegisters.forEach(function (event) {
464
- homebridge.on(node.hbDevice.id + event.aid + event.iid, node.command);
465
- node.eventName.push(node.hbDevice.id + event.aid + event.iid);
466
- });
467
- node.status({
468
- text: 'connected',
469
- shape: 'dot',
470
- fill: 'green'
471
- });
472
- clearTimeout(node.timeout);
473
- node.timeout = setTimeout(function () {
474
- node.status({});
475
- }, 30 * 1000);
476
- } else {
477
- node.error("365:Can't find device " + node.device, null);
478
- }
479
- }.bind(this));
480
-
481
- node.on('close', function (callback) {
482
- node.conf.deregister(node, callback);
483
- });
484
- }
485
-
486
- RED.nodes.registerType("hb-resume", hbResume);
487
-
488
- /**
489
- * hbControl - description
490
- *
491
- * @param {type} n description
492
- * @return {type} description
493
- */
494
-
495
- function hbControl(n) {
496
- RED.nodes.createNode(this, n);
497
- this.conf = RED.nodes.getNode(n.conf); // The configuration node
498
- this.confId = n.conf;
499
- this.device = n.device;
500
- this.service = n.Service;
501
- this.name = n.name;
502
- this.fullName = n.name + ' - ' + n.Service;
503
-
504
- var node = this;
505
-
506
- node.on('input', function (msg) {
507
- this.msg = msg;
508
- _control.call(this, node, msg.payload, function (err, data) {
509
- // debug('hbControl complete [%s] - [%s]', node, node.hbDevice); // Images produce alot of noise
510
- if (!err && data && (node.deviceType == '00000110' || node.deviceType == '00000111')) {
511
- const msg = {
512
- name: node.name,
513
- payload: node.state,
514
- _device: node.device,
515
- _confId: node.confId
516
- };
517
- if (node.hbDevice) {
518
- msg.Homebridge = node.hbDevice.homebridge;
519
- msg.Manufacturer = node.hbDevice.manufacturer;
520
- msg.Service = node.hbDevice.deviceType;
521
- }
522
- msg.payload = data;
523
- node.send(msg);
524
- } else if (err) {
525
- node.error(err, this.msg);
526
- }
527
- }.bind(this));
528
-
529
- });
530
-
531
- node.on('close', function (callback) {
532
- callback();
533
- });
534
-
535
- node.conf.register(node, function () {
536
- debug("hbControl.register:", node.fullName);
537
- this.hbDevice = hbDevices.findDevice(node.device);
538
- // console.log('hbControl Register', this.hbDevice)
539
- if (this.hbDevice) {
540
- node.hbDevice = this.hbDevice;
541
- node.deviceType = this.hbDevice.type;
542
- // Register for events
543
- node.listener = node.command;
544
- // node.eventName = this.hbDevice.host + this.hbDevice.port + this.hbDevice.aid;
545
- } else {
546
- node.error("437:Can't find device " + node.device, null);
547
- // this.error("Missing device " + node.device);
548
- }
549
- });
550
- }
551
-
552
- RED.nodes.registerType("hb-control", hbControl);
553
-
554
- /**
555
- * hbStatus - description
556
- *
557
- * @param {type} n description
558
- * @return {type} description
559
- */
560
-
561
- function hbStatus(n) {
562
- RED.nodes.createNode(this, n);
563
- this.conf = RED.nodes.getNode(n.conf); // The configuration node
564
- this.confId = n.conf;
565
- this.device = n.device;
566
- this.service = n.Service;
567
- this.name = n.name;
568
- this.fullName = n.name + ' - ' + n.Service;
569
-
570
- var node = this;
571
-
572
- node.conf.register(node, function () {
573
- debug("hbStatus Registered:", node.fullName);
574
- this.hbDevice = hbDevices.findDevice(node.device);
575
- if (this.hbDevice) {
576
- node.hbDevice = this.hbDevice;
577
- node.deviceType = this.hbDevice.deviceType;
578
- // Register for events
579
- node.listener = node.command;
580
- // node.eventName = this.hbDevice.host + this.hbDevice.port + this.hbDevice.aid;
581
- } else {
582
- node.error("437:Can't find device " + node.device, null);
583
- // this.error("Missing device " + node.device);
584
- }
585
- });
586
-
587
- node.on('input', function (msg) {
588
- this.msg = msg;
589
- _status(this.device, node, {
590
- perms: 'pr'
591
- }, function (err, message) {
592
- if (!err) {
593
- debug("hbStatus received: %s = %s", JSON.stringify(node.fullName), JSON.stringify(message).slice(0, 80) + '...', JSON.stringify(node.hbDevice));
594
- this.msg.name = node.name;
595
- this.msg._rawMessage = message;
596
- this.msg.payload = _convertHBcharactericToNode(message.characteristics, node);
597
-
598
- if (node.hbDevice) {
599
- this.msg.Homebridge = node.hbDevice.homebridge;
600
- this.msg.Manufacturer = node.hbDevice.manufacturer;
601
- this.msg.Service = node.hbDevice.service;
602
- this.msg._device = node.device;
603
- this.msg._confId = node.confId;
604
- }
605
- node.status({
606
- text: JSON.stringify(this.msg.payload).slice(0, 30) + '...',
607
- shape: 'dot',
608
- fill: 'green'
609
- });
610
- node.send(this.msg);
611
- } else {
612
- node.error(err, this.msg);
613
- }
614
- }.bind(this));
615
- });
616
-
617
- node.on('close', function (callback) {
618
- callback();
619
- });
620
- }
621
-
622
- RED.nodes.registerType("hb-status", hbStatus);
623
-
624
- RED.httpAdmin.post('/hap-device/refresh/:id', RED.auth.needsPermission('hb-event.read'), function (req, res) {
625
- var id = req.params.id;
626
- var conf = RED.nodes.getNode(id);
627
- if (conf) {
628
- res.status(200).send();
629
- } else {
630
- // not deployed yet
631
- console.log("Can't refresh until deployed");
632
- res.status(404).send();
633
- }
634
- });
635
-
636
- RED.httpAdmin.get('/hap-device/evDevices/', RED.auth.needsPermission('hb-event.read'), function (req, res) {
637
- debug("evDevices", hbDevices.toList({
638
- perms: 'ev'
639
- }).length);
640
- if (evDevices) {
641
- res.send(hbDevices.toList({
642
- perms: 'ev'
643
- }));
644
- } else {
645
- res.status(404).send();
646
- }
647
- });
648
-
649
- RED.httpAdmin.get('/hap-device/evDevices/:id', RED.auth.needsPermission('hb-event.read'), function (req, res) {
650
- if (evDevices && hbDevices) {
651
- debug("evDevices", hbDevices.toList({
652
- perms: 'ev'
653
- }).length);
654
- res.send(hbDevices.toList({
655
- perms: 'ev'
656
- }));
657
- } else {
658
- res.status(404).send();
659
- }
660
- });
661
-
662
- RED.httpAdmin.post('/hap-device/refresh/:id', RED.auth.needsPermission('hb-resume.read'), function (req, res) {
663
- var id = req.params.id;
664
- var conf = RED.nodes.getNode(id);
665
- if (conf) {
666
- res.status(200).send();
667
- } else {
668
- // not deployed yet
669
- console.log("Can't refresh until deployed");
670
- res.status(404).send();
671
- }
672
- });
673
-
674
- RED.httpAdmin.get('/hap-device/evDevices/', RED.auth.needsPermission('hb-resume.read'), function (req, res) {
675
- debug("evDevices", hbDevices.toList({
676
- perms: 'ev'
677
- }).length);
678
- if (evDevices) {
679
- res.send(hbDevices.toList({
680
- perms: 'ev'
681
- }));
682
- } else {
683
- res.status(404).send();
684
- }
685
- });
686
-
687
- RED.httpAdmin.get('/hap-device/evDevices/:id', RED.auth.needsPermission('hb-resume.read'), function (req, res) {
688
- debug("evDevices", hbDevices.toList({
689
- perms: 'ev'
690
- }).length);
691
- if (evDevices) {
692
- res.send(hbDevices.toList({
693
- perms: 'ev'
694
- }));
695
- } else {
696
- res.status(404).send();
697
- }
698
- });
699
-
700
- RED.httpAdmin.get('/hap-device/ctDevices/', RED.auth.needsPermission('hb-control.read'), function (req, res) {
701
- debug("ctDevices", hbDevices.toList({
702
- perms: 'pw'
703
- }).length);
704
- if (ctDevices) {
705
- res.send(hbDevices.toList({
706
- perms: 'pw'
707
- }));
708
- } else {
709
- res.status(404).send();
710
- }
711
- });
712
-
713
- RED.httpAdmin.get('/hap-device/ctDevices/:id', RED.auth.needsPermission('hb-control.read'), function (req, res) {
714
- debug("ctDevices", hbDevices.toList({
715
- perms: 'pw'
716
- }).length);
717
- if (ctDevices) {
718
- res.send(hbDevices.toList({
719
- perms: 'pw'
720
- }));
721
- } else {
722
- res.status(404).send();
723
- }
724
- });
725
-
726
- /**
727
- * _convertHBcharactericToNode - Convert homebridge characteric array to Node Payload
728
- *
729
- * @param {array} hbMessage description
730
- * @param {object} node description
731
- * @return {type} description
732
- */
733
-
734
- function _convertHBcharactericToNode(hbMessage, node) {
735
- // debug("_convertHBcharactericToNode", node.device);
736
- var payload = {};
737
- if (!hbMessage.payload) {
738
- var device = hbDevices.findDevice(node.device);
739
- // debug("Device", device);
740
-
741
- // characteristics = Object.assign(characteristics, characteristic.characteristic);
742
- if (device) {
743
- hbMessage.forEach(function (characteristic) {
744
- // debug("Exists", (device.characteristics[characteristic.aid + '.' + characteristic.iid]));
745
- if (device.characteristics[characteristic.aid + '.' + characteristic.iid]) {
746
- payload = Object.assign(payload, {
747
- [device.characteristics[characteristic.aid + '.' + characteristic.iid].characteristic]: characteristic.value
748
- });
749
- }
750
- });
751
- }
752
- } else {
753
- payload = hbMessage.payload;
754
- }
755
- // debug("payload", payload);
756
- return (payload);
757
- }
758
-
759
- /**
760
- * _createControlMessage - description
761
- *
762
- * @param {type} payload {"On":false,"Brightness":0}
763
- * @param {type} node description
764
- * @param {type} device description
765
- * @return {type} description
766
- */
767
-
768
- function _createControlMessage(payload, node, device) {
769
- // debug("_createControlMessage", payload, device);
770
- // debug("Device", device, device.characteristics[event.aid + '.' + event.iid]);
771
- var response = [];
772
-
773
- for (var key in payload) {
774
- // debug("IID", key, _getKey(device.characteristics, key));
775
- if (_getKey(device.characteristics, key)) {
776
- response.push({
777
- "aid": device.aid,
778
- "iid": _getKey(device.characteristics, key).iid,
779
- "value": payload[key]
780
- });
781
- } else {
782
- this.warn("Characteristic '" + key + "' is not valid.\nTry one of these: " + device.descriptions);
783
- node.status({
784
- text: 'warn - Invalid Characteristic ' + key,
785
- shape: 'ring',
786
- fill: 'yellow'
787
- });
788
- }
789
- }
790
- return ({
791
- "characteristics": response
792
- });
793
- }
794
-
795
- /**
796
- * _status - description
797
- *
798
- * @param {type} nrDevice description
799
- * @param {type} node description
800
- * @param {type} value description
801
- * @param {type} callback description
802
- * @return {type} description
803
- */
804
-
805
- function _status(nrDevice, node, perms, callback) {
806
- // debug("_status", new Error(), hbDevices);
807
- var error;
808
- try {
809
- if (!hbDevices) {
810
- throw new Error('hbDevices not initialized');
811
- }
812
- var device = hbDevices.findDevice(node.device, perms);
813
- if (device) {
814
- // debug("device.type", device.type);
815
- switch (device.type) {
816
- case "00000110": // Camera RTPStream Management
817
- case "00000111": // Camera Control
818
- var message = {
819
- "resource-type": "image",
820
- "image-width": 1920,
821
- "image-height": 1080
822
- };
823
- debug("_status Control %s -> %s", device.id, JSON.stringify(message));
824
- homebridge.HAPresourceByDeviceID(device.id, JSON.stringify(message), function (err, status) {
825
- // debug("status", err);
826
- if (!err) {
827
- debug("_status Controlled %s:%s ->", device.host, device.port);
828
- node.status({
829
- text: 'sent',
830
- shape: 'dot',
831
- fill: 'green'
832
- });
833
- clearTimeout(node.timeout);
834
- node.timeout = setTimeout(function () {
835
- node.status({});
836
- }, 30 * 1000);
837
- // {"characteristics":[{"aid":19,"iid":10,"value":false},{"aid":19,"iid":11,"value":0}]}
838
- callback(null, {
839
- characteristics: {
840
- payload: btoa(status)
841
- }
842
- });
843
- } else {
844
- node.error(device.host + ":" + device.port + " -> " + err);
845
- node.status({
846
- text: 'error',
847
- shape: 'ring',
848
- fill: 'red'
849
- });
850
- callback(err);
851
- }
852
- });
853
- break;
854
- default:
855
- var message = '?id=' + device.getCharacteristics;
856
- debug("_status request: %s -> %s:%s ->", node.fullName, device.id, message);
857
- homebridge.HAPstatusByDeviceID(device.id, message, function (err, status) {
858
- if (!err) {
859
- // debug("Status %s:%s ->", device.host, device.port, status);
860
- node.status({
861
- text: 'sent',
862
- shape: 'dot',
863
- fill: 'green'
864
- });
865
- clearTimeout(node.timeout);
866
- node.timeout = setTimeout(function () {
867
- node.status({});
868
- }, 30 * 1000);
869
- callback(null, status);
870
- } else {
871
- error = device.id + " -> " + err + " -> " + status;
872
- node.status({
873
- text: 'error',
874
- shape: 'ring',
875
- fill: 'red'
876
- });
877
- callback(error);
878
- }
879
- });
880
- } // End of switch
881
- } else {
882
- error = "Device not found: " + nrDevice;
883
- node.status({
884
- text: 'Device not found',
885
- shape: 'ring',
886
- fill: 'red'
887
- });
888
- callback(error);
889
- } // end of device if
890
- } catch (err) {
891
- // debug('_status', err);
892
- error = "Homebridge not initialized -2";
893
- node.status({
894
- text: error,
895
- shape: 'ring',
896
- fill: 'red'
897
- });
898
- callback(error);
29
+ class hbEventNode extends HbEventNode {
30
+ constructor(config) {
31
+ super(config, RED);
899
32
  }
900
33
  }
901
34
 
902
- /**
903
- * _control - description
904
- *
905
- * @param {type} nrDevice description
906
- * @param {type} node description
907
- * @param {type} payload {"On":false, "Brightness":0}
908
- * @param {type} callback description
909
- * @return {type} description
910
- */
35
+ RED.nodes.registerType("hb-event", hbEventNode);
911
36
 
912
- function _control(node, payload, callback) {
913
- try {
914
- if (!hbDevices) {
915
- throw new Error('hbDevices not initialized');
916
- }
917
- var device = hbDevices.findDevice(node.device, {
918
- perms: 'pw'
919
- });
920
- if (device) {
921
- var message;
922
- // console.log('device.type', device.type)
923
- switch (device.type) {
924
- case "00000110": // Camera RTPStream Management
925
- case "00000111": // Camera Control
926
- message = {
927
- "resource-type": "image",
928
- "image-width": 1920,
929
- "image-height": 1080
930
- };
931
- debug("Control %s ->", device.id, node.fullName, JSON.stringify(message));
932
- homebridge.HAPresourceByDeviceID(device.id, JSON.stringify(message), function (err, status) {
933
- if (!err) {
934
- // debug("Controlled %s ->", device.id, JSON.stringify(payload));
935
- // debug("Payload %s ->", device.id, status);
936
- node.status({
937
- text: JSON.stringify(payload).slice(0, 30) + '...',
938
- shape: 'dot',
939
- fill: 'green'
940
- });
941
- clearTimeout(node.timeout);
942
- node.timeout = setTimeout(function () {
943
- node.status({});
944
- }, 30 * 1000);
945
- callback(null, status);
946
- } else {
947
- node.error(device.id + " -> " + err);
948
- node.status({
949
- text: 'error',
950
- shape: 'ring',
951
- fill: 'red'
952
- });
953
- callback(err);
954
- }
955
- });
956
- break;
957
- default:
958
- // debug("Object type", typeof payload);
959
- if (typeof payload === "object") {
960
- message = _createControlMessage.call(this, payload, node, device);
961
- debug("Control %s ->", device.id, JSON.stringify(message));
962
- if (message.characteristics.length > 0) {
963
- homebridge.HAPcontrolByDeviceID(device.id, JSON.stringify(message), function (err, status) {
964
- if (!err && status && status.characteristics[0].status === 0) {
965
- debug("Controlled %s ->", device.id, JSON.stringify(status));
966
- node.status({
967
- text: JSON.stringify(payload).slice(0, 30) + '...',
968
- shape: 'dot',
969
- fill: 'green'
970
- });
971
- clearTimeout(node.timeout);
972
- node.timeout = setTimeout(function () {
973
- node.status({});
974
- }, 10 * 1000);
975
- callback(null);
976
- } else if (!err) {
977
- debug("Controlled %s ->", device.id, payload);
978
- node.status({
979
- text: JSON.stringify(payload).slice(0, 30) + '...',
980
- shape: 'dot',
981
- fill: 'green'
982
- });
983
- clearTimeout(node.timeout);
984
- node.timeout = setTimeout(function () {
985
- node.status({});
986
- }, 10 * 1000);
987
- callback(null);
988
- } else {
989
- node.error(device.id + " -> " + err + " -> " + status);
990
- node.status({
991
- text: 'error',
992
- shape: 'ring',
993
- fill: 'red'
994
- });
995
- callback(err);
996
- }
997
- });
998
- } else {
999
- // Bad message
1000
- /* - This is handled in createcontrolmessage
1001
- this.warn("Invalid payload-");
1002
- node.status({
1003
- text: 'error - Invalid payload',
1004
- shape: 'ring',
1005
- fill: 'red'
1006
- });
1007
- */
1008
- var err = 'Invalid payload';
1009
- callback(err);
1010
- }
1011
- } else {
1012
- node.error("Payload should be an JSON object containing device characteristics and values, ie {\"On\":false, \"Brightness\":0 }\nValid values include: " + device.descriptions);
1013
- var err = 'Invalid payload';
1014
- node.status({
1015
- text: err,
1016
- shape: 'ring',
1017
- fill: 'red'
1018
- });
1019
- callback(err);
1020
- }
1021
- } // End of switch
1022
- } else {
1023
- var error = 'Device not available';
1024
- node.status({
1025
- text: error,
1026
- shape: 'ring',
1027
- fill: 'red'
1028
- });
1029
- callback(error);
1030
- }
1031
- } catch (err) {
1032
- var error = "Homebridge not initialized - 3 "+ err;
1033
- node.status({
1034
- text: error,
1035
- shape: 'ring',
1036
- fill: 'red'
1037
- });
1038
- callback(error);
37
+ class hbResumeNode extends HbResumeNode {
38
+ constructor(config) {
39
+ super(config, RED);
1039
40
  }
1040
41
  }
1041
42
 
1042
- /**
1043
- * _register - description
1044
- *
1045
- * @param {type} node description
1046
- * @param {type} callback callback
1047
- * @return {type} description
1048
- */
43
+ RED.nodes.registerType("hb-resume", hbResumeNode);
1049
44
 
1050
- function _register(node, callback) {
1051
- debug("_register", node.device);
1052
- var device = hbDevices.findDevice(node.device, {
1053
- perms: 'ev'
1054
- });
1055
- if (node.type === 'hb-event' || node.type === 'hb-resume') {
1056
- var message = {
1057
- "characteristics": device.eventRegisters
1058
- };
1059
- debug("_register", node.fullName, device.id, message);
1060
- homebridge.HAPeventByDeviceID(device.id, JSON.stringify(message), function (err, status) {
1061
- if (!err && status === null) {
1062
- debug("%s registered: %s -> %s", node.type, node.fullName, device.id);
1063
- callback(null);
1064
- } else if (!err) {
1065
- debug("%s registered: %s -> %s", node.type, node.fullName, device.id, JSON.stringify(status));
1066
- callback(null);
1067
- } else {
1068
- // Fix for # 47
1069
- // node.error(device.host + ":" + device.port + " -> " + err);
1070
- callback(err);
1071
- }
1072
- }.bind(this));
1073
- } else {
1074
- callback(null);
45
+ class hbControlNode extends HbControlNode {
46
+ constructor(config) {
47
+ super(config, RED);
1075
48
  }
1076
49
  }
1077
- };
1078
50
 
1079
- function _getObjectDiff(obj1, obj2) {
1080
- const diff = Object.keys(obj1).reduce((result, key) => {
1081
- if (!obj2.hasOwnProperty(key)) {
1082
- result.push(key);
1083
- } else if (obj1[key] === obj2[key]) {
1084
- const resultKeyIndex = result.indexOf(key);
1085
- result.splice(resultKeyIndex, 1);
1086
- }
1087
- return result;
1088
- }, Object.keys(obj2));
51
+ RED.nodes.registerType("hb-control", hbControlNode);
1089
52
 
1090
- return diff;
1091
- }
1092
-
1093
- function _getKey(obj, value) {
1094
- for (var key in obj) {
1095
- // debug("%s === %s", obj[key].characteristic, value);
1096
- // debug("%s === %s", obj[key].characteristic.toLowerCase(), value.toLowerCase());
1097
- if (obj[key].characteristic.toLowerCase() === value.toLowerCase()) {
1098
- return obj[key];
53
+ class hbStatusNode extends HbStatusNode {
54
+ constructor(config) {
55
+ super(config, RED);
1099
56
  }
1100
57
  }
1101
- return null;
1102
- }
1103
58
 
1104
- function btoa(str) {
1105
- var buffer;
59
+ RED.nodes.registerType("hb-status", hbStatusNode);
1106
60
 
1107
- if (str instanceof Buffer) {
1108
- buffer = str;
1109
- } else {
1110
- buffer = Buffer.from(str.toString(), 'binary');
1111
- }
61
+ const hapDeviceRoutes = new HapDeviceRoutes(RED, hbDevices);
62
+ hapDeviceRoutes.registerRoutes();
1112
63
 
1113
- return buffer.toString('base64');
1114
- }
64
+ };