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 +1 -1
- package/src/hbBaseNode.js +115 -106
- package/src/hbConfigNode.js +7 -6
- package/src/hbControlNode.js +1 -1
- package/src/hbEventNode.js +9 -9
- package/src/hbResumeNode.js +29 -116
- package/src/hbStatusNode.js +1 -2
- package/test/node-red/flows.json +6 -9
package/package.json
CHANGED
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(
|
|
44
|
+
createMessage(service) {
|
|
42
45
|
return {
|
|
43
|
-
name: this.
|
|
44
|
-
payload:
|
|
45
|
-
Homebridge:
|
|
46
|
-
Manufacturer:
|
|
47
|
-
Service:
|
|
48
|
-
_device: this.
|
|
49
|
-
_confId: this.
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
done();
|
|
68
59
|
}
|
|
69
60
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
node.status({ text: `Invalid characteristic: ${key}`, shape: 'ring', fill: 'yellow' });
|
|
91
|
+
payload = hbMessage.payload;
|
|
101
92
|
}
|
|
93
|
+
return payload;
|
|
102
94
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const message =
|
|
135
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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;
|
package/src/hbConfigNode.js
CHANGED
|
@@ -152,17 +152,18 @@ class HBConfigNode {
|
|
|
152
152
|
}
|
|
153
153
|
cb(null);
|
|
154
154
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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;
|
package/src/hbControlNode.js
CHANGED
package/src/hbEventNode.js
CHANGED
|
@@ -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.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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;
|
package/src/hbResumeNode.js
CHANGED
|
@@ -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
|
-
|
|
26
|
-
debug('
|
|
18
|
+
handleInput(message, send) {
|
|
19
|
+
debug('handleInput', message.payload, this.name);
|
|
27
20
|
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
shape: '
|
|
31
|
-
|
|
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 (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
122
|
-
this.
|
|
123
|
-
|
|
124
|
-
|
|
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;
|
package/src/hbStatusNode.js
CHANGED
package/test/node-red/flows.json
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"tosidebar": true,
|
|
23
23
|
"console": false,
|
|
24
24
|
"tostatus": true,
|
|
25
|
-
"complete": "
|
|
26
|
-
"targetType": "
|
|
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": "
|
|
43
|
-
"targetType": "
|
|
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
|
{
|