diff --git a/README.md b/README.md
index 7c27292..3ff40af 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,15 @@ List of mesh nodes. Each mesh nodes has the following objects:
* parent_node_id: ID of the parent node
* signal_strength: Signal strength
+## wifi
+List of wifi networks and settings. Wifi settings can only be changed every 3s to avoid conflicting changes. Each mesh nodes has the following objects:
+
+* enable: Enable the wifi network (read/write)
+* enable_client_isolation: Enable client isolation (read/write)
+* hide_ssid: Hide the WIFI SSID (read/write)
+* mac_filter: Enable MAC filter (read)
+* schedule_enable: Enable schedule for network (read/write)
+
### Sentry
What is Sentry.io and what is reported to the servers of that company? `Sentry.io` is a service for developers to get an overview about errors from their applications. And exactly this is implemented in this adapter.
@@ -88,6 +97,9 @@ When the adapter crashes or another Code error happens, this error message that
## Changelog
+### **WORK IN PROGRESS**
+- Added new section for WIFI settings. Some settings can be changed via the adapter.
+
### 0.1.6 (2023-12-26)
- Account for different API versions
diff --git a/lib/objects.js b/lib/objects.js
index 0e26339..b8fa861 100644
--- a/lib/objects.js
+++ b/lib/objects.js
@@ -306,4 +306,66 @@ module.exports = {
native: {},
},
],
+ wifi: [
+ {
+ _id: 'enable',
+ type: 'state',
+ common: {
+ name: 'Wifi enabled',
+ type: 'boolean',
+ role: 'switch',
+ read: true,
+ write: true
+ },
+ native: {},
+ },
+ {
+ _id: 'enable_client_isolation',
+ type: 'state',
+ common: {
+ name: 'Client isolation enabled',
+ type: 'boolean',
+ role: 'switch',
+ read: true,
+ write: true
+ },
+ native: {},
+ },
+ {
+ _id: 'hide_ssid',
+ type: 'state',
+ common: {
+ name: 'Hide SSID enabled',
+ type: 'boolean',
+ role: 'switch',
+ read: true,
+ write: true
+ },
+ native: {},
+ },
+ {
+ _id: 'mac_filter',
+ type: 'state',
+ common: {
+ name: 'MAC filter enabled',
+ type: 'number',
+ role: 'value',
+ read: true,
+ write: false
+ },
+ native: {},
+ },
+ {
+ _id: 'schedule_enable',
+ type: 'state',
+ common: {
+ name: 'Schedule enabled',
+ type: 'boolean',
+ role: 'switch',
+ read: true,
+ write: true
+ },
+ native: {},
+ }
+ ],
};
\ No newline at end of file
diff --git a/lib/web_api.js b/lib/web_api.js
index 7f4259f..8e46904 100644
--- a/lib/web_api.js
+++ b/lib/web_api.js
@@ -166,10 +166,38 @@ class SrmClient {
this.sid = null;
}
+ /**
+ * Retrieve Wifi network settings
+ *
+ * @return {Promise} Network settings
+ */
+ async getWifiNetworkSettings () {
+ const data = {
+ method: 'get',
+ version: 1,
+ api: 'SYNO.Wifi.Network.Setting',
+ };
+ return (await this.request('/webapi/entry.cgi', data));
+ }
+
+ /**
+ * Update Wi-Fi settings
+ *
+ */
+ async setWifiNetworkSettings (profiles) {
+ const data = {
+ api: 'SYNO.Wifi.Network.Setting',
+ method: 'set',
+ version: 1,
+ profiles: JSON.stringify(profiles),
+ };
+ await this.request('/webapi/entry.cgi', data);
+ }
+
/**
* Retrieve WAN status
*
- * @return {Promise} Does WAN is ok
+ * @return {Promise} WAN status
*/
async getConnectionStatus () {
const data = {
diff --git a/main.js b/main.js
index f57b9bd..93bbad2 100644
--- a/main.js
+++ b/main.js
@@ -17,7 +17,6 @@ class Srm extends utils.Adapter {
name: 'srm',
});
this.on('ready', this.onReady.bind(this));
- // this.on('stateChange', this.onStateChange.bind(this));
// this.on('objectChange', this.onObjectChange.bind(this));
// this.on('message', this.onMessage.bind(this));
this.on('unload', this.onUnload.bind(this));
@@ -32,6 +31,7 @@ class Srm extends utils.Adapter {
this.stopTimer = null;
this.isStopping = false;
this.stopExecute = false;
+ this.skipExecute = false;
this.intervalId = null;
}
/**
@@ -56,10 +56,10 @@ class Srm extends utils.Adapter {
// Create traffic default states
await Promise.all(this.objects.devices.map(async o => {
- // @ts-ignore
- await this.setObjectNotExistsAsync('devices' + (o._id ? '.' + o._id : ''), o);
- this.log.debug('Create state for devices.' + o._id);
- }));
+ // @ts-ignore
+ await this.setObjectNotExistsAsync('devices' + (o._id ? '.' + o._id : ''), o);
+ this.log.debug('Create state for devices.' + o._id);
+ }));
// Create traffic default states
await Promise.all(this.objects.traffic.map(async o => {
@@ -82,6 +82,61 @@ class Srm extends utils.Adapter {
this.srmConnect();
}
+ /**
+ * Is called if a subscribed state changes
+ * @param {string} id
+ * @param {ioBroker.State | null | undefined} state
+ */
+ async onStateChange(id, state) {
+ if (!state || this.skipExecute) {
+ return;
+ }
+
+ if (id.includes('.wifi.') && state.ack === false) {
+ try {
+ this.skipExecute = true;
+
+ // Get wifi data
+ const wifiSettings = await this.client.getWifiNetworkSettings();
+ this.log.debug('Wifi settings: ${JSON.stringify(wifiSettings)}');
+
+ for (const profile of wifiSettings.profiles) {
+ const wifi_name = profile.radio_list[0].ssid.replace(this.FORBIDDEN_CHARS, '_');
+ // Change WIFI enable
+ if (id.endsWith('wifi.' + wifi_name + '.enable') && (profile.radio_list[0].enable != state.val)) {
+ profile.radio_list[0].enable = state.val;
+ await this.client.setWifiNetworkSettings(wifiSettings.profiles);
+ this.log.info('Wifi settings enable for ' + wifi_name + ' changed to ' + state.val);
+ }
+ // Change client isolation
+ if (id.endsWith('wifi.' + wifi_name + '.enable_client_isolation') && (profile.radio_list[0].enable_client_isolation != state.val)) {
+ profile.radio_list[0].enable_client_isolation = state.val;
+ await this.client.setWifiNetworkSettings(wifiSettings.profiles);
+ this.log.info('Wifi settings enable_client_isolation for ' + wifi_name + ' changed to ' + state.val);
+ }
+ // Change SSID visibility
+ if (id.endsWith('wifi.' + wifi_name + '.hide_ssid') && (profile.radio_list[0].hide_ssid != state.val)) {
+ profile.radio_list[0].hide_ssid = state.val;
+ await this.client.setWifiNetworkSettings(wifiSettings.profiles);
+ this.log.info('Wifi settings hide SSID ' + wifi_name + ' changed to ' + state.val);
+ }
+ // Change schedule enable
+ if (id.endsWith('wifi.' + wifi_name + '.schedule_enable') && (profile.radio_list[0].schedule.enable != state.val)) {
+ profile.radio_list[0].schedule.enable = state.val;
+ await this.client.setWifiNetworkSettings(wifiSettings.profiles);
+ this.log.info('Wifi settings schedule for ' + wifi_name + ' changed to ' + state.val);
+ }
+ }
+
+ // Wait for 3s before changing again or updating data
+ await new Promise(resolve => setTimeout(resolve, 3000));
+ this.skipExecute = false;
+ } catch (error) {
+ this.log.info('Wifi settings error for ' + id + ' error ' + error.message);
+ }
+ }
+ }
+
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
* @param {() => void} callback
@@ -138,6 +193,13 @@ class Srm extends utils.Adapter {
// Set connection indicator
this.setState('info.connection', true, true);
+ try {
+ // detect changes of objects
+ await this.subscribeStates('wifi.*');
+ } catch (error) {
+ this.log.error(`Cannot subscribe on object: ${error}`);
+ }
+
this.srmCyclicCall();
} catch (error) {
@@ -176,8 +238,14 @@ class Srm extends utils.Adapter {
if (this.stopExecute === false) {
this.srmUpdateData();
this.intervalId = this.setInterval(() => {
- this.srmUpdateData();
- }, this.config.interval*1000);
+ // Skip next update after changing wifi settings
+ if (this.skipExecute === false){
+ this.srmUpdateData();
+ }
+ else {
+ this.skipExecute = false;
+ }
+ }, this.config.interval * 1000);
}
}
}
@@ -207,12 +275,12 @@ class Srm extends utils.Adapter {
await this.setStateAsync('devices.online', { val: JSON.stringify(deviceOnline), ack: true });
// Get active WIFI device list
- const deviceOnlineWifi = deviceAll.filter(item => item.is_online === true && item.is_wireless === true);
+ const deviceOnlineWifi = deviceAll.filter(item => item.is_online === true && item.is_wireless === true);
this.log.debug('Device list WIFI online: ${JSON.stringify(deviceOnlineWifi)}');
await this.setStateAsync('devices.online_wifi', { val: JSON.stringify(deviceOnlineWifi), ack: true });
// Get active Ethernet device list
- const deviceOnlineEthernet = deviceAll.filter(item => item.is_online === true && item.is_wireless === false);
+ const deviceOnlineEthernet = deviceAll.filter(item => item.is_online === true && item.is_wireless === false);
this.log.debug('Device list Ethernet online: ${JSON.stringify(deviceOnlineEthernet)}');
await this.setStateAsync('devices.online_ethernet', { val: JSON.stringify(deviceOnlineEthernet), ack: true });
@@ -226,20 +294,39 @@ class Srm extends utils.Adapter {
await Promise.all(this.objects.mesh.map(async o => {
// @ts-ignore
await this.setObjectNotExistsAsync('mesh.' + node_name + (o._id ? '.' + o._id : ''), o);
- this.log.debug('Create state for mesh' + node_name.replace(this.FORBIDDEN_CHARS, '_') + '.' + o._id);
+ this.log.debug('Create state for mesh ' + node_name.replace(this.FORBIDDEN_CHARS, '_') + '.' + o._id);
}));
await this.setStateAsync('mesh.' + node_name + '.band', { val: node.band, ack: true });
await this.setStateAsync('mesh.' + node_name + '.connected_devices', { val: node.connected_devices, ack: true });
- await this.setStateAsync('mesh.' + node_name + '.current_rate_rx', { val: node.current_rate_rx/1000, ack: true });
- await this.setStateAsync('mesh.' + node_name + '.current_rate_tx', { val: node.current_rate_tx/1000, ack: true });
+ await this.setStateAsync('mesh.' + node_name + '.current_rate_rx', { val: node.current_rate_rx / 1000, ack: true });
+ await this.setStateAsync('mesh.' + node_name + '.current_rate_tx', { val: node.current_rate_tx / 1000, ack: true });
await this.setStateAsync('mesh.' + node_name + '.network_status', { val: node.network_status, ack: true });
await this.setStateAsync('mesh.' + node_name + '.node_id', { val: node.node_id, ack: true });
await this.setStateAsync('mesh.' + node_name + '.node_status', { val: node.node_status, ack: true });
await this.setStateAsync('mesh.' + node_name + '.parent_node_id', { val: node.parent_node_id, ack: true });
await this.setStateAsync('mesh.' + node_name + '.signal_strength', { val: node.signalstrength, ack: true });
}
- // await this.setStateAsync('traffic.live', { val: JSON.stringify(trafficLive), ack: true });
+
+ // Get wifi data
+ const wifiSettings = await this.client.getWifiNetworkSettings();
+ this.log.debug('Wifi settings: ${JSON.stringify(wifiSettings)}');
+
+ for (const profile of wifiSettings.profiles) {
+ const wifi_name = profile.radio_list[0].ssid.replace(this.FORBIDDEN_CHARS, '_');
+ // Create mesh node default states
+ await Promise.all(this.objects.wifi.map(async o => {
+ // @ts-ignore
+ await this.setObjectNotExistsAsync('wifi.' + wifi_name + (o._id ? '.' + o._id : ''), o);
+ this.log.debug('Create state for wifi ' + wifi_name.replace(this.FORBIDDEN_CHARS, '_') + '.' + o._id);
+ }));
+
+ await this.setStateAsync('wifi.' + wifi_name + '.enable', { val: profile.radio_list[0].enable, ack: true });
+ await this.setStateAsync('wifi.' + wifi_name + '.enable_client_isolation', { val: profile.radio_list[0].enable_client_isolation, ack: true });
+ await this.setStateAsync('wifi.' + wifi_name + '.hide_ssid', { val: profile.radio_list[0].hide_ssid, ack: true });
+ await this.setStateAsync('wifi.' + wifi_name + '.mac_filter', { val: profile.radio_list[0].mac_filter.profile_id, ack: true });
+ await this.setStateAsync('wifi.' + wifi_name + '.schedule_enable', { val: profile.radio_list[0].schedule.enable, ack: true });
+ }
// Get live traffic
const trafficLive = await this.client.getTraffic('live');
@@ -251,13 +338,15 @@ class Srm extends utils.Adapter {
// this.log.debug('Daily traffic: ${JSON.stringify(trafficDaily)}');
// await this.setStateAsync('traffic.daily', { val: JSON.stringify(trafficDaily), ack: true });
+ this.on('stateChange', this.onStateChange.bind(this));
+
} catch (error) {
if (String(error) === 'Error: Not connected') {
this.log.error('Router is not connected, try new reconnect in 90s');
this.stopExecute = true;
this.srmReconnect();
} else {
- this.log.error(error);
+ this.log.error('Error updating data: ' + error.message);
this.stopExecute = true;
}
}
@@ -281,4 +370,4 @@ if (require.main !== module) {
} else {
// otherwise start the instance directly
new Srm();
-}
\ No newline at end of file
+}