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 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.2.2](#april-15-2025---version-022)
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.2.2
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.0",
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-beta.5"
45
+ "@homebridge/hap-client": "^2.1.0"
46
46
  },
47
47
  "author": "NorthernMan54",
48
48
  "license": "ISC",
@@ -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>This allows you to register your homebridge PIN</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: {
@@ -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.debug && updatedDevices && updatedDevices.length) {
58
+ if (this.debugLogging && updatedDevices && updatedDevices.length && process.uptime() < 300) {
57
59
  try {
58
- const storagePath = path.join(process.cwd(), '/homebridge-automation-endpoints.json');
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
- clientNode.device === `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`
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
 
@@ -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.skip('Issue 142', () => {
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
- test.skip('toList filters and maps camera devices correctly', () => {
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
- expect(result).toEqual(testhbDevicesResult);
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: '924c9390e6a89452936dfff957faa127d87e78e3c1b5084863495a184804fe56',
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: 'c9efb02acc5cabb2b164d4c479355551a6360345571b31de50e90f4a1fa42df6',
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": "9959f43e6f32e451a9c13e0c028d863fa148c39cbbcb57f93d8d825fc31f8865",
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": "10dbaceb026b81f56c6226eca2c30b7ba06c29de632253972402bd3f489096f1",
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: 'e8e6aec782cd554108e95e4a61b7fa4e941f2c63b9e656e6c828ac89e26e2dbd',
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: 'f4827932577f2073ae5584a4a66607a1dd67af9b3e5bf22d59dffdc702d0f240',
2257
+ uniqueId: 'homebridge0E:89:A7:DA:D3:21EufySide door00000085',
2197
2258
  homebridge: 'homebridge',
2198
2259
  service: 'MotionSensor',
2199
2260
  manufacturer: 'Eufy'
@@ -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';