iobroker.zigbee2mqtt 0.2.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.
- package/README.md +35 -0
- package/admin/i18n/de/translations.json +22 -6
- package/admin/i18n/en/translations.json +20 -4
- package/admin/i18n/es/translations.json +20 -4
- package/admin/i18n/fr/translations.json +20 -4
- package/admin/i18n/it/translations.json +20 -4
- package/admin/i18n/nl/translations.json +20 -4
- package/admin/i18n/pl/translations.json +20 -4
- package/admin/i18n/pt/translations.json +20 -4
- package/admin/i18n/ru/translations.json +20 -4
- package/admin/i18n/zh-cn/translations.json +20 -4
- package/admin/jsonConfig.json +131 -9
- package/io-package.json +59 -7
- package/lib/check.js +36 -0
- package/lib/colors.js +5 -3
- package/lib/deviceController.js +234 -0
- package/lib/exposes.js +90 -45
- package/lib/messages.js +39 -0
- package/lib/mqttServerController.js +42 -0
- package/lib/rgb.js +42 -17
- package/lib/states.js +41 -19
- package/lib/statesController.js +138 -0
- package/lib/utils.js +19 -17
- package/lib/websocketController.js +84 -0
- package/lib/z2mController.js +80 -0
- package/main.js +130 -387
- package/package.json +11 -7
- package/lib/groups.js +0 -68
package/main.js
CHANGED
|
@@ -7,31 +7,29 @@
|
|
|
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
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
10
|
+
const mqtt = require('mqtt');
|
|
11
|
+
const checkConfig = require('./lib/check').checkConfig;
|
|
12
|
+
const adapterInfo = require('./lib/messages').adapterInfo;
|
|
13
|
+
const zigbee2mqttInfo = require('./lib/messages').zigbee2mqttInfo;
|
|
14
|
+
const Z2mController = require('./lib/z2mController').Z2mController;
|
|
15
|
+
const DeviceController = require('./lib/deviceController').DeviceController;
|
|
16
|
+
const StatesController = require('./lib/statesController').StatesController;
|
|
17
|
+
const WebsocketController = require('./lib/websocketController').WebsocketController;
|
|
18
|
+
const MqttServerController = require('./lib/mqttServerController').MqttServerController;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
let mqttClient;
|
|
20
22
|
// eslint-disable-next-line prefer-const
|
|
21
23
|
let deviceCache = [];
|
|
22
24
|
// eslint-disable-next-line prefer-const
|
|
23
25
|
let groupCache = [];
|
|
24
|
-
|
|
25
|
-
let
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
let
|
|
31
|
-
let checkAvailableTimout;
|
|
32
|
-
let debugDevices = '';
|
|
33
|
-
let logfilter = [];
|
|
34
|
-
let useKelvin = false;
|
|
26
|
+
const logCustomizations = { debugDevices: '', logfilter: [] };
|
|
27
|
+
let showInfo = true;
|
|
28
|
+
let statesController;
|
|
29
|
+
let deviceController;
|
|
30
|
+
let z2mController;
|
|
31
|
+
let websocketController;
|
|
32
|
+
let mqttServerController;
|
|
35
33
|
|
|
36
34
|
class Zigbee2mqtt extends core.Adapter {
|
|
37
35
|
|
|
@@ -46,96 +44,87 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
async onReady() {
|
|
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);
|
|
50
|
+
|
|
49
51
|
// Initialize your adapter here
|
|
50
|
-
|
|
51
|
-
this.log.info(`Zigbee2MQTT Frontend Server: ${this.config.server}`);
|
|
52
|
-
this.log.info(`Zigbee2MQTT Frontend Port: ${this.config.port}`);
|
|
53
|
-
this.log.info(`Zigbee2MQTT Debug Log: ${this.config.debugLogEnabled ? 'activated' : 'deactivated'}`);
|
|
54
|
-
this.log.info(`Proxy Zigbee2MQTT Logs to ioBroker Logs: ${this.config.proxyZ2MLogs ? 'activated' : 'deactivated'}`);
|
|
55
|
-
this.log.info(`Use Kelvin: ${this.config.useKelvin ? 'yes' : 'no'}`);
|
|
52
|
+
adapterInfo(this.config, this.log);
|
|
56
53
|
|
|
57
54
|
this.setStateAsync('info.connection', false, true);
|
|
58
|
-
this.createWsClient(this.config.server, this.config.port);
|
|
59
|
-
|
|
60
|
-
debugLogEnabled = this.config.debugLogEnabled;
|
|
61
|
-
proxyZ2MLogsEnabled = this.config.proxyZ2MLogs;
|
|
62
|
-
useKelvin = this.config.useKelvin;
|
|
63
55
|
|
|
64
56
|
const debugDevicesState = await this.getStateAsync('info.debugmessages');
|
|
65
57
|
if (debugDevicesState && debugDevicesState.val) {
|
|
66
|
-
debugDevices = String(debugDevicesState.val);
|
|
58
|
+
logCustomizations.debugDevices = String(debugDevicesState.val);
|
|
67
59
|
}
|
|
68
60
|
|
|
69
61
|
const logfilterState = await this.getStateAsync('info.logfilter');
|
|
70
62
|
if (logfilterState && logfilterState.val) {
|
|
71
|
-
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
logCustomizations.logfilter = String(logfilterState.val).split(';').filter(x => x); // filter removes empty strings here
|
|
72
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 });
|
|
73
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
|
+
}
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
async createWsClient(server, port) {
|
|
79
|
-
try {
|
|
80
|
-
wsClient = new WebSocket(`ws://${server}:${port}/api`);
|
|
81
|
-
wsClient.on('open', () => {
|
|
82
|
-
this.logDebug('Websocket connectet');
|
|
83
|
-
// Set connection state
|
|
84
|
-
this.setState('info.connection', true, true);
|
|
85
|
-
this.log.info('Connect to server over websocket connection.');
|
|
86
|
-
isConnected = true;
|
|
87
|
-
// Send ping to server
|
|
88
|
-
this.sendPingToServer();
|
|
89
|
-
// Start Heartbeat
|
|
90
|
-
this.wsHeartbeat();
|
|
85
|
+
// MQTT Client
|
|
86
|
+
mqttClient.on('connect', () => {
|
|
87
|
+
this.log.info(`Connect to Zigbee2MQTT over ${this.config.connectionType == 'exmqtt' ? 'external mqtt' : 'internal mqtt'} connection.`);
|
|
91
88
|
});
|
|
92
|
-
wsClient.on('pong', () => {
|
|
93
|
-
//this.logDebug('Receive pong from server');
|
|
94
|
-
this.wsHeartbeat();
|
|
95
|
-
});
|
|
96
|
-
// On Close
|
|
97
|
-
wsClient.on('close', async () => {
|
|
98
|
-
this.setState('info.connection', false, true);
|
|
99
|
-
this.log.warn('Websocket disconnectet');
|
|
100
|
-
await this.setAllAvailableToFalse();
|
|
101
|
-
clearTimeout(ping);
|
|
102
|
-
clearTimeout(pingTimeout);
|
|
103
|
-
isConnected = false;
|
|
104
89
|
|
|
105
|
-
|
|
106
|
-
this.autoRestart();
|
|
107
|
-
}
|
|
108
|
-
});
|
|
90
|
+
mqttClient.subscribe('zigbee2mqtt/#');
|
|
109
91
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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);
|
|
95
|
+
});
|
|
114
96
|
}
|
|
115
|
-
|
|
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
|
+
}
|
|
116
103
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
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
|
+
}
|
|
124
110
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
pingTimeout = setTimeout(() => {
|
|
128
|
-
this.logDebug('Websocked connection timed out');
|
|
129
|
-
wsClient.terminate();
|
|
130
|
-
clearTimeout(checkAvailableTimout);
|
|
131
|
-
}, wsHeartbeatIntervall + 1000);
|
|
132
|
-
}
|
|
111
|
+
websocketController = new WebsocketController(this);
|
|
112
|
+
const wsClient = await websocketController.initWsClient(this.config.wsServerIP, this.config.wsServerPort);
|
|
133
113
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
});
|
|
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
|
+
}
|
|
139
128
|
}
|
|
140
129
|
|
|
141
130
|
async messageParse(message) {
|
|
@@ -145,36 +134,45 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
145
134
|
case 'bridge/config':
|
|
146
135
|
break;
|
|
147
136
|
case 'bridge/info':
|
|
137
|
+
if (showInfo) {
|
|
138
|
+
zigbee2mqttInfo(messageObj.payload, this.log);
|
|
139
|
+
checkConfig(messageObj.payload.config, this.log);
|
|
140
|
+
showInfo = false;
|
|
141
|
+
}
|
|
148
142
|
break;
|
|
149
143
|
case 'bridge/state':
|
|
144
|
+
if (messageObj.payload.state != 'online') {
|
|
145
|
+
statesController.setAllAvailableToFalse();
|
|
146
|
+
}
|
|
147
|
+
this.setStateChangedAsync('info.connection', messageObj.payload.state == 'online', true);
|
|
150
148
|
break;
|
|
151
149
|
case 'bridge/devices':
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
await
|
|
155
|
-
|
|
156
|
-
this.subscribeWritableStates();
|
|
157
|
-
createDevicesOrReady = true;
|
|
158
|
-
|
|
159
|
-
// Now process all entries in the states queue
|
|
160
|
-
while (incStatsQueue.length > 0) {
|
|
161
|
-
this.processDeviceMessage(incStatsQueue.shift());
|
|
162
|
-
}
|
|
150
|
+
await deviceController.createDeviceDefinitions(messageObj.payload);
|
|
151
|
+
await deviceController.createOrUpdateDevices();
|
|
152
|
+
await statesController.subscribeWritableStates();
|
|
153
|
+
statesController.processQueue();
|
|
163
154
|
break;
|
|
164
155
|
case 'bridge/groups':
|
|
165
|
-
await
|
|
166
|
-
await
|
|
167
|
-
|
|
156
|
+
await deviceController.createGroupDefinitions(messageObj.payload);
|
|
157
|
+
await deviceController.createOrUpdateDevices();
|
|
158
|
+
await statesController.subscribeWritableStates();
|
|
159
|
+
statesController.processQueue();
|
|
168
160
|
break;
|
|
169
161
|
case 'bridge/event':
|
|
162
|
+
deviceController.processRemoveEvent(messageObj);
|
|
170
163
|
break;
|
|
171
164
|
case 'bridge/extensions':
|
|
172
165
|
break;
|
|
173
166
|
case 'bridge/logging':
|
|
174
|
-
if (
|
|
175
|
-
|
|
167
|
+
if (this.config.proxyZ2MLogs == true) {
|
|
168
|
+
z2mController.proxyZ2MLogs(messageObj);
|
|
176
169
|
}
|
|
177
170
|
break;
|
|
171
|
+
case 'bridge/response/device/rename':
|
|
172
|
+
await deviceController.renameDeviceInCache(messageObj);
|
|
173
|
+
await deviceController.createOrUpdateDevices();
|
|
174
|
+
statesController.processQueue();
|
|
175
|
+
break;
|
|
178
176
|
case 'bridge/response/networkmap':
|
|
179
177
|
break;
|
|
180
178
|
case 'bridge/response/touchlink/scan':
|
|
@@ -188,296 +186,34 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
188
186
|
// {"payload":{"state":"online"},"topic":"FL.Licht.Links/availability"} ----> {"payload":{"available":true},"topic":"FL.Licht.Links"}
|
|
189
187
|
if (messageObj.topic.endsWith('/availability')) {
|
|
190
188
|
const topicSplit = messageObj.topic.split('/');
|
|
189
|
+
|
|
190
|
+
// If an availability message for an old device ID comes with a payload of NULL, this is the indicator that a device has been unnamed.
|
|
191
|
+
// If this is then still available in the cache, the messages must first be cached.
|
|
192
|
+
if (messageObj.payload == 'null') {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
191
196
|
if (topicSplit.length == 2 && messageObj.payload && messageObj.payload.state) {
|
|
192
197
|
const newMessage = {
|
|
193
198
|
payload: { available: messageObj.payload.state == 'online' },
|
|
194
199
|
topic: topicSplit[0]
|
|
195
200
|
};
|
|
196
|
-
|
|
197
|
-
if (createDevicesOrReady == false) {
|
|
198
|
-
incStatsQueue[incStatsQueue.length] = newMessage;
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
this.processDeviceMessage(newMessage);
|
|
201
|
+
statesController.processDeviceMessage(newMessage);
|
|
202
202
|
}
|
|
203
203
|
// States
|
|
204
204
|
} else if (!messageObj.topic.includes('/')) {
|
|
205
|
-
|
|
206
|
-
if (createDevicesOrReady == false) {
|
|
207
|
-
incStatsQueue[incStatsQueue.length] = messageObj;
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
this.processDeviceMessage(messageObj);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async processDeviceMessage(messageObj) {
|
|
218
|
-
this.logDebug(`processDeviceMessage -> messageObj: ${JSON.stringify(messageObj)}`);
|
|
219
|
-
// Is payload present?
|
|
220
|
-
if (messageObj.payload == '') {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const device = groupCache.concat(deviceCache).find(x => x.id == messageObj.topic);
|
|
225
|
-
if (device) {
|
|
226
|
-
this.logDebug(`processDeviceMessage -> device: ${JSON.stringify(device)}`);
|
|
227
|
-
try {
|
|
228
|
-
this.setDeviceState(messageObj, device);
|
|
229
|
-
} catch (error) {
|
|
230
|
-
adapter.log.error(error);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
adapter.log.warn(`Device: ${messageObj.topic} not found`);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
async setDeviceState(messageObj, device) {
|
|
239
|
-
|
|
240
|
-
if (debugDevices.includes(device.ieee_address)) {
|
|
241
|
-
this.log.warn(`--->>> fromZ2M -> ${device.ieee_address} states: ${JSON.stringify(messageObj)}`);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
for (const [key, value] of Object.entries(messageObj.payload)) {
|
|
245
|
-
this.logDebug(`setDeviceState -> key: ${key}`);
|
|
246
|
-
this.logDebug(`setDeviceState -> value: ${JSON.stringify(value)}`);
|
|
247
|
-
|
|
248
|
-
let states;
|
|
249
|
-
if (key == 'action') {
|
|
250
|
-
states = device.states.filter(x => (x.prop && x.prop == key) && x.id == value);
|
|
251
|
-
} else {
|
|
252
|
-
states = device.states.filter(x => (x.prop && x.prop == key) || x.id == key);
|
|
253
|
-
}
|
|
254
|
-
this.logDebug(`setDeviceState -> states: ${JSON.stringify(states)}`);
|
|
255
|
-
|
|
256
|
-
for (const state of states) {
|
|
257
|
-
if (!state) {
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const stateName = `${device.ieee_address}.${state.id}`;
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
if (state.getter) {
|
|
265
|
-
await this.setStateAsync(stateName, state.getter(messageObj.payload), true);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
await this.setStateAsync(stateName, value, true);
|
|
205
|
+
statesController.processDeviceMessage(messageObj);
|
|
269
206
|
}
|
|
270
|
-
} catch (err) {
|
|
271
|
-
this.log.warn(`Can not set ${stateName}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async createDeviceDefinitions(cache, exposes) {
|
|
278
|
-
clearArray(cache);
|
|
279
|
-
for (const expose of exposes) {
|
|
280
|
-
if (expose.definition != null) {
|
|
281
|
-
// search for scenes in the endpoints and build them into an array
|
|
282
|
-
let scenes = [];
|
|
283
|
-
for (const key in expose.endpoints) {
|
|
284
|
-
if (expose.endpoints[key].scenes) {
|
|
285
|
-
scenes = scenes.concat(expose.endpoints[key].scenes);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
await defineDeviceFromExposes(cache, expose.friendly_name, expose.ieee_address, expose.definition, expose.power_source, scenes, useKelvin);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async createGroupDefinitions(cache, exposes) {
|
|
295
|
-
clearArray(cache);
|
|
296
|
-
for (const expose of exposes) {
|
|
297
|
-
await defineGroupDevice(cache, expose.friendly_name, `group_${expose.id}`, expose.scenes, useKelvin);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async createOrUpdateDevices(cache) {
|
|
302
|
-
for (const device of cache) {
|
|
303
|
-
const deviceName = device.id == device.ieee_address ? '' : device.id;
|
|
304
|
-
if (!createCache[device.ieee_address] || createCache[device.ieee_address].common.name != deviceName) {
|
|
305
|
-
const deviceObj = {
|
|
306
|
-
type: 'device',
|
|
307
|
-
common: {
|
|
308
|
-
name: deviceName,
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
native: {}
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
if (!device.ieee_address.includes('group_')) {
|
|
315
|
-
deviceObj.common.statusStates = {
|
|
316
|
-
onlineId: `${this.name}.${this.instance}.${device.ieee_address}.available`
|
|
317
|
-
};
|
|
318
207
|
}
|
|
319
|
-
|
|
320
|
-
//@ts-ignore
|
|
321
|
-
await this.extendObjectAsync(device.ieee_address, deviceObj);
|
|
322
|
-
createCache[device.ieee_address] = deviceObj;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Here it is checked whether the scenes match the current data from z2m.
|
|
326
|
-
// If necessary, scenes are automatically deleted from ioBroker.
|
|
327
|
-
const sceneStates = await this.getStatesAsync(`${device.ieee_address}.scene_*`);
|
|
328
|
-
const sceneIDs = Object.keys(sceneStates);
|
|
329
|
-
for (const sceneID of sceneIDs) {
|
|
330
|
-
const stateID = sceneID.split('.')[3];
|
|
331
|
-
if (device.states.find(x => x.id == stateID) == null) {
|
|
332
|
-
this.delObject(sceneID);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
for (const state of device.states) {
|
|
337
|
-
if (!createCache[device.ieee_address][state.id] || createCache[device.ieee_address][state.id].name != state.name) {
|
|
338
|
-
const iobState = await this.copyAndCleanStateObj(state);
|
|
339
|
-
this.logDebug(`Orig. state: ${JSON.stringify(state)}`);
|
|
340
|
-
this.logDebug(`Cleaned. state: ${JSON.stringify(iobState)}`);
|
|
341
|
-
|
|
342
|
-
await this.extendObjectAsync(`${device.ieee_address}.${state.id}`, {
|
|
343
|
-
type: 'state',
|
|
344
|
-
common: iobState,
|
|
345
|
-
native: {},
|
|
346
|
-
});
|
|
347
|
-
createCache[device.ieee_address][state.id] = state.name;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
async copyAndCleanStateObj(state) {
|
|
354
|
-
const iobState = { ...state };
|
|
355
|
-
const blacklistedKeys = [
|
|
356
|
-
'setter',
|
|
357
|
-
'setterOpt',
|
|
358
|
-
'getter',
|
|
359
|
-
'setattr',
|
|
360
|
-
'readable',
|
|
361
|
-
'writable',
|
|
362
|
-
'isOption',
|
|
363
|
-
'inOptions'
|
|
364
|
-
];
|
|
365
|
-
for (const blacklistedKey of blacklistedKeys) {
|
|
366
|
-
delete iobState[blacklistedKey];
|
|
367
|
-
}
|
|
368
|
-
return iobState;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async subscribeWritableStates() {
|
|
372
|
-
await this.unsubscribeObjectsAsync('*');
|
|
373
|
-
|
|
374
|
-
for (const device of groupCache.concat(deviceCache)) {
|
|
375
|
-
for (const state of device.states) {
|
|
376
|
-
if (state.write == true) {
|
|
377
|
-
this.subscribeStatesAsync(`${device.ieee_address}.${state.id}`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
async createZ2MMessage(id, state) {
|
|
384
|
-
|
|
385
|
-
const splitedID = id.split('.');
|
|
386
|
-
|
|
387
|
-
if (splitedID.length < 4) {
|
|
388
|
-
this.log.warn(`state ${id} not valid`);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const ieee_address = splitedID[2];
|
|
393
|
-
const stateName = splitedID[3];
|
|
394
|
-
|
|
395
|
-
const device = groupCache.concat(deviceCache).find(d => d.ieee_address == ieee_address);
|
|
396
|
-
|
|
397
|
-
if (!device) {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const deviceState = device.states.find(s => s.id == stateName);
|
|
402
|
-
|
|
403
|
-
if (!deviceState) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
let stateVal = state.val;
|
|
408
|
-
if (deviceState.setter) {
|
|
409
|
-
stateVal = deviceState.setter(state.val);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
let stateID = deviceState.id;
|
|
414
|
-
if (deviceState.prop) {
|
|
415
|
-
stateID = deviceState.prop;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
let topic = `${device.ieee_address}/set`;
|
|
419
|
-
if (device.ieee_address.includes('group_')) {
|
|
420
|
-
topic = `${device.id}/set`;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const controlObj = {
|
|
424
|
-
payload: {
|
|
425
|
-
[stateID]: stateVal
|
|
426
|
-
},
|
|
427
|
-
topic: topic
|
|
428
|
-
};
|
|
429
|
-
// set stats with the mentioned role or ids always immediately to ack = true, because these are not reported back by Zigbee2MQTT
|
|
430
|
-
if (isConnected == true && (['button'].includes(deviceState.role) || ['brightness_move', 'color_temp_move'].includes(stateID))) {
|
|
431
|
-
this.setState(id, state, true);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return JSON.stringify(controlObj);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
async proxyZ2MLogs(messageObj) {
|
|
438
|
-
this.logDebug(`proxyZ2MLogs -> messageObj: ${JSON.stringify(messageObj)}`);
|
|
439
|
-
|
|
440
|
-
const logMessage = messageObj.payload.message;
|
|
441
|
-
if (logfilter.some(x => logMessage.includes(x))) {
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const logLevel = messageObj.payload.level;
|
|
446
|
-
switch (logLevel) {
|
|
447
|
-
case 'debug':
|
|
448
|
-
case 'info':
|
|
449
|
-
case 'error':
|
|
450
|
-
this.log[logLevel](logMessage);
|
|
451
|
-
break;
|
|
452
|
-
case 'warning':
|
|
453
|
-
this.log.warn(logMessage);
|
|
454
208
|
break;
|
|
455
209
|
}
|
|
456
210
|
}
|
|
457
211
|
|
|
458
|
-
async logDebug(message) {
|
|
459
|
-
if (debugLogEnabled == true) {
|
|
460
|
-
this.log.debug(message);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
async setAllAvailableToFalse() {
|
|
465
|
-
for (const device of deviceCache) {
|
|
466
|
-
for (const state of device.states) {
|
|
467
|
-
if (state.id == 'available') {
|
|
468
|
-
await this.setStateAsync(`${device.ieee_address}.${state.id}`, false, true);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
212
|
async onUnload(callback) {
|
|
475
213
|
try {
|
|
476
|
-
await
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
clearTimeout(autoRestartTimeout);
|
|
480
|
-
clearTimeout(checkAvailableTimout);
|
|
214
|
+
await statesController.setAllAvailableToFalse();
|
|
215
|
+
await websocketController.allTimerClear();
|
|
216
|
+
await statesController.allTimerClear();
|
|
481
217
|
callback();
|
|
482
218
|
} catch (e) {
|
|
483
219
|
callback();
|
|
@@ -486,16 +222,23 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
486
222
|
|
|
487
223
|
async onStateChange(id, state) {
|
|
488
224
|
if (state && state.ack == false) {
|
|
489
|
-
const message = await this.createZ2MMessage(id, state);
|
|
490
|
-
wsClient.send(message);
|
|
491
|
-
|
|
492
225
|
if (id.includes('info.debugmessages')) {
|
|
493
|
-
debugDevices = state.val;
|
|
226
|
+
logCustomizations.debugDevices = state.val;
|
|
494
227
|
this.setState(id, state.val, true);
|
|
228
|
+
return;
|
|
495
229
|
}
|
|
496
230
|
if (id.includes('info.logfilter')) {
|
|
497
|
-
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
|
|
498
232
|
this.setState(id, state.val, true);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const message = await z2mController.createZ2MMessage(id, state) || { topic: '', 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));
|
|
499
242
|
}
|
|
500
243
|
}
|
|
501
244
|
}
|
|
@@ -511,4 +254,4 @@ if (require.main !== module) {
|
|
|
511
254
|
} else {
|
|
512
255
|
// otherwise start the instance directly
|
|
513
256
|
new Zigbee2mqtt();
|
|
514
|
-
}
|
|
257
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zigbee2mqtt",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Zigbee2MQTT adapter for ioBroker",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Dennis Rathjen",
|
|
@@ -19,12 +19,16 @@
|
|
|
19
19
|
"url": "https://github.com/o0shojo0o/ioBroker.zigbee2mqtt.git"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@
|
|
23
|
-
"
|
|
24
|
-
"
|
|
22
|
+
"@iobroker/adapter-core": "^2.6.7",
|
|
23
|
+
"aedes": "^0.48.0",
|
|
24
|
+
"aedes-persistence-nedb": "^2.0.3",
|
|
25
|
+
"mqtt": "^4.3.7",
|
|
26
|
+
"net": "^1.0.2",
|
|
25
27
|
"ws": "^8.9.0"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
30
|
+
"@alcalzone/release-script-plugin-iobroker": "^3.5.9",
|
|
31
|
+
"@alcalzone/release-script-plugin-license": "^3.5.9",
|
|
28
32
|
"@alcalzone/release-script": "^3.5.9",
|
|
29
33
|
"@iobroker/adapter-dev": "^1.1.0",
|
|
30
34
|
"@iobroker/testing": "^4.1.0",
|
|
@@ -32,19 +36,19 @@
|
|
|
32
36
|
"@types/chai": "^4.3.3",
|
|
33
37
|
"@types/chai-as-promised": "^7.1.5",
|
|
34
38
|
"@types/mocha": "^10.0.0",
|
|
35
|
-
"@types/node": "^18.
|
|
39
|
+
"@types/node": "^18.8.3",
|
|
36
40
|
"@types/proxyquire": "^1.3.28",
|
|
37
41
|
"@types/sinon": "^10.0.13",
|
|
38
42
|
"@types/sinon-chai": "^3.2.8",
|
|
39
43
|
"chai": "^4.3.6",
|
|
40
44
|
"chai-as-promised": "^7.1.1",
|
|
41
|
-
"eslint": "^8.
|
|
45
|
+
"eslint": "^8.25.0",
|
|
42
46
|
"eslint-config-prettier": "^8.5.0",
|
|
43
47
|
"eslint-plugin-prettier": "^4.2.1",
|
|
44
48
|
"mocha": "^10.0.0",
|
|
45
49
|
"prettier": "^2.7.1",
|
|
46
50
|
"proxyquire": "^2.1.3",
|
|
47
|
-
"sinon": "^14.0.
|
|
51
|
+
"sinon": "^14.0.1",
|
|
48
52
|
"sinon-chai": "^3.7.0",
|
|
49
53
|
"typescript": "~4.8.4"
|
|
50
54
|
},
|