iobroker.zigbee2mqtt 3.0.21 → 3.1.1
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 +9 -0
- package/admin/jsonConfig.json +37 -0
- package/io-package.json +16 -42
- package/lib/check.js +19 -11
- package/lib/deviceController.js +167 -133
- package/lib/exposes.js +1350 -1315
- package/lib/imageController.js +49 -47
- package/lib/messages.js +14 -6
- package/lib/mqttServerController.js +113 -22
- package/lib/states.js +12 -5
- package/lib/statesController.js +67 -45
- package/lib/utils.js +1 -50
- package/lib/websocketController.js +151 -46
- package/lib/z2mController.js +38 -29
- package/main.js +390 -194
- package/package.json +13 -12
package/lib/utils.js
CHANGED
|
@@ -46,17 +46,6 @@ function adapterLevelToBulbLevel(adapterLevel) {
|
|
|
46
46
|
// else
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
*
|
|
51
|
-
* @param ba
|
|
52
|
-
*/
|
|
53
|
-
function bytesArrayToWordArray(ba) {
|
|
54
|
-
const wa = [];
|
|
55
|
-
for (let i = 0; i < ba.length; i++) {
|
|
56
|
-
wa[(i / 2) | 0] |= ba[i] << (8 * (i % 2));
|
|
57
|
-
}
|
|
58
|
-
return wa;
|
|
59
|
-
}
|
|
60
49
|
|
|
61
50
|
// If the value is greater than 1000, kelvin is assumed.
|
|
62
51
|
// If smaller, it is assumed to be mired.
|
|
@@ -88,7 +77,7 @@ function miredKelvinConversion(t) {
|
|
|
88
77
|
*/
|
|
89
78
|
function decimalToHex(decimal, padding) {
|
|
90
79
|
let hex = Number(decimal).toString(16);
|
|
91
|
-
padding = typeof padding === 'undefined' || padding === null ?
|
|
80
|
+
padding = typeof padding === 'undefined' || padding === null ? 2 : padding;
|
|
92
81
|
|
|
93
82
|
while (hex.length < padding) {
|
|
94
83
|
hex = `0${hex}`;
|
|
@@ -97,26 +86,6 @@ function decimalToHex(decimal, padding) {
|
|
|
97
86
|
return hex;
|
|
98
87
|
}
|
|
99
88
|
|
|
100
|
-
/**
|
|
101
|
-
*
|
|
102
|
-
* @param adapterDevId
|
|
103
|
-
*/
|
|
104
|
-
function getZbId(adapterDevId) {
|
|
105
|
-
const idx = adapterDevId.indexOf('group');
|
|
106
|
-
if (idx > 0) {
|
|
107
|
-
return adapterDevId.substr(idx + 6);
|
|
108
|
-
}
|
|
109
|
-
return `0x${ adapterDevId.split('.')[2]}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
*
|
|
114
|
-
* @param adapter
|
|
115
|
-
* @param id
|
|
116
|
-
*/
|
|
117
|
-
function getAdId(adapter, id) {
|
|
118
|
-
return `${adapter.namespace }.${ id.split('.')[2]}`; // iobroker device id
|
|
119
|
-
}
|
|
120
89
|
|
|
121
90
|
/**
|
|
122
91
|
*
|
|
@@ -147,32 +116,14 @@ function isObject(item) {
|
|
|
147
116
|
return typeof item === 'object' && !Array.isArray(item) && item !== null;
|
|
148
117
|
}
|
|
149
118
|
|
|
150
|
-
/**
|
|
151
|
-
*
|
|
152
|
-
* @param item
|
|
153
|
-
*/
|
|
154
|
-
function isJson(item) {
|
|
155
|
-
let value = typeof item !== 'string' ? JSON.stringify(item) : item;
|
|
156
|
-
try {
|
|
157
|
-
value = JSON.parse(value);
|
|
158
|
-
} catch (e) {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return typeof value === 'object' && value !== null;
|
|
163
|
-
}
|
|
164
119
|
|
|
165
120
|
module.exports = {
|
|
166
121
|
bulbLevelToAdapterLevel,
|
|
167
122
|
adapterLevelToBulbLevel,
|
|
168
|
-
bytesArrayToWordArray,
|
|
169
123
|
toMired,
|
|
170
124
|
miredKelvinConversion,
|
|
171
125
|
decimalToHex,
|
|
172
|
-
getZbId,
|
|
173
|
-
getAdId,
|
|
174
126
|
clearArray,
|
|
175
127
|
moveArray,
|
|
176
128
|
isObject,
|
|
177
|
-
isJson,
|
|
178
129
|
};
|
|
@@ -1,128 +1,233 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
const WebSocket = require('ws');
|
|
2
|
-
let wsClient;
|
|
3
4
|
const wsHeartbeatIntervall = 5000;
|
|
4
5
|
const restartTimeout = 1000;
|
|
5
|
-
let ping;
|
|
6
|
-
let pingTimeout;
|
|
7
|
-
let autoRestartTimeout;
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
*
|
|
11
9
|
*/
|
|
12
10
|
class WebsocketController {
|
|
13
11
|
/**
|
|
14
|
-
*
|
|
15
12
|
* @param adapter
|
|
16
13
|
*/
|
|
17
14
|
constructor(adapter) {
|
|
18
15
|
this.adapter = adapter;
|
|
16
|
+
this.wsClient = null;
|
|
17
|
+
this.ping = null;
|
|
18
|
+
this.pingTimeout = null;
|
|
19
|
+
this.autoRestartTimeout = null;
|
|
20
|
+
// Flag: wird bei closeConnection() gesetzt damit autoRestart() nicht feuert
|
|
21
|
+
this._intentionalClose = false;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
*
|
|
25
|
+
* Baut eine neue WebSocket-Verbindung auf.
|
|
26
|
+
* Bestehende Verbindung und alle Timer werden zuerst sicher bereinigt.
|
|
23
27
|
*/
|
|
24
28
|
initWsClient() {
|
|
29
|
+
// Config-Guard: Pflichtfelder müssen vorhanden sein
|
|
30
|
+
if (!this.adapter.config.wsScheme || !this.adapter.config.wsServerIP || !this.adapter.config.wsServerPort) {
|
|
31
|
+
this.adapter.log.error('WebSocket config incomplete (wsScheme / wsServerIP / wsServerPort missing).');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this._intentionalClose = false;
|
|
36
|
+
|
|
37
|
+
// Vorherige Timer stoppen
|
|
38
|
+
clearTimeout(this.ping);
|
|
39
|
+
clearTimeout(this.pingTimeout);
|
|
40
|
+
clearTimeout(this.autoRestartTimeout);
|
|
41
|
+
|
|
42
|
+
// Vorherige Verbindung sicher schließen und null setzen
|
|
43
|
+
if (this.wsClient) {
|
|
44
|
+
this.wsClient.removeAllListeners();
|
|
45
|
+
if (this.wsClient.readyState !== WebSocket.CLOSED &&
|
|
46
|
+
this.wsClient.readyState !== WebSocket.CLOSING) {
|
|
47
|
+
try {
|
|
48
|
+
this.wsClient.terminate();
|
|
49
|
+
} catch (e) {
|
|
50
|
+
this.adapter.log.debug(`initWsClient: old socket terminate error: ${e}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
this.wsClient = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
25
56
|
try {
|
|
26
57
|
let wsURL = `${this.adapter.config.wsScheme}://${this.adapter.config.wsServerIP}:${this.adapter.config.wsServerPort}/api`;
|
|
27
58
|
|
|
28
|
-
if (this.adapter.config.wsTokenEnabled
|
|
59
|
+
if (this.adapter.config.wsTokenEnabled === true) {
|
|
29
60
|
wsURL += `?token=${this.adapter.config.wsToken}`;
|
|
30
61
|
}
|
|
31
62
|
|
|
32
|
-
wsClient = new WebSocket(wsURL, { rejectUnauthorized: false });
|
|
63
|
+
this.wsClient = new WebSocket(wsURL, { rejectUnauthorized: false });
|
|
33
64
|
|
|
34
|
-
wsClient.on('open', () => {
|
|
35
|
-
|
|
65
|
+
this.wsClient.on('open', () => {
|
|
66
|
+
this.adapter.log.info('Connect to Zigbee2MQTT over websocket connection.');
|
|
36
67
|
this.sendPingToServer();
|
|
37
|
-
// Start Heartbeat
|
|
38
68
|
this.wsHeartbeat();
|
|
39
69
|
});
|
|
40
70
|
|
|
41
|
-
wsClient.on('pong', () => {
|
|
71
|
+
this.wsClient.on('pong', () => {
|
|
42
72
|
this.wsHeartbeat();
|
|
43
73
|
});
|
|
44
74
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
// ws@8 liefert Buffer, nicht String → immer .toString() verwenden
|
|
76
|
+
this.wsClient.on('message', (message) => {
|
|
77
|
+
let messageObj;
|
|
78
|
+
try {
|
|
79
|
+
messageObj = JSON.parse(message.toString());
|
|
80
|
+
} catch {
|
|
81
|
+
this.adapter.log.debug(`Invalid WebSocket message: ${message.toString().slice(0, 200)}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// messageParse ist async – Fehler mit .catch() abfangen
|
|
85
|
+
this.adapter.messageParse(messageObj).catch((err) => {
|
|
86
|
+
this.adapter.log.error(`messageParse error: ${err}`);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
this.wsClient.on('close', async (code, reason) => {
|
|
91
|
+
clearTimeout(this.pingTimeout);
|
|
92
|
+
clearTimeout(this.ping);
|
|
93
|
+
|
|
94
|
+
this.adapter.log.debug(`WebSocket closed – code: ${code}, reason: ${reason ? reason.toString() : 'none'}`);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
if (this.adapter.statesController) {
|
|
98
|
+
this.adapter.setStateChanged('info.connection', false, true);
|
|
99
|
+
await this.adapter.statesController.setAllAvailableToFalse();
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
this.adapter.log.error(`close handler setAllAvailableToFalse error: ${err}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Caches leeren
|
|
106
|
+
this.adapter.deviceCache.length = 0;
|
|
107
|
+
this.adapter.groupCache.length = 0;
|
|
108
|
+
for (const key of Object.keys(this.adapter.createCache)) {
|
|
109
|
+
delete this.adapter.createCache[key];
|
|
110
|
+
}
|
|
48
111
|
|
|
49
|
-
|
|
112
|
+
// Nur reconnecten wenn kein intentionales Shutdown
|
|
113
|
+
if (!this._intentionalClose) {
|
|
50
114
|
this.autoRestart();
|
|
51
115
|
}
|
|
52
116
|
});
|
|
53
117
|
|
|
54
|
-
wsClient.on('
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.adapter.log.debug(
|
|
118
|
+
this.wsClient.on('error', (err) => {
|
|
119
|
+
// err kann undefined sein oder kein Error-Objekt
|
|
120
|
+
const msg = err && err.message ? err.message : String(err || 'unknown');
|
|
121
|
+
this.adapter.log.debug(`WebSocket error: ${msg}`);
|
|
122
|
+
// Kein throw – der 'close'-Event folgt nach 'error' immer automatisch
|
|
58
123
|
});
|
|
59
124
|
|
|
60
|
-
return wsClient;
|
|
61
125
|
} catch (err) {
|
|
62
|
-
this.adapter.log.error(err);
|
|
126
|
+
this.adapter.log.error(`WebSocket init error: ${err}`);
|
|
127
|
+
// Retry nach restartTimeout
|
|
128
|
+
if (!this._intentionalClose) {
|
|
129
|
+
this.autoRestart();
|
|
130
|
+
}
|
|
63
131
|
}
|
|
64
132
|
}
|
|
65
133
|
|
|
66
134
|
/**
|
|
135
|
+
* Sendet eine Nachricht an Z2M.
|
|
67
136
|
*
|
|
68
|
-
* @param message
|
|
137
|
+
* @param {string} message
|
|
69
138
|
*/
|
|
70
139
|
send(message) {
|
|
71
|
-
if (wsClient.readyState !== WebSocket.OPEN) {
|
|
140
|
+
if (!this.wsClient || this.wsClient.readyState !== WebSocket.OPEN) {
|
|
72
141
|
this.adapter.log.warn('Cannot set State, no websocket connection to Zigbee2MQTT!');
|
|
73
142
|
return;
|
|
74
143
|
}
|
|
75
|
-
|
|
144
|
+
try {
|
|
145
|
+
this.wsClient.send(message);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
this.adapter.log.error(`WebSocket send error: ${err && err.message ? err.message : String(err)}`);
|
|
148
|
+
}
|
|
76
149
|
}
|
|
77
150
|
|
|
78
151
|
/**
|
|
79
|
-
*
|
|
152
|
+
* Sendet regelmäßig Pings an den Server.
|
|
80
153
|
*/
|
|
81
154
|
sendPingToServer() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
155
|
+
if (!this.wsClient || this.wsClient.readyState !== WebSocket.OPEN) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
this.wsClient.ping();
|
|
160
|
+
} catch (err) {
|
|
161
|
+
this.adapter.log.debug(`WebSocket ping error: ${err && err.message ? err.message : String(err)}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.ping = setTimeout(() => {
|
|
85
165
|
this.sendPingToServer();
|
|
86
166
|
}, wsHeartbeatIntervall);
|
|
87
167
|
}
|
|
88
168
|
|
|
89
169
|
/**
|
|
90
|
-
*
|
|
170
|
+
* Überwacht den Heartbeat. Wenn kein Pong innerhalb des Timeouts kommt, wird die Verbindung getrennt.
|
|
91
171
|
*/
|
|
92
172
|
wsHeartbeat() {
|
|
93
|
-
clearTimeout(pingTimeout);
|
|
94
|
-
pingTimeout = setTimeout(() => {
|
|
95
|
-
this.adapter.log.warn('
|
|
96
|
-
|
|
173
|
+
clearTimeout(this.pingTimeout);
|
|
174
|
+
this.pingTimeout = setTimeout(() => {
|
|
175
|
+
this.adapter.log.warn('WebSocket connection timed out – terminating.');
|
|
176
|
+
try {
|
|
177
|
+
if (this.wsClient) {
|
|
178
|
+
this.wsClient.terminate();
|
|
179
|
+
}
|
|
180
|
+
} catch (err) {
|
|
181
|
+
this.adapter.log.debug(`wsHeartbeat terminate error: ${err}`);
|
|
182
|
+
}
|
|
183
|
+
// terminate() feuert 'close' → autoRestart() wird dort aufgerufen
|
|
97
184
|
}, wsHeartbeatIntervall + 3000);
|
|
98
185
|
}
|
|
99
186
|
|
|
100
187
|
/**
|
|
101
|
-
*
|
|
188
|
+
* Wartet kurz und baut dann die Verbindung neu auf.
|
|
102
189
|
*/
|
|
103
|
-
|
|
104
|
-
this.adapter.log.warn(`
|
|
105
|
-
autoRestartTimeout
|
|
106
|
-
|
|
190
|
+
autoRestart() {
|
|
191
|
+
this.adapter.log.warn(`WebSocket disconnected – reconnecting in ${restartTimeout / 1000} second(s)...`);
|
|
192
|
+
clearTimeout(this.autoRestartTimeout);
|
|
193
|
+
this.autoRestartTimeout = setTimeout(() => {
|
|
194
|
+
try {
|
|
195
|
+
this.initWsClient();
|
|
196
|
+
} catch (err) {
|
|
197
|
+
this.adapter.log.error(`autoRestart initWsClient error: ${err}`);
|
|
198
|
+
}
|
|
107
199
|
}, restartTimeout);
|
|
108
200
|
}
|
|
109
201
|
|
|
110
202
|
/**
|
|
111
|
-
*
|
|
203
|
+
* Schließt die Verbindung intentional (kein autoRestart).
|
|
112
204
|
*/
|
|
113
205
|
closeConnection() {
|
|
114
|
-
|
|
115
|
-
|
|
206
|
+
this._intentionalClose = true;
|
|
207
|
+
clearTimeout(this.ping);
|
|
208
|
+
clearTimeout(this.pingTimeout);
|
|
209
|
+
clearTimeout(this.autoRestartTimeout);
|
|
210
|
+
if (this.wsClient) {
|
|
211
|
+
this.wsClient.removeAllListeners();
|
|
212
|
+
try {
|
|
213
|
+
if (this.wsClient.readyState !== WebSocket.CLOSED &&
|
|
214
|
+
this.wsClient.readyState !== WebSocket.CLOSING) {
|
|
215
|
+
this.wsClient.close();
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
this.adapter.log.debug(`closeConnection error: ${err}`);
|
|
219
|
+
}
|
|
220
|
+
this.wsClient = null;
|
|
116
221
|
}
|
|
117
222
|
}
|
|
118
223
|
|
|
119
224
|
/**
|
|
120
|
-
*
|
|
225
|
+
* Stoppt alle Timer (z.B. beim Adapter-Stop).
|
|
121
226
|
*/
|
|
122
227
|
async allTimerClear() {
|
|
123
|
-
clearTimeout(pingTimeout);
|
|
124
|
-
clearTimeout(ping);
|
|
125
|
-
clearTimeout(autoRestartTimeout);
|
|
228
|
+
clearTimeout(this.pingTimeout);
|
|
229
|
+
clearTimeout(this.ping);
|
|
230
|
+
clearTimeout(this.autoRestartTimeout);
|
|
126
231
|
}
|
|
127
232
|
}
|
|
128
233
|
|
package/lib/z2mController.js
CHANGED
|
@@ -29,25 +29,38 @@ class Z2mController {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (id.endsWith('info.coordinator_check')) {
|
|
32
|
-
|
|
32
|
+
// Fix 1: Z2M erwartet JSON-Objekt {} nicht leeren String ''
|
|
33
|
+
return { topic: 'bridge/request/coordinator_check', payload: {} };
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const ieee_address = splitedID[2];
|
|
36
37
|
const stateName = splitedID[3];
|
|
37
38
|
|
|
38
|
-
const device = this.groupCache.concat(this.deviceCache).find((d) => d.ieee_address
|
|
39
|
+
const device = this.groupCache.concat(this.deviceCache).find((d) => d.ieee_address === ieee_address);
|
|
39
40
|
if (!device) {
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const deviceState = device.states.find((s) => s.id
|
|
44
|
+
const deviceState = device.states.find((s) => s.id === stateName);
|
|
44
45
|
if (!deviceState) {
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
let stateVal = state.val;
|
|
49
50
|
if (deviceState.setter) {
|
|
50
|
-
|
|
51
|
+
// Fix 3: setter kann crashen (z.B. ungültiger Farbwert) → try/catch
|
|
52
|
+
try {
|
|
53
|
+
stateVal = deviceState.setter(state.val);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
this.adapter.log.warn(`${device.ieee_address} state: ${stateName} setter error: ${err.message || err}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Fix 5: wenn setter undefined zurückgibt (z.B. Toggle-Release) → nichts senden
|
|
61
|
+
if (stateVal === undefined) {
|
|
62
|
+
this.adapter.log.debug(`${device.ieee_address} state: ${stateName} setter returned undefined, skipping`);
|
|
63
|
+
return;
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
let stateID = deviceState.id;
|
|
@@ -66,16 +79,18 @@ class Z2mController {
|
|
|
66
79
|
topic: `${device.id}/set`,
|
|
67
80
|
};
|
|
68
81
|
|
|
69
|
-
if (stateID
|
|
82
|
+
if (stateID === 'send_payload') {
|
|
70
83
|
try {
|
|
71
84
|
controlObj.payload = JSON.parse(stateVal);
|
|
72
85
|
this.adapter.setState(id, state, true);
|
|
73
86
|
} catch (error) {
|
|
74
|
-
|
|
87
|
+
// Fix 9: rawStr als String direkt als payload – wird in main.js via
|
|
88
|
+
// JSON.stringify() nochmals gewrappt → würde doppelt quoten.
|
|
89
|
+
// Stattdessen: Fehler loggen und NICHT senden (ungültiges JSON bleibt ungültig)
|
|
75
90
|
this.adapter.log.warn(
|
|
76
|
-
`${device.ieee_address} state: ${stateID} error: value passed is not a valid JSON`
|
|
91
|
+
`${device.ieee_address} state: ${stateID} error: value passed is not a valid JSON – not sent`
|
|
77
92
|
);
|
|
78
|
-
this.adapter.log.debug(`${device.ieee_address}
|
|
93
|
+
this.adapter.log.debug(`${device.ieee_address} raw value: ${stateVal}`);
|
|
79
94
|
return;
|
|
80
95
|
}
|
|
81
96
|
}
|
|
@@ -83,17 +98,14 @@ class Z2mController {
|
|
|
83
98
|
// if available read option and set payload
|
|
84
99
|
if (deviceState.options) {
|
|
85
100
|
for (const option of deviceState.options) {
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// optionsValues Cache
|
|
92
|
-
device.optionsValues[option] = optionValue;
|
|
101
|
+
// Fix 2: Cache-Check mit === undefined statt !value
|
|
102
|
+
// Falsy-Check schlägt bei 0, false, null fehl (z.B. transition=0 = sofort)
|
|
103
|
+
if (device.optionsValues[option] === undefined) {
|
|
104
|
+
const optState = await this.adapter.getStateAsync(`${splitedID[0]}.${splitedID[1]}.${splitedID[2]}.${option}`);
|
|
105
|
+
device.optionsValues[option] = optState ? optState.val : null;
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
|
|
96
|
-
if (option == 'transition' && device.optionsValues[option] == -1) {
|
|
108
|
+
if (option === 'transition' && device.optionsValues[option] === -1) {
|
|
97
109
|
continue;
|
|
98
110
|
}
|
|
99
111
|
|
|
@@ -103,23 +115,17 @@ class Z2mController {
|
|
|
103
115
|
|
|
104
116
|
// If an option datapoint has been set, it does not have to be sent.
|
|
105
117
|
// This is confirmed directly by the adapter (ack = true)
|
|
106
|
-
if (deviceState.isOption
|
|
118
|
+
if (deviceState.isOption) {
|
|
107
119
|
// set optionsValues 'Cache'
|
|
108
120
|
device.optionsValues[stateName] = state.val;
|
|
109
121
|
this.adapter.setState(id, state, true);
|
|
110
122
|
return;
|
|
111
123
|
}
|
|
112
124
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// set stats with the mentioned ids always immediately to ack = true, because these are not reported back by Zigbee2MQTT
|
|
118
|
-
if (
|
|
119
|
-
['brightness_move', 'colortemp_move', 'brightness_move', 'brightness_step', 'effect'].includes(
|
|
120
|
-
deviceState.id
|
|
121
|
-
)
|
|
122
|
-
) {
|
|
125
|
+
// States die nicht von Z2M zurückgemeldet werden → sofort ack=true setzen
|
|
126
|
+
const immediateAckRole = ['button'].includes(deviceState.role);
|
|
127
|
+
const immediateAckId = ['brightness_move', 'colortemp_move', 'brightness_step', 'effect'].includes(deviceState.id);
|
|
128
|
+
if (immediateAckRole || immediateAckId) {
|
|
123
129
|
this.adapter.setState(id, state, true);
|
|
124
130
|
}
|
|
125
131
|
|
|
@@ -134,7 +140,10 @@ class Z2mController {
|
|
|
134
140
|
* @param messageObj
|
|
135
141
|
*/
|
|
136
142
|
async proxyZ2MLogs(messageObj) {
|
|
137
|
-
const logMessage = messageObj.payload.message;
|
|
143
|
+
const logMessage = messageObj.payload && messageObj.payload.message;
|
|
144
|
+
if (!logMessage) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
138
147
|
if (this.logCustomizations.logfilter.some((x) => logMessage.includes(x))) {
|
|
139
148
|
return;
|
|
140
149
|
}
|