UNPKG

26.7 kBJavaScriptView Raw
1/* jshint -W097 */
2/* jshint -W030 */
3/* jshint strict:true */
4/* jslint node: true */
5/* jslint esversion: 6 */
6'use strict';
7
8
9const Shelly = require('shelly-iot');
10const ping = require('ping');
11const tcpp = require('tcp-ping');
12const request = require('request');
13const datapoints = require(__dirname + '/datapoints');
14
15function sleep(ms) {
16 return new Promise((resolve) => setTimeout(resolve, ms));
17}
18
19function isAsync(funct) {
20 if (funct && funct.constructor) return funct.constructor.name == 'AsyncFunction';
21 return undefined;
22}
23
24/**
25 * ping host
26 * @param {string} host - hostname or ip-address
27 */
28function pingAsyncOld(host) {
29 return new Promise((resolve, reject) => {
30 ping.sys.probe(host, (isAlive) => {
31 if (isAlive) {
32 resolve(true);
33 } else {
34 resolve(false);
35 }
36 });
37 });
38}
39
40/**
41 * Ping host with tcp
42 * @param {*} host - hostname like www.google.de or 192.168.20.1
43 * @param {*} port - 80 or 443
44 */
45function pingAsync(host, port) {
46 if (!port) port = 80;
47 return new Promise((resolve, reject) => {
48 tcpp.probe(host, port, (error, isAlive) => {
49 resolve(isAlive);
50 });
51 });
52}
53
54function requestAsync(url) {
55 return new Promise((resolve, reject) => {
56 request(url, (error, res, body) => {
57 if (!error && body) {
58 resolve(body);
59 } else {
60 reject(error);
61 }
62 });
63 });
64}
65
66function recursiveSubStringReplace(source, pattern, replacement) {
67 function recursiveReplace(objSource) {
68 switch (typeof objSource) {
69 case 'string':
70 return objSource.replace(pattern, replacement);
71 case 'object':
72 if (objSource === null) {
73 return null;
74 }
75 Object.keys(objSource).forEach(function (property) {
76 objSource[property] = recursiveReplace(objSource[property]);
77 });
78 return objSource;
79 default:
80 return objSource;
81 }
82 }
83 return recursiveReplace(source);
84}
85
86/**
87 * get the CoAP value by key
88 * @param {object/string} key - like 112 or '112' or [11,12] or ['11','12']
89 * @param {array} array - [[0,111,0],[0,112,1]]
90 */
91function getCoapValue(objkey, payload) {
92 let isArray = (typeof objkey !== 'string' && typeof objkey !== 'number');
93 if (!isArray) {
94 let key = Number(objkey);
95 let index = payload.findIndex((item) => item[1] === key);
96 return index >= 0 ? payload[index][2] : undefined;
97 } else {
98 let ret = [];
99 for (let i in objkey) {
100 let key = Number(objkey[i]);
101 let index = payload.findIndex((item) => item[1] === key);
102 if (index >= 0) { ret.push(payload[index][2]); } else { ret.push(undefined); }
103 }
104 return ret;
105 }
106}
107
108
109function descrToSensor(description) {
110 let sensors = {};
111 if (description && description.sen) {
112 for (let i in description.sen) {
113 try {
114 let sensor = description.sen[i];
115 let descr = sensor.D;
116 let key = sensor.I;
117 if (descr) sensors[descr.toLowerCase()] = key;
118 } catch (error) {
119 //
120 }
121 }
122 }
123 return sensors;
124}
125
126class CoAPClient {
127 constructor(adapter, objectHelper, eventEmitter, shelly, devicename, ip, payload, description) {
128 this.getOldDeivceInfo(devicename);
129 this.active = true;
130 this.adapter = adapter;
131 this.objectHelper = objectHelper;
132 this.eventEmitter = eventEmitter;
133 this.shelly = shelly;
134 this.devicename = devicename;
135 this.ip = ip;
136 this.states = {};
137 this.device = {};
138 this.http = {};
139 this.auth;
140 this.polltime = this.adapter.config.polltime * 1000 || 5000;
141 this.id;
142 this.devicetype;
143 this.deviceid;
144 this.serialid;
145 this.deviceexist;
146 this.httptimeout = 5 * 1000;
147 this.description = description;
148 this.sensors = descrToSensor(description);
149 if (this.adapter.config.httpusername && this.adapter.config.httppassword && this.adapter.config.httpusername.length > 0 && this.adapter.config.httppassword.length > 0)
150 this.auth = 'Basic ' + Buffer.from(this.adapter.config.httpusername + ':' + this.adapter.config.httppassword).toString('base64');
151 this.start(payload, description);
152 }
153
154 /**
155 * Change Device ID from #2 to #1 in fimrware verson >= 1.8
156 * Example SHRGBW2#1234#1 will change to SHRGBW2#1234#2
157 */
158 getOldDeivceInfo(devicename) {
159 // if(devicename) return devicename.split('#').slice(0, 2).join() + '#1';
160 if (devicename && devicename.substr(-2) === '#2') {
161 return devicename.substr(0, devicename.length - 2) + '#1';
162 } else {
163 return devicename;
164 }
165 }
166
167 /**
168 * Get IP of device back. For example
169 * 192.168.1.2
170 */
171 getIP() {
172 return this.ip;
173 }
174
175 /**
176 * Get the ID of the Shelly Device. For example: shellyplug-s-12345
177 */
178 getId() {
179 if (!this.id) {
180 let devicetype = datapoints.getDeviceNameForCoAP(this.getDeviceType());
181 let serialid = this.getSerialId();
182 if (devicetype && serialid) this.id = devicetype + '-' + serialid;
183 }
184 return this.id;
185 }
186
187 /**
188 * Get the Shelly Device type with the serialnumber of the device back.
189 * Device type could be for example SHRGBW2. The serial number of the
190 * device like 1234 will be added
191 * Example: SHRGBW2#1234#1
192 */
193 getDeviceName() {
194 return this.getOldDeivceInfo(this.devicename);
195 }
196
197 /**
198 * Get the Shelly Device type without serialnumber of the device back.
199 * Example: SHRGBW2
200 */
201 getDeviceType() {
202 if (!this.devicetype) {
203 let devicename = this.getDeviceName();
204 if (typeof devicename === 'string') {
205 this.devicetype = devicename.split('#').slice(0, 1).join();
206 }
207 }
208 return this.devicetype;
209 }
210
211 /**
212 * Get the deviceid back without serial number.
213 * For example, you get shellyplug-s back
214 */
215 getDeviceId() {
216 if (!this.deviceid) {
217 this.deviceid = datapoints.getDeviceNameForCoAP(this.getDeviceType());
218 }
219 return this.deviceid;
220 }
221
222 /**
223 * Get the serialid back without devicename.
224 * For example, you get 12345 for shellyplug-s-12345 back
225 */
226 getSerialId() {
227 if (!this.serialid) {
228 let devicename = this.getDeviceName();
229 if (typeof devicename === 'string') {
230 let devicetype = devicename.split('#');
231 if (devicetype) this.serialid = devicetype[1];
232 }
233 }
234 return this.serialid;
235 }
236
237 /**
238 * Checks if Shelly device type in the configuration exist. If missing
239 * you have to add a configuration in the ./lib/devices direcotory
240 */
241 deviceExist() {
242 if (this.deviceexist === undefined) {
243 let deviceid = this.getDeviceId();
244 this.deviceexist = datapoints.getDeviceNameForMQTT(deviceid) ? true : false;
245 }
246 return this.deviceexist;
247 }
248
249 /**
250 * Returns a string for Logging with the IP address and name of Shelly Device and type
251 */
252 getName() {
253 let name = this.getDeviceName(); // SHRGBW2#1234#1
254 let ip = this.getIP(); // 192.168.11.1
255 let deviceid = this.getDeviceId(); // shellyplug-s-12345
256 let id = this.getId(); // shellyplug-s-12345
257 return ip + ' (' + deviceid + ' / ' + id + ' / ' + name + ')';
258 }
259
260 /**
261 * Cleanup, destroy this object
262 */
263 destroy() {
264 if (this.active) {
265 this.adapter.log.info('Destroy ' + this.getName());
266 this.active = false;
267 clearTimeout(this.timerid);
268 clearTimeout(this.autoupdateid);
269 this.devicename = undefined;
270 this.http = {};
271 this.states = {};
272 this.device = {};
273 this.ip = undefined;
274 this.id = undefined;
275 this.devicetype = undefined;
276 this.deviceid = undefined;
277 this.serialid = undefined;
278 this.deviceexist = undefined;
279 this.description = undefined;
280 if (this.listenerus) this.shelly.removeListener('update-device-status', this.listenerus);
281 if (this.listenerds) this.shelly.removeListener('device-connection-status', this.listenerds);
282 }
283 }
284
285 /**
286 * delete old states in objects unter shelly.X.
287 * For example if the configuration for the device change
288 */
289 async deleteOldStates() {
290 let id = this.adapter.namespace + '.' + this.getDeviceName();
291 let obj = await this.adapter.getAdapterObjectsAsync();
292 let devicetype = datapoints.getDeviceNameForCoAP(this.getDeviceType());
293 let dps = datapoints.getAll('coap');
294 dps = dps[devicetype];
295 if (dps) {
296 for (let i in obj) {
297 let tmpid = obj[i];
298 if (tmpid && tmpid._id && tmpid.type) {
299 let stateid = tmpid._id.replace(id + '.', '');
300 if (tmpid.type === 'state' && tmpid._id.startsWith(id)) {
301 if (!dps[stateid]) {
302 try {
303 await this.adapter.delObjectAsync(tmpid._id);
304 delete obj[tmpid._id];
305 this.adapter.log.info('Delete old state: ' + tmpid._id);
306 } catch (error) {
307 this.adapter.log.error('Cound not delete old state: ' + tmpid._id);
308 }
309 }
310 }
311 }
312 }
313 }
314 // delete empty channels
315 for (let i in obj) {
316 let tmpidi = obj[i];
317 if (tmpidi && tmpidi.type && tmpidi._id && tmpidi.type === 'channel') {
318 let found = false;
319 for (let j in obj) {
320 let tmpidj = obj[j];
321 if (!tmpidj) {
322 continue;
323 }
324 if (tmpidj && tmpidj.type && tmpidj._id && tmpidj.type === 'state' && tmpidj._id.startsWith(tmpidi._id)) {
325 found = true;
326 break;
327 }
328 }
329 if (found === false) {
330 try {
331 this.adapter.log.info('Delete old channel: ' + tmpidi._id);
332 await this.adapter.delObjectAsync(tmpidi._id);
333 delete obj[tmpidi._id];
334 } catch (error) {
335 this.adapter.log.error('Could not delete old channel: ' + tmpidi._id);
336 }
337 }
338 }
339 }
340 }
341
342 /**
343 * Create objects unter shelly.0 for a new shelly device
344 * The Shelly device has to exist in the ./lib/devices/ directory
345 */
346 createObjects() {
347 if (Object.keys(this.device).length === 0) {
348 let devicetype = datapoints.getDeviceNameForCoAP(this.getDeviceType());
349 let devices = datapoints.getDeviceByType(devicetype, 'coap');
350 if (devices) {
351 devices = recursiveSubStringReplace(devices, new RegExp('<devicetype>', 'g'), devicetype);
352 devices = recursiveSubStringReplace(devices, new RegExp('<deviceid>', 'g'), this.getSerialId());
353 for (let j in devices) {
354 let statename = j;
355 let state = devices[statename];
356 state.state = statename;
357 let deviceid = this.getDeviceName();
358 if (!this.states[deviceid] || this.states[deviceid] !== deviceid) {
359 this.states[deviceid] = deviceid;
360 this.objectHelper.setOrUpdateObject(deviceid, {
361 type: 'device',
362 common: {
363 name: 'Device ' + deviceid
364 },
365 native: {}
366 }, ['name']);
367 }
368 let channel = statename.split('.').slice(0, 1).join();
369 if (channel !== statename) {
370 let channelid = deviceid + '.' + channel;
371 if (!this.states[channelid] || this.states[channelid] !== channelid) {
372 this.states[channelid] = channelid;
373 this.objectHelper.setOrUpdateObject(channelid, {
374 type: 'channel',
375 common: {
376 name: 'Channel ' + channel
377 }
378 }, ['name']);
379 }
380 }
381 let stateid = deviceid + '.' + statename;
382 let controlFunction;
383 if (state.coap && state.coap.http_cmd && !state.coap.coap_cmd) {
384 controlFunction = async (value) => {
385 if (state.coap && state.coap.http_cmd_funct) {
386 try {
387 value = isAsync(state.coap.http_cmd_funct) ? await state.coap.http_cmd_funct(value, this) : state.coap.http_cmd_funct(value, this);
388 } catch (error) {
389 this.adapter.log.error('Error in function state.coap.http_cmd_funct for state ' + stateid + ' for ' + this.getName() + ' (' + error + ')');
390 }
391 }
392 let body;
393 let params;
394 try {
395 if (this.auth) {
396 params = {
397 url: 'http://' + this.getIP() + state.coap.http_cmd,
398 timeout: this.httptimeout,
399 qs: value,
400 headers: {
401 'Authorization': this.auth
402 }
403 };
404 } else {
405 params = {
406 url: 'http://' + this.getIP() + state.coap.http_cmd,
407 timeout: this.httptimeout,
408 qs: value
409 };
410 }
411 this.adapter.log.debug('Call url ' + JSON.stringify(params) + ' for ' + this.getName());
412 body = await requestAsync(params);
413 // this.adapter.log.debug('Create Object body : ' + body);
414 } catch (error) {
415 if (body && body === '401 Unauthorized') {
416 this.adapter.log.error('Wrong http username or http password! Please enter the user credential from restricted login for ' + this.getName());
417 } else {
418 this.adapter.log.error('Error in function state.coap.http_cmd for state ' + stateid + ' and request' + JSON.stringify(params) + ' for ' + this.getName() + ' (' + error + ')');
419 }
420 }
421 delete this.states[stateid];
422 };
423 }
424 if (state.coap && state.coap.http_publish && !state.coap.coap_publish) {
425 if (!this.http[state.coap.http_publish]) this.http[state.coap.http_publish] = [];
426 this.http[state.coap.http_publish].push(statename);
427 }
428 let value;
429 if (state.coap.coap_init_value) value = state.coap.coap_init_value;
430 this.objectHelper.setOrUpdateObject(stateid, {
431 type: 'state',
432 common: state.common
433 }, ['name'], value, controlFunction);
434 }
435 this.device = devices;
436 }
437 this.objectHelper.processObjectQueue(() => { });
438 }
439 }
440
441 getDevices() {
442 let states = [];
443 for (let i in this.device) {
444 let state = this.device[i];
445 // if (state.coap && state.coap.coap_publish_funct) states.push(state);
446 if (state.coap) {
447 if (state.coap.coap_publish) {
448 states.push(state);
449 } else if (state.coap.coap_publish_funct) {
450 states.push(state);
451 }
452 }
453 }
454 return states;
455 }
456
457
458 /**
459 * State changes from device will be saved in the ioBroker states
460 * @param {object} payload - object can be ervery type of value
461 */
462 async createIoBrokerState(payload) {
463 this.adapter.log.debug('CoAP Message for ' + this.getDeviceName() + ' : ' + JSON.stringify(payload));
464 let dps = this.getDevices();
465 for (let i in dps) {
466 let dp = dps[i];
467 let deviceid = this.getDeviceName();
468 let stateid = deviceid + '.' + dp.state;
469 let value = payload;
470 this.adapter.log.debug('Create State : ' + stateid + ', Payload: ' + JSON.stringify(payload) + ' for ' + this.getDeviceName());
471 this.adapter.log.debug('Create State : ' + stateid + ', Payload: ' + payload.toString() + ' for ' + this.getDeviceName());
472 try {
473 if (dp.coap && dp.coap.coap_publish) {
474 value = getCoapValue(dp.coap.coap_publish, value.G);
475 if (dp.coap && dp.coap.coap_publish_funct) {
476 value = isAsync(dp.coap.coap_publish_funct) ? await dp.coap.coap_publish_funct(value, this) : dp.coap.coap_publish_funct(value, this);
477 }
478 if (dp.common.type === 'boolean' && value === 'false') value = false;
479 if (dp.common.type === 'boolean' && value === 'true') value = true;
480 if (dp.common.type === 'number' && value !== undefined) value = Number(value);
481 // this.adapter.log.debug('createIoBrokerState(), State : ' + stateid + ', Value: ' + JSON.stringify(value));
482 // if (value !== undefined && (!Object.prototype.hasOwnProperty.call(this.states, stateid) || this.states[stateid] !== value)) {}
483 if (value !== undefined && (!Object.prototype.hasOwnProperty.call(this.states, stateid) || this.states[stateid] !== value || this.adapter.config.updateUnchangedObjects)) {
484 this.adapter.log.debug('State change : ' + stateid + ', Value: ' + JSON.stringify(value) + ' for ' + this.getName());
485 this.states[stateid] = value;
486 this.objectHelper.setOrUpdateObject(stateid, {
487 type: 'state',
488 common: dp.common
489 }, ['name'], value);
490
491 }
492 }
493 } catch (error) {
494 this.adapter.log.error('Error ' + error + ' in function dp.coap.coap_publish_funct for state ' + stateid + ' for ' + this.getName());
495 }
496 }
497 this.objectHelper.processObjectQueue(() => { });
498 }
499
500 /**
501 * Missting data in CoAP will be pulled by http
502 */
503 async httpIoBrokerState() {
504 // Test - to delete after a while
505 if (!this.httpIoBrokerStateTime || Date.now() >= (this.httpIoBrokerStateTime + (1000 * 60 * 10))) {
506 this.httpIoBrokerStateTime = Date.now();
507 // this.adapter.log.info('httpIoBrokerState() still running on ' + this.getName() + ' for ' + JSON.stringify(this.http));
508 }
509 let alive = await pingAsync(this.getIP());
510 if (alive === false) {
511 this.timerid = setTimeout(async () => await this.httpIoBrokerState(), 100);
512 return;
513 }
514 for (let i in this.http) {
515 let params;
516 let states = this.http[i];
517 try {
518 if (this.auth) {
519 params = {
520 url: 'http://' + this.getIP() + i,
521 timeout: this.httptimeout,
522 headers: {
523 'Authorization': this.auth
524 }
525 };
526 } else {
527 params = {
528 url: 'http://' + this.getIP() + i,
529 timeout: this.httptimeout
530 };
531 }
532 this.adapter.log.debug('http request' + JSON.stringify(params) + ' for ' + this.getName());
533 let body = await requestAsync(params);
534 for (let j in states) {
535 let state = this.device[states[j]];
536 if (state && state.state) {
537 let deviceid = this.getDeviceName();
538 let stateid = deviceid + '.' + state.state;
539 let value = body;
540 try {
541 if (state.coap && state.coap.http_publish_funct)
542 value = isAsync(state.coap.http_publish_funct) ? await state.coap.http_publish_funct(value, this) : state.coap.http_publish_funct(value, this);
543 if (state.common.type === 'boolean' && value === 'false') value = false;
544 if (state.common.type === 'boolean' && value === 'true') value = true;
545 if (state.common.type === 'number' && value !== undefined) value = Number(value);
546 if (value !== undefined && (!Object.prototype.hasOwnProperty.call(this.states, stateid) || this.states[stateid] !== value || this.adapter.config.updateUnchangedObjects)) {
547 this.adapter.log.debug('Set http state ' + stateid + ', Value: ' + JSON.stringify(value) + ' for ' + this.getName());
548 this.states[stateid] = value;
549 this.objectHelper.setOrUpdateObject(stateid, {
550 type: 'state',
551 common: state.common
552 }, ['name'], value);
553 }
554 this.polltime = this.adapter.config.polltime * 1000 || 5000;
555 } catch (error) {
556 if (error.name && error.name.startsWith('TypeError')) {
557 this.adapter.log.debug('Could not find property for state ' + stateid + ' and request' + JSON.stringify(params) + ' for ' + this.getName() + ' (' + error + ')');
558 } else {
559 this.polltime = 60 * 1000;
560 if (body && body === '401 Unauthorized') {
561 this.adapter.log.error('Wrong http username or http password! Please enter the user credential from restricted login for ' + this.getName());
562 break;
563 } else {
564 this.adapter.log.error('Error in function httpIoBrokerState for state ' + stateid + ' and request' + JSON.stringify(params) + ' for ' + this.getName() + ' (' + error + ')');
565 }
566 }
567 }
568 }
569 }
570 this.objectHelper.processObjectQueue(() => { });
571 } catch (error) {
572 // this.polltime = 60 * 1000;
573 // this.adapter.log.error('Error ' + error + ' - ' + this.getName());
574 }
575 }
576 if (this.http && Object.keys(this.http).length > 0) {
577 // await sleep(this.polltime);
578 this.timerid = setTimeout(async () => await this.httpIoBrokerState(), this.polltime);
579 }
580 }
581
582 async firmwareUpdatePolling() {
583 if (this.adapter.config.autoupdate) {
584 await this.firmwareUpdate(true);
585 this.autoupdateid = setTimeout(async () => await this.firmwareUpdatePolling(), 60 * 1000);
586 }
587 }
588
589 async firmwareUpdate(update) {
590 if (!update) return;
591 this.adapter.log.debug('Calling function firmwareUpdate');
592 let params;
593 try {
594 if (this.auth) {
595 params = {
596 url: 'http://' + this.getIP() + '/ota?update=true',
597 timeout: this.httptimeout,
598 headers: {
599 'Authorization': this.auth
600 }
601 };
602 } else {
603 params = {
604 url: 'http://' + this.getIP() + '/ota?update=true',
605 timeout: this.httptimeout
606 };
607 }
608 this.adapter.log.debug('Call url ' + JSON.stringify(params) + ' for ' + this.getName());
609 let body = await requestAsync(params);
610 // this.adapter.log.info('Executing Firmwareupdate for ' + this.getName());
611 } catch (error) {
612 this.adapter.log.error('Error in function firmwareUpdate and request' + JSON.stringify(params) + ' for ' + this.getName() + ' (' + error + ')');
613 }
614 }
615
616 start(payload, description) {
617 if (this.deviceExist()) {
618 this.adapter.log.info('Shelly device ' + this.getName() + ' with CoAP connected!');
619 this.adapter.log.debug('1. Shelly device Info: ' + this.getDeviceName() + ' : ' + JSON.stringify(description));
620 this.adapter.log.debug('2. Shelly device Info: ' + this.getDeviceName() + ' : ' + JSON.stringify(payload));
621 this.deleteOldStates();
622 this.deleteOldStates();
623 this.createObjects();
624 this.httpIoBrokerState();
625 if (payload) this.createIoBrokerState(payload);
626 this.eventEmitter.on('onFirmwareUpdate', async () => await this.firmwareUpdate(true));
627 // this.firmwareUpdatePolling();
628 this.listener();
629 } else {
630 this.adapter.log.error('Shelly Device unknown, configuratin for Shelly device ' + this.getName() + ' for CoAP does not exist!');
631 this.adapter.log.error('1. Send developer following Info: ' + this.getDeviceName() + ' : ' + JSON.stringify(description));
632 this.adapter.log.error('2 .Send developer following Info: ' + this.getDeviceName() + ' : ' + JSON.stringify(payload));
633 }
634 }
635
636 listener() {
637 this.shelly.on('error', (err) => {
638 this.adapter.log.debug('Error handling Shelly data: ' + err);
639 });
640 this.shelly.on('update-device-status', this.listenerus = (devicename, payload) => {
641 if (this.getOldDeivceInfo(devicename) === this.getDeviceName()) {
642 this.createIoBrokerState(payload);
643 }
644 });
645 this.shelly.on('device-connection-status', this.listenerds = (devicename, connected) => {
646 this.adapter.log.debug('Connection update received for ' + devicename + ': ' + connected);
647 if (this.getOldDeivceInfo(devicename) === this.getDeviceName()) {
648 // adapter.setState(deviceId + '.online', connected, true);
649 }
650 });
651 }
652
653
654}
655
656class CoAPServer {
657
658 constructor(adapter, objectHelper, eventEmitter) {
659 if (!(this instanceof CoAPServer)) return new CoAPServer(adapter, objectHelper, eventEmitter);
660 this.adapter = adapter;
661 this.objectHelper = objectHelper;
662 this.clients = {};
663 this.blacklist = {};
664 this.eventEmitter = eventEmitter;
665 }
666
667 isBlackListed(deviceId) {
668 if (deviceId && this.blacklist[deviceId]) {
669 return true;
670 }
671 if (deviceId && this.adapter.config.keys) {
672 for (let i in this.adapter.config.keys) {
673 let key = this.adapter.config.keys[i];
674 if (key.blacklist && deviceId) {
675 let reg = new RegExp(key.blacklist, 'gm');
676 let res = deviceId.match(reg);
677 if (res) {
678 this.blacklist[deviceId] = deviceId;
679 return true;
680 }
681 }
682 }
683 }
684 return false;
685 }
686
687 listen() {
688 let options = {};
689 if (this.adapter.config.httpusername && this.adapter.config.httppassword) {
690 options = {
691 logger: this.adapter.log.debug,
692 user: this.adapter.config.httpusername,
693 password: this.adapter.config.httppassword,
694 multicastInterface: null
695 };
696 } else {
697 options = {
698 logger: this.adapter.log.debug,
699 };
700 }
701 if (this.adapter.config.coapbind && this.adapter.config.coapbind != '0.0.0.0') {
702 options.multicastInterface = this.adapter.config.coapbind;
703 }
704 let shelly = new Shelly(options);
705 shelly.on('error', (err) => {
706 this.adapter.log.debug('Error handling Shelly data: ' + err);
707 });
708 shelly.on('update-device-status', (deviceId, status) => {
709 this.adapter.log.debug('Status update received for ' + deviceId + ': ' + JSON.stringify(status));
710 if (deviceId && typeof deviceId === 'string') {
711 shelly.getDeviceDescription(deviceId, (err, deviceId, description, ip) => {
712 if (!err && deviceId && ip) {
713 // if ip address change of coap device change
714 if (this.clients[deviceId] && this.clients[deviceId].getIP() !== ip) {
715 this.clients[deviceId].destroy();
716 delete this.clients[deviceId];
717 }
718 if (!this.clients[deviceId]) {
719 if (!this.isBlackListed(deviceId) && !this.isBlackListed(ip)) {
720 this.clients[deviceId] = new CoAPClient(this.adapter, this.objectHelper, this.eventEmitter, shelly, deviceId, ip, status, description);
721 }
722 }
723 }
724 });
725 } else {
726 this.adapter.log.debug('Device Id is missing ' + deviceId);
727 }
728 });
729 shelly.on('disconnect', () => {
730 for (let i in this.clients) {
731 this.clients[i].destroy();
732 delete this.clients[i];
733 }
734 });
735 shelly.listen(() => {
736 this.adapter.log.info('Listening for Shelly packets in the network');
737 });
738 }
739
740}
741
742module.exports = {
743 CoAPServer: CoAPServer
744};