node-red-contrib-homebridge-automation 0.1.12-beta.17 → 0.1.12-beta.19
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/HAP-NodeRed.js +1 -1
- package/src/HapDeviceRoutes.js +1 -1
- package/src/hbBaseNode.js +8 -3
- package/src/hbConfigNode.js +147 -82
- package/src/hbConfigNode.test.js +185 -0
- package/src/hbControlNode.js +52 -38
- package/test/node-red/flows.json +178 -18
package/package.json
CHANGED
package/src/HAP-NodeRed.js
CHANGED
|
@@ -2,7 +2,7 @@ var debug = require('debug')('hapNodeRed');
|
|
|
2
2
|
|
|
3
3
|
// var register = require('./lib/register.js');
|
|
4
4
|
|
|
5
|
-
const HBConfigNode = require('./hbConfigNode');
|
|
5
|
+
const HBConfigNode = require('./hbConfigNode.js');
|
|
6
6
|
const HbEventNode = require('./hbEventNode'); // Import the class
|
|
7
7
|
const HbResumeNode = require('./hbResumeNode'); // Import the class
|
|
8
8
|
const HbControlNode = require('./hbControlNode');
|
package/src/HapDeviceRoutes.js
CHANGED
|
@@ -88,7 +88,7 @@ class HapDeviceRoutes {
|
|
|
88
88
|
getCtDeviceById(req, res) {
|
|
89
89
|
debug('getCtDeviceById', req.params.id);
|
|
90
90
|
const ctDevices = this.RED.nodes.getNode(req.params.id).ctDevices;
|
|
91
|
-
debug('ctDevices', ctDevices);
|
|
91
|
+
// debug('ctDevices', ctDevices);
|
|
92
92
|
debug("ctDevices", ctDevices.length);
|
|
93
93
|
if (ctDevices) {
|
|
94
94
|
res.send(ctDevices);
|
package/src/hbBaseNode.js
CHANGED
|
@@ -5,6 +5,9 @@ class HbBaseNode {
|
|
|
5
5
|
debug("HbBaseNode - constructor", config);
|
|
6
6
|
// RED.nodes.createNode(this, config);
|
|
7
7
|
RED.nodes.createNode(this, config);
|
|
8
|
+
if (!config.conf) {
|
|
9
|
+
debug('Warning: %s @ %s.%s not connected to a HB Configuration Node', config.type, config.x, config.y);
|
|
10
|
+
}
|
|
8
11
|
this.hbConfigNode = RED.nodes.getNode(config.conf); // The configuration node
|
|
9
12
|
// console.log("HbBaseNode - conf", this.conf);
|
|
10
13
|
this.config = config;
|
|
@@ -14,9 +17,11 @@ class HbBaseNode {
|
|
|
14
17
|
this.name = config.name;
|
|
15
18
|
this.fullName = `${config.name} - ${config.Service}`;
|
|
16
19
|
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
this.hbDevice = null;
|
|
21
|
+
this.hbConfigNode.register(this);
|
|
22
|
+
if (this.handleInput) {
|
|
23
|
+
this.on('input', this.handleInput.bind(this));
|
|
24
|
+
}
|
|
20
25
|
this.on('close', this.handleClose.bind(this));
|
|
21
26
|
}
|
|
22
27
|
|
package/src/hbConfigNode.js
CHANGED
|
@@ -1,67 +1,59 @@
|
|
|
1
|
-
const hbBaseNode = require('./hbBaseNode');
|
|
1
|
+
const hbBaseNode = require('./hbBaseNode.js');
|
|
2
2
|
// const HAPNodeJSClient = require('hap-node-client').HAPNodeJSClient;
|
|
3
3
|
const { HapClient } = require('@homebridge/hap-client');
|
|
4
4
|
const debug = require('debug')('hapNodeRed:hbConfigNode');
|
|
5
5
|
const { Homebridges } = require('./lib/Homebridges.js');
|
|
6
6
|
const { Log } = require('./lib/logger.js');
|
|
7
7
|
var Queue = require('better-queue');
|
|
8
|
+
const { manualSync } = require('rimraf');
|
|
8
9
|
|
|
9
10
|
class HBConfigNode {
|
|
10
11
|
constructor(config, RED) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
config: { debug: false },
|
|
57
|
-
pin: config.username,
|
|
58
|
-
logger: this.log,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
this.waitForNoMoreDiscoveries();
|
|
62
|
-
this.hapClient.on('instance-discovered', this.waitForNoMoreDiscoveries);
|
|
63
|
-
// Handle 'Ready' event
|
|
64
|
-
this.homebridge.on('Ready', this.handleReady.bind(this));
|
|
12
|
+
if (!config.jest) {
|
|
13
|
+
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
|
+
|
|
21
|
+
// console.log('HBConfNode', config);
|
|
22
|
+
this.username = config.username;
|
|
23
|
+
this.macAddress = config.macAddress || '';
|
|
24
|
+
// this.password = config.credentials.password;
|
|
25
|
+
this.users = {};
|
|
26
|
+
this.homebridge = null;
|
|
27
|
+
this.evDevices = [];
|
|
28
|
+
this.ctDevices = [];
|
|
29
|
+
this.hbDevices = [];
|
|
30
|
+
|
|
31
|
+
this.clientNodes = []; // An array of client nodes attached
|
|
32
|
+
|
|
33
|
+
this.log = new Log(console, true);
|
|
34
|
+
|
|
35
|
+
this.reqisterQueue = new Queue((clientNode, cb) => {
|
|
36
|
+
// debug('Queue execute', clientNode);
|
|
37
|
+
this._register(clientNode, cb);
|
|
38
|
+
}, {
|
|
39
|
+
concurrent: 1,
|
|
40
|
+
autoResume: false,
|
|
41
|
+
maxRetries: 1000,
|
|
42
|
+
retryDelay: 30000,
|
|
43
|
+
batchDelay: 2000,
|
|
44
|
+
batchSize: 150
|
|
45
|
+
});
|
|
46
|
+
this.reqisterQueue.pause();
|
|
47
|
+
|
|
48
|
+
this.hapClient = new HapClient({
|
|
49
|
+
config: { debug: false },
|
|
50
|
+
pin: config.username,
|
|
51
|
+
logger: this.log,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.waitForNoMoreDiscoveries();
|
|
55
|
+
this.hapClient.on('instance-discovered', () => this.waitForNoMoreDiscoveries);
|
|
56
|
+
}
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
waitForNoMoreDiscoveries = () => {
|
|
@@ -74,22 +66,77 @@ class HBConfigNode {
|
|
|
74
66
|
this.discoveryTimeout = setTimeout(() => {
|
|
75
67
|
this.log.debug('No more instances discovered, publishing services');
|
|
76
68
|
this.hapClient.removeListener('instance-discovered', this.waitForNoMoreDiscoveries);
|
|
77
|
-
this
|
|
78
|
-
this.
|
|
79
|
-
|
|
69
|
+
// debug('waitfornomore', this);
|
|
70
|
+
this.handleReady();
|
|
71
|
+
// this.requestSync();
|
|
72
|
+
// this.hapClient.on('instance-discovered', this.requestSync.bind(this)); // Request sync on new instance discovery
|
|
80
73
|
}, 5000);
|
|
81
74
|
};
|
|
82
75
|
|
|
83
76
|
// Handle Homebridge 'Ready' event
|
|
84
|
-
handleReady(accessories) {
|
|
85
|
-
this.hbDevices =
|
|
86
|
-
debug('
|
|
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);
|
|
87
81
|
|
|
88
|
-
this.evDevices = this.
|
|
89
|
-
this.ctDevices = this.
|
|
82
|
+
this.evDevices = this.toList({ perms: 'ev' });
|
|
83
|
+
this.ctDevices = this.toList({ perms: 'pw' });
|
|
90
84
|
this.handleDuplicates(this.evDevices);
|
|
85
|
+
debug('Queue', this.reqisterQueue.getStats());
|
|
86
|
+
this.reqisterQueue.resume();
|
|
91
87
|
}
|
|
92
88
|
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
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
|
+
}
|
|
93
140
|
/**
|
|
94
141
|
* Start processing
|
|
95
142
|
*/
|
|
@@ -130,24 +177,46 @@ class HBConfigNode {
|
|
|
130
177
|
}
|
|
131
178
|
|
|
132
179
|
// Register a device node
|
|
133
|
-
register(
|
|
134
|
-
debug('hbConf.register',
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
180
|
+
register(clientNode) {
|
|
181
|
+
// debug('hbConf.register', clientNode);
|
|
182
|
+
debug('Register %s -> %s', clientNode.type, clientNode.name);
|
|
183
|
+
this.clientNodes[clientNode.id] = clientNode;
|
|
138
184
|
this.reqisterQueue.push(
|
|
139
|
-
|
|
140
|
-
that: this,
|
|
141
|
-
device: deviceNode.device,
|
|
142
|
-
type: deviceNode.type,
|
|
143
|
-
name: deviceNode.name,
|
|
144
|
-
fullName: deviceNode.fullName,
|
|
145
|
-
node: this,
|
|
146
|
-
},
|
|
147
|
-
callback
|
|
185
|
+
clientNode
|
|
148
186
|
);
|
|
149
187
|
}
|
|
150
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Process batched event registration messages
|
|
191
|
+
*/
|
|
192
|
+
async _register(clientNodes, cb) {
|
|
193
|
+
// debug('_register', clientNodes);
|
|
194
|
+
|
|
195
|
+
// debug('clientNodes', this.clientNodes);
|
|
196
|
+
|
|
197
|
+
for (const clientNode of clientNodes) {
|
|
198
|
+
debug('_Register %s -> %s', clientNode.type, clientNode.name);
|
|
199
|
+
clientNode.hbDevice = this.hbDevices.find(service => {
|
|
200
|
+
|
|
201
|
+
// console.log('clientNodeDevice', clientNode);
|
|
202
|
+
// debug('Testing:', { clientNodeDevice: clientNode.device, serviceName: service });
|
|
203
|
+
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 });
|
|
205
|
+
return clientNode.device === testValue;
|
|
206
|
+
});
|
|
207
|
+
// debug('Updated clientNode', clientNode);
|
|
208
|
+
if (!clientNode.hbDevice) {
|
|
209
|
+
console.log('ERROR: _register - HB Device Missing', clientNode.name);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// const monitor = await this.hapClient.monitorCharacteristics(clientNodes);
|
|
213
|
+
// monitor.on('service-update', (services) => {
|
|
214
|
+
// debug('service-update', services);
|
|
215
|
+
// });
|
|
216
|
+
|
|
217
|
+
cb(null);
|
|
218
|
+
}
|
|
219
|
+
|
|
151
220
|
// Deregister a device node
|
|
152
221
|
deregister(deviceNode, callback) {
|
|
153
222
|
deviceNode.status({
|
|
@@ -156,11 +225,7 @@ class HBConfigNode {
|
|
|
156
225
|
fill: 'red',
|
|
157
226
|
});
|
|
158
227
|
|
|
159
|
-
|
|
160
|
-
this.homebridge.removeListener(event, deviceNode.listener);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
callback();
|
|
228
|
+
this.clientNodes[clientNode.id] = {};
|
|
164
229
|
}
|
|
165
230
|
|
|
166
231
|
// Clean up resources
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
const debug = require('debug')('hapNodeRed:JEST');
|
|
2
|
+
|
|
3
|
+
const HBConfigNode = require('./hbConfigNode.js');
|
|
4
|
+
|
|
5
|
+
describe("toList", () => {
|
|
6
|
+
var hbConfigNode;
|
|
7
|
+
createNode = function () {
|
|
8
|
+
};
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
console.log('init');
|
|
12
|
+
hbConfigNode = new HBConfigNode({ jest: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("should return an empty array when inputs is empty", () => {
|
|
16
|
+
const inputs = [];
|
|
17
|
+
const perms = [];
|
|
18
|
+
const result = hbConfigNode.toList(inputs, perms);
|
|
19
|
+
expect(result).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("should map inputs to the expected output format", () => {
|
|
23
|
+
const inputs = [
|
|
24
|
+
{
|
|
25
|
+
"aid": 167,
|
|
26
|
+
"iid": 8,
|
|
27
|
+
"uuid": "00000040-0000-1000-8000-0026BB765291",
|
|
28
|
+
"type": "Fan",
|
|
29
|
+
"humanType": "Fan",
|
|
30
|
+
"serviceName": "Bunkie Fan",
|
|
31
|
+
"serviceCharacteristics": [
|
|
32
|
+
{
|
|
33
|
+
"aid": 167,
|
|
34
|
+
"iid": 10,
|
|
35
|
+
"uuid": "00000025-0000-1000-8000-0026BB765291",
|
|
36
|
+
"type": "On",
|
|
37
|
+
"serviceType": "Fan",
|
|
38
|
+
"serviceName": "Bunkie Fan",
|
|
39
|
+
"description": "On",
|
|
40
|
+
"value": 1,
|
|
41
|
+
"format": "bool",
|
|
42
|
+
"perms": [
|
|
43
|
+
"ev",
|
|
44
|
+
"pr",
|
|
45
|
+
"pw"
|
|
46
|
+
],
|
|
47
|
+
"canRead": true,
|
|
48
|
+
"canWrite": true,
|
|
49
|
+
"ev": true
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"aid": 167,
|
|
53
|
+
"iid": 11,
|
|
54
|
+
"uuid": "000000E3-0000-1000-8000-0026BB765291",
|
|
55
|
+
"type": "ConfiguredName",
|
|
56
|
+
"serviceType": "Fan",
|
|
57
|
+
"serviceName": "Bunkie Fan",
|
|
58
|
+
"description": "Configured Name",
|
|
59
|
+
"value": "Bunkie Fan",
|
|
60
|
+
"format": "string",
|
|
61
|
+
"perms": [
|
|
62
|
+
"ev",
|
|
63
|
+
"pr",
|
|
64
|
+
"pw"
|
|
65
|
+
],
|
|
66
|
+
"canRead": true,
|
|
67
|
+
"canWrite": true,
|
|
68
|
+
"ev": true
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"aid": 167,
|
|
72
|
+
"iid": 12,
|
|
73
|
+
"uuid": "00000029-0000-1000-8000-0026BB765291",
|
|
74
|
+
"type": "RotationSpeed",
|
|
75
|
+
"serviceType": "Fan",
|
|
76
|
+
"serviceName": "Bunkie Fan",
|
|
77
|
+
"description": "Rotation Speed",
|
|
78
|
+
"value": 33,
|
|
79
|
+
"format": "float",
|
|
80
|
+
"perms": [
|
|
81
|
+
"ev",
|
|
82
|
+
"pr",
|
|
83
|
+
"pw"
|
|
84
|
+
],
|
|
85
|
+
"unit": "percentage",
|
|
86
|
+
"maxValue": 100,
|
|
87
|
+
"minValue": 0,
|
|
88
|
+
"minStep": 1,
|
|
89
|
+
"canRead": true,
|
|
90
|
+
"canWrite": true,
|
|
91
|
+
"ev": true
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"accessoryInformation": {
|
|
95
|
+
"Manufacturer": "Tasmota",
|
|
96
|
+
"Model": "Sonoff iFan03",
|
|
97
|
+
"Name": "Bunkie Fan",
|
|
98
|
+
"Serial Number": "302D6B-jessie",
|
|
99
|
+
"Firmware Revision": "9.5.0tasmota"
|
|
100
|
+
},
|
|
101
|
+
"values": {
|
|
102
|
+
"On": 1,
|
|
103
|
+
"ConfiguredName": "Bunkie Fan",
|
|
104
|
+
"RotationSpeed": 33
|
|
105
|
+
},
|
|
106
|
+
"instance": {
|
|
107
|
+
"name": "homebridge",
|
|
108
|
+
"username": "1C:22:3D:E3:CF:34",
|
|
109
|
+
"ipAddress": "192.168.1.11",
|
|
110
|
+
"port": 35215,
|
|
111
|
+
"services": [],
|
|
112
|
+
"connectionFailedCount": 0
|
|
113
|
+
},
|
|
114
|
+
"uniqueId": "9fd9e494282f14d80d438aad8ffde153893f99a97195b816749786e9a012aa2f"
|
|
115
|
+
},
|
|
116
|
+
// Add more inputs here if needed
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const perms = { perms: 'ev' };
|
|
120
|
+
|
|
121
|
+
const result = hbConfigNode.toList(inputs, perms);
|
|
122
|
+
/*
|
|
123
|
+
{
|
|
124
|
+
"homebridge": "homebridge",
|
|
125
|
+
"host": "192.168.1.11",
|
|
126
|
+
"port": 35215,
|
|
127
|
+
"id": "1C:22:3D:E3:CF:34",
|
|
128
|
+
"manufacturer": "Tasmota",
|
|
129
|
+
"aid": 75,
|
|
130
|
+
"type": "00000043",
|
|
131
|
+
"name": "West Bedroom",
|
|
132
|
+
"service": "Lightbulb",
|
|
133
|
+
"fullName": "West Bedroom - Lightbulb",
|
|
134
|
+
"sortName": "West Bedroom:Lightbulb",
|
|
135
|
+
"uniqueId": "homebridge1C:22:3D:E3:CF:34TasmotaWest Bedroom00000043",
|
|
136
|
+
"descriptions": "On",
|
|
137
|
+
"characteristics": {
|
|
138
|
+
"75.10": {
|
|
139
|
+
"characteristic": "On",
|
|
140
|
+
"iid": 10
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"getCharacteristics": "75.10",
|
|
144
|
+
"eventRegisters": [{
|
|
145
|
+
"aid": 75,
|
|
146
|
+
"iid": 10,
|
|
147
|
+
"ev": true
|
|
148
|
+
}]
|
|
149
|
+
}
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
expect(result).toHaveLength(1);
|
|
153
|
+
console.log(result);
|
|
154
|
+
/*
|
|
155
|
+
expect(result).toEqual([
|
|
156
|
+
{
|
|
157
|
+
uniqueId: "1",
|
|
158
|
+
serviceName: "Service 1",
|
|
159
|
+
characteristics: [
|
|
160
|
+
{
|
|
161
|
+
id: "1.1",
|
|
162
|
+
type: "Type 1",
|
|
163
|
+
description: "Description 1",
|
|
164
|
+
value: "Value 1",
|
|
165
|
+
format: "Format 1",
|
|
166
|
+
unit: "Unit 1",
|
|
167
|
+
perms: ["perm1", "perm2"],
|
|
168
|
+
canRead: true,
|
|
169
|
+
canWrite: false,
|
|
170
|
+
ev: true
|
|
171
|
+
},
|
|
172
|
+
// Add more expected characteristics here if needed
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
// Add more expected outputs here if needed
|
|
176
|
+
]);
|
|
177
|
+
*/
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
afterAll(async () => {
|
|
181
|
+
// eslint-disable-next-line no-console
|
|
182
|
+
console.log('destroy');
|
|
183
|
+
// await hap.destroy();
|
|
184
|
+
});
|
|
185
|
+
});
|
package/src/hbControlNode.js
CHANGED
|
@@ -6,16 +6,61 @@ class HbControlNode extends hbBaseNode {
|
|
|
6
6
|
super(config, RED);
|
|
7
7
|
|
|
8
8
|
// Register the node-specific input and close handlers
|
|
9
|
-
this.on('input', this.handleInput.bind(this));
|
|
10
|
-
|
|
9
|
+
// this.on('input', this.handleInput.bind(this));
|
|
11
10
|
// Register the node with the configuration
|
|
12
|
-
this.hbConfigNode.register(this.config, this.registerNode.bind(this));
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
// Handle input messages
|
|
16
|
-
handleInput(
|
|
17
|
-
this.
|
|
18
|
-
|
|
14
|
+
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
|
+
);
|
|
24
|
+
|
|
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
|
+
});
|
|
40
|
+
|
|
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
|
+
});
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
this.error("HB not initialized");
|
|
56
|
+
this.status({
|
|
57
|
+
text: 'HB not initialized',
|
|
58
|
+
shape: 'ring',
|
|
59
|
+
fill: 'red',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
/*
|
|
19
64
|
this._control.call(this, this.node, msg.payload, (err, data) => {
|
|
20
65
|
if (!err && data && (this.deviceType === '00000110' || this.deviceType === '00000111')) {
|
|
21
66
|
const outputMsg = this.createOutputMessage(data);
|
|
@@ -24,44 +69,13 @@ class HbControlNode extends hbBaseNode {
|
|
|
24
69
|
this.error(err, this.msg);
|
|
25
70
|
}
|
|
26
71
|
});
|
|
72
|
+
*/
|
|
27
73
|
}
|
|
28
74
|
|
|
29
75
|
// Handle node closure
|
|
30
76
|
handleClose(callback) {
|
|
31
77
|
callback();
|
|
32
78
|
}
|
|
33
|
-
|
|
34
|
-
// Register the node with the configuration and find the device
|
|
35
|
-
registerNode() {
|
|
36
|
-
debug('hbControl.register:', this.node.fullName);
|
|
37
|
-
|
|
38
|
-
this.node.hbDevice = this.findDevice(this.node.device);
|
|
39
|
-
|
|
40
|
-
if (this.node.hbDevice) {
|
|
41
|
-
this.node.deviceType = this.node.hbDevice.type;
|
|
42
|
-
} else {
|
|
43
|
-
this.error(`437: Can't find device ${this.node.device}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Create an output message based on the received data
|
|
48
|
-
createOutputMessage(data) {
|
|
49
|
-
const outputMsg = {
|
|
50
|
-
name: this.node.name,
|
|
51
|
-
payload: this.node.state,
|
|
52
|
-
_device: this.node.device,
|
|
53
|
-
_confId: this.node.confId,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
if (this.node.hbDevice) {
|
|
57
|
-
outputMsg.Homebridge = this.node.hbDevice.homebridge;
|
|
58
|
-
outputMsg.Manufacturer = this.node.hbDevice.manufacturer;
|
|
59
|
-
outputMsg.Service = this.node.hbDevice.deviceType;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
outputMsg.payload = data;
|
|
63
|
-
return outputMsg;
|
|
64
|
-
}
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
module.exports = HbControlNode;
|
package/test/node-red/flows.json
CHANGED
|
@@ -82,13 +82,13 @@
|
|
|
82
82
|
"type": "hb-status",
|
|
83
83
|
"z": "caef1e7b5b399e80",
|
|
84
84
|
"d": true,
|
|
85
|
-
"name": "",
|
|
85
|
+
"name": "West Bedroom",
|
|
86
86
|
"Homebridge": "homebridge",
|
|
87
87
|
"Manufacturer": "Tasmota",
|
|
88
88
|
"Service": "Lightbulb",
|
|
89
89
|
"device": "homebridge1C:22:3D:E3:CF:34TasmotaWest Bedroom00000043",
|
|
90
90
|
"conf": "557aec8e8c47e61e",
|
|
91
|
-
"x":
|
|
91
|
+
"x": 480,
|
|
92
92
|
"y": 140,
|
|
93
93
|
"wires": [
|
|
94
94
|
[
|
|
@@ -138,13 +138,13 @@
|
|
|
138
138
|
"type": "hb-resume",
|
|
139
139
|
"z": "caef1e7b5b399e80",
|
|
140
140
|
"d": true,
|
|
141
|
-
"name": "",
|
|
141
|
+
"name": "West Bedroom",
|
|
142
142
|
"Homebridge": "homebridge",
|
|
143
143
|
"Manufacturer": "Tasmota",
|
|
144
144
|
"Service": "Lightbulb",
|
|
145
145
|
"device": "homebridge1C:22:3D:E3:CF:34TasmotaWest Bedroom00000043",
|
|
146
146
|
"conf": "557aec8e8c47e61e",
|
|
147
|
-
"x":
|
|
147
|
+
"x": 480,
|
|
148
148
|
"y": 460,
|
|
149
149
|
"wires": [
|
|
150
150
|
[
|
|
@@ -173,16 +173,59 @@
|
|
|
173
173
|
"id": "0ed3cd7e0d60beda",
|
|
174
174
|
"type": "hb-control",
|
|
175
175
|
"z": "caef1e7b5b399e80",
|
|
176
|
-
"
|
|
176
|
+
"name": "West Bedroom Fan",
|
|
177
|
+
"Homebridge": "homebridge",
|
|
178
|
+
"Manufacturer": "Tasmota",
|
|
179
|
+
"Service": "Fan",
|
|
180
|
+
"device": "homebridge1C:22:3D:E3:CF:34TasmotaWest Bedroom Fan00000040",
|
|
181
|
+
"conf": "557aec8e8c47e61e",
|
|
182
|
+
"outputs": 0,
|
|
183
|
+
"x": 510,
|
|
184
|
+
"y": 600,
|
|
185
|
+
"wires": []
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "d9f8181e9e6b3cfd",
|
|
189
|
+
"type": "inject",
|
|
190
|
+
"z": "caef1e7b5b399e80",
|
|
177
191
|
"name": "",
|
|
192
|
+
"props": [
|
|
193
|
+
{
|
|
194
|
+
"p": "payload"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"p": "topic",
|
|
198
|
+
"vt": "str"
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"repeat": "60",
|
|
202
|
+
"crontab": "",
|
|
203
|
+
"once": true,
|
|
204
|
+
"onceDelay": "60",
|
|
205
|
+
"topic": "",
|
|
206
|
+
"payload": "",
|
|
207
|
+
"payloadType": "date",
|
|
208
|
+
"x": 210,
|
|
209
|
+
"y": 460,
|
|
210
|
+
"wires": [
|
|
211
|
+
[
|
|
212
|
+
"452e3e6171aa7a25"
|
|
213
|
+
]
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"id": "6703815a8874b156",
|
|
218
|
+
"type": "hb-control",
|
|
219
|
+
"z": "caef1e7b5b399e80",
|
|
220
|
+
"name": "West Bedroom",
|
|
178
221
|
"Homebridge": "homebridge",
|
|
179
222
|
"Manufacturer": "Tasmota",
|
|
180
223
|
"Service": "Lightbulb",
|
|
181
224
|
"device": "homebridge1C:22:3D:E3:CF:34TasmotaWest Bedroom00000043",
|
|
182
225
|
"conf": "557aec8e8c47e61e",
|
|
183
226
|
"outputs": 0,
|
|
184
|
-
"x":
|
|
185
|
-
"y":
|
|
227
|
+
"x": 740,
|
|
228
|
+
"y": 660,
|
|
186
229
|
"wires": []
|
|
187
230
|
},
|
|
188
231
|
{
|
|
@@ -204,10 +247,10 @@
|
|
|
204
247
|
"once": false,
|
|
205
248
|
"onceDelay": 0.1,
|
|
206
249
|
"topic": "",
|
|
207
|
-
"payload": "",
|
|
208
|
-
"payloadType": "
|
|
209
|
-
"x":
|
|
210
|
-
"y":
|
|
250
|
+
"payload": "{\"On\": true, \"RotationSpeed\": 33}",
|
|
251
|
+
"payloadType": "json",
|
|
252
|
+
"x": 190,
|
|
253
|
+
"y": 580,
|
|
211
254
|
"wires": [
|
|
212
255
|
[
|
|
213
256
|
"0ed3cd7e0d60beda"
|
|
@@ -215,7 +258,7 @@
|
|
|
215
258
|
]
|
|
216
259
|
},
|
|
217
260
|
{
|
|
218
|
-
"id": "
|
|
261
|
+
"id": "8c0ecac45b01df73",
|
|
219
262
|
"type": "inject",
|
|
220
263
|
"z": "caef1e7b5b399e80",
|
|
221
264
|
"name": "",
|
|
@@ -228,18 +271,135 @@
|
|
|
228
271
|
"vt": "str"
|
|
229
272
|
}
|
|
230
273
|
],
|
|
231
|
-
"repeat": "
|
|
274
|
+
"repeat": "",
|
|
232
275
|
"crontab": "",
|
|
233
|
-
"once":
|
|
234
|
-
"onceDelay":
|
|
276
|
+
"once": false,
|
|
277
|
+
"onceDelay": 0.1,
|
|
278
|
+
"topic": "",
|
|
279
|
+
"payload": "{\"On\": false}",
|
|
280
|
+
"payloadType": "json",
|
|
281
|
+
"x": 210,
|
|
282
|
+
"y": 640,
|
|
283
|
+
"wires": [
|
|
284
|
+
[
|
|
285
|
+
"0ed3cd7e0d60beda"
|
|
286
|
+
]
|
|
287
|
+
]
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"id": "b14ebf264be0c60d",
|
|
291
|
+
"type": "inject",
|
|
292
|
+
"z": "caef1e7b5b399e80",
|
|
293
|
+
"name": "",
|
|
294
|
+
"props": [
|
|
295
|
+
{
|
|
296
|
+
"p": "payload"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"p": "topic",
|
|
300
|
+
"vt": "str"
|
|
301
|
+
}
|
|
302
|
+
],
|
|
303
|
+
"repeat": "",
|
|
304
|
+
"crontab": "",
|
|
305
|
+
"once": false,
|
|
306
|
+
"onceDelay": 0.1,
|
|
307
|
+
"topic": "",
|
|
308
|
+
"payload": "{\"On\": true}",
|
|
309
|
+
"payloadType": "json",
|
|
310
|
+
"x": 350,
|
|
311
|
+
"y": 800,
|
|
312
|
+
"wires": [
|
|
313
|
+
[
|
|
314
|
+
"6703815a8874b156"
|
|
315
|
+
]
|
|
316
|
+
]
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"id": "f872545e74246764",
|
|
320
|
+
"type": "inject",
|
|
321
|
+
"z": "caef1e7b5b399e80",
|
|
322
|
+
"name": "",
|
|
323
|
+
"props": [
|
|
324
|
+
{
|
|
325
|
+
"p": "payload"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"p": "topic",
|
|
329
|
+
"vt": "str"
|
|
330
|
+
}
|
|
331
|
+
],
|
|
332
|
+
"repeat": "",
|
|
333
|
+
"crontab": "",
|
|
334
|
+
"once": false,
|
|
335
|
+
"onceDelay": 0.1,
|
|
336
|
+
"topic": "",
|
|
337
|
+
"payload": "{\"On\": false}",
|
|
338
|
+
"payloadType": "json",
|
|
339
|
+
"x": 350,
|
|
340
|
+
"y": 860,
|
|
341
|
+
"wires": [
|
|
342
|
+
[
|
|
343
|
+
"6703815a8874b156"
|
|
344
|
+
]
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"id": "7eae7b87f319966e",
|
|
349
|
+
"type": "inject",
|
|
350
|
+
"z": "caef1e7b5b399e80",
|
|
351
|
+
"name": "",
|
|
352
|
+
"props": [
|
|
353
|
+
{
|
|
354
|
+
"p": "payload"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"p": "topic",
|
|
358
|
+
"vt": "str"
|
|
359
|
+
}
|
|
360
|
+
],
|
|
361
|
+
"repeat": "",
|
|
362
|
+
"crontab": "",
|
|
363
|
+
"once": false,
|
|
364
|
+
"onceDelay": 0.1,
|
|
235
365
|
"topic": "",
|
|
236
366
|
"payload": "",
|
|
237
367
|
"payloadType": "date",
|
|
238
|
-
"x":
|
|
239
|
-
"y":
|
|
368
|
+
"x": 520,
|
|
369
|
+
"y": 540,
|
|
240
370
|
"wires": [
|
|
241
371
|
[
|
|
242
|
-
"
|
|
372
|
+
"6703815a8874b156",
|
|
373
|
+
"0ed3cd7e0d60beda"
|
|
374
|
+
]
|
|
375
|
+
]
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"id": "6a7b40f6149f2d36",
|
|
379
|
+
"type": "inject",
|
|
380
|
+
"z": "caef1e7b5b399e80",
|
|
381
|
+
"name": "",
|
|
382
|
+
"props": [
|
|
383
|
+
{
|
|
384
|
+
"p": "payload"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"p": "topic",
|
|
388
|
+
"vt": "str"
|
|
389
|
+
}
|
|
390
|
+
],
|
|
391
|
+
"repeat": "",
|
|
392
|
+
"crontab": "",
|
|
393
|
+
"once": false,
|
|
394
|
+
"onceDelay": 0.1,
|
|
395
|
+
"topic": "",
|
|
396
|
+
"payload": "{\"Off\": false}",
|
|
397
|
+
"payloadType": "json",
|
|
398
|
+
"x": 490,
|
|
399
|
+
"y": 920,
|
|
400
|
+
"wires": [
|
|
401
|
+
[
|
|
402
|
+
"6703815a8874b156"
|
|
243
403
|
]
|
|
244
404
|
]
|
|
245
405
|
}
|