iobroker.alias-workbench 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +516 -0
  3. package/admin/alias-workbench.png +0 -0
  4. package/admin/css/werkbank.css +916 -0
  5. package/admin/detector.js +1 -0
  6. package/admin/geraetewissen/tasmota-befehle.json +116 -0
  7. package/admin/i18n/de.json +552 -0
  8. package/admin/i18n/en.json +552 -0
  9. package/admin/js/aufzaehlungen.js +332 -0
  10. package/admin/js/basis.js +36 -0
  11. package/admin/js/baum.js +453 -0
  12. package/admin/js/detail.js +335 -0
  13. package/admin/js/einstellungen.js +40 -0
  14. package/admin/js/entwurf.js +908 -0
  15. package/admin/js/enums.js +23 -0
  16. package/admin/js/ergebnis.js +934 -0
  17. package/admin/js/erkennung.js +126 -0
  18. package/admin/js/katalog.js +313 -0
  19. package/admin/js/mqtt.js +885 -0
  20. package/admin/js/musternamen.js +94 -0
  21. package/admin/js/musterwahl.js +334 -0
  22. package/admin/js/objekte.js +243 -0
  23. package/admin/js/pruefungen.js +130 -0
  24. package/admin/js/quellentausch.js +570 -0
  25. package/admin/js/rollenwahl.js +199 -0
  26. package/admin/js/schreiben.js +1537 -0
  27. package/admin/js/sprache.js +137 -0
  28. package/admin/js/start.js +39 -0
  29. package/admin/js/verbindung.js +10 -0
  30. package/admin/js/vorlagen.js +572 -0
  31. package/admin/js/vorlagenblatt.js +1907 -0
  32. package/admin/js/vorschlagen.js +391 -0
  33. package/admin/js/werte.js +193 -0
  34. package/admin/js/zielleiste.js +339 -0
  35. package/admin/js/zuordnung.js +695 -0
  36. package/admin/js/zustand.js +47 -0
  37. package/admin/js/zustandsliste.js +515 -0
  38. package/admin/jsonConfig.json +249 -0
  39. package/admin/tab.html +148 -0
  40. package/admin/vorlagen/hm-dimmer.json +204 -0
  41. package/admin/vorlagen/hm-fensterkontakt-hm.json +176 -0
  42. package/admin/vorlagen/hm-fensterkontakt-hmip.json +191 -0
  43. package/admin/vorlagen/hm-heizgruppe.json +214 -0
  44. package/admin/vorlagen/hm-heizkoerper.json +236 -0
  45. package/admin/vorlagen/hm-rollladen.json +190 -0
  46. package/admin/vorlagen/hm-schaltaktor-lampe.json +197 -0
  47. package/admin/vorlagen/hm-schaltaktor-mehrfach.json +203 -0
  48. package/admin/vorlagen/hm-schaltaktor-steckdose.json +197 -0
  49. package/admin/vorlagen/hm-temperatur-feuchte.json +166 -0
  50. package/admin/vorlagen/hm-wandthermostat.json +228 -0
  51. package/admin/vorlagen/index.json +21 -0
  52. package/admin/vorlagen/messpunkt.json +225 -0
  53. package/admin/vorlagen/tasmota-lampe.json +263 -0
  54. package/admin/vorlagen/tasmota-mehrfach.json +203 -0
  55. package/admin/vorlagen/tasmota-rgb.json +239 -0
  56. package/admin/vorlagen/tasmota-steckdose.json +265 -0
  57. package/io-package.json +122 -0
  58. package/package.json +48 -0
@@ -0,0 +1,332 @@
1
+ /* Raum und Funktion als Fachlichkeit: Listen, Zuordnung, Vorschlag.
2
+
3
+ Die Oberflaeche dazu (Felder, Angebote) liegt beim Ergebnis - hier nur,
4
+ was aus Bestand und Katalog folgt. */
5
+
6
+ import { S } from './zustand.js';
7
+ import { txt } from './sprache.js';
8
+ import { enums } from './enums.js';
9
+ import { enumVorlagen, ikonCache, vorlageZu } from './katalog.js';
10
+ import { zusatzName } from './baum.js';
11
+ import { aliasFuer, quelleVon } from './entwurf.js';
12
+
13
+ /* Eine Aufzaehlung, die es noch nicht gibt.
14
+
15
+ Steht sie im Vorlagenkatalog, uebernehmen wir den mehrsprachigen Namen
16
+ und das Icon - dann sieht sie hinterher aus wie eine im Admin
17
+ angelegte, nicht wie ein Fremdkoerper. Sonst bleibt der eingetippte
18
+ Name allein. */
19
+ export function neueEnum(id, art) {
20
+ var kurz = id.slice(('enum.' + art + '.').length);
21
+ var v = null;
22
+ (enumVorlagen[art] || []).forEach(function (x) { if (x._id === kurz) { v = x; } });
23
+ var c = { name: v ? JSON.parse(JSON.stringify(v.name)) : kurz };
24
+ var ik = (v && v.icon) ? ikonCache[art + ':' + v.icon] : null;
25
+ if (ik) { c.icon = ik; }
26
+ /* Die Herkunftsmarke erlaubt dem Loeschweg, eine selbst angelegte
27
+ Aufzaehlung wieder mitzunehmen, wenn ihr letztes Mitglied faellt.
28
+ Ohne Marke blieb `enum.functions.socket` als leere Huelle stehen
29
+ (25.08.2026 am Produktivsystem gefunden). Fremde und vorbestehende
30
+ Aufzaehlungen tragen sie nicht und bleiben darum unangetastet. */
31
+ return { type: 'enum', common: c, native: { angelegtVon: 'alias-workbench' } };
32
+ }
33
+
34
+ /* Alle Aufzaehlungen einer Art, benutzte zuerst.
35
+
36
+ Die Reihenfolge ist kein Schmuck: ioBroker legt bei der Erstinstallation
37
+ leere englische Raeume an (`bathroom`, `bedroom` …), deren deutscher Name
38
+ genau so lautet wie der eines benutzten Raums. Ricardo hat „Badezimmer"
39
+ zweimal - einmal mit fuenf Mitgliedern, einmal leer. Wer nur Namen zeigt,
40
+ schickt den Nutzer ins Leere. Deshalb steht die Kennung mit dabei, und
41
+ was schon benutzt wird, kommt zuerst. */
42
+ export function enumListe(art, nurBestand) {
43
+ var pre = 'enum.' + art + '.';
44
+ /* `autocreate` haelt der ping-Adapter als Sammelbecken mit hunderten
45
+ Mitgliedern - als Ablageort taugt es nie und stand durch die
46
+ Benutzt-zuerst-Sortierung ganz oben. Es faellt aus der Liste;
47
+ wer es wirklich am Geraet hat, sieht es weiter im Feld stehen. */
48
+ var raus = Object.keys(enums).filter(function (k) {
49
+ return k.indexOf(pre) === 0 && k.slice(pre.length) !== 'autocreate';
50
+ })
51
+ .map(function (k) {
52
+ var c = enums[k].common || {};
53
+ return { id: k, kurz: k.slice(pre.length), name: txt(c.name) || k.slice(pre.length),
54
+ anzahl: (c.members || []).length };
55
+ })
56
+ .sort(function (a, b) {
57
+ return (b.anzahl > 0) - (a.anzahl > 0) || a.name.localeCompare(b.name);
58
+ });
59
+
60
+ /* Fuer den Vorschlag ist hier Schluss.
61
+
62
+ Ein Vorschlag darf nur auf Vorhandenes zeigen. Sonst legt die
63
+ Werkbank ungefragt Aufzaehlungen an: `Weihnachten_aussen` liegt im
64
+ Pfad unter „Garten", es gibt eine Vorlage dieses Namens - und schon
65
+ stand `enum.rooms.garden` als Vorschlag im Feld, obwohl Ricardo
66
+ keinen Raum „Garten" fuehrt. Wer dann nur noch auf Anlegen drueckt,
67
+ bekommt einen Raum, den er nie gewaehlt hat. Gefunden im Test zu
68
+ 0.0.78, nachdem der Vorschlag dieselbe Liste benutzte wie die
69
+ Auswahl. */
70
+ if (nurBestand) { return raus; }
71
+
72
+ /* Hinter dem Bestand steht, was der Admin anbietet und es hier noch
73
+ nicht gibt: Rollladen, Steckdose, Waschmaschine. Sie stehen hinten,
74
+ weil das Vorhandene naeher liegt - wer eine Vorlage sucht, tippt.
75
+
76
+ Gleicher Name, andere Kennung ist die Falle: der Admin legt beim
77
+ Klick auf HEIZUNG ein `enum.functions.heating` an, waehrend die
78
+ Geraete in `enum.functions.Heizung` haengen. Solche Doppelgaenger
79
+ kommen gar nicht erst in die Liste - wer sie anlegte, bekam zwei
80
+ gleichnamige Aufzaehlungen und sah nie, warum die eine leer bleibt.
81
+ (Vorher stand nur ein Warnvermerk dran; Ricardos Befund 25.08.2026:
82
+ „gibt es schon und neu anlegen wird angezeigt".) */
83
+ var da = {}, nachName = {};
84
+ raus.forEach(function (x) {
85
+ da[x.id] = true;
86
+ nachName[x.name.toLowerCase()] = x.id;
87
+ });
88
+ (enumVorlagen[art] || []).forEach(function (v) {
89
+ var id = pre + v._id;
90
+ if (da[id]) { return; }
91
+ var name = txt(v.name) || v._id;
92
+ /* Auch der Katalog selbst fuehrt Namensvettern (zweimal
93
+ „Stromverbrauch" unter verschiedenen Kennungen) - einer reicht. */
94
+ if (nachName[name.toLowerCase()]) { return; }
95
+ nachName[name.toLowerCase()] = id;
96
+ raus.push({ id: id, kurz: v._id, name: name, anzahl: 0, vorlage: v });
97
+ });
98
+ return raus;
99
+ }
100
+
101
+ /* In welchen Aufzaehlungen dieser Art steckt die Kennung? */
102
+ export function enumsVon(id, art) {
103
+ var pre = 'enum.' + art + '.';
104
+ return Object.keys(enums).filter(function (k) {
105
+ if (k.indexOf(pre) !== 0) { return false; }
106
+ var m = (enums[k].common || {}).members || [];
107
+ return m.indexOf(id) > -1;
108
+ });
109
+ }
110
+
111
+ /* Der Raum einer Quelle - auch wenn er an einem ihrer Kanaele haengt.
112
+
113
+ Gemessen: die Raeume stehen bei Homematic fast immer am Kanal
114
+ (`hm-rpc.0.000A9D89900001.0`), nicht am Geraet. Wer nur die Geraete-ID
115
+ sucht, findet bei 61 zugeordneten Quellen fast keine. */
116
+ export function enumVonQuelle(quelle, art) {
117
+ var direkt = enumsVon(quelle, art);
118
+ if (direkt.length) { return direkt[0]; }
119
+ var pre = 'enum.' + art + '.';
120
+ var treffer = null;
121
+ Object.keys(enums).forEach(function (k) {
122
+ if (treffer || k.indexOf(pre) !== 0) { return; }
123
+ var m = (enums[k].common || {}).members || [];
124
+ if (m.some(function (x) { return String(x).indexOf(quelle + '.') === 0; })) { treffer = k; }
125
+ });
126
+ return treffer;
127
+ }
128
+
129
+
130
+ /* Welche Funktion passt zu welchem Geraetetyp.
131
+
132
+ Bewusst eine Zuordnung ueber den *Namen* der Aufzaehlung, nicht ueber
133
+ ihre Kennung: Ricardos Funktionen heissen „Licht", „Heizung",
134
+ „Verschluss" - auf einer englischen Anlage heissen sie anders. Deshalb
135
+ wird nur vorgeschlagen, was es auch gibt. */
136
+ /* Welche Funktion zu einem Geraetetyp gehoert.
137
+
138
+ Hier stand eine Tabelle, die Gewerke auf Typen abbildete - „Verschluss“
139
+ auf blind, window, door, lock, gate. Sie hatte zwei Fehler: Sie war zu
140
+ grob (ein Fensterkontakt ist ein Fenster, kein „Verschluss“), und sie
141
+ zeigte auf Namen aus Ricardos CCU. Auf einer Anlage ohne Homematic
142
+ traf sie nichts.
143
+
144
+ Jetzt wird abgeleitet, in dieser Reihenfolge:
145
+
146
+ 1. was in der Vorlage steht `"funktion": "window"`
147
+ 2. Typkennung = Vorlagenkennung window, door, socket, light, …
148
+ 3. diese Ausnahmeliste was im Admin anders heisst
149
+ 4. nichts
150
+
151
+ Gemessen: 9 der 53 Detector-Typen heissen genauso wie eine
152
+ Admin-Vorlage und brauchen keinen Eintrag - door, fan, gate, humidity,
153
+ light, lock, pump, socket, window. Fuer den Rest steht hier, was
154
+ gemeint ist, und zwar als Vorlagenkennung, nicht als Anzeigename:
155
+ daraus loest die Werkbank beides auf, den vorhandenen Eintrag und die
156
+ Vorlage samt Uebersetzung und Bild. */
157
+ export var TYP_ZU_VORLAGE = {
158
+ blind: 'shutters',
159
+ windowTilt: 'window',
160
+ thermostat: 'heating',
161
+ temperature: 'temperature_sensor',
162
+ dimmer: 'light',
163
+ rgb: 'light',
164
+ rgbSingle: 'light',
165
+ rgbwSingle: 'light',
166
+ ct: 'light',
167
+ hue: 'light',
168
+ cie: 'light',
169
+ fireAlarm: 'smoke_detector',
170
+ floodAlarm: 'water',
171
+ vacuumCleaner: 'vacuum_cleaner',
172
+ weatherCurrent: 'weather',
173
+ weatherForecast: 'weather',
174
+ airCondition: 'climate',
175
+ volume: 'speaker',
176
+ volumeGroup: 'speaker',
177
+ media: 'music'
178
+ };
179
+
180
+ /* Der vorgeschlagene Raum. Vier Wege, der erste Treffer gewinnt.
181
+
182
+ Der erste ist der wichtigste: was die Quelle schon weiss, muss man nicht
183
+ erraten. Die drei danach lesen den Namen - erst den Zielordner, dann den
184
+ Pfad der Quelle (bei Tasmota steht der Raum im Topic), zuletzt den
185
+ Geraetenamen selbst (`HK_Bastelzimmer` trifft bei Homematic fast immer). */
186
+ export function raumVorschlag(e) {
187
+ if (!e || !e.kanal) { return ''; }
188
+
189
+ /* 1. Von der Quelle erben */
190
+ var quelle = (e.kanal.indexOf('alias.') === 0) ? quelleVon(e.kanal) : e.kanal;
191
+ if (quelle) {
192
+ var geerbt = enumVonQuelle(quelle, 'rooms');
193
+ if (geerbt) { return geerbt; }
194
+ }
195
+
196
+ var liste = enumListe('rooms', true);
197
+ var suche = function (wort) {
198
+ if (!wort) { return ''; }
199
+ var w = String(wort).toLowerCase();
200
+ var t = liste.filter(function (r) {
201
+ return r.name.toLowerCase() === w || r.kurz.toLowerCase() === w;
202
+ });
203
+ return t.length ? t[0].id : '';
204
+ };
205
+
206
+ /* 2. Aus dem Zielordner.
207
+
208
+ Nur wenn der Ordner von Hand gesetzt wurde oder der Alias schon
209
+ liegt. Sonst dreht es sich im Kreis: der Ordner kommt aus dem Raum,
210
+ und der Raum kaeme aus dem Ordner. Abgeleitet wird immer nur aus
211
+ dem, was jemand entschieden hat. */
212
+ var ziel = (e.ordnerVonHand || aliasFuer(e.kanal)) ? (e.ziel || '') : '';
213
+ if (ziel.indexOf('alias.0.') === 0) {
214
+ var teile = ziel.slice('alias.0.'.length).split('.');
215
+ teile.pop();
216
+ for (var i = teile.length - 1; i >= 0; i--) {
217
+ var tr = suche(teile[i]);
218
+ if (tr) { return tr; }
219
+ }
220
+ }
221
+
222
+ /* 3. Aus dem Pfad der Quelle */
223
+ if (quelle) {
224
+ var qt = quelle.split('.');
225
+ for (var j = qt.length - 2; j >= 2; j--) {
226
+ var qr = suche(qt[j]);
227
+ if (qr) { return qr; }
228
+ }
229
+ }
230
+
231
+ /* 4. Aus dem Geraetenamen: „HK_Bastelzimmer" -> Bastelzimmer.
232
+ Von hinten, weil der Raum am Ende steht, und stueckweise, weil
233
+ „Max_Spielzimmer" aus zwei Teilen besteht. */
234
+ var name = zusatzName(e.kanal) || (quelle ? zusatzName(quelle) : '') || '';
235
+ if (name) {
236
+ var nt = String(name).split(/[_ .]+/);
237
+ for (var k = 0; k < nt.length; k++) {
238
+ var rest = nt.slice(k).join('_');
239
+ var nr = suche(rest);
240
+ if (nr) { return nr; }
241
+ }
242
+ }
243
+ return '';
244
+ }
245
+
246
+ /* Die vorgeschlagene Funktion, aus dem erkannten Geraetetyp. */
247
+ /* Zu einer Vorlagenkennung die Aufzaehlung, die gemeint ist.
248
+
249
+ Der Bestand hat Vorrang: heisst eine vorhandene Funktion so oder
250
+ traegt sie den Namen der Vorlage, wird die genommen. Sonst die
251
+ Vorlage selbst - sie entsteht dann beim Schreiben, sichtbar als NEU
252
+ im Trockenlauf. */
253
+ export function funktionZuKennung(kennung) {
254
+ if (!kennung) { return ''; }
255
+ var vorlage = null;
256
+ (enumVorlagen.functions || []).forEach(function (v) {
257
+ if (v._id === kennung) { vorlage = v; }
258
+ });
259
+ var name = vorlage ? String(txt(vorlage.name) || '').toLowerCase() : '';
260
+ var da = '';
261
+ enumListe('functions', true).forEach(function (f) {
262
+ if (da) { return; }
263
+ if (f.kurz === kennung) { da = f.id; return; }
264
+ if (name && f.name.toLowerCase() === name) { da = f.id; }
265
+ });
266
+ if (da) { return da; }
267
+ return vorlage ? ('enum.functions.' + kennung) : '';
268
+ }
269
+
270
+ /* Die Auswahlliste der Funktionen - einmal gebaut, zweimal gebraucht:
271
+ im Vorlagenblatt und im Dialog „Als Vorlage speichern".
272
+
273
+ Gespeichert wird die Kennung aus dem Katalog des Admin, nicht der
274
+ Anzeigename; daraus loest die Werkbank beides auf, den vorhandenen
275
+ Eintrag und die Vorlage samt Uebersetzung und Bild. */
276
+ export function funktionsAuswahl(gewaehlt) {
277
+ var raus = [], da = false;
278
+ (enumVorlagen.functions || []).slice()
279
+ .sort(function (a, b) {
280
+ return String(txt(a.name) || a._id).localeCompare(String(txt(b.name) || b._id));
281
+ })
282
+ .forEach(function (fv) {
283
+ if (fv._id === gewaehlt) { da = true; }
284
+ raus.push({ wert: fv._id,
285
+ text: (txt(fv.name) || fv._id) + ' · ' + fv._id,
286
+ gewaehlt: fv._id === gewaehlt });
287
+ });
288
+ /* Steht in der Vorlage etwas, das der Katalog nicht kennt, geht es
289
+ nicht verloren - sonst waere es beim ersten Speichern weg. */
290
+ if (gewaehlt && !da) { raus.push({ wert: gewaehlt, text: gewaehlt, gewaehlt: true }); }
291
+ return raus;
292
+ }
293
+
294
+ /* Der Rueckweg: aus `enum.functions.Klima` wird `climate`.
295
+
296
+ `funktionZuKennung` geht die eine Richtung, hier die andere. Gebraucht
297
+ wird sie beim Speichern einer Vorlage: was am Geraet als Funktion
298
+ steht, soll im Dialog schon vorgeschlagen sein.
299
+
300
+ Gespeichert wird die Kennung des Katalogs, nicht die der Anlage - eine
301
+ Vorlage, die `Klima` sagt, passt nur auf eine Anlage mit genau diesem
302
+ deutschen Namen. Findet sich im Katalog nichts, bleibt die kurze
303
+ Kennung der Aufzaehlung; `funktionZuKennung` findet auch die wieder. */
304
+ export function kennungZuFunktion(enumId) {
305
+ if (!enumId) { return ''; }
306
+ var kurz = enumId.slice('enum.functions.'.length);
307
+ var o = enums[enumId];
308
+ var v = vorlageZu(enumId, 'functions', txt(o && o.common && o.common.name));
309
+ return v ? v._id : kurz;
310
+ }
311
+
312
+ export function funktionVorschlag(e) {
313
+ if (!e) { return ''; }
314
+
315
+ /* 1. Was die Vorlage sagt. Sie weiss mehr als der Typ: `socket` kann
316
+ eine Steckdose sein, eine Waschmaschine oder eine Druckerleiste -
317
+ der Detector sieht keinen Unterschied, die Vorlage schon. */
318
+ if (e.vorlage) {
319
+ var vq = null;
320
+ S.VORLAGEN.forEach(function (v) { if (v.id === e.vorlage) { vq = v; } });
321
+ if (vq && vq.funktion) {
322
+ var ausVorlage = funktionZuKennung(vq.funktion);
323
+ if (ausVorlage) { return ausVorlage; }
324
+ }
325
+ }
326
+
327
+ if (!e.want) { return ''; }
328
+
329
+ /* 2. Der Typ heisst wie eine Vorlage. 3. Sonst die Ausnahmeliste. */
330
+ return funktionZuKennung(e.want) ||
331
+ funktionZuKennung(TYP_ZU_VORLAGE[e.want]);
332
+ }
@@ -0,0 +1,36 @@
1
+ /* Zustandsfreie Helfer, die jedes Modul brauchen darf.
2
+
3
+ Hier liegt nichts, was sich aendert - nur Werkzeug. */
4
+
5
+ export var D = window.AWDetector;
6
+ export var $ = function (s) { return document.querySelector(s); };
7
+
8
+ /* Plus und Minus in der Form, die auch der Admin verwendet. Fuer
9
+ Abschnitte, die man auf- und zuklappt — im Gegensatz zum Baum, wo ein
10
+ Dreieck die gewohnte Form ist. „Mehr davon" und „hier geht es weiter"
11
+ sind zwei verschiedene Aussagen. */
12
+ var WEG_PLUS = 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z';
13
+ var WEG_MINUS = 'M19 13H5v-2h14z';
14
+
15
+ export function klappZeichen(offen) {
16
+ var sp = document.createElement('span');
17
+ sp.className = 'klappz';
18
+ sp.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="' +
19
+ (offen ? WEG_MINUS : WEG_PLUS) + '"/></svg>';
20
+ return sp;
21
+ }
22
+ export function el(tag, cls, txt) {
23
+ var e = document.createElement(tag);
24
+ if (cls) { e.className = cls; }
25
+ if (txt !== undefined) { e.textContent = txt; }
26
+ return e;
27
+ }
28
+
29
+ export function holeText(pfad) {
30
+ return fetch(pfad).then(function (r) {
31
+ if (!r.ok) { throw new Error(String(r.status)); }
32
+ return r.text();
33
+ });
34
+ }
35
+
36
+ export function kurz(id, kanal) { return id.slice(kanal.length + 1); }