node-red-contrib-homebridge-automation 0.3.0-beta.2 → 0.3.0-beta.20
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/README.md +2 -0
- package/package.json +7 -7
- package/src/HAP-NodeRed.html +18 -1
- package/src/hbConfigNode.js +19 -12
- package/src/hbConfigNode.test.js +2 -5
- package/src/hbControlNode.js +16 -5
- package/test/homebridge-automation-endpoints.json +7740 -7736
- package/test/homebridge-automation-hbDevices-v3.json +19 -19
- package/test/node-red/flows.json +599 -110
package/README.md
CHANGED
|
@@ -199,6 +199,8 @@ With a plugin, you can see if it supports Real Time events, by opening the Home
|
|
|
199
199
|
|
|
200
200
|
- Add common supported types Window, Window Covering, Light Sensor #151, tks @HDeKnop
|
|
201
201
|
- HB-Control Node Turns Off then On when this message is received #152
|
|
202
|
+
- Added `Debug logging` configuration option that creates a file `homebridge-automation-endpoints.json`, which contains all the homebridge devices discovered. It can be used as part of troubleshooting device issues.
|
|
203
|
+
- Fix for some camara devices not outputing events
|
|
202
204
|
|
|
203
205
|
# Backlog / Roadmap
|
|
204
206
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-homebridge-automation",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.20",
|
|
4
4
|
"description": "NodeRED Automation for HomeBridge",
|
|
5
5
|
"main": "src/HAP-NodeRed.js",
|
|
6
6
|
"scripts": {
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"@types/node-red": "^1.3.5",
|
|
29
29
|
"@types/jest": "^29.5.14",
|
|
30
30
|
"@eslint/js": "^9.16.0",
|
|
31
|
-
"eslint": "^
|
|
32
|
-
"eslint-plugin-format": "^0.
|
|
33
|
-
"eslint-plugin-jest": "^
|
|
34
|
-
"globals": "^
|
|
31
|
+
"eslint": "^9.36.0",
|
|
32
|
+
"eslint-plugin-format": "^1.0.2",
|
|
33
|
+
"eslint-plugin-jest": "^29.0.1",
|
|
34
|
+
"globals": "^16.4.0",
|
|
35
35
|
"jest": "^29.7.0",
|
|
36
36
|
"node-red": "^4.0.2",
|
|
37
37
|
"node-red-node-test-helper": "^0.3.4",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"better-queue": ">=3.8.12",
|
|
44
|
-
"debug": "^4.
|
|
45
|
-
"@homebridge/hap-client": "^
|
|
44
|
+
"debug": "^4.4.1",
|
|
45
|
+
"@homebridge/hap-client": "^3.1.1"
|
|
46
46
|
},
|
|
47
47
|
"author": "NorthernMan54",
|
|
48
48
|
"license": "ISC",
|
package/src/HAP-NodeRed.html
CHANGED
|
@@ -7,10 +7,23 @@
|
|
|
7
7
|
<label for="node-config-input-macAddress"><i class="icon-wifi"></i> MAC Address (optional)</label>
|
|
8
8
|
<input type="text" id="node-config-input-macAddress" placeholder="00:00:00:A1:2B:CC">
|
|
9
9
|
</div>
|
|
10
|
+
<div class="form-row">
|
|
11
|
+
<label for="node-config-input-debug"><i class="icon-tag"></i>Debug Logging</label>
|
|
12
|
+
<input type="checkbox" id="node-config-input-debug" placeholder=false>
|
|
13
|
+
</div>
|
|
10
14
|
</script>
|
|
11
15
|
|
|
12
16
|
<script type="text/x-red" data-help-name="hb-conf">
|
|
13
|
-
<p>
|
|
17
|
+
<p>Configuration Node for Homebridge</p>
|
|
18
|
+
<h3>Settings</h3>
|
|
19
|
+
<dl class="message-properties">
|
|
20
|
+
<dt>PIN<span class="property-type">string</span></dt>
|
|
21
|
+
<dd>Please enter the PIN from your homebridge instances. Please note that the same pin must be used for all instances.</dd>
|
|
22
|
+
<dt>MAC Address<span class="property-type">object</span></dt>
|
|
23
|
+
<dd>Optional - Not implemented</dd>
|
|
24
|
+
<dt>Debug Logging<span class="property-type">string</span></dt>
|
|
25
|
+
<dd>Enables debug logging and creates a file `homebridge-automation-endpoints.json`, which contains all the homebridge devices discovered. It can be used as part of troubleshooting device issues.</dd>
|
|
26
|
+
</dl>
|
|
14
27
|
</script>
|
|
15
28
|
|
|
16
29
|
<script type="text/javascript">
|
|
@@ -36,6 +49,10 @@
|
|
|
36
49
|
},
|
|
37
50
|
required: false
|
|
38
51
|
},
|
|
52
|
+
debug: {
|
|
53
|
+
value: false,
|
|
54
|
+
required: false
|
|
55
|
+
}
|
|
39
56
|
},
|
|
40
57
|
credentials: {
|
|
41
58
|
password: {
|
package/src/hbConfigNode.js
CHANGED
|
@@ -2,6 +2,7 @@ const { HapClient } = require('@homebridge/hap-client');
|
|
|
2
2
|
const debug = require('debug')('hapNodeRed:hbConfigNode');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const process = require('process');
|
|
5
6
|
|
|
6
7
|
class HBConfigNode {
|
|
7
8
|
constructor(config, RED) {
|
|
@@ -10,6 +11,7 @@ class HBConfigNode {
|
|
|
10
11
|
// Initialize properties
|
|
11
12
|
this.username = config.username;
|
|
12
13
|
this.macAddress = config.macAddress || '';
|
|
14
|
+
this.debugLogging = config.debug || false;
|
|
13
15
|
this.users = {};
|
|
14
16
|
this.homebridge = null;
|
|
15
17
|
this.evDevices = [];
|
|
@@ -53,14 +55,14 @@ class HBConfigNode {
|
|
|
53
55
|
*/
|
|
54
56
|
async handleReady() {
|
|
55
57
|
const updatedDevices = await this.hapClient.getAllServices();
|
|
56
|
-
if (this.
|
|
58
|
+
if (this.debugLogging && updatedDevices && updatedDevices.length && process.uptime() < 300) {
|
|
57
59
|
try {
|
|
58
|
-
const storagePath = path.join(process.cwd(), '
|
|
60
|
+
const storagePath = path.join(process.cwd(), 'homebridge-automation-endpoints.json');
|
|
59
61
|
this.warn(`Writing Homebridge endpoints to ${storagePath}`);
|
|
60
62
|
fs.writeFileSync(storagePath, JSON.stringify(updatedDevices, null, 2));
|
|
61
63
|
} catch (e) {
|
|
62
64
|
this.error(`Error writing Homebridge endpoints to file: ${e.message}`);
|
|
63
|
-
}
|
|
65
|
+
}
|
|
64
66
|
}
|
|
65
67
|
// Fix broken uniqueId's from HAP-Client
|
|
66
68
|
updatedDevices.forEach((service) => {
|
|
@@ -86,9 +88,9 @@ class HBConfigNode {
|
|
|
86
88
|
|
|
87
89
|
toList(perms) {
|
|
88
90
|
const supportedTypes = new Set([
|
|
89
|
-
'Battery', 'Carbon Dioxide Sensor', 'Carbon Monoxide Sensor', 'Camera Rtp Stream Management',
|
|
91
|
+
'Air Purifier', 'Air Quality Sensor', 'Battery', 'Carbon Dioxide Sensor', 'Carbon Monoxide Sensor', 'Camera Rtp Stream Management',
|
|
90
92
|
'Doorbell', 'Fan', 'Fanv2', 'Garage Door Opener', 'Humidity Sensor', 'Input Source',
|
|
91
|
-
'Leak Sensor', 'Lightbulb', 'Lock Mechanism', 'Motion Sensor', 'Occupancy Sensor',
|
|
93
|
+
'Leak Sensor', 'Light Sensor', 'Lightbulb', 'Lock Mechanism', 'Motion Sensor', 'Occupancy Sensor',
|
|
92
94
|
'Outlet', 'Smoke Sensor', 'Speaker', 'Stateless Programmable Switch', 'Switch',
|
|
93
95
|
'Television', 'Temperature Sensor', 'Thermostat', 'Contact Sensor',
|
|
94
96
|
'Window', 'Window Covering', 'Light Sensor'
|
|
@@ -136,10 +138,12 @@ class HBConfigNode {
|
|
|
136
138
|
async connectClientNodes() {
|
|
137
139
|
debug('connect %s nodes', Object.keys(this.clientNodes).length);
|
|
138
140
|
for (const [key, clientNode] of Object.entries(this.clientNodes)) {
|
|
139
|
-
// debug('_Register: %s type: %s', clientNode.type, clientNode.name, clientNode.instance);
|
|
140
|
-
const matchedDevice = this.hbDevices.find(service =>
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// debug('_Register: %s type: "%s" "%s" "%s"', clientNode.type, clientNode.name, clientNode.instance, clientNode.device);
|
|
142
|
+
const matchedDevice = this.hbDevices.find(service => {
|
|
143
|
+
const friendlyName = (service.accessoryInformation.Name ? service.accessoryInformation.Name : service.serviceName);
|
|
144
|
+
const deviceIdentifier = `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${friendlyName}${service.uuid.slice(0, 8)}`;
|
|
145
|
+
return clientNode.device === deviceIdentifier;
|
|
146
|
+
});
|
|
143
147
|
|
|
144
148
|
if (matchedDevice) {
|
|
145
149
|
clientNode.hbDevice = matchedDevice;
|
|
@@ -147,7 +151,7 @@ class HBConfigNode {
|
|
|
147
151
|
clientNode.emit('hbReady', matchedDevice);
|
|
148
152
|
debug('_Registered: %s type: %s', clientNode.type, matchedDevice.type, matchedDevice.serviceName);
|
|
149
153
|
} else {
|
|
150
|
-
this.error(`ERROR: Device registration failed '${clientNode.fullName}'`);
|
|
154
|
+
this.error(`ERROR: Device registration failed '${clientNode.fullName}' - '${clientNode.device}'`);
|
|
151
155
|
}
|
|
152
156
|
};
|
|
153
157
|
|
|
@@ -170,8 +174,11 @@ class HBConfigNode {
|
|
|
170
174
|
this.monitor = await this.hapClient.monitorCharacteristics(monitorNodes);
|
|
171
175
|
this.monitor.on('service-update', (services) => {
|
|
172
176
|
services.forEach(service => {
|
|
173
|
-
const eventNodes = Object.values(this.clientNodes).filter(clientNode =>
|
|
174
|
-
|
|
177
|
+
const eventNodes = Object.values(this.clientNodes).filter(clientNode => {
|
|
178
|
+
const deviceIdentifier = `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${(service.accessoryInformation.Name ? service.accessoryInformation.Name : service.serviceName)}${service.uuid.slice(0, 8)}`;
|
|
179
|
+
// debug('service-update: compare', clientNode.config.device, deviceIdentifier);
|
|
180
|
+
return clientNode.config.device === deviceIdentifier;
|
|
181
|
+
}
|
|
175
182
|
);
|
|
176
183
|
// debug('service-update', service.serviceName, eventNodes);
|
|
177
184
|
eventNodes.forEach(eventNode => eventNode.emit('hbEvent', service));
|
package/src/hbConfigNode.test.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
const { HapClient } = require('@homebridge/hap-client');
|
|
1
|
+
// File: src/hbConfigNode.test.js
|
|
3
2
|
const HBConfigNode = require('./hbConfigNode'); // Update the path as necessary
|
|
4
3
|
const fs = require('fs');
|
|
5
4
|
const path = require('path');
|
|
5
|
+
const process = require('process');
|
|
6
6
|
|
|
7
7
|
jest.mock('@homebridge/hap-client', () => {
|
|
8
8
|
return {
|
|
@@ -20,7 +20,6 @@ describe('Issue 142', () => {
|
|
|
20
20
|
let mockConfig;
|
|
21
21
|
let RED;
|
|
22
22
|
let node;
|
|
23
|
-
let mockHapClient;
|
|
24
23
|
|
|
25
24
|
beforeEach(() => {
|
|
26
25
|
mockConfig = {
|
|
@@ -57,7 +56,6 @@ describe('HBConfigNode', () => {
|
|
|
57
56
|
let mockConfig;
|
|
58
57
|
let RED;
|
|
59
58
|
let node;
|
|
60
|
-
let mockHapClient;
|
|
61
59
|
|
|
62
60
|
beforeEach(() => {
|
|
63
61
|
mockConfig = {
|
|
@@ -94,7 +92,6 @@ describe('from files', () => {
|
|
|
94
92
|
let mockConfig;
|
|
95
93
|
let RED;
|
|
96
94
|
let node;
|
|
97
|
-
let mockHapClient;
|
|
98
95
|
|
|
99
96
|
beforeEach(() => {
|
|
100
97
|
mockConfig = {
|
package/src/hbControlNode.js
CHANGED
|
@@ -17,12 +17,12 @@ class HbControlNode extends hbBaseNode {
|
|
|
17
17
|
const isCamera = this.hbDevice.type === 'CameraRTPStreamManagement';
|
|
18
18
|
const payloadType = typeof message.payload;
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// Is the payload a valid JSON object?
|
|
21
|
+
|
|
21
22
|
if (!isCamera && payloadType !== 'object') {
|
|
22
23
|
const validNames = Object.keys(this.hbDevice.values)
|
|
23
24
|
.filter(key => key !== 'ConfiguredName')
|
|
24
25
|
.join(', ');
|
|
25
|
-
|
|
26
26
|
this.error(
|
|
27
27
|
`Invalid payload. Expected JSON object, e.g., {"On":false, "Brightness":0}. Valid values: ${validNames}`
|
|
28
28
|
);
|
|
@@ -30,6 +30,16 @@ class HbControlNode extends hbBaseNode {
|
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// Validate payload
|
|
34
|
+
let keysToKeep = Object.keys(this.hbDevice.values);
|
|
35
|
+
|
|
36
|
+
Object.keys(message.payload).forEach(key => {
|
|
37
|
+
if (!keysToKeep.includes(key)) {
|
|
38
|
+
this.handleWarning(`Unhandled Characteristic '${key}'`);
|
|
39
|
+
delete message.payload[key];
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
33
43
|
const results = [];
|
|
34
44
|
let fill = 'green';
|
|
35
45
|
|
|
@@ -50,12 +60,13 @@ class HbControlNode extends hbBaseNode {
|
|
|
50
60
|
} else {
|
|
51
61
|
// Handle other characteristics
|
|
52
62
|
try {
|
|
63
|
+
// debug('Setting value for', message.payload);
|
|
53
64
|
const result = await this.hbDevice.setCharacteristicsByTypes(filterIfOff(message.payload));
|
|
65
|
+
// debug('Result', result.values);
|
|
54
66
|
results.push(result.values);
|
|
55
67
|
} catch (error) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
results.push({ 'Error': `Error: ${error.message}` });
|
|
68
|
+
this.error(`${error.message} for ${JSON.stringify(message.payload)}`);
|
|
69
|
+
results.push({ Error: `${error.message} for ${JSON.stringify(message.payload)}` });
|
|
59
70
|
fill = 'red';
|
|
60
71
|
this.hbConfigNode.disconnectClientNodes(this.hbDevice.instance);
|
|
61
72
|
}
|