homebridge-openwrt-control 0.0.2-beta.1 → 0.0.2-beta.12
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/config.schema.json +6 -6
- package/index.js +20 -20
- package/package.json +1 -1
- package/src/functions.js +0 -24
- package/src/openwrt.js +45 -45
package/config.schema.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"singular": true,
|
|
5
5
|
"fixArrays": true,
|
|
6
6
|
"strictValidation": true,
|
|
7
|
-
"headerDisplay": "This plugin works with OpenWrt
|
|
7
|
+
"headerDisplay": "This plugin works with OpenWrt flashed devices. Devices are exposed to HomeKit as separate accessories and each needs to be manually paired.",
|
|
8
8
|
"footerDisplay": "For documentation please see [GitHub repository](https://github.com/grzegorz914/homebridge-openwrt-control).",
|
|
9
9
|
"schema": {
|
|
10
10
|
"type": "object",
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"placeholder": "Name",
|
|
112
112
|
"description": "Here set the Name to be displayed in Homebridge/HomeKit for this access point.",
|
|
113
113
|
"condition": {
|
|
114
|
-
"functionBody": "return model.devices[arrayIndices
|
|
114
|
+
"functionBody": "return model.devices[arrayIndices].accessPoint.enable === true;"
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
117
|
"namePrefix": {
|
|
@@ -119,13 +119,13 @@
|
|
|
119
119
|
"type": "boolean",
|
|
120
120
|
"description": "Here enable/disable the accessory name as a prefix for the access point name.",
|
|
121
121
|
"condition": {
|
|
122
|
-
"functionBody": "return model.devices[arrayIndices
|
|
122
|
+
"functionBody": "return model.devices[arrayIndices].accessPoint.enable === true;"
|
|
123
123
|
}
|
|
124
124
|
},
|
|
125
125
|
"sensor": {
|
|
126
|
-
"title": "
|
|
126
|
+
"title": "Sensor",
|
|
127
127
|
"type": "boolean",
|
|
128
|
-
"description": "Here Enable/Disable sensor for every
|
|
128
|
+
"description": "Here Enable/Disable sensor for every SSID.",
|
|
129
129
|
"condition": {
|
|
130
130
|
"functionBody": "return model.devices[arrayIndices].accessPoint.enable === true;"
|
|
131
131
|
}
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
"devices[].accessPoint.enable",
|
|
376
376
|
"devices[].accessPoint.name",
|
|
377
377
|
"devices[].accessPoint.namePrefix",
|
|
378
|
-
"devices[].accessPoint.
|
|
378
|
+
"devices[].accessPoint.sensor"
|
|
379
379
|
]
|
|
380
380
|
},
|
|
381
381
|
{
|
package/index.js
CHANGED
|
@@ -26,34 +26,34 @@ class OpenWrtPlatform {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
api.on('didFinishLaunching', async () => {
|
|
29
|
-
for (const
|
|
30
|
-
const { name, host,
|
|
31
|
-
if (!name || !host || !
|
|
32
|
-
log.warn(`Device: ${host || 'host missing'}, ${name || 'name missing'}, ${
|
|
29
|
+
for (const deviceConfig of config.devices) {
|
|
30
|
+
const { name, host, displayType } = deviceConfig;
|
|
31
|
+
if (!name || !host || !displayType) {
|
|
32
|
+
log.warn(`Device: ${host || 'host missing'}, ${name || 'name missing'}, ${!displayType ? ', disply type disabled' : ''} in config, will not be published in the Home app`);
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
//log config
|
|
37
37
|
const logLevel = {
|
|
38
|
-
devInfo:
|
|
39
|
-
success:
|
|
40
|
-
info:
|
|
41
|
-
warn:
|
|
42
|
-
error:
|
|
43
|
-
debug:
|
|
38
|
+
devInfo: deviceConfig.log?.deviceInfo,
|
|
39
|
+
success: deviceConfig.log?.success,
|
|
40
|
+
info: deviceConfig.log?.info,
|
|
41
|
+
warn: deviceConfig.log?.warn,
|
|
42
|
+
error: deviceConfig.log?.error,
|
|
43
|
+
debug: deviceConfig.log?.debug
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
if (logLevel.debug) {
|
|
47
47
|
log.info(`Device: ${host} ${name}, did finish launching.`);
|
|
48
48
|
const safeConfig = {
|
|
49
|
-
...
|
|
49
|
+
...deviceConfig,
|
|
50
50
|
auth: {
|
|
51
|
-
...
|
|
51
|
+
...deviceConfig.auth,
|
|
52
52
|
passwd: 'removed',
|
|
53
53
|
},
|
|
54
54
|
mqtt: {
|
|
55
55
|
auth: {
|
|
56
|
-
...
|
|
56
|
+
...deviceConfig.mqtt?.auth,
|
|
57
57
|
passwd: 'removed',
|
|
58
58
|
}
|
|
59
59
|
},
|
|
@@ -61,13 +61,13 @@ class OpenWrtPlatform {
|
|
|
61
61
|
log.info(`Device: ${host} ${name}, Config: ${JSON.stringify(safeConfig, null, 2)}`);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const refreshInterval = (
|
|
65
|
-
if (
|
|
66
|
-
if (
|
|
64
|
+
const refreshInterval = (deviceConfig.refreshInterval ?? 5) * 1000;
|
|
65
|
+
if (deviceConfig.accessPoint?.enable) this.devices.push('accessPoint');
|
|
66
|
+
if (deviceConfig.switch?.enable) this.devices.push('switch');
|
|
67
67
|
|
|
68
68
|
if (this.devices.length === 0) return;
|
|
69
69
|
|
|
70
|
-
const openWrt = new OpenWrt(
|
|
70
|
+
const openWrt = new OpenWrt(deviceConfig)
|
|
71
71
|
.on('success', msg => logLevel.success && log.success(`Device: ${host}, ${msg}`))
|
|
72
72
|
.on('info', msg => log.info(`Device: ${host}, ${msg}`))
|
|
73
73
|
.on('debug', msg => log.info(`Device: ${host}, debug: ${msg}`))
|
|
@@ -90,10 +90,10 @@ class OpenWrtPlatform {
|
|
|
90
90
|
// create device instance
|
|
91
91
|
let type;
|
|
92
92
|
switch (device) {
|
|
93
|
-
case 'accessPoint': type = new AccessPoint(api,
|
|
94
|
-
case 'switch': type = new Switch(api,
|
|
93
|
+
case 'accessPoint': type = new AccessPoint(api, deviceConfig, openWrt, openWrtInfo); break;
|
|
94
|
+
case 'switch': type = new Switch(api, deviceConfig, openWrt, openWrtInfo); break;
|
|
95
95
|
default:
|
|
96
|
-
if (logLevel.warn) log.warn(`Device: ${host} ${name}, unknown
|
|
96
|
+
if (logLevel.warn) log.warn(`Device: ${host} ${name}, unknown device: ${device}`);
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
|
package/package.json
CHANGED
package/src/functions.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { promises as fsPromises } from 'fs';
|
|
2
|
-
import { DiacriticsMap } from './constants.js';
|
|
3
|
-
|
|
4
2
|
class Functions {
|
|
5
3
|
constructor() {
|
|
6
4
|
}
|
|
@@ -45,28 +43,6 @@ class Functions {
|
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
async sanitizeString(str) {
|
|
49
|
-
if (!str) return '';
|
|
50
|
-
|
|
51
|
-
// Replace diacritics using map
|
|
52
|
-
str = str.replace(/[^\u0000-\u007E]/g, ch => DiacriticsMap[ch] || ch);
|
|
53
|
-
|
|
54
|
-
// Replace separators between words with space
|
|
55
|
-
str = str.replace(/(\w)[.:;+\-\/]+(\w)/g, '$1 $2');
|
|
56
|
-
|
|
57
|
-
// Replace remaining standalone separators with space
|
|
58
|
-
str = str.replace(/[.:;+\-\/]/g, ' ');
|
|
59
|
-
|
|
60
|
-
// Remove remaining invalid characters (keep letters, digits, space, apostrophe)
|
|
61
|
-
str = str.replace(/[^A-Za-z0-9 ']/g, ' ');
|
|
62
|
-
|
|
63
|
-
// Collapse multiple spaces
|
|
64
|
-
str = str.replace(/\s+/g, ' ');
|
|
65
|
-
|
|
66
|
-
// Trim
|
|
67
|
-
return str.trim();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
46
|
async findIfaceBySsid(status, targetSsid) {
|
|
71
47
|
for (const radio of Object.values(status.radios)) {
|
|
72
48
|
for (const iface of Object.values(radio.interfaces)) {
|
package/src/openwrt.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import EventEmitter from
|
|
2
|
-
import axios from
|
|
1
|
+
import EventEmitter from 'events';
|
|
2
|
+
import axios from 'axios';
|
|
3
3
|
import Functions from './functions.js';
|
|
4
|
-
import ImpulseGenerator from
|
|
4
|
+
import ImpulseGenerator from './impulsegenerator.js';
|
|
5
5
|
|
|
6
6
|
class OpenWrt extends EventEmitter {
|
|
7
7
|
constructor(config) {
|
|
8
8
|
super();
|
|
9
9
|
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
12
|
-
this.user = config.user;
|
|
13
|
-
this.passwd = config.passwd;
|
|
10
|
+
this.user = config.auth?.user || 'root';
|
|
11
|
+
this.passwd = config.auth?.passwd;
|
|
14
12
|
this.logError = config.log?.error;
|
|
15
13
|
this.logDebug = config.log?.debug;
|
|
16
14
|
|
|
@@ -26,19 +24,20 @@ class OpenWrt extends EventEmitter {
|
|
|
26
24
|
|
|
27
25
|
this.functions = new Functions();
|
|
28
26
|
this.axiosInstance = axios.create({
|
|
29
|
-
baseURL:
|
|
27
|
+
baseURL: `http://${config.host}/ubus`,
|
|
30
28
|
timeout: 5000,
|
|
31
29
|
headers: {
|
|
32
|
-
"Content-Type": "application/json"
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
"Accept": "application/json"
|
|
33
32
|
}
|
|
34
33
|
});
|
|
35
34
|
|
|
36
35
|
this.impulseGenerator = new ImpulseGenerator()
|
|
37
|
-
.on(
|
|
36
|
+
.on('connect', () => this.handleWithLock(async () => {
|
|
38
37
|
await this.connect();
|
|
39
38
|
}))
|
|
40
|
-
.on(
|
|
41
|
-
this.emit(state ?
|
|
39
|
+
.on('state', (state) => {
|
|
40
|
+
this.emit(state ? 'success' : 'warn', `Impulse generator ${state ? 'started' : 'stopped'}`);
|
|
42
41
|
});
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -49,7 +48,7 @@ class OpenWrt extends EventEmitter {
|
|
|
49
48
|
try {
|
|
50
49
|
await fn();
|
|
51
50
|
} catch (error) {
|
|
52
|
-
this.emit(
|
|
51
|
+
this.emit('error', `Impulse generator error: ${error.message}`
|
|
53
52
|
);
|
|
54
53
|
} finally {
|
|
55
54
|
this.lock = false;
|
|
@@ -62,52 +61,53 @@ class OpenWrt extends EventEmitter {
|
|
|
62
61
|
return this.sessionId;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
const response = await this.axiosInstance.post(
|
|
66
|
-
jsonrpc:
|
|
64
|
+
const response = await this.axiosInstance.post('', {
|
|
65
|
+
jsonrpc: '2.0',
|
|
67
66
|
id: 1,
|
|
68
|
-
method:
|
|
69
|
-
params: [
|
|
67
|
+
method: 'call',
|
|
68
|
+
params: ['00000000000000000000000000000000', 'session', 'login', { username: this.user, password: this.passwd }]
|
|
70
69
|
});
|
|
71
70
|
|
|
72
71
|
const result = response.data?.result?.[1];
|
|
73
72
|
if (!result?.ubus_rpc_session) {
|
|
74
|
-
throw new Error(
|
|
73
|
+
throw new Error('ubus login failed');
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
this.sessionId = result.ubus_rpc_session;
|
|
78
77
|
this.sessionExpiresAt = now + 240_000;
|
|
79
78
|
|
|
80
|
-
if (this.logDebug) this.emit(
|
|
79
|
+
if (this.logDebug) this.emit('debug', `Ubus login OK`);
|
|
81
80
|
return this.sessionId;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
async ubusCall(service, method, params = {}) {
|
|
85
84
|
const session = await this.login();
|
|
86
85
|
|
|
87
|
-
const response = await this.axiosInstance.post(
|
|
88
|
-
jsonrpc:
|
|
86
|
+
const response = await this.axiosInstance.post('', {
|
|
87
|
+
jsonrpc: '2.0',
|
|
89
88
|
id: 2,
|
|
90
|
-
method:
|
|
89
|
+
method: 'call',
|
|
91
90
|
params: [session, service, method, params]
|
|
92
91
|
});
|
|
93
92
|
|
|
94
93
|
if (response.data?.error) {
|
|
95
|
-
throw new Error(response.data.error.message ||
|
|
94
|
+
throw new Error(response.data.error.message || 'ubus call error');
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
return response.data.result[1];
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
async connect() {
|
|
101
|
+
const openWrtInfo = { state: false, systemInfo: {}, wirelessStatus: {}, wirelessRadios: [], ssids: [] }
|
|
102
|
+
|
|
102
103
|
try {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
if (this.logDebug) this.emit("debug", `System info data: ${JSON.stringify(systemInfo, null, 2)}`);
|
|
104
|
+
const systemInfo = await this.ubusCall('system', 'board');
|
|
105
|
+
if (this.logDebug) this.emit('debug', `System info data: ${JSON.stringify(systemInfo, null, 2)}`);
|
|
106
106
|
|
|
107
|
-
const wirelessStatus = await this.ubusCall(
|
|
108
|
-
if (this.logDebug) this.emit(
|
|
107
|
+
const wirelessStatus = await this.ubusCall('network.wireless', 'status');
|
|
108
|
+
if (this.logDebug) this.emit('debug', `Wireless status data: ${JSON.stringify(wirelessStatus, null, 2)}`);
|
|
109
109
|
|
|
110
|
-
const wirelessRadios = Object.values(
|
|
110
|
+
const wirelessRadios = Object.values(wirelessStatus.radios).map(radio => ({
|
|
111
111
|
name: radio.name,
|
|
112
112
|
state: radio.up === true,
|
|
113
113
|
interfaces: Object.values(radio.interfaces).map(i => ({
|
|
@@ -118,13 +118,13 @@ class OpenWrt extends EventEmitter {
|
|
|
118
118
|
}));
|
|
119
119
|
|
|
120
120
|
const ssids = wirelessRadios.flatMap(radio => radio.interfaces);
|
|
121
|
-
this.emit(
|
|
122
|
-
this.emit(
|
|
123
|
-
this.emit(
|
|
124
|
-
this.emit(
|
|
121
|
+
this.emit('systemInfo', systemInfo);
|
|
122
|
+
this.emit('wirelessStatus', wirelessStatus);
|
|
123
|
+
this.emit('wirelessRadios', wirelessRadios);
|
|
124
|
+
this.emit('ssids', ssids);
|
|
125
125
|
|
|
126
126
|
if (this.firstRun) {
|
|
127
|
-
this.emit(
|
|
127
|
+
this.emit('success', `Connect success`);
|
|
128
128
|
this.firstRun = false;
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -134,11 +134,11 @@ class OpenWrt extends EventEmitter {
|
|
|
134
134
|
openWrtInfo.wirelessRadios = wirelessRadios;
|
|
135
135
|
openWrtInfo.ssids = ssids;
|
|
136
136
|
|
|
137
|
-
if (this.logDebug) this.emit(
|
|
137
|
+
if (this.logDebug) this.emit('debug', `OpenWrt Data: ${JSON.stringify(openWrtInfo, null, 2)}`);
|
|
138
138
|
return openWrtInfo;
|
|
139
139
|
} catch (error) {
|
|
140
|
-
if (this.logError) this.emit(
|
|
141
|
-
return
|
|
140
|
+
if (this.logError) this.emit('error', `Connect error: ${error.message}`);
|
|
141
|
+
return openWrtInfo;
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -146,29 +146,29 @@ class OpenWrt extends EventEmitter {
|
|
|
146
146
|
switch (type) {
|
|
147
147
|
case 'ssid':
|
|
148
148
|
await this.handleWithLock(async () => {
|
|
149
|
-
if (this.logDebug) this.emit(
|
|
149
|
+
if (this.logDebug) this.emit('debug', `${state ? 'Enabling' : 'Disabling'} SSID ${ssidName}`);
|
|
150
150
|
|
|
151
|
-
const status = await this.ubusCall(
|
|
151
|
+
const status = await this.ubusCall('network.wireless', 'status');
|
|
152
152
|
const iface = await this.functions.findIfaceBySsid(status, ssidName);
|
|
153
153
|
if (!iface) throw new Error(`SSID ${ssidName} not found`);
|
|
154
154
|
|
|
155
155
|
const section = iface.section;
|
|
156
156
|
if (!section) throw new Error(`No UCI section for SSID ${ssidName}`);
|
|
157
157
|
|
|
158
|
-
await this.ubusCall(
|
|
158
|
+
await this.ubusCall('uci', 'set',
|
|
159
159
|
{
|
|
160
|
-
config:
|
|
160
|
+
config: 'wireless',
|
|
161
161
|
section: section,
|
|
162
162
|
values: {
|
|
163
|
-
disabled: state ?
|
|
163
|
+
disabled: state ? '0' : '1'
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
);
|
|
167
167
|
|
|
168
|
-
await this.ubusCall(
|
|
169
|
-
await this.ubusCall(
|
|
168
|
+
await this.ubusCall('uci', 'commit', { config: 'wireless' });
|
|
169
|
+
await this.ubusCall('network.wireless', 'reload', {});
|
|
170
170
|
|
|
171
|
-
if (this.logDebug) this.emit(
|
|
171
|
+
if (this.logDebug) this.emit('debug', `Send SSID ${ssidName} ${state ? 'enabled' : 'disabled'}`);
|
|
172
172
|
});
|
|
173
173
|
break;
|
|
174
174
|
case 'switch':
|