node-red-contrib-homebridge-automation 0.1.12-beta.25 → 0.1.12-beta.27

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.25",
3
+ "version": "0.1.12-beta.27",
4
4
  "description": "NodeRED Automation for HomeBridge",
5
5
  "main": "src/HAP-NodeRed.js",
6
6
  "scripts": {
package/src/hbBaseNode.js CHANGED
@@ -23,6 +23,9 @@ class HbBaseNode {
23
23
  if (this.handleInput) {
24
24
  this.on('input', this.handleInput.bind(this));
25
25
  }
26
+ if (this.handleHbReady) {
27
+ this.on('hbReady', this.handleHbReady.bind(this))
28
+ }
26
29
  this.on('close', this.handleClose.bind(this));
27
30
  this.on('hbEvent', this.handleHBEventMessage.bind(this));
28
31
  }
@@ -38,127 +41,133 @@ class HbBaseNode {
38
41
  this.send({ payload: service.values });
39
42
  }
40
43
 
41
- createMessage(event, isInitialState = false) {
44
+ createMessage(service) {
42
45
  return {
43
- name: this.node.name,
44
- payload: this.state,
45
- Homebridge: this.node.hbDevice.homebridge,
46
- Manufacturer: this.node.hbDevice.manufacturer,
47
- Service: this.node.hbDevice.deviceType,
48
- _device: this.node.device,
49
- _confId: this.node.confId,
50
- _rawEvent: isInitialState ? event : undefined,
46
+ name: this.name,
47
+ payload: service.values,
48
+ Homebridge: service.instance.name,
49
+ Manufacturer: service.accessoryInformation.Manufacturer,
50
+ Service: service.type,
51
+ _device: this.device,
52
+ _confId: this.confId,
51
53
  };
52
54
  }
53
55
 
54
- registerNode() {
55
- debug("Registering node:", this.fullName);
56
- this.hbDevice = hbDevices.findDevice(this.device);
57
-
58
- if (!this.hbDevice) {
59
- this.error(`Device not found: ${this.device}`);
60
- } else {
61
- this.deviceType = this.hbDevice.deviceType;
62
- }
63
- }
64
-
65
- handleClose(callback) {
56
+ handleClose(removed, done) {
66
57
  debug('close', this.name);
67
- callback();
58
+ done();
68
59
  }
69
60
 
70
- _convertHBcharacteristicToNode(hbMessage, node) {
71
- let payload = {};
72
- if (!hbMessage.payload) {
73
- const device = hbDevices.findDevice(node.device);
74
- if (device) {
75
- hbMessage.forEach(characteristic => {
76
- const charKey = `${characteristic.aid}.${characteristic.iid}`;
77
- if (device.characteristics[charKey]) {
78
- payload[device.characteristics[charKey].characteristic] = characteristic.value;
79
- }
80
- });
61
+ /*
62
+ registerNode() {
63
+ debug("Registering node:", this.fullName);
64
+ this.hbDevice = hbDevices.findDevice(this.device);
65
+
66
+ if (!this.hbDevice) {
67
+ this.error(`Device not found: ${this.device}`);
68
+ } else {
69
+ this.deviceType = this.hbDevice.deviceType;
81
70
  }
82
- } else {
83
- payload = hbMessage.payload;
84
71
  }
85
- return payload;
86
- }
87
-
88
- _createControlMessage(payload, node, device) {
89
- const response = [];
90
- for (const key in payload) {
91
- const characteristic = this._getKey(device.characteristics, key);
92
- if (characteristic) {
93
- response.push({
94
- aid: device.aid,
95
- iid: characteristic.iid,
96
- value: payload[key],
97
- });
72
+
73
+ handleClose(callback) {
74
+ debug('close', this.name);
75
+ callback();
76
+ }
77
+
78
+ _convertHBcharacteristicToNode(hbMessage, node) {
79
+ let payload = {};
80
+ if (!hbMessage.payload) {
81
+ const device = hbDevices.findDevice(node.device);
82
+ if (device) {
83
+ hbMessage.forEach(characteristic => {
84
+ const charKey = `${characteristic.aid}.${characteristic.iid}`;
85
+ if (device.characteristics[charKey]) {
86
+ payload[device.characteristics[charKey].characteristic] = characteristic.value;
87
+ }
88
+ });
89
+ }
98
90
  } else {
99
- this.warn(`Invalid characteristic: '${key}'. Available: ${device.descriptions}`);
100
- node.status({ text: `Invalid characteristic: ${key}`, shape: 'ring', fill: 'yellow' });
91
+ payload = hbMessage.payload;
101
92
  }
93
+ return payload;
102
94
  }
103
- return { characteristics: response };
104
- }
105
-
106
- async _status(nrDevice, node, perms) {
107
- try {
108
- const device = hbDevices.findDevice(node.device, perms);
109
- if (!device) throw new Error(`Device not found: ${nrDevice}`);
110
-
111
- const message = device.type === "00000110" || device.type === "00000111"
112
- ? { "resource-type": "image", "image-width": 1920, "image-height": 1080 }
113
- : `?id=${device.getCharacteristics}`;
114
-
115
- const status = device.type === "00000110" || device.type === "00000111"
116
- ? await this.HAPresourceByDeviceIDAsync(device.id, JSON.stringify(message))
117
- : await this.HAPstatusByDeviceIDAsync(device.id, message);
118
-
119
- node.status({ text: 'Success', shape: 'dot', fill: 'green' });
120
- return device.type === "00000110" || device.type === "00000111"
121
- ? { characteristics: { payload: this.btoa(status) } }
122
- : status;
123
- } catch (err) {
124
- debug("Error in _status:", err);
125
- node.status({ text: 'Error retrieving status', shape: 'ring', fill: 'red' });
126
- throw err;
95
+
96
+ _createControlMessage(payload, node, device) {
97
+ const response = [];
98
+ for (const key in payload) {
99
+ const characteristic = this._getKey(device.characteristics, key);
100
+ if (characteristic) {
101
+ response.push({
102
+ aid: device.aid,
103
+ iid: characteristic.iid,
104
+ value: payload[key],
105
+ });
106
+ } else {
107
+ this.warn(`Invalid characteristic: '${key}'. Available: ${device.descriptions}`);
108
+ node.status({ text: `Invalid characteristic: ${key}`, shape: 'ring', fill: 'yellow' });
109
+ }
110
+ }
111
+ return { characteristics: response };
127
112
  }
128
- }
129
-
130
- async _register(node) {
131
- try {
132
- const device = hbDevices.findDevice(node.device, { perms: 'ev' });
133
- if (device) {
134
- const message = { characteristics: device.eventRegisters };
135
- await hapEventByDeviceIDAsync(device.id, JSON.stringify(message));
113
+
114
+ async _status(nrDevice, node, perms) {
115
+ try {
116
+ const device = hbDevices.findDevice(node.device, perms);
117
+ if (!device) throw new Error(`Device not found: ${nrDevice}`);
118
+
119
+ const message = device.type === "00000110" || device.type === "00000111"
120
+ ? { "resource-type": "image", "image-width": 1920, "image-height": 1080 }
121
+ : `?id=${device.getCharacteristics}`;
122
+
123
+ const status = device.type === "00000110" || device.type === "00000111"
124
+ ? await this.HAPresourceByDeviceIDAsync(device.id, JSON.stringify(message))
125
+ : await this.HAPstatusByDeviceIDAsync(device.id, message);
126
+
127
+ node.status({ text: 'Success', shape: 'dot', fill: 'green' });
128
+ return device.type === "00000110" || device.type === "00000111"
129
+ ? { characteristics: { payload: this.btoa(status) } }
130
+ : status;
131
+ } catch (err) {
132
+ debug("Error in _status:", err);
133
+ node.status({ text: 'Error retrieving status', shape: 'ring', fill: 'red' });
134
+ throw err;
136
135
  }
137
- } catch (err) {
138
- debug("Error in _register:", err);
139
- node.status({ text: 'Register error', shape: 'ring', fill: 'red' });
140
136
  }
141
- }
142
-
143
- _getKey(obj, value) {
144
- return Object.values(obj).find(char => char.characteristic.toLowerCase() === value.toLowerCase()) || null;
145
- }
146
-
147
- btoa(str) {
148
- return Buffer.from(str.toString(), 'binary').toString('base64');
149
- }
150
-
151
- async HAPresourceByDeviceIDAsync(deviceId, message) {
152
- return new Promise((resolve, reject) => {
153
- homebridge.HAPresourceByDeviceID(deviceId, message, (err, status) => err ? reject(err) : resolve(status));
154
- });
155
- }
156
-
157
- async HAPstatusByDeviceIDAsync(deviceId, message) {
158
- return new Promise((resolve, reject) => {
159
- homebridge.HAPstatusByDeviceID(deviceId, message, (err, status) => err ? reject(err) : resolve(status));
160
- });
161
- }
137
+
138
+ async _register(node) {
139
+ try {
140
+ const device = hbDevices.findDevice(node.device, { perms: 'ev' });
141
+ if (device) {
142
+ const message = { characteristics: device.eventRegisters };
143
+ await hapEventByDeviceIDAsync(device.id, JSON.stringify(message));
144
+ }
145
+ } catch (err) {
146
+ debug("Error in _register:", err);
147
+ node.status({ text: 'Register error', shape: 'ring', fill: 'red' });
148
+ }
149
+ }
150
+
151
+ _getKey(obj, value) {
152
+ return Object.values(obj).find(char => char.characteristic.toLowerCase() === value.toLowerCase()) || null;
153
+ }
154
+
155
+ btoa(str) {
156
+ return Buffer.from(str.toString(), 'binary').toString('base64');
157
+ }
158
+
159
+ async HAPresourceByDeviceIDAsync(deviceId, message) {
160
+ return new Promise((resolve, reject) => {
161
+ homebridge.HAPresourceByDeviceID(deviceId, message, (err, status) => err ? reject(err) : resolve(status));
162
+ });
163
+ }
164
+
165
+ async HAPstatusByDeviceIDAsync(deviceId, message) {
166
+ return new Promise((resolve, reject) => {
167
+ homebridge.HAPstatusByDeviceID(deviceId, message, (err, status) => err ? reject(err) : resolve(status));
168
+ });
169
+ }
170
+ */
162
171
  }
163
172
 
164
173
  module.exports = HbBaseNode;
@@ -152,17 +152,18 @@ class HBConfigNode {
152
152
  }
153
153
  cb(null);
154
154
  }
155
-
156
- deregister(clientNode) {
157
- clientNode.status({ text: 'disconnected', shape: 'ring', fill: 'red' });
158
- delete this.clientNodes[clientNode.id];
159
- }
160
-
155
+ /*
156
+ deregister(clientNode) {
157
+ clientNode.status({ text: 'disconnected', shape: 'ring', fill: 'red' });
158
+ delete this.clientNodes[clientNode.id];
159
+ }
160
+ */
161
161
  close() {
162
162
  if (this.hapClient) {
163
163
  this.hapClient.destroy();
164
164
  }
165
165
  }
166
+
166
167
  }
167
168
 
168
169
  module.exports = HBConfigNode;
@@ -6,7 +6,7 @@ class HbControlNode extends hbBaseNode {
6
6
  super(config, RED);
7
7
  }
8
8
 
9
- async handleInput(message) {
9
+ async handleInput(message, send) {
10
10
  debug('handleInput', message.payload, this.name);
11
11
  if (!this.hbDevice) {
12
12
  this.error('HB not initialized');
@@ -5,19 +5,19 @@ class HbEventNode extends hbBaseNode {
5
5
  constructor(config, RED) {
6
6
  super(config, RED);
7
7
  this.sendInitialState = config.sendInitialState === true;
8
- this.on('hbReady', this.handleHbReady.bind(this))
9
8
  }
10
9
 
11
10
  handleHbReady(service) {
12
- this.status({
13
- text: JSON.stringify(service.values),
14
- shape: 'dot',
15
- fill: 'green',
16
- });
17
- this.send({ payload: service.values });
11
+ debug('handleHbReady', this.id, this.name, service.values)
12
+ if (this.sendInitialState) {
13
+ this.status({
14
+ text: JSON.stringify(service.values),
15
+ shape: 'dot',
16
+ fill: 'green',
17
+ });
18
+ this.send({ ...this.createMessage(service) });
19
+ }
18
20
  }
19
- // Create a message payload for event or initial state
20
-
21
21
  }
22
22
 
23
23
  module.exports = HbEventNode;
@@ -5,12 +5,7 @@ class HbResumeNode extends HbBaseNode {
5
5
  constructor(config, RED) {
6
6
  super(config, RED);
7
7
 
8
-
9
- this.state = null;
10
- this.lastMessageTime = null;
11
- this.lastMessageValue = null;
12
- this.lastPayload = { On: false };
13
- this.timeout = null;
8
+ this.storedState = null;
14
9
 
15
10
  // Set up input and command handlers
16
11
  // this.on('input', this.handleInput.bind(this));
@@ -18,127 +13,45 @@ class HbResumeNode extends HbBaseNode {
18
13
 
19
14
  // Handle device registration
20
15
  debug('hbResume - hbConfigNode', this.hbConfigNode);
21
- // debug('hbResume - hbConfigNode', this.configNode.hbConfigNode);
22
-
23
16
  }
24
17
 
25
- handleHBEventMessage(service) {
26
- debug('topic for', this.id, service.serviceName, service.values);
18
+ handleInput(message, send) {
19
+ debug('handleInput', message.payload, this.name);
27
20
 
28
- this.status({
29
- text: JSON.stringify(service.values),
30
- shape: 'dot',
31
- fill: 'green',
32
- });
33
- this.send({ payload: service.values });
34
- }
35
-
36
- handleInput(msg, send) {
37
- this.msg = msg;
38
- debug("hbResume.input: %s input", this.fullName, JSON.stringify(msg));
39
-
40
- if (typeof msg.payload === "object") {
41
- if (this.hbDevice) {
42
- const message = this._createControlMessage.call(this, msg.payload, this, this.hbDevice);
43
-
44
- if (message.characteristics.length > 0) {
45
- let newMsg;
46
- if (!msg.payload.On) {
47
- if (this.lastPayload.On) {
48
- newMsg = {
49
- name: this.name,
50
- _device: this.device,
51
- _confId: this.confId,
52
- payload: this.state,
53
- Homebridge: this.hbDevice?.homebridge,
54
- Manufacturer: this.hbDevice?.manufacturer,
55
- Type: this.hbDevice?.deviceType,
56
- };
57
- } else {
58
- this.state = JSON.parse(JSON.stringify(msg.payload));
59
- newMsg = msg;
60
- }
61
- } else {
62
- newMsg = msg;
63
- }
64
-
65
- send(newMsg.payload.On ? newMsg : { ...newMsg, payload: { On: false } });
66
- debug("hbResume.input: %s output", this.fullName, JSON.stringify(newMsg));
67
- this.updateStatus(newMsg.payload);
68
- this.lastMessageValue = newMsg.payload;
69
- this.lastMessageTime = Date.now();
70
- this.lastPayload = JSON.parse(JSON.stringify(msg.payload));
71
- }
72
- } else {
73
- this.handleError("Homebridge not initialized - 1");
74
- }
75
- } else {
76
- this.handleError(
77
- "Payload should be a JSON object containing device characteristics and values, e.g., {\"On\":false, \"Brightness\":0 }"
78
- );
21
+ if (!this.hbDevice) {
22
+ this.warn('HB not initialized');
23
+ this.status({ text: 'HB not initialized', shape: 'ring', fill: 'red' });
24
+ return;
79
25
  }
80
- }
81
-
82
- handleCommand(event) {
83
- const payload = { ...this.state, ...this._convertHBcharactericToNode([event], this) };
84
- debug("hbResume.event: %s %s -> %s", this.fullName, JSON.stringify(this.state), JSON.stringify(payload));
85
26
 
86
- if (event.status === true && event.value !== undefined) {
87
- if (Date.now() - this.lastMessageTime > 5000) {
88
- debug("hbResume.update: %s - updating stored event >5", this.fullName, payload);
89
- this.state = JSON.parse(JSON.stringify(payload));
90
- }
91
- } else if (event.status === true) {
92
- this.updateStatus({ text: 'connected', shape: 'dot', fill: 'green' });
93
- } else {
94
- this.updateStatus({ text: `disconnected: ${event.status}`, shape: 'ring', fill: 'red' });
27
+ if (typeof message.payload !== 'object') {
28
+ const validNames = Object.keys(this.hbDevice.values)
29
+ .filter(key => key !== 'ConfiguredName')
30
+ .join(', ');
31
+ this.warn(`Payload should be a JSON object containing device characteristics and values, e.g. {"On":false, "Brightness":0}. Valid values: ${validNames}`);
32
+ this.status({ text: 'Invalid payload', shape: 'dot', fill: 'red' });
33
+ return;
95
34
  }
96
- }
97
-
98
- handleDeviceRegistration() {
99
- debug("hbResume.register:", this.fullName);
100
- this.hbDevice = hbDevices.findDevice(this.device, { perms: 'pw' });
101
-
102
- if (this.hbDevice) {
103
- this._status(this.device, this, { perms: 'pw' }, (err, message) => {
104
- if (!err) {
105
- this.state = this._convertHBcharactericToNode(message.characteristics, this);
106
- debug("hbResume received: %s = %s", this.fullName, JSON.stringify(message.characteristics).slice(0, 80) + '...');
107
- } else {
108
- this.error(err);
109
- }
110
- });
111
-
112
- this.deviceType = this.hbDevice.deviceType;
113
- this.listener = this.command;
114
- this.eventName = [];
115
35
 
116
- this.hbDevice.eventRegisters.forEach((event) => {
117
- homebridge.on(this.hbDevice.id + event.aid + event.iid, this.command);
118
- this.eventName.push(this.hbDevice.id + event.aid + event.iid);
119
- });
36
+ // if on, store the current values object to storedState before passing
37
+ // if off, if storedState, then send stored state else passthru
120
38
 
121
- this.updateStatus({ text: 'connected', shape: 'dot', fill: 'green' });
122
- this.resetTimeout(30000);
123
- } else {
124
- this.error(`Can't find device ${this.device}`);
39
+ if (message.payload.On) {
40
+ this.storedState = this.hbDevice.values;
41
+ debug('Storing', this.storedState);
42
+ } else if (!message.payload.On && this.storedState) {
43
+ debug('Restoring', this.storedState)
44
+ message.payload = { ...message.payload, ...this.storedState }
45
+ this.storedState = null;
125
46
  }
47
+ this.status({
48
+ text: JSON.stringify(message.payload),
49
+ shape: 'dot',
50
+ fill: 'green',
51
+ });
52
+ send(message);
126
53
  }
127
54
 
128
- updateStatus(status) {
129
- this.status(status);
130
- this.resetTimeout(10000);
131
- }
132
-
133
- resetTimeout(duration) {
134
- clearTimeout(this.timeout);
135
- this.timeout = setTimeout(() => this.status({}), duration);
136
- }
137
-
138
- handleError(message) {
139
- this.error(message, this.msg);
140
- this.updateStatus({ text: message, shape: 'ring', fill: 'red' });
141
- }
142
55
  }
143
56
 
144
57
  module.exports = HbResumeNode;
@@ -22,8 +22,7 @@ class HbStatusNode extends HbBaseNode {
22
22
  fill: 'green'
23
23
  });
24
24
 
25
- message.payload = result.values;
26
- send(message);
25
+ send(Object.assign(message, this.createMessage(result)));
27
26
  }
28
27
  }
29
28
 
@@ -22,8 +22,8 @@
22
22
  "tosidebar": true,
23
23
  "console": false,
24
24
  "tostatus": true,
25
- "complete": "payload",
26
- "targetType": "msg",
25
+ "complete": "true",
26
+ "targetType": "full",
27
27
  "statusVal": "payload",
28
28
  "statusType": "auto",
29
29
  "x": 800,
@@ -39,8 +39,8 @@
39
39
  "tosidebar": true,
40
40
  "console": false,
41
41
  "tostatus": true,
42
- "complete": "payload",
43
- "targetType": "msg",
42
+ "complete": "true",
43
+ "targetType": "full",
44
44
  "statusVal": "payload",
45
45
  "statusType": "auto",
46
46
  "x": 800,
@@ -72,8 +72,7 @@
72
72
  "y": 140,
73
73
  "wires": [
74
74
  [
75
- "3d7babac3a298e60",
76
- "452e3e6171aa7a25"
75
+ "3d7babac3a298e60"
77
76
  ]
78
77
  ]
79
78
  },
@@ -169,9 +168,7 @@
169
168
  "x": 210,
170
169
  "y": 460,
171
170
  "wires": [
172
- [
173
- "452e3e6171aa7a25"
174
- ]
171
+ []
175
172
  ]
176
173
  },
177
174
  {