iobroker.device-watcher 2.15.13 → 2.15.15

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 CHANGED
@@ -191,6 +191,12 @@ This adapter would not have been possible without the great work of Christian Be
191
191
  Placeholder for the next version (at the beginning of the line):
192
192
  ### **WORK IN PROGRESS**
193
193
  -->
194
+ ### 2.15.15 (2026-05-13)
195
+ * (arteck) fix adapter device mix
196
+
197
+ ### 2.15.14 (2026-05-13)
198
+ * (arteck) fix high i/o
199
+
194
200
  ### 2.15.13 (2026-05-13)
195
201
  * (arteck) fix new devices
196
202
 
@@ -200,13 +206,6 @@ This adapter would not have been possible without the great work of Christian Be
200
206
  ### 2.15.11 (2026-05-06)
201
207
  * (arteck)
202
208
 
203
- ### 2.15.10 (2026-05-06)
204
- - (copilot) Adapter requires node.js >= 22 now
205
- * (arteck) fix adapter crash after delete a device
206
-
207
- ### 2.15.9 (2026-04-22)
208
- * (arteck) new xsense (v. 0.4.0) structure, plz update before
209
-
210
209
  ## License
211
210
 
212
211
  MIT License
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "device-watcher",
4
- "version": "2.15.13",
4
+ "version": "2.15.15",
5
5
  "news": {
6
+ "2.15.15": {
7
+ "en": "fix adapter device mix",
8
+ "de": "befestigungsadapter-mix",
9
+ "ru": "смесь адаптерного устройства",
10
+ "pt": "corrigir mix de dispositivo adaptador",
11
+ "nl": "fix adapter apparaat mix",
12
+ "fr": "fixer le mélange de périphérique d'adaptateur",
13
+ "it": "fix adattatore dispositivo mix",
14
+ "es": "mezcla de dispositivo de fijación",
15
+ "pl": "mix urządzenia adaptera",
16
+ "uk": "фіксувати пристрій адаптера",
17
+ "zh-cn": "固定适配器设备组合"
18
+ },
19
+ "2.15.14": {
20
+ "en": "fix high i/o",
21
+ "de": "hoch i/o",
22
+ "ru": "фиксировать высокий i/o",
23
+ "pt": "fixar i/o elevado",
24
+ "nl": "fix high i/o",
25
+ "fr": "fixer haut i/o",
26
+ "it": "correzione alta i/o",
27
+ "es": "fijar alto i/o",
28
+ "pl": "fix high i / o",
29
+ "uk": "фіксувати високий i/o",
30
+ "zh-cn": "固定高 i/o"
31
+ },
6
32
  "2.15.13": {
7
33
  "en": "fix new devices",
8
34
  "de": "neue geräte installieren",
package/lib/crud.js CHANGED
@@ -1,6 +1,53 @@
1
1
  const translations = require('./translations');
2
2
  const tools = require('./tools');
3
3
 
4
+ // In-Memory-Cache für writeDatapoints – verhindert unnötige State-Schreibvorgänge
5
+ const _dpWriteCache = new Map();
6
+
7
+ /**
8
+ * Serialisiert für den Cache-Vergleich.
9
+ * LastContact-Felder werden ignoriert – sie ändern sich jede Minute ("5 min" → "6 min")
10
+ * und dürfen keinen State-Write triggern.
11
+ *
12
+ * @param val
13
+ */
14
+ function serializeForCache(val) {
15
+ if (typeof val !== 'string' || !val.startsWith('[')) {
16
+ return typeof val === 'object' ? JSON.stringify(val) : String(val);
17
+ }
18
+ try {
19
+ const parsed = JSON.parse(val);
20
+ if (!Array.isArray(parsed)) {
21
+ return val;
22
+ }
23
+ const stripped = parsed.map((entry) => {
24
+ if (typeof entry !== 'object' || entry === null) {
25
+ return entry;
26
+ }
27
+ const copy = { ...entry };
28
+ for (const key of Object.keys(copy)) {
29
+ if (key.toLowerCase().includes('contact') || key.toLowerCase().includes('kontakt')) {
30
+ delete copy[key];
31
+ }
32
+ }
33
+ return copy;
34
+ });
35
+ return JSON.stringify(stripped);
36
+ } catch {
37
+ return val;
38
+ }
39
+ }
40
+
41
+ async function setStateIfChanged(adaptr, dpKey, val) {
42
+ const cached = _dpWriteCache.get(dpKey);
43
+ const cacheKey = serializeForCache(val);
44
+ if (cached === cacheKey) {
45
+ return;
46
+ }
47
+ _dpWriteCache.set(dpKey, cacheKey);
48
+ await adaptr.setStateChangedAsync(dpKey, { val, ack: true });
49
+ }
50
+
4
51
  /**
5
52
  * @param adaptr
6
53
  * @param {object} adptName - Adaptername of devices
@@ -1524,7 +1571,8 @@ async function createLists(adaptr, adptName) {
1524
1571
  }
1525
1572
 
1526
1573
  if (adaptr.config.createOwnFolder && adptName !== '') {
1527
- if (!deviceData.adapterID.includes(adptName)) {
1574
+ // Exakter Vergleich statt .includes() – verhindert dass z.B. zigbee2MQTT-Geräte im zigbee-Ordner erscheinen
1575
+ if (deviceData.adapterID !== adptName) {
1528
1576
  continue;
1529
1577
  }
1530
1578
  /*---------- fill user lists for each adapter ----------*/
@@ -1547,22 +1595,19 @@ async function writeDatapoints(adaptr, adptName) {
1547
1595
 
1548
1596
  try {
1549
1597
  let dpSubFolder;
1550
- //write the datapoints in subfolders with the adaptername otherwise write the dP's in the root folder
1551
1598
  if (adptName) {
1552
1599
  dpSubFolder = `${adptName}.`;
1553
1600
  } else {
1554
1601
  dpSubFolder = '';
1555
1602
  }
1556
1603
 
1557
- // Write Datapoints for counts
1558
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}offlineCount`, { val: adaptr.offlineDevicesCount, ack: true });
1559
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}countAll`, { val: adaptr.deviceCounter, ack: true });
1560
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}batteryCount`, { val: adaptr.batteryPoweredCount, ack: true });
1561
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}lowBatteryCount`, { val: adaptr.lowBatteryPoweredCount, ack: true });
1562
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}upgradableCount`, { val: adaptr.upgradableDevicesCount, ack: true });
1563
- // List all devices
1604
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}offlineCount`, adaptr.offlineDevicesCount);
1605
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}countAll`, adaptr.deviceCounter);
1606
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}batteryCount`, adaptr.batteryPoweredCount);
1607
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}lowBatteryCount`, adaptr.lowBatteryPoweredCount);
1608
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}upgradableCount`, adaptr.upgradableDevicesCount);
1609
+
1564
1610
  if (adaptr.deviceCounter === 0) {
1565
- // if no device is count, write the JSON List with default value
1566
1611
  adaptr.listAllDevices = [
1567
1612
  {
1568
1613
  [translations.Device[adaptr.config.userSelectedLanguage]]: '--none--',
@@ -1590,12 +1635,10 @@ async function writeDatapoints(adaptr, adptName) {
1590
1635
  },
1591
1636
  ];
1592
1637
  }
1593
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}listAll`, { val: JSON.stringify(adaptr.listAllDevices), ack: true });
1594
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}listAllRawJSON`, { val: JSON.stringify(adaptr.listAllDevicesUserRaw), ack: true });
1638
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}listAll`, JSON.stringify(adaptr.listAllDevices));
1639
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}listAllRawJSON`, JSON.stringify(adaptr.listAllDevicesUserRaw));
1595
1640
 
1596
- // List link quality
1597
1641
  if (adaptr.linkQualityCount === 0) {
1598
- // if no device is count, write the JSON List with default value
1599
1642
  adaptr.linkQualityDevices = [
1600
1643
  {
1601
1644
  [translations.Device[adaptr.config.userSelectedLanguage]]: '--none--',
@@ -1604,15 +1647,9 @@ async function writeDatapoints(adaptr, adptName) {
1604
1647
  },
1605
1648
  ];
1606
1649
  }
1607
- //write JSON list
1608
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}linkQualityList`, {
1609
- val: JSON.stringify(adaptr.linkQualityDevices),
1610
- ack: true,
1611
- });
1650
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}linkQualityList`, JSON.stringify(adaptr.linkQualityDevices));
1612
1651
 
1613
- // List offline devices
1614
1652
  if (adaptr.offlineDevicesCount === 0) {
1615
- // if no device is count, write the JSON List with default value
1616
1653
  adaptr.offlineDevices = [
1617
1654
  {
1618
1655
  [translations.Device[adaptr.config.userSelectedLanguage]]: '--none--',
@@ -1621,15 +1658,9 @@ async function writeDatapoints(adaptr, adptName) {
1621
1658
  },
1622
1659
  ];
1623
1660
  }
1624
- //write JSON list
1625
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}offlineList`, {
1626
- val: JSON.stringify(adaptr.offlineDevices),
1627
- ack: true,
1628
- });
1661
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}offlineList`, JSON.stringify(adaptr.offlineDevices));
1629
1662
 
1630
- // List updatable
1631
1663
  if (adaptr.upgradableDevicesCount === 0) {
1632
- // if no device is count, write the JSON List with default value
1633
1664
  adaptr.upgradableList = [
1634
1665
  {
1635
1666
  [translations.Device[adaptr.config.userSelectedLanguage]]: '--none--',
@@ -1638,15 +1669,9 @@ async function writeDatapoints(adaptr, adptName) {
1638
1669
  },
1639
1670
  ];
1640
1671
  }
1641
- //write JSON list
1642
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}upgradableList`, {
1643
- val: JSON.stringify(adaptr.upgradableList),
1644
- ack: true,
1645
- });
1672
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}upgradableList`, JSON.stringify(adaptr.upgradableList));
1646
1673
 
1647
- // List battery powered
1648
1674
  if (adaptr.batteryPoweredCount === 0) {
1649
- // if no device is count, write the JSON List with default value
1650
1675
  adaptr.batteryPowered = [
1651
1676
  {
1652
1677
  [translations.Device[adaptr.config.userSelectedLanguage]]: '--none--',
@@ -1655,15 +1680,9 @@ async function writeDatapoints(adaptr, adptName) {
1655
1680
  },
1656
1681
  ];
1657
1682
  }
1658
- //write JSON list
1659
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}batteryList`, {
1660
- val: JSON.stringify(adaptr.batteryPowered),
1661
- ack: true,
1662
- });
1683
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}batteryList`, JSON.stringify(adaptr.batteryPowered));
1663
1684
 
1664
- // list battery low powered
1665
1685
  if (adaptr.lowBatteryPoweredCount === 0) {
1666
- // if no device is count, write the JSON List with default value
1667
1686
  adaptr.batteryLowPowered = [
1668
1687
  {
1669
1688
  [translations.Device[adaptr.config.userSelectedLanguage]]: '--none--',
@@ -1672,93 +1691,37 @@ async function writeDatapoints(adaptr, adptName) {
1672
1691
  },
1673
1692
  ];
1674
1693
  }
1675
- //write JSON list
1676
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}lowBatteryList`, {
1677
- val: JSON.stringify(adaptr.batteryLowPowered),
1678
- ack: true,
1679
- });
1694
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}lowBatteryList`, JSON.stringify(adaptr.batteryLowPowered));
1680
1695
 
1681
- // set booleans datapoints
1682
- if (adaptr.offlineDevicesCount === 0) {
1683
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}oneDeviceOffline`, {
1684
- val: false,
1685
- ack: true,
1686
- });
1687
- } else {
1688
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}oneDeviceOffline`, {
1689
- val: true,
1690
- ack: true,
1691
- });
1692
- }
1693
-
1694
- if (adaptr.lowBatteryPoweredCount === 0) {
1695
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}oneDeviceLowBat`, {
1696
- val: false,
1697
- ack: true,
1698
- });
1699
- } else {
1700
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}oneDeviceLowBat`, {
1701
- val: true,
1702
- ack: true,
1703
- });
1704
- }
1705
-
1706
- if (adaptr.upgradableDevicesCount === 0) {
1707
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}oneDeviceUpdatable`, {
1708
- val: false,
1709
- ack: true,
1710
- });
1711
- } else {
1712
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}oneDeviceUpdatable`, {
1713
- val: true,
1714
- ack: true,
1715
- });
1716
- }
1696
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}oneDeviceOffline`, adaptr.offlineDevicesCount > 0);
1697
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}oneDeviceLowBat`, adaptr.lowBatteryPoweredCount > 0);
1698
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}oneDeviceUpdatable`, adaptr.upgradableDevicesCount > 0);
1717
1699
 
1718
- //write HTML list
1719
1700
  if (adaptr.configCreateHtmlList) {
1720
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}linkQualityListHTML`, {
1721
- val: await this.createListHTML(adaptr, 'linkQualityList', adaptr.linkQualityDevices, adaptr.linkQualityCount, null),
1722
- ack: true,
1723
- });
1724
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}offlineListHTML`, {
1725
- val: await this.createListHTML(adaptr, 'offlineList', adaptr.offlineDevices, adaptr.offlineDevicesCount, null),
1726
- ack: true,
1727
- });
1728
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}batteryListHTML`, {
1729
- val: await this.createListHTML(adaptr, 'batteryList', adaptr.batteryPowered, adaptr.batteryPoweredCount, false),
1730
- ack: true,
1731
- });
1732
- await adaptr.setStateChangedAsync(`devices.${dpSubFolder}lowBatteryListHTML`, {
1733
- val: await this.createListHTML(adaptr, 'batteryList', adaptr.batteryLowPowered, adaptr.lowBatteryPoweredCount, true),
1734
- ack: true,
1735
- });
1701
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}linkQualityListHTML`,
1702
+ await this.createListHTML(adaptr, 'linkQualityList', adaptr.linkQualityDevices, adaptr.linkQualityCount, null));
1703
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}offlineListHTML`,
1704
+ await this.createListHTML(adaptr, 'offlineList', adaptr.offlineDevices, adaptr.offlineDevicesCount, null));
1705
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}batteryListHTML`,
1706
+ await this.createListHTML(adaptr, 'batteryList', adaptr.batteryPowered, adaptr.batteryPoweredCount, false));
1707
+ await setStateIfChanged(adaptr, `devices.${dpSubFolder}lowBatteryListHTML`,
1708
+ await this.createListHTML(adaptr, 'batteryList', adaptr.batteryLowPowered, adaptr.lowBatteryPoweredCount, true));
1736
1709
 
1737
1710
  if (adaptr.config.checkAdapterInstances) {
1738
- await adaptr.setStateChangedAsync(`adapterAndInstances.HTML_Lists.listAllInstancesHTML`, {
1739
- val: await this.createListHTMLInstances(adaptr, 'allInstancesList', adaptr.listAllInstances, adaptr.countAllInstances),
1740
- ack: true,
1741
- });
1742
- await adaptr.setStateChangedAsync(`adapterAndInstances.HTML_Lists.listAllActiveInstancesHTML`, {
1743
- val: await this.createListHTMLInstances(adaptr, 'allActiveInstancesList', adaptr.listAllActiveInstances, adaptr.countAllActiveInstances),
1744
- ack: true,
1745
- });
1746
- await adaptr.setStateChangedAsync(`adapterAndInstances.HTML_Lists.listInstancesErrorHTML`, {
1747
- val: await this.createListHTMLInstances(adaptr, 'errorInstanceList', adaptr.listErrorInstance, adaptr.countErrorInstance),
1748
- ack: true,
1749
- });
1750
- await adaptr.setStateChangedAsync(`adapterAndInstances.HTML_Lists.listDeactivatedInstancesHTML`, {
1751
- val: await this.createListHTMLInstances(adaptr, 'deactivatedInstanceList', adaptr.listDeactivatedInstances, adaptr.countDeactivatedInstances),
1752
- ack: true,
1753
- });
1754
- await adaptr.setStateChangedAsync(`adapterAndInstances.HTML_Lists.listAdapterUpdatesHTML`, {
1755
- val: await this.createListHTMLInstances(adaptr, 'updateAdapterList', adaptr.listAdapterUpdates, adaptr.countAdapterUpdates),
1756
- ack: true,
1757
- });
1711
+ await setStateIfChanged(adaptr, `adapterAndInstances.HTML_Lists.listAllInstancesHTML`,
1712
+ await this.createListHTMLInstances(adaptr, 'allInstancesList', adaptr.listAllInstances, adaptr.countAllInstances));
1713
+ await setStateIfChanged(adaptr, `adapterAndInstances.HTML_Lists.listAllActiveInstancesHTML`,
1714
+ await this.createListHTMLInstances(adaptr, 'allActiveInstancesList', adaptr.listAllActiveInstances, adaptr.countAllActiveInstances));
1715
+ await setStateIfChanged(adaptr, `adapterAndInstances.HTML_Lists.listInstancesErrorHTML`,
1716
+ await this.createListHTMLInstances(adaptr, 'errorInstanceList', adaptr.listErrorInstance, adaptr.countErrorInstance));
1717
+ await setStateIfChanged(adaptr, `adapterAndInstances.HTML_Lists.listDeactivatedInstancesHTML`,
1718
+ await this.createListHTMLInstances(adaptr, 'deactivatedInstanceList', adaptr.listDeactivatedInstances, adaptr.countDeactivatedInstances));
1719
+ await setStateIfChanged(adaptr, `adapterAndInstances.HTML_Lists.listAdapterUpdatesHTML`,
1720
+ await this.createListHTMLInstances(adaptr, 'updateAdapterList', adaptr.listAdapterUpdates, adaptr.countAdapterUpdates));
1758
1721
  }
1759
1722
  }
1760
1723
 
1761
- // create timestamp of last run
1724
+ // Heartbeat immer schreiben
1762
1725
  const lastCheck = `${adaptr.formatDate(new Date(), 'DD.MM.YYYY')} - ${adaptr.formatDate(new Date(), 'hh:mm:ss')}`;
1763
1726
  await adaptr.setStateChangedAsync('lastCheck', lastCheck, true);
1764
1727
  } catch (error) {
package/lib/tools.js CHANGED
@@ -121,6 +121,8 @@ async function countDevices(adaptr) {
121
121
  * @param adaptr
122
122
  */
123
123
  async function checkLastContact(adaptr) {
124
+ let statusChanged = false;
125
+
124
126
  for (const [deviceID, deviceData] of adaptr.listAllDevicesRaw.entries()) {
125
127
  if (deviceData.instanceDeviceConnected !== false && deviceData.instanceDeviceConnected !== undefined) {
126
128
  deviceData.UnreachState = await getInitValue(adaptr, deviceData.UnreachDP);
@@ -152,14 +154,23 @@ async function checkLastContact(adaptr) {
152
154
  deviceData.Status = contactData[1];
153
155
  deviceData.linkQuality = contactData[2];
154
156
  }
157
+
158
+ // Dirty-Flag: NUR Status-Änderung (Online↔Offline) triggert Listenneuaufbau.
159
+ // LastContact ist ein sich jede Minute ändernder Anzeigestring ("5 min", "6 min")
160
+ // und darf NICHT als Änderungskriterium gelten.
161
+ if (deviceData.Status !== oldContactState) {
162
+ statusChanged = true;
163
+ }
164
+
155
165
  if (adaptr.config.checkSendOfflineMsg && oldContactState !== deviceData.Status && !adaptr.blacklistNotify.includes(deviceData.Path)) {
156
- // check if the generally deviceData connected state is for a while true
157
166
  if (await getTimestampConnectionDP(adaptr, deviceData.instanceDeviceConnectionDP, 50000)) {
158
167
  await adaptr.sendStateNotifications('Devices', 'onlineStateDevice', deviceID, silentEnabled.telegramSilent);
159
168
  }
160
169
  }
161
170
  }
162
171
  }
172
+
173
+ return statusChanged;
163
174
  }
164
175
 
165
176
  module.exports = {