node-red-contrib-homebridge-automation 0.1.12-beta.30 → 0.1.12-beta.32
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/package.json +2 -2
- package/src/HapDeviceRoutes.js +1 -1
- package/src/hbConfigNode.js +52 -56
- package/src/hbControlNode.js +1 -0
- package/src/hbStatusNode.js +12 -6
- package/test/node-red/flows.json +161 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-homebridge-automation",
|
|
3
|
-
"version": "0.1.12-beta.
|
|
3
|
+
"version": "0.1.12-beta.32",
|
|
4
4
|
"description": "NodeRED Automation for HomeBridge",
|
|
5
5
|
"main": "src/HAP-NodeRed.js",
|
|
6
6
|
"scripts": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"better-queue": ">=3.8.12",
|
|
50
50
|
"debug": "^4.3.5",
|
|
51
|
-
"@homebridge/hap-client": "2.0.5-beta.
|
|
51
|
+
"@homebridge/hap-client": "2.0.5-beta.7"
|
|
52
52
|
},
|
|
53
53
|
"author": "NorthernMan54",
|
|
54
54
|
"license": "ISC",
|
package/src/HapDeviceRoutes.js
CHANGED
|
@@ -30,7 +30,7 @@ class HapDeviceRoutes {
|
|
|
30
30
|
getDeviceById(req, res, key) {
|
|
31
31
|
const devices = this.RED.nodes.getNode(req.params.id)?.[key];
|
|
32
32
|
if (devices) {
|
|
33
|
-
debug(`${key} devices`, devices.length);
|
|
33
|
+
// debug(`${key} devices`, devices.length);
|
|
34
34
|
res.send(devices);
|
|
35
35
|
} else {
|
|
36
36
|
res.status(404).send();
|
package/src/hbConfigNode.js
CHANGED
|
@@ -8,6 +8,7 @@ class HBConfigNode {
|
|
|
8
8
|
if (!config.jest) {
|
|
9
9
|
RED.nodes.createNode(this, config);
|
|
10
10
|
|
|
11
|
+
// Initialize properties
|
|
11
12
|
this.username = config.username;
|
|
12
13
|
this.macAddress = config.macAddress || '';
|
|
13
14
|
this.users = {};
|
|
@@ -19,6 +20,7 @@ class HBConfigNode {
|
|
|
19
20
|
this.monitorNodes = [];
|
|
20
21
|
this.log = new Log(console, true);
|
|
21
22
|
|
|
23
|
+
// Initialize queue
|
|
22
24
|
this.reqisterQueue = new Queue(this._register.bind(this), {
|
|
23
25
|
concurrent: 1,
|
|
24
26
|
autoResume: false,
|
|
@@ -29,27 +31,25 @@ class HBConfigNode {
|
|
|
29
31
|
});
|
|
30
32
|
this.reqisterQueue.pause();
|
|
31
33
|
|
|
34
|
+
// Initialize HAP client
|
|
32
35
|
this.hapClient = new HapClient({
|
|
33
36
|
config: { debug: false },
|
|
34
37
|
pin: config.username,
|
|
35
38
|
logger: this.log,
|
|
36
39
|
});
|
|
37
40
|
|
|
38
|
-
this.waitForNoMoreDiscoveries();
|
|
39
41
|
this.hapClient.on('instance-discovered', this.waitForNoMoreDiscoveries);
|
|
40
|
-
|
|
42
|
+
this.waitForNoMoreDiscoveries();
|
|
41
43
|
this.on('close', this.close.bind(this));
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
waitForNoMoreDiscoveries = () => {
|
|
46
|
-
|
|
47
|
-
clearTimeout(this.discoveryTimeout);
|
|
48
|
-
}
|
|
49
|
-
|
|
48
|
+
clearTimeout(this.discoveryTimeout);
|
|
50
49
|
this.discoveryTimeout = setTimeout(() => {
|
|
51
50
|
this.log.debug('No more instances discovered, publishing services');
|
|
52
51
|
this.hapClient.removeListener('instance-discovered', this.waitForNoMoreDiscoveries);
|
|
52
|
+
this.hapClient.on('instance-discovered', async (instance) => { debug('instance-discovered', instance); await this.monitorDevices(); });
|
|
53
53
|
this.handleReady();
|
|
54
54
|
}, 5000);
|
|
55
55
|
};
|
|
@@ -58,9 +58,9 @@ class HBConfigNode {
|
|
|
58
58
|
this.hbDevices = await this.hapClient.getAllServices();
|
|
59
59
|
this.evDevices = this.toList({ perms: 'ev' });
|
|
60
60
|
this.ctDevices = this.toList({ perms: 'pw' });
|
|
61
|
-
|
|
61
|
+
this.log.info(`Devices initialized: evDevices: ${this.evDevices.length}, ctDevices: ${this.ctDevices.length}`);
|
|
62
62
|
this.handleDuplicates(this.evDevices);
|
|
63
|
-
debug('Queue', this.reqisterQueue.getStats());
|
|
63
|
+
debug('Queue stats:', this.reqisterQueue.getStats());
|
|
64
64
|
this.reqisterQueue.resume();
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -73,7 +73,7 @@ class HBConfigNode {
|
|
|
73
73
|
'Television', 'Temperature Sensor', 'Thermostat', 'Contact Sensor',
|
|
74
74
|
];
|
|
75
75
|
|
|
76
|
-
return this.hbDevices
|
|
76
|
+
return filterUnique(this.hbDevices)
|
|
77
77
|
.filter(service => supportedTypes.includes(service.humanType))
|
|
78
78
|
.map(service => ({
|
|
79
79
|
name: service.serviceName,
|
|
@@ -91,23 +91,19 @@ class HBConfigNode {
|
|
|
91
91
|
const seenFullNames = new Set();
|
|
92
92
|
const seenUniqueIds = new Set();
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
if (seenFullNames.
|
|
94
|
+
list.forEach(endpoint => {
|
|
95
|
+
if (!seenFullNames.add(endpoint.fullName)) {
|
|
96
96
|
console.warn('WARNING: Duplicate device name', endpoint.fullName);
|
|
97
|
-
} else {
|
|
98
|
-
seenFullNames.add(endpoint.fullName);
|
|
99
97
|
}
|
|
100
98
|
|
|
101
|
-
if (seenUniqueIds.
|
|
102
|
-
console.error('ERROR:
|
|
103
|
-
} else {
|
|
104
|
-
seenUniqueIds.add(endpoint.uniqueId);
|
|
99
|
+
if (!seenUniqueIds.add(endpoint.uniqueId)) {
|
|
100
|
+
console.error('ERROR: Duplicate uniqueId detected.', endpoint.fullName);
|
|
105
101
|
}
|
|
106
|
-
}
|
|
102
|
+
});
|
|
107
103
|
}
|
|
108
104
|
|
|
109
105
|
register(clientNode) {
|
|
110
|
-
debug('Register %s
|
|
106
|
+
debug('Register: %s type: %s', clientNode.type, clientNode.name);
|
|
111
107
|
this.clientNodes[clientNode.id] = clientNode;
|
|
112
108
|
this.reqisterQueue.push(clientNode);
|
|
113
109
|
clientNode.status({ fill: 'yellow', shape: 'ring', text: 'connecting' });
|
|
@@ -115,56 +111,56 @@ class HBConfigNode {
|
|
|
115
111
|
|
|
116
112
|
async _register(clientNodes, cb) {
|
|
117
113
|
for (const clientNode of clientNodes) {
|
|
118
|
-
debug('_Register %s
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
debug('_Register: %s type: %s', clientNode.type, clientNode.name);
|
|
115
|
+
const matchedDevice = this.hbDevices.find(service =>
|
|
116
|
+
clientNode.device === `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (matchedDevice) {
|
|
120
|
+
clientNode.hbDevice = matchedDevice;
|
|
121
|
+
clientNode.status({ fill: 'green', shape: 'dot', text: 'connected' });
|
|
122
|
+
clientNode.emit('hbReady', matchedDevice);
|
|
123
|
+
if (clientNode.config.type === 'hb-status') {
|
|
124
|
+
this.monitorNodes[clientNode.device] = matchedDevice;
|
|
124
125
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (clientNode.config.type === 'hb-status') {
|
|
129
|
-
this.monitorNodes[clientNode.device] = clientNode.hbDevice;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!clientNode.hbDevice) {
|
|
133
|
-
console.error('ERROR: _register - HB Device Missing', clientNode.name);
|
|
126
|
+
} else {
|
|
127
|
+
console.error('ERROR: Device registration failed', clientNode.name);
|
|
134
128
|
}
|
|
135
129
|
}
|
|
136
130
|
|
|
131
|
+
await this.monitorDevices();
|
|
132
|
+
|
|
133
|
+
cb(null);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async monitorDevices() {
|
|
137
137
|
if (Object.keys(this.monitorNodes).length) {
|
|
138
138
|
this.monitor = await this.hapClient.monitorCharacteristics(Object.values(this.monitorNodes));
|
|
139
139
|
this.monitor.on('service-update', (services) => {
|
|
140
|
-
|
|
141
|
-
const eventNodes = Object.values(this.clientNodes).filter(
|
|
142
|
-
clientNode
|
|
143
|
-
clientNode.config.device === `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`
|
|
140
|
+
services.forEach(service => {
|
|
141
|
+
const eventNodes = Object.values(this.clientNodes).filter(clientNode =>
|
|
142
|
+
clientNode.config.device === `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`
|
|
144
143
|
);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (eventNode._events && typeof eventNode.emit === 'function') {
|
|
148
|
-
eventNode.emit('hbEvent', service);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}
|
|
144
|
+
eventNodes.forEach(eventNode => eventNode.emit('hbEvent', service));
|
|
145
|
+
});
|
|
152
146
|
});
|
|
153
147
|
}
|
|
154
|
-
cb(null);
|
|
155
148
|
}
|
|
156
|
-
/*
|
|
157
|
-
deregister(clientNode) {
|
|
158
|
-
clientNode.status({ text: 'disconnected', shape: 'ring', fill: 'red' });
|
|
159
|
-
delete this.clientNodes[clientNode.id];
|
|
160
|
-
}
|
|
161
|
-
*/
|
|
162
149
|
close() {
|
|
163
|
-
|
|
164
|
-
this.hapClient.destroy();
|
|
165
|
-
}
|
|
150
|
+
this.hapClient?.destroy();
|
|
166
151
|
}
|
|
167
|
-
|
|
168
152
|
}
|
|
169
153
|
|
|
154
|
+
|
|
155
|
+
// Filter unique devices by AID, service name, username, and port
|
|
156
|
+
const filterUnique = (data) => {
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
return data.filter(item => {
|
|
159
|
+
const uniqueKey = `${item.aid}-${item.serviceName}-${item.instance.username}-${item.instance.port}`;
|
|
160
|
+
if (seen.has(uniqueKey)) return false;
|
|
161
|
+
seen.add(uniqueKey);
|
|
162
|
+
return true;
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
170
166
|
module.exports = HBConfigNode;
|
package/src/hbControlNode.js
CHANGED
|
@@ -54,6 +54,7 @@ class HbControlNode extends hbBaseNode {
|
|
|
54
54
|
const result = await this.hbDevice.setCharacteristicByType(key, message.payload[key]);
|
|
55
55
|
results.push({ [result.type]: result.value });
|
|
56
56
|
} catch (error) {
|
|
57
|
+
console.log(error)
|
|
57
58
|
this.error(`Failed to set value for "${key}": ${error.message}`);
|
|
58
59
|
results.push({ [key]: `Error: ${error.message}` });
|
|
59
60
|
fill = 'red';
|
package/src/hbStatusNode.js
CHANGED
|
@@ -15,13 +15,19 @@ class HbStatusNode extends HbBaseNode {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const result = await this.hbDevice.refreshCharacteristics();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
if (result) {
|
|
19
|
+
this.status({
|
|
20
|
+
text: this.statusText(JSON.stringify(await this.hbDevice.values)),
|
|
21
|
+
shape: 'dot',
|
|
22
|
+
fill: 'green'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
send(Object.assign(message, this.createMessage(result)));
|
|
26
|
+
} else {
|
|
27
|
+
this.status({ fill: "red", shape: "ring", text: "disconnected" });
|
|
28
|
+
this.error("No response from device", this.name);
|
|
29
|
+
}
|
|
23
30
|
|
|
24
|
-
send(Object.assign(message, this.createMessage(result)));
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
|
package/test/node-red/flows.json
CHANGED
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
"payload": "{\"On\": true}",
|
|
244
244
|
"payloadType": "json",
|
|
245
245
|
"x": 110,
|
|
246
|
-
"y":
|
|
246
|
+
"y": 300,
|
|
247
247
|
"wires": [
|
|
248
248
|
[
|
|
249
249
|
"6703815a8874b156"
|
|
@@ -436,14 +436,14 @@
|
|
|
436
436
|
"id": "12ce98441601c981",
|
|
437
437
|
"type": "hb-event",
|
|
438
438
|
"z": "caef1e7b5b399e80",
|
|
439
|
-
"name": "Driveway
|
|
439
|
+
"name": "Driveway",
|
|
440
440
|
"Homebridge": "ECI-T24F2",
|
|
441
441
|
"Manufacturer": "HikVision",
|
|
442
|
-
"Service": "
|
|
443
|
-
"device": "ECI-T24F2CB:6F:94:DD:43:
|
|
442
|
+
"Service": "MotionSensor",
|
|
443
|
+
"device": "ECI-T24F2CB:6F:94:DD:43:77HikVisionDriveway00000085",
|
|
444
444
|
"conf": "557aec8e8c47e61e",
|
|
445
445
|
"sendInitialState": true,
|
|
446
|
-
"x":
|
|
446
|
+
"x": 320,
|
|
447
447
|
"y": 520,
|
|
448
448
|
"wires": [
|
|
449
449
|
[
|
|
@@ -603,5 +603,161 @@
|
|
|
603
603
|
"wires": [
|
|
604
604
|
[]
|
|
605
605
|
]
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"id": "4b787bfb023ccf23",
|
|
609
|
+
"type": "hb-event",
|
|
610
|
+
"z": "caef1e7b5b399e80",
|
|
611
|
+
"name": "Backyard",
|
|
612
|
+
"Homebridge": "Default Model",
|
|
613
|
+
"Manufacturer": "NRCHKB",
|
|
614
|
+
"Service": "TemperatureSensor",
|
|
615
|
+
"device": "Default Model69:62:B7:AE:38:D4NRCHKBBackyard0000008A",
|
|
616
|
+
"conf": "557aec8e8c47e61e",
|
|
617
|
+
"sendInitialState": true,
|
|
618
|
+
"x": 160,
|
|
619
|
+
"y": 660,
|
|
620
|
+
"wires": [
|
|
621
|
+
[
|
|
622
|
+
"c68bef6563c5d07e"
|
|
623
|
+
]
|
|
624
|
+
]
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
"id": "c68bef6563c5d07e",
|
|
628
|
+
"type": "debug",
|
|
629
|
+
"z": "caef1e7b5b399e80",
|
|
630
|
+
"name": "debug 7",
|
|
631
|
+
"active": true,
|
|
632
|
+
"tosidebar": true,
|
|
633
|
+
"console": false,
|
|
634
|
+
"tostatus": true,
|
|
635
|
+
"complete": "payload",
|
|
636
|
+
"targetType": "msg",
|
|
637
|
+
"statusVal": "payload",
|
|
638
|
+
"statusType": "auto",
|
|
639
|
+
"x": 620,
|
|
640
|
+
"y": 740,
|
|
641
|
+
"wires": []
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
"id": "3c36252a6f45eed9",
|
|
645
|
+
"type": "hb-status",
|
|
646
|
+
"z": "caef1e7b5b399e80",
|
|
647
|
+
"name": "Backyard Tree",
|
|
648
|
+
"Homebridge": "Default Model",
|
|
649
|
+
"Manufacturer": "NRCHKB",
|
|
650
|
+
"Service": "TemperatureSensor",
|
|
651
|
+
"device": "Default Model69:62:B7:AE:38:D4NRCHKBBackyard Tree0000008A",
|
|
652
|
+
"conf": "557aec8e8c47e61e",
|
|
653
|
+
"x": 360,
|
|
654
|
+
"y": 800,
|
|
655
|
+
"wires": [
|
|
656
|
+
[
|
|
657
|
+
"c68bef6563c5d07e"
|
|
658
|
+
]
|
|
659
|
+
]
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
"id": "3b7537939b63eee8",
|
|
663
|
+
"type": "inject",
|
|
664
|
+
"z": "caef1e7b5b399e80",
|
|
665
|
+
"name": "",
|
|
666
|
+
"props": [
|
|
667
|
+
{
|
|
668
|
+
"p": "payload"
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
"p": "topic",
|
|
672
|
+
"vt": "str"
|
|
673
|
+
}
|
|
674
|
+
],
|
|
675
|
+
"repeat": "60",
|
|
676
|
+
"crontab": "",
|
|
677
|
+
"once": true,
|
|
678
|
+
"onceDelay": "60",
|
|
679
|
+
"topic": "",
|
|
680
|
+
"payload": "",
|
|
681
|
+
"payloadType": "date",
|
|
682
|
+
"x": 150,
|
|
683
|
+
"y": 800,
|
|
684
|
+
"wires": [
|
|
685
|
+
[
|
|
686
|
+
"3c36252a6f45eed9"
|
|
687
|
+
]
|
|
688
|
+
]
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
"id": "aabce5ff7fbbeec6",
|
|
692
|
+
"type": "status",
|
|
693
|
+
"z": "caef1e7b5b399e80",
|
|
694
|
+
"name": "",
|
|
695
|
+
"scope": [
|
|
696
|
+
"3d7babac3a298e60",
|
|
697
|
+
"452e3e6171aa7a25",
|
|
698
|
+
"0ed3cd7e0d60beda",
|
|
699
|
+
"6703815a8874b156",
|
|
700
|
+
"82638dac6ac32bb1",
|
|
701
|
+
"6216377792cba653",
|
|
702
|
+
"12ce98441601c981",
|
|
703
|
+
"4b787bfb023ccf23",
|
|
704
|
+
"3c36252a6f45eed9"
|
|
705
|
+
],
|
|
706
|
+
"x": 1020,
|
|
707
|
+
"y": 380,
|
|
708
|
+
"wires": [
|
|
709
|
+
[
|
|
710
|
+
"94f051597d18eaa3"
|
|
711
|
+
]
|
|
712
|
+
]
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
"id": "94f051597d18eaa3",
|
|
716
|
+
"type": "debug",
|
|
717
|
+
"z": "caef1e7b5b399e80",
|
|
718
|
+
"name": "debug 8",
|
|
719
|
+
"active": true,
|
|
720
|
+
"tosidebar": true,
|
|
721
|
+
"console": false,
|
|
722
|
+
"tostatus": true,
|
|
723
|
+
"complete": "true",
|
|
724
|
+
"targetType": "full",
|
|
725
|
+
"statusVal": "status.source.name",
|
|
726
|
+
"statusType": "msg",
|
|
727
|
+
"x": 1180,
|
|
728
|
+
"y": 380,
|
|
729
|
+
"wires": []
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
"id": "296297c79b544f59",
|
|
733
|
+
"type": "catch",
|
|
734
|
+
"z": "caef1e7b5b399e80",
|
|
735
|
+
"name": "",
|
|
736
|
+
"scope": null,
|
|
737
|
+
"uncaught": false,
|
|
738
|
+
"x": 1020,
|
|
739
|
+
"y": 300,
|
|
740
|
+
"wires": [
|
|
741
|
+
[
|
|
742
|
+
"0073218b1ebc53f1"
|
|
743
|
+
]
|
|
744
|
+
]
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
"id": "0073218b1ebc53f1",
|
|
748
|
+
"type": "debug",
|
|
749
|
+
"z": "caef1e7b5b399e80",
|
|
750
|
+
"name": "debug 9",
|
|
751
|
+
"active": true,
|
|
752
|
+
"tosidebar": true,
|
|
753
|
+
"console": false,
|
|
754
|
+
"tostatus": true,
|
|
755
|
+
"complete": "payload",
|
|
756
|
+
"targetType": "msg",
|
|
757
|
+
"statusVal": "payload",
|
|
758
|
+
"statusType": "auto",
|
|
759
|
+
"x": 1200,
|
|
760
|
+
"y": 300,
|
|
761
|
+
"wires": []
|
|
606
762
|
}
|
|
607
763
|
]
|