-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.js
403 lines (362 loc) · 16.7 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
// ---------------------------------------------------------------------------------------------
// ioBroker Synology router SRM adapter
// ---------------------------------------------------------------------------------------------
'use strict';
// iobroker core
const utils = require('@iobroker/adapter-core');
class Srm extends utils.Adapter {
/**
* @param [options]
*/
constructor(options) {
super({
...options,
name: 'srm',
});
this.on('ready', this.onReady.bind(this));
// this.on('objectChange', this.onObjectChange.bind(this));
// this.on('message', this.onMessage.bind(this));
this.on('unload', this.onUnload.bind(this));
// SRM connection
this.SrmClient = require(`${__dirname}/lib/web_api.js`).SrmClient;
this.client = new this.SrmClient();
// iobroker objects
this.objects = require('./lib/objects');
// Define variables
this.stopTimer = null;
this.isStopping = false;
this.stopExecute = false;
this.skipExecute = false;
this.intervalId = null;
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
// Reset the connection indicator during startup
this.setState('info.connection', false, true);
// Create router default states
await Promise.all(
this.objects.router.map(async o => {
await this.setObjectNotExistsAsync(`router${o._id ? `.${o._id}` : ''}`, o);
this.log.debug(`Create state for router.${o._id}`);
}),
);
// Create traffic default states
await Promise.all(
this.objects.devices.map(async o => {
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 => {
await this.setObjectNotExistsAsync(`traffic${o._id ? `.${o._id}` : ''}`, o);
this.log.debug(`Create state for traffic.${o._id}`);
}),
);
// Validate IP address
const checkRouterAddress = this.validateIPaddress(this.config.IP);
if (!checkRouterAddress) {
this.log.error(`The server address ${this.config.IP} is not a valid IP-Address`);
this.srmStop();
return;
}
// Force polling minimum to 60 seconds
if (this.config.interval < 60) {
this.config.interval = 60;
}
this.srmConnect();
}
/**
* Is called if a subscribed state changes
*
* @param id
* @param 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 callback
*/
async onUnload(callback) {
try {
if (this.intervalId) {
this.clearInterval(this.intervalId);
this.intervalId = null;
}
await this.client.logout();
this.isStopping = true;
callback();
} catch {
callback();
}
}
// ---------------------------------------------------------------------------------------------
// Connect to Synology router
async srmConnect() {
try {
// Create base URL
const baseUrl = `https://${this.config.IP}:${this.config.port}`;
this.log.debug(`Connecting to router ${baseUrl}`);
// Login to router
await this.client.authenticate(baseUrl, null, { timeout: 5000 }, this.config.user, this.config.password);
this.log.info('Connection to router is ready, starting device checking');
this.stopExecute = false;
// 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) {
this.log.error(`${error.message} for ${this.config.IP}`);
if (error.message === 'Request timeout') {
this.srmReconnect();
} else {
this.srmStop();
}
return;
}
}
// ---------------------------------------------------------------------------------------------
// Reconnect to Synology router
async srmReconnect() {
this.client = null;
this.client = new this.SrmClient();
this.log.info('Try to reconnect in 60s ...');
this.setTimeout(async () => {
this.srmConnect();
}, 60000);
}
// ---------------------------------------------------------------------------------------------
// Stop communication with Synology router
async srmStop() {
if (this.stopTimer) {
this.clearTimeout(this.stopTimer);
}
}
// ---------------------------------------------------------------------------------------------
// Run in circles until stopped
srmCyclicCall() {
if (this.stopTimer) {
this.clearTimeout(this.stopTimer);
}
if (!this.isStopping) {
if (this.stopExecute === false) {
this.srmUpdateData();
this.intervalId = this.setInterval(() => {
// Skip next update after changing wifi settings
if (this.skipExecute === false) {
this.srmUpdateData();
} else {
this.skipExecute = false;
}
}, this.config.interval * 1000);
}
}
}
// ---------------------------------------------------------------------------------------------
// Get data from Synology router
async srmUpdateData() {
this.log.info('Polling data from router ${this.config.IP}');
try {
// Get connection status
const conStatus = await this.client.getConnectionStatus();
this.log.debug('Connection status: ${JSON.stringify(conStatus)}');
await this.setStateAsync('router.IPV4_status', { val: conStatus.ipv4.conn_status, ack: true });
await this.setStateAsync('router.IPV4_IP', { val: conStatus.ipv4.ip, ack: true });
await this.setStateAsync('router.IPV6_status', { val: conStatus.ipv6.conn_status, ack: true });
await this.setStateAsync('router.IPV6_IP', { val: conStatus.ipv6.ip, ack: true });
// Get complete device list
const deviceAll = await this.client.getAllDevices();
this.log.debug('Device list all: ${JSON.stringify(deviceAll)}');
await this.setStateAsync('devices.all', { val: JSON.stringify(deviceAll), ack: true });
// Get active device list
const deviceOnline = deviceAll.filter(item => item.is_online === true);
this.log.debug('Device list online: ${JSON.stringify(deviceOnline)}');
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);
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,
);
this.log.debug('Device list Ethernet online: ${JSON.stringify(deviceOnlineEthernet)}');
await this.setStateAsync('devices.online_ethernet', {
val: JSON.stringify(deviceOnlineEthernet),
ack: true,
});
// Get mesh node data
const meshNodes = await this.client.getMeshNodes();
this.log.debug('Mesh nodes: ${JSON.stringify(meshNodes)}');
await this.setStateAsync('devices.mesh', { val: JSON.stringify(meshNodes), ack: true });
for (const node of meshNodes) {
const node_name = node.name.replace(this.FORBIDDEN_CHARS, '_');
// Create mesh node default states
await Promise.all(
this.objects.mesh.map(async o => {
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}`,
);
}),
);
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}.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,
});
}
// 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 => {
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');
this.log.debug('Live traffic: ${JSON.stringify(trafficLive)}');
await this.setStateAsync('traffic.live', { val: JSON.stringify(trafficLive), ack: true });
// Get daily traffic
// const trafficDaily = await client.getTraffic('day');
// 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 updating data: ${error.message}`);
this.stopExecute = true;
}
}
}
// ---------------------------------------------------------------------------------------------
// Validate IP address
validateIPaddress(inputText) {
const ipformat =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return !!inputText.match(ipformat);
}
}
if (require.main !== module) {
// Export the constructor in compact mode
/**
* @param [options]
*/
module.exports = options => new Srm(options);
} else {
// otherwise start the instance directly
new Srm();
}