node-red-contrib-homebridge-automation 0.1.12-beta.33 → 0.1.12-beta.35
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/hbBaseNode.js +17 -3
- package/src/hbConfigNode.js +65 -39
- package/src/hbControlNode.js +3 -3
- package/src/hbResumeNode.js +2 -2
- package/src/hbStatusNode.js +2 -1
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.35",
|
|
4
4
|
"description": "NodeRED Automation for HomeBridge",
|
|
5
5
|
"main": "src/HAP-NodeRed.js",
|
|
6
6
|
"scripts": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"better-queue": ">=3.8.12",
|
|
48
48
|
"debug": "^4.3.5",
|
|
49
|
-
"@homebridge/hap-client": "2.0.5-beta.
|
|
49
|
+
"@homebridge/hap-client": "2.0.5-beta.18"
|
|
50
50
|
},
|
|
51
51
|
"author": "NorthernMan54",
|
|
52
52
|
"license": "ISC",
|
package/src/hbBaseNode.js
CHANGED
|
@@ -18,7 +18,7 @@ class HbBaseNode {
|
|
|
18
18
|
this.fullName = `${config.name} - ${config.Service}`;
|
|
19
19
|
this.hbDevice = null;
|
|
20
20
|
|
|
21
|
-
this.hbConfigNode?.
|
|
21
|
+
this.hbConfigNode?.registerClientNode(this);
|
|
22
22
|
|
|
23
23
|
if (this.handleInput) {
|
|
24
24
|
this.on('input', this.handleInput.bind(this));
|
|
@@ -61,20 +61,34 @@ class HbBaseNode {
|
|
|
61
61
|
statusText(message) {
|
|
62
62
|
return message.slice(0, 20)
|
|
63
63
|
}
|
|
64
|
+
|
|
64
65
|
/**
|
|
65
66
|
*
|
|
66
67
|
* @param {*} warning - Message to log and display in debug panel
|
|
67
68
|
* @param {*} statusText - Message to display under Node ( If not present, uses warning message text)
|
|
68
69
|
*/
|
|
69
|
-
|
|
70
|
+
handleWarning(warning, statusText) {
|
|
70
71
|
this.warn(warning);
|
|
71
72
|
this.status({
|
|
72
73
|
text: (statusText ? statusText : warning).slice(0, 20),
|
|
73
74
|
shape: 'ring',
|
|
74
|
-
fill: '
|
|
75
|
+
fill: 'yellow',
|
|
75
76
|
});
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
/**
|
|
80
|
+
*
|
|
81
|
+
* @param {*} warning - Message to log and display in debug panel
|
|
82
|
+
* @param {*} statusText - Message to display under Node ( If not present, uses warning message text)
|
|
83
|
+
*/
|
|
84
|
+
handleError(error, statusText) {
|
|
85
|
+
this.error(error);
|
|
86
|
+
this.status({
|
|
87
|
+
text: (statusText ? statusText : error).slice(0, 20),
|
|
88
|
+
shape: 'ring',
|
|
89
|
+
fill: 'red',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
module.exports = HbBaseNode;
|
package/src/hbConfigNode.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const { HapClient } = require('@homebridge/hap-client');
|
|
2
2
|
const debug = require('debug')('hapNodeRed:hbConfigNode');
|
|
3
3
|
const { Log } = require('./lib/logger.js');
|
|
4
|
-
const Queue = require('better-queue');
|
|
5
4
|
|
|
6
5
|
class HBConfigNode {
|
|
7
6
|
constructor(config, RED) {
|
|
@@ -17,21 +16,9 @@ class HBConfigNode {
|
|
|
17
16
|
this.ctDevices = [];
|
|
18
17
|
this.hbDevices = [];
|
|
19
18
|
this.clientNodes = [];
|
|
20
|
-
this.monitorNodes = [];
|
|
21
19
|
this.log = new Log(console, true);
|
|
22
20
|
this.discoveryTimeout = null;
|
|
23
21
|
|
|
24
|
-
// Initialize queue
|
|
25
|
-
this.reqisterQueue = new Queue(this._register.bind(this), {
|
|
26
|
-
concurrent: 1,
|
|
27
|
-
autoResume: false,
|
|
28
|
-
maxRetries: 1000,
|
|
29
|
-
retryDelay: 30000,
|
|
30
|
-
batchDelay: 2000,
|
|
31
|
-
batchSize: 150,
|
|
32
|
-
});
|
|
33
|
-
this.reqisterQueue.pause();
|
|
34
|
-
|
|
35
22
|
// Initialize HAP client
|
|
36
23
|
this.hapClient = new HapClient({
|
|
37
24
|
config: { debug: true },
|
|
@@ -42,31 +29,59 @@ class HBConfigNode {
|
|
|
42
29
|
this.hapClient.on('instance-discovered', this.waitForNoMoreDiscoveries);
|
|
43
30
|
this.waitForNoMoreDiscoveries();
|
|
44
31
|
this.on('close', this.close.bind(this));
|
|
32
|
+
this.refreshInProcess = true; // Prevents multiple refreshes, hapClient kicks of a discovery on start
|
|
45
33
|
}
|
|
46
34
|
}
|
|
47
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Start device discovery after monitor reports issues
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
refreshDevices = () => {
|
|
41
|
+
if (!this.refreshInProcess) {
|
|
42
|
+
|
|
43
|
+
this.log.debug('Monitor reported homebridge stability issues, refreshing devices');
|
|
44
|
+
this.hapClient.on('instance-discovered', this.waitForNoMoreDiscoveries);
|
|
45
|
+
this.hapClient.resetInstancePool();
|
|
46
|
+
this.waitForNoMoreDiscoveries();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wait for no more instance discoveries to be made before publishing services
|
|
52
|
+
*/
|
|
48
53
|
waitForNoMoreDiscoveries = () => {
|
|
49
54
|
if (!this.discoveryTimeout) {
|
|
50
55
|
clearTimeout(this.discoveryTimeout);
|
|
51
56
|
this.discoveryTimeout = setTimeout(() => {
|
|
52
57
|
this.log.debug('No more instances discovered, publishing services');
|
|
53
58
|
this.hapClient.removeListener('instance-discovered', this.waitForNoMoreDiscoveries);
|
|
54
|
-
this.hapClient.on('instance-discovered', async (instance) => { debug('instance-discovered', instance); await this.monitorDevices(); });
|
|
55
|
-
this.hapClient.on('discovery-ended', async () => { debug('discovery-ended'); });
|
|
56
59
|
this.handleReady();
|
|
57
60
|
this.discoveryTimeout = null;
|
|
58
|
-
|
|
61
|
+
this.refreshInProcess = false;
|
|
62
|
+
}, 20000); // resetInstancePool() triggers a discovery after 6 seconds. Need to wait for it to finish.
|
|
59
63
|
}
|
|
60
64
|
};
|
|
61
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Populate the list of devices and handle duplicates
|
|
68
|
+
*/
|
|
62
69
|
async handleReady() {
|
|
63
|
-
|
|
70
|
+
const updatedDevices = await this.hapClient.getAllServices();
|
|
71
|
+
updatedDevices.forEach((updatedService, index) => {
|
|
72
|
+
if (this.hbDevices.find(service => service.uniqueId === updatedService.uniqueId)) {
|
|
73
|
+
const update = this.hbDevices.find(service => service.uniqueId === updatedService.uniqueId);
|
|
74
|
+
update.instance = updatedService.instance;
|
|
75
|
+
} else {
|
|
76
|
+
this.hbDevices.push(updatedService);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
64
80
|
this.evDevices = this.toList({ perms: 'ev' });
|
|
65
81
|
this.ctDevices = this.toList({ perms: 'pw' });
|
|
66
82
|
this.log.info(`Devices initialized: evDevices: ${this.evDevices.length}, ctDevices: ${this.ctDevices.length}`);
|
|
67
83
|
this.handleDuplicates(this.evDevices);
|
|
68
|
-
|
|
69
|
-
this.reqisterQueue.resume();
|
|
84
|
+
this.connectClientNodes();
|
|
70
85
|
}
|
|
71
86
|
|
|
72
87
|
toList(perms) {
|
|
@@ -107,16 +122,17 @@ class HBConfigNode {
|
|
|
107
122
|
});
|
|
108
123
|
}
|
|
109
124
|
|
|
110
|
-
|
|
125
|
+
registerClientNode(clientNode) {
|
|
111
126
|
debug('Register: %s type: %s', clientNode.type, clientNode.name);
|
|
112
127
|
this.clientNodes[clientNode.id] = clientNode;
|
|
113
|
-
this.reqisterQueue.push(clientNode);
|
|
114
128
|
clientNode.status({ fill: 'yellow', shape: 'ring', text: 'connecting' });
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
async
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
async connectClientNodes() {
|
|
132
|
+
debug('connect %s nodes', Object.keys(this.clientNodes).length);
|
|
133
|
+
console.log(this.clientNodes);
|
|
134
|
+
for (const [key, clientNode] of Object.entries(this.clientNodes)) {
|
|
135
|
+
// debug('_Register: %s type: %s', clientNode.type, clientNode.name, clientNode.instance);
|
|
120
136
|
const matchedDevice = this.hbDevices.find(service =>
|
|
121
137
|
clientNode.device === `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`
|
|
122
138
|
);
|
|
@@ -126,23 +142,19 @@ class HBConfigNode {
|
|
|
126
142
|
clientNode.status({ fill: 'green', shape: 'dot', text: 'connected' });
|
|
127
143
|
clientNode.emit('hbReady', matchedDevice);
|
|
128
144
|
debug('_Registered: %s type: %s', matchedDevice.type, matchedDevice.serviceName, matchedDevice.instance);
|
|
129
|
-
if (clientNode.config.type === 'hb-status' || clientNode.config.type === 'hb-event') {
|
|
130
|
-
this.monitorNodes[clientNode.device] = matchedDevice;
|
|
131
|
-
}
|
|
132
145
|
} else {
|
|
133
146
|
console.error('ERROR: Device registration failed', clientNode.name);
|
|
134
147
|
}
|
|
135
|
-
}
|
|
148
|
+
};
|
|
136
149
|
|
|
137
150
|
await this.monitorDevices();
|
|
138
|
-
|
|
139
|
-
cb(null);
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
async monitorDevices() {
|
|
143
|
-
debug('monitorDevices', Object.keys(this.
|
|
144
|
-
if (Object.keys(this.
|
|
145
|
-
|
|
154
|
+
debug('monitorDevices', Object.keys(this.clientNodes).length);
|
|
155
|
+
if (Object.keys(this.clientNodes).length) {
|
|
156
|
+
const monitorNodes = this.clientNodes.filter(clientNode => clientNode.config.type === 'hb-status' || clientNode.config.type === 'hb-event').hbDevice;
|
|
157
|
+
this.monitor = await this.hapClient.monitorCharacteristics(monitorNodes);
|
|
146
158
|
this.monitor.on('service-update', (services) => {
|
|
147
159
|
services.forEach(service => {
|
|
148
160
|
const eventNodes = Object.values(this.clientNodes).filter(clientNode =>
|
|
@@ -151,15 +163,29 @@ class HBConfigNode {
|
|
|
151
163
|
eventNodes.forEach(eventNode => eventNode.emit('hbEvent', service));
|
|
152
164
|
});
|
|
153
165
|
});
|
|
154
|
-
this.monitor.on('monitor-close', (hadError) => {
|
|
155
|
-
debug('monitor-close', hadError)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
this.monitor.on('monitor-close', (instance, hadError) => {
|
|
167
|
+
debug('monitor-close', instance.name, instance.ipAddress, instance.port, hadError)
|
|
168
|
+
this.disconnectClientNodes(instance);
|
|
169
|
+
this.refreshDevices();
|
|
170
|
+
})
|
|
171
|
+
this.monitor.on('monitor-error', (instance, hadError) => {
|
|
172
|
+
debug('monitor-error', instance, hadError)
|
|
160
173
|
})
|
|
161
174
|
}
|
|
162
175
|
}
|
|
176
|
+
|
|
177
|
+
disconnectClientNodes(instance) {
|
|
178
|
+
debug('disconnectClientNodes', `${instance.ipAddress}:${instance.port}`);
|
|
179
|
+
const clientNodes = Object.values(this.clientNodes).filter(clientNode => {
|
|
180
|
+
return `${clientNode.hbDevice.instance.ipAddress}:${clientNode.hbDevice.instance.port}` === `${instance.ipAddress}:${instance.port}`;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
clientNodes.forEach(clientNode => {
|
|
184
|
+
clientNode.status({ fill: 'red', shape: 'ring', text: 'disconnected' });
|
|
185
|
+
clientNode.emit('hbDisconnected', instance);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
163
189
|
close() {
|
|
164
190
|
debug('hb-config: close');
|
|
165
191
|
this.hapClient?.destroy();
|
package/src/hbControlNode.js
CHANGED
|
@@ -10,7 +10,7 @@ class HbControlNode extends hbBaseNode {
|
|
|
10
10
|
debug('handleInput', message.payload, this.name);
|
|
11
11
|
|
|
12
12
|
if (!this.hbDevice) {
|
|
13
|
-
this.
|
|
13
|
+
this.handleWarning('HB not initialized');
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -58,6 +58,7 @@ class HbControlNode extends hbBaseNode {
|
|
|
58
58
|
this.error(`Failed to set value for "${key}": ${error.message}`);
|
|
59
59
|
results.push({ [key]: `Error: ${error.message}` });
|
|
60
60
|
fill = 'red';
|
|
61
|
+
this.hbConfigNode.disconnectClientNodes(this.hbDevice.instance);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -67,8 +68,7 @@ class HbControlNode extends hbBaseNode {
|
|
|
67
68
|
this.status({ text: statusText, shape: 'dot', fill });
|
|
68
69
|
done
|
|
69
70
|
} catch (error) {
|
|
70
|
-
this.error
|
|
71
|
-
this.status({ text: 'Unhandled error', shape: 'dot', fill: 'red' });
|
|
71
|
+
this.handleError(error, 'Unhandled error');
|
|
72
72
|
done(`Unhandled error: ${error.message}`);
|
|
73
73
|
}
|
|
74
74
|
}
|
package/src/hbResumeNode.js
CHANGED
|
@@ -12,7 +12,7 @@ class HbResumeNode extends HbBaseNode {
|
|
|
12
12
|
debug('handleInput', message.payload, this.name);
|
|
13
13
|
|
|
14
14
|
if (!this.hbDevice) {
|
|
15
|
-
this.
|
|
15
|
+
this.handleWarning('HB not initialized');
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ class HbResumeNode extends HbBaseNode {
|
|
|
20
20
|
const validNames = Object.keys(this.hbDevice.values)
|
|
21
21
|
.filter(key => key !== 'ConfiguredName')
|
|
22
22
|
.join(', ');
|
|
23
|
-
this.
|
|
23
|
+
this.handleWarning(
|
|
24
24
|
`Invalid payload. Expected: {"On": false, "Brightness": 0}. Valid values: ${validNames}`,
|
|
25
25
|
'Invalid payload'
|
|
26
26
|
);
|
package/src/hbStatusNode.js
CHANGED
|
@@ -10,7 +10,7 @@ class HbStatusNode extends HbBaseNode {
|
|
|
10
10
|
debug('handleInput', message.payload, this.name);
|
|
11
11
|
|
|
12
12
|
if (!this.hbDevice) {
|
|
13
|
-
this.
|
|
13
|
+
this.handleWarning('HB not initialized');
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -27,6 +27,7 @@ class HbStatusNode extends HbBaseNode {
|
|
|
27
27
|
} else {
|
|
28
28
|
this.status({ fill: "red", shape: "ring", text: "disconnected" });
|
|
29
29
|
this.error("No response from device", this.name);
|
|
30
|
+
this.hbConfigNode.disconnectClientNodes(this.hbDevice.instance);
|
|
30
31
|
done("No response from device");
|
|
31
32
|
}
|
|
32
33
|
|