iobroker.zigbee2mqtt 0.1.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 +30 -0
- package/admin/i18n/de/translations.json +11 -4
- package/admin/i18n/en/translations.json +10 -3
- package/admin/i18n/es/translations.json +10 -3
- package/admin/i18n/fr/translations.json +10 -3
- package/admin/i18n/it/translations.json +10 -3
- package/admin/i18n/nl/translations.json +10 -3
- package/admin/i18n/pl/translations.json +10 -3
- package/admin/i18n/pt/translations.json +10 -3
- package/admin/i18n/ru/translations.json +10 -3
- package/admin/i18n/zh-cn/translations.json +10 -3
- package/admin/jsonConfig.json +61 -7
- package/io-package.json +71 -9
- package/lib/check.js +37 -0
- package/lib/deviceController.js +182 -0
- package/lib/exposes.js +229 -166
- package/lib/messages.js +18 -0
- package/lib/states.js +61 -36
- package/lib/statesController.js +127 -0
- package/lib/utils.js +21 -12
- package/lib/z2mController.js +80 -0
- package/main.js +107 -395
- package/package.json +9 -5
- package/lib/groups.js +0 -45
package/main.js
CHANGED
|
@@ -7,31 +7,31 @@
|
|
|
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
|
-
const
|
|
18
|
-
|
|
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;
|
|
19
21
|
// eslint-disable-next-line prefer-const
|
|
20
22
|
let deviceCache = [];
|
|
21
23
|
// eslint-disable-next-line prefer-const
|
|
22
24
|
let groupCache = [];
|
|
23
|
-
const lastSeenCache = {};
|
|
24
25
|
let ping;
|
|
25
26
|
let pingTimeout;
|
|
26
27
|
let autoRestartTimeout;
|
|
27
|
-
const wsHeartbeatIntervall = 5000;
|
|
28
|
-
const restartTimeout = 1000;
|
|
29
|
-
const deviceAvailableTimeout = 10 * 60; // 10 Minutes
|
|
30
|
-
const batteryDeviceAvailableTimeout = 24 * 60 * 60; // 24 Hours
|
|
31
|
-
const checkAvailableInterval = 30 * 1000; // 10 Seconds
|
|
32
|
-
let debugLogEnabled;
|
|
33
|
-
let proxyZ2MLogsEnabled;
|
|
34
28
|
let checkAvailableTimout;
|
|
29
|
+
let debugDevices = '';
|
|
30
|
+
let logfilter = [];
|
|
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,81 +46,52 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async onReady() {
|
|
49
|
-
// Initialize your adapter here
|
|
50
|
-
adapter = this;
|
|
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
49
|
|
|
56
|
-
this.setStateAsync('info.connection', false, true);
|
|
57
|
-
this.createWsClient(this.config.server, this.config.port);
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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);
|
|
62
54
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
wsClient = new WebSocket(`ws://${server}:${port}/api`);
|
|
66
|
-
wsClient.on('open', () => {
|
|
67
|
-
this.logDebug('Websocket connectet');
|
|
68
|
-
// Set connection state
|
|
69
|
-
this.setState('info.connection', true, true);
|
|
70
|
-
this.log.info('Connect to server over websocket connection.');
|
|
71
|
-
// Send ping to server
|
|
72
|
-
this.sendPingToServer();
|
|
73
|
-
// Start Heartbeat
|
|
74
|
-
this.wsHeartbeat();
|
|
75
|
-
// Start CheckAvailableTimer
|
|
76
|
-
this.checkAvailableTimer();
|
|
77
|
-
});
|
|
78
|
-
wsClient.on('pong', () => {
|
|
79
|
-
//this.logDebug('Receive pong from server');
|
|
80
|
-
this.wsHeartbeat();
|
|
81
|
-
});
|
|
82
|
-
// On Close
|
|
83
|
-
wsClient.on('close', () => {
|
|
84
|
-
this.setState('info.connection', false, true);
|
|
85
|
-
this.log.warn('Websocket disconnectet');
|
|
86
|
-
clearTimeout(ping);
|
|
87
|
-
clearTimeout(pingTimeout);
|
|
55
|
+
// Initialize your adapter here
|
|
56
|
+
adapterInfo(this.config, this.log);
|
|
88
57
|
|
|
89
|
-
|
|
90
|
-
this.autoRestart();
|
|
91
|
-
}
|
|
92
|
-
});
|
|
58
|
+
this.setStateAsync('info.connection', false, true);
|
|
93
59
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this.logDebug(err);
|
|
60
|
+
const debugDevicesState = await this.getStateAsync('info.debugmessages');
|
|
61
|
+
if (debugDevicesState && debugDevicesState.val) {
|
|
62
|
+
debugDevices = String(debugDevicesState.val);
|
|
98
63
|
}
|
|
99
|
-
}
|
|
100
64
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.sendPingToServer();
|
|
106
|
-
}, wsHeartbeatIntervall);
|
|
107
|
-
}
|
|
65
|
+
const logfilterState = await this.getStateAsync('info.logfilter');
|
|
66
|
+
if (logfilterState && logfilterState.val) {
|
|
67
|
+
logfilter = String(logfilterState.val).split(';').filter(x => x); // filter removes empty strings here
|
|
68
|
+
}
|
|
108
69
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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: ''
|
|
78
|
+
});
|
|
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
|
+
});
|
|
116
93
|
}
|
|
117
94
|
|
|
118
|
-
async autoRestart() {
|
|
119
|
-
this.log.warn(`Start try again in ${restartTimeout / 1000} seconds...`);
|
|
120
|
-
autoRestartTimeout = setTimeout(() => {
|
|
121
|
-
this.onReady();
|
|
122
|
-
}, restartTimeout);
|
|
123
|
-
}
|
|
124
95
|
|
|
125
96
|
async messageParse(message) {
|
|
126
97
|
const messageObj = JSON.parse(message);
|
|
@@ -129,34 +100,41 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
129
100
|
case 'bridge/config':
|
|
130
101
|
break;
|
|
131
102
|
case 'bridge/info':
|
|
103
|
+
if (showInfo) {
|
|
104
|
+
zigbee2mqttInfo(messageObj.payload, this.log);
|
|
105
|
+
checkConfig(messageObj.payload.config, this.log);
|
|
106
|
+
showInfo = false;
|
|
107
|
+
}
|
|
132
108
|
break;
|
|
133
109
|
case 'bridge/state':
|
|
134
110
|
break;
|
|
135
111
|
case 'bridge/devices':
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
await
|
|
139
|
-
|
|
140
|
-
createDevicesOrReady = true;
|
|
141
|
-
|
|
142
|
-
// Now process all entries in the states queue
|
|
143
|
-
while (incStatsQueue.length > 0) {
|
|
144
|
-
this.processDeviceMessage(incStatsQueue.shift());
|
|
145
|
-
}
|
|
112
|
+
await deviceController.createDeviceDefinitions(messageObj.payload);
|
|
113
|
+
await deviceController.createOrUpdateDevices();
|
|
114
|
+
await statesController.subscribeWritableStates();
|
|
115
|
+
statesController.processQueue();
|
|
146
116
|
break;
|
|
147
117
|
case 'bridge/groups':
|
|
148
|
-
await
|
|
149
|
-
await
|
|
118
|
+
await deviceController.createGroupDefinitions(messageObj.payload);
|
|
119
|
+
await deviceController.createOrUpdateDevices();
|
|
120
|
+
await statesController.subscribeWritableStates();
|
|
121
|
+
statesController.processQueue();
|
|
150
122
|
break;
|
|
151
123
|
case 'bridge/event':
|
|
124
|
+
deviceController.processRemoveEvent(messageObj);
|
|
152
125
|
break;
|
|
153
126
|
case 'bridge/extensions':
|
|
154
127
|
break;
|
|
155
128
|
case 'bridge/logging':
|
|
156
|
-
if (
|
|
157
|
-
|
|
129
|
+
if (this.config.proxyZ2MLogs == true) {
|
|
130
|
+
z2mController.proxyZ2MLogs(messageObj);
|
|
158
131
|
}
|
|
159
132
|
break;
|
|
133
|
+
case 'bridge/response/device/rename':
|
|
134
|
+
await deviceController.renameDeviceInCache(messageObj);
|
|
135
|
+
await deviceController.createOrUpdateDevices();
|
|
136
|
+
statesController.processQueue();
|
|
137
|
+
break;
|
|
160
138
|
case 'bridge/response/networkmap':
|
|
161
139
|
break;
|
|
162
140
|
case 'bridge/response/touchlink/scan':
|
|
@@ -166,314 +144,36 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
166
144
|
case 'bridge/response/touchlink/factory_reset':
|
|
167
145
|
break;
|
|
168
146
|
default:
|
|
169
|
-
// States
|
|
170
147
|
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
148
|
+
// {"payload":{"state":"online"},"topic":"FL.Licht.Links/availability"} ----> {"payload":{"available":true},"topic":"FL.Licht.Links"}
|
|
149
|
+
if (messageObj.topic.endsWith('/availability')) {
|
|
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') {
|
|
175
155
|
break;
|
|
176
156
|
}
|
|
177
|
-
this.processDeviceMessage(messageObj);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async processDeviceMessage(messageObj) {
|
|
185
|
-
this.logDebug(`processDeviceMessage -> messageObj: ${JSON.stringify(messageObj)}`);
|
|
186
|
-
// Is payload present?
|
|
187
|
-
if (messageObj.payload == '') {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const device = deviceCache.find(x => x.id == messageObj.topic);
|
|
192
|
-
if (device) {
|
|
193
|
-
this.logDebug(`processDeviceMessage -> device: ${JSON.stringify(device)}`);
|
|
194
|
-
try {
|
|
195
|
-
// The state available must not be considered for the cacheLastSeen
|
|
196
|
-
// Groups must not be considered for the cacheLastSeen
|
|
197
|
-
if (messageObj.payload.available == undefined && !device.ieee_address.startsWith('group_')) {
|
|
198
|
-
await this.cacheLastSeen(device, messageObj);
|
|
199
|
-
}
|
|
200
|
-
this.setDeviceState(messageObj, device);
|
|
201
|
-
this.checkAvailable(device.ieee_address);
|
|
202
|
-
|
|
203
|
-
} catch (error) {
|
|
204
|
-
adapter.log.error(error);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
adapter.log.warn(`Device: ${messageObj.topic} not found`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async cacheLastSeen(device, messageObj) {
|
|
213
|
-
this.logDebug(`cacheLastSeen -> device: ${JSON.stringify(device)}`);
|
|
214
|
-
this.logDebug(`cacheLastSeen -> messageObj: ${JSON.stringify(messageObj)}`);
|
|
215
|
-
if (messageObj.payload.last_seen) {
|
|
216
|
-
lastSeenCache[device.ieee_address] = new Date(messageObj.payload.last_seen).getTime();
|
|
217
|
-
} else {
|
|
218
|
-
lastSeenCache[device.ieee_address] = new Date().getTime();
|
|
219
|
-
}
|
|
220
|
-
this.logDebug(`cacheLastSeen -> deviceLastSeenCache: ${JSON.stringify(lastSeenCache)}`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async checkAvailableTimer() {
|
|
224
|
-
checkAvailableTimout = setTimeout(async () => {
|
|
225
|
-
await this.checkAvailable(null);
|
|
226
|
-
this.checkAvailableTimer();
|
|
227
|
-
}, checkAvailableInterval);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async checkAvailable(ieee_address) {
|
|
231
|
-
this.logDebug(`checkAvailable -> ieee_address: ${ieee_address}`);
|
|
232
|
-
let checkList = {};
|
|
233
|
-
if (ieee_address) {
|
|
234
|
-
checkList[ieee_address] = null;
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
checkList = lastSeenCache;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
for (const ieee_address in checkList) {
|
|
241
|
-
const device = deviceCache.find(x => x.ieee_address == ieee_address);
|
|
242
|
-
|
|
243
|
-
if (!device) {
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const isBatteryDevice = device.power_source == 'Battery' ? true : false;
|
|
248
|
-
const offlineTimeout = isBatteryDevice ? batteryDeviceAvailableTimeout : deviceAvailableTimeout;
|
|
249
|
-
const diffSec = Math.round((new Date().getTime() - lastSeenCache[ieee_address]) / 1000);
|
|
250
|
-
const available = diffSec < offlineTimeout;
|
|
251
|
-
|
|
252
|
-
this.logDebug(`checkAvailable -> device.id: ${device.id}, available: ${available}, diffSec: ${diffSec}, isBatteryDevice: ${isBatteryDevice}`);
|
|
253
|
-
|
|
254
|
-
if (device.available == null || device.available != available) {
|
|
255
|
-
this.logDebug(`checkAvailable -> device.id: ${device.id}, available: ${available}, diffSec: ${diffSec}, isBatteryDevice: ${isBatteryDevice}`);
|
|
256
|
-
device.available = available;
|
|
257
|
-
const messageObj = {
|
|
258
|
-
topic: device.id,
|
|
259
|
-
payload: {
|
|
260
|
-
available: available,
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
this.processDeviceMessage(messageObj);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async setDeviceState(messageObj, device) {
|
|
270
|
-
|
|
271
|
-
for (const [key, value] of Object.entries(messageObj.payload)) {
|
|
272
|
-
this.logDebug(`setDeviceState -> key: ${key}`);
|
|
273
|
-
this.logDebug(`setDeviceState -> value: ${JSON.stringify(value)}`);
|
|
274
|
-
|
|
275
|
-
let states;
|
|
276
|
-
if (key == 'action') {
|
|
277
|
-
states = device.states.filter(x => (x.prop && x.prop == key) && x.id == value);
|
|
278
|
-
} else {
|
|
279
|
-
states = device.states.filter(x => (x.prop && x.prop == key) || x.id == key);
|
|
280
|
-
}
|
|
281
|
-
this.logDebug(`setDeviceState -> states: ${JSON.stringify(states)}`);
|
|
282
|
-
|
|
283
|
-
for (const state of states) {
|
|
284
|
-
if (!state) {
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
const stateName = `${device.ieee_address}.${state.id}`;
|
|
288
|
-
|
|
289
|
-
if (state.getter) {
|
|
290
|
-
this.setState(stateName, state.getter(messageObj.payload), true);
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
this.setState(stateName, value, true);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async createDeviceDefinitions(cache, exposes) {
|
|
300
|
-
clearArray(cache);
|
|
301
|
-
for (const expose of exposes) {
|
|
302
|
-
if (expose.definition != null) {
|
|
303
|
-
await defineDeviceFromExposes(cache, expose.friendly_name, expose.ieee_address, expose.definition, expose.power_source);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
157
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (!createCache[device.ieee_address] || createCache[device.ieee_address].common.name != deviceName) {
|
|
319
|
-
const deviceObj = {
|
|
320
|
-
type: 'device',
|
|
321
|
-
common: {
|
|
322
|
-
name: deviceName,
|
|
323
|
-
statusStates: {
|
|
324
|
-
onlineId: `${this.name}.${this.instance}.${device.ieee_address}.available`
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
|
|
328
|
-
native: {}
|
|
329
|
-
};
|
|
330
|
-
//@ts-ignore
|
|
331
|
-
await this.extendObjectAsync(device.ieee_address, deviceObj);
|
|
332
|
-
createCache[device.ieee_address] = deviceObj;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Special handling for groups, here it is checked whether the scenes match the current data from z2m.
|
|
336
|
-
// If necessary, scenes are automatically deleted from ioBroker.
|
|
337
|
-
if (device.ieee_address.startsWith('group_')) {
|
|
338
|
-
const sceneStates = await this.getStatesAsync(`${device.ieee_address}.scene_*`);
|
|
339
|
-
const sceneIDs = Object.keys(sceneStates);
|
|
340
|
-
for (const sceneID of sceneIDs) {
|
|
341
|
-
const stateID = sceneID.split('.')[3];
|
|
342
|
-
if (device.states.find(x => x.id == stateID) == null) {
|
|
343
|
-
this.delObject(sceneID);
|
|
158
|
+
if (topicSplit.length == 2 && messageObj.payload && messageObj.payload.state) {
|
|
159
|
+
const newMessage = {
|
|
160
|
+
payload: { available: messageObj.payload.state == 'online' },
|
|
161
|
+
topic: topicSplit[0]
|
|
162
|
+
};
|
|
163
|
+
statesController.processDeviceMessage(newMessage);
|
|
164
|
+
}
|
|
165
|
+
// States
|
|
166
|
+
} else if (!messageObj.topic.includes('/')) {
|
|
167
|
+
statesController.processDeviceMessage(messageObj);
|
|
344
168
|
}
|
|
345
169
|
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
for (const state of device.states) {
|
|
349
|
-
if (!createCache[device.ieee_address][state.id] || createCache[device.ieee_address][state.id].name != state.name) {
|
|
350
|
-
const iobState = await this.copyAndCleanStateObj(state);
|
|
351
|
-
this.logDebug(`Orig. state: ${JSON.stringify(state)}`);
|
|
352
|
-
this.logDebug(`Cleaned. state: ${JSON.stringify(iobState)}`);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
await this.extendObjectAsync(`${device.ieee_address}.${state.id}`, {
|
|
357
|
-
type: 'state',
|
|
358
|
-
common: iobState,
|
|
359
|
-
native: {},
|
|
360
|
-
});
|
|
361
|
-
createCache[device.ieee_address][state.id] = state.name;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
this.subscribeWritableStates();
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async copyAndCleanStateObj(state) {
|
|
369
|
-
const iobState = { ...state };
|
|
370
|
-
const blacklistedKeys = [
|
|
371
|
-
'setter',
|
|
372
|
-
'setterOpt',
|
|
373
|
-
'getter',
|
|
374
|
-
'setattr',
|
|
375
|
-
'readable',
|
|
376
|
-
'writable',
|
|
377
|
-
'isOption',
|
|
378
|
-
'inOptions'
|
|
379
|
-
];
|
|
380
|
-
for (const blacklistedKey of blacklistedKeys) {
|
|
381
|
-
delete iobState[blacklistedKey];
|
|
382
|
-
}
|
|
383
|
-
return iobState;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
async subscribeWritableStates() {
|
|
387
|
-
await this.unsubscribeObjectsAsync('*');
|
|
388
|
-
for (const device of deviceCache) {
|
|
389
|
-
for (const state of device.states) {
|
|
390
|
-
if (state.write == true) {
|
|
391
|
-
this.subscribeStatesAsync(`${device.ieee_address}.${state.id}`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
async createZ2MMessage(id, state) {
|
|
398
|
-
|
|
399
|
-
const splitedID = id.split('.');
|
|
400
|
-
|
|
401
|
-
if (splitedID.length < 4) {
|
|
402
|
-
this.log.warn(`state ${id} not valid`);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const ieee_address = splitedID[2];
|
|
407
|
-
const stateName = splitedID[3];
|
|
408
|
-
|
|
409
|
-
const device = deviceCache.find(d => d.ieee_address == ieee_address);
|
|
410
|
-
|
|
411
|
-
if (!device) {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const deviceState = device.states.find(s => s.id == stateName);
|
|
416
|
-
|
|
417
|
-
if (!deviceState) {
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
let stateVal = state.val;
|
|
422
|
-
if (deviceState.setter) {
|
|
423
|
-
stateVal = deviceState.setter(state.val);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
let stateID = deviceState.id;
|
|
428
|
-
if (deviceState.prop) {
|
|
429
|
-
stateID = deviceState.prop;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
let topic = `${device.ieee_address}/set`;
|
|
433
|
-
if (device.ieee_address.includes('group_')) {
|
|
434
|
-
topic = `${device.id}/set`;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const controlObj = {
|
|
438
|
-
payload: {
|
|
439
|
-
[stateID]: stateVal
|
|
440
|
-
},
|
|
441
|
-
topic: topic
|
|
442
|
-
};
|
|
443
|
-
// set stats with role 'button' always immediately to ack = true, because these are not reported back by Zigbee2MQTT
|
|
444
|
-
if (deviceState.role == 'button') {
|
|
445
|
-
this.setState(id, state, true);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
return JSON.stringify(controlObj);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
async proxyZ2MLogs(messageObj) {
|
|
452
|
-
this.logDebug(`proxyZ2MLogs -> messageObj: ${JSON.stringify(messageObj)}`);
|
|
453
|
-
|
|
454
|
-
const logLevel = messageObj.payload.level;
|
|
455
|
-
const logMessage = messageObj.payload.message;
|
|
456
|
-
|
|
457
|
-
switch (logLevel) {
|
|
458
|
-
case 'debug':
|
|
459
|
-
case 'info':
|
|
460
|
-
case 'error':
|
|
461
|
-
this.log[logLevel](logMessage);
|
|
462
170
|
break;
|
|
463
|
-
case 'warning':
|
|
464
|
-
this.log.warn(logMessage);
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
async logDebug(message) {
|
|
470
|
-
if (debugLogEnabled == true) {
|
|
471
|
-
this.log.debug(message);
|
|
472
171
|
}
|
|
473
172
|
}
|
|
474
173
|
|
|
475
|
-
onUnload(callback) {
|
|
174
|
+
async onUnload(callback) {
|
|
476
175
|
try {
|
|
176
|
+
await statesController.setAllAvailableToFalse();
|
|
477
177
|
clearTimeout(ping);
|
|
478
178
|
clearTimeout(pingTimeout);
|
|
479
179
|
clearTimeout(autoRestartTimeout);
|
|
@@ -486,12 +186,24 @@ class Zigbee2mqtt extends core.Adapter {
|
|
|
486
186
|
|
|
487
187
|
async onStateChange(id, state) {
|
|
488
188
|
if (state && state.ack == false) {
|
|
489
|
-
|
|
490
|
-
|
|
189
|
+
if (id.includes('info.debugmessages')) {
|
|
190
|
+
debugDevices = state.val;
|
|
191
|
+
this.setState(id, state.val, true);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (id.includes('info.logfilter')) {
|
|
195
|
+
logfilter = state.val.split(';').filter(x => x); // filter removes empty strings here
|
|
196
|
+
this.setState(id, state.val, true);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const message = await z2mController.createZ2MMessage(id, state) || { topic: '', payload: '' };
|
|
201
|
+
mqttClient.publish('zigbee2mqtt/' + message.topic, JSON.stringify(message.payload));
|
|
491
202
|
}
|
|
492
203
|
}
|
|
493
204
|
}
|
|
494
205
|
|
|
206
|
+
|
|
495
207
|
if (require.main !== module) {
|
|
496
208
|
// Export the constructor in compact mode
|
|
497
209
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zigbee2mqtt",
|
|
3
|
-
"version": "
|
|
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,45 +0,0 @@
|
|
|
1
|
-
const states = require('./states').states;
|
|
2
|
-
const utils = require('./utils');
|
|
3
|
-
|
|
4
|
-
function defineGroupDevice(devices, groupID, ieee_address, scenes) {
|
|
5
|
-
const newDevice = {
|
|
6
|
-
id: groupID,
|
|
7
|
-
ieee_address: ieee_address,
|
|
8
|
-
icon: undefined,
|
|
9
|
-
states: [
|
|
10
|
-
states.state,
|
|
11
|
-
states.brightness,
|
|
12
|
-
states.colortemp,
|
|
13
|
-
states.color,
|
|
14
|
-
states.brightness_move,
|
|
15
|
-
states.colortemp_move,
|
|
16
|
-
states.transition_time,
|
|
17
|
-
//states.brightness_step
|
|
18
|
-
],
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// Create buttons for scenes
|
|
22
|
-
for (const scene of scenes) {
|
|
23
|
-
const sceneSate = {
|
|
24
|
-
id: `scene_${scene.id}`,
|
|
25
|
-
prop: `scene_recall`,
|
|
26
|
-
name: scene.name,
|
|
27
|
-
icon: undefined,
|
|
28
|
-
role: 'button',
|
|
29
|
-
write: true,
|
|
30
|
-
read: true,
|
|
31
|
-
type: 'boolean',
|
|
32
|
-
setter: (value) => (value) ? scene.id : undefined
|
|
33
|
-
};
|
|
34
|
-
// @ts-ignore
|
|
35
|
-
newDevice.states.push(sceneSate);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// if the device is already present in the cache, remove it
|
|
39
|
-
utils.removeDeviceByIeee(devices, ieee_address);
|
|
40
|
-
devices.push(newDevice);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
defineGroupDevice: defineGroupDevice,
|
|
45
|
-
};
|