iobroker.zigbee2mqtt 0.2.0 → 1.0.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 +16 -0
- package/admin/i18n/de/translations.json +8 -4
- package/admin/i18n/en/translations.json +8 -4
- package/admin/i18n/es/translations.json +8 -4
- package/admin/i18n/fr/translations.json +8 -4
- package/admin/i18n/it/translations.json +8 -4
- package/admin/i18n/nl/translations.json +8 -4
- package/admin/i18n/pl/translations.json +8 -4
- package/admin/i18n/pt/translations.json +8 -4
- package/admin/i18n/ru/translations.json +8 -4
- package/admin/i18n/zh-cn/translations.json +8 -4
- package/admin/jsonConfig.json +58 -9
- package/io-package.json +27 -4
- package/lib/check.js +37 -0
- package/lib/deviceController.js +182 -0
- package/lib/exposes.js +34 -3
- package/lib/messages.js +18 -0
- package/lib/states.js +2 -0
- package/lib/statesController.js +127 -0
- package/lib/utils.js +20 -11
- package/lib/z2mController.js +80 -0
- package/main.js +80 -378
- package/package.json +9 -5
- package/lib/groups.js +0 -68
package/main.js
CHANGED
|
@@ -7,16 +7,17 @@
|
|
|
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
|
-
|
|
19
|
-
|
|
10
|
+
const NedbPersistence = require('aedes-persistence-nedb');
|
|
11
|
+
const mqtt = require('mqtt');
|
|
12
|
+
const checkConfig = require('./lib/check').checkConfig;
|
|
13
|
+
const adapterInfo = require('./lib/messages').adapterInfo;
|
|
14
|
+
const zigbee2mqttInfo = require('./lib/messages').zigbee2mqttInfo;
|
|
15
|
+
const Z2mController = require('./lib/z2mController').Z2mController;
|
|
16
|
+
const DeviceController = require('./lib/deviceController').DeviceController;
|
|
17
|
+
const StatesController = require('./lib/statesController').StatesController;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
let mqttClient;
|
|
20
21
|
// eslint-disable-next-line prefer-const
|
|
21
22
|
let deviceCache = [];
|
|
22
23
|
// eslint-disable-next-line prefer-const
|
|
@@ -24,14 +25,13 @@ let groupCache = [];
|
|
|
24
25
|
let ping;
|
|
25
26
|
let pingTimeout;
|
|
26
27
|
let autoRestartTimeout;
|
|
27
|
-
const wsHeartbeatIntervall = 5000;
|
|
28
|
-
const restartTimeout = 1000;
|
|
29
|
-
let debugLogEnabled;
|
|
30
|
-
let proxyZ2MLogsEnabled;
|
|
31
28
|
let checkAvailableTimout;
|
|
32
29
|
let debugDevices = '';
|
|
33
30
|
let logfilter = [];
|
|
34
|
-
let
|
|
31
|
+
let showInfo = true;
|
|
32
|
+
let statesController;
|
|
33
|
+
let deviceController;
|
|
34
|
+
let z2mController;
|
|
35
35
|
|
|
36
36
|
class Zigbee2mqtt extends core.Adapter {
|
|
37
37
|
|
|
@@ -46,20 +46,16 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
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);
|
|
54
|
+
|
|
49
55
|
// 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'}`);
|
|
56
|
+
adapterInfo(this.config, this.log);
|
|
56
57
|
|
|
57
58
|
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
59
|
|
|
64
60
|
const debugDevicesState = await this.getStateAsync('info.debugmessages');
|
|
65
61
|
if (debugDevicesState && debugDevicesState.val) {
|
|
@@ -71,72 +67,31 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
71
67
|
logfilter = String(logfilterState.val).split(';').filter(x => x); // filter removes empty strings here
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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();
|
|
91
|
-
});
|
|
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
|
-
|
|
105
|
-
if (wsClient.readyState === WebSocket.CLOSED) {
|
|
106
|
-
this.autoRestart();
|
|
107
|
-
}
|
|
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: ''
|
|
108
78
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async wsHeartbeat() {
|
|
126
|
-
clearTimeout(pingTimeout);
|
|
127
|
-
pingTimeout = setTimeout(() => {
|
|
128
|
-
this.logDebug('Websocked connection timed out');
|
|
129
|
-
wsClient.terminate();
|
|
130
|
-
clearTimeout(checkAvailableTimout);
|
|
131
|
-
}, wsHeartbeatIntervall + 1000);
|
|
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
|
+
}
|
|
85
|
+
|
|
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
|
+
});
|
|
132
93
|
}
|
|
133
94
|
|
|
134
|
-
async autoRestart() {
|
|
135
|
-
this.log.warn(`Start try again in ${restartTimeout / 1000} seconds...`);
|
|
136
|
-
autoRestartTimeout = setTimeout(() => {
|
|
137
|
-
this.onReady();
|
|
138
|
-
}, restartTimeout);
|
|
139
|
-
}
|
|
140
95
|
|
|
141
96
|
async messageParse(message) {
|
|
142
97
|
const messageObj = JSON.parse(message);
|
|
@@ -145,36 +100,41 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
145
100
|
case 'bridge/config':
|
|
146
101
|
break;
|
|
147
102
|
case 'bridge/info':
|
|
103
|
+
if (showInfo) {
|
|
104
|
+
zigbee2mqttInfo(messageObj.payload, this.log);
|
|
105
|
+
checkConfig(messageObj.payload.config, this.log);
|
|
106
|
+
showInfo = false;
|
|
107
|
+
}
|
|
148
108
|
break;
|
|
149
109
|
case 'bridge/state':
|
|
150
110
|
break;
|
|
151
111
|
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
|
-
}
|
|
112
|
+
await deviceController.createDeviceDefinitions(messageObj.payload);
|
|
113
|
+
await deviceController.createOrUpdateDevices();
|
|
114
|
+
await statesController.subscribeWritableStates();
|
|
115
|
+
statesController.processQueue();
|
|
163
116
|
break;
|
|
164
117
|
case 'bridge/groups':
|
|
165
|
-
await
|
|
166
|
-
await
|
|
167
|
-
|
|
118
|
+
await deviceController.createGroupDefinitions(messageObj.payload);
|
|
119
|
+
await deviceController.createOrUpdateDevices();
|
|
120
|
+
await statesController.subscribeWritableStates();
|
|
121
|
+
statesController.processQueue();
|
|
168
122
|
break;
|
|
169
123
|
case 'bridge/event':
|
|
124
|
+
deviceController.processRemoveEvent(messageObj);
|
|
170
125
|
break;
|
|
171
126
|
case 'bridge/extensions':
|
|
172
127
|
break;
|
|
173
128
|
case 'bridge/logging':
|
|
174
|
-
if (
|
|
175
|
-
|
|
129
|
+
if (this.config.proxyZ2MLogs == true) {
|
|
130
|
+
z2mController.proxyZ2MLogs(messageObj);
|
|
176
131
|
}
|
|
177
132
|
break;
|
|
133
|
+
case 'bridge/response/device/rename':
|
|
134
|
+
await deviceController.renameDeviceInCache(messageObj);
|
|
135
|
+
await deviceController.createOrUpdateDevices();
|
|
136
|
+
statesController.processQueue();
|
|
137
|
+
break;
|
|
178
138
|
case 'bridge/response/networkmap':
|
|
179
139
|
break;
|
|
180
140
|
case 'bridge/response/touchlink/scan':
|
|
@@ -188,292 +148,32 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
188
148
|
// {"payload":{"state":"online"},"topic":"FL.Licht.Links/availability"} ----> {"payload":{"available":true},"topic":"FL.Licht.Links"}
|
|
189
149
|
if (messageObj.topic.endsWith('/availability')) {
|
|
190
150
|
const topicSplit = messageObj.topic.split('/');
|
|
151
|
+
|
|
152
|
+
// 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.
|
|
153
|
+
// If this is then still available in the cache, the messages must first be cached.
|
|
154
|
+
if (messageObj.payload == 'null') {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
|
|
191
158
|
if (topicSplit.length == 2 && messageObj.payload && messageObj.payload.state) {
|
|
192
159
|
const newMessage = {
|
|
193
160
|
payload: { available: messageObj.payload.state == 'online' },
|
|
194
161
|
topic: topicSplit[0]
|
|
195
162
|
};
|
|
196
|
-
|
|
197
|
-
if (createDevicesOrReady == false) {
|
|
198
|
-
incStatsQueue[incStatsQueue.length] = newMessage;
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
this.processDeviceMessage(newMessage);
|
|
163
|
+
statesController.processDeviceMessage(newMessage);
|
|
202
164
|
}
|
|
203
165
|
// States
|
|
204
166
|
} else if (!messageObj.topic.includes('/')) {
|
|
205
|
-
|
|
206
|
-
if (createDevicesOrReady == false) {
|
|
207
|
-
incStatsQueue[incStatsQueue.length] = messageObj;
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
this.processDeviceMessage(messageObj);
|
|
167
|
+
statesController.processDeviceMessage(messageObj);
|
|
211
168
|
}
|
|
212
169
|
}
|
|
213
170
|
break;
|
|
214
171
|
}
|
|
215
172
|
}
|
|
216
173
|
|
|
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);
|
|
269
|
-
}
|
|
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
|
-
}
|
|
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
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
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
174
|
async onUnload(callback) {
|
|
475
175
|
try {
|
|
476
|
-
await
|
|
176
|
+
await statesController.setAllAvailableToFalse();
|
|
477
177
|
clearTimeout(ping);
|
|
478
178
|
clearTimeout(pingTimeout);
|
|
479
179
|
clearTimeout(autoRestartTimeout);
|
|
@@ -486,17 +186,19 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
486
186
|
|
|
487
187
|
async onStateChange(id, state) {
|
|
488
188
|
if (state && state.ack == false) {
|
|
489
|
-
const message = await this.createZ2MMessage(id, state);
|
|
490
|
-
wsClient.send(message);
|
|
491
|
-
|
|
492
189
|
if (id.includes('info.debugmessages')) {
|
|
493
190
|
debugDevices = state.val;
|
|
494
191
|
this.setState(id, state.val, true);
|
|
192
|
+
return;
|
|
495
193
|
}
|
|
496
194
|
if (id.includes('info.logfilter')) {
|
|
497
195
|
logfilter = state.val.split(';').filter(x => x); // filter removes empty strings here
|
|
498
196
|
this.setState(id, state.val, true);
|
|
197
|
+
return;
|
|
499
198
|
}
|
|
199
|
+
|
|
200
|
+
const message = await z2mController.createZ2MMessage(id, state) || { topic: '', payload: '' };
|
|
201
|
+
mqttClient.publish('zigbee2mqtt/' + message.topic, JSON.stringify(message.payload));
|
|
500
202
|
}
|
|
501
203
|
}
|
|
502
204
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zigbee2mqtt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Zigbee2MQTT adapter for ioBroker",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Dennis Rathjen",
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@alcalzone/release-script-plugin-iobroker": "^3.5.9",
|
|
23
23
|
"@alcalzone/release-script-plugin-license": "^3.5.9",
|
|
24
|
-
"@iobroker/adapter-core": "^2.6.
|
|
24
|
+
"@iobroker/adapter-core": "^2.6.7",
|
|
25
|
+
"aedes": "^0.48.0",
|
|
26
|
+
"aedes-persistence-nedb": "^2.0.3",
|
|
27
|
+
"mqtt": "^4.3.7",
|
|
28
|
+
"net": "^1.0.2",
|
|
25
29
|
"ws": "^8.9.0"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
@@ -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
|
},
|
package/lib/groups.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
const states = require('./states').states;
|
|
2
|
-
const utils = require('./utils');
|
|
3
|
-
|
|
4
|
-
function defineGroupDevice(devices, groupID, ieee_address, scenes, useKelvin) {
|
|
5
|
-
const newDevice = {
|
|
6
|
-
id: groupID,
|
|
7
|
-
ieee_address: ieee_address,
|
|
8
|
-
icon: undefined,
|
|
9
|
-
states: [
|
|
10
|
-
states.state,
|
|
11
|
-
states.brightness,
|
|
12
|
-
states.color,
|
|
13
|
-
states.brightness_move,
|
|
14
|
-
states.colortemp_move,
|
|
15
|
-
],
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const colortemp = {
|
|
19
|
-
id: 'colortemp',
|
|
20
|
-
prop: 'color_temp',
|
|
21
|
-
name: 'Color temperature',
|
|
22
|
-
icon: undefined,
|
|
23
|
-
role: 'level.color.temperature',
|
|
24
|
-
write: true,
|
|
25
|
-
read: true,
|
|
26
|
-
type: 'number',
|
|
27
|
-
unit: useKelvin == true ? 'K' : 'mired',
|
|
28
|
-
setter: (value) => {
|
|
29
|
-
return utils.toMired(value);
|
|
30
|
-
},
|
|
31
|
-
getter: (payload) => {
|
|
32
|
-
if (useKelvin == true) {
|
|
33
|
-
return utils.miredKelvinConversion(payload.color_temp);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
return payload.color_temp;
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// @ts-ignore
|
|
42
|
-
newDevice.states.push(colortemp);
|
|
43
|
-
|
|
44
|
-
// Create buttons for scenes
|
|
45
|
-
for (const scene of scenes) {
|
|
46
|
-
const sceneSate = {
|
|
47
|
-
id: `scene_${scene.id}`,
|
|
48
|
-
prop: `scene_recall`,
|
|
49
|
-
name: scene.name,
|
|
50
|
-
icon: undefined,
|
|
51
|
-
role: 'button',
|
|
52
|
-
write: true,
|
|
53
|
-
read: true,
|
|
54
|
-
type: 'boolean',
|
|
55
|
-
setter: (value) => (value) ? scene.id : undefined
|
|
56
|
-
};
|
|
57
|
-
// @ts-ignore
|
|
58
|
-
newDevice.states.push(sceneSate);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// if the device is already present in the cache, remove it
|
|
62
|
-
utils.removeDeviceByIeee(devices, ieee_address);
|
|
63
|
-
devices.push(newDevice);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
module.exports = {
|
|
67
|
-
defineGroupDevice: defineGroupDevice,
|
|
68
|
-
};
|