homebridge-openwrt-control 0.0.1-beta.0

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.
@@ -0,0 +1,463 @@
1
+ {
2
+ "pluginAlias": "OpenWrt",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "fixArrays": true,
6
+ "strictValidation": true,
7
+ "headerDisplay": "This plugin works with OpenWrt Devices based on Dashboard API. Devices are exposed to HomeKit as separate accessories and each needs to be manually paired.",
8
+ "footerDisplay": "For documentation please see [GitHub repository](https://github.com/grzegorz914/homebridge-openwrt-control).",
9
+ "schema": {
10
+ "type": "object",
11
+ "properties": {
12
+ "devices": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "title": "Network",
17
+ "properties": {
18
+ "name": {
19
+ "title": "Name",
20
+ "type": "string",
21
+ "placeholder": "OpenWrt"
22
+ },
23
+ "host": {
24
+ "title": "IP/Hostname",
25
+ "type": "string",
26
+ "placeholder": "192.168.1.10 or openwrt.local",
27
+ "pattern": "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(\\.([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*)$"
28
+ },
29
+ "auth": {
30
+ "title": "Authorization",
31
+ "type": "object",
32
+ "properties": {
33
+ "enable": {
34
+ "title": "Enable",
35
+ "type": "boolean",
36
+ "default": false
37
+ },
38
+ "user": {
39
+ "title": "User",
40
+ "type": "string",
41
+ "placeholder": "User",
42
+ "condition": {
43
+ "functionBody": "return model.devices[arrayIndices].auth.enable === true;"
44
+ }
45
+ },
46
+ "passwd": {
47
+ "title": "Password",
48
+ "type": "string",
49
+ "placeholder": "Password",
50
+ "format": "password",
51
+ "condition": {
52
+ "functionBody": "return model.devices[arrayIndices].auth.enable === true;"
53
+ }
54
+ }
55
+ },
56
+ "allOf": [
57
+ {
58
+ "if": {
59
+ "required": [
60
+ "enable"
61
+ ],
62
+ "properties": {
63
+ "enable": {
64
+ "const": true
65
+ }
66
+ }
67
+ },
68
+ "then": {
69
+ "required": [
70
+ "user",
71
+ "passwd"
72
+ ]
73
+ }
74
+ }
75
+ ]
76
+ },
77
+ "displayType": {
78
+ "title": "Accessory",
79
+ "type": "integer",
80
+ "minimum": 0,
81
+ "maximum": 1,
82
+ "default": 0,
83
+ "description": "Accessory type for Home app",
84
+ "anyOf": [
85
+ {
86
+ "title": "Disabled",
87
+ "enum": [
88
+ 0
89
+ ]
90
+ },
91
+ {
92
+ "title": "Enabled",
93
+ "enum": [
94
+ 1
95
+ ]
96
+ }
97
+ ]
98
+ },
99
+ "accessPoint": {
100
+ "title": "Access point",
101
+ "type": "object",
102
+ "properties": {
103
+ "enable": {
104
+ "title": "Enable",
105
+ "type": "boolean",
106
+ "description": "Here Enable/Disable this access point control."
107
+ },
108
+ "name": {
109
+ "title": "Name",
110
+ "type": "string",
111
+ "placeholder": "Name",
112
+ "description": "Here set the Name to be displayed in Homebridge/HomeKit for this access point.",
113
+ "condition": {
114
+ "functionBody": "return model.devices[arrayIndices[0]].accessPoint.enable === true;"
115
+ }
116
+ },
117
+ "namePrefix": {
118
+ "title": "Prefix",
119
+ "type": "boolean",
120
+ "description": "Here enable/disable the accessory name as a prefix for the access point name.",
121
+ "condition": {
122
+ "functionBody": "return model.devices[arrayIndices[0]].accessPoint.enable === true;"
123
+ }
124
+ },
125
+ "sensor": {
126
+ "title": "Enable",
127
+ "type": "boolean",
128
+ "description": "Here Enable/Disable sensor for every ssid.",
129
+ "condition": {
130
+ "functionBody": "return model.devices[arrayIndices].accessPoint.enable === true;"
131
+ }
132
+ }
133
+ }
134
+ },
135
+ "refreshInterval": {
136
+ "title": "Refresh Interval (sec)",
137
+ "type": "integer",
138
+ "minimum": 0,
139
+ "maximum": 60,
140
+ "default": 5,
141
+ "description": "Here set the data refresh time in (sec)."
142
+ },
143
+ "log": {
144
+ "title": "Log",
145
+ "type": "object",
146
+ "properties": {
147
+ "deviceInfo": {
148
+ "title": "Device Info",
149
+ "type": "boolean",
150
+ "default": true,
151
+ "description": "This enable logging device info by every connections device to the network."
152
+ },
153
+ "success": {
154
+ "title": "Success",
155
+ "type": "boolean",
156
+ "default": true
157
+ },
158
+ "info": {
159
+ "title": "Info",
160
+ "type": "boolean",
161
+ "default": false
162
+ },
163
+ "warn": {
164
+ "title": "Warn",
165
+ "type": "boolean",
166
+ "default": true
167
+ },
168
+ "error": {
169
+ "title": "Error",
170
+ "type": "boolean",
171
+ "default": true
172
+ },
173
+ "debug": {
174
+ "title": "Debug",
175
+ "type": "boolean",
176
+ "default": false
177
+ }
178
+ }
179
+ },
180
+ "restFul": {
181
+ "title": "RESTFul",
182
+ "type": "object",
183
+ "properties": {
184
+ "enable": {
185
+ "title": "Enable",
186
+ "type": "boolean",
187
+ "default": false,
188
+ "description": "This enable RESTful server."
189
+ },
190
+ "port": {
191
+ "title": "Port",
192
+ "type": "integer",
193
+ "placeholder": 3000,
194
+ "description": "Here set the listening Port for RESTful server.",
195
+ "condition": {
196
+ "functionBody": "return model.devices[arrayIndices].restFul.enable == true;"
197
+ }
198
+ }
199
+ }
200
+ },
201
+ "mqtt": {
202
+ "title": "MQTT",
203
+ "type": "object",
204
+ "properties": {
205
+ "enable": {
206
+ "title": "Enable",
207
+ "type": "boolean",
208
+ "default": false,
209
+ "description": "This enable MQTT client."
210
+ },
211
+ "host": {
212
+ "title": "IP/Hostname",
213
+ "type": "string",
214
+ "placeholder": "192.168.1.20 or mqtt.local",
215
+ "pattern": "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}|([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(\\.([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*)$",
216
+ "description": "Here set the IP/Hostname of MQTT Broker.",
217
+ "condition": {
218
+ "functionBody": "return model.devices[arrayIndices].mqtt.enable == true;"
219
+ }
220
+ },
221
+ "port": {
222
+ "title": "Port",
223
+ "type": "integer",
224
+ "placeholder": 1883,
225
+ "description": "Here set the port of MQTT Broker.",
226
+ "condition": {
227
+ "functionBody": "return model.devices[arrayIndices].mqtt.enable == true;"
228
+ }
229
+ },
230
+ "clientId": {
231
+ "title": "Client ID",
232
+ "type": "string",
233
+ "placeholder": "client id",
234
+ "description": "Here optional set the Client ID of MQTT Broker.",
235
+ "condition": {
236
+ "functionBody": "return model.devices[arrayIndices].mqtt.enable == true;"
237
+ }
238
+ },
239
+ "prefix": {
240
+ "title": "Prefix",
241
+ "type": "string",
242
+ "placeholder": "home",
243
+ "description": "Here set the prefix.",
244
+ "condition": {
245
+ "functionBody": "return model.devices[arrayIndices].mqtt.enable == true;"
246
+ }
247
+ },
248
+ "auth": {
249
+ "title": "Authorization",
250
+ "type": "object",
251
+ "properties": {
252
+ "enable": {
253
+ "title": "Enable",
254
+ "type": "boolean",
255
+ "description": "This enable authorization for MQTT Broker."
256
+ },
257
+ "user": {
258
+ "title": "User",
259
+ "type": "string",
260
+ "placeholder": "user",
261
+ "description": "Here set the user of MQTT Broker.",
262
+ "condition": {
263
+ "functionBody": "return model.devices[arrayIndices].mqtt.auth.enable === true;"
264
+ }
265
+ },
266
+ "passwd": {
267
+ "title": "Password",
268
+ "type": "string",
269
+ "placeholder": "password",
270
+ "description": "Here set the password of MQTT Broker.",
271
+ "format": "password",
272
+ "condition": {
273
+ "functionBody": "return model.devices[arrayIndices].mqtt.auth.enable === true;"
274
+ }
275
+ }
276
+ },
277
+ "condition": {
278
+ "functionBody": "return model.devices[arrayIndices].mqtt.enable == true;"
279
+ },
280
+ "allOf": [
281
+ {
282
+ "if": {
283
+ "required": [
284
+ "enable"
285
+ ],
286
+ "properties": {
287
+ "enable": {
288
+ "const": true
289
+ }
290
+ }
291
+ },
292
+ "then": {
293
+ "required": [
294
+ "user",
295
+ "passwd"
296
+ ]
297
+ }
298
+ }
299
+ ]
300
+ }
301
+ },
302
+ "allOf": [
303
+ {
304
+ "if": {
305
+ "required": [
306
+ "enable"
307
+ ],
308
+ "properties": {
309
+ "enable": {
310
+ "const": true
311
+ }
312
+ }
313
+ },
314
+ "then": {
315
+ "required": [
316
+ "host"
317
+ ]
318
+ }
319
+ }
320
+ ]
321
+ }
322
+ },
323
+ "required": [
324
+ "name",
325
+ "host",
326
+ "displayType"
327
+ ],
328
+ "allOf": [
329
+ {
330
+ "if": {
331
+ "properties": {
332
+ "displayType": {
333
+ "enum": [
334
+ 1
335
+ ]
336
+ }
337
+ }
338
+ },
339
+ "then": {
340
+ "required": [
341
+ "host"
342
+ ]
343
+ }
344
+ }
345
+ ]
346
+ }
347
+ }
348
+ }
349
+ },
350
+ "layout": [
351
+ {
352
+ "key": "devices",
353
+ "type": "tabarray",
354
+ "title": "{{ value.name || 'Device' }}",
355
+ "items": [
356
+ "devices[].name",
357
+ "devices[].host",
358
+ "devices[].displayType",
359
+ {
360
+ "key": "devices[]",
361
+ "type": "section",
362
+ "title": "Advanced Settings",
363
+ "expandable": true,
364
+ "expanded": false,
365
+ "items": [
366
+ {
367
+ "key": "devices[]",
368
+ "type": "tabarray",
369
+ "title": "{{ value.title }}",
370
+ "items": [
371
+ {
372
+ "key": "devices[].accessPoint",
373
+ "title": "Access Point",
374
+ "items": [
375
+ "devices[].accessPoint.enable",
376
+ "devices[].accessPoint.name",
377
+ "devices[].accessPoint.namePrefix",
378
+ "devices[].accessPoint.sensors"
379
+ ]
380
+ },
381
+ {
382
+ "key": "devices[].auth",
383
+ "title": "Authorization",
384
+ "items": [
385
+ "devices[].auth.enable",
386
+ "devices[].auth.user",
387
+ {
388
+ "key": "devices[].auth.passwd",
389
+ "type": "password"
390
+ }
391
+ ]
392
+ },
393
+ {
394
+ "key": "devices[]",
395
+ "title": "Devices",
396
+ "items": [
397
+ "devices[].refreshInterval"
398
+ ]
399
+ },
400
+ {
401
+ "key": "devices[].log",
402
+ "title": "Log",
403
+ "items": [
404
+ "devices[].log.deviceInfo",
405
+ "devices[].log.success",
406
+ "devices[].log.info",
407
+ "devices[].log.warn",
408
+ "devices[].log.error",
409
+ "devices[].log.debug"
410
+ ]
411
+ },
412
+ {
413
+ "key": "devices[]",
414
+ "title": "External Integrations",
415
+ "items": [
416
+ {
417
+ "key": "devices[]",
418
+ "type": "tabarray",
419
+ "title": "{{ value.title }}",
420
+ "items": [
421
+ {
422
+ "key": "devices[].restFul",
423
+ "title": "RESTFul",
424
+ "items": [
425
+ "devices[].restFul.enable",
426
+ "devices[].restFul.port"
427
+ ]
428
+ },
429
+ {
430
+ "key": "devices[].mqtt",
431
+ "title": "MQTT",
432
+ "items": [
433
+ "devices[].mqtt.enable",
434
+ "devices[].mqtt.host",
435
+ "devices[].mqtt.port",
436
+ "devices[].mqtt.clientId",
437
+ "devices[].mqtt.prefix",
438
+ {
439
+ "key": "devices[].mqtt.auth",
440
+ "title": "Authorization",
441
+ "items": [
442
+ "devices[].mqtt.auth.enable",
443
+ "devices[].mqtt.auth.user",
444
+ {
445
+ "key": "devices[].mqtt.auth.passwd",
446
+ "type": "password"
447
+ }
448
+ ]
449
+ }
450
+ ]
451
+ }
452
+ ]
453
+ }
454
+ ]
455
+ }
456
+ ]
457
+ }
458
+ ]
459
+ }
460
+ ]
461
+ }
462
+ ]
463
+ }
package/index.js ADDED
@@ -0,0 +1,143 @@
1
+ import { join } from 'path';
2
+ import { mkdirSync, existsSync, writeFileSync } from 'fs';
3
+ import AccessPointDevice from './src/apdevice.js';
4
+ import OpenWrt from './src/openwrt.js';
5
+ import ImpulseGenerator from './src/impulsegenerator.js';
6
+ import { PluginName, PlatformName } from './src/constants.js';
7
+
8
+ class OpenWrtPlatform {
9
+ constructor(log, config, api) {
10
+ // only load if configured
11
+ if (!config || !Array.isArray(config.devices)) {
12
+ log.warn(`No configuration found for ${PluginName}`);
13
+ return;
14
+ }
15
+ this.accessories = [];
16
+ this.devices = [];
17
+
18
+ //check if prefs directory exist
19
+ const prefDir = join(api.user.storagePath(), 'openWrt');
20
+ try {
21
+ mkdirSync(prefDir, { recursive: true });
22
+ } catch (error) {
23
+ log.error(`Prepare directory error: ${error.message ?? error}`);
24
+ return;
25
+ }
26
+
27
+ api.on('didFinishLaunching', async () => {
28
+ for (const config of config.devices) {
29
+ const { name, host, port, displayType } = config;
30
+ if (!name || !host || !port || !displayType) {
31
+ log.warn(`Device: ${host || 'host missing'}, ${name || 'name missing'}, ${port || 'port missing'}${!displayType ? ', disply type disabled' : ''} in config, will not be published in the Home app`);
32
+ continue;
33
+ }
34
+
35
+ //log config
36
+ const logLevel = {
37
+ devInfo: config.log?.deviceInfo,
38
+ success: config.log?.success,
39
+ info: config.log?.info,
40
+ warn: config.log?.warn,
41
+ error: config.log?.error,
42
+ debug: config.log?.debug
43
+ };
44
+
45
+ if (logLevel.debug) {
46
+ log.info(`Device: ${host} ${name}, did finish launching.`);
47
+ const safeConfig = {
48
+ ...config,
49
+ auth: {
50
+ ...config.auth,
51
+ passwd: 'removed',
52
+ },
53
+ mqtt: {
54
+ auth: {
55
+ ...config.mqtt?.auth,
56
+ passwd: 'removed',
57
+ }
58
+ },
59
+ };
60
+ log.info(`Device: ${host} ${name}, Config: ${JSON.stringify(safeConfig, null, 2)}`);
61
+ }
62
+
63
+ const refreshInterval = (config.refreshInterval ?? 5) * 1000;
64
+ if (config.accessPoint?.enable) this.devices.push('accessPoint');
65
+ if (config.switch?.enable) this.devices.push('switch');
66
+
67
+ if (this.devices.length === 0) return;
68
+
69
+ const openWrt = new OpenWrt(config)
70
+ .on('success', msg => logLevel.success && log.success(`Device: ${host}, ${msg}`))
71
+ .on('info', msg => log.info(`Device: ${host}, ${msg}`))
72
+ .on('debug', msg => log.info(`Device: ${host}, debug: ${msg}`))
73
+ .on('warn', msg => log.warn(`Device: ${host}, ${msg}`))
74
+ .on('error', msg => log.error(`Device: ${host}, ${msg}`))
75
+
76
+ const openWrtInfo = await openWrt.connect();
77
+ if (!openWrtInfo.state) {
78
+ if (logLevel.warn) log.warn(`Device: ${host} ${name}, no data received`);
79
+ return;
80
+ }
81
+
82
+ try {
83
+ for (const device of this.devices) {
84
+ // create impulse generator
85
+ const impulseGenerator = new ImpulseGenerator()
86
+ .on('start', async () => {
87
+ try {
88
+
89
+ // create device instance
90
+ let type;
91
+ switch (device) {
92
+ case 'accessPoint': type = new AccessPointDevice(api, config, openWrt, openWrtInfo); break;
93
+ case 'switch': type = new SwitchDevice(api, config, device.switch, openWrt, openWrtInfo); break;
94
+ default:
95
+ if (logLevel.warn) log.warn(`Device: ${host} ${name}, unknown zone: ${zoneControl}`);
96
+ return;
97
+ }
98
+
99
+ type
100
+ .on('devInfo', msg => logLevel.devInfo && log.info(msg))
101
+ .on('success', msg => logLevel.success && log.success(`Device: ${host} ${name}, ${msg}`))
102
+ .on('info', msg => log.info(`Device: ${host} ${name}, ${msg}`))
103
+ .on('debug', msg => log.info(`Device: ${host} ${name}, debug: ${msg}`))
104
+ .on('warn', msg => log.warn(`Device: ${host} ${name}, ${msg}`))
105
+ .on('error', msg => log.error(`Device: ${host} ${name}, ${msg}`));
106
+
107
+ const accessory = await type.start();
108
+ if (accessory) {
109
+ api.publishExternalAccessories(PluginName, [accessory]);
110
+ if (logLevel.success) log.success(`Device: ${host} ${name}, Published as external accessory.`);
111
+ }
112
+
113
+ // stop master impulse generator
114
+ await impulseGenerator.state(false);
115
+ } catch (error) {
116
+ if (logLevel.error) log.error(`Device: ${host} ${name}, Start impulse generator error: ${error.message ?? error}, trying again.`);
117
+ }
118
+ })
119
+ .on('state', (state) => {
120
+ if (logLevel.debug) log.info(`Device: ${host} ${name}, Start impulse generator ${state ? 'started' : 'stopped'}.`);
121
+ });
122
+
123
+ // start impulse generator
124
+ await impulseGenerator.state(true, [{ name: 'start', sampling: 120000 }]);
125
+ }
126
+
127
+ // start openwrt impulse generator
128
+ await openWrt.impulseGenerator.state(true, [{ name: 'connect', sampling: refreshInterval }], false);
129
+ } catch (error) {
130
+ if (logLevel.error) log.error(`Device: ${host} ${name}, Did finish launching error: ${error.message ?? error}`);
131
+ }
132
+ }
133
+ });
134
+ }
135
+
136
+ configureAccessory(accessory) {
137
+ this.accessories.push(accessory);
138
+ }
139
+ }
140
+
141
+ export default (api) => {
142
+ api.registerPlatform(PluginName, PlatformName, OpenWrtPlatform);
143
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "displayName": "OpenWrt Control",
3
+ "name": "homebridge-openwrt-control",
4
+ "version": "0.0.1-beta.0",
5
+ "description": "Homebridge plugin to control OpenWrt flashed devices.",
6
+ "license": "MIT",
7
+ "author": "grzegorz914",
8
+ "maintainers": [
9
+ "grzegorz914"
10
+ ],
11
+ "homepage": "https://github.com/grzegorz914/homebridge-openwrt-control#readme",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/grzegorz914/homebridge-openwrt-control.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/grzegorz914/homebridge-openwrt-control/issues"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": "./index.js"
22
+ },
23
+ "files": [
24
+ "src",
25
+ "index.js",
26
+ "config.schema.json",
27
+ "package.json",
28
+ "CHANGELOG.md",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "engines": {
33
+ "homebridge": "^1.8.0 || ^2.0.0 || ^2.0.0-beta.80 || ^2.0.0-alpha.63",
34
+ "node": "^20 || ^22 || ^24 || ^25"
35
+ },
36
+ "dependencies": {
37
+ "axios": "^1.13.2"
38
+ },
39
+ "keywords": [
40
+ "homebridge",
41
+ "homebridge-plugin",
42
+ "homekit",
43
+ "openwrt",
44
+ "accesspoint",
45
+ "router",
46
+ "switch"
47
+
48
+ ],
49
+ "contributors": [],
50
+ "scripts": {
51
+ "test": "echo \"Error: no test specified\" && exit 1"
52
+ }
53
+ }