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