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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-homebridge-automation",
3
- "version": "0.1.12-beta.17",
3
+ "version": "0.1.12-beta.19",
4
4
  "description": "NodeRED Automation for HomeBridge",
5
5
  "main": "src/HAP-NodeRed.js",
6
6
  "scripts": {
@@ -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');
@@ -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.node = this;
18
-
19
- this.on('input', this.handleInput.bind(this));
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
 
@@ -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
- RED.nodes.createNode(this, config);
12
- this.username = config.username;
13
- this.macAddress = config.macAddress || '';
14
- this.password = this.password;
15
- this.on('close', function () {
16
- this.hbConf.close(); // Close any open connections
17
- });
18
-
19
- // console.log('HBConfNode', config);
20
- this.username = config.username;
21
- this.macAddress = config.macAddress || '';
22
- // this.password = config.credentials.password;
23
- this.users = {};
24
- this.homebridge = null;
25
- this.evDevices = [];
26
- this.ctDevices = [];
27
- this.hbDevices = [];
28
-
29
- this.log = new Log(console, true);
30
-
31
- this.reqisterQueue = new Queue(function (node, cb) {
32
- this._register.call(node.that, node, cb);
33
- }, {
34
- concurrent: 1,
35
- autoResume: false,
36
- maxRetries: 1000,
37
- retryDelay: 30000
38
- });
39
- this.reqisterQueue.pause();
40
-
41
- this.initHomebridge(config);
42
- }
43
-
44
- // Initialize the Homebridge client
45
- initHomebridge(config) {
46
-
47
- //this.homebridge = new HAPNodeJSClient({
48
- // pin: config.username,
49
- // refresh: 900,
50
- // debug: false,
51
- // timeout: 5,
52
- // reqTimeout: 7000,
53
- // });
54
- debug('initHomebridge - hapClient', config);
55
- this.hapClient = new HapClient({
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.start();
78
- this.requestSync();
79
- this.hapClient.on('instance-discovered', this.requestSync.bind(this)); // Request sync on new instance discovery
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 = new Homebridges(accessories);
86
- debug('Discovered %s new evDevices', this.hbDevices.toList({ perms: 'ev' }).length);
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.hbDevices.toList({ perms: 'ev' });
89
- this.ctDevices = this.hbDevices.toList({ perms: 'pw' });
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(deviceNode, callback) {
134
- debug('hbConf.register', deviceNode);
135
- this.users[deviceNode.id] = deviceNode;
136
- debug('Register %s -> %s', deviceNode.type, deviceNode.name);
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
- for (const event of deviceNode.eventName) {
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
+ });
@@ -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(msg) {
17
- this.msg = msg;
18
- debug('handleInput', msg);
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;
@@ -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": 520,
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": 520,
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
- "d": true,
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": 540,
185
- "y": 600,
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": "date",
209
- "x": 200,
210
- "y": 600,
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": "d9f8181e9e6b3cfd",
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": "60",
274
+ "repeat": "",
232
275
  "crontab": "",
233
- "once": true,
234
- "onceDelay": "60",
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": 210,
239
- "y": 460,
368
+ "x": 520,
369
+ "y": 540,
240
370
  "wires": [
241
371
  [
242
- "452e3e6171aa7a25"
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
  }