homebridge-openwrt-control 0.0.2-beta.1 → 0.0.2-beta.10
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 +5 -5
- package/index.js +20 -20
- package/package.json +1 -1
- package/src/functions.js +0 -24
- package/src/openwrt.js +42 -79
package/config.schema.json
CHANGED
|
@@ -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,37 +1,23 @@
|
|
|
1
1
|
import EventEmitter from "events";
|
|
2
|
-
import
|
|
3
|
-
import Functions from
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import Functions from "./functions.js";
|
|
4
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.name = config.name;
|
|
11
10
|
this.host = config.host;
|
|
12
|
-
this.user = config.user;
|
|
13
|
-
this.passwd = config.passwd;
|
|
11
|
+
this.user = config.auth?.user || "root";
|
|
12
|
+
this.passwd = config.auth?.passwd || "";
|
|
13
|
+
|
|
14
14
|
this.logError = config.log?.error;
|
|
15
15
|
this.logDebug = config.log?.debug;
|
|
16
16
|
|
|
17
|
-
//external integration
|
|
18
|
-
this.restFulEnabled = config.restFul?.enable || false;
|
|
19
|
-
this.mqttEnabled = config.mqtt?.enable || false;
|
|
20
|
-
|
|
21
17
|
this.firstRun = true;
|
|
22
18
|
this.lock = false;
|
|
23
19
|
|
|
24
|
-
this.sessionId = null;
|
|
25
|
-
this.sessionExpiresAt = 0;
|
|
26
|
-
|
|
27
20
|
this.functions = new Functions();
|
|
28
|
-
this.axiosInstance = axios.create({
|
|
29
|
-
baseURL: `${config.host}/ubus`,
|
|
30
|
-
timeout: 5000,
|
|
31
|
-
headers: {
|
|
32
|
-
"Content-Type": "application/json"
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
21
|
|
|
36
22
|
this.impulseGenerator = new ImpulseGenerator()
|
|
37
23
|
.on("connect", () => this.handleWithLock(async () => {
|
|
@@ -45,69 +31,48 @@ class OpenWrt extends EventEmitter {
|
|
|
45
31
|
async handleWithLock(fn) {
|
|
46
32
|
if (this.lock) return;
|
|
47
33
|
this.lock = true;
|
|
48
|
-
|
|
49
34
|
try {
|
|
50
35
|
await fn();
|
|
51
36
|
} catch (error) {
|
|
52
|
-
this.emit("error", `Impulse generator error: ${error.message}`
|
|
53
|
-
);
|
|
37
|
+
this.emit("error", `Impulse generator error: ${error.message}`);
|
|
54
38
|
} finally {
|
|
55
39
|
this.lock = false;
|
|
56
40
|
}
|
|
57
41
|
}
|
|
58
42
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
// --- Helper do wywołań SSH z wyłączeniem StrictHostKeyChecking ---
|
|
44
|
+
async sshCall(command) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const sshCmd = `ssh -o StrictHostKeyChecking=no ${this.user}@${this.host} '${command.replace(/'/g, `'\\''`)}'`;
|
|
47
|
+
exec(sshCmd, (error, stdout, stderr) => {
|
|
48
|
+
if (error) return reject(new Error(stderr || error.message));
|
|
49
|
+
try {
|
|
50
|
+
const json = JSON.parse(stdout);
|
|
51
|
+
resolve(json);
|
|
52
|
+
} catch (parseError) {
|
|
53
|
+
reject(new Error(`Invalid JSON from SSH: ${parseError.message}`));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
70
56
|
});
|
|
71
|
-
|
|
72
|
-
const result = response.data?.result?.[1];
|
|
73
|
-
if (!result?.ubus_rpc_session) {
|
|
74
|
-
throw new Error("ubus login failed");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
this.sessionId = result.ubus_rpc_session;
|
|
78
|
-
this.sessionExpiresAt = now + 240_000;
|
|
79
|
-
|
|
80
|
-
if (this.logDebug) this.emit("debug", `Ubus login OK`);
|
|
81
|
-
return this.sessionId;
|
|
82
57
|
}
|
|
83
58
|
|
|
59
|
+
// --- ubus call przez SSH ---
|
|
84
60
|
async ubusCall(service, method, params = {}) {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const response = await this.axiosInstance.post("", {
|
|
88
|
-
jsonrpc: "2.0",
|
|
89
|
-
id: 2,
|
|
90
|
-
method: "call",
|
|
91
|
-
params: [session, service, method, params]
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
if (response.data?.error) {
|
|
95
|
-
throw new Error(response.data.error.message || "ubus call error");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return response.data.result[1];
|
|
61
|
+
const cmd = `ubus call ${service} ${method} '${JSON.stringify(params)}'`;
|
|
62
|
+
return await this.sshCall(cmd);
|
|
99
63
|
}
|
|
100
64
|
|
|
65
|
+
// --- Pobranie info i statusu ---
|
|
101
66
|
async connect() {
|
|
67
|
+
const openWrtInfo = { state: false, systemInfo: {}, wirelessStatus: {}, wirelessRadios: [], ssids: [] };
|
|
102
68
|
try {
|
|
103
|
-
const openWrtInfo = { state: false, systemInfo: {}, wirelessStatus: {}, wirelessRadios: [], ssids: [] }
|
|
104
69
|
const systemInfo = await this.ubusCall("system", "board");
|
|
105
|
-
if (this.logDebug) this.emit("debug", `System info
|
|
70
|
+
if (this.logDebug) this.emit("debug", `[${this.host}] System info: ${JSON.stringify(systemInfo, null, 2)}`);
|
|
106
71
|
|
|
107
72
|
const wirelessStatus = await this.ubusCall("network.wireless", "status");
|
|
108
|
-
if (this.logDebug) this.emit("debug", `Wireless status
|
|
73
|
+
if (this.logDebug) this.emit("debug", `[${this.host}] Wireless status: ${JSON.stringify(wirelessStatus, null, 2)}`);
|
|
109
74
|
|
|
110
|
-
const wirelessRadios = Object.values(
|
|
75
|
+
const wirelessRadios = Object.values(wirelessStatus.radios).map(radio => ({
|
|
111
76
|
name: radio.name,
|
|
112
77
|
state: radio.up === true,
|
|
113
78
|
interfaces: Object.values(radio.interfaces).map(i => ({
|
|
@@ -118,6 +83,7 @@ class OpenWrt extends EventEmitter {
|
|
|
118
83
|
}));
|
|
119
84
|
|
|
120
85
|
const ssids = wirelessRadios.flatMap(radio => radio.interfaces);
|
|
86
|
+
|
|
121
87
|
this.emit("systemInfo", systemInfo);
|
|
122
88
|
this.emit("wirelessStatus", wirelessStatus);
|
|
123
89
|
this.emit("wirelessRadios", wirelessRadios);
|
|
@@ -134,19 +100,20 @@ class OpenWrt extends EventEmitter {
|
|
|
134
100
|
openWrtInfo.wirelessRadios = wirelessRadios;
|
|
135
101
|
openWrtInfo.ssids = ssids;
|
|
136
102
|
|
|
137
|
-
if (this.logDebug) this.emit("debug", `OpenWrt Data: ${JSON.stringify(openWrtInfo, null, 2)}`);
|
|
103
|
+
if (this.logDebug) this.emit("debug", `[${this.host}] OpenWrt Data: ${JSON.stringify(openWrtInfo, null, 2)}`);
|
|
138
104
|
return openWrtInfo;
|
|
139
105
|
} catch (error) {
|
|
140
|
-
if (this.logError) this.emit("error", `Connect error: ${error.message}`);
|
|
141
|
-
return
|
|
106
|
+
if (this.logError) this.emit("error", `[${this.host}] Connect error: ${error.message}`);
|
|
107
|
+
return openWrtInfo;
|
|
142
108
|
}
|
|
143
109
|
}
|
|
144
110
|
|
|
111
|
+
// --- Włączanie / wyłączanie SSID ---
|
|
145
112
|
async send(type, ssidName, state) {
|
|
146
113
|
switch (type) {
|
|
147
|
-
case
|
|
114
|
+
case "ssid":
|
|
148
115
|
await this.handleWithLock(async () => {
|
|
149
|
-
if (this.logDebug) this.emit("debug",
|
|
116
|
+
if (this.logDebug) this.emit("debug", `[${this.host}] ${state ? "Enabling" : "Disabling"} SSID ${ssidName}`);
|
|
150
117
|
|
|
151
118
|
const status = await this.ubusCall("network.wireless", "status");
|
|
152
119
|
const iface = await this.functions.findIfaceBySsid(status, ssidName);
|
|
@@ -155,23 +122,18 @@ class OpenWrt extends EventEmitter {
|
|
|
155
122
|
const section = iface.section;
|
|
156
123
|
if (!section) throw new Error(`No UCI section for SSID ${ssidName}`);
|
|
157
124
|
|
|
158
|
-
await this.ubusCall("uci", "set",
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
disabled: state ? "0" : "1"
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
);
|
|
167
|
-
|
|
125
|
+
await this.ubusCall("uci", "set", {
|
|
126
|
+
config: "wireless",
|
|
127
|
+
section: section,
|
|
128
|
+
values: { disabled: state ? "0" : "1" }
|
|
129
|
+
});
|
|
168
130
|
await this.ubusCall("uci", "commit", { config: "wireless" });
|
|
169
131
|
await this.ubusCall("network.wireless", "reload", {});
|
|
170
132
|
|
|
171
|
-
if (this.logDebug) this.emit("debug", `
|
|
133
|
+
if (this.logDebug) this.emit("debug", `[${this.host}] SSID ${ssidName} ${state ? "enabled" : "disabled"}`);
|
|
172
134
|
});
|
|
173
135
|
break;
|
|
174
|
-
case
|
|
136
|
+
case "switch":
|
|
175
137
|
break;
|
|
176
138
|
}
|
|
177
139
|
}
|
|
@@ -179,3 +141,4 @@ class OpenWrt extends EventEmitter {
|
|
|
179
141
|
|
|
180
142
|
export default OpenWrt;
|
|
181
143
|
|
|
144
|
+
|