iobroker.mqtt-plus 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 proxy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ <p align="center">
2
+ <img src="admin/mqtt-plus.png" alt="MQTT Plus Logo" width="96">
3
+ </p>
4
+
5
+ <h1 align="center">ioBroker.mqtt-plus</h1>
6
+
7
+ <p align="center">
8
+ <a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/License-MIT-blue"></a>
9
+ <img alt="Version" src="https://img.shields.io/badge/Version-1.6.1-informational">
10
+ <img alt="js-controller" src="https://img.shields.io/badge/js--controller-%3E%3D6.0.11-informational">
11
+ <img alt="Node" src="https://img.shields.io/badge/Node.js-%3E%3D18-informational">
12
+ <img alt="Tests" src="https://img.shields.io/badge/Tests-not%20implemented-yellow">
13
+ </p>
14
+
15
+ **mqtt-plus** ist ein Adapter für [ioBroker](https://www.iobroker.net/), der ioBroker-Datenpunkte
16
+ mit dem Namespace eines bereits installierten MQTT-Client-Adapters (z. B. `mqtt.0`) spiegelt.
17
+ Er legt die nötige Ordnerstruktur automatisch an, konvertiert Werte live, bietet ein
18
+ abgesichertes Web-Dashboard für Backup/Restore und kann Daten zusätzlich per HTTP(S) an einen
19
+ externen Webhook senden.
20
+
21
+ `mqtt-plus` spricht selbst kein MQTT-Protokoll — Verbindung, QoS und `retain` zum eigentlichen
22
+ Broker liegen vollständig beim separat installierten MQTT-Adapter, auf dessen Namespace
23
+ `mqtt-plus` nur liest/schreibt.
24
+
25
+ ## Funktionsumfang
26
+
27
+ * **Bidirektionale Spiegelung** — `out` (IOB → MQTT), `in` (MQTT → IOB) oder `both`.
28
+ * **Topic-Modus pro Mapping** — `single` (ein gemeinsames Topic) oder `dual` nach üblicher
29
+ MQTT-Konvention (Status auf `<topic>`, Befehle auf `<topic>/set`).
30
+ * **Typ-Konvertierung** — Runden, Bool ↔ Zahl oder automatische Erkennung des Zieltyps, in
31
+ beide Richtungen.
32
+ * **Unverfälschte Zeitstempel** — der Spiegel übernimmt `ts`/`lc`/`q` der Quelle statt bei
33
+ jedem Kopiervorgang ein neues "jetzt" zu setzen. Inaktive Quellen (Alter oder Qualität)
34
+ werden erkannt und nicht künstlich frisch gehalten; drei Sync-Modi pro Mapping erlauben
35
+ Ausnahmen für selten meldende Sensoren oder bewusst immer frische Ziele.
36
+ * **Force-Sync** — vergleicht Quelle und Ziel direkt und heilt nur echte Abweichungen, z. B.
37
+ nach einem Neustart oder Verbindungsabbruch.
38
+ * **Ack-Filter pro Mapping** — für Datenpunkte ohne echtes Gerät dahinter
39
+ (`0_userdata.0.*`, `alias.0.*`).
40
+ * **Abgesichertes Web-Dashboard** — Basic-Auth mit Brute-Force-Sperre, optional HTTPS,
41
+ CSRF-/CORS-Schutz. Live-Status, JSON-Struktur-Vorschau, Backup & Restore.
42
+ * **Remote Sync** — sendet die Daten zyklisch oder manuell per HTTP(S) POST an einen externen
43
+ Webhook, Payload frei per Template konfigurierbar, TLS-Prüfung immer aktiv (optionale eigene
44
+ CA für interne/selbstsignierte Ziele).
45
+ * **Admin 7 Ready** — modernes `jsonConfig` mit responsiven Elementen.
46
+
47
+ Die vollständige Konfigurationsanleitung inklusive aller Details zu Topic-Modus, Echo-Schutz,
48
+ Sync-Modus und Remote-Sync-Templates steht in **[admin/readme.md](admin/readme.md)** — das ist
49
+ derselbe Text, den ioBroker Admin auch in der Instanz-Ansicht anzeigt.
50
+
51
+ ## Status der Aufnahme ins offizielle ioBroker-Repository
52
+
53
+ `mqtt-plus` ist derzeit **nicht** Teil des offiziellen ioBroker-`latest`-Repositories und noch
54
+ nicht auf npm veröffentlicht — Installation läuft bis dahin ausschließlich direkt aus diesem
55
+ Git-Repository (siehe [Installation](#installation)). Der offizielle
56
+ [ioBroker Adapter-Checker](https://adapter-check.iobroker.in/) läuft bereits fehlerfrei bis auf
57
+ die Prüfungen, die zwingend eine vorherige npm-Veröffentlichung voraussetzen. Bis zur Aufnahme
58
+ fehlen noch:
59
+
60
+ 1. `npm publish` — Veröffentlichung auf dem npm-Registry.
61
+ 2. Ein Pull Request gegen [ioBroker/ioBroker.repositories](https://github.com/ioBroker/ioBroker.repositories),
62
+ der den Adapter in die `latest`-Liste einträgt.
63
+
64
+ ## Installation
65
+
66
+ Installation direkt aus diesem Repository:
67
+
68
+ **Über die Admin-Oberfläche:**
69
+ Instanzen → "+" → Reiter *Benutzerdefiniert* → GitHub-URL eintragen:
70
+
71
+ ```
72
+ https://github.com/Proxyyy87/ioBroker.mqtt-plus/
73
+ ```
74
+
75
+ **Über die Kommandozeile:**
76
+
77
+ ```bash
78
+ iobroker url https://github.com/Proxyyy87/ioBroker.mqtt-plus/tarball/main mqtt-plus
79
+ ```
80
+
81
+ Beim Installieren aus dem Git-Repository wird TypeScript automatisch kompiliert
82
+ (`npm`-`prepare`-Skript) — ein manueller Build-Schritt ist nicht nötig.
83
+
84
+ Vorausgesetzt wird ein bereits eingerichteter MQTT-Client-Adapter (z. B.
85
+ [ioBroker.mqtt](https://github.com/ioBroker/ioBroker.mqtt)), dessen Namespace `mqtt-plus` als
86
+ Ziel-Präfix verwendet.
87
+
88
+ ## Entwicklung
89
+
90
+ ```bash
91
+ git clone https://github.com/Proxyyy87/ioBroker.mqtt-plus.git
92
+ cd ioBroker.mqtt-plus
93
+ npm install
94
+ npm run build # kompiliert src/*.ts nach build/
95
+ npm run watch # kompiliert bei Änderungen automatisch neu
96
+ ```
97
+
98
+ **Wichtig für Pull Requests:** Der Ordner `build/` wird bewusst mitversioniert (nicht per
99
+ `.gitignore` ausgeschlossen), weil der offizielle ioBroker Adapter-Checker die Datei `main`
100
+ (`build/main.js`) direkt aus dem Repository lädt, ohne vorher `npm install`/`prepare`
101
+ auszuführen. Nach jeder Änderung an `src/*.ts` also `npm run build` laufen lassen und den
102
+ aktualisierten `build/`-Ordner mit committen.
103
+
104
+ Technisch basiert der Adapter auf TypeScript, `@iobroker/adapter-core` und `axios`; das
105
+ Web-Dashboard und der HTTP(S)-Server sind mit Bordmitteln von Node.js (`http`/`https`)
106
+ umgesetzt, ohne zusätzliches Web-Framework. `@iobroker/testing` ist als devDependency
107
+ vorbereitet, aktuell gibt es aber noch keine automatisierten Tests (siehe Test-Badge oben).
108
+
109
+ ## Changelog
110
+
111
+ Die vollständige Versionshistorie steht in [`io-package.json`](io-package.json) unter
112
+ `common.news`. Die letzten Versionen:
113
+
114
+ * **1.6.1** — Sync-Modus pro Mapping (`Standard`, `Jede Meldung weiterreichen`,
115
+ `Force wie vor 1.6.0`); Mapping-Einstellungen als aufklappbare Liste mit zwei kurzen Zeilen
116
+ pro Eintrag statt einer breiten Tabelle.
117
+ * **1.6.0** — Zeitstempel (`ts`/`lc`/`q`) werden beim Spiegeln durchgereicht statt überschrieben;
118
+ Start, Update-Intervall und Force-Sync überspringen inaktive Quellen; Force-Sync schreibt nur
119
+ tatsächlich abweichende Werte; neue Remote-Sync-Platzhalter `%LC%`, `%ACK%`, `%Q%`.
120
+ * **1.5.x** — Topic-Modus `dual` (Status/Befehl auf getrennten Topics) pro Mapping.
121
+
122
+ ## Lizenz
123
+
124
+ MIT — siehe [LICENSE](LICENSE).
@@ -0,0 +1,358 @@
1
+ {
2
+ "type": "tabs",
3
+ "items": {
4
+ "main": {
5
+ "type": "panel",
6
+ "label": "Einstellungen & Routen",
7
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTMgMTNIMXYtMmgydjJ6bTA-MmgydjJIN1YzHptMTQgMTlIMXYtMmgydjJ6bTQtMmgydjJIN1YxN3ptNCAwaC0ydjJoMnYtMnoiLz48L3N2Zz4=",
8
+ "items": {
9
+ "targetBasePath": {
10
+ "type": "text",
11
+ "label": "MQTT Ziel-Pfad (Prefix)",
12
+ "default": "mqtt.0.",
13
+ "sm": 6,
14
+ "help": "Muss auf . enden! Bsp: mqtt.0."
15
+ },
16
+ "updateIntervalSec": {
17
+ "type": "number",
18
+ "label": "Update Intervall (s)",
19
+ "default": 30,
20
+ "sm": 3
21
+ },
22
+ "logTransfers": {
23
+ "type": "checkbox",
24
+ "label": "Log Aktiv",
25
+ "default": true,
26
+ "sm": 3
27
+ },
28
+ "forceSyncIntervalMin": {
29
+ "type": "number",
30
+ "label": "Force-Sync Intervall (Min, 0 = aus)",
31
+ "default": 5,
32
+ "sm": 3,
33
+ "help": "Vergleicht Quelle und Ziel und schreibt nur Abweichungen - Mappings im Sync-Modus \"Force\" werden immer neu geschrieben."
34
+ },
35
+ "staleAfterMin": {
36
+ "type": "number",
37
+ "label": "Aktualitätsgrenze (Min, 0 = aus)",
38
+ "default": 1440,
39
+ "min": 0,
40
+ "sm": 3,
41
+ "help": "Quellen, deren letztes Update älter ist (oder mit Qualität q≠0), werden bei Start, Update-Intervall und Force-Sync nicht gespiegelt - ein inaktives Gerät wird so auf der Zielseite nicht wieder \"frisch\". Echte Änderungs-Ereignisse gehen immer durch. Standard: 1440 (24 h)."
42
+ },
43
+ "mappings": {
44
+ "type": "accordion",
45
+ "label": "Mappings",
46
+ "titleAttr": "mqttName",
47
+ "clone": true,
48
+ "newLine": true,
49
+ "sm": 12,
50
+ "items": [
51
+ {
52
+ "type": "objectId",
53
+ "attr": "id",
54
+ "label": "Quell-ID (ioBroker)",
55
+ "xs": 12,
56
+ "sm": 12,
57
+ "md": 6,
58
+ "lg": 4,
59
+ "xl": 4
60
+ },
61
+ {
62
+ "type": "text",
63
+ "attr": "mqttName",
64
+ "label": "Pfad Suffix (z.B. home/küche/licht)",
65
+ "xs": 12,
66
+ "sm": 12,
67
+ "md": 6,
68
+ "lg": 3,
69
+ "xl": 3
70
+ },
71
+ {
72
+ "type": "select",
73
+ "attr": "dir",
74
+ "label": "Richtung",
75
+ "options": [
76
+ {
77
+ "value": "out",
78
+ "label": "IOB -> MQTT"
79
+ },
80
+ {
81
+ "value": "in",
82
+ "label": "MQTT -> IOB"
83
+ },
84
+ {
85
+ "value": "both",
86
+ "label": "Beide"
87
+ }
88
+ ],
89
+ "default": "out",
90
+ "xs": 12,
91
+ "sm": 6,
92
+ "md": 4,
93
+ "lg": 2,
94
+ "xl": 2
95
+ },
96
+ {
97
+ "type": "select",
98
+ "attr": "topicMode",
99
+ "label": "MQTT-Topic-Modus",
100
+ "options": [
101
+ {
102
+ "value": "single",
103
+ "label": "Single (ein Topic)"
104
+ },
105
+ {
106
+ "value": "dual",
107
+ "label": "Dual (Befehl auf /set)"
108
+ }
109
+ ],
110
+ "default": "single",
111
+ "xs": 12,
112
+ "sm": 6,
113
+ "md": 4,
114
+ "lg": 2,
115
+ "xl": 2,
116
+ "help": "Dual: Befehle auf <topic>/set"
117
+ },
118
+ {
119
+ "type": "text",
120
+ "attr": "commandSuffix",
121
+ "label": "Befehls-Suffix",
122
+ "default": "/set",
123
+ "xs": 12,
124
+ "sm": 6,
125
+ "md": 4,
126
+ "lg": 1,
127
+ "xl": 1,
128
+ "hidden": "data.topicMode !== 'dual'"
129
+ },
130
+ {
131
+ "type": "select",
132
+ "attr": "type",
133
+ "label": "Typ",
134
+ "options": [
135
+ {
136
+ "value": "auto",
137
+ "label": "Auto"
138
+ },
139
+ {
140
+ "value": "round",
141
+ "label": "Runden"
142
+ },
143
+ {
144
+ "value": "boolToNum",
145
+ "label": "Bool->Num"
146
+ },
147
+ {
148
+ "value": "numToBool",
149
+ "label": "Num->Bool"
150
+ }
151
+ ],
152
+ "default": "auto",
153
+ "newLine": true,
154
+ "xs": 12,
155
+ "sm": 6,
156
+ "md": 3,
157
+ "lg": 2,
158
+ "xl": 2
159
+ },
160
+ {
161
+ "type": "number",
162
+ "attr": "decimals",
163
+ "label": "Nachkommastellen",
164
+ "default": 2,
165
+ "xs": 12,
166
+ "sm": 6,
167
+ "md": 3,
168
+ "lg": 1,
169
+ "xl": 1,
170
+ "hidden": "data.type !== 'round'"
171
+ },
172
+ {
173
+ "type": "text",
174
+ "attr": "unit",
175
+ "label": "Einheit",
176
+ "xs": 12,
177
+ "sm": 6,
178
+ "md": 2,
179
+ "lg": 1,
180
+ "xl": 1
181
+ },
182
+ {
183
+ "type": "select",
184
+ "attr": "ackFilter",
185
+ "label": "Ack-Filter",
186
+ "options": [
187
+ {
188
+ "value": "confirmed",
189
+ "label": "Nur bestätigt (ack=true)"
190
+ },
191
+ {
192
+ "value": "any",
193
+ "label": "Auch unbestätigt (userdata/alias)"
194
+ }
195
+ ],
196
+ "default": "confirmed",
197
+ "xs": 12,
198
+ "sm": 6,
199
+ "md": 4,
200
+ "lg": 3,
201
+ "xl": 3,
202
+ "help": "\"Auch unbestätigt\" für 0_userdata/alias"
203
+ },
204
+ {
205
+ "type": "number",
206
+ "attr": "staleAfterMin",
207
+ "label": "Aktualität (Min)",
208
+ "min": 0,
209
+ "xs": 12,
210
+ "sm": 6,
211
+ "md": 3,
212
+ "lg": 2,
213
+ "xl": 2,
214
+ "help": "leer = global, 0 = aus",
215
+ "hidden": "data.syncMode === 'force'"
216
+ },
217
+ {
218
+ "type": "select",
219
+ "attr": "syncMode",
220
+ "label": "Sync-Modus",
221
+ "default": "standard",
222
+ "xs": 12,
223
+ "sm": 6,
224
+ "md": 4,
225
+ "lg": 3,
226
+ "xl": 3,
227
+ "options": [
228
+ {
229
+ "value": "standard",
230
+ "label": "Standard (nur Änderungen)"
231
+ },
232
+ {
233
+ "value": "refresh",
234
+ "label": "Jede Meldung weiterreichen"
235
+ },
236
+ {
237
+ "value": "force",
238
+ "label": "Force wie vor 1.6.0"
239
+ }
240
+ ],
241
+ "help": "Siehe Hilfe: Sync-Modus"
242
+ }
243
+ ]
244
+ }
245
+ }
246
+ },
247
+ "webTab": {
248
+ "type": "panel",
249
+ "label": "Web Dashboard",
250
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTE0IDJINkM0Ljg5IDIgNCAyLjg5IDQgNHYxNmMwIDEuMTEuODkgMiAyIDJoMTJjMS4xMSAwIDItLjg5IDItMlY4bC02LTZ6bS0xIDlIN3YtMmg2djJ6bTMgN0g3di0yaDEwdjJ6bTAtNEg3di0yaDEwdjJ6Ii8+PC9zdmc+",
251
+ "items": {
252
+ "webInfo": {
253
+ "type": "staticText",
254
+ "text": "Der Adapter stellt ein Web-Interface für Backup/Restore und Status bereit. Nutzen Sie den Link in der Instanz-Übersicht.",
255
+ "sm": 12
256
+ },
257
+ "serverPort": {
258
+ "type": "number",
259
+ "label": "Webserver Port",
260
+ "default": 8095,
261
+ "sm": 4
262
+ },
263
+ "bindHost": {
264
+ "type": "select",
265
+ "label": "Bind-Interface",
266
+ "default": "0.0.0.0",
267
+ "sm": 4,
268
+ "options": [
269
+ {
270
+ "value": "0.0.0.0",
271
+ "label": "Alle Interfaces (LAN erreichbar)"
272
+ },
273
+ {
274
+ "value": "127.0.0.1",
275
+ "label": "Nur lokal (127.0.0.1)"
276
+ }
277
+ ]
278
+ },
279
+ "dashboardUser": {
280
+ "type": "text",
281
+ "label": "Dashboard Benutzername",
282
+ "default": "admin",
283
+ "sm": 4
284
+ },
285
+ "dashboardPassword": {
286
+ "type": "password",
287
+ "label": "Dashboard Passwort",
288
+ "sm": 4,
289
+ "help": "Leer lassen = kein Zugangsschutz (nicht empfohlen). Wird per HTTP Basic-Auth abgefragt."
290
+ },
291
+ "dashboardTlsCert": {
292
+ "type": "text",
293
+ "label": "TLS-Zertifikat (PEM, optional)",
294
+ "multiline": true,
295
+ "rows": 6,
296
+ "sm": 6,
297
+ "help": "Mit TLS-Key zusammen gesetzt: Dashboard läuft über HTTPS statt HTTP, Zugangsdaten werden verschlüsselt übertragen."
298
+ },
299
+ "dashboardTlsKey": {
300
+ "type": "text",
301
+ "label": "TLS-Key (PEM, optional)",
302
+ "multiline": true,
303
+ "rows": 6,
304
+ "sm": 6,
305
+ "help": "Privater Schlüssel zum obigen Zertifikat. Leer lassen = HTTP (unverschlüsselt). Wird verschlüsselt gespeichert (encryptedNative)."
306
+ }
307
+ }
308
+ },
309
+ "syncTab": {
310
+ "type": "panel",
311
+ "label": "Remote Sync",
312
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTE5LjM1IDEwLjA0QzE4LjY3IDYuNTkgMTUuNjQgNCAxMiA0IDkuMTEgNCA2LjYgNS42NCA1LjM1IDguMDQgMi4zNCA4LjM2IDAgMTAuOTEgMCAxNGMwIDMuMzEgMi42OSA2IDYgNmgxM2MzLjMxIDAgNi0yLjY5IDYtNkwyNCAxNGMtMy4zMSAwLTYtMi42OS02LTYgMC0zLjMxIDIuNjktNiA2LTZ6Ii8+PC9zdmc+",
313
+ "items": {
314
+ "syncUrl": {
315
+ "type": "text",
316
+ "label": "Webhook URL",
317
+ "sm": 12,
318
+ "help": "Komplette URL inkl. API Key Parameter, z.B. https://.../sync?api-key=123"
319
+ },
320
+ "syncIntervalMin": {
321
+ "type": "number",
322
+ "label": "Intervall (Min)",
323
+ "default": 60,
324
+ "sm": 6
325
+ },
326
+ "remoteSyncSkipStale": {
327
+ "type": "checkbox",
328
+ "label": "Inaktive Quellen weglassen",
329
+ "default": false,
330
+ "sm": 6,
331
+ "help": "Werte, die laut Aktualitätsgrenze/Qualität als inaktiv gelten, werden nicht an den Webhook gesendet."
332
+ },
333
+ "syncCaCert": {
334
+ "type": "text",
335
+ "label": "Vertrauenswürdiges Zertifikat / CA (PEM, optional)",
336
+ "multiline": true,
337
+ "rows": 6,
338
+ "sm": 12,
339
+ "help": "Nur bei selbstsigniertem/internem Zertifikat der Sync-Gegenstelle nötig. Leer lassen bei öffentlichem Zertifikat (Standard-Systemvertrauensstore wird genutzt)."
340
+ },
341
+ "checkSync": {
342
+ "type": "sendTo",
343
+ "command": "checkSync",
344
+ "label": "Verbindung Prüfen",
345
+ "sm": 4,
346
+ "variant": "contained"
347
+ },
348
+ "lastSyncStatus": {
349
+ "type": "text",
350
+ "oid": "info.lastSyncStatus",
351
+ "label": "Letzter Status / Fehlercode",
352
+ "sm": 8,
353
+ "readOnly": true
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
Binary file