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 +}