iobroker.esphome 0.3.1 → 0.4.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/CHANGELOG_OLD.md CHANGED
@@ -1,4 +1,13 @@
1
1
  # Older changes
2
+ ## 0.2.3 (2021-06-29)
3
+ * (Jey-Cee) Bugfix : Light component state not changed [#74](https://github.com/DrozmotiX/ioBroker.esphome/issues/74)
4
+ * (DutchmanNL) Update compatibility to version 1.19.4 of ESPHome Dashboard
5
+
6
+ ## 0.2.2 (2021-06-24)
7
+ * (DutchmanNL) [!!! Breaking !!!] Make YAML file persistent, backup your configuration before updating ! solves [#57](https://github.com/DrozmotiX/ioBroker.esphome/issues/57)
8
+ * (DutchmanNL) Update ESPHome Dashboard to 1.18.0, requires Python >=3.7 (and ensure <4.0!)
9
+ * (DutchmanNL) Bugfix : Reconnect to devices without autodiscovery / MDNS-Broadcast in network, solves [#66](https://github.com/DrozmotiX/ioBroker.esphome/issues/66)
10
+
2
11
  ## 0.2.1-1 (2021-03-30)
3
12
  * (DutchmanNL) add cover component
4
13
  * (DutchmanNL) add transitionLength for lights
package/README.md CHANGED
@@ -112,30 +112,30 @@ If you like my work, please consider a personal donation
112
112
  ### __WORK IN PROGRESS__
113
113
  * (DutchmanNL)
114
114
  -->
115
+ ### 0.4.0 (2023-11-03)
116
+ * (DutchmanNL) Added cleanup capability for unused channels & states after initialisation of device, resolves #39
117
+ * (DutchmanNL) Added button to info channel which allows to delete all offline devices from adapter tree. resolves #39
118
+ * (DutchmanNL) [Breaking] Backup strategy changed, requires [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) and activate option for ESPHome, fixes #129
119
+
120
+ ### 0.3.2 (2023-11-01)
121
+ * (DutchmanNL) Improved error handling if devices are not reachable/disconnected
122
+ * (DutchmanNL) Bugfix: Allow control of brightness and color for light component, resolves #173
123
+
115
124
  ### 0.3.1 (2023-10-31)
116
- * (DutchmanNL) Bugfix: Show online state of ESP Device correctly, Fixes #106
125
+ * (DutchmanNL) Bugfix: Show online state of ESP Device correctly, resolves #106
117
126
 
118
127
  ### 0.3.0 (2023-10-31) - Bugfixes & Improvements
119
128
  * (Dutchman & SimonFischer04) Several Bugfixes
120
129
  * (SimonFischer04) Support type "select device"
121
130
  * (DutchmanNL) ESPHome dashboard default disabled
122
131
  * (SimonFischer04) Migrate to @2colors/esphome-native-api
123
- * (DutchmanNL) Automatically create needed directories, Fixes #168
132
+ * (DutchmanNL) Automatically create needed directories, resolves #168
124
133
  * (SimonFischer04) Migrate usage of python to new structure, should solve all ESPHome Dashboard related installation issues
125
134
 
126
135
  ### 0.2.4 (2021-08-24)
127
136
  * (DutchmanNL) Version of ESPHome Dashboard updated to 2021.8
128
137
  * (DutchmanNL) Add option if config of ESPHome device should be shown as states (default = FALSE, safes 8 states for each sensor)
129
138
 
130
- ### 0.2.3 (2021-06-29)
131
- * (Jey-Cee) Bugfix : Light component state not changed [#74](https://github.com/DrozmotiX/ioBroker.esphome/issues/74)
132
- * (DutchmanNL) Update compatibility to version 1.19.4 of ESPHome Dashboard
133
-
134
- ### 0.2.2 (2021-06-24)
135
- * (DutchmanNL) [!!! Breaking !!!] Make YAML file persistent, backup your configuration before updating ! solves [#57](https://github.com/DrozmotiX/ioBroker.esphome/issues/57)
136
- * (DutchmanNL) Update ESPHome Dashboard to 1.18.0, requires Python >=3.7 (and ensure <4.0!)
137
- * (DutchmanNL) Bugfix : Reconnect to devices without autodiscovery / MDNS-Broadcast in network, solves [#66](https://github.com/DrozmotiX/ioBroker.esphome/issues/66)
138
-
139
139
  ## License
140
140
  MIT License
141
141
 
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "esphome",
4
- "version": "0.3.1",
4
+ "version": "0.4.0",
5
5
  "news": {
6
+ "0.4.0": {
7
+ "en": "Added cleanup capability for unused channels & states after initialisation of device, resolves #39\nAdded button to info channel which allows to delete all offline devices from adapter tree. resolves #39\n[Breaking] Backup strategy changed, requires [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) and activate option for ESPHome, fixes #129",
8
+ "de": "Zusätzliche Reinigungsfähigkeit für ungenutzte Kanäle & Zustände nach der Initialisierung des Geräts, löst #39\nZusätzlicher Button zum Info-Kanal, der alle Offline-Geräte aus dem Adapterbaum löschen kann. löst #39\n[Breaking] Backup-Strategie geändert, erfordert [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) und aktiviert Option für ESPHome, repariert #129",
9
+ "ru": "Добавлена возможность очистки неиспользуемых каналов и государств после инициализации устройства, решает #39\nДобавлена кнопка к информационному каналу, что позволяет удалить все оффлайн устройства из дерева адаптера. решает #39\n[Breaking] Стратегия резервного копирования изменена, требует [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) и активировать вариант для ESPHome, исправлений #129",
10
+ "pt": "Adicionado capacidade de limpeza para canais não utilizados & estados após a inicialização do dispositivo, resolve #39\nAdicionado botão para info canal que permite excluir todos os dispositivos off-line da árvore do adaptador. resolve #39\n[Breaking] Estratégia de backup alterada, requer [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) e opção ativa para ESPHome, correções #129",
11
+ "nl": "Vervoegde schoonmaak capaciteit voor onbruikbare kanalen apparaat na de initialisatie van 39\nKnop toegevoegd aan info kanaal dat alle offline apparaten van adapter boom laat verwijderen. 39\nOndersteuningsstrategie veranderde, vereist [Backitup v2.9.1, htps:com/simateioBroker, en tactiveer de optie van 12 uur",
12
+ "fr": "Ajout d'une capacité de nettoyage pour les canaux non utilisés & états après l'initialisation du dispositif, résout #39\nAjout du bouton sur le canal d'info qui permet de supprimer tous les périphériques hors ligne de l'arbre d'adaptateur. résolution #39\n[Breaking] La stratégie de sauvegarde a changé, nécessite [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) et l'option active pour ESPHome, corrige #129",
13
+ "it": "Aggiunta funzionalità di pulizia per canali e stati non utilizzati dopo l'inizializzazione del dispositivo, risolve #39\nPulsante aggiunto al canale info che consente di eliminare tutti i dispositivi offline dall'albero dell'adattatore. risolve #39\n[Breaking] La strategia di backup è cambiata, richiede [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) e attiva l'opzione per ESPHome, corregge #129",
14
+ "es": "Capacidad de limpieza agregada para canales no utilizados \" estados después de la inicialización del dispositivo, resuelve #39\nSe agregó botón a canal de información que permite eliminar todos los dispositivos fuera de línea del árbol del adaptador. resuelve #39\n[Breaking] La estrategia de respaldo cambió, requiere [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) y activar la opción para ESPHome, fija #129",
15
+ "pl": "Po zainicjowaniu możliwości usuwania nieużywanych kanałów i stanów po inicjalizacji urządzenia, rozwiązał #39\nNaciśnięty przycisk do kanału informacyjnego, który umożliwia usunięcie wszystkich urządzeń offline z drzewa adapterowego. Rozwiązanie #39\n(Po zmianie strategii Backupa, wymaga od BOATUTUp v2.9.1(https:/github.com/simatec/ioBroker.backitup) i aktywacji opcji dla ESPHome, naprawa #129",
16
+ "uk": "Додано можливість очищення каналів і станів після ініціалізації пристрою, вирішує #39\nДодано кнопку на інформаційний канал, який дозволяє видалити всі offline пристрої з дерева адаптера. розчини #39\n[Breaking] Стратегія резервного копіювання змінено, вимагає [BackitUp v2.9.1](https://github.com/simatec/ioBroker.backitup) і активувати варіант для ESPHome, фіксує #129",
17
+ "zh-cn": "在装置初始化后增加无用渠道和各州的清洁能力,第39号决定\n添加但道托顿以电器,使所有线装置能够从适应树中删除。 行动39\n[Breaking] Backup战略改变,要求[BackitUp v2.9.1](http://github.com/simatec/ioBroker.backitup)和“ESPHome”的主动选择,第129号行动。"
18
+ },
19
+ "0.3.2": {
20
+ "en": "Improved error handling if devices are not reachable/disconnected\nBugfix: Allow control of brightness and color for light component, fixes #173",
21
+ "de": "Verbesserte Fehlerbehandlung, wenn Geräte nicht erreichbar/vernetzt sind\nBugfix: Erlauben Sie die Kontrolle von Helligkeit und Farbe für die Lichtkomponente, fixiert #173",
22
+ "ru": "Улучшена обработка ошибок, если устройства не доступны / отключены\nBugfix: Разрешить контроль яркости и цвета для легкой составляющей, исправляет #173",
23
+ "pt": "Melhor manuseio de erros se os dispositivos não forem acessíveis/desconectados\nBugfix: Permitir controle de brilho e cor para componente de luz, correções #173",
24
+ "nl": "Improviserende fouten als de apparatuur niet bereikbaar is\nBugfix: Controle van helderheid en kleur voor lichtcomonent, reparatie #173",
25
+ "fr": "Amélioration de la manipulation des erreurs si les dispositifs ne sont pas accessibles/déconnectés\nBugfix: Permettre le contrôle de la luminosité et de la couleur pour le composant de lumière, corrige #173",
26
+ "it": "Miglioramento della gestione degli errori se i dispositivi non sono raggiungibili/disconnessi\nBugfix: Consentire il controllo di luminosità e colore per componenti leggeri, correzioni #173",
27
+ "es": "Mejor manejo de errores si los dispositivos no son accesibles/desconectados\nBugfix: Permitir el control del brillo y el color para el componente de luz, fija #173",
28
+ "pl": "Poprawiona obsługa błędów, jeśli urządzenia nie są dosyć dopasowalne i rozproszone\nBugfix: Allow kontrola jasności i koloru dla komponentów świetlnych naprawia numer #173",
29
+ "uk": "Поліпшення обробки помилок, якщо пристрої не досягаються / відключені\nПомилки: Дозволяє контролювати яскравість і колір для легкого компонента, закріплює #173",
30
+ "zh-cn": "如果装置无法达到/无联系,改进错误处理\nBugfix: 允许控制光明和颜色,确定第173号行动。"
31
+ },
6
32
  "0.3.1": {
7
33
  "en": "Bugfix: Show online state of ESP Device correctly, Fixes #106",
8
34
  "de": "Bugfix: Online-Zustand von ESP Device korrekt anzeigen, Fixes #106",
@@ -64,30 +90,6 @@
64
90
  "es": "[!!! ¡¡¡Rompiendo !!!] ¡Haga que el archivo YAML sea persistente, haga una copia de seguridad de su configuración antes de actualizar! resuelve [# 57] (https://github.com/DrozmotiX/ioBroker.esphome/issues/57)\nActualice ESPHome Dashboard a 1.18.0, requiere Python> = 3.7 (¡y asegúrese de <4.0!)\nCorrección de error: vuelva a conectarse a dispositivos sin detección automática / MDNS-Broadcast en la red, resuelve [# 66] (https://github.com/DrozmotiX/ioBroker.esphome/issues/66)",
65
91
  "pl": "[!!! Breaking !!!] Utrwal plik YAML, wykonaj kopię zapasową konfiguracji przed aktualizacją! rozwiązuje [#57](https://github.com/DrozmotiX/ioBroker.esphome/issues/57)\nZaktualizuj pulpit nawigacyjny ESPHome do wersji 1.18.0, wymaga Pythona >=3.7 (i upewnij się, że <4.0!)\nBugfix: Połącz się ponownie z urządzeniami bez automatycznego wykrywania / MDNS-Broadcast w sieci, rozwiązuje [#66](https://github.com/DrozmotiX/ioBroker.esphome/issues/66)",
66
92
  "zh-cn": "[!!!打破 !!!] 使 YAML 文件持久化,更新前备份您的配置!解决 [#57](https://github.com/DrozmotiX/ioBroker.esphome/issues/57)\n将 ESPHome Dashboard 更新到 1.18.0,需要 Python >=3.7(并确保 <4.0!)\n修正:在网络中没有自动发现/MDNS-Broadcast 的情况下重新连接到设备,解决[#66](https://github.com/DrozmotiX/ioBroker.esphome/issues/66)"
67
- },
68
- "0.2.1-1": {
69
- "en": "add cover component\nadd transitionLength for lights",
70
- "de": "Abdeckungskomponente hinzufügen\nÜbergangslänge für Lichter hinzufügen",
71
- "ru": "добавить компонент обложки\nдобавить transitionLength для огней",
72
- "pt": "adicionar componente de capa\nadicionar comprimento de transição para luzes",
73
- "nl": "voeg een omslagcomponent toe\nvoeg transitionLength toe voor lichten",
74
- "fr": "ajouter un composant de couverture\najouter une longueur de transition pour les lumières",
75
- "it": "aggiungi il componente di copertina\naggiungi TransitionLength per le luci",
76
- "es": "agregar componente de portada\nagregar transiciónLength para luces",
77
- "pl": "dodaj komponent okładki\ndodaj długość przejścia dla świateł",
78
- "zh-cn": "添加封面组件\n为灯光添加transitionLength"
79
- },
80
- "0.2.1-0": {
81
- "en": "Logging improved, solves [#48](https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nName of states changed, solves [#49](https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
82
- "de": "Protokollierung verbessert, löst [# 48] (https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nName der Staaten geändert, löst [# 49] (https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
83
- "ru": "Ведение журнала улучшено, решает [# 48] (https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nНазвание состояний изменено, решает [# 49] (https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
84
- "pt": "Logging melhorado, resolve [# 48] (https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nNome dos estados alterados, resolve [# 49] (https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
85
- "nl": "Logboekregistratie verbeterd, lost op [# 48] (https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nNaam van staten gewijzigd, lost [# 49] op (https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
86
- "fr": "Journalisation améliorée, résout [# 48] (https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nLe nom des états a changé, résout [# 49] (https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
87
- "it": "Registrazione migliorata, risolve [# 48] (https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nIl nome degli stati è cambiato, risolve [# 49] (https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
88
- "es": "Registro mejorado, resuelve [# 48] (https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nNombre de los estados cambiado, resuelve [# 49] (https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
89
- "pl": "Logowanie poprawione, rozwiązuje [# 48] (https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\nZmiana nazwy stanów, rozwiązuje [# 49] (https://github.com/DrozmotiX/ioBroker.esphome/issues/49)",
90
- "zh-cn": "日志记录得到改进,解决了[#48](https://github.com/DrozmotiX/ioBroker.esphome/issues/48)\n状态名称更改,解决[#49](https://github.com/DrozmotiX/ioBroker.esphome/issues/49)"
91
93
  }
92
94
  },
93
95
  "title": "Control ESPHome managed devices",
@@ -144,7 +146,6 @@
144
146
  "dsn": "https://52227cfa250b49e99b1f28408273402c@sentry.iobroker.net/122"
145
147
  }
146
148
  },
147
- "dataFolder": "esphome.%INSTANCE%",
148
149
  "localLink": "http://%ESPHomeDashboardIP%:%ESPHomeDashboardPort%",
149
150
  "adminTab": {
150
151
  "link": "http://%ESPHomeDashboardIP%:%ESPHomeDashboardPort%",
package/main.js CHANGED
@@ -14,8 +14,10 @@ const stateAttr = require(__dirname + '/lib/stateAttr.js'); // Load attribute li
14
14
  const disableSentry = false; // Ensure to set to true during development!
15
15
  const warnMessages = {}; // Store warn messages to avoid multiple sending to sentry
16
16
  const fs = require('fs');
17
+ const {clearTimeout} = require('timers');
17
18
  const client = {};
18
- let reconnectTimer, reconnectInterval, apiPass, autodiscovery, dashboardProcess, createConfigStates;
19
+ const resetTimers = {}; // Memory allocation for all running timers
20
+ let reconnectInterval, apiPass, autodiscovery, dashboardProcess, createConfigStates;
19
21
 
20
22
  class Esphome extends utils.Adapter {
21
23
 
@@ -70,8 +72,23 @@ class Esphome extends utils.Adapter {
70
72
  this.log.info(`Native Integration of ESPHome Dashboard disabled `);
71
73
  }
72
74
 
75
+ // Create & Subscribe to button handling offline Device cleanup
76
+ this.extendObjectAsync('esphome.0.info.deviceCleanup',
77
+ {
78
+ 'type': 'state',
79
+ 'common': {
80
+ 'role': 'button',
81
+ 'name': 'Device or service connected',
82
+ 'type': 'boolean',
83
+ 'read': false,
84
+ 'write': true,
85
+ 'def': false
86
+ }
87
+ });
88
+ this.subscribeStates('esphome.0.info.deviceCleanup');
89
+
73
90
  } catch (e) {
74
- this.log.error(`Connection issue ${e}`);
91
+ this.log.error(`[Adapter start] Fatal error occurred ${e}`);
75
92
  }
76
93
  }
77
94
 
@@ -154,11 +171,7 @@ class Esphome extends utils.Adapter {
154
171
 
155
172
  // Get basic data of known devices and start reading data
156
173
  for (const i in knownDevices) {
157
- this.deviceInfo[knownDevices[i].native.ip] = {
158
- ip: knownDevices[i].native.ip,
159
- passWord: this.decrypt(knownDevices[i].native.passWord),
160
- };
161
- this.connectDevices(knownDevices[i].native.ip, this.deviceInfo[knownDevices[i].native.ip].passWord);
174
+ this.connectDevices(knownDevices[i].native.ip);
162
175
  }
163
176
  } catch (e) {
164
177
  this.sendSentry(`[tryKnownDevices] ${e}`);
@@ -176,13 +189,8 @@ class Esphome extends utils.Adapter {
176
189
  try {
177
190
  this.log.debug(`Discovery message ${JSON.stringify(message)}`);
178
191
  if (this.deviceInfo[message.address] == null) {
179
- this.log.info(`[AutoDiscovery] New ESPHome device found at IP ${message.address}`);
180
- // Store new Device information to device array in memory
181
- this.deviceInfo[message.address] = {
182
- ip: message.address,
183
- passWord: apiPass
184
- };
185
- this.connectDevices(`${message.address}`, `${apiPass}`);
192
+ this.log.info(`[AutoDiscovery] New ESPHome device found at IP ${message.address}, trying to initialize`);
193
+ this.connectDevices(`${message.address}`);
186
194
  }
187
195
  } catch (e) {
188
196
  this.log.error(`[deviceDiscovery handler] ${e}`);
@@ -197,16 +205,15 @@ class Esphome extends utils.Adapter {
197
205
  /**
198
206
  * Handle Socket connections
199
207
  * @param {string} host IP adress of device
200
- * @param {string | number | boolean} pass Password used for APU
201
208
  */
202
- connectDevices(host, pass) {
209
+ connectDevices(host) {
203
210
  try {
204
211
  // const host = espDevices[device].ip;
205
212
  this.log.info(`Try to connect to ${host}`);
206
213
  // Prepare connection attributes
207
214
  client[host] = new Client({
208
215
  host: host,
209
- password: pass,
216
+ password: apiPass,
210
217
  clientInfo: `${this.host}`,
211
218
  clearSession: true,
212
219
  initializeDeviceInfo: true,
@@ -223,6 +230,16 @@ class Esphome extends utils.Adapter {
223
230
  // Connection listener
224
231
  client[host].on('connected', async () => {
225
232
  try {
233
+ // Clear any existing memory information for this device
234
+ delete this.deviceInfo[host];
235
+
236
+ // Reserve basic memory information for this device
237
+ this.deviceInfo[host] = {
238
+ connected : true,
239
+ connectionError : false,
240
+ initialized: false,
241
+ ip : host
242
+ };
226
243
  this.log.info(`ESPHome client ${host} connected`);
227
244
  // Clear possible present warn messages for device from previous connection
228
245
  delete warnMessages[host];
@@ -231,13 +248,39 @@ class Esphome extends utils.Adapter {
231
248
  }
232
249
  });
233
250
 
234
- client[host].on('disconnected', () => {
251
+ client[host].on('disconnected', () => {
235
252
  try {
236
253
  if (this.deviceInfo[host].deviceName != null) {
237
254
  this.setState(`${this.deviceInfo[host].deviceName}.info._online`, {val: false, ack: true});
238
- this.log.warn(`ESPHome client ${this.deviceInfo[host].deviceInfo.name} disconnected`);
255
+
256
+ // Check all created states in memory if their are related to this device
257
+ for (const state in this.createdStatesDetails) {
258
+ // Remove states from cache
259
+ if (state.split('.')[0] === this.deviceInfo[host].deviceName) {
260
+ delete this.createdStatesDetails[state];
261
+ }
262
+ }
263
+
264
+ // Cache relevant data before clearing memory space of device
265
+ const cacheDeviceInformation = {
266
+ deviceName: this.deviceInfo[host].deviceName,
267
+ deviceInfoName: this.deviceInfo[host].deviceInfoName,
268
+ };
269
+
270
+ // Clear any existing memory information for this device
271
+ delete this.deviceInfo[host];
272
+
273
+ // Reserve basic memory information for this device
274
+ this.deviceInfo[host] = {
275
+ connected : false,
276
+ connectionError : true,
277
+ deviceName : cacheDeviceInformation.deviceName,
278
+ deviceInfoName : cacheDeviceInformation.deviceInfoName,
279
+ ip : host
280
+ };
281
+ this.log.warn(`ESPHome client ${this.deviceInfo[host].deviceInfoName} | ${this.deviceInfo[host].deviceName} | on ${host} disconnected`);
239
282
  } else {
240
- this.log.warn(`ESPHome client ${host} disconnected`);
283
+ this.log.warn(`ESPHome client ${host} disconnected`);
241
284
  }
242
285
  } catch (e) {
243
286
  this.log.debug(`ESPHome disconnect error : ${e}`);
@@ -245,20 +288,23 @@ class Esphome extends utils.Adapter {
245
288
  });
246
289
 
247
290
  client[host].on('initialized', () => {
248
- this.log.info(`ESPHome client ${this.deviceInfo[host].deviceInfo.name} on ip ${host} initialized`);
249
- });
250
-
251
- client[host].on('logs', (/** @type {object} */ messageObj) => {
252
- this.log.debug(`ESPHome client log : ${JSON.stringify(messageObj)}`);
291
+ this.log.info(`ESPHome client ${this.deviceInfo[host].deviceInfoName} on ip ${host} initialized`);
292
+ this.deviceInfo[host].initialized = true;
293
+
294
+ // Start timer to cleanup unneeded objects
295
+ if (resetTimers[host]) resetTimers[host] = clearTimeout(resetTimers[host]);
296
+ resetTimers[host] = setTimeout(async () => {
297
+ await this.objectCleanup(host);
298
+ }, (10000));
253
299
  });
254
300
 
255
301
  // Log message listener
256
302
  client[host].connection.on('message', (/** @type {object} */ message) => {
257
- this.log.debug(`${host} client log ${message}`);
303
+ this.log.debug(`[ESPHome Device Message] ${host} client log ${message}`);
258
304
  });
259
305
 
260
306
  client[host].connection.on('data', (/** @type {object} */ data) => {
261
- this.log.debug(`${host} client data ${data}`);
307
+ this.log.debug(`[ESPHome Device Data] ${host} client data ${data}`);
262
308
  });
263
309
 
264
310
  // Handle device information when connected or information updated
@@ -270,15 +316,21 @@ class Esphome extends utils.Adapter {
270
316
  // Store device information into memory
271
317
  const deviceName = this.replaceAll(deviceInfo.macAddress, `:`, ``);
272
318
  this.deviceInfo[host] = {
319
+ adapterObjects : {
320
+ channels : []
321
+ },
273
322
  ip: host,
323
+ connectError : false,
324
+ connected : true,
274
325
  mac: deviceInfo.macAddress,
275
326
  deviceInfo: deviceInfo,
276
327
  deviceName: deviceName,
277
328
  deviceInfoName: deviceInfo.name,
278
- passWord: pass,
329
+ passWord: apiPass,
279
330
  };
280
331
 
281
- // Store MAC & IP relation
332
+ // Store MAC & IP relation, delete possible existing entry before
333
+ delete this.deviceStateRelation[deviceName];
282
334
  this.deviceStateRelation[deviceName] = {'ip': host};
283
335
 
284
336
  this.log.debug(`DeviceInfo ${this.deviceInfo[host].deviceInfo.name}: ${JSON.stringify(this.deviceInfo)}`);
@@ -297,7 +349,7 @@ class Esphome extends utils.Adapter {
297
349
  name: this.deviceInfo[host].deviceInfoName,
298
350
  mac: deviceInfo.macAddress,
299
351
  deviceName: deviceName,
300
- passWord: this.encrypt(pass),
352
+ passWord: this.encrypt(apiPass),
301
353
  },
302
354
  });
303
355
 
@@ -351,6 +403,11 @@ class Esphome extends utils.Adapter {
351
403
  native: {},
352
404
  });
353
405
 
406
+ // Cache created channel in device memory
407
+ if (!this.deviceInfo[host].adapterObjects.channels.includes(`${this.namespace}.${this.deviceInfo[host].deviceName}.${entity.type}`)) {
408
+ this.deviceInfo[host].adapterObjects.channels.push(`${this.namespace}.${this.deviceInfo[host].deviceName}.${entity.type}`);
409
+ }
410
+
354
411
  // Create state specific channel by id
355
412
  await this.extendObjectAsync(`${this.deviceInfo[host].deviceName}.${entity.type}.${entity.id}`, {
356
413
  type: 'channel',
@@ -360,6 +417,11 @@ class Esphome extends utils.Adapter {
360
417
  native: {},
361
418
  });
362
419
 
420
+ // Cache created channel in device memory
421
+ if (!this.deviceInfo[host].adapterObjects.channels.includes(`${this.namespace}.${this.deviceInfo[host].deviceName}.${entity.type}.${entity.id}`)) {
422
+ this.deviceInfo[host].adapterObjects.channels.push(`${this.namespace}.${this.deviceInfo[host].deviceName}.${entity.type}.${entity.id}`);
423
+ }
424
+
363
425
  //Check if config channel should be created
364
426
  if (!createConfigStates) {
365
427
  // Delete folder structure if already present
@@ -380,6 +442,12 @@ class Esphome extends utils.Adapter {
380
442
  },
381
443
  native: {},
382
444
  });
445
+
446
+ // Cache created channel in device memory
447
+ if (!this.deviceInfo[host].adapterObjects.channels.includes(`${this.namespace}.${this.deviceInfo[host].deviceName}.${entity.type}.${entity.id}.config`)) {
448
+ this.deviceInfo[host].adapterObjects.channels.push(`${this.namespace}.${this.deviceInfo[host].deviceName}.${entity.type}.${entity.id}.config`);
449
+ }
450
+
383
451
  // Handle Entity JSON structure and write related config channel data
384
452
  await this.TraverseJson(entity.config, `${this.deviceInfo[host].deviceName}.${entity.type}.${entity.id}.config`);
385
453
  }
@@ -390,7 +458,7 @@ class Esphome extends utils.Adapter {
390
458
  await client[host].connection.subscribeStatesService();
391
459
  this.log.debug(`[DeviceInfoData] ${this.deviceInfo[host].deviceInfo.name} ${JSON.stringify(this.deviceInfo[host])}`);
392
460
 
393
- // Listen to state changes an write values to states (create state if not yet exists)
461
+ // Listen to state changes and write values to states (create state if not yet exists)
394
462
  entity.on(`state`, async (/** @type {object} */ state) => {
395
463
  this.log.debug(`StateData: ${JSON.stringify(state)}`);
396
464
  try {
@@ -475,7 +543,7 @@ class Esphome extends utils.Adapter {
475
543
 
476
544
 
477
545
  } catch (e) {
478
- this.log.error(`Connection issue for ${entity.name} ${e}`);
546
+ this.log.error(`Connection issue for ${entity.name} ${e} | ${e.stack}`);
479
547
  }
480
548
 
481
549
  });
@@ -483,23 +551,46 @@ class Esphome extends utils.Adapter {
483
551
  // Connection data handler
484
552
  client[host].on('error', (error) => {
485
553
  try {
554
+
555
+ // Reserve memory space for connection error if not already exist
556
+ if (!this.deviceInfo[host]){
557
+ this.deviceInfo[host] = {
558
+ ip : host,
559
+ connectError : false,
560
+ connected : false
561
+ };
562
+ }
563
+
486
564
  let optimisedError = error.message;
487
565
  // Optimise error messages
488
- if (error.message.includes('EHOSTUNREACH')) {
489
- optimisedError = `Client ${this.deviceInfo[host].ip} not reachable !`;
490
- if (!warnMessages[host].connectError) {
566
+ if (error.code === 'ETIMEDOUT') {
567
+ optimisedError = `Client ${host} not reachable !`;
568
+ if (!this.deviceInfo[host].connectError) {
491
569
  this.log.error(optimisedError);
492
- warnMessages[host].connectError = true;
570
+ this.deviceInfo[host].connectError = true;
493
571
  }
494
572
  } else if (error.message.includes('Invalid password')) {
495
573
  optimisedError = `Client ${host} incorrect password !`;
496
- this.log.error(optimisedError);
574
+ if (!this.deviceInfo[host].connectError) {
575
+ this.log.error(optimisedError);
576
+ this.deviceInfo[host].connectError = true;
577
+ }
497
578
  } else if (error.message.includes('ECONNRESET')) {
498
- optimisedError = `Client ${this.deviceInfo[host].ip} Connection Lost, will reconnect automatically when device is available!`;
499
- this.log.warn(optimisedError);
579
+ optimisedError = `Client ${host} Connection Lost, will reconnect automatically when device is available!`;
580
+ if (!this.deviceInfo[host].connectError) {
581
+ this.log.warn(optimisedError);
582
+ this.deviceInfo[host].connectError = true;
583
+ }
500
584
  } else if (error.message.includes('timeout')) {
501
- optimisedError = `Client ${host} Timeout, connection Lost, will reconnect automatically when device is available!`;
585
+ optimisedError = `Client ${host} Timeout, will reconnect automatically when device is available!`;
586
+ if (!this.deviceInfo[host].connectError) {
587
+ this.log.warn(optimisedError);
588
+ this.deviceInfo[host].connectError = true;
589
+ }
590
+ } else if (error.message.includes('ECONNREFUSED')) {
591
+ optimisedError = `Client ${host} not yet ready to connect, will try again!`;
502
592
  this.log.warn(optimisedError);
593
+
503
594
  } else if (error.message.includes('write after end')) {
504
595
  // Ignore error
505
596
  } else {
@@ -527,10 +618,6 @@ class Esphome extends utils.Adapter {
527
618
  // connect to socket
528
619
  try {
529
620
  this.log.debug(`trying to connect to ${host}`);
530
- // Reserve memory for warn messages
531
- warnMessages[host] = {
532
- connectError: false
533
- };
534
621
  client[host].connect();
535
622
  } catch (e) {
536
623
  this.log.error(`Client ${host} connect error ${e}`);
@@ -636,6 +723,8 @@ class Esphome extends utils.Adapter {
636
723
  // Check if state contains value
637
724
  if (transitionLength) {
638
725
  this.deviceInfo[host][entity.id].states.transitionLength = transitionLength.val;
726
+ // Run create state routine to ensure state is cached in memory
727
+ await this.stateSetCreate(`${this.deviceInfo[host].deviceName}.${entity.type}.${entity.id}.transitionLength`, `${stateName} of ${entity.config.name}`, transitionLength.val, `s`, writable);
639
728
  } else { // Else just create it
640
729
  await this.stateSetCreate(`${this.deviceInfo[host].deviceName}.${entity.type}.${entity.id}.transitionLength`, `${stateName} of ${entity.config.name}`, 0, `s`, writable);
641
730
  this.deviceInfo[host][entity.id].states.transitionLength = 0;
@@ -912,6 +1001,8 @@ class Esphome extends utils.Adapter {
912
1001
  onUnload(callback) {
913
1002
  try {
914
1003
  this.log.debug(JSON.stringify(this.deviceInfo));
1004
+
1005
+ // Set all online states to false
915
1006
  for (const device in this.deviceInfo) {
916
1007
 
917
1008
  // Ensure all known online states are set to false
@@ -926,9 +1017,12 @@ class Esphome extends utils.Adapter {
926
1017
  this.log.debug(`[onUnload] ${JSON.stringify(e)}`);
927
1018
  }
928
1019
  }
929
- if (reconnectTimer) {
930
- reconnectTimer = clearTimeout();
1020
+
1021
+ // Ensure all possible running timers are cleared
1022
+ for (const timer in resetTimers) {
1023
+ if (resetTimers[timer]) resetTimers[timer] = clearTimeout(resetTimers[timer]);
931
1024
  }
1025
+
932
1026
  try {
933
1027
  if (dashboardProcess) {
934
1028
  dashboardProcess.kill('SIGTERM', {
@@ -992,8 +1086,8 @@ class Esphome extends utils.Adapter {
992
1086
  } else {
993
1087
  this.log.info(`Valid IP address received`);
994
1088
  this.messageResponse[obj.message['device-ip']] = obj;
995
- const pass = this.decrypt(obj.message['device-pass']);
996
- await this.connectDevices(obj.message['device-ip'], pass);
1089
+ // const pass = this.decrypt(obj.message['device-pass']);
1090
+ await this.connectDevices(obj.message['device-ip']);
997
1091
  }
998
1092
  break;
999
1093
  }
@@ -1043,6 +1137,17 @@ class Esphome extends utils.Adapter {
1043
1137
  try {
1044
1138
  if (state && state.ack === false) {
1045
1139
  const device = id.split('.');
1140
+
1141
+ try {
1142
+ // Verify if trigger is related to device-cleanup
1143
+ if (id.split('.')[3] === 'deviceCleanup'){
1144
+ await this.offlineDeviceCleanup();
1145
+ return;
1146
+ }
1147
+ } catch (e) {
1148
+ // Skip action
1149
+ }
1150
+
1046
1151
  const deviceIP = this.deviceStateRelation[device[2]].ip;
1047
1152
 
1048
1153
  // Handle Switch State
@@ -1126,18 +1231,18 @@ class Esphome extends utils.Adapter {
1126
1231
  state: this.deviceInfo[deviceIP][device[4]].states.state,
1127
1232
  transitionLength: this.deviceInfo[deviceIP][device[4]].states.transitionLength
1128
1233
  };
1129
- if (this.deviceInfo[deviceIP][device[4]].config.supportsBrightness === true) {
1234
+ if (this.deviceInfo[deviceIP][device[4]].config.legacySupportsBrightness === true) {
1130
1235
  data.brightness = this.deviceInfo[deviceIP][device[4]].states.brightness;
1131
1236
  }
1132
- if (this.deviceInfo[deviceIP][device[4]].config.supportsRgb === true) {
1237
+ if (this.deviceInfo[deviceIP][device[4]].config.legacySupportsRgb === true) {
1133
1238
  data.red = this.deviceInfo[deviceIP][device[4]].states.red;
1134
1239
  data.green = this.deviceInfo[deviceIP][device[4]].states.green;
1135
1240
  data.blue = this.deviceInfo[deviceIP][device[4]].states.blue;
1136
1241
  }
1137
- if (this.deviceInfo[deviceIP][device[4]].config.supportsWhiteValue === true) {
1242
+ if (this.deviceInfo[deviceIP][device[4]].config.legacySupportsWhiteValue === true) {
1138
1243
  data.white = this.deviceInfo[deviceIP][device[4]].states.white;
1139
1244
  }
1140
- if (this.deviceInfo[deviceIP][device[4]].config.supportsColorTemperature === true) {
1245
+ if (this.deviceInfo[deviceIP][device[4]].config.legacySupportsColorTemperature === true) {
1141
1246
  data.colorTemperature = this.deviceInfo[deviceIP][device[4]].states.colorTemperature;
1142
1247
  }
1143
1248
  const effect = this.deviceInfo[deviceIP][device[4]].states.effect;
@@ -1198,6 +1303,76 @@ class Esphome extends utils.Adapter {
1198
1303
  }
1199
1304
  }
1200
1305
 
1306
+ async objectCleanup(ip){
1307
+ try {
1308
+ this.log.debug(`[objectCleanup] Starting channel and state cleanup for ${this.deviceInfo[ip].deviceName} | ${ip} | ${this.deviceInfo[ip].ip}`);
1309
+
1310
+ // Cancel cleanup operation in case device is not connected anymore
1311
+ if (this.deviceInfo[ip].connectionError || !this.deviceInfo[ip].connected) return;
1312
+
1313
+ // Set parameters for object view to only include objects within adapter namespace
1314
+ const params = {
1315
+ startkey : `${this.namespace}.${this.deviceInfo[ip].deviceName}.`,
1316
+ endkey : `${this.namespace}.\u9999`,
1317
+ };
1318
+
1319
+ // Get all current channels
1320
+ const _channels = await this.getObjectViewAsync('system', 'channel', params);
1321
+ // List all found channels & compare with memory, delete unneeded channels
1322
+ for (const currDevice in _channels.rows) {
1323
+ // @ts-ignore
1324
+ if (!this.deviceInfo[ip].adapterObjects.channels.includes(_channels.rows[currDevice].id)
1325
+ && _channels.rows[currDevice].id.split('.')[2] === this.deviceInfo[ip].deviceName){
1326
+ this.log.debug(`[objectCleanup] Unknown Channel found, delete ${_channels.rows[currDevice].id}`);
1327
+ await this.delObjectAsync(_channels.rows[currDevice].id, {recursive: true});
1328
+ }
1329
+ }
1330
+
1331
+ // Get all current states in adapter tree
1332
+ const _states = await this.getObjectViewAsync('system', 'state', params);
1333
+ // List all found states & compare with memory, delete unneeded states
1334
+ for (const currDevice in _states.rows) {
1335
+ if (!this.createdStatesDetails[_states.rows[currDevice].id.replace(`esphome.0.`, ``)]
1336
+ && _states.rows[currDevice].id.split('.')[2] === this.deviceInfo[ip].deviceName){
1337
+ this.log.debug(`[objectCleanup] Unknown State found, delete ${_states.rows[currDevice].id}`);
1338
+ // await this.delObjectAsync(_states.rows[currDevice].id);
1339
+ }
1340
+ }
1341
+
1342
+ } catch (e) {
1343
+ this.log.error(`[objectCleanup] Fatal error ${e} | ${e.stack}`);
1344
+ }
1345
+ }
1346
+
1347
+ async offlineDeviceCleanup () {
1348
+ this.log.info(`Offline Device cleanup started`);
1349
+
1350
+ try {
1351
+
1352
+ // Get an overview of all current devices known by adapter
1353
+ const knownDevices = await this.getDevicesAsync();
1354
+ console.log(`KnownDevices: ${knownDevices}`);
1355
+
1356
+ // Loop to all devices, check if online state = TRUE otherwise delete device
1357
+ for (const device in knownDevices){
1358
+
1359
+ // Get online value
1360
+ const online = await this.getStateAsync(`${knownDevices[device]._id}.info._online`);
1361
+ if (!online || !online.val){
1362
+ this.log.info(`Offline device ${knownDevices[device]._id.split('.')[2]} expected on ip ${knownDevices[device].native.ip} removed`);
1363
+ await this.delObjectAsync(knownDevices[device]._id, {recursive: true});
1364
+ }
1365
+
1366
+ }
1367
+
1368
+ if (!knownDevices) return; // exit function if no known device are detected
1369
+ if (knownDevices.length > 0) this.log.info(`Try to contact ${knownDevices.length} known devices`);
1370
+ } catch (e) {
1371
+ this.log.error(`[offlineDeviceCleanup] Fatal error occurred, cannot cleanup offline devices ${e} | ${e.stack}`);
1372
+
1373
+ }
1374
+ }
1375
+
1201
1376
  }
1202
1377
 
1203
1378
  if (require.main !== module) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.esphome",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Control your ESP8266/ESP32 with simple yet powerful configuration files created and managed by ESPHome",
5
5
  "author": {
6
6
  "name": "DutchmanNL",
@@ -28,6 +28,8 @@
28
28
  "@alcalzone/release-script-plugin-iobroker": "^3.6.0",
29
29
  "@alcalzone/release-script-plugin-license": "^3.5.9",
30
30
  "@alcalzone/release-script-plugin-manual-review": "^3.5.9",
31
+ "@iobroker/adapter-dev": "^1.2.0",
32
+ "@iobroker/dev-server": "^0.6.0",
31
33
  "@iobroker/testing": "^4.1.0",
32
34
  "@types/chai": "^4.3.6",
33
35
  "@types/chai-as-promised": "^7.1.6",
@@ -52,6 +54,7 @@
52
54
  "main": "main.js",
53
55
  "scripts": {
54
56
  "release": "release-script",
57
+ "dev-server": "dev-server",
55
58
  "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"",
56
59
  "test:package": "mocha test/package --exit",
57
60
  "test:unit": "mocha test/unit --exit",