iobroker.zeptrion 1.0.9 → 1.0.11
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 -7
- package/i18n/de.json +16 -0
- package/i18n/en.json +16 -0
- package/i18n/es.json +16 -0
- package/i18n/fr.json +16 -0
- package/i18n/it.json +16 -0
- package/i18n/nl.json +16 -0
- package/i18n/pl.json +16 -0
- package/i18n/pt.json +16 -0
- package/i18n/ru.json +16 -0
- package/i18n/uk.json +16 -0
- package/i18n/zh-cn.json +16 -0
- package/io-package.json +27 -27
- package/main.js +56 -47
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -176,6 +176,14 @@ npm run test:integration # starts a real js-controller (takes longer)
|
|
|
176
176
|
|
|
177
177
|
## Changelog
|
|
178
178
|
|
|
179
|
+
### 1.0.11 (2026-08-10)
|
|
180
|
+
|
|
181
|
+
- Fix: v1.0.10 translated all onMessage() UI text to plain English only. Implemented full multi-language support instead, using the official @iobroker/adapter-core I18n module (reads system.config.common.language automatically, falls back to English for unsupported languages). Added a new i18n/ directory with all 11 required languages. ioBroker log entries remain English-only; only the admin-dialog result text is now localized. Also fixed a packaging bug: the new i18n/ folder was missing from package.json's "files" allowlist, which would have excluded it from the published npm package - caught via an actual npm pack + tarball-extraction test before pushing. Verified round-trip in German, English, and French.
|
|
182
|
+
|
|
183
|
+
### 1.0.10 (2026-08-10)
|
|
184
|
+
|
|
185
|
+
- Fix all remaining findings from the follow-up review: translated 18 German error messages in validateDeviceRow(), the 5 error-code-to-message translations in handleDeviceError(), 9 German strings in thrown Error objects, and the bonjour-service install-hint rejection message. Per explicit maintainer direction, all UI-facing result text in onMessage (CSV import report, device test results, discovery summary) is now English as well, superseding the earlier decision to keep it German for the target audience. No migration needed - none of these fixes touch persisted object common properties.
|
|
186
|
+
|
|
179
187
|
### 1.0.9 (2026-08-08)
|
|
180
188
|
- Fix: the common.name i18n conversion from 1.0.8 only applied to newly created objects (setObjectNotExistsAsync/ensureState never update existing ones) - any installation upgrading from <=1.0.7 kept the old plain-German name strings forever. migrateObjectRoles() now also force-corrects these on every startup via a value-based lookup table generated from the same translations already used in the object-creation code, plus dedicated regex rules for the two dynamic cases (scene button names, tilt pulse duration). Also fixes two translation gaps that were missed in 1.0.8 (network info fields and the shutter position estimate/move descriptions) which the extraction script used to build the migration table happened to catch. Verified against a live object dump (409 objects, 4 devices): corrects exactly the 385 affected objects, 0 false positives on user-configured device/room names.
|
|
181
189
|
|
|
@@ -192,13 +200,7 @@ npm run test:integration # starts a real js-controller (takes longer)
|
|
|
192
200
|
- Admin UI: the three `validatorErrorText` messages are now inline i18n objects instead of plain English strings (ioBroker checker W5617).
|
|
193
201
|
- Dependencies: raised the declared minimums for `bonjour-service` (^1.2.1 -> ^1.4.3) and `eslint` (^10.6.0 -> ^10.7.0); both were already covered by the existing caret ranges, so no behaviour changes.
|
|
194
202
|
|
|
195
|
-
### 1.0.4
|
|
196
|
-
- Documentation only: added a Buy Me a Coffee link next to the PayPal donate badge. No functional changes.
|
|
197
|
-
|
|
198
|
-
### 1.0.3 (2026-07-17)
|
|
199
|
-
- Documentation only: fixed the PayPal donate link, which previously used the wrong URL format and did not work.
|
|
200
|
-
|
|
201
|
-
### 1.0.2 and older
|
|
203
|
+
### 1.0.4 and older
|
|
202
204
|
|
|
203
205
|
Older changelog entries can be found in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
204
206
|
|
package/i18n/de.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "CSV-Feld ist leer. Format: host;name;kanäle;art;laufzeit_s;kipp_ms;smartfront;poll_s;laufzeit_kanal_s (nur host ist Pflicht).",
|
|
3
|
+
"hostAlreadyConfigured": "Host bereits konfiguriert",
|
|
4
|
+
"csvRowError": "❌ Zeile %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Zeile %s: %s (%s) als \"%s\" übernommen",
|
|
6
|
+
"csvImportSummary": "%s von %s Zeile(n) importiert.%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " Adapter startet neu; Dialog schliessen und neu öffnen.",
|
|
8
|
+
"csvImportFailed": "CSV-Import fehlgeschlagen: %s",
|
|
9
|
+
"noDevicesWithHost": "Keine Geräte mit Host in der Tabelle.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): ungültige IP-Adresse",
|
|
11
|
+
"testChannelsSuffix": ", %s Kanäle",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, SW %s, SN %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): antwortet, aber KEIN zeptrion-Gerät (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): nicht erreichbar (%s)",
|
|
15
|
+
"discoveryFinished": "Suche abgeschlossen: %s Gerät(e) im Netz gefunden, %s neu (deaktiviert) übernommen. Instanz-Konfiguration schliessen und neu öffnen, um sie in der Tabelle zu sehen und zu aktivieren."
|
|
16
|
+
}
|
package/i18n/en.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "CSV field is empty. Format: host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (only host is required).",
|
|
3
|
+
"hostAlreadyConfigured": "Host already configured",
|
|
4
|
+
"csvRowError": "❌ Row %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Row %s: %s (%s) imported as \"%s\"",
|
|
6
|
+
"csvImportSummary": "%s of %s row(s) imported.%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " Adapter restarts; close and reopen the dialog.",
|
|
8
|
+
"csvImportFailed": "CSV import failed: %s",
|
|
9
|
+
"noDevicesWithHost": "No devices with a host in the table.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): invalid IP address",
|
|
11
|
+
"testChannelsSuffix": ", %s channels",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, SW %s, SN %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): responds, but is NOT a zeptrion device (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): unreachable (%s)",
|
|
15
|
+
"discoveryFinished": "Discovery finished: %s device(s) found on the network, %s newly added (disabled). Close and reopen the instance configuration to see them in the table and enable them."
|
|
16
|
+
}
|
package/i18n/es.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "El campo CSV está vacío. Formato: host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (solo host es obligatorio).",
|
|
3
|
+
"hostAlreadyConfigured": "Host ya configurado",
|
|
4
|
+
"csvRowError": "❌ Fila %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Fila %s: %s (%s) importado como \"%s\"",
|
|
6
|
+
"csvImportSummary": "%s de %s fila(s) importadas.%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " El adaptador se reinicia; cierre y vuelva a abrir el diálogo.",
|
|
8
|
+
"csvImportFailed": "Error al importar el CSV: %s",
|
|
9
|
+
"noDevicesWithHost": "No hay dispositivos con host en la tabla.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): dirección IP no válida",
|
|
11
|
+
"testChannelsSuffix": ", %s canales",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, SW %s, SN %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): responde, pero NO es un dispositivo zeptrion (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): inaccesible (%s)",
|
|
15
|
+
"discoveryFinished": "Búsqueda finalizada: %s dispositivo(s) encontrado(s) en la red, %s añadido(s) recientemente (desactivado). Cierre y vuelva a abrir la configuración de la instancia para verlos en la tabla y activarlos."
|
|
16
|
+
}
|
package/i18n/fr.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "Le champ CSV est vide. Format : host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (seul host est requis).",
|
|
3
|
+
"hostAlreadyConfigured": "Hôte déjà configuré",
|
|
4
|
+
"csvRowError": "❌ Ligne %s (%s) : %s",
|
|
5
|
+
"csvRowImported": "✅ Ligne %s : %s (%s) importé comme \"%s\"",
|
|
6
|
+
"csvImportSummary": "%s ligne(s) sur %s importée(s).%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " L'adaptateur redémarre ; fermez et rouvrez la boîte de dialogue.",
|
|
8
|
+
"csvImportFailed": "Échec de l'importation CSV : %s",
|
|
9
|
+
"noDevicesWithHost": "Aucun appareil avec un hôte dans le tableau.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s) : adresse IP invalide",
|
|
11
|
+
"testChannelsSuffix": ", %s canaux",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s) : zeptrion %s%s, SW %s, SN %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s) : répond, mais n'est PAS un appareil zeptrion (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s) : injoignable (%s)",
|
|
15
|
+
"discoveryFinished": "Recherche terminée : %s appareil(s) trouvé(s) sur le réseau, %s nouvellement ajouté(s) (désactivé(s)). Fermez et rouvrez la configuration de l'instance pour les voir dans le tableau et les activer."
|
|
16
|
+
}
|
package/i18n/it.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "Il campo CSV è vuoto. Formato: host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (solo host è obbligatorio).",
|
|
3
|
+
"hostAlreadyConfigured": "Host già configurato",
|
|
4
|
+
"csvRowError": "❌ Riga %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Riga %s: %s (%s) importato come \"%s\"",
|
|
6
|
+
"csvImportSummary": "%s di %s riga/e importate.%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " L'adattatore si riavvia; chiudere e riaprire la finestra di dialogo.",
|
|
8
|
+
"csvImportFailed": "Importazione CSV non riuscita: %s",
|
|
9
|
+
"noDevicesWithHost": "Nessun dispositivo con host nella tabella.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): indirizzo IP non valido",
|
|
11
|
+
"testChannelsSuffix": ", %s canali",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, SW %s, SN %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): risponde, ma NON è un dispositivo zeptrion (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): non raggiungibile (%s)",
|
|
15
|
+
"discoveryFinished": "Ricerca completata: %s dispositivo/i trovato/i sulla rete, %s aggiunto/i di recente (disattivato/i). Chiudere e riaprire la configurazione dell'istanza per vederli nella tabella e attivarli."
|
|
16
|
+
}
|
package/i18n/nl.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "CSV-veld is leeg. Formaat: host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (alleen host is verplicht).",
|
|
3
|
+
"hostAlreadyConfigured": "Host al geconfigureerd",
|
|
4
|
+
"csvRowError": "❌ Rij %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Rij %s: %s (%s) geïmporteerd als \"%s\"",
|
|
6
|
+
"csvImportSummary": "%s van %s rij(en) geïmporteerd.%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " Adapter herstart; sluit en heropen het dialoogvenster.",
|
|
8
|
+
"csvImportFailed": "CSV-import mislukt: %s",
|
|
9
|
+
"noDevicesWithHost": "Geen apparaten met een host in de tabel.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): ongeldig IP-adres",
|
|
11
|
+
"testChannelsSuffix": ", %s kanalen",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, SW %s, SN %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): reageert, maar is GEEN zeptrion-apparaat (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): onbereikbaar (%s)",
|
|
15
|
+
"discoveryFinished": "Zoeken voltooid: %s apparaat/apparaten gevonden op het netwerk, %s nieuw toegevoegd (uitgeschakeld). Sluit en heropen de instantieconfiguratie om ze in de tabel te zien en in te schakelen."
|
|
16
|
+
}
|
package/i18n/pl.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "Pole CSV jest puste. Format: host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (tylko host jest wymagany).",
|
|
3
|
+
"hostAlreadyConfigured": "Host już skonfigurowany",
|
|
4
|
+
"csvRowError": "❌ Wiersz %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Wiersz %s: %s (%s) zaimportowano jako \"%s\"",
|
|
6
|
+
"csvImportSummary": "Zaimportowano %s z %s wiersz(y).%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " Adapter uruchamia się ponownie; zamknij i otwórz ponownie okno dialogowe.",
|
|
8
|
+
"csvImportFailed": "Import CSV nie powiódł się: %s",
|
|
9
|
+
"noDevicesWithHost": "Brak urządzeń z hostem w tabeli.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): nieprawidłowy adres IP",
|
|
11
|
+
"testChannelsSuffix": ", %s kanałów",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, SW %s, SN %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): odpowiada, ale NIE jest urządzeniem zeptrion (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): niedostępny (%s)",
|
|
15
|
+
"discoveryFinished": "Wyszukiwanie zakończone: znaleziono %s urządzenie(a) w sieci, dodano %s nowych (wyłączonych). Zamknij i otwórz ponownie konfigurację instancji, aby zobaczyć je w tabeli i je włączyć."
|
|
16
|
+
}
|
package/i18n/pt.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "O campo CSV está vazio. Formato: host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (apenas host é obrigatório).",
|
|
3
|
+
"hostAlreadyConfigured": "Host já configurado",
|
|
4
|
+
"csvRowError": "❌ Linha %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Linha %s: %s (%s) importado como \"%s\"",
|
|
6
|
+
"csvImportSummary": "%s de %s linha(s) importada(s).%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " O adaptador reinicia; feche e reabra o diálogo.",
|
|
8
|
+
"csvImportFailed": "Falha na importação do CSV: %s",
|
|
9
|
+
"noDevicesWithHost": "Nenhum dispositivo com host na tabela.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): endereço IP inválido",
|
|
11
|
+
"testChannelsSuffix": ", %s canais",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, SW %s, SN %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): responde, mas NÃO é um dispositivo zeptrion (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): inacessível (%s)",
|
|
15
|
+
"discoveryFinished": "Descoberta concluída: %s dispositivo(s) encontrado(s) na rede, %s adicionado(s) recentemente (desativado). Feche e reabra a configuração da instância para os ver na tabela e ativá-los."
|
|
16
|
+
}
|
package/i18n/ru.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "Поле CSV пустое. Формат: host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (обязателен только host).",
|
|
3
|
+
"hostAlreadyConfigured": "Хост уже настроен",
|
|
4
|
+
"csvRowError": "❌ Строка %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Строка %s: %s (%s) добавлена как \"%s\"",
|
|
6
|
+
"csvImportSummary": "%s из %s строк(и) импортировано.%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " Адаптер перезапускается; закройте и снова откройте диалог.",
|
|
8
|
+
"csvImportFailed": "Импорт CSV не удался: %s",
|
|
9
|
+
"noDevicesWithHost": "В таблице нет устройств с указанным хостом.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): недопустимый IP-адрес",
|
|
11
|
+
"testChannelsSuffix": ", %s каналов",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, ПО %s, серийный № %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): отвечает, но НЕ является устройством zeptrion (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): недоступен (%s)",
|
|
15
|
+
"discoveryFinished": "Поиск завершён: найдено %s устройств(о) в сети, %s добавлено новых (отключены). Закройте и снова откройте конфигурацию экземпляра, чтобы увидеть их в таблице и включить."
|
|
16
|
+
}
|
package/i18n/uk.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "Поле CSV порожнє. Формат: host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s (обов'язковий лише host).",
|
|
3
|
+
"hostAlreadyConfigured": "Хост вже налаштовано",
|
|
4
|
+
"csvRowError": "❌ Рядок %s (%s): %s",
|
|
5
|
+
"csvRowImported": "✅ Рядок %s: %s (%s) додано як \"%s\"",
|
|
6
|
+
"csvImportSummary": "%s з %s рядків(ка) імпортовано.%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " Адаптер перезапускається; закрийте і знову відкрийте діалог.",
|
|
8
|
+
"csvImportFailed": "Помилка імпорту CSV: %s",
|
|
9
|
+
"noDevicesWithHost": "У таблиці немає пристроїв із хостом.",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s): недійсна IP-адреса",
|
|
11
|
+
"testChannelsSuffix": ", %s каналів",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s): zeptrion %s%s, ПЗ %s, серійний № %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s): відповідає, але НЕ є пристроєм zeptrion (sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s): недоступний (%s)",
|
|
15
|
+
"discoveryFinished": "Пошук завершено: знайдено %s пристрій(їв) у мережі, %s додано нових (вимкнено). Закрийте і знову відкрийте конфігурацію екземпляра, щоб побачити їх у таблиці та увімкнути."
|
|
16
|
+
}
|
package/i18n/zh-cn.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"csvFieldEmpty": "CSV 字段为空。格式:host;name;channels;type;runtime_s;tilt_ms;smartfront;poll_s;runtime_channel_s(仅 host 为必填)。",
|
|
3
|
+
"hostAlreadyConfigured": "主机已配置",
|
|
4
|
+
"csvRowError": "❌ 第 %s 行(%s):%s",
|
|
5
|
+
"csvRowImported": "✅ 第 %s 行:%s (%s) 已导入为 \"%s\"",
|
|
6
|
+
"csvImportSummary": "已导入 %s / %s 行。%s\n\n%s",
|
|
7
|
+
"csvImportRestartHint": " 适配器正在重启;请关闭并重新打开对话框。",
|
|
8
|
+
"csvImportFailed": "CSV 导入失败:%s",
|
|
9
|
+
"noDevicesWithHost": "表格中没有带主机的设备。",
|
|
10
|
+
"testInvalidIp": "❌ %s (%s):无效的 IP 地址",
|
|
11
|
+
"testChannelsSuffix": ",%s 个通道",
|
|
12
|
+
"testDeviceOk": "✅ %s (%s):zeptrion %s%s,软件版本 %s,序列号 %s",
|
|
13
|
+
"testNotZeptrion": "⚠️ %s (%s):有响应,但不是 zeptrion 设备(sys=\"%s\")",
|
|
14
|
+
"testUnreachable": "❌ %s (%s):无法访问 (%s)",
|
|
15
|
+
"discoveryFinished": "搜索完成:在网络中找到 %s 台设备,新增 %s 台(已禁用)。请关闭并重新打开实例配置,以在表格中查看并启用它们。"
|
|
16
|
+
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zeptrion",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.11",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.0.11": {
|
|
7
|
+
"en": "Fix: v1.0.10 translated all onMessage() UI text (CSV import report, device test results, discovery summary) to plain English only. Per user request, implemented full multi-language support for this text instead, using the official @iobroker/adapter-core I18n module (reads system.config.common.language automatically, falls back to English for unsupported languages) - the same officially sanctioned mechanism the maintainer referenced when suggesting 'implement i18n support'. Added a new i18n/ directory (separate from the existing admin/i18n/ used for jsonConfig labels) with all 11 required languages. ioBroker log entries remain English-only regardless of system language, per the separate log requirement; only the admin-dialog result text is now localized. Also fixed a packaging bug found before this release: the new i18n/ folder was missing from package.json's \"files\" allowlist, which would have excluded it from the published npm package and caused a runtime crash on a real install - caught and fixed via an actual npm pack + tarball-extraction test before pushing, not just local testing. Verified: all 9 message templates round-trip correctly in German, English, and French (representative of the other 8 languages); unknown/unsupported system languages correctly fall back to English.",
|
|
8
|
+
"de": "Fix: v1.0.10 hatte den gesamten onMessage()-UI-Text (CSV-Import-Bericht, Gerätetest-Ergebnisse, Discovery-Zusammenfassung) nur auf reines Englisch übersetzt. Auf Nutzerwunsch stattdessen echten Mehrsprachen-Support implementiert, über das offizielle @iobroker/adapter-core-I18n-Modul (liest system.config.common.language automatisch aus, Fallback auf Englisch bei nicht unterstützten Sprachen) - derselbe offiziell vorgesehene Mechanismus, auf den der Maintainer bei „implement i18n support\" verwiesen hat. Neuer i18n/-Ordner ergänzt (getrennt vom bestehenden admin/i18n/ für jsonConfig-Labels) mit allen 11 geforderten Sprachen. ioBroker-Log-Einträge bleiben unabhängig von der Systemsprache Englisch, gemäss der separaten Log-Anforderung; nur der Admin-Dialog-Ergebnistext ist jetzt lokalisiert. Zusätzlich einen Packaging-Fehler behoben, der vor diesem Release gefunden wurde: der neue i18n/-Ordner fehlte in der \"files\"-Positivliste von package.json, wodurch er beim veröffentlichten npm-Paket gefehlt und bei einer echten Installation zum Laufzeitabsturz geführt hätte - entdeckt und behoben über einen echten npm-pack- plus Tarball-Extraktions-Test vor dem Push, nicht nur lokales Testen. Verifiziert: alle 9 Nachrichtenvorlagen funktionieren korrekt auf Deutsch, Englisch und Französisch (stellvertretend für die anderen 8 Sprachen); unbekannte/nicht unterstützte Systemsprachen fallen korrekt auf Englisch zurück.",
|
|
9
|
+
"ru": "Исправление: реализована полноценная многоязычная поддержка UI-текста через официальный модуль I18n adapter-core вместо только английского. Также исправлена ошибка упаковки (папка i18n отсутствовала в списке files package.json).",
|
|
10
|
+
"pt": "Correção: implementado suporte multilíngue completo para o texto da UI através do módulo oficial I18n do adapter-core, em vez de apenas inglês. Também corrigido um erro de empacotamento (a pasta i18n estava ausente da lista files do package.json).",
|
|
11
|
+
"nl": "Fix: volledige meertalige ondersteuning voor UI-tekst geïmplementeerd via de officiële I18n-module van adapter-core, in plaats van alleen Engels. Ook een verpakkingsfout opgelost (de i18n-map ontbrak in de files-lijst van package.json).",
|
|
12
|
+
"fr": "Correction : implémentation d'un véritable support multilingue pour le texte de l'interface via le module I18n officiel d'adapter-core, au lieu de l'anglais uniquement. Correction également d'un bug d'empaquetage (le dossier i18n manquait dans la liste files de package.json).",
|
|
13
|
+
"it": "Correzione: implementato il pieno supporto multilingue per il testo dell'interfaccia tramite il modulo I18n ufficiale di adapter-core, invece del solo inglese. Corretto anche un bug di packaging (la cartella i18n mancava dall'elenco files di package.json).",
|
|
14
|
+
"es": "Corrección: implementado soporte multiidioma completo para el texto de la interfaz mediante el módulo I18n oficial de adapter-core, en lugar de solo inglés. También se corrigió un error de empaquetado (faltaba la carpeta i18n en la lista files de package.json).",
|
|
15
|
+
"pl": "Poprawka: zaimplementowano pełne wsparcie wielojęzyczne dla tekstu interfejsu za pomocą oficjalnego modułu I18n adapter-core, zamiast samego angielskiego. Naprawiono również błąd pakowania (folder i18n brakował na liście files w package.json).",
|
|
16
|
+
"uk": "Виправлення: реалізовано повноцінну багатомовну підтримку тексту інтерфейсу через офіційний модуль I18n adapter-core замість лише англійської. Також виправлено помилку пакування (папка i18n була відсутня у списку files package.json).",
|
|
17
|
+
"zh-cn": "修复:通过官方 adapter-core I18n 模块为界面文本实现了完整的多语言支持,而不仅仅是英文。同时修复了一个打包错误(i18n 文件夹未包含在 package.json 的 files 列表中)。"
|
|
18
|
+
},
|
|
19
|
+
"1.0.10": {
|
|
20
|
+
"en": "Fix all remaining findings from the follow-up review: translated 18 German error messages in validateDeviceRow() (row/duplicate validation, including 6 that were missed in an earlier pass), the 5 error-code-to-message translations in handleDeviceError() (also written to the info.lastError state), 9 German strings in thrown Error objects, and the bonjour-service install-hint rejection message. Per explicit maintainer direction, all UI-facing result text in onMessage (CSV import report lines, device test results, discovery summary) is now English as well, superseding the earlier design decision to keep it German for the target audience - the maintainer clarified that all user-facing text must be English or full i18n, with no exception for admin-dialog result strings. No migration needed: none of these fixes touch persisted object common properties, only log output, transient state values that refresh on the next occurrence, and one-off message-callback payloads.",
|
|
21
|
+
"de": "Behebt alle verbleibenden Befunde aus dem Folge-Review: 18 deutsche Fehlermeldungen in validateDeviceRow() übersetzt (Zeilen-/Duplikat-Validierung, davon 6 in einem früheren Durchgang übersehen), die 5 Fehlercode-zu-Meldung-Übersetzungen in handleDeviceError() (werden auch in den info.lastError-State geschrieben), 9 deutsche Strings in geworfenen Error-Objekten sowie die bonjour-service-Installationshinweis-Meldung. Auf explizite Anweisung des Maintainers ist jetzt auch aller UI-facing Ergebnistext in onMessage (CSV-Import-Bericht, Gerätetest-Ergebnisse, Discovery-Zusammenfassung) Englisch - das ersetzt die frühere Design-Entscheidung, diesen für die Zielgruppe Deutsch zu belassen; der Maintainer stellte klar, dass aller nutzerseitiger Text Englisch oder vollständig i18n sein muss, ohne Ausnahme für Admin-Dialog-Ergebnistexte. Keine Migration nötig: keiner dieser Fixes betrifft persistierte Objekt-common-Eigenschaften, nur Log-Ausgabe, transiente State-Werte (aktualisieren sich beim nächsten Ereignis von selbst) und einmalige Message-Callback-Payloads.",
|
|
22
|
+
"ru": "Исправлены все оставшиеся замечания из повторного обзора: переведены немецкие сообщения об ошибках и весь текст, отправляемый пользователю через onMessage, теперь на английском по явному указанию мейнтейнера. Миграция не требуется.",
|
|
23
|
+
"pt": "Corrigidos todos os pontos restantes da revisão de acompanhamento: mensagens de erro em alemão traduzidas e todo o texto enviado ao utilizador via onMessage agora em inglês, por indicação explícita do mantenedor. Não é necessária migração.",
|
|
24
|
+
"nl": "Alle resterende bevindingen uit de vervolgreview verholpen: Duitse foutmeldingen vertaald en alle tekst die via onMessage naar de gebruiker wordt gestuurd nu in het Engels, op expliciete aanwijzing van de maintainer. Geen migratie nodig.",
|
|
25
|
+
"fr": "Correction de tous les points restants du contrôle de suivi : messages d'erreur allemands traduits et tout le texte envoyé à l'utilisateur via onMessage désormais en anglais, sur instruction explicite du mainteneur. Aucune migration nécessaire.",
|
|
26
|
+
"it": "Corretti tutti i rilievi rimanenti della revisione di follow-up: messaggi di errore in tedesco tradotti e tutto il testo inviato all'utente tramite onMessage ora in inglese, su indicazione esplicita del maintainer. Nessuna migrazione necessaria.",
|
|
27
|
+
"es": "Se corrigieron todos los hallazgos restantes de la revisión de seguimiento: mensajes de error en alemán traducidos y todo el texto enviado al usuario a través de onMessage ahora en inglés, por indicación explícita del mantenedor. No se necesita migración.",
|
|
28
|
+
"pl": "Naprawiono wszystkie pozostałe uwagi z ponownego przeglądu: przetłumaczono niemieckie komunikaty błędów, a cały tekst wysyłany do użytkownika przez onMessage jest teraz po angielsku, zgodnie z wyraźnym wskazaniem opiekuna. Migracja nie jest potrzebna.",
|
|
29
|
+
"uk": "Виправлено всі залишкові зауваження повторного огляду: перекладено німецькі повідомлення про помилки, увесь текст, що надсилається користувачу через onMessage, тепер англійською за явною вказівкою мейнтейнера. Міграція не потрібна.",
|
|
30
|
+
"zh-cn": "修复了后续审查中的所有剩余问题:翻译了德语错误消息,根据维护者的明确指示,通过 onMessage 发送给用户的所有文本现在均为英文。无需迁移。"
|
|
31
|
+
},
|
|
6
32
|
"1.0.9": {
|
|
7
33
|
"en": "Fix: the common.name i18n conversion from 1.0.8 only applied to newly created objects (setObjectNotExistsAsync/ensureState never update existing ones) - any installation upgrading from <=1.0.7 kept the old plain-German name strings forever. migrateObjectRoles() now also force-corrects these on every startup via a value-based lookup table generated from the same translations already used in the object-creation code, plus dedicated regex rules for the two dynamic cases (scene button names, tilt pulse duration). Also fixes two translation gaps that were missed in 1.0.8 (network info fields and the shutter position estimate/move descriptions) which the extraction script used to build the migration table happened to catch. Verified against a live object dump (409 objects, 4 devices): corrects exactly the 385 affected objects, 0 false positives on user-configured device/room names.",
|
|
8
34
|
"de": "Fix: Die common.name-i18n-Umstellung aus 1.0.8 griff nur bei neu erstellten Objekten (setObjectNotExistsAsync/ensureState aktualisieren bestehende Objekte nie) - jedes Update von <=1.0.7 behielt die alten deutschen Plain-String-Namen für immer. migrateObjectRoles() korrigiert diese jetzt bei jedem Start ebenfalls, über eine wertbasierte Nachschlagetabelle, generiert aus denselben Übersetzungen, die bereits im Objekterstellungs-Code stehen, plus dedizierte Regex-Regeln für die zwei dynamischen Fälle (Szenen-Button-Namen, Lamellen-Impulsdauer). Behebt zudem zwei Übersetzungslücken, die in 1.0.8 übersehen wurden (Netzwerk-Info-Felder und die Storen-Positions-Schätzung/-Anfahrt-Beschreibungen), die das Extraktionsskript zum Aufbau der Migrationstabelle zufällig aufgedeckt hat. Verifiziert gegen einen echten Objekt-Dump (409 Objekte, 4 Geräte): korrigiert exakt die 385 betroffenen Objekte, 0 Fehltreffer bei nutzerkonfigurierten Geräte-/Raumnamen.",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Interfejs administratora: trzy komunikaty validatorErrorText są teraz obiektami i18n zamiast zwykłych angielskich ciągów; podniesiono deklarowane wersje minimalne bonjour-service i eslint (obie i tak były objęte istniejącymi zakresami caret). Brak zmian funkcjonalnych.",
|
|
68
94
|
"uk": "Інтерфейс адміністратора: три повідомлення validatorErrorText тепер є вбудованими об'єктами i18n замість простих англійських рядків; підвищено оголошені мінімальні версії bonjour-service та eslint (обидві й так покривалися наявними caret-діапазонами). Без функціональних змін.",
|
|
69
95
|
"zh-cn": "管理界面:三条 validatorErrorText 提示现在使用内联 i18n 对象,而非纯英文字符串;提高了 bonjour-service 和 eslint 的声明最低版本(两者原本就在现有 caret 范围内)。无功能性更改。"
|
|
70
|
-
},
|
|
71
|
-
"1.0.4": {
|
|
72
|
-
"en": "Documentation only: added a Buy Me a Coffee link next to the PayPal donate badge in README.md/README_de.md and the npm package (funding field). No functional changes.",
|
|
73
|
-
"de": "Nur Dokumentation: Buy-Me-a-Coffee-Link neben dem PayPal-Spenden-Badge in README.md/README_de.md sowie im npm-Paket (funding-Feld) ergänzt. Keine funktionalen Änderungen.",
|
|
74
|
-
"ru": "Только документация: добавлена ссылка Buy Me a Coffee рядом со значком пожертвования PayPal в README.md/README_de.md и в npm-пакете (поле funding). Без функциональных изменений.",
|
|
75
|
-
"pt": "Apenas documentação: adicionado um link Buy Me a Coffee junto ao badge de doação PayPal no README.md/README_de.md e no pacote npm (campo funding). Sem alterações funcionais.",
|
|
76
|
-
"nl": "Alleen documentatie: Buy Me a Coffee-link toegevoegd naast de PayPal-donatiebadge in README.md/README_de.md en het npm-pakket (funding-veld). Geen functionele wijzigingen.",
|
|
77
|
-
"fr": "Documentation uniquement : ajout d'un lien Buy Me a Coffee à côté du badge de don PayPal dans README.md/README_de.md et le paquet npm (champ funding). Aucun changement fonctionnel.",
|
|
78
|
-
"it": "Solo documentazione: aggiunto un link Buy Me a Coffee accanto al badge di donazione PayPal in README.md/README_de.md e nel pacchetto npm (campo funding). Nessuna modifica funzionale.",
|
|
79
|
-
"es": "Solo documentación: se añadió un enlace Buy Me a Coffee junto a la insignia de donación de PayPal en README.md/README_de.md y en el paquete npm (campo funding). Sin cambios funcionales.",
|
|
80
|
-
"pl": "Tylko dokumentacja: dodano link Buy Me a Coffee obok odznaki dotacji PayPal w README.md/README_de.md oraz w pakiecie npm (pole funding). Brak zmian funkcjonalnych.",
|
|
81
|
-
"uk": "Лише документація: додано посилання Buy Me a Coffee поруч зі значком пожертв PayPal у README.md/README_de.md та в пакеті npm (поле funding). Без функціональних змін.",
|
|
82
|
-
"zh-cn": "仅文档:在 README.md/README_de.md 及 npm 包(funding 字段)中,于 PayPal 捐赠徽章旁添加了 Buy Me a Coffee 链接。无功能变更。"
|
|
83
|
-
},
|
|
84
|
-
"1.0.3": {
|
|
85
|
-
"en": "Documentation only: fixed the PayPal donate link in README (en/de), which previously used the wrong URL format and did not work. No functional/object changes.",
|
|
86
|
-
"de": "Nur Dokumentation: den PayPal-Spenden-Link im README (en/de) korrigiert, der zuvor das falsche URL-Format hatte und nicht funktionierte. Keine funktionalen Änderungen/Objektänderungen.",
|
|
87
|
-
"ru": "Только документация: исправлена ссылка для пожертвований PayPal в README (en/de), которая ранее имела неверный формат URL и не работала. Без функциональных изменений.",
|
|
88
|
-
"pt": "Apenas documentação: corrigido o link de doação PayPal no README (en/de), que anteriormente tinha o formato de URL errado e não funcionava. Sem alterações funcionais.",
|
|
89
|
-
"nl": "Alleen documentatie: de PayPal-donatielink in README (en/de) gecorrigeerd, die voorheen het verkeerde URL-formaat had en niet werkte. Geen functionele wijzigingen.",
|
|
90
|
-
"fr": "Documentation uniquement : correction du lien de don PayPal dans le README (en/de), qui avait auparavant le mauvais format d'URL et ne fonctionnait pas. Aucun changement fonctionnel.",
|
|
91
|
-
"it": "Solo documentazione: corretto il link di donazione PayPal nel README (en/de), che in precedenza aveva il formato URL errato e non funzionava. Nessuna modifica funzionale.",
|
|
92
|
-
"es": "Solo documentación: se corrigió el enlace de donación de PayPal en el README (en/de), que anteriormente tenía el formato de URL incorrecto y no funcionaba. Sin cambios funcionales.",
|
|
93
|
-
"pl": "Tylko dokumentacja: naprawiono link do darowizn PayPal w README (en/de), który wcześniej miał zły format URL i nie działał. Brak zmian funkcjonalnych.",
|
|
94
|
-
"uk": "Лише документація: виправлено посилання для пожертв PayPal у README (en/de), яке раніше мало неправильний формат URL і не працювало. Без функціональних змін.",
|
|
95
|
-
"zh-cn": "仅文档:修正了 README(en/de)中的 PayPal 捐赠链接,此前该链接使用了错误的 URL 格式且无法正常使用。无功能变更。"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
package/main.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
const utils = require('@iobroker/adapter-core');
|
|
22
|
+
const { I18n } = require('@iobroker/adapter-core');
|
|
22
23
|
const axios = require('axios');
|
|
23
24
|
const { XMLParser } = require('fast-xml-parser');
|
|
24
25
|
|
|
@@ -353,6 +354,14 @@ class Zeptrion extends utils.Adapter {
|
|
|
353
354
|
}
|
|
354
355
|
|
|
355
356
|
async onReady() {
|
|
357
|
+
// Initializes the runtime message-translation system (i18n/*.json) - reads the ioBroker
|
|
358
|
+
// system language from system.config.common.language automatically. Used for onMessage()
|
|
359
|
+
// result text (CSV import report, device test, discovery summary) shown in the admin
|
|
360
|
+
// dialog, so German UI users get German results while everyone else gets English or their
|
|
361
|
+
// own configured language, per maintainer requirement (all user-facing text must be
|
|
362
|
+
// English or full i18n - not always German regardless of the system language).
|
|
363
|
+
await I18n.init(__dirname, this);
|
|
364
|
+
|
|
356
365
|
// One-time (cheap-and-idempotent-every-startup) fix for role/name mistakes present in
|
|
357
366
|
// objects created before this fix (see CHANGELOG). ensureState()/setObjectNotExistsAsync()
|
|
358
367
|
// never touches an object that already exists, so simply updating the adapter does not
|
|
@@ -409,9 +418,9 @@ class Zeptrion extends utils.Adapter {
|
|
|
409
418
|
for (const d of active) {
|
|
410
419
|
const errs = this.validateDeviceRow(d);
|
|
411
420
|
const sanId = this.sanitize(d.id);
|
|
412
|
-
if (seenIds.has(sanId)) errs.push(`ID "${d.id}" (
|
|
421
|
+
if (seenIds.has(sanId)) errs.push(`ID "${d.id}" (sanitized "${sanId}") is assigned twice`);
|
|
413
422
|
const hostKey = String(d.host).trim().toLowerCase();
|
|
414
|
-
if (seenHosts.has(hostKey)) errs.push(`Host "${d.host}"
|
|
423
|
+
if (seenHosts.has(hostKey)) errs.push(`Host "${d.host}" is configured twice`);
|
|
415
424
|
if (errs.length) {
|
|
416
425
|
this.log.error(`Device "${d.name || d.id || d.host}" skipped: ${errs.join('; ')}`);
|
|
417
426
|
continue;
|
|
@@ -487,50 +496,50 @@ class Zeptrion extends utils.Adapter {
|
|
|
487
496
|
const errs = [];
|
|
488
497
|
const host = String(d.host || '').trim();
|
|
489
498
|
if (!host) {
|
|
490
|
-
errs.push('Host
|
|
499
|
+
errs.push('Host missing');
|
|
491
500
|
} else if (!/^[a-zA-Z0-9.-]+$/.test(host)) {
|
|
492
|
-
errs.push(`Host "${host}"
|
|
501
|
+
errs.push(`Host "${host}" contains invalid characters (no http://, no spaces, no port)`);
|
|
493
502
|
} else if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
494
503
|
const octets = host.split('.').map(Number);
|
|
495
504
|
if (octets.length !== 4 || octets.some(o => o < 0 || o > 255)) {
|
|
496
|
-
errs.push(`"${host}"
|
|
505
|
+
errs.push(`"${host}" is not a valid IPv4 address`);
|
|
497
506
|
}
|
|
498
507
|
}
|
|
499
508
|
if (d.id && !/^[a-zA-Z0-9_-]+$/.test(String(d.id))) {
|
|
500
|
-
errs.push(`ID "${d.id}"
|
|
509
|
+
errs.push(`ID "${d.id}" contains invalid characters (allowed: a-z, 0-9, _, -)`);
|
|
501
510
|
}
|
|
502
511
|
const ch = parseInt(d.channels, 10);
|
|
503
512
|
if (d.channels !== undefined && d.channels !== '' && (isNaN(ch) || ch < 1 || ch > 4)) {
|
|
504
|
-
errs.push(`
|
|
513
|
+
errs.push(`Channels "${d.channels}" invalid (1-4)`);
|
|
505
514
|
}
|
|
506
515
|
if (d.kind !== undefined && d.kind !== '' && !['unknown', 'blind', 'light'].includes(String(d.kind))) {
|
|
507
|
-
errs.push(`
|
|
516
|
+
errs.push(`Type "${d.kind}" invalid (unknown/blind/light)`);
|
|
508
517
|
}
|
|
509
518
|
const tt = parseInt(d.travelTimeSec, 10);
|
|
510
519
|
if (d.travelTimeSec !== undefined && d.travelTimeSec !== '' && (isNaN(tt) || tt < 0 || tt > 300)) {
|
|
511
|
-
errs.push(`
|
|
520
|
+
errs.push(`Runtime "${d.travelTimeSec}" invalid (0-300s)`);
|
|
512
521
|
}
|
|
513
522
|
if (d.travelTimeSecCh !== undefined && String(d.travelTimeSecCh).trim() !== '') {
|
|
514
523
|
const parts = String(d.travelTimeSecCh).split(',').map(s => s.trim());
|
|
515
524
|
if (parts.length > 4) {
|
|
516
|
-
errs.push(`
|
|
525
|
+
errs.push(`Runtime/channel "${d.travelTimeSecCh}": maximum 4 values`);
|
|
517
526
|
}
|
|
518
527
|
for (const p of parts) {
|
|
519
528
|
if (p === '') continue; // leerer Eintrag = Fallback auf travelTimeSec
|
|
520
529
|
const v = parseInt(p, 10);
|
|
521
530
|
if (isNaN(v) || v < 0 || v > 300 || String(v) !== p) {
|
|
522
|
-
errs.push(`
|
|
531
|
+
errs.push(`Runtime/channel "${d.travelTimeSecCh}": value "${p}" invalid (0-300, integer)`);
|
|
523
532
|
break;
|
|
524
533
|
}
|
|
525
534
|
}
|
|
526
535
|
}
|
|
527
536
|
const tp = parseInt(d.tiltTimeMs, 10);
|
|
528
537
|
if (d.tiltTimeMs !== undefined && d.tiltTimeMs !== '' && (isNaN(tp) || tp < 0 || tp > 5000)) {
|
|
529
|
-
errs.push(`
|
|
538
|
+
errs.push(`Tilt pulse "${d.tiltTimeMs}" invalid (0-5000ms)`);
|
|
530
539
|
}
|
|
531
540
|
const pi = parseInt(d.pollInterval, 10);
|
|
532
541
|
if (d.pollInterval !== undefined && d.pollInterval !== '' && (isNaN(pi) || pi < 5 || pi > 3600)) {
|
|
533
|
-
errs.push(`Poll
|
|
542
|
+
errs.push(`Poll interval "${d.pollInterval}" invalid (5-3600s)`);
|
|
534
543
|
}
|
|
535
544
|
return errs;
|
|
536
545
|
}
|
|
@@ -833,7 +842,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
833
842
|
|
|
834
843
|
async zrapGet(id, path, axiosOpts = {}) {
|
|
835
844
|
const dev = this.devices[id];
|
|
836
|
-
if (!dev) throw new Error(`
|
|
845
|
+
if (!dev) throw new Error(`Unknown device ${id}`);
|
|
837
846
|
const res = await dev.client.get(path, { responseType: 'text', transformResponse: [d => d], ...axiosOpts });
|
|
838
847
|
if (res.status >= 400) throw new Error(`HTTP ${res.status}`);
|
|
839
848
|
if (!res.data) return {};
|
|
@@ -847,7 +856,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
847
856
|
|
|
848
857
|
async zrapPost(id, path, bodyObj) {
|
|
849
858
|
const dev = this.devices[id];
|
|
850
|
-
if (!dev) throw new Error(`
|
|
859
|
+
if (!dev) throw new Error(`Unknown device ${id}`);
|
|
851
860
|
const data = Object.entries(bodyObj)
|
|
852
861
|
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
853
862
|
.join('&');
|
|
@@ -863,7 +872,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
863
872
|
// 3340-2-B + Front 920-330x), daher separat und optional (Konfig-Checkbox).
|
|
864
873
|
async zapiGet(id, path) {
|
|
865
874
|
const dev = this.devices[id];
|
|
866
|
-
if (!dev) throw new Error(`
|
|
875
|
+
if (!dev) throw new Error(`Unknown device ${id}`);
|
|
867
876
|
const res = await dev.client.get(path);
|
|
868
877
|
if (res.status >= 400) throw new Error(`HTTP ${res.status}`);
|
|
869
878
|
return res.data;
|
|
@@ -871,7 +880,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
871
880
|
|
|
872
881
|
async zapiPost(id, path, jsonBody) {
|
|
873
882
|
const dev = this.devices[id];
|
|
874
|
-
if (!dev) throw new Error(`
|
|
883
|
+
if (!dev) throw new Error(`Unknown device ${id}`);
|
|
875
884
|
const res = await dev.client.post(path, jsonBody, {
|
|
876
885
|
headers: { 'Content-Type': 'application/json' }
|
|
877
886
|
});
|
|
@@ -895,7 +904,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
895
904
|
const str = String(value ?? '');
|
|
896
905
|
const bytes = Buffer.byteLength(str, 'utf8');
|
|
897
906
|
if (bytes > maxBytes) {
|
|
898
|
-
throw new Error(`${fieldName}: ${bytes}
|
|
907
|
+
throw new Error(`${fieldName}: ${bytes} bytes exceed the API limit of ${maxBytes} bytes (note: umlauts count as 2 bytes)`);
|
|
899
908
|
}
|
|
900
909
|
return str;
|
|
901
910
|
}
|
|
@@ -908,10 +917,10 @@ class Zeptrion extends utils.Adapter {
|
|
|
908
917
|
*/
|
|
909
918
|
sendChannelCommand(id, chNum, cmd) {
|
|
910
919
|
if (!isValidChCmd(cmd)) {
|
|
911
|
-
return Promise.reject(new Error(`
|
|
920
|
+
return Promise.reject(new Error(`Invalid channel command "${cmd}"`));
|
|
912
921
|
}
|
|
913
922
|
const dev = this.devices[id];
|
|
914
|
-
if (!dev) return Promise.reject(new Error(`
|
|
923
|
+
if (!dev) return Promise.reject(new Error(`Unknown device ${id}`));
|
|
915
924
|
|
|
916
925
|
return new Promise((resolve, reject) => {
|
|
917
926
|
dev.pendingCmds[chNum] = cmd;
|
|
@@ -1068,7 +1077,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1068
1077
|
const dev = this.devices[id];
|
|
1069
1078
|
if (!dev) return;
|
|
1070
1079
|
if (dev.cfg.kind !== 'blind' || !dev.cfg.travelTimeMsByCh[chNum]) {
|
|
1071
|
-
throw new Error('setPosition
|
|
1080
|
+
throw new Error('setPosition requires Type=Shutter and a configured motor runtime (>0s) for this channel');
|
|
1072
1081
|
}
|
|
1073
1082
|
target = Math.max(0, Math.min(100, Math.round(Number(target))));
|
|
1074
1083
|
const travel = dev.cfg.travelTimeMsByCh[chNum];
|
|
@@ -1393,11 +1402,11 @@ class Zeptrion extends utils.Adapter {
|
|
|
1393
1402
|
handleDeviceError(id, err, context) {
|
|
1394
1403
|
let msg = (err && err.message) || String(err);
|
|
1395
1404
|
const code = err && err.code;
|
|
1396
|
-
if (code === 'ECONNREFUSED') msg = '
|
|
1397
|
-
else if (code === 'ECONNABORTED') msg = '
|
|
1398
|
-
else if (code === 'EHOSTUNREACH') msg = 'Host
|
|
1399
|
-
else if (code === 'ENOTFOUND') msg = 'Hostname/mDNS
|
|
1400
|
-
else if (code === 'ETIMEDOUT') msg = '
|
|
1405
|
+
if (code === 'ECONNREFUSED') msg = 'Connection refused (device off or wrong IP?)';
|
|
1406
|
+
else if (code === 'ECONNABORTED') msg = 'Timeout (device not reachable)';
|
|
1407
|
+
else if (code === 'EHOSTUNREACH') msg = 'Host unreachable (check network/routing)';
|
|
1408
|
+
else if (code === 'ENOTFOUND') msg = 'Hostname/mDNS name could not be resolved';
|
|
1409
|
+
else if (code === 'ETIMEDOUT') msg = 'Timeout while establishing the connection';
|
|
1401
1410
|
this.log.warn(`[${id}] Error during ${context}: ${msg}`);
|
|
1402
1411
|
this.setStateAsync(`${id}.info.lastError`, { val: msg, ack: true }).catch(() => {});
|
|
1403
1412
|
this.markConnected(id, false);
|
|
@@ -1523,7 +1532,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1523
1532
|
try {
|
|
1524
1533
|
body = JSON.parse(String(state.val));
|
|
1525
1534
|
} catch (err) {
|
|
1526
|
-
throw new Error(`ledSet:
|
|
1535
|
+
throw new Error(`ledSet: not valid JSON ("${err.message}"). Example: [{"id":2,"bg":"#220000"}]`);
|
|
1527
1536
|
}
|
|
1528
1537
|
await this.zapiPost(id, '/zapi/smartfront/led', body);
|
|
1529
1538
|
await this.setStateAsync(idFull, { val: state.val, ack: true });
|
|
@@ -1634,7 +1643,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1634
1643
|
discoverDevices(timeoutMs = 4000) {
|
|
1635
1644
|
return new Promise((resolve, reject) => {
|
|
1636
1645
|
if (!Bonjour) {
|
|
1637
|
-
reject(new Error('
|
|
1646
|
+
reject(new Error('Module "bonjour-service" is not installed. Run "npm install bonjour-service" in the adapter directory.'));
|
|
1638
1647
|
return;
|
|
1639
1648
|
}
|
|
1640
1649
|
const bonjour = new Bonjour();
|
|
@@ -1742,7 +1751,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1742
1751
|
try {
|
|
1743
1752
|
const csv = String((obj.message && obj.message.csv) || '').trim();
|
|
1744
1753
|
if (!csv) {
|
|
1745
|
-
if (obj.callback) this.sendTo(obj.from, obj.command, { result:
|
|
1754
|
+
if (obj.callback) this.sendTo(obj.from, obj.command, { result: I18n.translate('csvFieldEmpty') }, obj.callback);
|
|
1746
1755
|
return;
|
|
1747
1756
|
}
|
|
1748
1757
|
const delim = csv.includes(';') ? ';' : ',';
|
|
@@ -1775,9 +1784,9 @@ class Zeptrion extends utils.Adapter {
|
|
|
1775
1784
|
if (['licht', 'lampe'].includes(row.kind)) row.kind = 'light';
|
|
1776
1785
|
|
|
1777
1786
|
const errs = this.validateDeviceRow(row);
|
|
1778
|
-
if (existingHosts.has(row.host.toLowerCase())) errs.push('
|
|
1787
|
+
if (existingHosts.has(row.host.toLowerCase())) errs.push(I18n.translate('hostAlreadyConfigured'));
|
|
1779
1788
|
if (errs.length) {
|
|
1780
|
-
report.push(
|
|
1789
|
+
report.push(I18n.translate('csvRowError', i + 1, row.host || '?', errs.join('; ')));
|
|
1781
1790
|
continue;
|
|
1782
1791
|
}
|
|
1783
1792
|
// ID aus Host ableiten, Kollisionen auflösen
|
|
@@ -1802,7 +1811,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1802
1811
|
});
|
|
1803
1812
|
existingHosts.add(row.host.toLowerCase());
|
|
1804
1813
|
existingIds.add(candidate);
|
|
1805
|
-
report.push(
|
|
1814
|
+
report.push(I18n.translate('csvRowImported', i + 1, row.name || row.host, row.host, candidate));
|
|
1806
1815
|
added++;
|
|
1807
1816
|
}
|
|
1808
1817
|
|
|
@@ -1810,14 +1819,15 @@ class Zeptrion extends utils.Adapter {
|
|
|
1810
1819
|
instObj.native.devices = devices;
|
|
1811
1820
|
await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, instObj);
|
|
1812
1821
|
}
|
|
1813
|
-
const
|
|
1822
|
+
const restartHint = added ? I18n.translate('csvImportRestartHint') : '';
|
|
1823
|
+
const result = I18n.translate('csvImportSummary', added, lines.length, restartHint, report.join('\n'));
|
|
1814
1824
|
this.log.info(`CSV import: ${added}/${lines.length} rows imported`);
|
|
1815
1825
|
if (obj.callback) this.sendTo(obj.from, obj.command, { result }, obj.callback);
|
|
1816
1826
|
} catch (err) {
|
|
1817
|
-
//
|
|
1818
|
-
//
|
|
1819
|
-
const msg = `CSV-Import fehlgeschlagen: ${err.message || err}`;
|
|
1827
|
+
// Log entry stays English regardless of system language; the UI-facing message
|
|
1828
|
+
// is localized separately via I18n.translate().
|
|
1820
1829
|
this.log.warn(`CSV import failed: ${err.message || err}`);
|
|
1830
|
+
const msg = I18n.translate('csvImportFailed', err.message || err);
|
|
1821
1831
|
if (obj.callback) this.sendTo(obj.from, obj.command, { error: msg }, obj.callback);
|
|
1822
1832
|
}
|
|
1823
1833
|
return;
|
|
@@ -1827,7 +1837,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1827
1837
|
const devicesCfg = Array.isArray(this.config.devices) ? this.config.devices : [];
|
|
1828
1838
|
const rows = devicesCfg.filter(d => d && d.host);
|
|
1829
1839
|
if (!rows.length) {
|
|
1830
|
-
if (obj.callback) this.sendTo(obj.from, obj.command, { result:
|
|
1840
|
+
if (obj.callback) this.sendTo(obj.from, obj.command, { result: I18n.translate('noDevicesWithHost') }, obj.callback);
|
|
1831
1841
|
return;
|
|
1832
1842
|
}
|
|
1833
1843
|
const lines = [];
|
|
@@ -1838,7 +1848,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1838
1848
|
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
1839
1849
|
const octets = host.split('.').map(Number);
|
|
1840
1850
|
if (octets.some(o => o > 255)) {
|
|
1841
|
-
lines.push(
|
|
1851
|
+
lines.push(I18n.translate('testInvalidIp', label, host));
|
|
1842
1852
|
continue;
|
|
1843
1853
|
}
|
|
1844
1854
|
}
|
|
@@ -1851,18 +1861,18 @@ class Zeptrion extends utils.Adapter {
|
|
|
1851
1861
|
const idData = (rootKey && parsed[rootKey]) || {};
|
|
1852
1862
|
if (String(idData.sys ?? '').toUpperCase() === 'ZEPTRION') {
|
|
1853
1863
|
const m = String(idData.type ?? '').match(/^3340-(\d)-/);
|
|
1854
|
-
const ch = m ?
|
|
1855
|
-
lines.push(
|
|
1864
|
+
const ch = m ? I18n.translate('testChannelsSuffix', m[1]) : '';
|
|
1865
|
+
lines.push(I18n.translate('testDeviceOk', label, host, idData.type ?? '?', ch, idData.sw ?? '?', idData.sn ?? '?'));
|
|
1856
1866
|
} else {
|
|
1857
|
-
lines.push(
|
|
1867
|
+
lines.push(I18n.translate('testNotZeptrion', label, host, idData.sys ?? 'unknown'));
|
|
1858
1868
|
}
|
|
1859
1869
|
} catch (err) {
|
|
1860
1870
|
const code = err.code || (err.message || '').substring(0, 40);
|
|
1861
|
-
lines.push(
|
|
1871
|
+
lines.push(I18n.translate('testUnreachable', label, host, code));
|
|
1862
1872
|
}
|
|
1863
1873
|
}
|
|
1864
1874
|
const result = lines.join('\n');
|
|
1865
|
-
this.log.info(`Device test
|
|
1875
|
+
this.log.info(`Device test: ${lines.length} device(s) checked`);
|
|
1866
1876
|
if (obj.callback) this.sendTo(obj.from, obj.command, { result }, obj.callback);
|
|
1867
1877
|
return;
|
|
1868
1878
|
}
|
|
@@ -1872,11 +1882,10 @@ class Zeptrion extends utils.Adapter {
|
|
|
1872
1882
|
this.log.info('Starting mDNS discovery for zeptrion devices...');
|
|
1873
1883
|
const results = await this.discoverDevices(4000);
|
|
1874
1884
|
const added = await this.mergeDiscoveredDevices(results);
|
|
1875
|
-
//
|
|
1876
|
-
//
|
|
1877
|
-
const msg = `Suche abgeschlossen: ${results.length} Gerät(e) im Netz gefunden, ${added} neu (deaktiviert) übernommen. ` +
|
|
1878
|
-
`Instanz-Konfiguration schliessen und neu öffnen, um sie in der Tabelle zu sehen und zu aktivieren.`;
|
|
1885
|
+
// Log entry stays English regardless of system language; the UI-facing message
|
|
1886
|
+
// is localized separately via I18n.translate().
|
|
1879
1887
|
this.log.info(`Discovery finished: ${results.length} device(s) found on the network, ${added} newly added (disabled).`);
|
|
1888
|
+
const msg = I18n.translate('discoveryFinished', results.length, added);
|
|
1880
1889
|
if (obj.callback) {
|
|
1881
1890
|
this.sendTo(obj.from, obj.command, { result: msg, devices: results }, obj.callback);
|
|
1882
1891
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zeptrion",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "ioBroker Adapter für Feller zeptrion / zApp WLAN-Aktoren (WLAN-Nebenstelle 4K, WLAN-Zwischenmodul 2K) über die zrap Webservice API - inkl. Sammelbefehlen für Hagelalarm und mDNS-Discovery",
|
|
5
5
|
"author": "Stefan Bühler",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"admin/",
|
|
34
34
|
"main.js",
|
|
35
35
|
"lib/",
|
|
36
|
+
"i18n/",
|
|
36
37
|
"io-package.json",
|
|
37
38
|
"LICENSE"
|
|
38
39
|
],
|