node-red-contrib-homebridge-automation 0.3.0-beta.0 → 0.3.0-beta.10
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 +11 -2
- package/package.json +2 -2
- package/src/HAP-NodeRed.html +18 -1
- package/src/hbConfigNode.js +12 -8
- package/src/hbConfigNode.test.js +80 -19
- package/src/hbControlNode.js +0 -1
- package/test/homebridge-automation-endpoints.json +18815 -0
- package/test/homebridge-automation-hbDevices-v2.json +1244 -0
- package/test/homebridge-automation-hbDevices-v3.json +965 -0
- package/test/node-red/flows.json +285 -2
package/README.md
CHANGED
|
@@ -35,7 +35,9 @@ The above Node-RED Flow, turns on my 'Outside Office' light when the powder room
|
|
|
35
35
|
* [Jan 11, 2023 - Version 0.1.7](#jan-11-2023---version-017)
|
|
36
36
|
* [Jan 15, 2023 - Version 0.1.8](#jan-15-2023---version-018)
|
|
37
37
|
* [Dec 15, 2024 - Version 0.2.0](#dec-15-2024---version-020)
|
|
38
|
-
* [April 15, 2025 - Version 0.
|
|
38
|
+
* [April 15, 2025 - Version 0.3.0](#april-15-2025---version-030)
|
|
39
|
+
* [Breaking Change](#breaking-change)
|
|
40
|
+
* [Fixes](#fixes)
|
|
39
41
|
* [Backlog / Roadmap](#backlog--roadmap)
|
|
40
42
|
* [Dropped items](#dropped-items)
|
|
41
43
|
* [Installation Steps](#installation-steps)
|
|
@@ -187,10 +189,17 @@ With a plugin, you can see if it supports Real Time events, by opening the Home
|
|
|
187
189
|
- With the change in connectivity to homebridge, please validate all your nodes are connected after the update. And pay particular attention to `Camera` nodes, as for some devices the name has changed.
|
|
188
190
|
- Testing and Development was completed on Node-RED version: v4.0.2 and Node.js version: v20.18.1
|
|
189
191
|
|
|
190
|
-
### April 15, 2025 - Version 0.
|
|
192
|
+
### April 15, 2025 - Version 0.3.0
|
|
193
|
+
|
|
194
|
+
#### Breaking Change
|
|
195
|
+
|
|
196
|
+
- Problem when choosing between multiple accessories with the same name #142. This fix has a breaking change for the name structure for Fans, Input Sources from TV's, and Camera's. And these devices will need to be configured again.
|
|
197
|
+
|
|
198
|
+
#### Fixes
|
|
191
199
|
|
|
192
200
|
- Add common supported types Window, Window Covering, Light Sensor #151, tks @HDeKnop
|
|
193
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.
|
|
194
203
|
|
|
195
204
|
# Backlog / Roadmap
|
|
196
205
|
|
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.10",
|
|
4
4
|
"description": "NodeRED Automation for HomeBridge",
|
|
5
5
|
"main": "src/HAP-NodeRed.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"better-queue": ">=3.8.12",
|
|
44
44
|
"debug": "^4.3.7",
|
|
45
|
-
"@homebridge/hap-client": "^2.1.0
|
|
45
|
+
"@homebridge/hap-client": "^2.1.0"
|
|
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) => {
|
|
@@ -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
|
|
package/src/hbConfigNode.test.js
CHANGED
|
@@ -1,6 +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
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const process = require('process');
|
|
4
6
|
|
|
5
7
|
jest.mock('@homebridge/hap-client', () => {
|
|
6
8
|
return {
|
|
@@ -14,11 +16,10 @@ jest.mock('@homebridge/hap-client', () => {
|
|
|
14
16
|
};
|
|
15
17
|
});
|
|
16
18
|
|
|
17
|
-
describe
|
|
19
|
+
describe('Issue 142', () => {
|
|
18
20
|
let mockConfig;
|
|
19
21
|
let RED;
|
|
20
22
|
let node;
|
|
21
|
-
let mockHapClient;
|
|
22
23
|
|
|
23
24
|
beforeEach(() => {
|
|
24
25
|
mockConfig = {
|
|
@@ -55,7 +56,6 @@ describe('HBConfigNode', () => {
|
|
|
55
56
|
let mockConfig;
|
|
56
57
|
let RED;
|
|
57
58
|
let node;
|
|
58
|
-
let mockHapClient;
|
|
59
59
|
|
|
60
60
|
beforeEach(() => {
|
|
61
61
|
mockConfig = {
|
|
@@ -77,23 +77,84 @@ describe('HBConfigNode', () => {
|
|
|
77
77
|
|
|
78
78
|
test('Retrieve devices', async () => {
|
|
79
79
|
node.hapClient.getAllServices.mockResolvedValue(testhbDevices);
|
|
80
|
-
console.log('testhbDevices', testhbDevices);
|
|
81
80
|
await node.handleReady();
|
|
82
81
|
const result = node.toList({ perms: 'ev' });
|
|
83
82
|
expect(result).toEqual(testhbDevicesResult);
|
|
84
83
|
|
|
85
84
|
// Ensure the unsupported type was filtered out
|
|
86
85
|
expect(result.find(device => device.name === 'Garage Sensor')).toBeUndefined();
|
|
87
|
-
expect(result.find(device => device.name === 'Kitchen Curtain')).toBeDefined();
|
|
88
|
-
expect(result.find(device => device.name === 'Livingroom Curtain')).toBeDefined();
|
|
86
|
+
// expect(result.find(device => device.name === 'Kitchen Curtain')).toBeDefined();
|
|
87
|
+
// expect(result.find(device => device.name === 'Livingroom Curtain')).toBeDefined();
|
|
89
88
|
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('from files', () => {
|
|
92
|
+
let mockConfig;
|
|
93
|
+
let RED;
|
|
94
|
+
let node;
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
mockConfig = {
|
|
98
|
+
username: '123-45-678',
|
|
99
|
+
macAddress: '00:11:22:33:44:55',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
RED = {
|
|
103
|
+
nodes: {
|
|
104
|
+
createNode: jest.fn(),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
90
107
|
|
|
91
|
-
|
|
108
|
+
node = new HBConfigNode(mockConfig, RED);
|
|
109
|
+
// node.debug = true;
|
|
110
|
+
node.warn = console.log;
|
|
111
|
+
node.log = console.log;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test.skip('Retrieve devices, and compare with current (v2)', async () => {
|
|
115
|
+
// console.log('Reading Homebridge endpoints from file', process.cwd());
|
|
116
|
+
var storagePath = path.join(process.cwd(), 'test/homebridge-automation-endpoints.json');
|
|
117
|
+
// console.log(`Reading Homebridge endpoints from ${storagePath}`);
|
|
118
|
+
const fileHbDevices = JSON.parse(fs.readFileSync(storagePath, 'utf8'));
|
|
119
|
+
|
|
120
|
+
node.hapClient.getAllServices.mockResolvedValue(fileHbDevices);
|
|
121
|
+
// console.log('testhbDevices', fileHbDevices);
|
|
122
|
+
await node.handleReady();
|
|
92
123
|
const result = node.toList({ perms: 'ev' });
|
|
93
|
-
|
|
124
|
+
|
|
125
|
+
storagePath = path.join(process.cwd(), 'test/homebridge-automation-hbDevices-v2.json');
|
|
126
|
+
// console.log(`Reading Homebridge results from ${storagePath}`);
|
|
127
|
+
const fileResult = JSON.parse(fs.readFileSync(storagePath, 'utf8'));
|
|
128
|
+
expect(result.length).toBe(107);
|
|
129
|
+
expect(result).toEqual(fileResult);
|
|
94
130
|
|
|
95
131
|
// Ensure the unsupported type was filtered out
|
|
96
132
|
expect(result.find(device => device.name === 'Garage Sensor')).toBeUndefined();
|
|
133
|
+
// expect(result.find(device => device.name === 'Kitchen Curtain')).toBeDefined();
|
|
134
|
+
// expect(result.find(device => device.name === 'Livingroom Curtain')).toBeDefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('Retrieve devices, and compare with future (v3)', async () => {
|
|
138
|
+
// console.log('Reading Homebridge endpoints from file', process.cwd());
|
|
139
|
+
var storagePath = path.join(process.cwd(), 'test/homebridge-automation-endpoints.json');
|
|
140
|
+
// console.log(`Reading Homebridge endpoints from ${storagePath}`);
|
|
141
|
+
const fileHbDevices = JSON.parse(fs.readFileSync(storagePath, 'utf8'));
|
|
142
|
+
|
|
143
|
+
node.hapClient.getAllServices.mockResolvedValue(fileHbDevices);
|
|
144
|
+
// console.log('testhbDevices', fileHbDevices);
|
|
145
|
+
await node.handleReady();
|
|
146
|
+
const result = node.toList({ perms: 'ev' });
|
|
147
|
+
|
|
148
|
+
storagePath = path.join(process.cwd(), 'test/homebridge-automation-hbDevices-v3.json');
|
|
149
|
+
// console.log(`Reading Homebridge results from ${storagePath}`);
|
|
150
|
+
const fileResult = JSON.parse(fs.readFileSync(storagePath, 'utf8'));
|
|
151
|
+
expect(result.length).toBe(107);
|
|
152
|
+
expect(result).toEqual(fileResult);
|
|
153
|
+
|
|
154
|
+
// Ensure the unsupported type was filtered out
|
|
155
|
+
expect(result.find(device => device.name === 'Garage Sensor')).toBeUndefined();
|
|
156
|
+
// expect(result.find(device => device.name === 'Kitchen Curtain')).toBeDefined();
|
|
157
|
+
// expect(result.find(device => device.name === 'Livingroom Curtain')).toBeDefined();
|
|
97
158
|
});
|
|
98
159
|
});
|
|
99
160
|
|
|
@@ -2148,7 +2209,7 @@ const testhbDevicesResult = [
|
|
|
2148
2209
|
name: 'Backyard',
|
|
2149
2210
|
fullName: 'Backyard - Camera Rtp Stream Management',
|
|
2150
2211
|
sortName: 'Backyard:CameraRTPStreamManagement',
|
|
2151
|
-
uniqueId: '
|
|
2212
|
+
uniqueId: 'homebridge0E:89:A7:DA:D3:21EufyBackyard00000110',
|
|
2152
2213
|
homebridge: 'homebridge',
|
|
2153
2214
|
service: 'CameraRTPStreamManagement',
|
|
2154
2215
|
manufacturer: 'Eufy'
|
|
@@ -2157,7 +2218,7 @@ const testhbDevicesResult = [
|
|
|
2157
2218
|
name: 'Backyard',
|
|
2158
2219
|
fullName: 'Backyard - Motion Sensor',
|
|
2159
2220
|
sortName: 'Backyard:MotionSensor',
|
|
2160
|
-
uniqueId: '
|
|
2221
|
+
uniqueId: 'homebridge0E:89:A7:DA:D3:21EufyBackyard00000085',
|
|
2161
2222
|
homebridge: 'homebridge',
|
|
2162
2223
|
service: 'MotionSensor',
|
|
2163
2224
|
manufacturer: 'Eufy'
|
|
@@ -2169,22 +2230,22 @@ const testhbDevicesResult = [
|
|
|
2169
2230
|
"name": "Canoe 5036",
|
|
2170
2231
|
"service": "CameraRTPStreamManagement",
|
|
2171
2232
|
"sortName": "Canoe 5036:CameraRTPStreamManagement",
|
|
2172
|
-
"uniqueId": "
|
|
2233
|
+
"uniqueId": "ECI-T24F25C:EE:FE:4D:64:B4HikVisionCanoe 503600000110",
|
|
2173
2234
|
},
|
|
2174
2235
|
{
|
|
2175
|
-
"fullName": "Canoe - Motion Sensor",
|
|
2236
|
+
"fullName": "Canoe 5036 - Motion Sensor",
|
|
2176
2237
|
"homebridge": "ECI-T24F2",
|
|
2177
2238
|
"manufacturer": "HikVision",
|
|
2178
|
-
"name": "Canoe",
|
|
2239
|
+
"name": "Canoe 5036",
|
|
2179
2240
|
"service": "MotionSensor",
|
|
2180
|
-
"sortName": "Canoe:MotionSensor",
|
|
2181
|
-
"uniqueId": "
|
|
2241
|
+
"sortName": "Canoe 5036:MotionSensor",
|
|
2242
|
+
"uniqueId": "ECI-T24F25C:EE:FE:4D:64:B4HikVisionCanoe 503600000085",
|
|
2182
2243
|
},
|
|
2183
2244
|
{
|
|
2184
2245
|
name: 'Side door',
|
|
2185
2246
|
fullName: 'Side door - Camera Rtp Stream Management',
|
|
2186
2247
|
sortName: 'Side door:CameraRTPStreamManagement',
|
|
2187
|
-
uniqueId: '
|
|
2248
|
+
uniqueId: 'homebridge0E:89:A7:DA:D3:21EufySide door00000110',
|
|
2188
2249
|
homebridge: 'homebridge',
|
|
2189
2250
|
service: 'CameraRTPStreamManagement',
|
|
2190
2251
|
manufacturer: 'Eufy'
|
|
@@ -2193,7 +2254,7 @@ const testhbDevicesResult = [
|
|
|
2193
2254
|
name: 'Side door',
|
|
2194
2255
|
fullName: 'Side door - Motion Sensor',
|
|
2195
2256
|
sortName: 'Side door:MotionSensor',
|
|
2196
|
-
uniqueId: '
|
|
2257
|
+
uniqueId: 'homebridge0E:89:A7:DA:D3:21EufySide door00000085',
|
|
2197
2258
|
homebridge: 'homebridge',
|
|
2198
2259
|
service: 'MotionSensor',
|
|
2199
2260
|
manufacturer: 'Eufy'
|
package/src/hbControlNode.js
CHANGED
|
@@ -53,7 +53,6 @@ class HbControlNode extends hbBaseNode {
|
|
|
53
53
|
const result = await this.hbDevice.setCharacteristicsByTypes(filterIfOff(message.payload));
|
|
54
54
|
results.push(result.values);
|
|
55
55
|
} catch (error) {
|
|
56
|
-
console.log(error)
|
|
57
56
|
this.error(`Failed to set value for "${JSON.stringify(message.payload)}": ${error.message}`);
|
|
58
57
|
results.push({ 'Error': `Error: ${error.message}` });
|
|
59
58
|
fill = 'red';
|