node-red-contrib-homebridge-automation 0.1.12-beta.19 → 0.1.12-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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-homebridge-automation",
3
- "version": "0.1.12-beta.19",
3
+ "version": "0.1.12-beta.20",
4
4
  "description": "NodeRED Automation for HomeBridge",
5
5
  "main": "src/HAP-NodeRed.js",
6
6
  "scripts": {
package/src/hbBaseNode.js CHANGED
@@ -2,425 +2,160 @@ const debug = require('debug')('hapNodeRed:hbBaseNode');
2
2
 
3
3
  class HbBaseNode {
4
4
  constructor(config, RED) {
5
- debug("HbBaseNode - constructor", config);
6
- // RED.nodes.createNode(this, config);
5
+ debug("Constructor:", config);
7
6
  RED.nodes.createNode(this, config);
7
+
8
8
  if (!config.conf) {
9
- debug('Warning: %s @ %s.%s not connected to a HB Configuration Node', config.type, config.x, config.y);
9
+ this.error(`Warning: ${config.type} @ (${config.x}, ${config.y}) not connected to a HB Configuration Node`);
10
10
  }
11
- this.hbConfigNode = RED.nodes.getNode(config.conf); // The configuration node
12
- // console.log("HbBaseNode - conf", this.conf);
11
+
13
12
  this.config = config;
13
+ this.hbConfigNode = RED.nodes.getNode(config.conf);
14
14
  this.confId = config.conf;
15
15
  this.device = config.device;
16
16
  this.service = config.Service;
17
17
  this.name = config.name;
18
18
  this.fullName = `${config.name} - ${config.Service}`;
19
-
20
19
  this.hbDevice = null;
21
- this.hbConfigNode.register(this);
20
+
21
+ this.hbConfigNode?.register(this);
22
+
22
23
  if (this.handleInput) {
23
24
  this.on('input', this.handleInput.bind(this));
24
25
  }
25
26
  this.on('close', this.handleClose.bind(this));
26
27
  }
27
28
 
28
- /**
29
- * Common logic for registering the node
30
- */
31
29
  registerNode() {
32
- debug("hbBaseNode Registered:", this.fullName);
33
-
30
+ debug("Registering node:", this.fullName);
34
31
  this.hbDevice = hbDevices.findDevice(this.device);
35
32
 
36
- if (this.hbDevice) {
37
- this.deviceType = this.hbDevice.deviceType;
33
+ if (!this.hbDevice) {
34
+ this.error(`Device not found: ${this.device}`);
38
35
  } else {
39
- this.error(`437: Can't find device ${this.device}`, null);
36
+ this.deviceType = this.hbDevice.deviceType;
40
37
  }
41
38
  }
42
39
 
43
- /**
44
- * Common logic for handling the close event
45
- * @param {Function} callback - Callback to be executed on close
46
- */
47
40
  handleClose(callback) {
48
41
  callback();
49
42
  }
50
43
 
51
- /**
52
- * Convert a HB message to a node message
53
- * @param {*} hbMessage
54
- * @param {*} node
55
- * @returns
56
- */
57
- _convertHBcharactericToNode(hbMessage, node) {
58
- // debug("_convertHBcharactericToNode", node.device);
59
- var payload = {};
44
+ _convertHBcharacteristicToNode(hbMessage, node) {
45
+ let payload = {};
60
46
  if (!hbMessage.payload) {
61
- var device = hbDevices.findDevice(node.device);
62
- // debug("Device", device);
63
-
64
- // characteristics = Object.assign(characteristics, characteristic.characteristic);
47
+ const device = hbDevices.findDevice(node.device);
65
48
  if (device) {
66
- hbMessage.forEach(function (characteristic) {
67
- // debug("Exists", (device.characteristics[characteristic.aid + '.' + characteristic.iid]));
68
- if (device.characteristics[characteristic.aid + '.' + characteristic.iid]) {
69
- payload = Object.assign(payload, {
70
- [device.characteristics[characteristic.aid + '.' + characteristic.iid].characteristic]: characteristic.value
71
- });
49
+ hbMessage.forEach(characteristic => {
50
+ const charKey = `${characteristic.aid}.${characteristic.iid}`;
51
+ if (device.characteristics[charKey]) {
52
+ payload[device.characteristics[charKey].characteristic] = characteristic.value;
72
53
  }
73
54
  });
74
55
  }
75
56
  } else {
76
57
  payload = hbMessage.payload;
77
58
  }
78
- // debug("payload", payload);
79
- return (payload);
59
+ return payload;
80
60
  }
81
61
 
82
- /**
83
- * Create a HB ontrol message for a device
84
- * @param {*} payload
85
- * @param {*} node
86
- * @param {*} device
87
- * @returns
88
- */
89
62
  _createControlMessage(payload, node, device) {
90
- // debug("_createControlMessage", payload, device);
91
- // debug("Device", device, device.characteristics[event.aid + '.' + event.iid]);
92
- var response = [];
93
-
94
- for (var key in payload) {
95
- // debug("IID", key, _getKey(device.characteristics, key));
96
- if (this._getKey(device.characteristics, key)) {
63
+ const response = [];
64
+ for (const key in payload) {
65
+ const characteristic = this._getKey(device.characteristics, key);
66
+ if (characteristic) {
97
67
  response.push({
98
- "aid": device.aid,
99
- "iid": this._getKey(device.characteristics, key).iid,
100
- "value": payload[key]
68
+ aid: device.aid,
69
+ iid: characteristic.iid,
70
+ value: payload[key],
101
71
  });
102
72
  } else {
103
- this.warn("Characteristic '" + key + "' is not valid.\nTry one of these: " + device.descriptions);
104
- node.status({
105
- text: 'warn - Invalid Characteristic ' + key,
106
- shape: 'ring',
107
- fill: 'yellow'
108
- });
73
+ this.warn(`Invalid characteristic: '${key}'. Available: ${device.descriptions}`);
74
+ node.status({ text: `Invalid characteristic: ${key}`, shape: 'ring', fill: 'yellow' });
109
75
  }
110
76
  }
111
- return ({
112
- "characteristics": response
113
- });
77
+ return { characteristics: response };
114
78
  }
115
79
 
116
-
117
- /**
118
- * Return the status of a device
119
- * @param {*} nrDevice
120
- * @param {*} node
121
- * @param {*} perms
122
- * @returns
123
- */
124
80
  async _status(nrDevice, node, perms) {
125
- debug("_status", nrDevice);
126
- let error;
127
81
  try {
128
- if (!this.hbDevices) {
129
- throw new Error('_status hbDevices not initialized');
130
- }
131
-
132
82
  const device = hbDevices.findDevice(node.device, perms);
133
- if (device) {
134
- let status, message;
135
- switch (device.type) {
136
- case "00000110": // Camera RTPStream Management
137
- case "00000111": // Camera Control
138
- message = {
139
- "resource-type": "image",
140
- "image-width": 1920,
141
- "image-height": 1080
142
- };
143
- debug("_status Control %s -> %s", device.id, JSON.stringify(message));
144
-
145
- // Await the result of HAPresourceByDeviceIDAsync
146
- status = await this.HAPresourceByDeviceIDAsync(device.id, JSON.stringify(message));
147
-
148
- debug("_status Controlled %s:%s ->", device.host, device.port);
149
- node.status({
150
- text: 'sent',
151
- shape: 'dot',
152
- fill: 'green'
153
- });
154
-
155
- clearTimeout(node.timeout);
156
- node.timeout = setTimeout(() => {
157
- node.status({});
158
- }, 30 * 1000);
159
-
160
- return {
161
- characteristics: {
162
- payload: btoa(status)
163
- }
164
- };
165
-
166
- default:
167
- message = '?id=' + device.getCharacteristics;
168
- debug("_status request: %s -> %s:%s ->", node.fullName, device.id, message);
169
-
170
- // Await the result of HAPstatusByDeviceIDAsync
171
- status = await this.HAPstatusByDeviceIDAsync(device.id, message);
83
+ if (!device) throw new Error(`Device not found: ${nrDevice}`);
172
84
 
173
- node.status({
174
- text: 'sent',
175
- shape: 'dot',
176
- fill: 'green'
177
- });
85
+ const message = device.type === "00000110" || device.type === "00000111"
86
+ ? { "resource-type": "image", "image-width": 1920, "image-height": 1080 }
87
+ : `?id=${device.getCharacteristics}`;
178
88
 
179
- clearTimeout(node.timeout);
180
- node.timeout = setTimeout(() => {
181
- node.status({});
182
- }, 30 * 1000);
89
+ const status = device.type === "00000110" || device.type === "00000111"
90
+ ? await this.HAPresourceByDeviceIDAsync(device.id, JSON.stringify(message))
91
+ : await this.HAPstatusByDeviceIDAsync(device.id, message);
183
92
 
184
- return status;
185
- }
186
- } else {
187
- error = "Device not found: " + nrDevice;
188
- node.status({
189
- text: 'Device not found',
190
- shape: 'ring',
191
- fill: 'red'
192
- });
193
- throw new Error(error);
194
- }
93
+ node.status({ text: 'Success', shape: 'dot', fill: 'green' });
94
+ return device.type === "00000110" || device.type === "00000111"
95
+ ? { characteristics: { payload: this.btoa(status) } }
96
+ : status;
195
97
  } catch (err) {
196
98
  debug("Error in _status:", err);
197
- error = "Homebridge not initialized -2";
198
- node.status({
199
- text: error,
200
- shape: 'ring',
201
- fill: 'red'
202
- });
203
- // throw new Error(error);
99
+ node.status({ text: 'Error retrieving status', shape: 'ring', fill: 'red' });
100
+ throw err;
204
101
  }
205
102
  }
206
103
 
207
- /**
208
- * Control a HB Device
209
- * @param {*} node
210
- * @param {*} payload
211
- * @returns
212
- */
213
104
  async _control(node, payload) {
214
- debug("_control", node, payload);
215
105
  try {
216
- if (!this.hbDevices) {
217
- throw new Error('hbDevices not initialized');
218
- }
219
-
220
- const device = hbDevices.findDevice(node.device, {
221
- perms: 'pw'
222
- });
223
-
224
- if (device) {
225
- let message;
226
- switch (device.type) {
227
- case "00000110": // Camera RTPStream Management
228
- case "00000111": // Camera Control
229
- {
230
- message = {
231
- "resource-type": "image",
232
- "image-width": 1920,
233
- "image-height": 1080,
234
- "aid": node.hbDevice.aid
235
- };
236
- debug("Control %s ->", device.id, node.fullName, JSON.stringify(message));
106
+ const device = hbDevices.findDevice(node.device, { perms: 'pw' });
107
+ if (!device) throw new Error('Device not available');
237
108
 
238
- // Await the result of HAPresourceByDeviceIDAsync
239
- const status = await this.HAPresourceByDeviceIDAsync(device.id, JSON.stringify(message));
109
+ const message = typeof payload === "object"
110
+ ? this._createControlMessage(payload, node, device)
111
+ : null;
240
112
 
241
- node.status({
242
- text: JSON.stringify(payload).slice(0, 30) + '...',
243
- shape: 'dot',
244
- fill: 'green'
245
- });
246
-
247
- clearTimeout(node.timeout);
248
- node.timeout = setTimeout(() => {
249
- node.status({});
250
- }, 30 * 1000);
251
-
252
- return status;
253
- }
254
- default:
255
- if (typeof payload === "object") {
256
- message = this._createControlMessage.call(this, payload, node, device);
257
- debug("Control %s ->", device.id, JSON.stringify(message));
258
-
259
- if (message.characteristics.length > 0) {
260
- // Await the result of HAPcontrolByDeviceIDAsync
261
- const status = await this.HAPcontrolByDeviceIDAsync(device.id, JSON.stringify(message));
262
-
263
- if (status && status.characteristics[0].status === 0) {
264
- debug("Controlled %s ->", device.id, JSON.stringify(status));
265
- node.status({
266
- text: JSON.stringify(payload).slice(0, 30) + '...',
267
- shape: 'dot',
268
- fill: 'green'
269
- });
270
-
271
- clearTimeout(node.timeout);
272
- node.timeout = setTimeout(() => {
273
- node.status({});
274
- }, 10 * 1000);
275
-
276
- return; // Successful control, no error
277
- } else {
278
- debug("Controlled %s ->", device.id, payload);
279
- node.status({
280
- text: JSON.stringify(payload).slice(0, 30) + '...',
281
- shape: 'dot',
282
- fill: 'green'
283
- });
284
-
285
- clearTimeout(node.timeout);
286
- node.timeout = setTimeout(() => {
287
- node.status({});
288
- }, 10 * 1000);
289
-
290
- return; // Status controlled, no error
291
- }
292
- } else {
293
- throw new Error('Invalid payload');
294
- }
295
- } else {
296
- throw new Error("Payload should be a JSON object containing device characteristics and values.");
297
- }
298
- }
113
+ if (message && message.characteristics.length > 0) {
114
+ const status = await this.HAPcontrolByDeviceIDAsync(device.id, JSON.stringify(message));
115
+ node.status({ text: 'Controlled', shape: 'dot', fill: 'green' });
116
+ return status;
299
117
  } else {
300
- throw new Error('Device not available');
118
+ throw new Error('Invalid payload');
301
119
  }
302
120
  } catch (err) {
303
121
  debug("Error in _control:", err);
304
- let error = err.message || "Homebridge not initialized - 3";
305
- node.status({
306
- text: error,
307
- shape: 'ring',
308
- fill: 'red'
309
- });
310
- // throw new Error(error);
122
+ node.status({ text: 'Control error', shape: 'ring', fill: 'red' });
123
+ throw err;
311
124
  }
312
125
  }
313
126
 
314
127
  async _register(node) {
315
- debug("_register", node.device);
316
128
  try {
317
- debug("_register", node.device);
318
129
  const device = hbDevices.findDevice(node.device, { perms: 'ev' });
319
-
320
- if (node.type === 'hb-event' || node.type === 'hb-resume') {
321
- const message = {
322
- "characteristics": device.eventRegisters
323
- };
324
- debug("_register", node.fullName, device.id, message);
325
-
326
- // Use the shared async function here
327
- const status = await hapEventByDeviceIDAsync(device.id, JSON.stringify(message));
328
-
329
- // Check the result of the operation
330
- if (status === null) {
331
- debug("%s registered: %s -> %s", node.type, node.fullName, device.id);
332
- } else {
333
- debug("%s registered: %s -> %s", node.type, node.fullName, device.id, JSON.stringify(status));
334
- }
130
+ if (device) {
131
+ const message = { characteristics: device.eventRegisters };
132
+ await hapEventByDeviceIDAsync(device.id, JSON.stringify(message));
335
133
  }
336
134
  } catch (err) {
337
- // Handle errors that occur in the async function
338
135
  debug("Error in _register:", err);
339
- // You can handle errors here, like logging or setting the status
340
- node.status({
341
- text: 'error',
342
- shape: 'ring',
343
- fill: 'red'
344
- });
136
+ node.status({ text: 'Register error', shape: 'ring', fill: 'red' });
345
137
  }
346
138
  }
347
139
 
348
- _getObjectDiff(obj1, obj2) {
349
- const diff = Object.keys(obj1).reduce((result, key) => {
350
- if (!obj2.hasOwnProperty(key)) {
351
- result.push(key);
352
- } else if (obj1[key] === obj2[key]) {
353
- const resultKeyIndex = result.indexOf(key);
354
- result.splice(resultKeyIndex, 1);
355
- }
356
- return result;
357
- }, Object.keys(obj2));
358
-
359
- return diff;
360
- }
361
-
362
140
  _getKey(obj, value) {
363
- for (var key in obj) {
364
- // debug("%s === %s", obj[key].characteristic, value);
365
- // debug("%s === %s", obj[key].characteristic.toLowerCase(), value.toLowerCase());
366
- if (obj[key].characteristic.toLowerCase() === value.toLowerCase()) {
367
- return obj[key];
368
- }
369
- }
370
- return null;
141
+ return Object.values(obj).find(char => char.characteristic.toLowerCase() === value.toLowerCase()) || null;
371
142
  }
372
143
 
373
144
  btoa(str) {
374
- var buffer;
375
-
376
- if (str instanceof Buffer) {
377
- buffer = str;
378
- } else {
379
- buffer = Buffer.from(str.toString(), 'binary');
380
- }
381
-
382
- return buffer.toString('base64');
145
+ return Buffer.from(str.toString(), 'binary').toString('base64');
383
146
  }
384
147
 
385
-
386
- // Helper function to promisify HAPresourceByDeviceID
387
148
  async HAPresourceByDeviceIDAsync(deviceId, message) {
388
149
  return new Promise((resolve, reject) => {
389
- homebridge.HAPresourceByDeviceID(deviceId, message, (err, status) => {
390
- if (err) {
391
- reject(err);
392
- } else {
393
- resolve(status);
394
- }
395
- });
150
+ homebridge.HAPresourceByDeviceID(deviceId, message, (err, status) => err ? reject(err) : resolve(status));
396
151
  });
397
152
  }
398
153
 
399
- // Helper function to promisify HAPstatusByDeviceID
400
154
  async HAPstatusByDeviceIDAsync(deviceId, message) {
401
155
  return new Promise((resolve, reject) => {
402
- homebridge.HAPstatusByDeviceID(deviceId, message, (err, status) => {
403
- if (err) {
404
- reject(err);
405
- } else {
406
- resolve(status);
407
- }
408
- });
156
+ homebridge.HAPstatusByDeviceID(deviceId, message, (err, status) => err ? reject(err) : resolve(status));
409
157
  });
410
158
  }
411
-
412
- async hapEventByDeviceIDAsync(deviceId, message) {
413
- return new Promise((resolve, reject) => {
414
- homebridge.HAPeventByDeviceID(deviceId, message, (err, status) => {
415
- if (err) {
416
- reject(err);
417
- } else {
418
- resolve(status);
419
- }
420
- });
421
- });
422
- }
423
-
424
159
  }
425
160
 
426
161
  module.exports = HbBaseNode;
@@ -1,47 +1,32 @@
1
1
  const hbBaseNode = require('./hbBaseNode.js');
2
- // const HAPNodeJSClient = require('hap-node-client').HAPNodeJSClient;
3
2
  const { HapClient } = require('@homebridge/hap-client');
4
3
  const debug = require('debug')('hapNodeRed:hbConfigNode');
5
4
  const { Homebridges } = require('./lib/Homebridges.js');
6
5
  const { Log } = require('./lib/logger.js');
7
- var Queue = require('better-queue');
8
- const { manualSync } = require('rimraf');
6
+ const Queue = require('better-queue');
9
7
 
10
8
  class HBConfigNode {
11
9
  constructor(config, RED) {
12
10
  if (!config.jest) {
13
11
  RED.nodes.createNode(this, config);
14
- this.username = config.username;
15
- this.macAddress = config.macAddress || '';
16
- this.password = this.password;
17
- this.on('close', function () {
18
- this.hbConf.close(); // Close any open connections
19
- });
20
12
 
21
- // console.log('HBConfNode', config);
22
13
  this.username = config.username;
23
14
  this.macAddress = config.macAddress || '';
24
- // this.password = config.credentials.password;
25
15
  this.users = {};
26
16
  this.homebridge = null;
27
17
  this.evDevices = [];
28
18
  this.ctDevices = [];
29
19
  this.hbDevices = [];
30
-
31
- this.clientNodes = []; // An array of client nodes attached
32
-
20
+ this.clientNodes = [];
33
21
  this.log = new Log(console, true);
34
22
 
35
- this.reqisterQueue = new Queue((clientNode, cb) => {
36
- // debug('Queue execute', clientNode);
37
- this._register(clientNode, cb);
38
- }, {
23
+ this.reqisterQueue = new Queue(this._register.bind(this), {
39
24
  concurrent: 1,
40
25
  autoResume: false,
41
26
  maxRetries: 1000,
42
27
  retryDelay: 30000,
43
28
  batchDelay: 2000,
44
- batchSize: 150
29
+ batchSize: 150,
45
30
  });
46
31
  this.reqisterQueue.pause();
47
32
 
@@ -52,33 +37,28 @@ class HBConfigNode {
52
37
  });
53
38
 
54
39
  this.waitForNoMoreDiscoveries();
55
- this.hapClient.on('instance-discovered', () => this.waitForNoMoreDiscoveries);
40
+ this.hapClient.on('instance-discovered', this.waitForNoMoreDiscoveries);
41
+
42
+ this.on('close', () => {
43
+ this.close();
44
+ });
56
45
  }
57
46
  }
58
47
 
59
48
  waitForNoMoreDiscoveries = () => {
60
- // Clear any existing timeout
61
49
  if (this.discoveryTimeout) {
62
50
  clearTimeout(this.discoveryTimeout);
63
51
  }
64
52
 
65
- // Set up the timeout
66
53
  this.discoveryTimeout = setTimeout(() => {
67
54
  this.log.debug('No more instances discovered, publishing services');
68
55
  this.hapClient.removeListener('instance-discovered', this.waitForNoMoreDiscoveries);
69
- // debug('waitfornomore', this);
70
56
  this.handleReady();
71
- // this.requestSync();
72
- // this.hapClient.on('instance-discovered', this.requestSync.bind(this)); // Request sync on new instance discovery
73
57
  }, 5000);
74
58
  };
75
59
 
76
- // Handle Homebridge 'Ready' event
77
- async handleReady(accessories) {
78
- this.hbDevices = await this.hapClient.getAllServices(accessories);
79
- // debug('handleReady', JSON.stringify(this.hbDevices, null, 2));
80
- debug('Discovered %s new evDevices', this.toList({ perms: 'ev' }).length);
81
-
60
+ async handleReady() {
61
+ this.hbDevices = await this.hapClient.getAllServices();
82
62
  this.evDevices = this.toList({ perms: 'ev' });
83
63
  this.ctDevices = this.toList({ perms: 'pw' });
84
64
  this.handleDuplicates(this.evDevices);
@@ -86,75 +66,32 @@ class HBConfigNode {
86
66
  this.reqisterQueue.resume();
87
67
  }
88
68
 
89
-
90
-
91
69
  toList(perms) {
92
-
93
- function supportedServiceType(service) {
94
- switch (service.humanType) {
95
- case 'Battery':
96
- case 'Carbon Dioxide Sensor':
97
- case 'Carbon Monoxide Sensor':
98
- case 'Doorbell':
99
- case 'Fan':
100
- case 'Fanv2':
101
- case 'Garage Door Opener':
102
- case 'Humidity Sensor':
103
- case 'Input Source':
104
- case 'Leak Sensor':
105
- case 'Lightbulb':
106
- case 'Lock Mechanism':
107
- case 'Motion Sensor':
108
- case 'Occupancy Sensor':
109
- case 'Outlet':
110
- case 'Smoke Sensor':
111
- case 'Speaker':
112
- case 'Stateless Programmable Switch':
113
- case 'Switch':
114
- case 'Switch':
115
- case 'Television':
116
- case 'Temperature Sensor':
117
- case 'Thermostat':
118
- case 'Contact Sensor':
119
- return true;
120
- case 'Camera Operating Mode':
121
- case 'Camera Rtp Stream Management':
122
- case 'Protocol Information':
123
-
124
- return false;
125
- default:
126
- debug('Unsupport HomeKit Service Type \'%s\':', service.humanType);
127
- }
128
- }
129
-
130
- // debug('toList', this.hbDevices);
131
- return this.hbDevices.filter(service => supportedServiceType(service)).map(service => ({
132
- name: `${service.serviceName}`,
133
- fullName: `${service.serviceName} - ${service.type}`,
134
- sortName: `${service.serviceName}:${service.type}`,
135
- uniqueId: `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`, homebridge: `${service.instance.name}`,
136
- service: `${service.type}`,
137
- manufacturer: `${service.accessoryInformation.Manufacturer}`
138
- })).sort((a, b) => (a.sortName > b.sortName) ? 1 : ((b.sortName > a.sortName) ? -1 : 0));;
139
- }
140
- /**
141
- * Start processing
142
- */
143
- async start() {
144
- this.services = await this.loadAccessories();
145
- this.log.info(`Discovered ${this.services.length} accessories`);
146
- this.ready = true;
147
- await this.buildSyncResponse();
148
- const evServices = this.services.filter(x => this.evTypes.some(uuid => x.serviceCharacteristics.find(c => c.uuid === uuid)));
149
- this.log.debug(`Monitoring ${evServices.length} services for changes`);
150
-
151
- const monitor = await this.hapClient.monitorCharacteristics(evServices);
152
- monitor.on('service-update', (services) => {
153
- this.reportStateSubject.next(services[0].uniqueId);
154
- });
70
+ const supportedServiceType = (service) => {
71
+ const supportedTypes = [
72
+ 'Battery', 'Carbon Dioxide Sensor', 'Carbon Monoxide Sensor', 'Doorbell',
73
+ 'Fan', 'Fanv2', 'Garage Door Opener', 'Humidity Sensor', 'Input Source',
74
+ 'Leak Sensor', 'Lightbulb', 'Lock Mechanism', 'Motion Sensor', 'Occupancy Sensor',
75
+ 'Outlet', 'Smoke Sensor', 'Speaker', 'Stateless Programmable Switch', 'Switch',
76
+ 'Television', 'Temperature Sensor', 'Thermostat', 'Contact Sensor',
77
+ ];
78
+ return supportedTypes.includes(service.humanType);
79
+ };
80
+
81
+ return this.hbDevices
82
+ .filter(service => supportedServiceType(service))
83
+ .map(service => ({
84
+ name: service.serviceName,
85
+ fullName: `${service.serviceName} - ${service.type}`,
86
+ sortName: `${service.serviceName}:${service.type}`,
87
+ uniqueId: `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`,
88
+ homebridge: service.instance.name,
89
+ service: service.type,
90
+ manufacturer: service.accessoryInformation.Manufacturer,
91
+ }))
92
+ .sort((a, b) => a.sortName.localeCompare(b.sortName));
155
93
  }
156
94
 
157
- // Handle duplicate devices
158
95
  handleDuplicates(list) {
159
96
  const seenFullNames = new Set();
160
97
  const seenUniqueIds = new Set();
@@ -165,9 +102,7 @@ class HBConfigNode {
165
102
  } else {
166
103
  seenFullNames.add(endpoint.fullName);
167
104
  }
168
- }
169
105
 
170
- for (const endpoint of list) {
171
106
  if (seenUniqueIds.has(endpoint.uniqueId)) {
172
107
  console.error('ERROR: Parsing failed, duplicate uniqueID.', endpoint.fullName);
173
108
  } else {
@@ -176,62 +111,41 @@ class HBConfigNode {
176
111
  }
177
112
  }
178
113
 
179
- // Register a device node
180
114
  register(clientNode) {
181
- // debug('hbConf.register', clientNode);
182
115
  debug('Register %s -> %s', clientNode.type, clientNode.name);
183
116
  this.clientNodes[clientNode.id] = clientNode;
184
- this.reqisterQueue.push(
185
- clientNode
186
- );
117
+ this.reqisterQueue.push(clientNode);
118
+ clientNode.status({ fill: 'yellow', shape: 'ring', text: 'connecting' });
187
119
  }
188
120
 
189
- /**
190
- * Process batched event registration messages
191
- */
192
121
  async _register(clientNodes, cb) {
193
- // debug('_register', clientNodes);
194
-
195
- // debug('clientNodes', this.clientNodes);
196
-
197
122
  for (const clientNode of clientNodes) {
198
123
  debug('_Register %s -> %s', clientNode.type, clientNode.name);
199
124
  clientNode.hbDevice = this.hbDevices.find(service => {
200
-
201
- // console.log('clientNodeDevice', clientNode);
202
- // debug('Testing:', { clientNodeDevice: clientNode.device, serviceName: service });
203
125
  const testValue = `${service.instance.name}${service.instance.username}${service.accessoryInformation.Manufacturer}${service.serviceName}${service.uuid.slice(0, 8)}`;
204
- // debug('Testing - final:', { clientNodeDevice: clientNode.device, testValue });
126
+ clientNode.status({ fill: 'green', shape: 'dot', text: 'connected' });
205
127
  return clientNode.device === testValue;
206
128
  });
207
- // debug('Updated clientNode', clientNode);
129
+
208
130
  if (!clientNode.hbDevice) {
209
- console.log('ERROR: _register - HB Device Missing', clientNode.name);
131
+ console.error('ERROR: _register - HB Device Missing', clientNode.name);
210
132
  }
211
133
  }
212
- // const monitor = await this.hapClient.monitorCharacteristics(clientNodes);
213
- // monitor.on('service-update', (services) => {
214
- // debug('service-update', services);
215
- // });
216
-
217
134
  cb(null);
218
135
  }
219
136
 
220
- // Deregister a device node
221
- deregister(deviceNode, callback) {
222
- deviceNode.status({
137
+ deregister(clientNode) {
138
+ clientNode.status({
223
139
  text: 'disconnected',
224
140
  shape: 'ring',
225
141
  fill: 'red',
226
142
  });
227
-
228
- this.clientNodes[clientNode.id] = {};
143
+ delete this.clientNodes[clientNode.id];
229
144
  }
230
145
 
231
- // Clean up resources
232
146
  close() {
233
- if (this.client && this.client.connected) {
234
- this.client.end();
147
+ if (this.hapClient) {
148
+ this.hapClient.close();
235
149
  }
236
150
  }
237
151
  }
@@ -4,75 +4,54 @@ const debug = require('debug')('hapNodeRed:hbControlNode');
4
4
  class HbControlNode extends hbBaseNode {
5
5
  constructor(config, RED) {
6
6
  super(config, RED);
7
-
8
- // Register the node-specific input and close handlers
9
- // this.on('input', this.handleInput.bind(this));
10
- // Register the node with the configuration
11
7
  }
12
8
 
13
- // Handle input messages
14
9
  async handleInput(message) {
15
- debug('handleInput', message, this.hbDevice);
16
- if (this.hbDevice) {
17
- var results = [];
18
- if (typeof message.payload === "object") {
19
- var fill = 'green';
20
- for (const key of Object.keys(message.payload)) {
21
- const characteristic = this.hbDevice.serviceCharacteristics.find(
22
- c => c.type === key
23
- );
10
+ debug('handleInput', message.payload, this.name);
24
11
 
25
- if (characteristic) {
26
- const result = await characteristic.setValue(message.payload[key]);
27
- results.push({ [result.type]: result.value })
28
- } else {
29
- console.log('Not Found', key);
30
- this.error('Invalid Characteristic \'' + key + '\' found in the message ' + JSON.stringify(message));
31
- results.push({ 'Invalid Key': key });
32
- fill = 'red';
33
- };
34
- }
35
- this.status({
36
- text: JSON.stringify(Object.assign({}, ...results)),
37
- shape: 'dot',
38
- fill: fill
39
- });
12
+ if (!this.hbDevice) {
13
+ this.error('HB not initialized');
14
+ this.status({ text: 'HB not initialized', shape: 'ring', fill: 'red' });
15
+ return;
16
+ }
40
17
 
41
- } else {
42
- // Improper object
43
- const validNames = Object.keys(this.hbDevice.values)
44
- .filter(key => key !== 'ConfiguredName')
45
- .join(', ');
46
- this.error("Payload should be an JSON object containing device characteristics and values, ie {\"On\":false, \"Brightness\":0 }\nValid values include: " + validNames);
47
- this.status({
48
- text: 'Invalid payload',
49
- shape: 'ring',
50
- fill: 'red'
51
- });
18
+ if (typeof message.payload !== 'object') {
19
+ const validNames = Object.keys(this.hbDevice.values)
20
+ .filter(key => key !== 'ConfiguredName')
21
+ .join(', ');
22
+ this.error(`Payload should be a JSON object containing device characteristics and values, e.g. {"On":false, "Brightness":0}. Valid values: ${validNames}`);
23
+ this.status({ text: 'Invalid payload', shape: 'ring', fill: 'red' });
24
+ return;
25
+ }
52
26
 
53
- }
54
- } else {
55
- this.error("HB not initialized");
56
- this.status({
57
- text: 'HB not initialized',
58
- shape: 'ring',
59
- fill: 'red',
60
- });
27
+ const results = [];
28
+ let fill = 'green';
61
29
 
62
- }
63
- /*
64
- this._control.call(this, this.node, msg.payload, (err, data) => {
65
- if (!err && data && (this.deviceType === '00000110' || this.deviceType === '00000111')) {
66
- const outputMsg = this.createOutputMessage(data);
67
- this.send(outputMsg);
68
- } else if (err) {
69
- this.error(err, this.msg);
30
+ for (const key of Object.keys(message.payload)) {
31
+ const characteristic = this.hbDevice.serviceCharacteristics.find(c => c.type === key);
32
+
33
+ if (characteristic) {
34
+ try {
35
+ const result = await characteristic.setValue(message.payload[key]);
36
+ results.push({ [result.type]: result.value });
37
+ } catch (error) {
38
+ this.error(`Failed to set value for ${key}: ${error.message}`);
39
+ fill = 'red';
40
+ }
41
+ } else {
42
+ this.error(`Invalid characteristic '${key}' found in the message: ${JSON.stringify(message.payload)}`);
43
+ results.push({ 'Invalid Key': key });
44
+ fill = 'red';
70
45
  }
46
+ }
47
+
48
+ this.status({
49
+ text: JSON.stringify(Object.assign({}, ...results)),
50
+ shape: 'dot',
51
+ fill,
71
52
  });
72
- */
73
53
  }
74
54
 
75
- // Handle node closure
76
55
  handleClose(callback) {
77
56
  callback();
78
57
  }
@@ -402,5 +402,22 @@
402
402
  "6703815a8874b156"
403
403
  ]
404
404
  ]
405
+ },
406
+ {
407
+ "id": "ab3d4add7178f1f1",
408
+ "type": "hb-event",
409
+ "z": "caef1e7b5b399e80",
410
+ "name": "",
411
+ "Homebridge": "",
412
+ "Manufacturer": "",
413
+ "Service": "",
414
+ "device": "",
415
+ "conf": "",
416
+ "sendInitialState": false,
417
+ "x": 890,
418
+ "y": 800,
419
+ "wires": [
420
+ []
421
+ ]
405
422
  }
406
423
  ]