iobroker.zeptrion 1.0.10 → 1.0.12
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 -8
- package/README_de.md +277 -0
- package/i18n/de.json +29 -0
- package/i18n/en.json +29 -0
- package/i18n/es.json +29 -0
- package/i18n/fr.json +29 -0
- package/i18n/it.json +29 -0
- package/i18n/nl.json +29 -0
- package/i18n/pl.json +29 -0
- package/i18n/pt.json +29 -0
- package/i18n/ru.json +29 -0
- package/i18n/uk.json +29 -0
- package/i18n/zh-cn.json +29 -0
- package/io-package.json +27 -27
- package/main.js +74 -32
- package/package.json +4 -2
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.12 (2026-08-10)
|
|
180
|
+
|
|
181
|
+
- Fix a real i18n gap found during independent end-to-end testing of v1.0.11: embedded field-validation messages in the CSV import report stayed English even in the localized report. validateDeviceRow() now returns structured {key, args} entries with two explicit renderers: always-English for logs, I18n.translate() for the UI. Added 13 new i18n keys across all 11 languages. Also added the missing README_de.md to package.json's "files" allowlist - npm auto-includes README.md in every package but NOT README_de.md, so it was silently absent from the published tarball. Verified end-to-end: loaded the real I18n module directly, confirmed log output stays English regardless of active UI language across EN/DE/FR/ZH, and downloaded + extracted the real published npm tarball to confirm packaging.
|
|
182
|
+
|
|
183
|
+
### 1.0.11 (2026-08-10)
|
|
184
|
+
|
|
185
|
+
- 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.
|
|
186
|
+
|
|
179
187
|
### 1.0.10 (2026-08-10)
|
|
180
188
|
|
|
181
189
|
- 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.
|
|
@@ -192,14 +200,7 @@ npm run test:integration # starts a real js-controller (takes longer)
|
|
|
192
200
|
### 1.0.6 (2026-07-22)
|
|
193
201
|
- Fix: reverted validatorErrorText for id/host/travelTimeSecCh back to plain strings - the admin jsonConfig schema requires validatorErrorText to be a string, not an i18n object (E5512). The i18n conversion in 1.0.5 passed lint but failed schema validation.
|
|
194
202
|
|
|
195
|
-
### 1.0.5
|
|
196
|
-
- Admin UI: the three `validatorErrorText` messages are now inline i18n objects instead of plain English strings (ioBroker checker W5617).
|
|
197
|
-
- 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.
|
|
198
|
-
|
|
199
|
-
### 1.0.4 (2026-07-21)
|
|
200
|
-
- Documentation only: added a Buy Me a Coffee link next to the PayPal donate badge. No functional changes.
|
|
201
|
-
|
|
202
|
-
### 1.0.3 and older
|
|
203
|
+
### 1.0.5 and older
|
|
203
204
|
|
|
204
205
|
Older changelog entries can be found in [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
205
206
|
|
package/README_de.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# ioBroker.zeptrion
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/iobroker.zeptrion)
|
|
6
|
+
[](https://www.npmjs.com/package/iobroker.zeptrion)
|
|
7
|
+
[](https://github.com/bueste/ioBroker.zeptrion/actions)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://www.paypal.com/ncp/payment/TT6MTBLXX9L9U)
|
|
10
|
+
[](https://buymeacoffee.com/stefanbuehler)
|
|
11
|
+
|
|
12
|
+
Adapter für [Feller](https://www.feller.ch/) **[zeptrion / zApp](https://www.feller.ch/de/connected-buildings/zeptrion)** WLAN-Aktoren (WLAN-Nebenstelle 4K = zApp-Gateway,
|
|
13
|
+
WLAN-Zwischenmodul 2K = zApp-Booster) für Licht- und Storensteuerung, basierend auf der
|
|
14
|
+
zrap Webservice API (Feller-Dokument 10.ZEPAPI-E.1612 / Version 1.0, Firmware ab 01.08.18).
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Funktionsumfang
|
|
19
|
+
|
|
20
|
+
- **Kanalsteuerung** (`zrap/chctrl`): on/off/stop/toggle, open/close, move_open/move_close,
|
|
21
|
+
dim_up/dim_down inkl. zeitgesteuerter Varianten (`_t` in ms), sowie Szenen
|
|
22
|
+
recall_s1-4 / store_s1-4 / delete_s1-4 - als einzelne Buttons UND als freies
|
|
23
|
+
`command`-Textfeld.
|
|
24
|
+
- **Kanalzustand** (`zrap/chscan` als periodischer Resync + `zrap/chnotify` als
|
|
25
|
+
Long-Poll-Push für nahezu Echtzeit-Updates) und **Kanalbeschreibung** (`zrap/chdes`,
|
|
26
|
+
read/write: Name, Gruppe, Icon, Typ, Kategorie).
|
|
27
|
+
- **Geräteinformationen** (`zrap/id`): Hardware-/Software-/Bootloader-Version, Seriennummer,
|
|
28
|
+
Systemname, Gerätetyp.
|
|
29
|
+
- **Signalstärke** (`zrap/rssi`, gepollt).
|
|
30
|
+
- **Netzwerkstatus** (`zrap/net`, read-only): SSID, IP, MAC, Modus, Verschlüsselung,
|
|
31
|
+
Maske, Gateway.
|
|
32
|
+
- **Systembefehle** (`zrap/sys`): Neustart, Werksreset, Zurücksetzen in Access-Point-Modus.
|
|
33
|
+
- **Standort** (`zrap/loc`), **NTP-Konfiguration** (`zrap/ntp`) und **Datum/Zeit**
|
|
34
|
+
(`zrap/date`) inkl. Ein-Klick-Synchronisation der Geräte-Uhrzeit mit dem ioBroker-Host.
|
|
35
|
+
- **mDNS-Discovery** (Kapitel 4 der API-Doku): durchsucht das lokale Netz nach
|
|
36
|
+
zeptrion-Geräten und übernimmt Funde deaktiviert in die Konfigurationstabelle
|
|
37
|
+
(Discovery kombiniert mit manueller Kontrolle/Aktivierung).
|
|
38
|
+
- **Sammelbefehle für Hagelalarm**: `control.closeAllShutters` / `openAllShutters` /
|
|
39
|
+
`stopAllShutters` steuern alle konfigurierten Kanäle über alle aktiven Geräte gleichzeitig
|
|
40
|
+
- dank Multicast-Bündelung (siehe unten) als je EIN Request pro Gerät, nicht pro Kanal.
|
|
41
|
+
- **Multicast-Befehlsbündelung**: Kanalbefehle desselben Geräts, die innerhalb von 50ms
|
|
42
|
+
eintreffen, werden automatisch zu einem einzigen `zrap/chctrl`-Multicast-POST gebündelt
|
|
43
|
+
(Kapitel 3.6.5 der API-Doku) statt mehrerer sequentieller Einzelrequests.
|
|
44
|
+
- **Storen-Positionsschätzung** (optional, `posEstimate`): da die Hardware laut Doku für
|
|
45
|
+
Storenkanäle praktisch immer `-1` (unbekannt) liefert, kann pro Gerät eine
|
|
46
|
+
Motor-Laufzeit hinterlegt werden; der Adapter schätzt daraus die Position anhand
|
|
47
|
+
Bewegungsrichtung und verstrichener Zeit (Best Effort, kein Hardware-Feedback,
|
|
48
|
+
manuell kalibrierbar).
|
|
49
|
+
- **Smartfront-Unterstützung** (optional, `zapi/smartfront/*`): Temperatur/Helligkeit/
|
|
50
|
+
Luftfeuchtigkeit auslesen, LED-Hintergrundfarbe setzen (nur für Geräte mit
|
|
51
|
+
angeschlossenem Feller-Smartfront-Taster, Checkbox in der Konfiguration).
|
|
52
|
+
- Robustes Error-Handling: unterscheidet ECONNREFUSED/Timeout/DNS-Fehler, Backoff bei
|
|
53
|
+
wiederholten Fehlern, pro Gerät und global sichtbarer Verbindungsstatus. mDNS-Discovery
|
|
54
|
+
ist zusätzlich gegen Exceptions durch fremde/kaputte Netzwerkpakete abgesichert.
|
|
55
|
+
|
|
56
|
+
Nicht implementiert (siehe "Bekannte Einschränkungen"): Schreibzugriff auf `zrap/net`
|
|
57
|
+
(WLAN-Zugangsdaten ändern), `zrap/scheduler`, Smartbutton-Webhook-Programmierung
|
|
58
|
+
(`zapi/smartbt/*`).
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
Admin-Oberfläche -> Adapter -> "zeptrion" suchen -> Installieren.
|
|
63
|
+
|
|
64
|
+
## Konfiguration
|
|
65
|
+
|
|
66
|
+
- **HTTP Timeout**: Timeout pro Request an ein Gerät (Default 4000 ms).
|
|
67
|
+
- **Discovery-Button**: durchsucht das lokale Netz per mDNS (Service-Type `_zapp._tcp`,
|
|
68
|
+
Fallback `_http._tcp` für Firmware < 01.08.xx anhand des Hostname-Musters
|
|
69
|
+
`zapp-YYWWNNNN`). Neu gefundene Geräte werden **deaktiviert** in die Tabelle
|
|
70
|
+
übernommen - Zeile danach prüfen, ID/Name vergeben, Kanalzahl kontrollieren
|
|
71
|
+
(3340-4-x = 4 Kanäle, 3340-2-x = 2 Kanäle) und aktivieren. mDNS funktioniert nur
|
|
72
|
+
innerhalb des gleichen Netzsegments/VLANs.
|
|
73
|
+
- **Geräte-Tabelle** (auch komplett manuell befüllbar, ohne Discovery):
|
|
74
|
+
- `Aktiv`, `ID` (a-z 0-9 _ -), `Bezeichnung`, `IP-Adresse/Hostname`,
|
|
75
|
+
`Kanäle` (1-4), `Art` (Storen/Licht/unbekannt - steuert die ioBroker-Objektrollen,
|
|
76
|
+
siehe unten), `Laufzeit Storenmotor` (Sekunden, 0=deaktiviert - schaltet
|
|
77
|
+
`posEstimate` frei, siehe unten, gilt als Standard für alle Kanäle),
|
|
78
|
+
`Laufzeit/Kanal` (optional, kommagetrennt, z.B. `22,28` - überschreibt die
|
|
79
|
+
Standard-Laufzeit einzeln je Kanal; nützlich bei 2K-Geräten, deren beide
|
|
80
|
+
Kanäle unterschiedliche Motor-Laufzeiten haben; leere Einträge fallen auf
|
|
81
|
+
die Standard-Laufzeit zurück), `Smartfront` (Checkbox, nur bei angeschlossenem
|
|
82
|
+
Feller-Smartfront-Taster aktivieren), `Poll (s)` (Default 30, für RSSI +
|
|
83
|
+
periodischen chscan-Resync; die eigentlichen Kanalupdates laufen über den
|
|
84
|
+
chnotify-Long-Poll unabhängig davon).
|
|
85
|
+
|
|
86
|
+
## Objektbaum pro Gerät (`zeptrion.0.<id>`)
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
<id>.info.connection / lastError / hw / sw / boot / sn / sys / type / oen / rssi / refresh
|
|
90
|
+
<id>.network.ssid / ip / mac / mode / enc / mask / gw / bssid (read-only)
|
|
91
|
+
<id>.system.reboot / unlock / factoryDefault / networkDefault (Buttons; factoryDefault erfordert unlock binnen 30s)
|
|
92
|
+
<id>.location.name (read/write)
|
|
93
|
+
<id>.ntp.url / per (read/write)
|
|
94
|
+
<id>.date.rfc1123 / tz / dst / syncNow (read/write + Button)
|
|
95
|
+
|
|
96
|
+
<id>.channels.chN.val Kanalzustand 0-100 / -1 (roher Hardwarewert)
|
|
97
|
+
<id>.channels.chN.posEstimate nur bei Art=Storen: Software-Positionsschätzung
|
|
98
|
+
0=zu/100=offen, auch manuell schreibbar (Kalibrierung)
|
|
99
|
+
<id>.channels.chN.name / group / icon / type / cat Kanalbeschreibung (read/write)
|
|
100
|
+
<id>.channels.chN.command freies Kommando (String)
|
|
101
|
+
<id>.channels.chN.stop / on / off / toggle / open / close /
|
|
102
|
+
move_open / move_close / dim_up / dim_down (Buttons)
|
|
103
|
+
<id>.channels.chN.recall_s1..4 / store_s1..4 / delete_s1..4 (Buttons)
|
|
104
|
+
|
|
105
|
+
<id>.smartfront.temp / lux / hum nur wenn "Smartfront" aktiviert (read)
|
|
106
|
+
<id>.smartfront.ledState aktueller LED-Status als JSON (read)
|
|
107
|
+
<id>.smartfront.ledSet LED(s) setzen, JSON-Array (write)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Global:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
info.connection mind. ein Gerät erreichbar
|
|
114
|
+
control.closeAllShutters Button: ALLE konfigurierten Kanäle -> "close"
|
|
115
|
+
control.openAllShutters Button: ALLE konfigurierten Kanäle -> "open"
|
|
116
|
+
control.stopAllShutters Button: ALLE konfigurierten Kanäle -> "stop"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Objekt-Rollen und "Art" (kind)
|
|
120
|
+
|
|
121
|
+
Die zrap-API selbst unterscheidet nicht zwischen Licht- und Storenkanal - das steckt
|
|
122
|
+
allein in der Verkabelung/im Aktor. Damit Visualisierungen (VIS, evtl. spätere
|
|
123
|
+
ioBroker.iot/Alexa-Anbindung) Kanäle trotzdem sinnvoll klassifizieren können, kann pro
|
|
124
|
+
Gerät die "Art" gesetzt werden:
|
|
125
|
+
|
|
126
|
+
| Art | `<ch>.val` Rolle | `stop`/`open`/`close` Rolle |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| Storen/Rolladen | `level.blind` | `button.stop` / `button.open.blind` / `button.close.blind` |
|
|
129
|
+
| Licht | `level.dimmer` | generisch `button` |
|
|
130
|
+
| unbekannt (Default) | `value` | generisch `button` |
|
|
131
|
+
|
|
132
|
+
Wichtig: `level.blind` täuscht **keine** echte Positionsrückmeldung vor - laut Feller-Doku
|
|
133
|
+
liefert `chscan`/`chnotify` für einen Storenkanal so gut wie immer `-1` (unbekannt), da die
|
|
134
|
+
Hardware selbst keine Lamellenposition zurückmeldet. Die Rolle verbessert nur die
|
|
135
|
+
Erkennung durch VIS-Widgets, der Zahlenwert bleibt i.d.R. uninformativ.
|
|
136
|
+
|
|
137
|
+
## Hagelalarm-Nutzung
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
// JavaScript-Adapter Beispiel
|
|
141
|
+
on({id: 'wetter.0.warnungen.hagel', val: true}, function () {
|
|
142
|
+
setState('zeptrion.0.control.closeAllShutters', true);
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Fehler bei einzelnen Geräten (offline etc.) unterbrechen die übrigen Kanäle nicht -
|
|
147
|
+
jeder fehlgeschlagene Kanal wird einzeln geloggt und in `<id>.info.lastError` vermerkt.
|
|
148
|
+
|
|
149
|
+
## Bekannte Einschränkungen / bewusste Entscheidungen
|
|
150
|
+
|
|
151
|
+
- **Smartbutton-Webhook-Programmierung** (`zapi/smartbt/prgm`/`prgn`/`prgs`) ist nicht
|
|
152
|
+
implementiert: dabei ruft der Taster bei Tastendruck direkt eine URL auf ioBroker auf
|
|
153
|
+
(echtes Push, ganz ohne Polling). Das würde einen eingehenden HTTP-Server im Adapter
|
|
154
|
+
voraussetzen, den es aktuell nicht gibt - eine grössere Architekturerweiterung, kein
|
|
155
|
+
kleiner Zusatz. Bleibt als möglicher zukünftiger Ausbauschritt dokumentiert.
|
|
156
|
+
- **Schreibzugriff auf `zrap/net`** ist nicht implementiert - WLAN-Zugangsdaten eines
|
|
157
|
+
Aktors per Skript zu ändern ist riskant (Verbindungsverlust, Reboot nötig). Kann bei
|
|
158
|
+
Bedarf ergänzt werden.
|
|
159
|
+
- **Scheduler (`zrap/scheduler`)** und die **zeptrionAir-Smartfront-Services**
|
|
160
|
+
(`zapi/smartfront/*`, `zapi/smartbt/*`) sind nicht implementiert, da für den
|
|
161
|
+
Storen-/Hagel-Use-Case nicht relevant. Die vorhandene `zrapGet`/`zrapPost`-Struktur
|
|
162
|
+
in `main.js` lässt sich leicht erweitern.
|
|
163
|
+
- `chctrl` liefert laut Doku HTTP 302 ohne Body - Redirects werden bewusst nicht verfolgt
|
|
164
|
+
(`maxRedirects: 0`), um unnötige Zusatzrequests zu vermeiden.
|
|
165
|
+
- Bei wiederholten Fehlern eines Geräts wird das Poll-Intervall bis maximal das
|
|
166
|
+
5-fache verlängert (einfacher Backoff).
|
|
167
|
+
|
|
168
|
+
## Entwicklung / Tests
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm install
|
|
172
|
+
npm run lint
|
|
173
|
+
npm test # Package-Konsistenz + Unit-Tests
|
|
174
|
+
npm run test:integration # startet echten js-controller (dauert länger)
|
|
175
|
+
```
|
|
176
|
+
## Changelog
|
|
177
|
+
|
|
178
|
+
### 1.0.5 (2026-07-22)
|
|
179
|
+
- Admin-UI: Die drei `validatorErrorText`-Meldungen sind jetzt Inline-i18n-Objekte statt reiner englischer Texte (ioBroker-Checker W5617).
|
|
180
|
+
- Abhängigkeiten: Deklarierte Mindestversionen für `bonjour-service` (^1.2.1 -> ^1.4.3) und `eslint` (^10.6.0 -> ^10.7.0) angehoben; beide waren durch die bestehenden Caret-Ranges ohnehin abgedeckt, daher keine Verhaltensänderung.
|
|
181
|
+
|
|
182
|
+
### 1.0.4 (2026-07-21)
|
|
183
|
+
- Nur Dokumentation: Buy-Me-a-Coffee-Link neben dem PayPal-Spenden-Badge ergänzt. Keine funktionalen Änderungen.
|
|
184
|
+
|
|
185
|
+
### 0.7.0 (2026-07-10)
|
|
186
|
+
- Skalierung für 20+ Geräte: paralleles Setup, Poll-Jitter, Duplikat-Erkennung
|
|
187
|
+
- Strikte Startup-Validierung jeder konfigurierten Geräte-Zeile
|
|
188
|
+
- CSV-Massenimport (eigener Konfig-Tab) mit Zeilen-Validierung und Auto-ID
|
|
189
|
+
- FIX: Positionsschätzung nach Stopp während Endlagenfahrt korrekt
|
|
190
|
+
- FIX: Adapter-Timer-Cleanup (this.clearTimeout), führende Nullen in chdes-Codes bleiben erhalten
|
|
191
|
+
|
|
192
|
+
### 0.6.0 (2026-07-10)
|
|
193
|
+
- Auto-ID aus Host, Geräte-Test-Button (Erreichbarkeit + zeptrion-Verifikation + Kanalzahl-Prüfung)
|
|
194
|
+
- Kanal-Objektnamen aus dem Gerät (chdes), neues Icon, Geräte-Icons
|
|
195
|
+
|
|
196
|
+
### 0.5.1 (2026-07-10)
|
|
197
|
+
- KRITISCHER FIX: XML-Parser übersprang die Nutzdaten wegen des XML-Deklarations-Keys - alle GET-Werte blieben in 0.5.0 null
|
|
198
|
+
|
|
199
|
+
### 0.5.0 (2026-07-07)
|
|
200
|
+
- setPosition: zeitbasierte %-Anfahrt für Storen (Chunking wegen 32s-API-Limit, Referenzfahrt bei unbekannter Position)
|
|
201
|
+
- tiltOpen/tiltClose: Lamellen-Kipp-Impulse (konfigurierbare Impulsdauer)
|
|
202
|
+
- calibrate: Positionsschätzung ohne Fahrt setzen
|
|
203
|
+
|
|
204
|
+
### 0.4.0 (2026-07-07) - Security- & Qualitäts-Härtung
|
|
205
|
+
- **Verriegelter Werksreset**: `system.factoryDefault` funktioniert nur noch innerhalb
|
|
206
|
+
von 30s nach Setzen von `system.unlock` - ein einzelner versehentlicher setState aus
|
|
207
|
+
Script/VIS kann das Gerät nicht mehr plätten.
|
|
208
|
+
- **Crashsicheres onStateChange**: der komplette Handler (inkl. der Sammelbefehle) läuft
|
|
209
|
+
jetzt in einer zentralen Fehlerbehandlung - keine Unhandled Promise Rejections mehr
|
|
210
|
+
möglich.
|
|
211
|
+
- **Eingabevalidierung**: Kanalbeschreibung (32/32/24/4/4 Bytes UTF-8), Standort (32),
|
|
212
|
+
NTP-URL (32) und NTP-Intervall (0-255) werden vor dem Senden geprüft; klare
|
|
213
|
+
Fehlermeldung statt HTTP-400 vom Gerät. Umlaute zählen korrekt als 2 Bytes.
|
|
214
|
+
- **Adapter-verwaltete Timer** (`this.setTimeout`) überall - automatische Aufräumung
|
|
215
|
+
beim Unload gemäss ioBroker-Guidelines.
|
|
216
|
+
- **Verbindungs-Ökonomie**: solange der chnotify-Long-Poll gesund läuft, wird der
|
|
217
|
+
redundante chscan-Resync nur noch bei jedem 5. Poll ausgeführt (schont die
|
|
218
|
+
schwachen Embedded-Webserver der Unterputzaktoren).
|
|
219
|
+
- **chnotify abschaltbar** (Experten-Tab) für Umgebungen mit Verbindungsproblemen.
|
|
220
|
+
- **Admin-UI neu**: Tabs (Geräte/Experten), durchgängig EN+DE, Eingabe-Validatoren
|
|
221
|
+
(ID-Muster, Host-Muster), Tooltips an jeder Spalte, Sicherheitshinweis.
|
|
222
|
+
- ESLint auf Flat Config (v9) migriert, Lint läuft sauber durch; Smoke-Tests für
|
|
223
|
+
Kommando-Validierung, Byte-Limits, Positionsmathematik und Multicast-Body.
|
|
224
|
+
|
|
225
|
+
### 0.3.0 (2026-07-07)
|
|
226
|
+
- Kanalbefehle desselben Geräts werden innerhalb eines 50ms-Fensters automatisch zu
|
|
227
|
+
einem einzigen Multicast-POST an `/zrap/chctrl` gebündelt statt sequentiell einzeln
|
|
228
|
+
gesendet - insbesondere `control.closeAllShutters` (Hagelalarm) profitiert davon
|
|
229
|
+
massiv (ein Request pro Gerät statt einer pro Kanal).
|
|
230
|
+
- Optionale zeitbasierte Storen-Positionsschätzung (`posEstimate`) anhand konfigurierbarer
|
|
231
|
+
Motor-Laufzeit, da die Hardware selbst keine Position zurückmeldet.
|
|
232
|
+
- Optionale Smartfront-Unterstützung (`zapi/smartfront/*`): Temperatur/Helligkeit/
|
|
233
|
+
Feuchtigkeit auslesen, LED-Hintergrundfarbe setzen.
|
|
234
|
+
- Rollen-Korrektur: `level.blind` sitzt jetzt auf der Positionsschätzung statt auf dem
|
|
235
|
+
rohen (meist -1) Hardwarewert.
|
|
236
|
+
|
|
237
|
+
### 0.2.0 (2026-07-07)
|
|
238
|
+
- Kanalzustände werden jetzt primär über `zrap/chnotify` (Long-Poll) nahezu in
|
|
239
|
+
Echtzeit aktualisiert statt nur per Intervall-Polling; `chscan`-Poll bleibt als
|
|
240
|
+
periodischer Resync/Fallback erhalten.
|
|
241
|
+
- Zusätzliches Sicherheitsnetz (Busy-Window, 5s) verhindert, dass ein zeitgleicher
|
|
242
|
+
chscan-Resync einen gerade gesendeten Bewegungsbefehl mit einem veralteten Wert
|
|
243
|
+
überschreibt.
|
|
244
|
+
- mDNS-Discovery-Handler gegen Exceptions durch fremde/kaputte Netzwerkpakete
|
|
245
|
+
abgesichert (try/catch je Service-Event statt nur um die Subscription herum).
|
|
246
|
+
- Neues Geräte-Feld "Art" (Storen/Licht/unbekannt) steuert Standard-Objektrollen
|
|
247
|
+
(`level.blind`, `button.stop`, `button.open.blind`, `button.close.blind` bzw.
|
|
248
|
+
`level.dimmer`) für bessere VIS-/Smart-Home-Integration.
|
|
249
|
+
- Strukturierte `native`-Metadaten (Host, Kanalnummer, Art) an Geräte-/Kanal-Objekten.
|
|
250
|
+
|
|
251
|
+
### 0.1.0 (2026-07-07)
|
|
252
|
+
- Erste Version: Kanalsteuerung, Kanalzustand/-beschreibung, Geräte-/Netzwerkinfo,
|
|
253
|
+
Systembefehle, Standort/NTP/Datum, Sammelbefehle für Hagelalarm, mDNS-Discovery.
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT License
|
|
258
|
+
|
|
259
|
+
Copyright (c) 2026 Stefan Bühler
|
|
260
|
+
|
|
261
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
262
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
263
|
+
in the Software without restriction, including without limitation the rights
|
|
264
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
265
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
266
|
+
furnished to do so, subject to the following conditions:
|
|
267
|
+
|
|
268
|
+
The above copyright notice and this permission notice shall be included in all
|
|
269
|
+
copies or substantial portions of the Software.
|
|
270
|
+
|
|
271
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
272
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
273
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
274
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
275
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
276
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
277
|
+
SOFTWARE.
|
package/i18n/de.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Host fehlt",
|
|
17
|
+
"validateHostInvalidChars": "Host \"%s\" enthält ungültige Zeichen (kein http://, keine Leerzeichen, kein Port)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" ist keine gültige IPv4-Adresse",
|
|
19
|
+
"validateIdInvalidChars": "ID \"%s\" enthält ungültige Zeichen (erlaubt: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Kanäle \"%s\" ungültig (1-4)",
|
|
21
|
+
"validateKindInvalid": "Art \"%s\" ungültig (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Laufzeit \"%s\" ungültig (0-300s)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Laufzeit/Kanal \"%s\": maximal 4 Werte",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Laufzeit/Kanal \"%s\": Wert \"%s\" ungültig (0-300, ganzzahlig)",
|
|
25
|
+
"validateTiltPulseInvalid": "Kipp-Impuls \"%s\" ungültig (0-5000ms)",
|
|
26
|
+
"validatePollIntervalInvalid": "Poll-Intervall \"%s\" ungültig (5-3600s)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (sanitisiert \"%s\") ist doppelt vergeben",
|
|
28
|
+
"validateHostDuplicate": "Host \"%s\" ist doppelt konfiguriert"
|
|
29
|
+
}
|
package/i18n/en.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Host missing",
|
|
17
|
+
"validateHostInvalidChars": "Host \"%s\" contains invalid characters (no http://, no spaces, no port)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" is not a valid IPv4 address",
|
|
19
|
+
"validateIdInvalidChars": "ID \"%s\" contains invalid characters (allowed: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Channels \"%s\" invalid (1-4)",
|
|
21
|
+
"validateKindInvalid": "Type \"%s\" invalid (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Runtime \"%s\" invalid (0-300s)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Runtime/channel \"%s\" maximum 4 values",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Runtime/channel \"%s\": value \"%s\" invalid (0-300, integer)",
|
|
25
|
+
"validateTiltPulseInvalid": "Tilt pulse \"%s\" invalid (0-5000ms)",
|
|
26
|
+
"validatePollIntervalInvalid": "Poll interval \"%s\" invalid (5-3600s)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (sanitized \"%s\") is assigned twice",
|
|
28
|
+
"validateHostDuplicate": "Host \"%s\" is configured twice"
|
|
29
|
+
}
|
package/i18n/es.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Falta el host",
|
|
17
|
+
"validateHostInvalidChars": "El host \"%s\" contiene caracteres inválidos (sin http://, sin espacios, sin puerto)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" no es una dirección IPv4 válida",
|
|
19
|
+
"validateIdInvalidChars": "El ID \"%s\" contiene caracteres inválidos (permitido: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Canales \"%s\" no válido (1-4)",
|
|
21
|
+
"validateKindInvalid": "Tipo \"%s\" no válido (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Tiempo de funcionamiento \"%s\" no válido (0-300s)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Tiempo de funcionamiento/canal \"%s\": máximo 4 valores",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Tiempo de funcionamiento/canal \"%s\": valor \"%s\" no válido (0-300, entero)",
|
|
25
|
+
"validateTiltPulseInvalid": "Impulso de inclinación \"%s\" no válido (0-5000ms)",
|
|
26
|
+
"validatePollIntervalInvalid": "Intervalo de consulta \"%s\" no válido (5-3600s)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (saneado \"%s\") asignado dos veces",
|
|
28
|
+
"validateHostDuplicate": "Host \"%s\" configurado dos veces"
|
|
29
|
+
}
|
package/i18n/fr.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Hôte manquant",
|
|
17
|
+
"validateHostInvalidChars": "L'hôte \"%s\" contient des caractères invalides (pas de http://, pas d'espaces, pas de port)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" n'est pas une adresse IPv4 valide",
|
|
19
|
+
"validateIdInvalidChars": "L'ID \"%s\" contient des caractères invalides (autorisés : a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Canaux \"%s\" invalide (1-4)",
|
|
21
|
+
"validateKindInvalid": "Type \"%s\" invalide (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Durée \"%s\" invalide (0-300s)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Durée/canal \"%s\" : maximum 4 valeurs",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Durée/canal \"%s\" : valeur \"%s\" invalide (0-300, entier)",
|
|
25
|
+
"validateTiltPulseInvalid": "Impulsion d'inclinaison \"%s\" invalide (0-5000ms)",
|
|
26
|
+
"validatePollIntervalInvalid": "Intervalle d'interrogation \"%s\" invalide (5-3600s)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (nettoyé \"%s\") attribué deux fois",
|
|
28
|
+
"validateHostDuplicate": "Hôte \"%s\" configuré deux fois"
|
|
29
|
+
}
|
package/i18n/it.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Host mancante",
|
|
17
|
+
"validateHostInvalidChars": "L'host \"%s\" contiene caratteri non validi (nessun http://, nessuno spazio, nessuna porta)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" non è un indirizzo IPv4 valido",
|
|
19
|
+
"validateIdInvalidChars": "L'ID \"%s\" contiene caratteri non validi (consentiti: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Canali \"%s\" non validi (1-4)",
|
|
21
|
+
"validateKindInvalid": "Tipo \"%s\" non valido (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Tempo di funzionamento \"%s\" non valido (0-300s)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Tempo di funzionamento/canale \"%s\": massimo 4 valori",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Tempo di funzionamento/canale \"%s\": valore \"%s\" non valido (0-300, intero)",
|
|
25
|
+
"validateTiltPulseInvalid": "Impulso di inclinazione \"%s\" non valido (0-5000ms)",
|
|
26
|
+
"validatePollIntervalInvalid": "Intervallo di polling \"%s\" non valido (5-3600s)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (sanificato \"%s\") assegnato due volte",
|
|
28
|
+
"validateHostDuplicate": "Host \"%s\" configurato due volte"
|
|
29
|
+
}
|
package/i18n/nl.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Host ontbreekt",
|
|
17
|
+
"validateHostInvalidChars": "Host \"%s\" bevat ongeldige tekens (geen http://, geen spaties, geen poort)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" is geen geldig IPv4-adres",
|
|
19
|
+
"validateIdInvalidChars": "ID \"%s\" bevat ongeldige tekens (toegestaan: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Kanalen \"%s\" ongeldig (1-4)",
|
|
21
|
+
"validateKindInvalid": "Type \"%s\" ongeldig (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Looptijd \"%s\" ongeldig (0-300s)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Looptijd/kanaal \"%s\": maximaal 4 waarden",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Looptijd/kanaal \"%s\": waarde \"%s\" ongeldig (0-300, geheel getal)",
|
|
25
|
+
"validateTiltPulseInvalid": "Kantelpuls \"%s\" ongeldig (0-5000ms)",
|
|
26
|
+
"validatePollIntervalInvalid": "Poll-interval \"%s\" ongeldig (5-3600s)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (opgeschoond \"%s\") dubbel toegewezen",
|
|
28
|
+
"validateHostDuplicate": "Host \"%s\" dubbel geconfigureerd"
|
|
29
|
+
}
|
package/i18n/pl.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Brak hosta",
|
|
17
|
+
"validateHostInvalidChars": "Host \"%s\" zawiera nieprawidłowe znaki (bez http://, bez spacji, bez portu)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" nie jest prawidłowym adresem IPv4",
|
|
19
|
+
"validateIdInvalidChars": "ID \"%s\" zawiera nieprawidłowe znaki (dozwolone: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Kanały \"%s\" nieprawidłowe (1-4)",
|
|
21
|
+
"validateKindInvalid": "Typ \"%s\" nieprawidłowy (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Czas pracy \"%s\" nieprawidłowy (0-300s)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Czas pracy/kanał \"%s\": maksymalnie 4 wartości",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Czas pracy/kanał \"%s\": wartość \"%s\" nieprawidłowa (0-300, liczba całkowita)",
|
|
25
|
+
"validateTiltPulseInvalid": "Impuls przechyłu \"%s\" nieprawidłowy (0-5000ms)",
|
|
26
|
+
"validatePollIntervalInvalid": "Interwał odpytywania \"%s\" nieprawidłowy (5-3600s)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (oczyszczone \"%s\") przypisane dwukrotnie",
|
|
28
|
+
"validateHostDuplicate": "Host \"%s\" skonfigurowany dwukrotnie"
|
|
29
|
+
}
|
package/i18n/pt.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Host em falta",
|
|
17
|
+
"validateHostInvalidChars": "O host \"%s\" contém caracteres inválidos (sem http://, sem espaços, sem porta)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" não é um endereço IPv4 válido",
|
|
19
|
+
"validateIdInvalidChars": "O ID \"%s\" contém caracteres inválidos (permitido: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Canais \"%s\" inválido (1-4)",
|
|
21
|
+
"validateKindInvalid": "Tipo \"%s\" inválido (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Tempo de funcionamento \"%s\" inválido (0-300s)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Tempo de funcionamento/canal \"%s\": máximo 4 valores",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Tempo de funcionamento/canal \"%s\": valor \"%s\" inválido (0-300, inteiro)",
|
|
25
|
+
"validateTiltPulseInvalid": "Impulso de inclinação \"%s\" inválido (0-5000ms)",
|
|
26
|
+
"validatePollIntervalInvalid": "Intervalo de consulta \"%s\" inválido (5-3600s)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (sanitizado \"%s\") atribuído duas vezes",
|
|
28
|
+
"validateHostDuplicate": "Host \"%s\" configurado duas vezes"
|
|
29
|
+
}
|
package/i18n/ru.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Хост отсутствует",
|
|
17
|
+
"validateHostInvalidChars": "Хост \"%s\" содержит недопустимые символы (без http://, без пробелов, без порта)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" не является допустимым IPv4-адресом",
|
|
19
|
+
"validateIdInvalidChars": "ID \"%s\" содержит недопустимые символы (разрешено: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Каналы \"%s\" недопустимы (1-4)",
|
|
21
|
+
"validateKindInvalid": "Тип \"%s\" недопустим (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Время работы \"%s\" недопустимо (0-300с)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Время работы/канал \"%s\": максимум 4 значения",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Время работы/канал \"%s\": значение \"%s\" недопустимо (0-300, целое число)",
|
|
25
|
+
"validateTiltPulseInvalid": "Импульс наклона \"%s\" недопустим (0-5000мс)",
|
|
26
|
+
"validatePollIntervalInvalid": "Интервал опроса \"%s\" недопустим (5-3600с)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (очищено \"%s\") назначен дважды",
|
|
28
|
+
"validateHostDuplicate": "Хост \"%s\" настроен дважды"
|
|
29
|
+
}
|
package/i18n/uk.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "Відсутній хост",
|
|
17
|
+
"validateHostInvalidChars": "Хост \"%s\" містить недопустимі символи (без http://, без пробілів, без порту)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" не є дійсною IPv4-адресою",
|
|
19
|
+
"validateIdInvalidChars": "ID \"%s\" містить недопустимі символи (дозволено: a-z, 0-9, _, -)",
|
|
20
|
+
"validateChannelsInvalid": "Канали \"%s\" недійсні (1-4)",
|
|
21
|
+
"validateKindInvalid": "Тип \"%s\" недійсний (unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "Час роботи \"%s\" недійсний (0-300с)",
|
|
23
|
+
"validateRuntimePerChannelMax": "Час роботи/канал \"%s\": максимум 4 значення",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "Час роботи/канал \"%s\": значення \"%s\" недійсне (0-300, ціле число)",
|
|
25
|
+
"validateTiltPulseInvalid": "Імпульс нахилу \"%s\" недійсний (0-5000мс)",
|
|
26
|
+
"validatePollIntervalInvalid": "Інтервал опитування \"%s\" недійсний (5-3600с)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\" (очищено \"%s\") призначено двічі",
|
|
28
|
+
"validateHostDuplicate": "Хост \"%s\" налаштовано двічі"
|
|
29
|
+
}
|
package/i18n/zh-cn.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
"validateHostMissing": "缺少主机",
|
|
17
|
+
"validateHostInvalidChars": "主机 \"%s\" 包含无效字符(不含 http://、不含空格、不含端口)",
|
|
18
|
+
"validateHostInvalidIPv4": "\"%s\" 不是有效的 IPv4 地址",
|
|
19
|
+
"validateIdInvalidChars": "ID \"%s\" 包含无效字符(允许:a-z、0-9、_、-)",
|
|
20
|
+
"validateChannelsInvalid": "通道 \"%s\" 无效(1-4)",
|
|
21
|
+
"validateKindInvalid": "类型 \"%s\" 无效(unknown/blind/light)",
|
|
22
|
+
"validateRuntimeInvalid": "运行时间 \"%s\" 无效(0-300秒)",
|
|
23
|
+
"validateRuntimePerChannelMax": "运行时间/通道 \"%s\":最多 4 个值",
|
|
24
|
+
"validateRuntimePerChannelValueInvalid": "运行时间/通道 \"%s\":值 \"%s\" 无效(0-300,整数)",
|
|
25
|
+
"validateTiltPulseInvalid": "倾斜脉冲 \"%s\" 无效(0-5000毫秒)",
|
|
26
|
+
"validatePollIntervalInvalid": "轮询间隔 \"%s\" 无效(5-3600秒)",
|
|
27
|
+
"validateIdDuplicate": "ID \"%s\"(清理后 \"%s\")被分配两次",
|
|
28
|
+
"validateHostDuplicate": "主机 \"%s\" 被配置两次"
|
|
29
|
+
}
|
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.12",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.0.12": {
|
|
7
|
+
"en": "Fix a real i18n gap found during independent end-to-end testing of v1.0.11: the CSV import report wrapper text (csvRowError) was localized via I18n.translate(), but the embedded field-validation messages from validateDeviceRow() (host missing, invalid IP, etc.) were still hard-coded English strings from v1.0.10, producing a mixed-language sentence for non-English admin users (e.g. \"Zeile 3: Host missing\" in German). validateDeviceRow() now returns structured {key, args} entries instead of pre-rendered strings, with two explicit renderers: one that always renders English (for this.log.*() call sites, which must stay English regardless of system language) and one that uses I18n.translate() (for the localized CSV import UI report). Added 13 new i18n keys across all 11 languages. Also added the missing README_de.md entry to package.json's \"files\" allowlist - npm auto-includes README.md in every published package, but NOT README_de.md, so it was silently absent from the published npm tarball despite being present in the GitHub repo. Verified end-to-end by loading the actual @iobroker/adapter-core I18n module directly and calling I18n.translate() with real interpolated values in English, German, French, and Chinese, confirming log output stays English regardless of the active UI language, and by downloading and extracting the real published npm tarball to confirm both i18n/ and admin/i18n/ ship correctly.",
|
|
8
|
+
"de": "Behebt eine echte i18n-Lücke, gefunden bei einem unabhängigen End-to-End-Test von v1.0.11: Der Wrapper-Text des CSV-Import-Berichts (csvRowError) war über I18n.translate() lokalisiert, aber die eingebetteten Feld-Validierungsmeldungen aus validateDeviceRow() (Host fehlt, ungültige IP, etc.) waren noch die hartcodierten englischen Strings aus v1.0.10 - das ergab einen sprachlich gemischten Satz für nicht-englische Admin-Nutzer (z. B. \"Zeile 3: Host missing\" auf Deutsch). validateDeviceRow() gibt jetzt strukturierte {key, args}-Einträge statt fertiger Strings zurück, mit zwei expliziten Renderern: einer rendert immer Englisch (für this.log.*()-Aufrufstellen, die unabhängig von der Systemsprache Englisch bleiben müssen), einer nutzt I18n.translate() (für den lokalisierten CSV-Import-UI-Bericht). 13 neue i18n-Keys in allen 11 Sprachen ergänzt. Zudem den fehlenden README_de.md-Eintrag in package.json's \"files\"-Allowlist ergänzt - npm packt README.md automatisch in jedes veröffentlichte Paket, aber NICHT README_de.md, weshalb diese trotz Vorhandensein im GitHub-Repo im veröffentlichten npm-Tarball stillschweigend fehlte. End-to-End verifiziert durch direktes Laden des echten @iobroker/adapter-core I18n-Moduls und Aufrufe von I18n.translate() mit echten interpolierten Werten auf Englisch, Deutsch, Französisch und Chinesisch, bestätigt dass Log-Ausgabe unabhängig von der aktiven UI-Sprache Englisch bleibt, sowie durch Herunterladen und Entpacken des echten veröffentlichten npm-Tarballs zur Bestätigung, dass sowohl i18n/ als auch admin/i18n/ korrekt mitgeliefert werden.",
|
|
9
|
+
"ru": "Исправлен реальный пробел в i18n, обнаруженный при независимом сквозном тестировании v1.0.11: встроенные сообщения проверки полей оставались на английском языке даже в локализованном отчёте импорта CSV. Добавлено 13 новых ключей i18n на всех 11 языках. Также добавлен отсутствующий README_de.md в список файлов npm-пакета.",
|
|
10
|
+
"pt": "Corrigida uma lacuna real de i18n encontrada durante testes end-to-end independentes da v1.0.11: as mensagens de validação de campo incorporadas permaneciam em inglês mesmo no relatório localizado de importação CSV. Adicionadas 13 novas chaves i18n em todos os 11 idiomas. Também adicionado o README_de.md em falta à lista de ficheiros do pacote npm.",
|
|
11
|
+
"nl": "Een echt i18n-hiaat opgelost, gevonden tijdens onafhankelijke end-to-end tests van v1.0.11: de ingesloten veldvalidatieberichten bleven Engels, zelfs in het gelokaliseerde CSV-importrapport. 13 nieuwe i18n-sleutels toegevoegd in alle 11 talen. Ook het ontbrekende README_de.md toegevoegd aan de bestandslijst van het npm-pakket.",
|
|
12
|
+
"fr": "Correction d'une véritable lacune i18n découverte lors de tests de bout en bout indépendants de la v1.0.11 : les messages de validation de champs intégrés restaient en anglais même dans le rapport d'importation CSV localisé. Ajout de 13 nouvelles clés i18n dans les 11 langues. Ajout également du README_de.md manquant à la liste des fichiers du paquet npm.",
|
|
13
|
+
"it": "Corretta una vera lacuna i18n trovata durante test end-to-end indipendenti della v1.0.11: i messaggi di convalida dei campi incorporati rimanevano in inglese anche nel report di importazione CSV localizzato. Aggiunte 13 nuove chiavi i18n in tutte le 11 lingue. Aggiunto anche il README_de.md mancante all'elenco dei file del pacchetto npm.",
|
|
14
|
+
"es": "Corregida una brecha real de i18n encontrada durante pruebas end-to-end independientes de la v1.0.11: los mensajes de validación de campo incrustados permanecían en inglés incluso en el informe de importación CSV localizado. Se añadieron 13 nuevas claves i18n en los 11 idiomas. También se añadió el README_de.md faltante a la lista de archivos del paquete npm.",
|
|
15
|
+
"pl": "Naprawiono rzeczywistą lukę i18n znalezioną podczas niezależnych testów end-to-end wersji 1.0.11: osadzone komunikaty walidacji pól pozostawały po angielsku nawet w zlokalizowanym raporcie importu CSV. Dodano 13 nowych kluczy i18n we wszystkich 11 językach. Dodano także brakujący plik README_de.md do listy plików pakietu npm.",
|
|
16
|
+
"uk": "Виправлено реальну прогалину i18n, знайдену під час незалежного наскрізного тестування v1.0.11: вбудовані повідомлення валідації полів залишалися англійською навіть у локалізованому звіті імпорту CSV. Додано 13 нових ключів i18n усіма 11 мовами. Також додано відсутній README_de.md до списку файлів npm-пакета.",
|
|
17
|
+
"zh-cn": "修复了在 v1.0.11 独立端到端测试中发现的真实 i18n 缺口:即使在本地化的 CSV 导入报告中,嵌入的字段验证消息仍为英文。在所有 11 种语言中添加了 13 个新的 i18n 键。还将缺失的 README_de.md 添加到 npm 包的文件列表中。"
|
|
18
|
+
},
|
|
19
|
+
"1.0.11": {
|
|
20
|
+
"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.",
|
|
21
|
+
"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.",
|
|
22
|
+
"ru": "Исправление: реализована полноценная многоязычная поддержка UI-текста через официальный модуль I18n adapter-core вместо только английского. Также исправлена ошибка упаковки (папка i18n отсутствовала в списке files package.json).",
|
|
23
|
+
"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).",
|
|
24
|
+
"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).",
|
|
25
|
+
"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).",
|
|
26
|
+
"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).",
|
|
27
|
+
"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).",
|
|
28
|
+
"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).",
|
|
29
|
+
"uk": "Виправлення: реалізовано повноцінну багатомовну підтримку тексту інтерфейсу через офіційний модуль I18n adapter-core замість лише англійської. Також виправлено помилку пакування (папка i18n була відсутня у списку files package.json).",
|
|
30
|
+
"zh-cn": "修复:通过官方 adapter-core I18n 模块为界面文本实现了完整的多语言支持,而不仅仅是英文。同时修复了一个打包错误(i18n 文件夹未包含在 package.json 的 files 列表中)。"
|
|
31
|
+
},
|
|
6
32
|
"1.0.10": {
|
|
7
33
|
"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.",
|
|
8
34
|
"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.",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Poprawka: validatorErrorText dla id/host/travelTimeSecCh przywrócono do zwykłych ciągów znaków - schemat admin jsonConfig wymaga, aby validatorErrorText był ciągiem znaków, a nie obiektem i18n (E5512). Konwersja i18n w 1.0.5 przechodziła linter, ale nie walidację schematu.",
|
|
68
94
|
"uk": "Виправлення: validatorErrorText для id/host/travelTimeSecCh повернуто до звичайних рядків - схема admin jsonConfig вимагає, щоб validatorErrorText був рядком, а не об'єктом i18n (E5512). Перетворення на i18n у версії 1.0.5 проходило лінтер, але не проходило валідацію схеми.",
|
|
69
95
|
"zh-cn": "修复:将 id/host/travelTimeSecCh 的 validatorErrorText 恢复为普通字符串——管理端 jsonConfig 架构要求 validatorErrorText 必须是字符串,而非 i18n 对象(E5512)。1.0.5 中的 i18n 转换通过了 lint 检查,但未通过架构校验。"
|
|
70
|
-
},
|
|
71
|
-
"1.0.5": {
|
|
72
|
-
"en": "Admin UI: the three validatorErrorText messages are now inline i18n objects instead of plain English strings; raised the declared minimum versions for bonjour-service and eslint (both already covered by the existing caret ranges). No functional changes.",
|
|
73
|
-
"de": "Admin-UI: Die drei validatorErrorText-Meldungen sind jetzt Inline-i18n-Objekte statt reiner englischer Texte; die deklarierten Mindestversionen für bonjour-service und eslint wurden angehoben (beide waren durch die bestehenden Caret-Ranges ohnehin abgedeckt). Keine funktionalen Änderungen.",
|
|
74
|
-
"ru": "Интерфейс администратора: три сообщения validatorErrorText теперь являются встроенными объектами i18n вместо простых английских строк; повышены объявленные минимальные версии bonjour-service и eslint (обе и так покрывались существующими caret-диапазонами). Без функциональных изменений.",
|
|
75
|
-
"pt": "Interface de administração: as três mensagens validatorErrorText são agora objetos i18n inline em vez de texto simples em inglês; foram aumentadas as versões mínimas declaradas de bonjour-service e eslint (ambas já cobertas pelos intervalos caret existentes). Sem alterações funcionais.",
|
|
76
|
-
"nl": "Admin-UI: de drie validatorErrorText-meldingen zijn nu inline i18n-objecten in plaats van gewone Engelse tekst; de gedeclareerde minimumversies van bonjour-service en eslint zijn verhoogd (beide vielen al binnen de bestaande caret-ranges). Geen functionele wijzigingen.",
|
|
77
|
-
"fr": "Interface d'administration : les trois messages validatorErrorText sont désormais des objets i18n en ligne au lieu de simples chaînes anglaises ; les versions minimales déclarées de bonjour-service et eslint ont été relevées (toutes deux étaient déjà couvertes par les plages caret existantes). Aucune modification fonctionnelle.",
|
|
78
|
-
"it": "Interfaccia di amministrazione: i tre messaggi validatorErrorText sono ora oggetti i18n inline invece di semplici stringhe in inglese; sono state alzate le versioni minime dichiarate di bonjour-service ed eslint (entrambe già coperte dagli intervalli caret esistenti). Nessuna modifica funzionale.",
|
|
79
|
-
"es": "Interfaz de administración: los tres mensajes validatorErrorText son ahora objetos i18n en línea en lugar de simples cadenas en inglés; se han elevado las versiones mínimas declaradas de bonjour-service y eslint (ambas ya cubiertas por los rangos caret existentes). Sin cambios funcionales.",
|
|
80
|
-
"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.",
|
|
81
|
-
"uk": "Інтерфейс адміністратора: три повідомлення validatorErrorText тепер є вбудованими об'єктами i18n замість простих англійських рядків; підвищено оголошені мінімальні версії bonjour-service та eslint (обидві й так покривалися наявними caret-діапазонами). Без функціональних змін.",
|
|
82
|
-
"zh-cn": "管理界面:三条 validatorErrorText 提示现在使用内联 i18n 对象,而非纯英文字符串;提高了 bonjour-service 和 eslint 的声明最低版本(两者原本就在现有 caret 范围内)。无功能性更改。"
|
|
83
|
-
},
|
|
84
|
-
"1.0.4": {
|
|
85
|
-
"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.",
|
|
86
|
-
"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.",
|
|
87
|
-
"ru": "Только документация: добавлена ссылка Buy Me a Coffee рядом со значком пожертвования PayPal в README.md/README_de.md и в npm-пакете (поле funding). Без функциональных изменений.",
|
|
88
|
-
"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.",
|
|
89
|
-
"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.",
|
|
90
|
-
"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.",
|
|
91
|
-
"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.",
|
|
92
|
-
"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.",
|
|
93
|
-
"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.",
|
|
94
|
-
"uk": "Лише документація: додано посилання Buy Me a Coffee поруч зі значком пожертв PayPal у README.md/README_de.md та в пакеті npm (поле funding). Без функціональних змін.",
|
|
95
|
-
"zh-cn": "仅文档:在 README.md/README_de.md 及 npm 包(funding 字段)中,于 PayPal 捐赠徽章旁添加了 Buy Me a Coffee 链接。无功能变更。"
|
|
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,11 +418,11 @@ 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(
|
|
421
|
+
if (seenIds.has(sanId)) errs.push({ key: 'validateIdDuplicate', args: [d.id, sanId] });
|
|
413
422
|
const hostKey = String(d.host).trim().toLowerCase();
|
|
414
|
-
if (seenHosts.has(hostKey)) errs.push(
|
|
423
|
+
if (seenHosts.has(hostKey)) errs.push({ key: 'validateHostDuplicate', args: [d.host] });
|
|
415
424
|
if (errs.length) {
|
|
416
|
-
this.log.error(`Device "${d.name || d.id || d.host}" skipped: ${
|
|
425
|
+
this.log.error(`Device "${d.name || d.id || d.host}" skipped: ${this.renderValidationErrorsEn(errs)}`);
|
|
417
426
|
continue;
|
|
418
427
|
}
|
|
419
428
|
seenIds.add(sanId);
|
|
@@ -483,54 +492,85 @@ class Zeptrion extends utils.Adapter {
|
|
|
483
492
|
|
|
484
493
|
/** Validiert eine Geräte-Zeile aus der Konfiguration/dem CSV-Import. Gibt eine
|
|
485
494
|
* Liste menschenlesbarer Fehler zurück (leer = gültig). */
|
|
495
|
+
// Field-validation templates, always in English regardless of the current I18n language -
|
|
496
|
+
// used exclusively for this.log.*() output, which must stay English per the adapter checklist.
|
|
497
|
+
// Keys match the corresponding entries in i18n/<lang>.json used for the localized UI path
|
|
498
|
+
// (CSV import report) - see validateDeviceRow()/renderValidationErrorsEn()/renderValidationErrorsLocalized().
|
|
499
|
+
static VALIDATION_TEMPLATES_EN = {
|
|
500
|
+
validateHostMissing: () => 'Host missing',
|
|
501
|
+
validateHostInvalidChars: (host) => `Host "${host}" contains invalid characters (no http://, no spaces, no port)`,
|
|
502
|
+
validateHostInvalidIPv4: (host) => `"${host}" is not a valid IPv4 address`,
|
|
503
|
+
validateIdInvalidChars: (id) => `ID "${id}" contains invalid characters (allowed: a-z, 0-9, _, -)`,
|
|
504
|
+
validateChannelsInvalid: (channels) => `Channels "${channels}" invalid (1-4)`,
|
|
505
|
+
validateKindInvalid: (kind) => `Type "${kind}" invalid (unknown/blind/light)`,
|
|
506
|
+
validateRuntimeInvalid: (t) => `Runtime "${t}" invalid (0-300s)`,
|
|
507
|
+
validateRuntimePerChannelMax: (v) => `Runtime/channel "${v}" maximum 4 values`,
|
|
508
|
+
validateRuntimePerChannelValueInvalid: (v, p) => `Runtime/channel "${v}": value "${p}" invalid (0-300, integer)`,
|
|
509
|
+
validateTiltPulseInvalid: (t) => `Tilt pulse "${t}" invalid (0-5000ms)`,
|
|
510
|
+
validatePollIntervalInvalid: (t) => `Poll interval "${t}" invalid (5-3600s)`,
|
|
511
|
+
validateIdDuplicate: (id, sanId) => `ID "${id}" (sanitized "${sanId}") is assigned twice`,
|
|
512
|
+
validateHostDuplicate: (host) => `Host "${host}" is configured twice`,
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// Renders validateDeviceRow() entries in English only - for this.log.*() call sites.
|
|
516
|
+
renderValidationErrorsEn(errs) {
|
|
517
|
+
return errs.map(e => Zeptrion.VALIDATION_TEMPLATES_EN[e.key](...e.args)).join('; ');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Renders validateDeviceRow() entries in the current adapter language - for UI-facing
|
|
521
|
+
// sendTo() responses (e.g. the CSV import report).
|
|
522
|
+
renderValidationErrorsLocalized(errs) {
|
|
523
|
+
return errs.map(e => I18n.translate(e.key, ...e.args)).join('; ');
|
|
524
|
+
}
|
|
525
|
+
|
|
486
526
|
validateDeviceRow(d) {
|
|
487
527
|
const errs = [];
|
|
488
528
|
const host = String(d.host || '').trim();
|
|
489
529
|
if (!host) {
|
|
490
|
-
errs.push('
|
|
530
|
+
errs.push({ key: 'validateHostMissing', args: [] });
|
|
491
531
|
} else if (!/^[a-zA-Z0-9.-]+$/.test(host)) {
|
|
492
|
-
errs.push(
|
|
532
|
+
errs.push({ key: 'validateHostInvalidChars', args: [host] });
|
|
493
533
|
} else if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
494
534
|
const octets = host.split('.').map(Number);
|
|
495
535
|
if (octets.length !== 4 || octets.some(o => o < 0 || o > 255)) {
|
|
496
|
-
errs.push(
|
|
536
|
+
errs.push({ key: 'validateHostInvalidIPv4', args: [host] });
|
|
497
537
|
}
|
|
498
538
|
}
|
|
499
539
|
if (d.id && !/^[a-zA-Z0-9_-]+$/.test(String(d.id))) {
|
|
500
|
-
errs.push(
|
|
540
|
+
errs.push({ key: 'validateIdInvalidChars', args: [d.id] });
|
|
501
541
|
}
|
|
502
542
|
const ch = parseInt(d.channels, 10);
|
|
503
543
|
if (d.channels !== undefined && d.channels !== '' && (isNaN(ch) || ch < 1 || ch > 4)) {
|
|
504
|
-
errs.push(
|
|
544
|
+
errs.push({ key: 'validateChannelsInvalid', args: [d.channels] });
|
|
505
545
|
}
|
|
506
546
|
if (d.kind !== undefined && d.kind !== '' && !['unknown', 'blind', 'light'].includes(String(d.kind))) {
|
|
507
|
-
errs.push(
|
|
547
|
+
errs.push({ key: 'validateKindInvalid', args: [d.kind] });
|
|
508
548
|
}
|
|
509
549
|
const tt = parseInt(d.travelTimeSec, 10);
|
|
510
550
|
if (d.travelTimeSec !== undefined && d.travelTimeSec !== '' && (isNaN(tt) || tt < 0 || tt > 300)) {
|
|
511
|
-
errs.push(
|
|
551
|
+
errs.push({ key: 'validateRuntimeInvalid', args: [d.travelTimeSec] });
|
|
512
552
|
}
|
|
513
553
|
if (d.travelTimeSecCh !== undefined && String(d.travelTimeSecCh).trim() !== '') {
|
|
514
554
|
const parts = String(d.travelTimeSecCh).split(',').map(s => s.trim());
|
|
515
555
|
if (parts.length > 4) {
|
|
516
|
-
errs.push(
|
|
556
|
+
errs.push({ key: 'validateRuntimePerChannelMax', args: [d.travelTimeSecCh] });
|
|
517
557
|
}
|
|
518
558
|
for (const p of parts) {
|
|
519
559
|
if (p === '') continue; // leerer Eintrag = Fallback auf travelTimeSec
|
|
520
560
|
const v = parseInt(p, 10);
|
|
521
561
|
if (isNaN(v) || v < 0 || v > 300 || String(v) !== p) {
|
|
522
|
-
errs.push(
|
|
562
|
+
errs.push({ key: 'validateRuntimePerChannelValueInvalid', args: [d.travelTimeSecCh, p] });
|
|
523
563
|
break;
|
|
524
564
|
}
|
|
525
565
|
}
|
|
526
566
|
}
|
|
527
567
|
const tp = parseInt(d.tiltTimeMs, 10);
|
|
528
568
|
if (d.tiltTimeMs !== undefined && d.tiltTimeMs !== '' && (isNaN(tp) || tp < 0 || tp > 5000)) {
|
|
529
|
-
errs.push(
|
|
569
|
+
errs.push({ key: 'validateTiltPulseInvalid', args: [d.tiltTimeMs] });
|
|
530
570
|
}
|
|
531
571
|
const pi = parseInt(d.pollInterval, 10);
|
|
532
572
|
if (d.pollInterval !== undefined && d.pollInterval !== '' && (isNaN(pi) || pi < 5 || pi > 3600)) {
|
|
533
|
-
errs.push(
|
|
573
|
+
errs.push({ key: 'validatePollIntervalInvalid', args: [d.pollInterval] });
|
|
534
574
|
}
|
|
535
575
|
return errs;
|
|
536
576
|
}
|
|
@@ -1742,7 +1782,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1742
1782
|
try {
|
|
1743
1783
|
const csv = String((obj.message && obj.message.csv) || '').trim();
|
|
1744
1784
|
if (!csv) {
|
|
1745
|
-
if (obj.callback) this.sendTo(obj.from, obj.command, { result:
|
|
1785
|
+
if (obj.callback) this.sendTo(obj.from, obj.command, { result: I18n.translate('csvFieldEmpty') }, obj.callback);
|
|
1746
1786
|
return;
|
|
1747
1787
|
}
|
|
1748
1788
|
const delim = csv.includes(';') ? ';' : ',';
|
|
@@ -1775,9 +1815,9 @@ class Zeptrion extends utils.Adapter {
|
|
|
1775
1815
|
if (['licht', 'lampe'].includes(row.kind)) row.kind = 'light';
|
|
1776
1816
|
|
|
1777
1817
|
const errs = this.validateDeviceRow(row);
|
|
1778
|
-
if (existingHosts.has(row.host.toLowerCase())) errs.push('
|
|
1818
|
+
if (existingHosts.has(row.host.toLowerCase())) errs.push({ key: 'hostAlreadyConfigured', args: [] });
|
|
1779
1819
|
if (errs.length) {
|
|
1780
|
-
report.push(
|
|
1820
|
+
report.push(I18n.translate('csvRowError', i + 1, row.host || '?', this.renderValidationErrorsLocalized(errs)));
|
|
1781
1821
|
continue;
|
|
1782
1822
|
}
|
|
1783
1823
|
// ID aus Host ableiten, Kollisionen auflösen
|
|
@@ -1802,7 +1842,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1802
1842
|
});
|
|
1803
1843
|
existingHosts.add(row.host.toLowerCase());
|
|
1804
1844
|
existingIds.add(candidate);
|
|
1805
|
-
report.push(
|
|
1845
|
+
report.push(I18n.translate('csvRowImported', i + 1, row.name || row.host, row.host, candidate));
|
|
1806
1846
|
added++;
|
|
1807
1847
|
}
|
|
1808
1848
|
|
|
@@ -1810,13 +1850,15 @@ class Zeptrion extends utils.Adapter {
|
|
|
1810
1850
|
instObj.native.devices = devices;
|
|
1811
1851
|
await this.setForeignObjectAsync(`system.adapter.${this.namespace}`, instObj);
|
|
1812
1852
|
}
|
|
1813
|
-
const
|
|
1853
|
+
const restartHint = added ? I18n.translate('csvImportRestartHint') : '';
|
|
1854
|
+
const result = I18n.translate('csvImportSummary', added, lines.length, restartHint, report.join('\n'));
|
|
1814
1855
|
this.log.info(`CSV import: ${added}/${lines.length} rows imported`);
|
|
1815
1856
|
if (obj.callback) this.sendTo(obj.from, obj.command, { result }, obj.callback);
|
|
1816
1857
|
} catch (err) {
|
|
1817
|
-
//
|
|
1818
|
-
|
|
1819
|
-
this.log.warn(
|
|
1858
|
+
// Log entry stays English regardless of system language; the UI-facing message
|
|
1859
|
+
// is localized separately via I18n.translate().
|
|
1860
|
+
this.log.warn(`CSV import failed: ${err.message || err}`);
|
|
1861
|
+
const msg = I18n.translate('csvImportFailed', err.message || err);
|
|
1820
1862
|
if (obj.callback) this.sendTo(obj.from, obj.command, { error: msg }, obj.callback);
|
|
1821
1863
|
}
|
|
1822
1864
|
return;
|
|
@@ -1826,7 +1868,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1826
1868
|
const devicesCfg = Array.isArray(this.config.devices) ? this.config.devices : [];
|
|
1827
1869
|
const rows = devicesCfg.filter(d => d && d.host);
|
|
1828
1870
|
if (!rows.length) {
|
|
1829
|
-
if (obj.callback) this.sendTo(obj.from, obj.command, { result:
|
|
1871
|
+
if (obj.callback) this.sendTo(obj.from, obj.command, { result: I18n.translate('noDevicesWithHost') }, obj.callback);
|
|
1830
1872
|
return;
|
|
1831
1873
|
}
|
|
1832
1874
|
const lines = [];
|
|
@@ -1837,7 +1879,7 @@ class Zeptrion extends utils.Adapter {
|
|
|
1837
1879
|
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
|
|
1838
1880
|
const octets = host.split('.').map(Number);
|
|
1839
1881
|
if (octets.some(o => o > 255)) {
|
|
1840
|
-
lines.push(
|
|
1882
|
+
lines.push(I18n.translate('testInvalidIp', label, host));
|
|
1841
1883
|
continue;
|
|
1842
1884
|
}
|
|
1843
1885
|
}
|
|
@@ -1850,18 +1892,18 @@ class Zeptrion extends utils.Adapter {
|
|
|
1850
1892
|
const idData = (rootKey && parsed[rootKey]) || {};
|
|
1851
1893
|
if (String(idData.sys ?? '').toUpperCase() === 'ZEPTRION') {
|
|
1852
1894
|
const m = String(idData.type ?? '').match(/^3340-(\d)-/);
|
|
1853
|
-
const ch = m ?
|
|
1854
|
-
lines.push(
|
|
1895
|
+
const ch = m ? I18n.translate('testChannelsSuffix', m[1]) : '';
|
|
1896
|
+
lines.push(I18n.translate('testDeviceOk', label, host, idData.type ?? '?', ch, idData.sw ?? '?', idData.sn ?? '?'));
|
|
1855
1897
|
} else {
|
|
1856
|
-
lines.push(
|
|
1898
|
+
lines.push(I18n.translate('testNotZeptrion', label, host, idData.sys ?? 'unknown'));
|
|
1857
1899
|
}
|
|
1858
1900
|
} catch (err) {
|
|
1859
1901
|
const code = err.code || (err.message || '').substring(0, 40);
|
|
1860
|
-
lines.push(
|
|
1902
|
+
lines.push(I18n.translate('testUnreachable', label, host, code));
|
|
1861
1903
|
}
|
|
1862
1904
|
}
|
|
1863
1905
|
const result = lines.join('\n');
|
|
1864
|
-
this.log.info(`Device test
|
|
1906
|
+
this.log.info(`Device test: ${lines.length} device(s) checked`);
|
|
1865
1907
|
if (obj.callback) this.sendTo(obj.from, obj.command, { result }, obj.callback);
|
|
1866
1908
|
return;
|
|
1867
1909
|
}
|
|
@@ -1871,10 +1913,10 @@ class Zeptrion extends utils.Adapter {
|
|
|
1871
1913
|
this.log.info('Starting mDNS discovery for zeptrion devices...');
|
|
1872
1914
|
const results = await this.discoverDevices(4000);
|
|
1873
1915
|
const added = await this.mergeDiscoveredDevices(results);
|
|
1874
|
-
//
|
|
1875
|
-
|
|
1876
|
-
`Close and reopen the instance configuration to see them in the table and enable them.`;
|
|
1916
|
+
// Log entry stays English regardless of system language; the UI-facing message
|
|
1917
|
+
// is localized separately via I18n.translate().
|
|
1877
1918
|
this.log.info(`Discovery finished: ${results.length} device(s) found on the network, ${added} newly added (disabled).`);
|
|
1919
|
+
const msg = I18n.translate('discoveryFinished', results.length, added);
|
|
1878
1920
|
if (obj.callback) {
|
|
1879
1921
|
this.sendTo(obj.from, obj.command, { result: msg, devices: results }, obj.callback);
|
|
1880
1922
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zeptrion",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
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,8 +33,10 @@
|
|
|
33
33
|
"admin/",
|
|
34
34
|
"main.js",
|
|
35
35
|
"lib/",
|
|
36
|
+
"i18n/",
|
|
36
37
|
"io-package.json",
|
|
37
|
-
"LICENSE"
|
|
38
|
+
"LICENSE",
|
|
39
|
+
"README_de.md"
|
|
38
40
|
],
|
|
39
41
|
"engines": {
|
|
40
42
|
"node": ">=22"
|