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/README.md
CHANGED
|
@@ -22,6 +22,15 @@ This adapter allows to control the data points of the devices of a Zigbee2MQTT i
|
|
|
22
22
|
[Adapter Documentation](https://github.com/arteck/ioBroker.zigbee2mqtt/blob/main/docs/wiki.md)
|
|
23
23
|
|
|
24
24
|
## Changelog
|
|
25
|
+
### 3.1.1 (2026-04-21)
|
|
26
|
+
* (arteck) fix connection handling
|
|
27
|
+
|
|
28
|
+
### 3.1.0 (2026-04-20)
|
|
29
|
+
* (arteck) refactoring
|
|
30
|
+
|
|
31
|
+
### 3.0.22 (2026-04-12)
|
|
32
|
+
* (arteck) Dependencies have been updated
|
|
33
|
+
|
|
25
34
|
### 3.0.21 (2026-01-29)
|
|
26
35
|
* (arteck) add coordinator status info
|
|
27
36
|
|
package/admin/jsonConfig.json
CHANGED
|
@@ -560,6 +560,43 @@
|
|
|
560
560
|
"lg": 12,
|
|
561
561
|
"xl": 12,
|
|
562
562
|
"newLine": true
|
|
563
|
+
},
|
|
564
|
+
"spacerDeleteStates": {
|
|
565
|
+
"type": "staticText",
|
|
566
|
+
"text": "",
|
|
567
|
+
"xs": 12,
|
|
568
|
+
"newLine": true
|
|
569
|
+
},
|
|
570
|
+
"headerDeleteStates": {
|
|
571
|
+
"type": "header",
|
|
572
|
+
"text": "coming soon ",
|
|
573
|
+
"size": 4,
|
|
574
|
+
"newLine": true
|
|
575
|
+
},
|
|
576
|
+
"buttonDeleteOldStates": {
|
|
577
|
+
"type": "sendTo",
|
|
578
|
+
"command": "deleteOldStates",
|
|
579
|
+
"label": "Delete old / unused states",
|
|
580
|
+
"variant": "contained",
|
|
581
|
+
"color": "secondary",
|
|
582
|
+
"icon": "delete",
|
|
583
|
+
"disabled": "1 === 1",
|
|
584
|
+
"xs": 12,
|
|
585
|
+
"sm": 6,
|
|
586
|
+
"md": 4,
|
|
587
|
+
"lg": 3,
|
|
588
|
+
"xl": 3,
|
|
589
|
+
"newLine": true
|
|
590
|
+
},
|
|
591
|
+
"_deleteOldStatesResult": {
|
|
592
|
+
"type": "staticText",
|
|
593
|
+
"text": "",
|
|
594
|
+
"xs": 12,
|
|
595
|
+
"newLine": false,
|
|
596
|
+
"style": {
|
|
597
|
+
"marginTop": 8,
|
|
598
|
+
"fontStyle": "italic"
|
|
599
|
+
}
|
|
563
600
|
}
|
|
564
601
|
}
|
|
565
602
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zigbee2mqtt",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.1.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"3.1.1": {
|
|
7
|
+
"en": "fix connection handling",
|
|
8
|
+
"de": "befestigungstechnik",
|
|
9
|
+
"ru": "фиксировать связь",
|
|
10
|
+
"pt": "corrigir o tratamento da ligação",
|
|
11
|
+
"nl": "fix verbinding handling",
|
|
12
|
+
"fr": "fixer la manipulation de la connexion",
|
|
13
|
+
"it": "gestione del collegamento",
|
|
14
|
+
"es": "corrección de conexión",
|
|
15
|
+
"pl": "naprawić obsługę połączenia",
|
|
16
|
+
"uk": "кріплення з'єднання",
|
|
17
|
+
"zh-cn": "固定连接处理"
|
|
18
|
+
},
|
|
6
19
|
"3.0.21": {
|
|
7
20
|
"en": "add coordinator status info",
|
|
8
21
|
"de": "koordinator statusinfo hinzufügen",
|
|
@@ -54,45 +67,6 @@
|
|
|
54
67
|
"pl": "typo",
|
|
55
68
|
"uk": "типи",
|
|
56
69
|
"zh-cn": "类型"
|
|
57
|
-
},
|
|
58
|
-
"3.0.17": {
|
|
59
|
-
"en": "typo",
|
|
60
|
-
"de": "typo",
|
|
61
|
-
"ru": "опечатка",
|
|
62
|
-
"pt": "erro de digitação",
|
|
63
|
-
"nl": "type",
|
|
64
|
-
"fr": "typo",
|
|
65
|
-
"it": "tipo",
|
|
66
|
-
"es": "typo",
|
|
67
|
-
"pl": "typo",
|
|
68
|
-
"uk": "типи",
|
|
69
|
-
"zh-cn": "类型"
|
|
70
|
-
},
|
|
71
|
-
"3.0.16": {
|
|
72
|
-
"en": "fix action dp",
|
|
73
|
-
"de": "funktion dp",
|
|
74
|
-
"ru": "исправить действие dp",
|
|
75
|
-
"pt": "corrigir ação dp",
|
|
76
|
-
"nl": "fix actie dp",
|
|
77
|
-
"fr": "corriger l'action dp",
|
|
78
|
-
"it": "fix azione dp",
|
|
79
|
-
"es": "arreglar acción dp",
|
|
80
|
-
"pl": "fix action dp",
|
|
81
|
-
"uk": "фіксувати дію dp",
|
|
82
|
-
"zh-cn": "固定动作 dp"
|
|
83
|
-
},
|
|
84
|
-
"3.0.15": {
|
|
85
|
-
"en": "update",
|
|
86
|
-
"de": "aktualisierung",
|
|
87
|
-
"ru": "обновление",
|
|
88
|
-
"pt": "atualizar",
|
|
89
|
-
"nl": "bijwerken",
|
|
90
|
-
"fr": "mise à jour",
|
|
91
|
-
"it": "aggiornamento",
|
|
92
|
-
"es": "actualización",
|
|
93
|
-
"pl": "aktualizacja",
|
|
94
|
-
"uk": "оновлення",
|
|
95
|
-
"zh-cn": "更新"
|
|
96
70
|
}
|
|
97
71
|
},
|
|
98
72
|
"messages": [
|
|
@@ -220,12 +194,12 @@
|
|
|
220
194
|
},
|
|
221
195
|
"dependencies": [
|
|
222
196
|
{
|
|
223
|
-
"js-controller": ">=
|
|
197
|
+
"js-controller": ">= 6.0.11"
|
|
224
198
|
}
|
|
225
199
|
],
|
|
226
200
|
"globalDependencies": [
|
|
227
201
|
{
|
|
228
|
-
"admin": ">=7.
|
|
202
|
+
"admin": ">=7.6.20"
|
|
229
203
|
}
|
|
230
204
|
]
|
|
231
205
|
},
|
package/lib/check.js
CHANGED
|
@@ -5,28 +5,36 @@
|
|
|
5
5
|
* @param version
|
|
6
6
|
*/
|
|
7
7
|
function checkConfig(config, log, version) {
|
|
8
|
+
if (!config || !log) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const advanced = config.advanced || {};
|
|
13
|
+
const deviceOptions = config.device_options || {};
|
|
14
|
+
|
|
8
15
|
const checkAPIOptions = {
|
|
9
|
-
legacy_api_enabled:
|
|
10
|
-
legacy_availability_payload_enabled:
|
|
11
|
-
device_legacy_enabled:
|
|
16
|
+
legacy_api_enabled: advanced.legacy_api !== false,
|
|
17
|
+
legacy_availability_payload_enabled: advanced.legacy_availability_payload !== false,
|
|
18
|
+
device_legacy_enabled: deviceOptions.legacy !== false,
|
|
12
19
|
};
|
|
13
20
|
|
|
14
21
|
const checkAPIOptionsOutput = {
|
|
15
|
-
payload_contains_not_json:
|
|
22
|
+
payload_contains_not_json: advanced.output !== 'attribute_and_json' && advanced.output !== 'json',
|
|
16
23
|
};
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
// Fix: version kann undefined/null sein (ältere Z2M-Versionen)
|
|
26
|
+
if (version && version.startsWith('1.')) {
|
|
27
|
+
if (Object.values(checkAPIOptions).filter((x) => x === true).length > 0) {
|
|
20
28
|
log.error('===================================================');
|
|
21
29
|
log.error('===================================================');
|
|
22
|
-
if (checkAPIOptions.legacy_api_enabled
|
|
30
|
+
if (checkAPIOptions.legacy_api_enabled === true) {
|
|
23
31
|
log.error('Legacy api is activated, so the adapter can not work correctly!!!');
|
|
24
32
|
log.error('Please add the following lines to your Zigbee2MQTT configuration.yaml:');
|
|
25
33
|
log.error('advanced:');
|
|
26
34
|
log.error(' legacy_api: false');
|
|
27
35
|
log.error('');
|
|
28
36
|
}
|
|
29
|
-
if (checkAPIOptions.legacy_availability_payload_enabled
|
|
37
|
+
if (checkAPIOptions.legacy_availability_payload_enabled === true) {
|
|
30
38
|
log.error(
|
|
31
39
|
'Legacy Availability Payload is activated, thus the adapter cannot represent the availability of the devices!!!'
|
|
32
40
|
);
|
|
@@ -35,7 +43,7 @@ function checkConfig(config, log, version) {
|
|
|
35
43
|
log.error('legacy_availability_payload: false');
|
|
36
44
|
log.error('');
|
|
37
45
|
}
|
|
38
|
-
if (checkAPIOptions.device_legacy_enabled
|
|
46
|
+
if (checkAPIOptions.device_legacy_enabled === true) {
|
|
39
47
|
log.error(
|
|
40
48
|
'Device Legacy Payload is activated, therefore the adapter may process the states of the devices correctly!!!'
|
|
41
49
|
);
|
|
@@ -46,8 +54,8 @@ function checkConfig(config, log, version) {
|
|
|
46
54
|
log.error('===================================================');
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
|
-
if (Object.values(checkAPIOptionsOutput).filter((x) => x
|
|
50
|
-
if (checkAPIOptions.payload_contains_not_json
|
|
57
|
+
if (Object.values(checkAPIOptionsOutput).filter((x) => x === true).length > 0) {
|
|
58
|
+
if (checkAPIOptions.payload_contains_not_json === true ) {
|
|
51
59
|
log.error('===================================================');
|
|
52
60
|
log.error(
|
|
53
61
|
'MQTT output type must "attribute_and_json" or "json" , therefore the adapter may process the states of the devices correctly!!!'
|
package/lib/deviceController.js
CHANGED
|
@@ -33,8 +33,17 @@ class DeviceController {
|
|
|
33
33
|
* @param devicesMessage
|
|
34
34
|
*/
|
|
35
35
|
async createDeviceDefinitions(devicesMessage) {
|
|
36
|
+
// Fix 1: devicesMessage kann null/kein Array sein (z.B. leerer bridge/devices payload)
|
|
37
|
+
if (!Array.isArray(devicesMessage)) {
|
|
38
|
+
this.adapter.log.warn('createDeviceDefinitions: payload ist kein Array');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
36
41
|
utils.clearArray(this.deviceCache);
|
|
37
42
|
for (const devicesMessag of devicesMessage) {
|
|
43
|
+
if (!devicesMessag || !devicesMessag.ieee_address) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
if (this.logCustomizations.debugDevices.includes(devicesMessag.ieee_address)) {
|
|
39
48
|
this.adapter.log.warn(
|
|
40
49
|
`--->>> fromZ2M -> ${devicesMessag.ieee_address} exposes: ${JSON.stringify(devicesMessag)}`
|
|
@@ -42,16 +51,17 @@ class DeviceController {
|
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
if (devicesMessag.definition != null) {
|
|
45
|
-
// if the device is already present in the cache, remove it
|
|
46
54
|
this.removeDeviceByIeee(this.deviceCache, devicesMessag.ieee_address);
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
// Fix 3: exposes muss auch ein Array sein
|
|
57
|
+
if (Array.isArray(devicesMessag.definition.exposes)) {
|
|
49
58
|
try {
|
|
50
59
|
const newDevice = await createDeviceFromExposes(devicesMessag, this.adapter);
|
|
51
60
|
newDevice.icon = await this.imageController.getDeviceIcon(devicesMessag);
|
|
52
61
|
this.deviceCache.push(newDevice);
|
|
53
62
|
} catch (err) {
|
|
54
|
-
|
|
63
|
+
// Fix 2: friendly_name im Fehlerlog verwenden
|
|
64
|
+
this.adapter.log.warn(`Cannot create Device from Exposes for ${devicesMessag.friendly_name || devicesMessag.ieee_address}!`);
|
|
55
65
|
this.adapter.log.debug(JSON.stringify(devicesMessag));
|
|
56
66
|
this.adapter.log.debug(err);
|
|
57
67
|
}
|
|
@@ -67,23 +77,22 @@ class DeviceController {
|
|
|
67
77
|
* @param scenes
|
|
68
78
|
*/
|
|
69
79
|
async defineGroupDevice(groupID, ieee_address, scenes) {
|
|
70
|
-
const brmPropName =
|
|
71
|
-
|
|
72
|
-
const brsPropName =
|
|
73
|
-
this.adapter.config.brightnessStepOnOff == true ? 'brightness_step_onoff' : 'brightness_step';
|
|
80
|
+
const brmPropName = this.adapter.config.brightnessMoveOnOff === true ? 'brightness_move_onoff' : 'brightness_move';
|
|
81
|
+
const brsPropName = this.adapter.config.brightnessStepOnOff === true ? 'brightness_step_onoff' : 'brightness_step';
|
|
74
82
|
const newDevice = {
|
|
75
83
|
id: groupID,
|
|
76
84
|
ieee_address: ieee_address,
|
|
77
85
|
icon: undefined, // await imageController.getDeviceIcon(devicesMessag), device.definition.model
|
|
78
86
|
optionsValues: {},
|
|
79
87
|
states: [
|
|
80
|
-
statesDefs
|
|
81
|
-
statesDefs.
|
|
82
|
-
statesDefs.
|
|
83
|
-
statesDefs.
|
|
88
|
+
// Fix 1: Klone aller statesDefs-Objekte – sonst werden geteilte Referenzen mutiert
|
|
89
|
+
Object.assign({}, statesDefs.available),
|
|
90
|
+
Object.assign({}, statesDefs.brightness),
|
|
91
|
+
Object.assign({}, statesDefs.colortemp_move),
|
|
92
|
+
Object.assign({}, statesDefs.transition),
|
|
84
93
|
{
|
|
85
94
|
id: 'state',
|
|
86
|
-
|
|
95
|
+
prop: 'state',
|
|
87
96
|
name: 'Switch state',
|
|
88
97
|
options: ['transition'],
|
|
89
98
|
icon: undefined,
|
|
@@ -133,29 +142,28 @@ class DeviceController {
|
|
|
133
142
|
type: 'string',
|
|
134
143
|
def: '#ff00ff',
|
|
135
144
|
setter: (value) => {
|
|
136
|
-
|
|
145
|
+
// Fix 6: redundante [0,0]-Initialisierung entfernt
|
|
137
146
|
const rgbcolor = colors.ParseColor(value);
|
|
138
|
-
xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
|
|
147
|
+
const xy = rgb.rgb_to_cie(rgbcolor.r, rgbcolor.g, rgbcolor.b);
|
|
139
148
|
return {
|
|
140
149
|
x: xy[0],
|
|
141
150
|
y: xy[1],
|
|
142
151
|
};
|
|
143
152
|
},
|
|
144
153
|
getter: (payload) => {
|
|
145
|
-
if (payload.color_mode
|
|
154
|
+
if (payload.color_mode !== 'xy' && !this.config.colorTempSyncColor) {
|
|
146
155
|
return undefined;
|
|
147
156
|
}
|
|
148
|
-
|
|
157
|
+
// Fix 2: x=0 / y=0 sind gültige CIE-Koordinaten → != null statt truthy
|
|
158
|
+
if (payload.color && payload.color.x != null && payload.color.y != null) {
|
|
149
159
|
const colorval = rgb.cie_to_rgb(payload.color.x, payload.color.y);
|
|
150
160
|
return (
|
|
151
|
-
`#${
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}${utils.decimalToHex(colorval[2])}`
|
|
161
|
+
`#${utils.decimalToHex(colorval[0])
|
|
162
|
+
}${utils.decimalToHex(colorval[1])
|
|
163
|
+
}${utils.decimalToHex(colorval[2])}`
|
|
155
164
|
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
159
167
|
},
|
|
160
168
|
},
|
|
161
169
|
{
|
|
@@ -168,22 +176,21 @@ class DeviceController {
|
|
|
168
176
|
write: true,
|
|
169
177
|
read: true,
|
|
170
178
|
type: 'number',
|
|
171
|
-
min: this.config.useKelvin
|
|
172
|
-
max: this.config.useKelvin
|
|
173
|
-
def: this.config.useKelvin
|
|
174
|
-
unit: this.config.useKelvin
|
|
179
|
+
min: this.config.useKelvin === true ? utils.miredKelvinConversion(550) : 150,
|
|
180
|
+
max: this.config.useKelvin === true ? utils.miredKelvinConversion(153) : 500,
|
|
181
|
+
def: this.config.useKelvin === true ? utils.miredKelvinConversion(153) : 500,
|
|
182
|
+
unit: this.config.useKelvin === true ? 'K' : 'mired',
|
|
175
183
|
setter: (value) => {
|
|
176
184
|
return utils.toMired(value);
|
|
177
185
|
},
|
|
178
186
|
getter: (payload) => {
|
|
179
|
-
if (payload.color_mode
|
|
187
|
+
if (payload.color_mode !== 'color_temp') {
|
|
180
188
|
return undefined;
|
|
181
189
|
}
|
|
182
|
-
if (this.config.useKelvin
|
|
190
|
+
if (this.config.useKelvin === true) {
|
|
183
191
|
return utils.miredKelvinConversion(payload.color_temp);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
192
|
+
}
|
|
193
|
+
return payload.color_temp;
|
|
187
194
|
},
|
|
188
195
|
},
|
|
189
196
|
{
|
|
@@ -248,12 +255,21 @@ class DeviceController {
|
|
|
248
255
|
* @param groupsMessage
|
|
249
256
|
*/
|
|
250
257
|
async createGroupDefinitions(groupsMessage) {
|
|
258
|
+
// Fix 4: groupsMessage kann null/kein Array sein
|
|
259
|
+
if (!Array.isArray(groupsMessage)) {
|
|
260
|
+
this.adapter.log.warn('createGroupDefinitions: payload ist kein Array');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
251
263
|
utils.clearArray(this.groupCache);
|
|
252
264
|
for (const groupMessage of groupsMessage) {
|
|
253
|
-
|
|
265
|
+
// Fix 5: fehlende Pflichtfelder abfangen
|
|
266
|
+
if (!groupMessage || groupMessage.id == null || !groupMessage.friendly_name) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (this.logCustomizations.debugDevices.includes(String(groupMessage.id))) {
|
|
254
270
|
this.adapter.log.warn(`--->>> fromZ2M -> ${groupMessage.id} exposes: ${JSON.stringify(groupMessage)}`);
|
|
255
271
|
}
|
|
256
|
-
await this.defineGroupDevice(groupMessage.friendly_name, `group_${groupMessage.id}`, groupMessage.scenes);
|
|
272
|
+
await this.defineGroupDevice(groupMessage.friendly_name, `group_${groupMessage.id}`, groupMessage.scenes || []);
|
|
257
273
|
}
|
|
258
274
|
}
|
|
259
275
|
|
|
@@ -261,144 +277,158 @@ class DeviceController {
|
|
|
261
277
|
*
|
|
262
278
|
*/
|
|
263
279
|
async createOrUpdateDevices() {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
280
|
+
// Gruppen und Geräte getrennt verarbeiten
|
|
281
|
+
for (const device of this.groupCache) {
|
|
282
|
+
await this._createOrUpdateSingleDevice(device, true);
|
|
283
|
+
}
|
|
284
|
+
for (const device of this.deviceCache) {
|
|
285
|
+
await this._createOrUpdateSingleDevice(device, false);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
267
288
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
289
|
+
/**
|
|
290
|
+
* @param device
|
|
291
|
+
* @param isGroup
|
|
292
|
+
*/
|
|
293
|
+
async _createOrUpdateSingleDevice(device, isGroup) {
|
|
294
|
+
let deviceName = await this.getDeviceName(device);
|
|
295
|
+
let description = await this.getDeviceDescription(device);
|
|
272
296
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
if (deviceName === '' && device.description) {
|
|
298
|
+
deviceName = device.description;
|
|
299
|
+
description = '';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Disabled-Flag nur bei echten Geräten – Gruppen können nicht disabled sein
|
|
303
|
+
if (!isGroup && device.disabled === true) {
|
|
304
|
+
if (this.config.useEventInDesc === true) {
|
|
305
|
+
description = 'Device is disabled!';
|
|
306
|
+
} else {
|
|
307
|
+
deviceName = `[Disabled] ${deviceName}`;
|
|
280
308
|
}
|
|
309
|
+
}
|
|
281
310
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
common: {
|
|
286
|
-
icon: device.icon,
|
|
287
|
-
name: deviceName,
|
|
288
|
-
desc: description,
|
|
289
|
-
statusStates: { onlineId: '' },
|
|
290
|
-
},
|
|
291
|
-
native: {
|
|
292
|
-
deviceRemoved: false,
|
|
293
|
-
groupDevice: false,
|
|
294
|
-
},
|
|
295
|
-
};
|
|
311
|
+
if (!this.createCache[device.ieee_address] ||
|
|
312
|
+
this.createCache[device.ieee_address].name !== deviceName ||
|
|
313
|
+
this.createCache[device.ieee_address].description !== description) {
|
|
296
314
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
await this.adapter.extendObjectAsync(device.ieee_address, deviceObj);
|
|
311
|
-
this.createCache[device.ieee_address] = { name: deviceName, description: description };
|
|
312
|
-
}
|
|
315
|
+
const deviceObj = {
|
|
316
|
+
type: 'device',
|
|
317
|
+
common: {
|
|
318
|
+
icon: device.icon,
|
|
319
|
+
name: deviceName,
|
|
320
|
+
desc: description,
|
|
321
|
+
statusStates: { onlineId: '' },
|
|
322
|
+
},
|
|
323
|
+
native: {
|
|
324
|
+
deviceRemoved: false,
|
|
325
|
+
groupDevice: isGroup,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
313
328
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const sceneStates = await this.adapter.getStatesAsync(`${device.ieee_address}.scene_*`);
|
|
317
|
-
const sceneIDs = Object.keys(sceneStates);
|
|
318
|
-
for (const sceneID of sceneIDs) {
|
|
319
|
-
const stateID = sceneID.split('.')[3];
|
|
320
|
-
if (device.states.find((x) => x.id == stateID) == null) {
|
|
321
|
-
this.adapter.delObject(sceneID);
|
|
322
|
-
}
|
|
329
|
+
if (isGroup || device.disabled !== true) {
|
|
330
|
+
deviceObj.common.statusStates.onlineId = `${this.adapter.name}.${this.adapter.instance}.${device.ieee_address}.available`;
|
|
323
331
|
}
|
|
324
332
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const iobState = {
|
|
329
|
-
type: 'state',
|
|
330
|
-
common: await this.copyAndCleanStateObj(state),
|
|
331
|
-
native: {},
|
|
332
|
-
};
|
|
333
|
+
await this.adapter.extendObjectAsync(device.ieee_address, deviceObj);
|
|
334
|
+
this.createCache[device.ieee_address] = { name: deviceName, description: description };
|
|
335
|
+
}
|
|
333
336
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
+
if (!Array.isArray(device.states)) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Veraltete scene_*-States bereinigen – getForeignObjectsAsync mit vollem Namespace-Pfad
|
|
342
|
+
const sceneObjects = await this.adapter.getForeignObjectsAsync(
|
|
343
|
+
`${this.adapter.namespace}.${device.ieee_address}.scene_*`
|
|
344
|
+
);
|
|
345
|
+
if (sceneObjects) {
|
|
346
|
+
for (const sceneObjId of Object.keys(sceneObjects)) {
|
|
347
|
+
const parts = sceneObjId.split('.');
|
|
348
|
+
const stateID = parts[parts.length - 1];
|
|
349
|
+
if (!device.states.find((x) => x.id === stateID)) {
|
|
350
|
+
await this.adapter.delForeignObjectAsync(sceneObjId);
|
|
337
351
|
}
|
|
338
352
|
}
|
|
339
353
|
}
|
|
340
|
-
}
|
|
341
354
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
355
|
+
for (const state of device.states) {
|
|
356
|
+
if (state && (!this.createCache[device.ieee_address][state.id] ||
|
|
357
|
+
this.createCache[device.ieee_address][state.id].name !== state.name)) {
|
|
358
|
+
|
|
359
|
+
const iobState = {
|
|
360
|
+
type: 'state',
|
|
361
|
+
common: await this.copyAndCleanStateObj(state),
|
|
362
|
+
native: {},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
await this.adapter.extendObjectAsync(`${device.ieee_address}.${state.id}`, iobState);
|
|
366
|
+
this.createCache[device.ieee_address][state.id] = { name: state.name, created: true };
|
|
367
|
+
}
|
|
352
368
|
}
|
|
353
369
|
}
|
|
354
370
|
|
|
355
371
|
/**
|
|
356
|
-
*
|
|
372
|
+
* Nur echte Geräte prüfen – Gruppen werden hier nie verarbeitet
|
|
357
373
|
*/
|
|
358
374
|
async checkAndProgressDeviceRemove() {
|
|
359
|
-
let description = '';
|
|
360
|
-
let deviceName = '';
|
|
361
375
|
let iobDevices = await this.adapter.getDevicesAsync();
|
|
362
|
-
//
|
|
363
|
-
iobDevices = iobDevices.filter((x) => x.native.deviceRemoved
|
|
364
|
-
|
|
365
|
-
iobDevices = iobDevices.filter((x) => x.native.groupDevice == false);
|
|
376
|
+
// Nur nicht-entfernte, echte Geräte (keine Gruppen)
|
|
377
|
+
iobDevices = iobDevices.filter((x) => x.native && x.native.deviceRemoved === false);
|
|
378
|
+
iobDevices = iobDevices.filter((x) => x.native && x.native.groupDevice === false);
|
|
366
379
|
|
|
367
380
|
for (const iobDevice of iobDevices) {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
deviceName = iobDevice.common.name;
|
|
381
|
+
const idParts = iobDevice._id.split('.');
|
|
382
|
+
if (idParts.length < 3) {continue;}
|
|
383
|
+
const ieee_address = idParts[2];
|
|
372
384
|
|
|
373
|
-
|
|
385
|
+
// Gruppen-Einträge im ioBroker-Baum explizit überspringen
|
|
386
|
+
if (ieee_address.startsWith('group_')) {continue;}
|
|
387
|
+
|
|
388
|
+
if (!this.deviceCache.find((x) => x.ieee_address === ieee_address)) {
|
|
389
|
+
let deviceName = iobDevice.common && iobDevice.common.name ? iobDevice.common.name : ieee_address;
|
|
390
|
+
let description = '';
|
|
391
|
+
|
|
392
|
+
if (this.config.useEventInDesc === true) {
|
|
374
393
|
description = 'Device was removed!';
|
|
375
394
|
} else {
|
|
376
395
|
deviceName = `[Removed] ${deviceName}`;
|
|
377
396
|
}
|
|
378
397
|
|
|
379
|
-
this.adapter.
|
|
380
|
-
common: {
|
|
381
|
-
|
|
382
|
-
desc: description,
|
|
383
|
-
},
|
|
384
|
-
native: {
|
|
385
|
-
deviceRemoved: true,
|
|
386
|
-
},
|
|
398
|
+
await this.adapter.extendObjectAsync(ieee_address, {
|
|
399
|
+
common: { name: deviceName, desc: description },
|
|
400
|
+
native: { deviceRemoved: true },
|
|
387
401
|
});
|
|
388
|
-
this.adapter.setStateChangedAsync(`${ieee_address}.available`, false, true);
|
|
402
|
+
await this.adapter.setStateChangedAsync(`${ieee_address}.available`, false, true);
|
|
389
403
|
|
|
390
404
|
delete this.createCache[ieee_address];
|
|
391
405
|
}
|
|
392
406
|
}
|
|
393
407
|
}
|
|
394
408
|
|
|
409
|
+
/**
|
|
410
|
+
*
|
|
411
|
+
* @param messageObj
|
|
412
|
+
*/
|
|
413
|
+
async renameDeviceInCache(messageObj) {
|
|
414
|
+
if (!messageObj.payload || !messageObj.payload.data) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const renamedDevice = this.groupCache
|
|
418
|
+
.concat(this.deviceCache)
|
|
419
|
+
.find((x) => x.id === messageObj.payload.data.from);
|
|
420
|
+
if (renamedDevice) {
|
|
421
|
+
renamedDevice.id = messageObj.payload.data.to;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
395
425
|
/**
|
|
396
426
|
*
|
|
397
427
|
* @param devices
|
|
398
428
|
* @param ieee_address
|
|
399
429
|
*/
|
|
400
430
|
removeDeviceByIeee(devices, ieee_address) {
|
|
401
|
-
const idx = devices.findIndex((x) => x.ieee_address
|
|
431
|
+
const idx = devices.findIndex((x) => x.ieee_address === ieee_address);
|
|
402
432
|
if (idx > -1) {
|
|
403
433
|
devices.splice(idx, 1);
|
|
404
434
|
}
|
|
@@ -434,7 +464,7 @@ class DeviceController {
|
|
|
434
464
|
* @param device
|
|
435
465
|
*/
|
|
436
466
|
getDeviceName(device) {
|
|
437
|
-
return device.id
|
|
467
|
+
return device.id === device.ieee_address ? '' : device.id;
|
|
438
468
|
}
|
|
439
469
|
|
|
440
470
|
/**
|
|
@@ -456,7 +486,11 @@ class DeviceController {
|
|
|
456
486
|
this.adapter.setState('info.missing_routers_count', missingRoutersCount, true);
|
|
457
487
|
|
|
458
488
|
if (missingRoutersCount > 0) {
|
|
459
|
-
this.
|
|
489
|
+
const logLvl = this.config.coordinatorCheckLogLvl;
|
|
490
|
+
const logFn = (logLvl && typeof this.adapter.log[logLvl] === 'function')
|
|
491
|
+
? this.adapter.log[logLvl].bind(this.adapter.log)
|
|
492
|
+
: this.adapter.log.warn.bind(this.adapter.log);
|
|
493
|
+
logFn(
|
|
460
494
|
`Coordinator check: ${missingRoutersCount} missing routers were found, please check the data point 'zigbee2mqtt.x.info.missing_routers'!`
|
|
461
495
|
);
|
|
462
496
|
} else {
|