iobroker.zigbee2mqtt 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,13 @@
1
1
  const utils = require('./utils');
2
2
  const incStatsQueue = [];
3
+ const timeOutCache = {};
3
4
 
4
5
  class StatesController {
5
- constructor(adapter, deviceCache, groupCache, debugDevices) {
6
+ constructor(adapter, deviceCache, groupCache, logCustomizations) {
6
7
  this.adapter = adapter;
7
8
  this.groupCache = groupCache;
8
9
  this.deviceCache = deviceCache;
9
- this.debugDevices = debugDevices;
10
+ this.logCustomizations = logCustomizations;
10
11
  }
11
12
 
12
13
  async processDeviceMessage(messageObj) {
@@ -22,15 +23,14 @@ class StatesController {
22
23
  } catch (error) {
23
24
  this.adapter.log.error(error);
24
25
  }
25
- }
26
- else {
26
+ } else {
27
27
  incStatsQueue[incStatsQueue.length] = messageObj;
28
28
  this.adapter.log.debug(`Device: ${messageObj.topic} not found, queue state in incStatsQueue!`);
29
29
  }
30
30
  }
31
31
 
32
32
  async setDeviceState(messageObj, device) {
33
- if (this.debugDevices.includes(device.ieee_address)) {
33
+ if (this.logCustomizations.debugDevices.includes(device.ieee_address)) {
34
34
  this.adapter.log.warn(`--->>> fromZ2M -> ${device.ieee_address} states: ${JSON.stringify(messageObj)}`);
35
35
  }
36
36
 
@@ -53,23 +53,19 @@ class StatesController {
53
53
  if (state.isEvent) {
54
54
  if (state.getter) {
55
55
  await this.setStateWithTimeoutAsync(stateName, state.getter(messageObj.payload), 300);
56
- }
57
- else {
56
+ } else {
58
57
  await this.setStateWithTimeoutAsync(stateName, value, 300);
59
58
  }
60
- }
61
- else {
59
+ } else {
62
60
  if (state.getter) {
63
- await this.setStateAsync(stateName, state.getter(messageObj.payload));
64
- }
65
- else {
66
- await this.setStateAsync(stateName, value);
61
+ await this.setStateChangedAsync(stateName, state.getter(messageObj.payload));
62
+ } else {
63
+ await this.setStateChangedAsync(stateName, value);
67
64
  }
68
65
  }
69
66
  } catch (err) {
70
- //this.adapter.log.warn(`Can not set ${stateName}`);
71
67
  incStatsQueue[incStatsQueue.length] = messageObj;
72
- this.adapter.log.debug(`Can not set ${stateName} for ${messageObj.topic}, queue state in incStatsQueue!`);
68
+ this.adapter.log.debug(`Can not set ${stateName}, queue state in incStatsQueue!`);
73
69
  }
74
70
  }
75
71
  }
@@ -81,10 +77,19 @@ class StatesController {
81
77
  }
82
78
  }
83
79
 
80
+ async setStateChangedAsync(stateName, value) {
81
+ if (value !== undefined) {
82
+ await this.adapter.setStateChangedAsync(stateName, value, true);
83
+ }
84
+ }
85
+
84
86
  async setStateWithTimeoutAsync(stateName, value, timeout) {
85
87
  if (value !== undefined) {
86
88
  await this.adapter.setStateAsync(stateName, value, true);
87
- setTimeout(() => {
89
+ if (timeOutCache[stateName]) {
90
+ clearTimeout(timeOutCache[stateName]);
91
+ }
92
+ timeOutCache[stateName] = setTimeout(() => {
88
93
  this.adapter.setStateAsync(stateName, !value, true);
89
94
  }, timeout);
90
95
  }
@@ -115,13 +120,19 @@ class StatesController {
115
120
  for (const device of this.deviceCache) {
116
121
  for (const state of device.states) {
117
122
  if (state.id == 'available') {
118
- await this.adapter.setStateAsync(`${device.ieee_address}.${state.id}`, false, true);
123
+ await this.adapter.setStateChangedAsync(`${device.ieee_address}.${state.id}`, false, true);
119
124
  }
120
125
  }
121
126
  }
122
127
  }
128
+
129
+ async allTimerClear() {
130
+ for (const timer in timeOutCache) {
131
+ clearTimeout(timeOutCache[timer]);
132
+ }
133
+ }
123
134
  }
124
135
 
125
136
  module.exports = {
126
137
  StatesController
127
- };
138
+ };
package/lib/utils.js CHANGED
@@ -106,12 +106,6 @@ function getDeviceIcon(definition) {
106
106
  return icon;
107
107
  }
108
108
 
109
- function removeDeviceByIeee(devices, ieee_address) {
110
- const idx = devices.findIndex(x => x.ieee_address == ieee_address);
111
- if (idx > -1) {
112
- devices.splice(idx, 1);
113
- }
114
- }
115
109
  function clearArray(array) {
116
110
  while (array.length > 0) {
117
111
  array.pop();
@@ -125,16 +119,15 @@ function moveArray(source, target) {
125
119
  }
126
120
 
127
121
  module.exports = {
128
- bulbLevelToAdapterLevel: bulbLevelToAdapterLevel,
129
- adapterLevelToBulbLevel: adapterLevelToBulbLevel,
130
- bytesArrayToWordArray: bytesArrayToWordArray,
131
- toMired: toMired,
132
- miredKelvinConversion: miredKelvinConversion,
133
- decimalToHex: decimalToHex,
134
- getZbId: getZbId,
135
- getAdId: getAdId,
136
- removeDeviceByIeee: removeDeviceByIeee,
137
- getDeviceIcon: getDeviceIcon,
138
- clearArray: clearArray,
139
- moveArray: moveArray,
140
- };
122
+ bulbLevelToAdapterLevel,
123
+ adapterLevelToBulbLevel,
124
+ bytesArrayToWordArray,
125
+ toMired,
126
+ miredKelvinConversion,
127
+ decimalToHex,
128
+ getZbId,
129
+ getAdId,
130
+ getDeviceIcon,
131
+ clearArray,
132
+ moveArray,
133
+ };
@@ -0,0 +1,84 @@
1
+ const WebSocket = require('ws');
2
+ let wsClient;
3
+ const wsHeartbeatIntervall = 5000;
4
+ const restartTimeout = 1000;
5
+ let ping;
6
+ let pingTimeout;
7
+ let autoRestartTimeout;
8
+
9
+ class WebsocketController {
10
+ constructor(adapter) {
11
+ this.adapter = adapter;
12
+ }
13
+
14
+ async initWsClient(server, port) {
15
+ try {
16
+ wsClient = new WebSocket(`ws://${server}:${port}/api`);
17
+
18
+ wsClient.on('open', () => {
19
+ // Send ping to server
20
+ this.sendPingToServer();
21
+ // Start Heartbeat
22
+ this.wsHeartbeat();
23
+ });
24
+
25
+ wsClient.on('pong', () => {
26
+ this.wsHeartbeat();
27
+ });
28
+
29
+ wsClient.on('close', async () => {
30
+ clearTimeout(pingTimeout);
31
+ clearTimeout(ping);
32
+
33
+ if (wsClient.readyState === WebSocket.CLOSED) {
34
+ this.autoRestart();
35
+ }
36
+ });
37
+
38
+ wsClient.on('message', () => { });
39
+
40
+ wsClient.on('error', (err) => { this.adapter.log.debug(err); });
41
+
42
+ return wsClient;
43
+ } catch (err) {
44
+ this.adapter.log.error(err);
45
+ }
46
+ }
47
+
48
+ async send(message) {
49
+ wsClient.send(message);
50
+ }
51
+
52
+ async sendPingToServer() {
53
+ //this.logDebug('Send ping to server');
54
+ wsClient.ping();
55
+ ping = setTimeout(() => {
56
+ this.sendPingToServer();
57
+ }, wsHeartbeatIntervall);
58
+ }
59
+
60
+ async wsHeartbeat() {
61
+ clearTimeout(pingTimeout);
62
+ pingTimeout = setTimeout(() => {
63
+ this.adapter.log.warn('Websocked connection timed out');
64
+ wsClient.terminate();
65
+ }, wsHeartbeatIntervall + 1000);
66
+ }
67
+
68
+ async autoRestart() {
69
+ this.adapter.log.warn(`Start try again in ${restartTimeout / 1000} seconds...`);
70
+ autoRestartTimeout = setTimeout(() => {
71
+ this.adapter.onReady();
72
+ }, restartTimeout);
73
+ }
74
+
75
+ async allTimerClear() {
76
+ clearTimeout(pingTimeout);
77
+ clearTimeout(ping);
78
+ clearTimeout(autoRestartTimeout);
79
+ }
80
+ }
81
+
82
+ module.exports = {
83
+ WebsocketController
84
+ };
@@ -1,9 +1,9 @@
1
1
  class Z2mController {
2
- constructor(adapter, deviceCache, groupCache, logfilter) {
2
+ constructor(adapter, deviceCache, groupCache, logCustomizations) {
3
3
  this.adapter = adapter;
4
4
  this.groupCache = groupCache;
5
5
  this.deviceCache = deviceCache;
6
- this.logfilter = logfilter;
6
+ this.logCustomizations = logCustomizations;
7
7
  }
8
8
 
9
9
  async createZ2MMessage(id, state) {
@@ -57,7 +57,7 @@ class Z2mController {
57
57
 
58
58
  async proxyZ2MLogs(messageObj) {
59
59
  const logMessage = messageObj.payload.message;
60
- if (this.logfilter.some(x => logMessage.includes(x))) {
60
+ if (this.logCustomizations.logfilter.some(x => logMessage.includes(x))) {
61
61
  return;
62
62
  }
63
63
 
package/main.js CHANGED
@@ -7,7 +7,6 @@
7
7
  // The adapter-core module gives you access to the core ioBroker functions
8
8
  // you need to create an adapter
9
9
  const core = require('@iobroker/adapter-core');
10
- const NedbPersistence = require('aedes-persistence-nedb');
11
10
  const mqtt = require('mqtt');
12
11
  const checkConfig = require('./lib/check').checkConfig;
13
12
  const adapterInfo = require('./lib/messages').adapterInfo;
@@ -15,6 +14,8 @@ const zigbee2mqttInfo = require('./lib/messages').zigbee2mqttInfo;
15
14
  const Z2mController = require('./lib/z2mController').Z2mController;
16
15
  const DeviceController = require('./lib/deviceController').DeviceController;
17
16
  const StatesController = require('./lib/statesController').StatesController;
17
+ const WebsocketController = require('./lib/websocketController').WebsocketController;
18
+ const MqttServerController = require('./lib/mqttServerController').MqttServerController;
18
19
 
19
20
 
20
21
  let mqttClient;
@@ -22,16 +23,13 @@ let mqttClient;
22
23
  let deviceCache = [];
23
24
  // eslint-disable-next-line prefer-const
24
25
  let groupCache = [];
25
- let ping;
26
- let pingTimeout;
27
- let autoRestartTimeout;
28
- let checkAvailableTimout;
29
- let debugDevices = '';
30
- let logfilter = [];
26
+ const logCustomizations = { debugDevices: '', logfilter: [] };
31
27
  let showInfo = true;
32
28
  let statesController;
33
29
  let deviceController;
34
30
  let z2mController;
31
+ let websocketController;
32
+ let mqttServerController;
35
33
 
36
34
  class Zigbee2mqtt extends core.Adapter {
37
35
 
@@ -46,11 +44,9 @@ class Zigbee2mqtt extends core.Adapter {
46
44
  }
47
45
 
48
46
  async onReady() {
49
-
50
-
51
- statesController = new StatesController(this, deviceCache, groupCache, debugDevices);
52
- deviceController = new DeviceController(this, deviceCache, groupCache, this.config.useKelvin);
53
- z2mController = new Z2mController(this, deviceCache, groupCache, logfilter);
47
+ statesController = new StatesController(this, deviceCache, groupCache, logCustomizations);
48
+ deviceController = new DeviceController(this, deviceCache, groupCache, this.config);
49
+ z2mController = new Z2mController(this, deviceCache, groupCache, logCustomizations);
54
50
 
55
51
  // Initialize your adapter here
56
52
  adapterInfo(this.config, this.log);
@@ -59,39 +55,77 @@ class Zigbee2mqtt extends core.Adapter {
59
55
 
60
56
  const debugDevicesState = await this.getStateAsync('info.debugmessages');
61
57
  if (debugDevicesState && debugDevicesState.val) {
62
- debugDevices = String(debugDevicesState.val);
58
+ logCustomizations.debugDevices = String(debugDevicesState.val);
63
59
  }
64
60
 
65
61
  const logfilterState = await this.getStateAsync('info.logfilter');
66
62
  if (logfilterState && logfilterState.val) {
67
- logfilter = String(logfilterState.val).split(';').filter(x => x); // filter removes empty strings here
63
+ // @ts-ignore
64
+ logCustomizations.logfilter = String(logfilterState.val).split(';').filter(x => x); // filter removes empty strings here
68
65
  }
66
+ // MQTT
67
+ if (['exmqtt', 'intmqtt'].includes(this.config.connectionType)) {
68
+ // External MQTT-Server
69
+ if (this.config.connectionType == 'exmqtt') {
70
+ if (this.config.externalMqttServerIP == '') {
71
+ this.log.warn('Please configure the External MQTT-Server connection!');
72
+ return;
73
+ }
74
+ mqttClient = mqtt.connect(`mqtt://${this.config.externalMqttServerIP}:${this.config.externalMqttServerPort}`, { clientId: `ioBroker.zigbee2mqtt_${Math.random().toString(16).slice(2, 8)}`, clean: true, reconnectPeriod: 500 });
75
+
76
+ }
77
+ // Internal MQTT-Server
78
+ else {
79
+ mqttServerController = new MqttServerController(this);
80
+ await mqttServerController.createMQTTServer();
81
+ await this.delay(1500);
82
+ mqttClient = mqtt.connect(`mqtt://${this.config.mqttServerIPBind}:${this.config.mqttServerPort}`, { clientId: `ioBroker.zigbee2mqtt_${Math.random().toString(16).slice(2, 8)}`, clean: true, reconnectPeriod: 500 });
83
+ }
69
84
 
70
- if (this.config.useExternalMqtt == true) {
71
- mqttClient = mqtt.connect(`mqtt://${this.config.externalMqttServerIP}:${this.config.externalMqttServerPort}`, { clientId: `ioBroker.zigbee2mqtt_${Math.random().toString(16).slice(2, 8)}`, clean: true, reconnectPeriod: 500 });
72
- } else {
73
- const Aedes = require('aedes');
74
- const net = require('net');
75
- const db = new NedbPersistence({
76
- path: `${core.getAbsoluteInstanceDataDir(this)}/mqttData`,
77
- prefix: ''
85
+ // MQTT Client
86
+ mqttClient.on('connect', () => {
87
+ this.log.info(`Connect to Zigbee2MQTT over ${this.config.connectionType == 'exmqtt' ? 'external mqtt' : 'internal mqtt'} connection.`);
88
+ });
89
+
90
+ mqttClient.subscribe('zigbee2mqtt/#');
91
+
92
+ mqttClient.on('message', (topic, payload) => {
93
+ const newMessage = `{"payload":${payload.toString() == '' ? '"null"' : payload.toString()},"topic":"${topic.slice(topic.search('/') + 1)}"}`;
94
+ this.messageParse(newMessage);
78
95
  });
79
- // @ts-ignore
80
- const aedes = Aedes({ persistence: db });
81
- const mqttServer = net.createServer(aedes.handle);
82
- mqttServer.listen(this.config.mqttServerPort, this.config.mqttServerIPBind, () => { });
83
- mqttClient = mqtt.connect(`mqtt://${this.config.mqttServerIPBind}:${this.config.mqttServerPort}`, { clientId: 'ioBroker.zigbee2mqtt', clean: true, reconnectPeriod: 500 });
84
96
  }
97
+ // Websocket
98
+ else if (this.config.connectionType == 'ws') {
99
+ if (this.config.wsServerIP == '') {
100
+ this.log.warn('Please configure the Websoket connection!');
101
+ return;
102
+ }
85
103
 
86
- mqttClient.on('connect', () => { this.setStateAsync('info.connection', true, true); });
87
- mqttClient.subscribe('#');
88
- mqttClient.on('message', (topic, payload) => {
89
- const newMessage = `{"payload":${payload.toString() == '' ? '"null"' : payload.toString()},"topic":"${topic.slice(topic.search('/') + 1)}"}`;
90
- console.log(newMessage);
91
- this.messageParse(newMessage);
92
- });
93
- }
104
+ // Dummy MQTT-Server
105
+ if (this.config.dummyMqtt == true) {
106
+ mqttServerController = new MqttServerController(this);
107
+ await mqttServerController.createDummyMQTTServer();
108
+ await this.delay(1500);
109
+ }
110
+
111
+ websocketController = new WebsocketController(this);
112
+ const wsClient = await websocketController.initWsClient(this.config.wsServerIP, this.config.wsServerPort);
113
+
114
+ wsClient.on('open', () => {
115
+ this.log.info('Connect to Zigbee2MQTT over websocket connection.');
116
+ });
117
+
118
+ wsClient.on('message', (message) => {
119
+ this.messageParse(message);
120
+ });
94
121
 
122
+ wsClient.on('clo', async () => {
123
+ this.setStateChangedAsync('info.connection', false, true);
124
+ await statesController.setAllAvailableToFalse();
125
+ this.log.warn('Websocket disconnectet');
126
+ });
127
+ }
128
+ }
95
129
 
96
130
  async messageParse(message) {
97
131
  const messageObj = JSON.parse(message);
@@ -107,6 +141,10 @@ class Zigbee2mqtt extends core.Adapter {
107
141
  }
108
142
  break;
109
143
  case 'bridge/state':
144
+ if (messageObj.payload.state != 'online') {
145
+ statesController.setAllAvailableToFalse();
146
+ }
147
+ this.setStateChangedAsync('info.connection', messageObj.payload.state == 'online', true);
110
148
  break;
111
149
  case 'bridge/devices':
112
150
  await deviceController.createDeviceDefinitions(messageObj.payload);
@@ -174,10 +212,8 @@ class Zigbee2mqtt extends core.Adapter {
174
212
  async onUnload(callback) {
175
213
  try {
176
214
  await statesController.setAllAvailableToFalse();
177
- clearTimeout(ping);
178
- clearTimeout(pingTimeout);
179
- clearTimeout(autoRestartTimeout);
180
- clearTimeout(checkAvailableTimout);
215
+ await websocketController.allTimerClear();
216
+ await statesController.allTimerClear();
181
217
  callback();
182
218
  } catch (e) {
183
219
  callback();
@@ -187,18 +223,23 @@ class Zigbee2mqtt extends core.Adapter {
187
223
  async onStateChange(id, state) {
188
224
  if (state && state.ack == false) {
189
225
  if (id.includes('info.debugmessages')) {
190
- debugDevices = state.val;
226
+ logCustomizations.debugDevices = state.val;
191
227
  this.setState(id, state.val, true);
192
228
  return;
193
229
  }
194
230
  if (id.includes('info.logfilter')) {
195
- logfilter = state.val.split(';').filter(x => x); // filter removes empty strings here
231
+ logCustomizations.logfilter = state.val.split(';').filter(x => x); // filter removes empty strings here
196
232
  this.setState(id, state.val, true);
197
233
  return;
198
234
  }
199
235
 
200
236
  const message = await z2mController.createZ2MMessage(id, state) || { topic: '', payload: '' };
201
- mqttClient.publish('zigbee2mqtt/' + message.topic, JSON.stringify(message.payload));
237
+
238
+ if (['exmqtt', 'intmqtt'].includes(this.config.connectionType)) {
239
+ mqttClient.publish(`zigbee2mqtt/${message.topic}`, JSON.stringify(message.payload));
240
+ } else if (this.config.connectionType == 'ws') {
241
+ websocketController.send(JSON.stringify(message));
242
+ }
202
243
  }
203
244
  }
204
245
  }
@@ -213,4 +254,4 @@ if (require.main !== module) {
213
254
  } else {
214
255
  // otherwise start the instance directly
215
256
  new Zigbee2mqtt();
216
- }
257
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.zigbee2mqtt",
3
- "version": "1.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Zigbee2MQTT adapter for ioBroker",
5
5
  "author": {
6
6
  "name": "Dennis Rathjen",
@@ -19,8 +19,6 @@
19
19
  "url": "https://github.com/o0shojo0o/ioBroker.zigbee2mqtt.git"
20
20
  },
21
21
  "dependencies": {
22
- "@alcalzone/release-script-plugin-iobroker": "^3.5.9",
23
- "@alcalzone/release-script-plugin-license": "^3.5.9",
24
22
  "@iobroker/adapter-core": "^2.6.7",
25
23
  "aedes": "^0.48.0",
26
24
  "aedes-persistence-nedb": "^2.0.3",
@@ -29,6 +27,8 @@
29
27
  "ws": "^8.9.0"
30
28
  },
31
29
  "devDependencies": {
30
+ "@alcalzone/release-script-plugin-iobroker": "^3.5.9",
31
+ "@alcalzone/release-script-plugin-license": "^3.5.9",
32
32
  "@alcalzone/release-script": "^3.5.9",
33
33
  "@iobroker/adapter-dev": "^1.1.0",
34
34
  "@iobroker/testing": "^4.1.0",