iobroker.alias-workbench 0.9.11 → 0.10.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.
- package/README.md +106 -4
- package/admin/css/werkbank.css +50 -6
- package/admin/js/aufzaehlungen.js +24 -8
- package/admin/js/baum.js +13 -2
- package/admin/js/detail.js +310 -89
- package/admin/js/entwurf.js +443 -197
- package/admin/js/enums.js +9 -2
- package/admin/js/ergebnis.js +148 -28
- package/admin/js/erkennung.js +52 -0
- package/admin/js/mqtt.js +175 -65
- package/admin/js/musterwahl.js +103 -26
- package/admin/js/objekte.js +16 -0
- package/admin/js/pruefungen.js +10 -8
- package/admin/js/quellentausch.js +59 -12
- package/admin/js/schreiben.js +76 -43
- package/admin/js/vorlagen.js +28 -5
- package/admin/js/vorlagenblatt.js +49 -7
- package/admin/js/vorschlagen.js +60 -7
- package/admin/js/werte.js +99 -21
- package/admin/js/zielleiste.js +8 -2
- package/admin/js/zuordnung.js +79 -28
- package/admin/js/zustand.js +5 -1
- package/admin/js/zustandsliste.js +219 -42
- package/admin/sprachen/de.json +45 -18
- package/admin/sprachen/en.json +45 -18
- package/admin/tab.html +44 -44
- package/admin/vorlagen/hm-rollladen.json +3 -2
- package/admin/vorlagen/messpunkt.json +7 -2
- package/admin/vorlagen/tasmota-lampe.json +7 -2
- package/admin/vorlagen/tasmota-mehrfach.json +7 -2
- package/admin/vorlagen/tasmota-rgb.json +6 -2
- package/admin/vorlagen/tasmota-steckdose.json +7 -2
- package/io-package.json +38 -29
- package/package.json +1 -1
package/admin/js/entwurf.js
CHANGED
|
@@ -9,10 +9,10 @@ import { txt, tr } from './sprache.js';
|
|
|
9
9
|
import './enums.js';
|
|
10
10
|
import { zusatzName, setzeModus, zeichneBaum, merkeKlappstand } from './baum.js';
|
|
11
11
|
import { kindZustaende, aliasQuellen, holeWerte,
|
|
12
|
-
mitVorspann, schreibQuelle, zeileAusAlias
|
|
12
|
+
mitVorspann, schreibQuelle, zeileAusAlias, istTaster as istTasterObjekt
|
|
13
13
|
} from './werte.js';
|
|
14
14
|
import { erkenneEntwurf } from './erkennung.js';
|
|
15
|
-
import { vorschlag , pruefeVorlage, zeigeAbozahl } from './vorlagen.js';
|
|
15
|
+
import { vorschlag , pruefeVorlage, zeigeAbozahl, setzeInstanz } from './vorlagen.js';
|
|
16
16
|
|
|
17
17
|
import { zeichneErgebnis } from './ergebnis.js';
|
|
18
18
|
|
|
@@ -75,6 +75,21 @@ export function baueEntwurf(kanal) {
|
|
|
75
75
|
kindZustaende(kanal).forEach(function (id) {
|
|
76
76
|
var o = S.objects[id], c = o.common || {}, a = c.alias || {}, q = aliasQuellen(o);
|
|
77
77
|
var kurzName = id.slice(kanal.length + 1);
|
|
78
|
+
/* Ein Knopfdruck hat keinen Zustand — sonst hat auch ein
|
|
79
|
+
nur-schreibender Punkt einen.
|
|
80
|
+
|
|
81
|
+
`read: false` allein reicht als Merkmal nicht. Homematic vergibt es
|
|
82
|
+
fuer beides: fuer `OLD_LEVEL` und `RAMP_STOP` (Gerätebeschreibung
|
|
83
|
+
`TYPE: ACTION` — ein Tastendruck) ebenso wie fuer `ON_TIME` und
|
|
84
|
+
`RAMP_TIME` (`TYPE: FLOAT` — eine Vorgabe fuer den naechsten
|
|
85
|
+
Schaltbefehl, die stehen bleibt). Nur beim ersten ist eine
|
|
86
|
+
Lesequelle sinnlos.
|
|
87
|
+
|
|
88
|
+
Nachgemessen am 17.09.2026: einen Alias auf `1.ON_TIME` gelegt, die
|
|
89
|
+
QUELLE direkt auf 30 gesetzt — der Alias zeigte 30. `read: false`
|
|
90
|
+
sperrt das Lesen also nicht, es sagt nur, dass von allein nichts
|
|
91
|
+
kommt. Wer den Wert setzt, will ihn auch sehen. */
|
|
92
|
+
var istTaster = istTasterObjekt(o);
|
|
78
93
|
e.states.push({
|
|
79
94
|
/* Bei einer Quelle wird aus stat.POWER der Zustand stat_POWER —
|
|
80
95
|
ein Punkt im Namen wuerde sonst eine Unterebene im Alias
|
|
@@ -93,7 +108,12 @@ export function baueEntwurf(kanal) {
|
|
|
93
108
|
dann „keine Rolle" und „nichts erkannt". Typ und Einheit hat sie
|
|
94
109
|
von jeher uebernommen; die Rolle auszunehmen war nicht zu
|
|
95
110
|
begruenden. Ueberschreiben laesst sie sich weiterhin. */
|
|
96
|
-
|
|
111
|
+
/* Ein Tastendruck laut Geraetebeschreibung bekommt die Knopfrolle,
|
|
112
|
+
wenn der Adapter ihm keine gegeben hat - an ihr haengt `read:
|
|
113
|
+
false` am Alias (`knopfZeile`). hm-rpc gibt `OLD_LEVEL`
|
|
114
|
+
`value.dimmer` und `INSTALL_TEST` `indicator`; die uebrigen
|
|
115
|
+
(PRESS_SHORT, STOP, RAMP_STOP) tragen schon `button…`. */
|
|
116
|
+
role: (istTaster && !/^button/.test(String(c.role || ''))) ? 'button' : (c.role || ''),
|
|
97
117
|
typ: c.type || '',
|
|
98
118
|
unit: c.unit || '',
|
|
99
119
|
/* Ohne die Werteliste bleibt jeder Platz mit statesDefined leer —
|
|
@@ -109,7 +129,37 @@ export function baueEntwurf(kanal) {
|
|
|
109
129
|
nichts abzuholen ist (K23). Dieselbe Ablesung wie in
|
|
110
130
|
`zeileAusAlias` — sonst zeigte „Alias bearbeiten" eine Quelle,
|
|
111
131
|
die „Alias anlegen" nie eingetragen hat. */
|
|
112
|
-
|
|
132
|
+
/* An der Quelle ist der Punkt seine eigene Lesequelle — ausser er
|
|
133
|
+
sagt selbst, dass dort nichts ankommt.
|
|
134
|
+
|
|
135
|
+
`read: false` heisst nicht „gesperrt": lesen kann den Punkt
|
|
136
|
+
jeder, er liegt in der Datenbank. Es heisst, dass nie eine
|
|
137
|
+
Aktualisierung eintrifft. Was drinsteht, ist der Wert, den
|
|
138
|
+
zuletzt jemand hineingeschrieben hat — meist der Anfangswert
|
|
139
|
+
beim Adapterstart. Ein Alias, der daraus liest, zeigt fuer immer
|
|
140
|
+
diesen einen Wert und sieht dabei aus wie ein Messwert.
|
|
141
|
+
|
|
142
|
+
Bei Homematic steht es in der Geraetebeschreibung: `OPERATIONS`
|
|
143
|
+
ist eine Bitmaske (1 lesen, 2 schreiben, 4 meldet von selbst),
|
|
144
|
+
und `2.OLD_LEVEL` traegt 2 bei `TYPE: ACTION` — ein Knopfdruck,
|
|
145
|
+
kein Zustand. Am Wohnzimmer_Dimmer sind das 15 von 43 Zeilen,
|
|
146
|
+
jede davon las und schrieb bisher auf denselben Befehlspunkt
|
|
147
|
+
(Ricardo, 17.09.2026).
|
|
148
|
+
|
|
149
|
+
Ist der Punkt beschreibbar, wird daraus also ein Taster: keine
|
|
150
|
+
Lesequelle, nur ein Schreibziel. Genau die Form, die K23/K26 am
|
|
151
|
+
Rollladen-STOP beschreiben — der Alias bekommt `read: false`,
|
|
152
|
+
und vis, Alexa und matter zeichnen einen Knopf statt eines
|
|
153
|
+
Schalters, der ewig auf „aus" steht. Kann der Punkt auch nicht
|
|
154
|
+
schreiben, bleibt er, wie er ist: dann ist die fehlende Meldung
|
|
155
|
+
wirklich ein Mangel, und die Marke sagt es. */
|
|
156
|
+
/* Seit 19.09.2026 bekommt auch der Taster seine Lesequelle: der
|
|
157
|
+
Alias sieht dann jeden Druck, auch einen von aussen. Zum Knopf
|
|
158
|
+
wird er trotzdem - `read: false` haengt jetzt an der Rolle
|
|
159
|
+
(`knopfZeile` in werte.js), nicht mehr an der leeren Lesequelle.
|
|
160
|
+
Was oben ueber `read: false` an der Quelle steht, ist damit
|
|
161
|
+
Geschichte: gemessen liest ein solcher Alias tadellos. */
|
|
162
|
+
srcR: istAlias ? (q.read || '') : id,
|
|
113
163
|
/* Steht in `common.alias.id` ein einzelner Text, gilt er fuer Lesen
|
|
114
164
|
UND Schreiben - dann ist die Schreibquelle dieselbe wie die
|
|
115
165
|
Lesequelle. Hier stand frueher eine leere Zeichenkette, gedacht
|
|
@@ -364,7 +414,11 @@ export function quelleVon(aliasId) {
|
|
|
364
414
|
kindZustaende(aliasId).forEach(function (id) {
|
|
365
415
|
var qq = aliasQuellen(S.objects[id]);
|
|
366
416
|
[qq.read, qq.write].forEach(function (x) {
|
|
367
|
-
|
|
417
|
+
/* Nur Quellen, die es gibt. Zeigt jeder Punkt ins Leere, fand der
|
|
418
|
+
Rueckfall darunter („eine Ebene hoeher") den naechsten VORHANDENEN
|
|
419
|
+
Ordner - und „zur Quelle" sprang auf `0_userdata.0.wbfake` mit 31
|
|
420
|
+
fremden Geraeten statt auf die verschwundene Quelle (D10). */
|
|
421
|
+
if (!x || !S.objects[x]) { return; }
|
|
368
422
|
var t = String(x).split('.');
|
|
369
423
|
t.pop();
|
|
370
424
|
var eltern = t.join('.');
|
|
@@ -565,8 +619,30 @@ export function steuerKanaele(dev) {
|
|
|
565
619
|
if (!S.objects[id] || S.objects[id].type !== 'channel') { return; }
|
|
566
620
|
var e2 = baueEntwurf(id);
|
|
567
621
|
if (!e2.states.length) { return; }
|
|
568
|
-
|
|
569
|
-
|
|
622
|
+
/* Auch Informationskanaele zaehlen mit (Ricardo, 17.09.2026).
|
|
623
|
+
|
|
624
|
+
Vorher fielen sie hier heraus — und damit sagte der Knopf am
|
|
625
|
+
Geschirrspueler „3 Stueck", waehrend im Baum daneben sechs Kanaele
|
|
626
|
+
standen. Wer `general` einzeln anklickt, bekommt laengst einen
|
|
627
|
+
Entwurf („Information ✓ 7/14 belegt") und kann ihn anlegen; nur
|
|
628
|
+
der Sammelweg liess ihn aus. Zwei Wege, zwei Antworten am selben
|
|
629
|
+
Geraet.
|
|
630
|
+
|
|
631
|
+
Der Zweck dieses Wegs ist, die Struktur der Quelle abzubilden —
|
|
632
|
+
dann gehoert jeder Kanal hinein, der fuer sich einen Entwurf
|
|
633
|
+
ergibt, und nicht nur der, den der Detektor fuer ein Geraet haelt.
|
|
634
|
+
Nebenbei faellt damit das Verteilen der „gemeinsamen" Kanaele in
|
|
635
|
+
den meisten Faellen weg: was einen eigenen Ordner bekommt, muss
|
|
636
|
+
nicht mehr in jeden anderen hineinkopiert werden (produktiv
|
|
637
|
+
gemessen: beim Maeher wanderten 206 Rohdatenpunkte in jeden der
|
|
638
|
+
vier Entwuerfe, beim Shelly SHEM-3 galten die drei Emeter-Phasen
|
|
639
|
+
als „gemeinsam" statt als eigene Kanaele). */
|
|
640
|
+
var alle = erkenneEntwurf(e2, null);
|
|
641
|
+
if (!alle.length) { return; }
|
|
642
|
+
/* Ein Kanal, der ein Geraet ergibt, wird danach benannt; nur wenn er
|
|
643
|
+
nichts als Information hergibt, gilt der info-Fund. */
|
|
644
|
+
var f = alle.filter(function (x) { return x.type !== 'info'; });
|
|
645
|
+
if (!f.length) { f = alle; }
|
|
570
646
|
/* Nur zaehlen, was einen Pflichtplatz wirklich besetzt — sonst
|
|
571
647
|
gilt jeder Kanal mit irgendeinem Messwert als Geraet. */
|
|
572
648
|
/* Alles, was der Kanal an Plaetzen belegt — nicht nur die
|
|
@@ -590,92 +666,131 @@ export function steuerKanaele(dev) {
|
|
|
590
666
|
return raus;
|
|
591
667
|
}
|
|
592
668
|
|
|
593
|
-
/*
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
669
|
+
/* Welche Unterordner entstehen, wenn die Struktur der Quelle abgebildet
|
|
670
|
+
wird — und zwar nach den HAKEN, nicht nach einer Vermutung.
|
|
671
|
+
|
|
672
|
+
Bis 17.09.2026 baute dieser Weg je Kanal einen frischen Entwurf und
|
|
673
|
+
hakte an, was das Muster des Kanals hergab. Damit entstand, was
|
|
674
|
+
niemand ausgewaehlt hatte: am `MISCHKANAL` bekam der Wartungskanal
|
|
675
|
+
`LOW_BAT`, `RSSI_DEVICE` und `UNREACH`, obwohl oben nur `UNREACH`
|
|
676
|
+
angehakt war. Umgekehrt fiel weg, was jemand von Hand angehakt hatte —
|
|
677
|
+
der Entwurf wurde ja gar nicht gelesen (Ricardo, 17.09.2026).
|
|
678
|
+
|
|
679
|
+
Jetzt gilt: was angehakt ist, bestimmt beides — welche Ordner es gibt
|
|
680
|
+
und was darin steht. Ein Kanal ohne angehakte Zeile kommt nicht vor.
|
|
681
|
+
Zeilen, deren Quelle direkt am Geraet haengt oder von woanders kommt,
|
|
682
|
+
bleiben im Behaelter selbst (`eigen`). */
|
|
683
|
+
export function kanalGruppen(e) {
|
|
684
|
+
if (!e || !e.kanal || e.kanal.indexOf('alias.') === 0) { return []; }
|
|
685
|
+
var basis = e.kanal + '.';
|
|
686
|
+
var gruppen = [], nach = {}, eigen = [];
|
|
687
|
+
(e.states || []).forEach(function (st) {
|
|
688
|
+
if (!st.on || !st.n) { return; }
|
|
689
|
+
var quelle = st.srcR || st.srcW || '';
|
|
690
|
+
var rest = (quelle.indexOf(basis) === 0) ? quelle.slice(basis.length) : '';
|
|
691
|
+
var p = rest.indexOf('.');
|
|
692
|
+
/* Ohne Punkt liegt der Punkt direkt am Geraet; ohne `rest` liest die
|
|
693
|
+
Zeile von woanders. Beides gehoert in den Behaelter, nicht in einen
|
|
694
|
+
erfundenen Unterordner. */
|
|
695
|
+
if (!rest || p < 0) { eigen.push(st); return; }
|
|
696
|
+
var kid = e.kanal + '.' + rest.slice(0, p);
|
|
697
|
+
if (!S.objects[kid]) { eigen.push(st); return; }
|
|
698
|
+
if (!nach[kid]) {
|
|
699
|
+
var kn = eigenerKanalName(kid);
|
|
700
|
+
var kurz = rest.slice(0, p);
|
|
701
|
+
nach[kid] = { id: kid, kurz: kurz, name: kn || kurz,
|
|
702
|
+
kennung: kennungtauglich(kn || '') || kurz, zeilen: [] };
|
|
703
|
+
gruppen.push(nach[kid]);
|
|
704
|
+
}
|
|
705
|
+
nach[kid].zeilen.push(st);
|
|
612
706
|
});
|
|
613
|
-
|
|
707
|
+
if (eigen.length) {
|
|
708
|
+
gruppen.unshift({ id: e.kanal, kurz: '', name: '', kennung: '',
|
|
709
|
+
eigen: true, zeilen: eigen });
|
|
710
|
+
}
|
|
711
|
+
return gruppen;
|
|
614
712
|
}
|
|
615
713
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
714
|
+
/* Der Entwurf ohne jede Vorlage — alle Punkte des Geraets, alle
|
|
715
|
+
angehakt, dazu das erkannte Muster als Anhaltspunkt.
|
|
716
|
+
|
|
717
|
+
Gebraucht fuer „— keine Vorlage —" in der Vorlagenwahl. Bis zum
|
|
718
|
+
17.09.2026 liess sich eine erkannte Vorlage nicht abwaehlen: der erste
|
|
719
|
+
Eintrag der Liste war eine blosse Ueberschrift, sein Wert leer, und der
|
|
720
|
+
Zweig stieg bei leerem Wert sofort aus. Wechseln ging, weglassen nicht
|
|
721
|
+
(Ricardo).
|
|
722
|
+
|
|
723
|
+
Zuerst hakte dieser Weg an, was im erkannten Muster einen Platz hat.
|
|
724
|
+
Das ging an zwei GLEICHARTIGEN Kanaelen schief: am `hm.ZWEIKANAL`
|
|
725
|
+
nahm der Detektor einen der beiden Ausgaenge — und zwar den zweiten —,
|
|
726
|
+
`Licht_Esstisch` stand angehakt da und `Licht_Bar` leer. Welcher es
|
|
727
|
+
wird, ist Zufall, und danach zu raten ist nicht die Aufgabe eines
|
|
728
|
+
Entwurfs OHNE Vorlage. Wer sie abwaehlt, will die Punkte selbst
|
|
729
|
+
aussuchen; also stehen alle da (Ricardo, 17.09.2026). */
|
|
730
|
+
export function rohEntwurf(id) {
|
|
619
731
|
var e2 = baueEntwurf(id);
|
|
620
732
|
if (!e2.states.length) { return null; }
|
|
621
|
-
|
|
622
|
-
/* Die Wartungsdaten des gemeinsamen Kanals gehoeren in jedes Geraet,
|
|
623
|
-
nicht in eines davon oder in einen Kanal daneben.
|
|
624
|
-
|
|
625
|
-
Ein Alias-Punkt ist ein Verweis, kein Wert — zwei Geraete duerfen
|
|
626
|
-
auf dieselbe Quelle zeigen, und der Batteriestand des Funkmoduls
|
|
627
|
-
gilt fuer beide Lampen gleichermassen. Lagerte man ihn in einen
|
|
628
|
-
eigenen `info`-Kanal aus, haette keine der beiden eine
|
|
629
|
-
Batteriewarnung: der Detektor schaut nur innerhalb eines Kanals.
|
|
630
|
-
Genau so macht es die Tasmota-Mehrfachvorlage mit RSSI — jeder der
|
|
631
|
-
vier Ausgaenge bekommt seinen eigenen, alle lesen aus tele/STATE. */
|
|
632
|
-
(gemeinsam || []).forEach(function (gid) {
|
|
633
|
-
kindZustaende(gid).forEach(function (qid) {
|
|
634
|
-
var o = S.objects[qid]; if (!o) { return; }
|
|
635
|
-
var c = o.common || {};
|
|
636
|
-
var kurz = qid.slice(gid.length + 1).replace(/\./g, '_');
|
|
637
|
-
/* Traegt der Kanal selbst schon einen Punkt dieses Namens, den
|
|
638
|
-
gemeinsamen unterscheidbar machen. */
|
|
639
|
-
if (e2.states.some(function (x) { return x.n === kurz; })) {
|
|
640
|
-
kurz = gid.split('.').pop() + '_' + kurz;
|
|
641
|
-
}
|
|
642
|
-
e2.states.push({
|
|
643
|
-
n: kurz, on: false, role: c.role || '', typ: c.type || '',
|
|
644
|
-
unit: c.unit || '', states: c.states || undefined, wr: false,
|
|
645
|
-
srcR: qid, srcW: '', f: '', fw: '', caption: '', manuell: false,
|
|
646
|
-
gemeinsam: true
|
|
647
|
-
});
|
|
648
|
-
});
|
|
649
|
-
});
|
|
650
733
|
var f = erkenneEntwurf(e2, null);
|
|
651
734
|
e2.wantAuto = f.length ? f[0].type : null;
|
|
652
735
|
e2.want = e2.wantAuto;
|
|
653
|
-
var funde = e2.want ? erkenneEntwurf(e2, e2.want) : f;
|
|
654
|
-
var haupt = funde.length ? funde[0] : null;
|
|
655
|
-
var platz = {};
|
|
656
|
-
if (haupt) {
|
|
657
|
-
haupt.states.forEach(function (x) {
|
|
658
|
-
if (x.id) { platz[x.id.slice(id.length + 1)] = x.name; }
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
if (Object.keys(platz).length) {
|
|
662
|
-
e2.states.forEach(function (st) { st.on = !!platz[st.n]; });
|
|
663
|
-
}
|
|
664
|
-
/* Zum Schluss die Wartungspunkte, die auf der Geraeteebene einen Platz
|
|
665
|
-
gefunden haben. Warum nicht einfach den Detektor auf dem Kanal
|
|
666
|
-
fragen? Weil er dort nur SET und WORKING zuordnet, obwohl dieselben
|
|
667
|
-
Punkte mit denselben Rollen im Abbild stehen — auf der Geraeteebene
|
|
668
|
-
findet er LOWBAT, UNREACH und RSSI. Die Zuordnung von dort ist die
|
|
669
|
-
belastbare; sie stammt aus demselben Detektor, nur aus dem Lauf, der
|
|
670
|
-
sie sieht. */
|
|
671
|
-
if (gemAn) {
|
|
672
|
-
e2.states.forEach(function (st) {
|
|
673
|
-
if (st.gemeinsam && gemAn[st.srcR]) { st.on = true; }
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
736
|
return merkeHakenVorgabe(e2);
|
|
677
737
|
}
|
|
678
738
|
|
|
739
|
+
/* Wohin die Unterordner kommen — der Behaelter fuer das Geraet.
|
|
740
|
+
|
|
741
|
+
Beim ersten Mal ist das schlicht `zielOrdner` plus Geraetename. Nach
|
|
742
|
+
dem Aufteilen gibt es aber mehrere Aliase zu derselben Quelle, je Kanal
|
|
743
|
+
einen, und `aliasFuer` liefert einen davon — den mit den meisten
|
|
744
|
+
Punkten. Der Behaelter waere dann dieser Unterordner, und der zweite
|
|
745
|
+
Druck haengte alles eine Ebene tiefer: aus
|
|
746
|
+
`alias.0.Geschirrspueler.Befehle` wurde
|
|
747
|
+
`alias.0.Geschirrspueler.Allgemeine_Informationen.Befehle`, beim
|
|
748
|
+
dritten Druck noch eine Ebene tiefer (17.09.2026).
|
|
749
|
+
|
|
750
|
+
Woran man es erkennt: das gefundene Ziel fuehrt in `native.quelle`
|
|
751
|
+
einen KANAL der Quelle, nicht das Geraet. Dann ist es selbst ein
|
|
752
|
+
Unterordner aus einem frueheren Durchgang, und der Behaelter ist sein
|
|
753
|
+
Elternknoten. */
|
|
754
|
+
export function aufteilBehaelter(e) {
|
|
755
|
+
var ordner = (e.zielOrdner || 'alias.0') + '.' + e.zielName;
|
|
756
|
+
var ziel = e.ziel;
|
|
757
|
+
if (!ziel) { return ordner; }
|
|
758
|
+
var q = ((S.objects[ziel] && S.objects[ziel].native) || {}).quelle;
|
|
759
|
+
if (q && e.kanal && q.indexOf(e.kanal + '.') === 0) {
|
|
760
|
+
var p = ziel.lastIndexOf('.');
|
|
761
|
+
if (p > 0) { return ziel.slice(0, p); }
|
|
762
|
+
}
|
|
763
|
+
return ordner;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/* Aus einer Gruppe wird ein Entwurf: dieselben Zeilen, nur auf den Kanal
|
|
767
|
+
bezogen benannt. Was jemand an einer Zeile geaendert hat — Rolle, Typ,
|
|
768
|
+
Einheit, Formel, Beschriftung — wandert unveraendert mit; es waere
|
|
769
|
+
sonderbar, eine Handeingabe beim Aufteilen wegzuwerfen. */
|
|
770
|
+
export function teilEntwurf(e, g) {
|
|
771
|
+
var v = { kanal: g.id, states: [], want: null, wantAuto: null,
|
|
772
|
+
alleMuster: false, raum: e.raum, funktion: e.funktion };
|
|
773
|
+
g.zeilen.forEach(function (st) {
|
|
774
|
+
var k = JSON.parse(JSON.stringify(st));
|
|
775
|
+
/* Der Name wird auf den Kanal bezogen: aus `commands_BSH_…` wird im
|
|
776
|
+
Kanal `Befehle` schlicht `BSH_…`. Gekuerzt wird ueber die Quelle —
|
|
777
|
+
und bei einem Taster ist das die SCHREIBquelle, denn `srcR` ist
|
|
778
|
+
dort leer. Ohne diesen Zusatz behielt genau diese Zeile ihren
|
|
779
|
+
langen Namen: `…Befehle.commands_BSH_Common_Command_StopProgram`
|
|
780
|
+
neben `…Allgemeine_Informationen.brand` (17.09.2026). */
|
|
781
|
+
var quelle = st.srcR || st.srcW || '';
|
|
782
|
+
if (!g.eigen && quelle && quelle.indexOf(g.id + '.') === 0) {
|
|
783
|
+
k.n = quelle.slice(g.id.length + 1).replace(/\./g, '_');
|
|
784
|
+
}
|
|
785
|
+
k.on = true;
|
|
786
|
+
v.states.push(k);
|
|
787
|
+
});
|
|
788
|
+
var f = erkenneEntwurf(v, null);
|
|
789
|
+
v.wantAuto = f.length ? f[0].type : null;
|
|
790
|
+
v.want = v.wantAuto;
|
|
791
|
+
return merkeHakenVorgabe(v);
|
|
792
|
+
}
|
|
793
|
+
|
|
679
794
|
/* Alle laufenden Abos abmelden.
|
|
680
795
|
|
|
681
796
|
Stand nur in `waehle` — und damit meldete kein einziger der Wege ab,
|
|
@@ -690,6 +805,108 @@ export function aboLoesen() {
|
|
|
690
805
|
zeigeAbozahl();
|
|
691
806
|
}
|
|
692
807
|
|
|
808
|
+
/* Die Quellen eines Entwurfs - alles, woraus eine Zeile liest oder
|
|
809
|
+
wohin sie schreibt. */
|
|
810
|
+
export function quellenVonEntwurf(e) {
|
|
811
|
+
var raus = [];
|
|
812
|
+
((e && e.states) || []).forEach(function (s) {
|
|
813
|
+
if (s.srcR) { raus.push(s.srcR); }
|
|
814
|
+
if (s.srcW) { raus.push(s.srcW); }
|
|
815
|
+
});
|
|
816
|
+
return raus;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/* Abonniert Quellen dazu, die rechts angezeigt werden.
|
|
820
|
+
|
|
821
|
+
Ergaenzt nur: was ein laufendes Muster schon deckt, kommt nicht noch
|
|
822
|
+
einmal dazu, doppelt rufen schadet also nicht. Die Regeln stammen aus
|
|
823
|
+
`waehle`, wo sie vorher fest verbaut waren und nur am Alias griffen.
|
|
824
|
+
|
|
825
|
+
`quellenVerteilung` fasst zusammen, was die rechte Seite als
|
|
826
|
+
„Quellen" zeigt — dafuer bildet sie den ELTERNknoten jeder Quelle.
|
|
827
|
+
Bei einem flachen MQTT-Geraet ist das der halbe Adapter: gemessen
|
|
828
|
+
09.09.2026 wurde daraus `mqtt-client.0.*` mit 322 Objekten, an einem
|
|
829
|
+
einzigen Alias. Abonniert wird deshalb der Kanal jeder wirklich
|
|
830
|
+
gelesenen Quelle. */
|
|
831
|
+
export function aboErgaenzen(quellen) {
|
|
832
|
+
var muster = (S.abo || []).slice();
|
|
833
|
+
var vorher = muster.length;
|
|
834
|
+
var drinSchon = function (was) {
|
|
835
|
+
return muster.some(function (m0) {
|
|
836
|
+
var p0 = m0.slice(-2) === '.*' ? m0.slice(0, -2) : m0;
|
|
837
|
+
return was === p0 || was.indexOf(p0 + '.') === 0;
|
|
838
|
+
});
|
|
839
|
+
};
|
|
840
|
+
var offen = [];
|
|
841
|
+
(quellen || []).forEach(function (x) {
|
|
842
|
+
if (x && !drinSchon(x) && offen.indexOf(x) === -1) { offen.push(x); }
|
|
843
|
+
});
|
|
844
|
+
if (!offen.length) { zeigeAbozahl(); return; }
|
|
845
|
+
|
|
846
|
+
var kanaele = [];
|
|
847
|
+
offen.forEach(function (x) {
|
|
848
|
+
var t = String(x).split('.');
|
|
849
|
+
t.pop();
|
|
850
|
+
var kn = t.join('.');
|
|
851
|
+
if (kn && kanaele.indexOf(kn) === -1) { kanaele.push(kn); }
|
|
852
|
+
});
|
|
853
|
+
var SCHWELLE = 30;
|
|
854
|
+
var wieBreit = function (knoten) {
|
|
855
|
+
var n = 0;
|
|
856
|
+
S.keysSorted.forEach(function (k0) { if (k0.indexOf(knoten + '.') === 0) { n++; } });
|
|
857
|
+
return n;
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
/* Sparten desselben Geraets zu einem Muster zusammenfassen.
|
|
861
|
+
|
|
862
|
+
Ein Tasmota-Alias liest aus `…Licht.stat`, `…Licht.tele` und
|
|
863
|
+
`…Licht.cmnd` — drei Kanaele, ein Geraet, und in der Kopfleiste
|
|
864
|
+
gehoert dort „ein Abo" zu stehen, nicht drei. Zusammengefasst wird
|
|
865
|
+
aber nur, wo wirklich MEHRERE Kanaele unter demselben Knoten
|
|
866
|
+
haengen: Sonst waere aus `…Garten.Weihnachten_aussen` schlicht
|
|
867
|
+
`…Garten` geworden — breiter als noetig, ohne dass etwas
|
|
868
|
+
zusammenfaellt. */
|
|
869
|
+
for (var runde = 0; runde < 4; runde++) {
|
|
870
|
+
var unter = {};
|
|
871
|
+
kanaele.forEach(function (kn) {
|
|
872
|
+
var t = kn.split('.');
|
|
873
|
+
if (t.length <= 3) { return; }
|
|
874
|
+
var ob = t.slice(0, -1).join('.');
|
|
875
|
+
(unter[ob] = unter[ob] || []).push(kn);
|
|
876
|
+
});
|
|
877
|
+
var geaendert = false;
|
|
878
|
+
Object.keys(unter).forEach(function (ob) {
|
|
879
|
+
if (unter[ob].length < 2 || wieBreit(ob) > SCHWELLE) { return; }
|
|
880
|
+
kanaele = kanaele.filter(function (kn) { return unter[ob].indexOf(kn) === -1; });
|
|
881
|
+
if (kanaele.indexOf(ob) === -1) { kanaele.push(ob); }
|
|
882
|
+
geaendert = true;
|
|
883
|
+
});
|
|
884
|
+
if (!geaendert) { break; }
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
|
|
888
|
+
|
|
889
|
+
Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
|
|
890
|
+
Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
|
|
891
|
+
unter `SmartHome`, und `SmartHome.*` sind 320 Objekte (gemessen
|
|
892
|
+
09.09.2026). Ab dieser Schwelle werden die Quellen einzeln
|
|
893
|
+
abonniert — ein paar Abos mehr, dafuer genau die, deren Werte auch
|
|
894
|
+
angezeigt werden. */
|
|
895
|
+
kanaele.forEach(function (kn) {
|
|
896
|
+
if (drinSchon(kn)) { return; }
|
|
897
|
+
if (wieBreit(kn) > SCHWELLE) { return; }
|
|
898
|
+
muster.push(kn + '.*');
|
|
899
|
+
});
|
|
900
|
+
offen.forEach(function (x) {
|
|
901
|
+
if (!drinSchon(x) && muster.indexOf(x) === -1) { muster.push(x); }
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
var neu = muster.slice(vorher);
|
|
905
|
+
S.abo = muster;
|
|
906
|
+
neu.forEach(function (m) { socket.emit('subscribe', m); });
|
|
907
|
+
zeigeAbozahl();
|
|
908
|
+
}
|
|
909
|
+
|
|
693
910
|
export function waehle(id, fertig) {
|
|
694
911
|
/* Wer links etwas anderes anklickt, will es rechts sehen - auch wenn
|
|
695
912
|
der Fokus noch in einem Feld der rechten Seite steht. `tipptGerade`
|
|
@@ -743,109 +960,28 @@ export function waehle(id, fertig) {
|
|
|
743
960
|
`alias.0.Solar.Balkon.Energie`, wo sich im Sekundentakt etwas
|
|
744
961
|
aendert, fiel es auf (Ricardo, 08.09.2026).
|
|
745
962
|
|
|
746
|
-
Deshalb am Alias zusaetzlich seine Quellen -
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
963
|
+
Deshalb am Alias zusaetzlich seine Quellen, und - seit dem
|
|
964
|
+
18.09.2026 - an der Quelle ebenso: dort ergaenzt `uebernehmeBestand`
|
|
965
|
+
die Zeilen des vorhandenen Alias, und die lesen oft woanders
|
|
966
|
+
(`Stromzaehler` zeigt drei Zeilen „aus Solar.Netz"). Die standen
|
|
967
|
+
nach dem ersten Abholen still, gemessen 428 W gegen 22 W am System
|
|
968
|
+
(J9). `aboErgaenzen` wird darum von jeder Stelle gerufen, an der
|
|
969
|
+
Zeilen mit neuen Quellen dazukommen. Eine Liste, damit beim
|
|
970
|
+
naechsten Wechsel `aboLoesen` alles abmeldet. */
|
|
752
971
|
aboLoesen();
|
|
753
|
-
|
|
972
|
+
S.abo = [id + '.*'];
|
|
973
|
+
socket.emit('subscribe', id + '.*');
|
|
754
974
|
if (id.indexOf('alias.') === 0) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
`quellenVerteilung` fasst zusammen, was die rechte Seite als
|
|
758
|
-
„Quellen" zeigt — dafuer bildet sie den ELTERNknoten jeder
|
|
759
|
-
Quelle. Bei einem flachen MQTT-Geraet ist das der halbe Adapter:
|
|
760
|
-
gemessen 09.09.2026 wurde daraus `mqtt-client.0.*` mit 322
|
|
761
|
-
Objekten, an einem einzigen Alias. Und `S.werte` wird nie
|
|
762
|
-
geleert, das sammelt sich also.
|
|
763
|
-
|
|
764
|
-
Abonniert wird deshalb der Kanal jeder wirklich gelesenen Quelle.
|
|
765
|
-
Wo die Quellen ohnehin gebuendelt liegen, bleibt es bei einem
|
|
766
|
-
Muster; wo nicht, sind es ein paar mehr — aber jedes davon eng. */
|
|
767
|
-
var kanaele = [];
|
|
768
|
-
kindZustaende(id).forEach(function (kid) {
|
|
769
|
-
var q = aliasQuellen(S.objects[kid]);
|
|
770
|
-
[q.read, q.write].forEach(function (x) {
|
|
771
|
-
if (!x) { return; }
|
|
772
|
-
var t = String(x).split('.');
|
|
773
|
-
t.pop();
|
|
774
|
-
var kn = t.join('.');
|
|
775
|
-
if (kn && kanaele.indexOf(kn) === -1) { kanaele.push(kn); }
|
|
776
|
-
});
|
|
777
|
-
});
|
|
778
|
-
/* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
|
|
779
|
-
|
|
780
|
-
Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
|
|
781
|
-
Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
|
|
782
|
-
unter `SmartHome`, und `SmartHome.*` sind 320 Objekte. Ab dieser
|
|
783
|
-
Schwelle werden die Quellen einzeln abonniert — ein paar Abos
|
|
784
|
-
mehr, dafuer genau die, deren Werte auch angezeigt werden. */
|
|
785
|
-
var SCHWELLE = 30;
|
|
786
|
-
var wieBreit = function (knoten) {
|
|
787
|
-
var n = 0;
|
|
788
|
-
S.keysSorted.forEach(function (k0) { if (k0.indexOf(knoten + '.') === 0) { n++; } });
|
|
789
|
-
return n;
|
|
790
|
-
};
|
|
791
|
-
var drinSchon = function (was) {
|
|
792
|
-
return muster.some(function (m0) {
|
|
793
|
-
var p0 = m0.slice(-2) === '.*' ? m0.slice(0, -2) : m0;
|
|
794
|
-
return was === p0 || was.indexOf(p0 + '.') === 0;
|
|
795
|
-
});
|
|
796
|
-
};
|
|
797
|
-
|
|
798
|
-
/* Sparten desselben Geraets zu einem Muster zusammenfassen.
|
|
799
|
-
|
|
800
|
-
Ein Tasmota-Alias liest aus `…Licht.stat`, `…Licht.tele` und
|
|
801
|
-
`…Licht.cmnd` — drei Kanaele, ein Geraet, und in der Kopfleiste
|
|
802
|
-
gehoert dort „ein Abo" zu stehen, nicht drei. Zusammengefasst wird
|
|
803
|
-
aber nur, wo wirklich MEHRERE Kanaele unter demselben Knoten
|
|
804
|
-
haengen: Sonst waere aus `…Garten.Weihnachten_aussen` schlicht
|
|
805
|
-
`…Garten` geworden — breiter als noetig, ohne dass etwas
|
|
806
|
-
zusammenfaellt. */
|
|
807
|
-
for (var runde = 0; runde < 4; runde++) {
|
|
808
|
-
var unter = {};
|
|
809
|
-
kanaele.forEach(function (kn) {
|
|
810
|
-
var t = kn.split('.');
|
|
811
|
-
if (t.length <= 3) { return; }
|
|
812
|
-
var ob = t.slice(0, -1).join('.');
|
|
813
|
-
(unter[ob] = unter[ob] || []).push(kn);
|
|
814
|
-
});
|
|
815
|
-
var geaendert = false;
|
|
816
|
-
Object.keys(unter).forEach(function (ob) {
|
|
817
|
-
if (unter[ob].length < 2 || wieBreit(ob) > SCHWELLE) { return; }
|
|
818
|
-
kanaele = kanaele.filter(function (kn) { return unter[ob].indexOf(kn) === -1; });
|
|
819
|
-
if (kanaele.indexOf(ob) === -1) { kanaele.push(ob); }
|
|
820
|
-
geaendert = true;
|
|
821
|
-
});
|
|
822
|
-
if (!geaendert) { break; }
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
|
|
826
|
-
|
|
827
|
-
Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
|
|
828
|
-
Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
|
|
829
|
-
unter `SmartHome`, und `SmartHome.*` sind 320 Objekte (gemessen
|
|
830
|
-
09.09.2026; vorher wurde daraus sogar `mqtt-client.0.*` mit 322).
|
|
831
|
-
Ab dieser Schwelle werden die Quellen einzeln abonniert — ein paar
|
|
832
|
-
Abos mehr, dafuer genau die, deren Werte auch angezeigt werden. */
|
|
833
|
-
kanaele.forEach(function (kn) {
|
|
834
|
-
if (drinSchon(kn)) { return; }
|
|
835
|
-
if (wieBreit(kn) > SCHWELLE) { return; }
|
|
836
|
-
muster.push(kn + '.*');
|
|
837
|
-
});
|
|
838
|
-
/* Was jetzt noch von keinem Muster gedeckt ist, kommt einzeln. */
|
|
975
|
+
var ausAlias = [];
|
|
839
976
|
kindZustaende(id).forEach(function (kid) {
|
|
840
977
|
var q = aliasQuellen(S.objects[kid]);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
});
|
|
978
|
+
if (q.read) { ausAlias.push(q.read); }
|
|
979
|
+
if (q.write) { ausAlias.push(q.write); }
|
|
844
980
|
});
|
|
981
|
+
aboErgaenzen(ausAlias);
|
|
982
|
+
} else {
|
|
983
|
+
zeigeAbozahl();
|
|
845
984
|
}
|
|
846
|
-
S.abo = muster;
|
|
847
|
-
muster.forEach(function (m) { socket.emit('subscribe', m); });
|
|
848
|
-
zeigeAbozahl();
|
|
849
985
|
|
|
850
986
|
/* Erst die Werte, dann der Vorschlag. Die Erkennung schaut in den
|
|
851
987
|
Rohwert von tele.SENSOR hinein — ohne Werte haelt sie ein Geraet
|
|
@@ -922,10 +1058,14 @@ export function waehle(id, fertig) {
|
|
|
922
1058
|
if (fest) { v.vonHandGewaehlt = false; v.ausBestand = true; }
|
|
923
1059
|
/* Gibt es den Alias schon, gilt sein Stand - in beiden Ansichten
|
|
924
1060
|
dasselbe Bild (Ricardo, 25.08.2026). */
|
|
925
|
-
|
|
1061
|
+
/* An einem Mehrfachgeraet zaehlt der Alias DIESES Ausgangs,
|
|
1062
|
+
nicht der mit den meisten Punkten (C13). */
|
|
1063
|
+
var vorhA = aliasFuer(id, ausgangsQuellen(v)) || (v.instanz === null || v.instanz === undefined ? vorh : null);
|
|
1064
|
+
if (vorhA && S.objects[vorhA]) { bestandVorrang(v, vorhA); }
|
|
926
1065
|
S.entwurf = v;
|
|
927
1066
|
}
|
|
928
1067
|
}
|
|
1068
|
+
aboErgaenzen(quellenVonEntwurf(S.entwurf));
|
|
929
1069
|
var nach = [];
|
|
930
1070
|
S.entwurf.states.forEach(function (s) {
|
|
931
1071
|
if (s.srcR && !S.werte[s.srcR]) { nach.push(s.srcR); }
|
|
@@ -1076,9 +1216,13 @@ export function geraeteDarunter(wurzel) {
|
|
|
1076
1216
|
}
|
|
1077
1217
|
});
|
|
1078
1218
|
|
|
1079
|
-
|
|
1219
|
+
/* Angezeigt wird wie im Baum: `seg_inf.10` hinter `seg_inf.9`, nicht
|
|
1220
|
+
hinter `seg_inf.1` (E45). Die Liste dient nur der Anzeige — die
|
|
1221
|
+
byteweise Ordnung von `keysSorted` bleibt unberuehrt (E42). */
|
|
1222
|
+
geraete.sort(function (a, b) { return GERAETE_VERGLEICH.compare(a.id, b.id); });
|
|
1080
1223
|
return geraete;
|
|
1081
1224
|
}
|
|
1225
|
+
var GERAETE_VERGLEICH = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
|
1082
1226
|
|
|
1083
1227
|
/* Gibt es fuer diese Quelle schon einen Alias?
|
|
1084
1228
|
|
|
@@ -1097,11 +1241,17 @@ export function geraeteDarunter(wurzel) {
|
|
|
1097
1241
|
`aliasFuer` lief zweimal ueber alle Kennungen — und wird je
|
|
1098
1242
|
Neuzeichnen mehrfach gerufen. Die Karte entsteht bei der ersten Frage
|
|
1099
1243
|
nach einem Indexwechsel und beantwortet alle weiteren aus dem
|
|
1100
|
-
Gedaechtnis.
|
|
1101
|
-
|
|
1102
|
-
|
|
1244
|
+
Gedaechtnis.
|
|
1245
|
+
|
|
1246
|
+
Erkannt wurde der Wechsel bis 19.09.2026 an Laenge und erster Kennung.
|
|
1247
|
+
Das reicht nicht: Verlegen loescht acht Objekte und legt acht neue an,
|
|
1248
|
+
die Zahl bleibt gleich, und ein Quellentausch aendert nur `alias.id`.
|
|
1249
|
+
Die Karte zeigte danach auf den geloeschten Alias, an der Quelle stand
|
|
1250
|
+
„wird neu angelegt", und ein Klick haette einen zweiten Alias erzeugt
|
|
1251
|
+
(T22). Jetzt zaehlt `S.objektStand` jede Aenderung an den Objekten. */
|
|
1252
|
+
var alsQuelleKarte = null, alsQuelleStand = -1;
|
|
1103
1253
|
function alsQuelleKarten() {
|
|
1104
|
-
var stand = S.
|
|
1254
|
+
var stand = S.objektStand;
|
|
1105
1255
|
if (alsQuelleKarte && alsQuelleStand === stand) { return alsQuelleKarte; }
|
|
1106
1256
|
var fest = {}, zweig = {};
|
|
1107
1257
|
mitVorspann('alias.').forEach(function (id) {
|
|
@@ -1123,7 +1273,75 @@ function alsQuelleKarten() {
|
|
|
1123
1273
|
return alsQuelleKarte;
|
|
1124
1274
|
}
|
|
1125
1275
|
|
|
1126
|
-
|
|
1276
|
+
/* Welche Quellen gehoeren zu genau diesem Ausgang?
|
|
1277
|
+
|
|
1278
|
+
An einem Mehrfachgeraet ist "der Alias dieses Geraets" keine
|
|
1279
|
+
beantwortbare Frage - es gibt je Ausgang einen, dazu oft einen fuer
|
|
1280
|
+
die gemeinsame Messung. Beantwortbar ist nur: welcher gehoert zu dem
|
|
1281
|
+
Ausgang, der gerade gewaehlt ist.
|
|
1282
|
+
|
|
1283
|
+
Geraten wird dabei nichts an Namen herum. Gefragt wird die Vorlage:
|
|
1284
|
+
welche ihrer Pfade tragen den Platzhalter? Genau die unterscheiden die
|
|
1285
|
+
Ausgaenge (bei `tasmota-mehrfach` `stat.POWER%N%` und `cmnd.POWER%N%`),
|
|
1286
|
+
alles andere (`tele.SENSOR`, `tele.LWT`) ist allen gemeinsam. Damit
|
|
1287
|
+
faellt auch die Falle aus I31 weg: `tele.INFO2` traegt zwar eine Zwei,
|
|
1288
|
+
aber keinen Platzhalter - es gehoert zu keinem Ausgang.
|
|
1289
|
+
|
|
1290
|
+
Fuer Ausgang 1 kommt das blanke `cmnd.POWER` dazu. Bei Tasmota ist
|
|
1291
|
+
POWER ohne Nummer ein Zweitname fuer POWER1 - am 16.09.2026 an der
|
|
1292
|
+
Steckdosenleiste nachgemessen: eine Abfrage an `cmnd/POWER` wird mit
|
|
1293
|
+
`{"POWER1":"ON"}` beantwortet, und `stat/POWER` gibt es nicht. Ein
|
|
1294
|
+
Alias, der so gebaut wurde, schaltet wirklich Ausgang 1 und gehoert
|
|
1295
|
+
dorthin. */
|
|
1296
|
+
export function ausgangsQuellen(e) {
|
|
1297
|
+
if (!e || e.instanz === null || e.instanz === undefined || !e.kanal) { return null; }
|
|
1298
|
+
var v = null;
|
|
1299
|
+
S.VORLAGEN.forEach(function (x) { if (x.id === e.vorlage) { v = x; } });
|
|
1300
|
+
if (!v || !v.mehrfach) { return null; }
|
|
1301
|
+
var platz = (v.mehrfach && v.mehrfach.platzhalter) || '%N%';
|
|
1302
|
+
var raus = [];
|
|
1303
|
+
var dazu = function (p) {
|
|
1304
|
+
if (!p) { return; }
|
|
1305
|
+
var voll = e.kanal + '.' + p;
|
|
1306
|
+
if (raus.indexOf(voll) === -1) { raus.push(voll); }
|
|
1307
|
+
};
|
|
1308
|
+
(v.zustaende || []).forEach(function (z) {
|
|
1309
|
+
[z.lesen, z.schreiben].forEach(function (p) {
|
|
1310
|
+
if (!p || String(p).indexOf(platz) === -1) { return; }
|
|
1311
|
+
dazu(setzeInstanz(p, v, e.instanz));
|
|
1312
|
+
if (String(e.instanz) === '1') { dazu(String(p).split(platz).join('')); }
|
|
1313
|
+
});
|
|
1314
|
+
});
|
|
1315
|
+
return raus.length ? raus : null;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/* Mit `ausgang` - der Liste aus `ausgangsQuellen` - fragt sie enger: nur
|
|
1319
|
+
ein Alias, der wirklich aus einer dieser Quellen liest oder in eine
|
|
1320
|
+
schreibt, gehoert zu diesem Ausgang. Ohne das Argument, und das sind
|
|
1321
|
+
alle anderen sieben Aufrufer, bleibt sie Zeile fuer Zeile wie sie war.
|
|
1322
|
+
|
|
1323
|
+
Die Mehrheitsregel unten ist an einem Geraet mit einem Ausgang richtig
|
|
1324
|
+
und an einem Mehrfachgeraet die falsche Frage: An Ricardos
|
|
1325
|
+
Steckdosenleiste gewann `Verbrauch` mit sechs Punkten aus
|
|
1326
|
+
`tele.SENSOR` gegen `RF1000` mit zwei Punkten aus `stat.POWER1`, und
|
|
1327
|
+
der Entwurf fuer Ausgang 1 uebernahm dessen Haken. Im Musterfeld stand
|
|
1328
|
+
danach "✕ SET fehlt" an einem Geraet mit funktionierendem SET
|
|
1329
|
+
(C13, produktiv gemessen 16.09.2026).
|
|
1330
|
+
|
|
1331
|
+
Auch die Abkuerzung ueber `native.quelle` faellt in diesem Fall weg:
|
|
1332
|
+
die zeigt auf das GERAET, nicht auf den Ausgang. */
|
|
1333
|
+
export function aliasFuer(quelle, ausgang) {
|
|
1334
|
+
if (ausgang && ausgang.length) {
|
|
1335
|
+
var karteA = alsQuelleKarten();
|
|
1336
|
+
var zaehlA = {}, bestA = null, vielA = 0;
|
|
1337
|
+
ausgang.forEach(function (q0) {
|
|
1338
|
+
(karteA.zweig[q0] || []).forEach(function (kanal) {
|
|
1339
|
+
zaehlA[kanal] = (zaehlA[kanal] || 0) + 1;
|
|
1340
|
+
if (zaehlA[kanal] > vielA) { vielA = zaehlA[kanal]; bestA = kanal; }
|
|
1341
|
+
});
|
|
1342
|
+
});
|
|
1343
|
+
return bestA;
|
|
1344
|
+
}
|
|
1127
1345
|
var karte = alsQuelleKarten();
|
|
1128
1346
|
var treffer = karte.fest[quelle] || null;
|
|
1129
1347
|
if (treffer) { return treffer; }
|
|
@@ -1170,6 +1388,22 @@ export function aliasFuer(quelle) {
|
|
|
1170
1388
|
export function bestandVorrang(e, aliasId) {
|
|
1171
1389
|
if (!e || !e.states || !aliasId) { return e; }
|
|
1172
1390
|
var kanal = aliasId;
|
|
1391
|
+
/* `vorlagenWert` heisst, was es heisst: was die VORLAGE sagen wuerde.
|
|
1392
|
+
Ist keine in Kraft — abgewaehlt oder nie gefunden —, gibt es nichts,
|
|
1393
|
+
wovon etwas abweichen koennte, und die Kopie darf nicht entstehen.
|
|
1394
|
+
|
|
1395
|
+
Bis zum 17.09.2026 wurde sie unbesehen gesetzt, und dann log die
|
|
1396
|
+
Zeile: an `hm-rpc.2.NEQ1660737` nach „keine Vorlage" stand an SET
|
|
1397
|
+
„weicht von der Vorlage ab" und darunter „Die Vorlage will hier:
|
|
1398
|
+
switch" — waehrend im Alias `switch.light` steht, was auf das Muster
|
|
1399
|
+
`light` sogar passt. Verglichen wurde in Wahrheit gegen den
|
|
1400
|
+
Rohentwurf, also gegen die blanke Rolle des Quellpunkts, und als
|
|
1401
|
+
abweichend markiert ausgerechnet der bessere Wert. Dasselbe an einem
|
|
1402
|
+
Geraet, zu dem NIE eine Vorlage gefunden wurde (nachgestellt an
|
|
1403
|
+
`echt.Warmwasser`): auch dort erschien die Marke. Ricardo: „es gibt
|
|
1404
|
+
ja gar keine Vorlage mehr, man kann hoechstens vom Muster
|
|
1405
|
+
abweichen." */
|
|
1406
|
+
var mitVorlage = !!(e.vorschlag && e.vorlage);
|
|
1173
1407
|
e.states.forEach(function (s) {
|
|
1174
1408
|
if (!s.n) { return; }
|
|
1175
1409
|
var o = S.objects[kanal + '.' + s.n];
|
|
@@ -1179,22 +1413,26 @@ export function bestandVorrang(e, aliasId) {
|
|
|
1179
1413
|
anlegen (Ricardo, 25.08.2026: UNREACH und RSSI standen angehakt,
|
|
1180
1414
|
obwohl sie im Alias fehlen). Anhaken kann man sie weiterhin, und
|
|
1181
1415
|
der Chip „Vorlage" holt die Vorgabe der Vorlage zurueck. */
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1416
|
+
if (mitVorlage) {
|
|
1417
|
+
s.vorlagenWert = { on: s.on, role: s.role, typ: s.typ, unit: s.unit,
|
|
1418
|
+
caption: s.caption, f: s.f, fw: s.fw,
|
|
1419
|
+
srcR: s.srcR, srcW: s.srcW, states: s.states };
|
|
1420
|
+
}
|
|
1185
1421
|
s.on = false;
|
|
1186
1422
|
return;
|
|
1187
1423
|
}
|
|
1188
1424
|
var c = o.common;
|
|
1189
1425
|
var q = aliasQuellen(o);
|
|
1190
1426
|
/* Was die Vorlage wollte - fuer den Vergleich und den Knopf. */
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1427
|
+
if (mitVorlage) {
|
|
1428
|
+
s.vorlagenWert = {
|
|
1429
|
+
role: s.role, typ: s.typ, unit: s.unit, caption: s.caption,
|
|
1430
|
+
f: s.f, fw: s.fw, srcR: s.srcR, srcW: s.srcW, states: s.states,
|
|
1431
|
+
/* Auch der Haken der Vorlage - der Chip „Vorlage" stellt ihn wieder
|
|
1432
|
+
her, nachdem der Bestand ihn ueberschrieben hat. */
|
|
1433
|
+
on: s.on
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1198
1436
|
var ist = zeileAusAlias(o, s.n);
|
|
1199
1437
|
/* Rolle und Typ NUR, wenn sie im Alias ueberhaupt stehen.
|
|
1200
1438
|
|
|
@@ -1297,7 +1535,15 @@ export function vorlageAnwenden(e) {
|
|
|
1297
1535
|
var anders = vorlagenAbweichung(s).length > 0;
|
|
1298
1536
|
s.role = v.role; s.typ = v.typ; s.unit = v.unit;
|
|
1299
1537
|
s.f = v.f; s.fw = v.fw; s.srcR = v.srcR; s.srcW = v.srcW;
|
|
1300
|
-
|
|
1538
|
+
/* Die Werteliste nur, wenn die Vorlage selbst eine mitbringt. Sagt sie
|
|
1539
|
+
nichts dazu, gilt der Bestand — dieselbe Regel wie bei Rolle und Typ
|
|
1540
|
+
in `bestandVorrang`. Vorher stand hier `s.states = v.states`, und an
|
|
1541
|
+
einer Vorlagenzeile ohne Werteliste hiess das `undefined`: am
|
|
1542
|
+
Rollladen verlor DIRECTION {0: NONE, 1: UP, 2: DOWN}, ohne dass
|
|
1543
|
+
Zaehler oder Marke es zeigten — `vorlagenAbweichung` vergleicht die
|
|
1544
|
+
Werteliste nicht, und die Werkbank hat kein Feld dafuer (Lauf vom
|
|
1545
|
+
19.09.2026, G10). */
|
|
1546
|
+
if (v.states !== undefined) { s.states = v.states; }
|
|
1301
1547
|
/* caption bleibt: der Anzeigename gehoert dem Nutzer. */
|
|
1302
1548
|
if (anders) { s.geaendert = true; }
|
|
1303
1549
|
});
|