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 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.2",
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": "^8.57.1",
32
- "eslint-plugin-format": "^0.1.2",
33
- "eslint-plugin-jest": "^28.8.3",
34
- "globals": "^15.13.0",
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.3.7",
45
- "@homebridge/hap-client": "^2.1.0-beta.5"
44
+ "debug": "^4.4.1",
45
+ "@homebridge/hap-client": "^3.1.1"
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) => {
@@ -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
- 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
 
@@ -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
- clientNode.config.device === `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`
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));
@@ -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 = {
@@ -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
- // Validate payload
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
- console.log(error)
57
- this.error(`Failed to set value for "${JSON.stringify(message.payload)}": ${error.message}`);
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
  }