iobroker.alias-workbench 0.8.3 → 0.9.7
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 +146 -3
- package/admin/css/werkbank.css +57 -8
- package/admin/detector.js +1 -1
- package/admin/geraetewissen/tasmota-befehle.json +3 -1
- package/admin/i18n/de.json +30 -6
- package/admin/i18n/en.json +30 -6
- package/admin/js/aufzaehlungen.js +11 -1
- package/admin/js/baum.js +118 -37
- package/admin/js/detail.js +76 -9
- package/admin/js/entwurf.js +313 -43
- package/admin/js/enums.js +50 -7
- package/admin/js/ergebnis.js +161 -37
- package/admin/js/erkennung.js +54 -5
- package/admin/js/katalog.js +12 -3
- package/admin/js/mqtt.js +76 -13
- package/admin/js/musternamen.js +91 -26
- package/admin/js/musterwahl.js +10 -0
- package/admin/js/objekte.js +29 -3
- package/admin/js/pruefungen.js +10 -2
- package/admin/js/quellentausch.js +31 -2
- package/admin/js/schreiben.js +209 -24
- package/admin/js/sprache.js +54 -7
- package/admin/js/start.js +32 -2
- package/admin/js/vorlagen.js +114 -6
- package/admin/js/vorlagenblatt.js +195 -26
- package/admin/js/werte.js +113 -6
- package/admin/js/zuordnung.js +71 -17
- package/admin/js/zustand.js +7 -1
- package/admin/js/zustandsliste.js +157 -26
- package/admin/tab.html +25 -3
- package/admin/vorlagen/hm-dimmer.json +1 -1
- package/admin/vorlagen/hm-schaltaktor-lampe.json +2 -2
- package/admin/vorlagen/hm-schaltaktor-mehrfach.json +2 -2
- package/admin/vorlagen/hm-schaltaktor-steckdose.json +2 -2
- package/io-package.json +105 -1
- package/package.json +1 -1
package/admin/js/entwurf.js
CHANGED
|
@@ -8,9 +8,11 @@ import { $ } from './basis.js';
|
|
|
8
8
|
import { txt, tr } from './sprache.js';
|
|
9
9
|
import './enums.js';
|
|
10
10
|
import { zusatzName, setzeModus, zeichneBaum, merkeKlappstand } from './baum.js';
|
|
11
|
-
import { kindZustaende, aliasQuellen, holeWerte
|
|
11
|
+
import { kindZustaende, aliasQuellen, holeWerte,
|
|
12
|
+
mitVorspann, schreibQuelle
|
|
13
|
+
} from './werte.js';
|
|
12
14
|
import { erkenneEntwurf } from './erkennung.js';
|
|
13
|
-
import { vorschlag , pruefeVorlage } from './vorlagen.js';
|
|
15
|
+
import { vorschlag , pruefeVorlage, zeigeAbozahl } from './vorlagen.js';
|
|
14
16
|
|
|
15
17
|
import { zeichneErgebnis } from './ergebnis.js';
|
|
16
18
|
|
|
@@ -101,11 +103,7 @@ export function baueEntwurf(kanal) {
|
|
|
101
103
|
alias.0.Badezimmer.Rollladen.CLOSE). Belegt ist das Schreiben
|
|
102
104
|
durch `common.write` oder durch eine vorhandene Schreibformel -
|
|
103
105
|
wer eine hinterlegt hat, wollte schreiben. */
|
|
104
|
-
srcW: istAlias
|
|
105
|
-
? (q.einfach
|
|
106
|
-
? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
|
|
107
|
-
: (q.write || ''))
|
|
108
|
-
: (c.write ? id : ''),
|
|
106
|
+
srcW: istAlias ? schreibQuelle(o) : (c.write ? id : ''),
|
|
109
107
|
f: (typeof a.read === 'string') ? a.read : '',
|
|
110
108
|
fw: (typeof a.write === 'string') ? a.write : '',
|
|
111
109
|
caption: istAlias ? txt(c.name) : kanalBeschriftung(kanal, kurzName),
|
|
@@ -132,8 +130,8 @@ export function baueEntwurf(kanal) {
|
|
|
132
130
|
entstanden ist - dieselbe Regel wie in `waehle`. Ohne sie durfte eine
|
|
133
131
|
spaeter angelegte Vorlage beim Umschalten auf die Alle-Ansicht das
|
|
134
132
|
fertige Geraet still uebernehmen (26.08.2026). */
|
|
135
|
-
export function alleUndVorlage(kanal, vorlagenId) {
|
|
136
|
-
var v = vorschlag(kanal, vorlagenId);
|
|
133
|
+
export function alleUndVorlage(kanal, vorlagenId, instanz) {
|
|
134
|
+
var v = vorschlag(kanal, vorlagenId, instanz);
|
|
137
135
|
var roh = baueEntwurf(kanal);
|
|
138
136
|
if (!v) { return roh; }
|
|
139
137
|
|
|
@@ -215,6 +213,120 @@ export function knotenDa(id) {
|
|
|
215
213
|
return kindZustaende(id).length > 0;
|
|
216
214
|
}
|
|
217
215
|
|
|
216
|
+
/* Aus welchen Knoten liest dieser Alias, und wie oft aus jedem?
|
|
217
|
+
|
|
218
|
+
Dieselbe Zaehlung wie in `quelleVon` - nur dass die dort das Ergebnis
|
|
219
|
+
auf einen Sieger eindampft. Fuer die Anzeige zaehlt aber gerade das,
|
|
220
|
+
was dabei verlorengeht: dass es mehr als einen gibt. Ricardos
|
|
221
|
+
`alias.0.Solar.Netz` liest drei Punkte aus `0_userdata` und zwei aus
|
|
222
|
+
`mqtt-client`; der Sprungknopf fuehrte zur Mehrheit und sagte kein
|
|
223
|
+
Wort ueber den Rest (Ricardo, 08.09.2026).
|
|
224
|
+
|
|
225
|
+
Absteigend nach Anzahl, bei Gleichstand alphabetisch - sonst
|
|
226
|
+
wechselte die Reihenfolge bei jedem Neuzeichnen. */
|
|
227
|
+
/* Zu welcher Quelle des Alias gehoert der Knoten, an dem ich stehe?
|
|
228
|
+
|
|
229
|
+
Die Antwort ist nicht „die Mehrheit", sondern „die hier". Sie traegt
|
|
230
|
+
zwei Dinge: den Rueckweg zum Alias (er gehoert an jede Quelle, nicht
|
|
231
|
+
nur an die groesste) und den Bezugspunkt der Zeilenmarken - markiert
|
|
232
|
+
wird, was NICHT von hier kommt (Ricardo, 08.09.2026).
|
|
233
|
+
|
|
234
|
+
Getroffen wird ein Eintrag der Verteilung auf drei Weisen: genau er,
|
|
235
|
+
ein Knoten unter ihm (`…Stromzaehler.stat` unter der zusammengefassten
|
|
236
|
+
Sparte) oder sein unmittelbarer Elternknoten - dort steht man am
|
|
237
|
+
Geraet, waehrend die Quelle eine Ebene tiefer liegt. Nur eine Ebene:
|
|
238
|
+
sonst bekaeme `mqtt-client.0.SmartHome` den Knopf und jede Ebene
|
|
239
|
+
darueber gleich mit. */
|
|
240
|
+
export function quelleHier(aliasId, knoten) {
|
|
241
|
+
if (!aliasId || !knoten) { return null; }
|
|
242
|
+
var treffer = null;
|
|
243
|
+
quellenVerteilung(aliasId).forEach(function (q) {
|
|
244
|
+
if (treffer) { return; }
|
|
245
|
+
if (q.id === knoten) { treffer = q.id; return; }
|
|
246
|
+
if (knoten.indexOf(q.id + '.') === 0) { treffer = q.id; return; }
|
|
247
|
+
var t = q.id.split('.');
|
|
248
|
+
t.pop();
|
|
249
|
+
if (t.join('.') === knoten) { treffer = q.id; }
|
|
250
|
+
});
|
|
251
|
+
return treffer;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function quellenVerteilung(aliasId) {
|
|
255
|
+
var zaehler = {};
|
|
256
|
+
kindZustaende(aliasId).forEach(function (id) {
|
|
257
|
+
var q = aliasQuellen(S.objects[id]);
|
|
258
|
+
/* Einmal je DATENPUNKT, nicht je Quellangabe. `quelleVon` zaehlt
|
|
259
|
+
Lese- und Schreibquelle getrennt - fuer den Mehrheitsentscheid
|
|
260
|
+
gleichgueltig, fuer eine Anzeige nicht: an einem Alias mit fuenf
|
|
261
|
+
Punkten stand „6 und 4" statt „3 und 2". Wo Lesen und Schreiben
|
|
262
|
+
auseinandergehen, zaehlt die Lesequelle: aus ihr kommt der Wert,
|
|
263
|
+
den man sieht. */
|
|
264
|
+
var x = q.read || q.write;
|
|
265
|
+
if (!x) { return; }
|
|
266
|
+
var t = String(x).split('.');
|
|
267
|
+
t.pop();
|
|
268
|
+
var eltern = t.join('.');
|
|
269
|
+
if (eltern) { zaehler[eltern] = (zaehler[eltern] || 0) + 1; }
|
|
270
|
+
});
|
|
271
|
+
var sortiere = function (liste) {
|
|
272
|
+
return liste.sort(function (a, b) { return (b.n - a.n) || (a.id < b.id ? -1 : 1); });
|
|
273
|
+
};
|
|
274
|
+
var liste = sortiere(Object.keys(zaehler).map(function (k) { return { id: k, n: zaehler[k] }; }));
|
|
275
|
+
|
|
276
|
+
/* Sparten desselben Geraets sind keine zweite Quelle.
|
|
277
|
+
|
|
278
|
+
Ein Tasmota-Alias liest aus `…Licht.stat` und `…Licht.tele` - das
|
|
279
|
+
sind zwei Knoten, aber ein Geraet, und „2 Quellen" waere dort
|
|
280
|
+
schlicht falsch. Alles, was unterhalb der Hauptquelle liegt, zaehlt
|
|
281
|
+
deshalb zu ihr. Uebrig bleiben nur eigene Zweige - bei Ricardos
|
|
282
|
+
`alias.0.Solar.Netz` `0_userdata` und `mqtt-client` (08.09.2026). */
|
|
283
|
+
var haupt = quelleVon(aliasId);
|
|
284
|
+
if (!haupt) { return liste; }
|
|
285
|
+
var raus = [], hauptN = 0;
|
|
286
|
+
liste.forEach(function (q) {
|
|
287
|
+
if (q.id === haupt || q.id.indexOf(haupt + '.') === 0) { hauptN += q.n; return; }
|
|
288
|
+
raus.push(q);
|
|
289
|
+
});
|
|
290
|
+
if (hauptN) { raus.push({ id: haupt, n: hauptN }); }
|
|
291
|
+
|
|
292
|
+
/* Dasselbe noch einmal unter den Nebenquellen.
|
|
293
|
+
|
|
294
|
+
Der Schritt darueber fasst nur gegen die HAUPTquelle zusammen. Bei
|
|
295
|
+
`alias.0.Solar.Balkon.Einspeisung` liegen aber zwei der Nebenquellen
|
|
296
|
+
ebenfalls beieinander - `…Solar_Balkon.stat` und `…Solar_Balkon.tele`
|
|
297
|
+
sind ein Tasmota, und es standen „4 Quellen" statt drei (Ricardo,
|
|
298
|
+
08.09.2026).
|
|
299
|
+
|
|
300
|
+
Zusammengefasst wird nur, was wirklich zusammengehoert: mehrere
|
|
301
|
+
Knoten unter demselben Elternknoten, und das auch nur, wenn sie
|
|
302
|
+
blosse Kennungsebenen ohne eigenes Objekt sind (so legt MQTT seine
|
|
303
|
+
Sparten an) oder ihr Elternknoten ein `device` ist (so liegen die
|
|
304
|
+
Kanaele bei Homematic). Zwei `channel` in einem Ordner - etwa
|
|
305
|
+
`0_userdata.0.Solar.Netz` und `…Solar.Balkon` - bleiben getrennt:
|
|
306
|
+
das sind zwei Geraete, keine zwei Sparten. */
|
|
307
|
+
var nachEltern = {};
|
|
308
|
+
raus.forEach(function (q) {
|
|
309
|
+
var t = q.id.split('.');
|
|
310
|
+
t.pop();
|
|
311
|
+
var eltern = t.join('.');
|
|
312
|
+
(nachEltern[eltern] = nachEltern[eltern] || []).push(q);
|
|
313
|
+
});
|
|
314
|
+
var zusammen = [];
|
|
315
|
+
Object.keys(nachEltern).forEach(function (eltern) {
|
|
316
|
+
var gruppe = nachEltern[eltern];
|
|
317
|
+
var ohneObjekt = gruppe.every(function (q) { return !S.objects[q.id]; });
|
|
318
|
+
var elternGeraet = !!(S.objects[eltern] && S.objects[eltern].type === 'device');
|
|
319
|
+
if (gruppe.length < 2 || !eltern || (!ohneObjekt && !elternGeraet)) {
|
|
320
|
+
zusammen = zusammen.concat(gruppe);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
var summe = 0;
|
|
324
|
+
gruppe.forEach(function (q) { summe += q.n; });
|
|
325
|
+
zusammen.push({ id: eltern, n: summe });
|
|
326
|
+
});
|
|
327
|
+
return sortiere(zusammen);
|
|
328
|
+
}
|
|
329
|
+
|
|
218
330
|
export function quelleVon(aliasId) {
|
|
219
331
|
var o = S.objects[aliasId];
|
|
220
332
|
var q = o && o.native && o.native.quelle;
|
|
@@ -436,7 +548,14 @@ export function steuerKanaele(dev) {
|
|
|
436
548
|
var pflicht = f[0].states.filter(function (st) { return st.id; })
|
|
437
549
|
.map(function (st) { return st.id; });
|
|
438
550
|
if (!f[0].states.some(function (st) { return st.required && st.id; })) { return; }
|
|
439
|
-
|
|
551
|
+
/* `name` ist der Anzeigename und darf alles enthalten, was ein Mensch
|
|
552
|
+
hinschreibt. `kennung` ist, was als Objekt-ID taugt — dieselbe
|
|
553
|
+
Saeuberung wie ueberall sonst (T19). Getrennt, weil beides
|
|
554
|
+
gebraucht wird: die Liste zeigt den Namen, das Ziel nimmt die
|
|
555
|
+
Kennung. */
|
|
556
|
+
var kn = eigenerKanalName(id);
|
|
557
|
+
raus.push({ id: id, typ: f[0].type, name: kn || id.slice(pre.length),
|
|
558
|
+
kennung: kennungtauglich(kn || '') || id.slice(pre.length),
|
|
440
559
|
pflicht: pflicht });
|
|
441
560
|
});
|
|
442
561
|
kanalGeraeteCache[dev] = raus;
|
|
@@ -529,6 +648,20 @@ export function entwurfFuer(id, gemeinsam, gemAn) {
|
|
|
529
648
|
return merkeHakenVorgabe(e2);
|
|
530
649
|
}
|
|
531
650
|
|
|
651
|
+
/* Alle laufenden Abos abmelden.
|
|
652
|
+
|
|
653
|
+
Stand nur in `waehle` — und damit meldete kein einziger der Wege ab,
|
|
654
|
+
die die Auswahl fallen lassen: der Moduswechsel, das Zuklappen eines
|
|
655
|
+
Zweigs, das Abschalten des Expertenmodus, das Loeschen eines Alias.
|
|
656
|
+
Danach lief ein Abo auf ein Geraet, das rechts gar nicht mehr steht,
|
|
657
|
+
der Zaehler in der Kopfleiste behauptete „ein Abo", und `S.werte`
|
|
658
|
+
fuellte sich weiter (gemessen 09.09.2026). */
|
|
659
|
+
export function aboLoesen() {
|
|
660
|
+
(S.abo || []).forEach(function (m) { socket.emit('unsubscribe', m); });
|
|
661
|
+
S.abo = [];
|
|
662
|
+
zeigeAbozahl();
|
|
663
|
+
}
|
|
664
|
+
|
|
532
665
|
export function waehle(id, fertig) {
|
|
533
666
|
/* Wer links etwas anderes anklickt, will es rechts sehen - auch wenn
|
|
534
667
|
der Fokus noch in einem Feld der rechten Seite steht. `tipptGerade`
|
|
@@ -573,9 +706,118 @@ export function waehle(id, fertig) {
|
|
|
573
706
|
sel.scrollIntoView({ block: 'nearest' });
|
|
574
707
|
}
|
|
575
708
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
709
|
+
/* Abonniert wird, was auch angezeigt wird.
|
|
710
|
+
|
|
711
|
+
Bisher nur `<knoten>.*`. An einer Quelle stimmt das - die Werte in
|
|
712
|
+
der Liste kommen von dort. Am Alias nicht: dort stehen die Werte der
|
|
713
|
+
QUELLEN, und die bekamen kein Ereignis. Die Zahlen wurden einmal
|
|
714
|
+
beim Auswaehlen geholt und standen dann still; an
|
|
715
|
+
`alias.0.Solar.Balkon.Energie`, wo sich im Sekundentakt etwas
|
|
716
|
+
aendert, fiel es auf (Ricardo, 08.09.2026).
|
|
717
|
+
|
|
718
|
+
Deshalb am Alias zusaetzlich seine Quellen - `quellenVerteilung`
|
|
719
|
+
weiss ja, welche das sind. Und deshalb eine Liste: sonst bliebe
|
|
720
|
+
beim naechsten Wechsel die Haelfte abonniert und die Werkbank
|
|
721
|
+
bekaeme mit der Zeit Ereignisse fuer das halbe System. Die Zahl der
|
|
722
|
+
laufenden Abos steht in der Kopfleiste - dort sieht man sofort, ob
|
|
723
|
+
sich etwas ansammelt. */
|
|
724
|
+
aboLoesen();
|
|
725
|
+
var muster = [id + '.*'];
|
|
726
|
+
if (id.indexOf('alias.') === 0) {
|
|
727
|
+
/* Aus den Quellen, nicht aus der Anzeige-Verteilung.
|
|
728
|
+
|
|
729
|
+
`quellenVerteilung` fasst zusammen, was die rechte Seite als
|
|
730
|
+
„Quellen" zeigt — dafuer bildet sie den ELTERNknoten jeder
|
|
731
|
+
Quelle. Bei einem flachen MQTT-Geraet ist das der halbe Adapter:
|
|
732
|
+
gemessen 09.09.2026 wurde daraus `mqtt-client.0.*` mit 322
|
|
733
|
+
Objekten, an einem einzigen Alias. Und `S.werte` wird nie
|
|
734
|
+
geleert, das sammelt sich also.
|
|
735
|
+
|
|
736
|
+
Abonniert wird deshalb der Kanal jeder wirklich gelesenen Quelle.
|
|
737
|
+
Wo die Quellen ohnehin gebuendelt liegen, bleibt es bei einem
|
|
738
|
+
Muster; wo nicht, sind es ein paar mehr — aber jedes davon eng. */
|
|
739
|
+
var kanaele = [];
|
|
740
|
+
kindZustaende(id).forEach(function (kid) {
|
|
741
|
+
var q = aliasQuellen(S.objects[kid]);
|
|
742
|
+
[q.read, q.write].forEach(function (x) {
|
|
743
|
+
if (!x) { return; }
|
|
744
|
+
var t = String(x).split('.');
|
|
745
|
+
t.pop();
|
|
746
|
+
var kn = t.join('.');
|
|
747
|
+
if (kn && kanaele.indexOf(kn) === -1) { kanaele.push(kn); }
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
/* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
|
|
751
|
+
|
|
752
|
+
Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
|
|
753
|
+
Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
|
|
754
|
+
unter `SmartHome`, und `SmartHome.*` sind 320 Objekte. Ab dieser
|
|
755
|
+
Schwelle werden die Quellen einzeln abonniert — ein paar Abos
|
|
756
|
+
mehr, dafuer genau die, deren Werte auch angezeigt werden. */
|
|
757
|
+
var SCHWELLE = 30;
|
|
758
|
+
var wieBreit = function (knoten) {
|
|
759
|
+
var n = 0;
|
|
760
|
+
S.keysSorted.forEach(function (k0) { if (k0.indexOf(knoten + '.') === 0) { n++; } });
|
|
761
|
+
return n;
|
|
762
|
+
};
|
|
763
|
+
var drinSchon = function (was) {
|
|
764
|
+
return muster.some(function (m0) {
|
|
765
|
+
var p0 = m0.slice(-2) === '.*' ? m0.slice(0, -2) : m0;
|
|
766
|
+
return was === p0 || was.indexOf(p0 + '.') === 0;
|
|
767
|
+
});
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
/* Sparten desselben Geraets zu einem Muster zusammenfassen.
|
|
771
|
+
|
|
772
|
+
Ein Tasmota-Alias liest aus `…Licht.stat`, `…Licht.tele` und
|
|
773
|
+
`…Licht.cmnd` — drei Kanaele, ein Geraet, und in der Kopfleiste
|
|
774
|
+
gehoert dort „ein Abo" zu stehen, nicht drei. Zusammengefasst wird
|
|
775
|
+
aber nur, wo wirklich MEHRERE Kanaele unter demselben Knoten
|
|
776
|
+
haengen: Sonst waere aus `…Garten.Weihnachten_aussen` schlicht
|
|
777
|
+
`…Garten` geworden — breiter als noetig, ohne dass etwas
|
|
778
|
+
zusammenfaellt. */
|
|
779
|
+
for (var runde = 0; runde < 4; runde++) {
|
|
780
|
+
var unter = {};
|
|
781
|
+
kanaele.forEach(function (kn) {
|
|
782
|
+
var t = kn.split('.');
|
|
783
|
+
if (t.length <= 3) { return; }
|
|
784
|
+
var ob = t.slice(0, -1).join('.');
|
|
785
|
+
(unter[ob] = unter[ob] || []).push(kn);
|
|
786
|
+
});
|
|
787
|
+
var geaendert = false;
|
|
788
|
+
Object.keys(unter).forEach(function (ob) {
|
|
789
|
+
if (unter[ob].length < 2 || wieBreit(ob) > SCHWELLE) { return; }
|
|
790
|
+
kanaele = kanaele.filter(function (kn) { return unter[ob].indexOf(kn) === -1; });
|
|
791
|
+
if (kanaele.indexOf(ob) === -1) { kanaele.push(ob); }
|
|
792
|
+
geaendert = true;
|
|
793
|
+
});
|
|
794
|
+
if (!geaendert) { break; }
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
|
|
798
|
+
|
|
799
|
+
Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
|
|
800
|
+
Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
|
|
801
|
+
unter `SmartHome`, und `SmartHome.*` sind 320 Objekte (gemessen
|
|
802
|
+
09.09.2026; vorher wurde daraus sogar `mqtt-client.0.*` mit 322).
|
|
803
|
+
Ab dieser Schwelle werden die Quellen einzeln abonniert — ein paar
|
|
804
|
+
Abos mehr, dafuer genau die, deren Werte auch angezeigt werden. */
|
|
805
|
+
kanaele.forEach(function (kn) {
|
|
806
|
+
if (drinSchon(kn)) { return; }
|
|
807
|
+
if (wieBreit(kn) > SCHWELLE) { return; }
|
|
808
|
+
muster.push(kn + '.*');
|
|
809
|
+
});
|
|
810
|
+
/* Was jetzt noch von keinem Muster gedeckt ist, kommt einzeln. */
|
|
811
|
+
kindZustaende(id).forEach(function (kid) {
|
|
812
|
+
var q = aliasQuellen(S.objects[kid]);
|
|
813
|
+
[q.read, q.write].forEach(function (x) {
|
|
814
|
+
if (x && !drinSchon(x) && muster.indexOf(x) === -1) { muster.push(x); }
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
S.abo = muster;
|
|
819
|
+
muster.forEach(function (m) { socket.emit('subscribe', m); });
|
|
820
|
+
zeigeAbozahl();
|
|
579
821
|
|
|
580
822
|
/* Erst die Werte, dann der Vorschlag. Die Erkennung schaut in den
|
|
581
823
|
Rohwert von tele.SENSOR hinein — ohne Werte haelt sie ein Geraet
|
|
@@ -600,7 +842,17 @@ export function waehle(id, fertig) {
|
|
|
600
842
|
var werteDa = false;
|
|
601
843
|
holeWerte(id, quellen, function () {
|
|
602
844
|
werteDa = true;
|
|
603
|
-
if (id !== S.current) {
|
|
845
|
+
if (id !== S.current) {
|
|
846
|
+
/* Der Knoten hat gewechselt, waehrend die Werte unterwegs waren.
|
|
847
|
+
Am Entwurf ist hier nichts mehr zu tun — der Rueckruf muss aber
|
|
848
|
+
trotzdem kommen. Sonst bleibt `so59Laeuft` oder `abfrageLaeuft`
|
|
849
|
+
fuer immer gesetzt (der Knopf steht dauerhaft auf „laeuft"), und
|
|
850
|
+
der Trockenlauf nach dem Anlegen fehlender Sendepunkte geht nie
|
|
851
|
+
auf. Zwoelf Sekunden Wartezeit bei einer SetOption59-Abfrage
|
|
852
|
+
sind reichlich Gelegenheit dafuer (09.09.2026). */
|
|
853
|
+
if (typeof fertig === 'function') { fertig(); }
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
604
856
|
|
|
605
857
|
/* Wer schon Hand angelegt hat, behaelt seinen Entwurf.
|
|
606
858
|
|
|
@@ -812,13 +1064,40 @@ export function geraeteDarunter(wurzel) {
|
|
|
812
1064
|
angelegt" und erzeugt beim Klick ein zweites Geraet, waehrend das
|
|
813
1065
|
erste verwaist stehenbleibt. Beim Karbonator lag der Alias unter
|
|
814
1066
|
alias.0.Strom, vorgeschlagen wurde alias.0.SmartHome. */
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1067
|
+
/* Rueckwaertskarte Quelle -> Alias, einmal je Indexstand.
|
|
1068
|
+
|
|
1069
|
+
`aliasFuer` lief zweimal ueber alle Kennungen — und wird je
|
|
1070
|
+
Neuzeichnen mehrfach gerufen. Die Karte entsteht bei der ersten Frage
|
|
1071
|
+
nach einem Indexwechsel und beantwortet alle weiteren aus dem
|
|
1072
|
+
Gedaechtnis. Erkannt wird der Wechsel an Laenge und erster Kennung;
|
|
1073
|
+
`indexNeu()` sortiert ohnehin bei jeder Aenderung neu. */
|
|
1074
|
+
var alsQuelleKarte = null, alsQuelleStand = '';
|
|
1075
|
+
function alsQuelleKarten() {
|
|
1076
|
+
var stand = S.keysSorted.length + '|' + (S.keysSorted[0] || '');
|
|
1077
|
+
if (alsQuelleKarte && alsQuelleStand === stand) { return alsQuelleKarte; }
|
|
1078
|
+
var fest = {}, zweig = {};
|
|
1079
|
+
mitVorspann('alias.').forEach(function (id) {
|
|
819
1080
|
var o = S.objects[id];
|
|
820
|
-
if (o
|
|
1081
|
+
if (!o) { return; }
|
|
1082
|
+
if (o.native && o.native.quelle && fest[o.native.quelle] === undefined) {
|
|
1083
|
+
fest[o.native.quelle] = id;
|
|
1084
|
+
}
|
|
1085
|
+
if (o.type !== 'state') { return; }
|
|
1086
|
+
var q = aliasQuellen(o);
|
|
1087
|
+
[q.read, q.write].forEach(function (x) {
|
|
1088
|
+
if (!x) { return; }
|
|
1089
|
+
var kanal = id.split('.').slice(0, -1).join('.');
|
|
1090
|
+
(zweig[x] = zweig[x] || []).push(kanal);
|
|
1091
|
+
});
|
|
821
1092
|
});
|
|
1093
|
+
alsQuelleKarte = { fest: fest, zweig: zweig };
|
|
1094
|
+
alsQuelleStand = stand;
|
|
1095
|
+
return alsQuelleKarte;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
export function aliasFuer(quelle) {
|
|
1099
|
+
var karte = alsQuelleKarten();
|
|
1100
|
+
var treffer = karte.fest[quelle] || null;
|
|
822
1101
|
if (treffer) { return treffer; }
|
|
823
1102
|
|
|
824
1103
|
/* Welcher Aliaskanal liest oder schreibt am haeufigsten in diesen
|
|
@@ -827,17 +1106,19 @@ export function aliasFuer(quelle) {
|
|
|
827
1106
|
erste Treffer. */
|
|
828
1107
|
var zaehler = {}, bester = null, wieviel = 0;
|
|
829
1108
|
var pre = quelle + '.';
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
if (
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1109
|
+
/* Aus der Karte: welche Aliaskanaele lesen oder schreiben in diesen
|
|
1110
|
+
Zweig? Die Quellen stehen dort als volle Kennung, gesucht ist alles
|
|
1111
|
+
darunter — also die Kennungen des Zweigs abklappern statt aller
|
|
1112
|
+
Aliase. */
|
|
1113
|
+
var karte2 = alsQuelleKarten();
|
|
1114
|
+
var kanaele = [];
|
|
1115
|
+
Object.keys(karte2.zweig).forEach(function (q0) {
|
|
1116
|
+
if (q0.indexOf(pre) !== 0) { return; }
|
|
1117
|
+
karte2.zweig[q0].forEach(function (kanal) { kanaele.push(kanal); });
|
|
1118
|
+
});
|
|
1119
|
+
kanaele.forEach(function (kanal) {
|
|
1120
|
+
zaehler[kanal] = (zaehler[kanal] || 0) + 1;
|
|
1121
|
+
if (zaehler[kanal] > wieviel) { wieviel = zaehler[kanal]; bester = kanal; }
|
|
841
1122
|
});
|
|
842
1123
|
return bester;
|
|
843
1124
|
}
|
|
@@ -897,9 +1178,7 @@ export function bestandVorrang(e, aliasId) {
|
|
|
897
1178
|
s.f = (typeof a.read === 'string') ? a.read : '';
|
|
898
1179
|
s.fw = (typeof a.write === 'string') ? a.write : '';
|
|
899
1180
|
if (q.read) { s.srcR = q.read; }
|
|
900
|
-
s.srcW =
|
|
901
|
-
? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
|
|
902
|
-
: (q.write || '');
|
|
1181
|
+
s.srcW = schreibQuelle(o);
|
|
903
1182
|
s.on = true;
|
|
904
1183
|
s.ausBestand = true;
|
|
905
1184
|
});
|
|
@@ -936,9 +1215,7 @@ export function bestandsAbweichung(s, aliasId) {
|
|
|
936
1215
|
f: (typeof a.read === 'string') ? a.read : '',
|
|
937
1216
|
fw: (typeof a.write === 'string') ? a.write : '',
|
|
938
1217
|
srcR: q.read || '',
|
|
939
|
-
srcW:
|
|
940
|
-
? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
|
|
941
|
-
: (q.write || '')
|
|
1218
|
+
srcW: schreibQuelle(o)
|
|
942
1219
|
};
|
|
943
1220
|
var raus = [];
|
|
944
1221
|
['role', 'typ', 'unit', 'f', 'fw', 'srcR', 'srcW'].forEach(function (k) {
|
|
@@ -948,13 +1225,6 @@ export function bestandsAbweichung(s, aliasId) {
|
|
|
948
1225
|
return raus;
|
|
949
1226
|
}
|
|
950
1227
|
|
|
951
|
-
/* Einen einzelnen Wert aus dem gespeicherten Alias zurueckholen. */
|
|
952
|
-
export function bestandsWert(s, aliasId, feld) {
|
|
953
|
-
var abw = bestandsAbweichung(s, aliasId);
|
|
954
|
-
var tr0 = null;
|
|
955
|
-
abw.forEach(function (x) { if (x.feld === feld) { tr0 = x; } });
|
|
956
|
-
return tr0 ? tr0.bestand : null;
|
|
957
|
-
}
|
|
958
1228
|
|
|
959
1229
|
/* Wo weicht die Vorlage vom Bestand ab? Liefert je Zeile die Felder, die
|
|
960
1230
|
„Vorlage anwenden" aendern wuerde - die Beschriftung ausgenommen. */
|
package/admin/js/enums.js
CHANGED
|
@@ -9,15 +9,58 @@ import { socket } from './verbindung.js';
|
|
|
9
9
|
suchen haben. */
|
|
10
10
|
export var enums = {};
|
|
11
11
|
|
|
12
|
+
/* Wissen wir gerade, was in den Aufzaehlungen steht?
|
|
13
|
+
|
|
14
|
+
Das ist keine Feinheit, sondern die Bedingung dafuer, ueberhaupt eine
|
|
15
|
+
schreiben zu duerfen. Frueher setzte ein Ladefehler `enums` auf `{}`
|
|
16
|
+
und verschwieg es. Danach hielt die Werkbank jede Aufzaehlung fuer
|
|
17
|
+
nicht vorhanden - der Katalog bot `garden` weiter an, weil der
|
|
18
|
+
Dublettenfilter gegen nichts pruefte, und `enumAenderungen` baute sie
|
|
19
|
+
als NEU aus der Vorlage. `setObject` ersetzt ein Objekt vollstaendig:
|
|
20
|
+
Gemessen 09.09.2026 am Testsystem verlor `enum.rooms.garden` dabei
|
|
21
|
+
drei Mitglieder und behielt eines, dazu die Herkunftsmarke, die ihn
|
|
22
|
+
spaeter auch noch loeschbar macht. Bei Ricardo waeren das die aus der
|
|
23
|
+
CCU gespiegelten Raeume.
|
|
24
|
+
|
|
25
|
+
Solange diese Marke steht, wird keine Aufzaehlung angefasst. */
|
|
26
|
+
var unbekannt = false;
|
|
27
|
+
export function enumsUnbekannt() { return unbekannt; }
|
|
28
|
+
|
|
29
|
+
/* Ein Aussetzer soll die Werkbank nicht fuer den Rest der Sitzung
|
|
30
|
+
lahmlegen. Dreimal mit wachsendem Abstand, dann ist Schluss - eine
|
|
31
|
+
Schleife ohne Ende waere schlimmer als der Fehler. */
|
|
32
|
+
var versuch = 0;
|
|
33
|
+
var wiederTimer = null;
|
|
34
|
+
|
|
12
35
|
export function ladeEnums(danach) {
|
|
36
|
+
if (wiederTimer) { clearTimeout(wiederTimer); wiederTimer = null; }
|
|
13
37
|
socket.emit('getObjectView', 'system', 'enum',
|
|
14
|
-
{ startkey: 'enum.', endkey: 'enum
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
38
|
+
{ startkey: 'enum.', endkey: 'enum.香' }, function (err, doc) {
|
|
39
|
+
if (err || !doc || !doc.rows) {
|
|
40
|
+
/* Den alten Stand behalten. Er ist vielleicht veraltet, aber
|
|
41
|
+
veraltet ist naeher an der Wahrheit als leer. */
|
|
42
|
+
unbekannt = true;
|
|
43
|
+
console.warn('[alias-workbench] Aufzaehlungen nicht geladen: ' +
|
|
44
|
+
(err || 'keine Antwort'));
|
|
45
|
+
if (versuch < 3) {
|
|
46
|
+
versuch++;
|
|
47
|
+
wiederTimer = setTimeout(function () {
|
|
48
|
+
wiederTimer = null;
|
|
49
|
+
ladeEnums();
|
|
50
|
+
}, versuch * 4000);
|
|
51
|
+
}
|
|
52
|
+
if (typeof danach === 'function') { danach(err || 'leer'); }
|
|
53
|
+
return;
|
|
20
54
|
}
|
|
21
|
-
|
|
55
|
+
/* Erst sammeln, dann tauschen: ein Fehler mittendrin liesse sonst
|
|
56
|
+
einen halben Vorrat stehen. */
|
|
57
|
+
var frisch = {};
|
|
58
|
+
doc.rows.forEach(function (r) {
|
|
59
|
+
if (r.value && r.id) { frisch[r.id] = r.value; }
|
|
60
|
+
});
|
|
61
|
+
enums = frisch;
|
|
62
|
+
unbekannt = false;
|
|
63
|
+
versuch = 0;
|
|
64
|
+
if (typeof danach === 'function') { danach(null); }
|
|
22
65
|
});
|
|
23
66
|
}
|