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.
@@ -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[0]].accessPoint.enable === true;"
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[0]].accessPoint.enable === true;"
122
+ "functionBody": "return model.devices[arrayIndices].accessPoint.enable === true;"
123
123
  }
124
124
  },
125
125
  "sensor": {
126
- "title": "Enable",
126
+ "title": "Sensor",
127
127
  "type": "boolean",
128
- "description": "Here Enable/Disable sensor for every ssid.",
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.sensors"
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 config of config.devices) {
30
- const { name, host, port, displayType } = config;
31
- if (!name || !host || !port || !displayType) {
32
- 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`);
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: config.log?.deviceInfo,
39
- success: config.log?.success,
40
- info: config.log?.info,
41
- warn: config.log?.warn,
42
- error: config.log?.error,
43
- debug: config.log?.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
- ...config,
49
+ ...deviceConfig,
50
50
  auth: {
51
- ...config.auth,
51
+ ...deviceConfig.auth,
52
52
  passwd: 'removed',
53
53
  },
54
54
  mqtt: {
55
55
  auth: {
56
- ...config.mqtt?.auth,
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 = (config.refreshInterval ?? 5) * 1000;
65
- if (config.accessPoint?.enable) this.devices.push('accessPoint');
66
- if (config.switch?.enable) this.devices.push('switch');
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(config)
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, config, openWrt, openWrtInfo); break;
94
- case 'switch': type = new Switch(api, config, openWrt, openWrtInfo); break;
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 zone: ${zoneControl}`);
96
+ if (logLevel.warn) log.warn(`Device: ${host} ${name}, unknown device: ${device}`);
97
97
  return;
98
98
  }
99
99
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "OpenWrt Control",
3
3
  "name": "homebridge-openwrt-control",
4
- "version": "0.0.2-beta.0",
4
+ "version": "0.0.2-beta.10",
5
5
  "description": "Homebridge plugin to control OpenWrt flashed devices.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
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
- //denon client
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 axios from "axios";
3
- import Functions from './functions.js';
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
- //external integration
17
- this.restFulEnabled = config.restFul?.enable || false;
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
- async login() {
59
- const now = Date.now();
60
- if (this.sessionId && now < this.sessionExpiresAt) {
61
- return this.sessionId;
62
- }
63
-
64
- const response = await this.axiosInstance.post("", {
65
- jsonrpc: "2.0",
66
- id: 1,
67
- method: "call",
68
- params: ["00000000000000000000000000000000", "session", "login", { username: this.user, password: this.passwd }]
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 session = await this.login();
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
- const systemInfo = await this.ubusCall("system", "board");
103
- const wirelessStatus = await this.ubusCall("network.wireless", "status");
104
- if (this.logDebug) this.emit("debug", `Status data: ${JSON.stringify(wirelessStatus, null, 2)}`);
105
-
106
- const wirelessRadios = Object.values(status.radios).map(radio => ({
107
- name: radio.name,
108
- state: radio.up === true,
109
- interfaces: Object.values(radio.interfaces).map(i => ({
110
- name: i.ssid,
111
- state: i.up === true,
112
- mode: i.mode
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
- const ssids = openWrtInfo.wirelessRadios.flatMap(radio => radio.interfaces);
117
- this.emit("systemInfo", systemInfo);
118
- this.emit("wirelessStatus", wirelessStatus);
119
- this.emit("wirelessRadios", wirelessRadios);
120
- this.emit("ssids", ssids);
97
+ openWrtInfo.state = true;
98
+ openWrtInfo.systemInfo = systemInfo;
99
+ openWrtInfo.wirelessStatus = wirelessStatus;
100
+ openWrtInfo.wirelessRadios = wirelessRadios;
101
+ openWrtInfo.ssids = ssids;
121
102
 
122
- if (this.firstRun) {
123
- this.emit("success", `Connect success`);
124
- this.firstRun = false;
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 'ssid':
114
+ case "ssid":
140
115
  await this.handleWithLock(async () => {
141
- if (this.logDebug) this.emit("debug", `${state ? "Enabling" : "Disabling"} SSID ${ssidName}`);
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
- config: "wireless",
153
- section: section,
154
- values: {
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("success", `SSID ${ssidName} ${state ? "enabled" : "disabled"}`);
133
+ if (this.logDebug) this.emit("debug", `[${this.host}] SSID ${ssidName} ${state ? "enabled" : "disabled"}`);
164
134
  });
165
135
  break;
166
- case 'switch':
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', `Release: ${this.openWrtInfo.systemInfo.release?.description}`);
198
+ this.emit('devInfo', `Firmware: ${this.openWrtInfo.systemInfo.release?.description}`);
199
199
  this.emit('devInfo', `----------------------------------`);
200
200
 
201
- //denon client
201
+ //openwrt client
202
202
  this.openWrt.on('systemInfo', (info) => {
203
- this.informationService?.updateCharacteristic(Characteristic.FirmwareRevision, info.release?.version);
203
+ this.informationService?.updateCharacteristic(Characteristic.FirmwareRevision, info.release?.description);
204
204
  })
205
205
  .on('switchStatus', async (status) => {
206
206
  })