1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 |
|
8 |
|
9 | const Shelly = require('shelly-iot');
|
10 | const ping = require('ping');
|
11 | const tcpp = require('tcp-ping');
|
12 | const request = require('request');
|
13 | const datapoints = require(__dirname + '/datapoints');
|
14 |
|
15 | function sleep(ms) {
|
16 | return new Promise((resolve) => setTimeout(resolve, ms));
|
17 | }
|
18 |
|
19 | function isAsync(funct) {
|
20 | if (funct && funct.constructor) return funct.constructor.name == 'AsyncFunction';
|
21 | return undefined;
|
22 | }
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | function 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 |
|
42 |
|
43 |
|
44 |
|
45 | function 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 |
|
54 | function 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 |
|
66 | function 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 |
|
88 |
|
89 |
|
90 |
|
91 | function 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 |
|
109 | function 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 |
|
126 | class 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 |
|
156 |
|
157 |
|
158 | getOldDeivceInfo(devicename) {
|
159 |
|
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 |
|
169 |
|
170 |
|
171 | getIP() {
|
172 | return this.ip;
|
173 | }
|
174 |
|
175 | |
176 |
|
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 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 | getDeviceName() {
|
194 | return this.getOldDeivceInfo(this.devicename);
|
195 | }
|
196 |
|
197 | |
198 |
|
199 |
|
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 |
|
213 |
|
214 |
|
215 | getDeviceId() {
|
216 | if (!this.deviceid) {
|
217 | this.deviceid = datapoints.getDeviceNameForCoAP(this.getDeviceType());
|
218 | }
|
219 | return this.deviceid;
|
220 | }
|
221 |
|
222 | |
223 |
|
224 |
|
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 |
|
239 |
|
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 |
|
251 |
|
252 | getName() {
|
253 | let name = this.getDeviceName();
|
254 | let ip = this.getIP();
|
255 | let deviceid = this.getDeviceId();
|
256 | let id = this.getId();
|
257 | return ip + ' (' + deviceid + ' / ' + id + ' / ' + name + ')';
|
258 | }
|
259 |
|
260 | |
261 |
|
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 |
|
287 |
|
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 |
|
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 |
|
344 |
|
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 |
|
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 |
|
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 |
|
460 |
|
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 |
|
482 |
|
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 |
|
502 |
|
503 | async httpIoBrokerState() {
|
504 |
|
505 | if (!this.httpIoBrokerStateTime || Date.now() >= (this.httpIoBrokerStateTime + (1000 * 60 * 10))) {
|
506 | this.httpIoBrokerStateTime = Date.now();
|
507 |
|
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 |
|
573 |
|
574 | }
|
575 | }
|
576 | if (this.http && Object.keys(this.http).length > 0) {
|
577 |
|
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 |
|
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 |
|
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 |
|
649 | }
|
650 | });
|
651 | }
|
652 |
|
653 |
|
654 | }
|
655 |
|
656 | class 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 |
|
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 |
|
742 | module.exports = {
|
743 | CoAPServer: CoAPServer
|
744 | };
|