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/ergebnis.js
CHANGED
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
mitNachfrage,
|
|
28
28
|
geraeteDarunter,
|
|
29
29
|
aliasFuer,
|
|
30
|
+
quellenVerteilung,
|
|
31
|
+
quelleHier,
|
|
30
32
|
tipptGerade,
|
|
31
33
|
opt,
|
|
32
34
|
vorlagenAbweichung,
|
|
@@ -77,6 +79,14 @@ var warumAuf = false;
|
|
|
77
79
|
var werteTimer = null;
|
|
78
80
|
function werteGezeichnet() {
|
|
79
81
|
werteTimer = null;
|
|
82
|
+
/* Im Vorlagenmodus steht rechts das Vorlagenblatt, nicht die
|
|
83
|
+
Zustandsliste — ein eintreffender Wert hat dort nichts zu melden.
|
|
84
|
+
`setzeModus('vorlagen')` laesst `S.current` ausdruecklich stehen,
|
|
85
|
+
damit man seine Auswahl beim Zurueckwechseln wiederfindet; ohne
|
|
86
|
+
diese Zeile baute jeder Takt eines Solarzaehlers das ganze Blatt
|
|
87
|
+
neu, samt `probelauf` ueber alle Kandidaten und alle Vorlagen
|
|
88
|
+
(gemessen 09.09.2026: vier Werte, vier Neuaufbauten). */
|
|
89
|
+
if (S.baumModus === 'vorlagen') { return; }
|
|
80
90
|
if (document.querySelector('dialog[open]')) { return; }
|
|
81
91
|
if (tipptGerade()) { werteTimer = setTimeout(werteGezeichnet, 700); return; }
|
|
82
92
|
if (S.current) { zeichneErgebnis(); }
|
|
@@ -331,18 +341,137 @@ var warumAuf = false;
|
|
|
331
341
|
erschien dadurch an jeder Sparte und jedem Unterkanal, also an
|
|
332
342
|
`…RGB.stat` und `…RGB.tele` ebenso wie am Geraet. Der Knopf
|
|
333
343
|
gehoert nur dorthin, wo der Alias auch wirklich herkommt. */
|
|
334
|
-
|
|
344
|
+
/* Rueckprobe, aber nicht zu streng.
|
|
345
|
+
|
|
346
|
+
Frueher galt nur: `quelleVon(alias) === S.current`. Bei einem
|
|
347
|
+
Alias aus zwei Zweigen fiel der Rueckweg damit weg - wer ueber
|
|
348
|
+
die neue Quellenauswahl auf `…Stromzaehler.stat` sprang, stand
|
|
349
|
+
dort ohne Knopf zurueck (Ricardo, 08.09.2026).
|
|
350
|
+
|
|
351
|
+
Jetzt gilt der Knopf auch an einer Nebenquelle - aber nur, wenn
|
|
352
|
+
sie GENAU in der Verteilung steht und einen eigenen Zweig
|
|
353
|
+
bildet. Sparten unterhalb der Hauptquelle bleiben damit weiter
|
|
354
|
+
aussen vor: `…Licht.stat` und `…Licht.tele` liegen beide unter
|
|
355
|
+
`…Bastelzimmer_Licht`, und der Knopf gehoert ans Geraet, nicht
|
|
356
|
+
an jede Sparte. Knoten weiter oben (`…Strom`) stehen gar nicht
|
|
357
|
+
erst in der Verteilung. */
|
|
358
|
+
/* Der Rueckweg gehoert an jede Quelle des Alias - an die groesste
|
|
359
|
+
wie an jede andere, und auch an das Geraet ueber einer Sparte.
|
|
360
|
+
`quelleHier` sagt, ob dieser Knoten zu einer von ihnen gehoert;
|
|
361
|
+
steht er zu weit oben oder gar nicht dabei, bleibt der Knopf
|
|
362
|
+
weg (Ricardo, 08.09.2026). */
|
|
363
|
+
if (sprungZiel && !quelleHier(sprungZiel, S.current)) { sprungZiel = null; }
|
|
335
364
|
sprungModus = 'aliase';
|
|
336
365
|
}
|
|
366
|
+
/* Liest der Alias aus mehr als einem Knoten, gehoert das in den Kopf.
|
|
367
|
+
|
|
368
|
+
`quelleVon` entscheidet nach Mehrheit und wirft den Rest weg. Das
|
|
369
|
+
ist als Sprungziel richtig, als Auskunft aber die halbe Wahrheit:
|
|
370
|
+
an `alias.0.Solar.Netz` - drei Punkte aus `0_userdata`, zwei aus
|
|
371
|
+
`mqtt-client` - stand nirgends, dass es zwei Quellen sind. Man
|
|
372
|
+
musste jede Zeile aufklappen, um es zu sehen (Ricardo,
|
|
373
|
+
08.09.2026). */
|
|
374
|
+
/* Der Chip gilt fuer den Alias, gleich von welcher Seite man ihn
|
|
375
|
+
ansieht: am Alias selbst und an jeder seiner Quellen. Wer an einer
|
|
376
|
+
Nebenquelle steht, soll sehen, dass er nur einen Teil vor sich
|
|
377
|
+
hat. */
|
|
378
|
+
var quellAlias = (S.current.indexOf('alias.') === 0) ? S.current
|
|
379
|
+
: (sprungZiel && sprungModus === 'aliase' ? sprungZiel : null);
|
|
380
|
+
var verteilung = quellAlias ? quellenVerteilung(quellAlias) : [];
|
|
381
|
+
var mehrQuellen = verteilung.length > 1;
|
|
337
382
|
if (knotenDa(sprungZiel)) {
|
|
338
383
|
var zumAlias = (sprungModus === 'aliase');
|
|
339
384
|
sprung = el('button', 'btn schmal sprungknopf',
|
|
340
385
|
(zumAlias ? tr('result.toAlias') : tr('result.toSource')) + ' \u2192');
|
|
341
386
|
sprung.title = (zumAlias ? tr('result.toAliasHint') : tr('result.toSourceHint'))
|
|
342
|
-
+
|
|
387
|
+
+ String.fromCharCode(10) + sprungZiel;
|
|
343
388
|
var ziel0 = sprungZiel, modus0 = sprungModus;
|
|
344
|
-
|
|
345
|
-
|
|
389
|
+
/* Die Auswahl gehoert nur an „zur Quelle" - dort gibt es mehrere
|
|
390
|
+
Ziele. „zum Alias" hat genau eines; eine Liste von QUELLEN unter
|
|
391
|
+
diesem Knopf ist sinnlos (Ricardo, 08.09.2026). Der Chip bleibt
|
|
392
|
+
an beiden Stellen: er sagt etwas ueber den Alias, nicht ueber
|
|
393
|
+
den Sprung. */
|
|
394
|
+
if (mehrQuellen && sprungModus === 'quellen') {
|
|
395
|
+
/* Die Auswahl haengt am Body, nicht am Kopf.
|
|
396
|
+
|
|
397
|
+
Erst stand sie neben dem Knopf - und war sofort wieder weg:
|
|
398
|
+
bei `alias.0.Solar.Netz` laeuft im Sekundentakt ein neuer
|
|
399
|
+
Messwert ein, jeder zeichnet die rechte Seite neu, und mit ihr
|
|
400
|
+
verschwand die eben geoeffnete Liste. Am Body ueberlebt sie
|
|
401
|
+
das; ihre Stelle bekommt sie aus der Lage des Knopfes.
|
|
402
|
+
|
|
403
|
+
`mousedown` statt `click`, wie bei den Vorschlagslisten der
|
|
404
|
+
Zielleiste: der Mausdruck kommt vor dem Neuzeichnen. */
|
|
405
|
+
/* Gefragt wird das Dokument, nicht die Erinnerung.
|
|
406
|
+
|
|
407
|
+
Die Liste haengt am `body`, damit sie ein automatisches
|
|
408
|
+
Neuzeichnen ueberlebt. Der Knopf aber ist danach ein anderer,
|
|
409
|
+
und mit ihm diese Merkstelle: sie stand wieder auf „keine",
|
|
410
|
+
waehrend die alte Liste weiter am Body hing. Der zweite Klick
|
|
411
|
+
legte eine zweite Liste ueber die erste, und der Zuhoerer am
|
|
412
|
+
Body raeumte nicht auf, weil der Knopf das Ereignis abfaengt
|
|
413
|
+
(gemessen 09.09.2026). */
|
|
414
|
+
var offeneListe = function () { return document.querySelector('.sprungliste'); };
|
|
415
|
+
var listeQ = null;
|
|
416
|
+
var zuQ = function () {
|
|
417
|
+
var da = offeneListe();
|
|
418
|
+
while (da) {
|
|
419
|
+
if (da.parentNode) { da.parentNode.removeChild(da); }
|
|
420
|
+
da = offeneListe();
|
|
421
|
+
}
|
|
422
|
+
listeQ = null;
|
|
423
|
+
};
|
|
424
|
+
sprung.addEventListener('mousedown', function (ev) {
|
|
425
|
+
ev.preventDefault();
|
|
426
|
+
ev.stopPropagation();
|
|
427
|
+
if (offeneListe()) { zuQ(); return; }
|
|
428
|
+
listeQ = el('div', 'vorschlaege sprungliste');
|
|
429
|
+
listeQ.appendChild(el('div', 'vzkopf', tr('result.pickSource')));
|
|
430
|
+
verteilung.forEach(function (q) {
|
|
431
|
+
/* Keine Zeile hervorheben. Die Hervorhebung meinte „das ist
|
|
432
|
+
die wichtigste" - eine Aussage, die bei drei zu drei
|
|
433
|
+
niemand treffen kann und die auch sonst niemand braucht:
|
|
434
|
+
alle Quellen des Alias sind gleichberechtigt (Ricardo,
|
|
435
|
+
08.09.2026). */
|
|
436
|
+
var z = el('div', 'vz');
|
|
437
|
+
z.appendChild(el('span', null, q.id));
|
|
438
|
+
z.appendChild(el('span', 'zq', ' \u00b7 ' + q.n));
|
|
439
|
+
z.addEventListener('mousedown', function (ev2) {
|
|
440
|
+
ev2.preventDefault();
|
|
441
|
+
ev2.stopPropagation();
|
|
442
|
+
zuQ();
|
|
443
|
+
springeZu(q.id, modus0);
|
|
444
|
+
});
|
|
445
|
+
listeQ.appendChild(z);
|
|
446
|
+
});
|
|
447
|
+
var r = sprung.getBoundingClientRect();
|
|
448
|
+
listeQ.style.position = 'fixed';
|
|
449
|
+
listeQ.style.left = Math.round(r.left) + 'px';
|
|
450
|
+
listeQ.style.top = Math.round(r.bottom + 2) + 'px';
|
|
451
|
+
listeQ.hidden = false;
|
|
452
|
+
document.body.appendChild(listeQ);
|
|
453
|
+
});
|
|
454
|
+
/* Woanders hin gedrueckt heisst: doch nicht. Einmal registriert,
|
|
455
|
+
nicht bei jedem Neuzeichnen - sonst haeuft sich der Zuhoerer. */
|
|
456
|
+
if (!S.sprungZuhoerer) {
|
|
457
|
+
S.sprungZuhoerer = true;
|
|
458
|
+
document.addEventListener('mousedown', function () {
|
|
459
|
+
var offen = document.querySelector('.sprungliste');
|
|
460
|
+
if (offen && offen.parentNode) { offen.parentNode.removeChild(offen); }
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
namensZeile.appendChild(sprung);
|
|
464
|
+
} else {
|
|
465
|
+
sprung.addEventListener('click', function () { springeZu(ziel0, modus0); });
|
|
466
|
+
namensZeile.appendChild(sprung);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (mehrQuellen) {
|
|
470
|
+
var chQ = el('span', 'chip mut quellenzahl', tr('result.sourceCount', verteilung.length));
|
|
471
|
+
chQ.title = tr('result.sourceCountHint') + String.fromCharCode(10, 10) +
|
|
472
|
+
verteilung.map(function (q) { return q.id + ' \u00b7 ' + q.n; })
|
|
473
|
+
.join(String.fromCharCode(10));
|
|
474
|
+
namensZeile.appendChild(chQ);
|
|
346
475
|
}
|
|
347
476
|
links.appendChild(sub);
|
|
348
477
|
head.appendChild(links);
|
|
@@ -558,8 +687,20 @@ var warumAuf = false;
|
|
|
558
687
|
var gid = S.objects[vorh].native.vorlage;
|
|
559
688
|
if (S.VORLAGEN.some(function (x) { return x.id === gid; })) { fest = gid; }
|
|
560
689
|
}
|
|
561
|
-
|
|
562
|
-
|
|
690
|
+
/* Ausgang und Handwahl gehoeren mit.
|
|
691
|
+
|
|
692
|
+
Ohne die Instanz kamen an einer Mehrfachvorlage die Zeilen von
|
|
693
|
+
Ausgang 1 zurueck, waehrend das Ziel weiter `…Ausgang_2` hiess.
|
|
694
|
+
Und `fest` allein reicht nicht: Es ist nur gesetzt, wenn schon
|
|
695
|
+
ein Alias mit Vorlage dasteht. Hatte die Automatik nichts
|
|
696
|
+
gefunden und man die Vorlage von Hand gewaehlt, suchte der
|
|
697
|
+
Neuaufbau wieder automatisch, fand nichts — und man landete im
|
|
698
|
+
Rohmodus, die Handwahl weg (gemessen 09.09.2026). Der
|
|
699
|
+
Ausgangswechsel eine Stelle weiter oben macht es laengst
|
|
700
|
+
richtig. */
|
|
701
|
+
var vid = fest || e.vorlage || null;
|
|
702
|
+
var neuE = rohJetzt ? alleUndVorlage(S.current, vid, e.instanz)
|
|
703
|
+
: vorschlag(S.current, vid, e.instanz);
|
|
563
704
|
if (!neuE) { return; }
|
|
564
705
|
neuE.roh = rohJetzt;
|
|
565
706
|
neuE.vorschlag = true;
|
|
@@ -609,31 +750,16 @@ var warumAuf = false;
|
|
|
609
750
|
return musterBlock;
|
|
610
751
|
}
|
|
611
752
|
|
|
612
|
-
/*
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
gleich, ob man handeln muss. */
|
|
623
|
-
ich.appendChild(el('span', 'typ', tr('info.notPartOf', e.want || '')));
|
|
624
|
-
ich.appendChild(el('span', 'chip mut', tr('info.countedSeparately')));
|
|
625
|
-
ic.appendChild(ich);
|
|
626
|
-
var ie = el('div', 'infoerkl');
|
|
627
|
-
ie.style.padding = '10px 15px 12px';
|
|
628
|
-
ie.style.fontSize = '11.5px';
|
|
629
|
-
ie.style.color = 'var(--ink-3)';
|
|
630
|
-
ie.style.lineHeight = '1.6';
|
|
631
|
-
ie.appendChild(el('b', null, tr('info.sameDatapoints', infoNamen.join(', '))));
|
|
632
|
-
ie.appendChild(document.createTextNode(tr('info.explain', e.want || '')));
|
|
633
|
-
ic.appendChild(ie);
|
|
634
|
-
host.appendChild(ic);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
753
|
+
/* Die Karte „Gehoert nicht zum <Muster>" stand hier bis zum
|
|
754
|
+
08.09.2026 — weit unten, mit den Namen der betroffenen Punkte und
|
|
755
|
+
der langen Erklaerung. Sie ist weg: dieselbe Auskunft haengt jetzt
|
|
756
|
+
am Hinweis der Legende, und die steht dort, wo die getoenten Zeilen
|
|
757
|
+
stehen. Zweimal dasselbe, einmal oben und einmal ganz unten, war
|
|
758
|
+
eine zu viel (Ricardo, 08.09.2026). Der Text `info.explain` bleibt in
|
|
759
|
+
Gebrauch — die Legende nutzt ihn. `info.notPartOf` dagegen rief nach
|
|
760
|
+
dem Umbau niemand mehr; dieser Satz behauptete das Gegenteil, und
|
|
761
|
+
der Schluessel stand noch ein Jahr in beiden Sprachdateien, weil der
|
|
762
|
+
Test nur die Richtung Code → JSON prueft (09.09.2026 entfernt). */
|
|
637
763
|
|
|
638
764
|
/* Was sich gegenueber dem gespeicherten Stand aendern wuerde. */
|
|
639
765
|
function baueDiffKarte(host, e) {
|
|
@@ -665,7 +791,7 @@ var warumAuf = false;
|
|
|
665
791
|
|
|
666
792
|
/* Pruefungen, Fusszeile und die Knopfleiste unten - beschriftet
|
|
667
793
|
nach dem, was der Klick tatsaechlich tut. */
|
|
668
|
-
function setzeKnoepfe(e, haupt, pflichtFehlt
|
|
794
|
+
function setzeKnoepfe(e, haupt, pflichtFehlt) {
|
|
669
795
|
baueChecks(e, haupt, pflichtFehlt);
|
|
670
796
|
|
|
671
797
|
/* Der Knopf soll sagen, was er tut — nicht, wie er es tut. */
|
|
@@ -796,21 +922,19 @@ var warumAuf = false;
|
|
|
796
922
|
}
|
|
797
923
|
|
|
798
924
|
var pl = berechnePlaetze(e, haupt);
|
|
799
|
-
|
|
925
|
+
|
|
800
926
|
|
|
801
927
|
baueListe(host, e, pl, rateKnopf, musterBlock);
|
|
802
928
|
rateKnopf = null;
|
|
803
929
|
musterBlock = null;
|
|
804
930
|
|
|
805
|
-
|
|
806
|
-
var infoNamen = Object.keys(imInfo);
|
|
807
|
-
baueInfoKarte(host, e, infoNamen);
|
|
931
|
+
|
|
808
932
|
|
|
809
933
|
/* --- Änderungen gegenüber dem, was gespeichert ist --- */
|
|
810
934
|
baueDiffKarte(host, e);
|
|
811
935
|
|
|
812
936
|
/* --- Prüfungen, Fusszeile, Knopfleiste --- */
|
|
813
|
-
setzeKnoepfe(e, haupt, pflichtFehlt
|
|
937
|
+
setzeKnoepfe(e, haupt, pflichtFehlt);
|
|
814
938
|
}
|
|
815
939
|
|
|
816
940
|
/* Uebersicht ueber alle Geraete unterhalb eines Ordners. */
|
package/admin/js/erkennung.js
CHANGED
|
@@ -4,6 +4,55 @@
|
|
|
4
4
|
import { S } from './zustand.js';
|
|
5
5
|
import { D } from './basis.js';
|
|
6
6
|
|
|
7
|
+
/* Der Ausdruck eines Platzes als blosser Text — ohne Schraegstriche.
|
|
8
|
+
|
|
9
|
+
Der Detector liefert seine Muster ueber `ChannelDetector.getPatterns()`
|
|
10
|
+
(so haengt `src/detector-bundle.js` sie an `window`), und diese Funktion
|
|
11
|
+
gibt `role` nicht als RegExp weiter, sondern als `role.toString()` —
|
|
12
|
+
also MIT den Schraegstrichen:
|
|
13
|
+
|
|
14
|
+
/^indicator(\.maintenance)?\.(lowbat|battery)$/
|
|
15
|
+
|
|
16
|
+
Das ist bei uns der Normalfall, nicht die Ausnahme: ein RegExp bekommen
|
|
17
|
+
wir nur, wenn jemand die Muster direkt aus `typePatterns` liest, etwa
|
|
18
|
+
ein Test. Wer bloss `.source` abfragt und sonst `String(r)` nimmt, haelt
|
|
19
|
+
die Schraegstriche fuer Teil des Ausdrucks und findet danach weder den
|
|
20
|
+
Anker `^` noch das Ende `$`.
|
|
21
|
+
|
|
22
|
+
Gefunden am 08.09.2026 am Produktivsystem, und der eine Fehler zog weit:
|
|
23
|
+
`ROLLEN` hatte 210 statt 232 Eintraege (E27) — `indicator.lowbat` fehlte,
|
|
24
|
+
die Rolle, die hm-rpc schreibt —, `BRIGHTNESS` bekam in sechs Mustern gar
|
|
25
|
+
keine Rolle (E6), `plaetzeFuerRollen` fand zu keiner einzigen Rolle einen
|
|
26
|
+
Platz, und die Abweichungskarte behauptete „haette ohnehin nicht
|
|
27
|
+
gezaehlt" ueber Punkte mit Platz. Die Node-Tests sahen nichts davon: sie
|
|
28
|
+
fuettern die Zerlegung mit echten RegExp-Literalen.
|
|
29
|
+
|
|
30
|
+
Deshalb geht seither jede Stelle, die einen Platzausdruck liest, hier
|
|
31
|
+
durch — es sind sieben. */
|
|
32
|
+
export function ausdruckText(r) {
|
|
33
|
+
if (!r) { return ''; }
|
|
34
|
+
if (r.source !== undefined) { return r.source; }
|
|
35
|
+
var q = String(r);
|
|
36
|
+
var m = /^\/(.*)\/[a-z]*$/.exec(q);
|
|
37
|
+
return m ? m[1] : q;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Der Ausdruck als RegExp — fuer alle, die damit pruefen wollen. Wirft
|
|
41
|
+
nicht: ein unbrauchbarer Ausdruck gibt `null`, und der Aufrufer
|
|
42
|
+
entscheidet, was das heisst. */
|
|
43
|
+
export function ausdruckRegExp(r) {
|
|
44
|
+
var q = ausdruckText(r);
|
|
45
|
+
if (!q) { return null; }
|
|
46
|
+
try { return new RegExp(q); } catch { return null; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Passt eine Rolle auf den Ausdruck eines Platzes? */
|
|
50
|
+
export function rolleTrifft(r, rolle) {
|
|
51
|
+
if (!rolle) { return false; }
|
|
52
|
+
var re = ausdruckRegExp(r);
|
|
53
|
+
return re ? re.test(rolle) : false;
|
|
54
|
+
}
|
|
55
|
+
|
|
7
56
|
/* Die Rollen kommen aus dem Paket, nicht aus einer eigenen Liste —
|
|
8
57
|
sonst pflegt man eine zweite Wahrheit, die auseinanderlaeuft. */
|
|
9
58
|
/* Manche Plaetze nennen keine Standardrolle, sondern nur einen
|
|
@@ -13,8 +62,8 @@ import { D } from './basis.js';
|
|
|
13
62
|
liess sich beliebig oft hinzufuegen, und die Rolle fehlte auch in der
|
|
14
63
|
Auswahlliste — der Platz war auf keinem Weg zu befuellen. */
|
|
15
64
|
export function rolleAusAusdruck(r) {
|
|
16
|
-
|
|
17
|
-
|
|
65
|
+
var q = ausdruckText(r);
|
|
66
|
+
if (!q) { return ''; }
|
|
18
67
|
if (q.charAt(0) !== '^' || q.slice(-1) !== '$') { return ''; }
|
|
19
68
|
var t = q.slice(1, -1).split(String.fromCharCode(92)).join('');
|
|
20
69
|
return /^[A-Za-z0-9_]+([.][A-Za-z0-9_]+)*$/.test(t) ? t : '';
|
|
@@ -41,8 +90,8 @@ export function rolleVonPlatz(st) { return st.defaultRole || rolleAusAusdruck(st
|
|
|
41
90
|
dann bleibt es bei der Vorgaberolle. Lieber eine Schreibweise zu wenig
|
|
42
91
|
als eine erfundene in der Liste. */
|
|
43
92
|
export function schreibweisenAus(r) {
|
|
44
|
-
|
|
45
|
-
|
|
93
|
+
var q = ausdruckText(r);
|
|
94
|
+
if (!q) { return []; }
|
|
46
95
|
if (q.charAt(0) !== '^' || q.slice(-1) !== '$') { return []; }
|
|
47
96
|
q = q.slice(1, -1);
|
|
48
97
|
|
|
@@ -160,7 +209,7 @@ export function plaetzeFuerRollen(musterName) {
|
|
|
160
209
|
ROLLEN.forEach(function (r) {
|
|
161
210
|
mu.states.forEach(function (st) {
|
|
162
211
|
if (raus[r] || !st.role) { return; }
|
|
163
|
-
|
|
212
|
+
if (rolleTrifft(st.role, r)) { raus[r] = st.name; }
|
|
164
213
|
});
|
|
165
214
|
});
|
|
166
215
|
return raus;
|
package/admin/js/katalog.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { holeText } from './basis.js';
|
|
2
|
-
import { txt } from './sprache.js';
|
|
2
|
+
import { txt, alleTexte } from './sprache.js';
|
|
3
3
|
import { einst } from './einstellungen.js';
|
|
4
4
|
import './enums.js';
|
|
5
5
|
|
|
@@ -304,10 +304,19 @@ export function vorlageZu(id, art, name) {
|
|
|
304
304
|
var v = null;
|
|
305
305
|
liste.forEach(function (x) { if (!v && x._id === kurz) { v = x; } });
|
|
306
306
|
if (v) { return v; }
|
|
307
|
-
var n = String(name || '').toLowerCase();
|
|
307
|
+
var n = String(name || '').trim().toLowerCase();
|
|
308
308
|
if (!n) { return null; }
|
|
309
|
+
/* Alle Sprachfassungen, nicht nur die eingestellte.
|
|
310
|
+
|
|
311
|
+
Im Bestand steht, was jemand getippt hat — „Light" etwa, waehrend
|
|
312
|
+
die Vorlage auf einem deutschen Admin „Licht" heisst. Verglichen
|
|
313
|
+
wurde aber nur die eine Fassung, und ohne Treffer entsteht beim
|
|
314
|
+
naechsten Schreiben ein Doppelgaenger `enum.functions.light` neben
|
|
315
|
+
der vorhandenen Funktion (gefunden 09.09.2026). Der Vergleich war
|
|
316
|
+
schon immer unabhaengig von Gross- und Kleinschreibung; was fehlte,
|
|
317
|
+
waren die anderen Sprachen. */
|
|
309
318
|
liste.forEach(function (x) {
|
|
310
|
-
if (!v &&
|
|
319
|
+
if (!v && alleTexte(x.name).indexOf(n) !== -1) { v = x; }
|
|
311
320
|
});
|
|
312
321
|
return v;
|
|
313
322
|
}
|
package/admin/js/mqtt.js
CHANGED
|
@@ -38,10 +38,28 @@ export function mqttEinstellung(id) {
|
|
|
38
38
|
if (!o || !o.common) { return null; }
|
|
39
39
|
var cu = o.common.custom || {};
|
|
40
40
|
var k = null;
|
|
41
|
-
|
|
41
|
+
/* Drei Bedingungen, nicht eine.
|
|
42
|
+
|
|
43
|
+
`cu[x]` kann `null` sein — ioBroker raeumt den Eintrag nicht weg,
|
|
44
|
+
wenn eine Instanz verschwindet. Der Zugriff auf `.topic` riss dann
|
|
45
|
+
die ganze Ergebnisansicht mit (gemessen 09.09.2026).
|
|
46
|
+
|
|
47
|
+
Und `enabled` kam hier gar nicht vor: `{ enabled: false,
|
|
48
|
+
publish: true }` galt als sendefaehig, obwohl die Instanz diesen
|
|
49
|
+
Punkt nicht bedient. Fehlt das Feld, gilt der Eintrag als an — so
|
|
50
|
+
legt der Admin es an, wenn man die Verknuepfung setzt. */
|
|
51
|
+
Object.keys(cu).forEach(function (x) {
|
|
52
|
+
if (!k && /^mqtt/.test(x) && cu[x] && cu[x].enabled !== false) { k = x; }
|
|
53
|
+
});
|
|
42
54
|
if (!k) {
|
|
43
|
-
/* Manche Objekte tragen das Thema nur in native.
|
|
44
|
-
|
|
55
|
+
/* Manche Objekte tragen das Thema nur in native — ioBroker.mqtt und
|
|
56
|
+
sonoff tun das. Fuer sie ist `publish` schlicht UNBEKANNT, nicht
|
|
57
|
+
nein: Frueher stand hier `false`, die Pruefung meldete „kann nicht
|
|
58
|
+
senden", der Chip stand rot, und der Knopf „senden erlauben"
|
|
59
|
+
schrieb das Objekt unveraendert zurueck — eine Meldung, die sich
|
|
60
|
+
nicht abstellen liess (gemessen 09.09.2026). `null` heisst: wir
|
|
61
|
+
wissen es nicht, also nicht behaupten. */
|
|
62
|
+
if (o.native && o.native.topic) { return { instanz: null, topic: o.native.topic, publish: null }; }
|
|
45
63
|
return null;
|
|
46
64
|
}
|
|
47
65
|
return { instanz: k, topic: cu[k].topic || (o.native || {}).topic || '', publish: !!cu[k].publish,
|
|
@@ -110,6 +128,12 @@ export function tasmotaBefehle(kanal) {
|
|
|
110
128
|
var liste = [];
|
|
111
129
|
Object.keys(aus).forEach(function (f) {
|
|
112
130
|
if (weg.indexOf(f) > -1) { return; }
|
|
131
|
+
/* Aus dem Schluessel wird eine Kennung UND ein MQTT-Thema. Ein `#`
|
|
132
|
+
oder `+` darin ergaebe ein Wildcard-Abo, ein Punkt eine
|
|
133
|
+
zusaetzliche Ebene im Objektbaum. Der Inhalt kommt vom Broker,
|
|
134
|
+
also von aussen — erreichbar ueber jeden fremden Publisher auf
|
|
135
|
+
demselben Broker. Tasmota-Befehle heissen ohnehin alle so. */
|
|
136
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(f)) { return; }
|
|
113
137
|
if (aus[f] !== null && typeof aus[f] === 'object' && !Array.isArray(aus[f])) { return; }
|
|
114
138
|
liste.push({ name: f, wert: aus[f] });
|
|
115
139
|
});
|
|
@@ -143,13 +167,21 @@ export function befehlsWissen(name) {
|
|
|
143
167
|
naechste Befehl die Antwort. Gemessen: nachfragen → „eingeschaltet",
|
|
144
168
|
einmal schalten → wieder „unbekannt", obwohl sich am Geraet nichts
|
|
145
169
|
geaendert hatte. Der Befund war praktisch nie stabil zu sehen. */
|
|
146
|
-
export function sofortRueckmeldung(kanal) {
|
|
170
|
+
export function sofortRueckmeldung(kanal, merken) {
|
|
147
171
|
var id = hatPunkt(kanal, 'stat.RESULT');
|
|
148
172
|
if (id) {
|
|
149
173
|
var j = jsonVon(id);
|
|
150
174
|
if (j && j.SetOption59 !== undefined) {
|
|
151
175
|
var w = String(j.SetOption59).toUpperCase() === 'ON';
|
|
152
|
-
|
|
176
|
+
/* Gemerkt wird nur nach einer Abfrage, nicht beim Zeichnen.
|
|
177
|
+
|
|
178
|
+
Vorher schrieb diese Funktion aus dem Zeichenpfad heraus ein
|
|
179
|
+
Objekt — ein Waechter verhinderte nur das Mehrfachschreiben,
|
|
180
|
+
der Fehler wurde verschluckt, und der Wert stand lokal schon.
|
|
181
|
+
Das widerspricht der Zusage, dass die Pruefungen nichts
|
|
182
|
+
anfassen: Wer die rechte Seite ansieht, aendert nichts.
|
|
183
|
+
`so59()` ruft mit `merken = true`, alle anderen lesen nur. */
|
|
184
|
+
if (merken) { merkeSo59(kanal, w); }
|
|
153
185
|
return w;
|
|
154
186
|
}
|
|
155
187
|
}
|
|
@@ -203,7 +235,10 @@ export function mqttLage(kanal) {
|
|
|
203
235
|
var id = hatPunkt(kanal, 'cmnd.' + x.name);
|
|
204
236
|
if (!id) { fehlen.push(x); return; }
|
|
205
237
|
var m = mqttEinstellung(id);
|
|
206
|
-
|
|
238
|
+
/* `publish === null` heisst unbekannt (Thema nur in `native`) —
|
|
239
|
+
so ein Punkt kommt zu den vorhandenen, nicht zu den stummen.
|
|
240
|
+
„Senden erlauben" haette an ihm ohnehin nichts zu schreiben. */
|
|
241
|
+
if (m && m.publish === false) { stumm.push({ name: x.name, id: id }); } else { da.push(x.name); }
|
|
207
242
|
});
|
|
208
243
|
}
|
|
209
244
|
/* Auch cmnd-Punkte, die es schon gibt, ohne dass sie im Zustand
|
|
@@ -215,7 +250,7 @@ export function mqttLage(kanal) {
|
|
|
215
250
|
if (stumm.some(function (x) { return x.name === name; })) { return; }
|
|
216
251
|
if (da.indexOf(name) > -1) { return; }
|
|
217
252
|
var m = mqttEinstellung(id);
|
|
218
|
-
if (m &&
|
|
253
|
+
if (m && m.publish === false) { stumm.push({ name: name, id: id }); }
|
|
219
254
|
});
|
|
220
255
|
return { geraet: g, befehle: b, fehlen: fehlen, stumm: stumm, da: da,
|
|
221
256
|
sofort: sofortRueckmeldung(kanal) };
|
|
@@ -509,7 +544,7 @@ function so59(kanal, knopf, wert) {
|
|
|
509
544
|
setTimeout(function () {
|
|
510
545
|
holeZweig(kanal + '.', function () {
|
|
511
546
|
delete so59Laeuft[kanal];
|
|
512
|
-
so59Stumm[kanal] = (sofortRueckmeldung(kanal) === null);
|
|
547
|
+
so59Stumm[kanal] = (sofortRueckmeldung(kanal, true) === null);
|
|
513
548
|
zeichneErgebnis();
|
|
514
549
|
});
|
|
515
550
|
}, 4000);
|
|
@@ -556,6 +591,18 @@ function mqttFragen(kanal, _knopf) {
|
|
|
556
591
|
/* Status 5 gleich mit: dort steht die IP, und die fehlt an
|
|
557
592
|
jedem Geraet, das seit dem Einbinden nicht neu gestartet
|
|
558
593
|
ist — tele/INFO2 kommt nur beim Start. */
|
|
594
|
+
/* Wie alt sind die Quellen JETZT?
|
|
595
|
+
|
|
596
|
+
Vorher wurde nur die Merkstelle geleert. Ein `stat.RESULT`,
|
|
597
|
+
das retained von vorgestern dasteht, reichte danach fuer
|
|
598
|
+
die gruene Rueckmeldung — auch wenn das Geraet aus ist und
|
|
599
|
+
gar nicht antwortet (gemessen 09.09.2026). Gewertet wird
|
|
600
|
+
jetzt nur, was nach dem Senden hereinkam. */
|
|
601
|
+
var vorTs = {};
|
|
602
|
+
['stat.STATUS11', 'tele.STATE', 'stat.RESULT'].forEach(function (q) {
|
|
603
|
+
var qid = hatPunkt(kanal, q);
|
|
604
|
+
if (qid && S.werte[qid]) { vorTs[q] = S.werte[qid].ts || 0; }
|
|
605
|
+
});
|
|
559
606
|
socket.emit('setState', id, { val: '5', ack: false }, function () {});
|
|
560
607
|
socket.emit('setState', id, { val: '11', ack: false }, function () {
|
|
561
608
|
setTimeout(function () {
|
|
@@ -569,7 +616,12 @@ function mqttFragen(kanal, _knopf) {
|
|
|
569
616
|
holeZweig(kanal + '.', function () {
|
|
570
617
|
delete abfrageLaeuft[kanal];
|
|
571
618
|
var bb = tasmotaBefehle(kanal);
|
|
572
|
-
|
|
619
|
+
/* Nur wenn die Quelle, aus der die Liste stammt, seit
|
|
620
|
+
dem Senden frisch geworden ist. */
|
|
621
|
+
var qid = bb && hatPunkt(kanal, bb.woher);
|
|
622
|
+
var jetzt = (qid && S.werte[qid]) ? (S.werte[qid].ts || 0) : 0;
|
|
623
|
+
var frisch = bb && jetzt > (vorTs[bb.woher] || 0);
|
|
624
|
+
mqttAbfrage[kanal] = (frisch && bb.befehle.length) ? bb.befehle.length : 0;
|
|
573
625
|
zeichneErgebnis();
|
|
574
626
|
});
|
|
575
627
|
}, 5000);
|
|
@@ -606,7 +658,18 @@ function mqttCustom(g, thema) {
|
|
|
606
658
|
export function mqttEinzelnStill(kanal, name, fertig) {
|
|
607
659
|
var bau = mqttPunktBauen(kanal, name, 'neu');
|
|
608
660
|
if (!bau) { return fertig && fertig(); }
|
|
609
|
-
socket.emit('setObject', bau.id, bau.obj, function () {
|
|
661
|
+
socket.emit('setObject', bau.id, bau.obj, function (err) {
|
|
662
|
+
/* Ein abgelehntes Schreiben ist kein Erfolg.
|
|
663
|
+
|
|
664
|
+
Der Rueckruf nahm `err` frueher nicht einmal entgegen und trug das
|
|
665
|
+
Objekt trotzdem in den eigenen Bestand ein. Danach hielt
|
|
666
|
+
`pruefeSchreiben` den Sendepunkt fuer vorhanden, die Sperre griff
|
|
667
|
+
nicht mehr, und „Jetzt anlegen" schrieb `alias.id.write` auf eine
|
|
668
|
+
Kennung, die es in der Datenbank nie gab — genau der Fehler, den
|
|
669
|
+
der js-controller sich dauerhaft merkt und nur durch Loeschen und
|
|
670
|
+
Neuanlegen vergisst. Der Zwilling `mqttEinzeln` prueft ihn seit
|
|
671
|
+
jeher richtig (gemessen 09.09.2026). */
|
|
672
|
+
if (err) { return fertig && fertig(err); }
|
|
610
673
|
uebernimmObjekt(bau.id, bau.obj);
|
|
611
674
|
if (fertig) { fertig(); }
|
|
612
675
|
});
|
|
@@ -622,7 +685,7 @@ function mqttPunktBauen(kanal, name, was) {
|
|
|
622
685
|
if (!alt0) { return null; }
|
|
623
686
|
obj = JSON.parse(JSON.stringify(alt0));
|
|
624
687
|
var cu0 = obj.common.custom || {};
|
|
625
|
-
Object.keys(cu0).forEach(function (i) { if (/^mqtt/.test(i)) { cu0[i].publish = true; } });
|
|
688
|
+
Object.keys(cu0).forEach(function (i) { if (/^mqtt/.test(i) && cu0[i]) { cu0[i].publish = true; } });
|
|
626
689
|
obj.common.custom = cu0;
|
|
627
690
|
} else {
|
|
628
691
|
var w0 = befehlsWissen(name) || {};
|
|
@@ -656,7 +719,7 @@ export function mqttEinzeln(kanal, name, was, knopf) {
|
|
|
656
719
|
if (!alt) { return; }
|
|
657
720
|
obj = JSON.parse(JSON.stringify(alt));
|
|
658
721
|
var cu = obj.common.custom || {};
|
|
659
|
-
Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i)) { cu[i].publish = true; } });
|
|
722
|
+
Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i) && cu[i]) { cu[i].publish = true; } });
|
|
660
723
|
obj.common.custom = cu;
|
|
661
724
|
} else {
|
|
662
725
|
var w = befehlsWissen(name) || {};
|
|
@@ -846,7 +909,7 @@ function mqttZuSchreiben() {
|
|
|
846
909
|
if (!alt) { return; }
|
|
847
910
|
var kopie = JSON.parse(JSON.stringify(alt));
|
|
848
911
|
var cu = kopie.common.custom || {};
|
|
849
|
-
Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i)) { cu[i].publish = true; } });
|
|
912
|
+
Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i) && cu[i]) { cu[i].publish = true; } });
|
|
850
913
|
kopie.common.custom = cu;
|
|
851
914
|
raus.push({ id: x.id, obj: kopie, neu: false });
|
|
852
915
|
});
|