iobroker.alias-workbench 0.9.9 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +29 -0
- package/admin/css/werkbank.css +88 -3
- package/admin/js/baum.js +8 -0
- package/admin/js/detail.js +53 -6
- package/admin/js/entwurf.js +60 -20
- package/admin/js/ergebnis.js +83 -5
- package/admin/js/erkennung.js +46 -2
- package/admin/js/mqtt.js +23 -2
- package/admin/js/musterwahl.js +208 -31
- package/admin/js/objekte.js +31 -0
- package/admin/js/pruefungen.js +28 -2
- package/admin/js/quellentausch.js +179 -19
- package/admin/js/schreiben.js +194 -52
- package/admin/js/sprache.js +6 -1
- package/admin/js/verwendung.js +478 -0
- package/admin/js/vorlagen.js +87 -8
- package/admin/js/vorlagenblatt.js +199 -20
- package/admin/js/vorschlagen.js +50 -17
- package/admin/js/werte.js +157 -5
- package/admin/js/zuordnung.js +32 -36
- package/admin/js/zustand.js +3 -0
- package/admin/js/zustandsliste.js +59 -7
- package/admin/sprachen/de.json +67 -3
- package/admin/sprachen/en.json +67 -3
- package/admin/vorlagen/messpunkt.json +2 -1
- package/admin/vorlagen/tasmota-lampe.json +2 -1
- package/admin/vorlagen/tasmota-mehrfach.json +2 -1
- package/admin/vorlagen/tasmota-rgb.json +2 -1
- package/admin/vorlagen/tasmota-steckdose.json +2 -1
- package/io-package.json +27 -27
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { katalogFehlt } from './katalog.js';
|
|
|
11
11
|
import { funktionsAuswahl, kennungZuFunktion } from './aufzaehlungen.js';
|
|
12
12
|
import { musterVon, platzFuerRolle, typenFuerRolle } from './erkennung.js';
|
|
13
13
|
import { musterZeile, musterName } from './musternamen.js';
|
|
14
|
-
import { ladeVorlagen, aendereVorlagen, setzeMeta, pruefeVorlage, hinweisTaugt, INSTANZ_ID } from './vorlagen.js';
|
|
14
|
+
import { ladeVorlagen, aendereVorlagen, setzeMeta, pruefeVorlage, wendeAn, hinweisTaugt, INSTANZ_ID } from './vorlagen.js';
|
|
15
15
|
import { kindZustaende, hatPunkt, feldAusFormel } from './werte.js';
|
|
16
16
|
import { opt } from './entwurf.js';
|
|
17
17
|
import { zeichneErgebnis } from './ergebnis.js';
|
|
@@ -287,7 +287,17 @@ export function zeichneVorlagenBlatt(host) {
|
|
|
287
287
|
Musterschluessel. musterwahl.js loest es seit laengerem so. */
|
|
288
288
|
(D ? Object.keys(D.patterns).sort() : []).forEach(function (m) {
|
|
289
289
|
var typ = D.patterns[m].type || m;
|
|
290
|
-
|
|
290
|
+
/* Beschriftet wird nach dem TYP, nicht nach dem Musterschluessel.
|
|
291
|
+
|
|
292
|
+
Der Wert stand seit dem 11.09.2026 richtig da, die Beschriftung
|
|
293
|
+
nicht: `musterZeile(m)` schlug den Schluessel nach, und fuer die
|
|
294
|
+
drei Muster, die anders heissen als ihr Typ (`blinds`,
|
|
295
|
+
`mediaPlayer`, `levelSlider`), kennt der Admin keinen Beinamen.
|
|
296
|
+
Im Feld stand am Rollladen deshalb „blinds" statt „Jalousien ·
|
|
297
|
+
blind" - der Musterschluessel, den der Nutzer nirgends sonst
|
|
298
|
+
sieht. Die Musterwahl (musterwahl.js) geht seit jeher ueber den
|
|
299
|
+
Typ. Gefunden produktiv am 15.09.2026 (I37). */
|
|
300
|
+
var o = opt(typ, musterZeile(typ));
|
|
291
301
|
if (typ === v.geraetetyp) { o.selected = true; }
|
|
292
302
|
st.appendChild(o);
|
|
293
303
|
});
|
|
@@ -465,6 +475,18 @@ export function zeichneVorlagenBlatt(host) {
|
|
|
465
475
|
? probelauf(ohneUnterstrich(v), v._quelle === 'benutzer') : [];
|
|
466
476
|
var gew = treffer.filter(function (t) { return t.gewinnt; }).length;
|
|
467
477
|
ph2.appendChild(el('span', 'chip ' + (gew ? 'ok' : 'warn'), tr('tpls.hits', treffer.length, gew)));
|
|
478
|
+
/* „Getroffen" heisst nur, dass die Erkennung anspringt. Wie oft die
|
|
479
|
+
Vorlage danach gar nicht antreten kann, weil eine Pflichtzeile ihre
|
|
480
|
+
Quelle nicht findet, gehoert daneben — das ist die Zahl, an der man
|
|
481
|
+
beim Bauen einer Vorlage merkt, dass ihre Erkennung breiter greift
|
|
482
|
+
als ihre Zeilen. Neutral gefaerbt: eine Spezialvorlage SOLL auf die
|
|
483
|
+
meisten Geraete nicht passen. */
|
|
484
|
+
var passtNicht = treffer.filter(function (t) { return !t.greift; }).length;
|
|
485
|
+
if (passtNicht) {
|
|
486
|
+
var cl = el('span', 'chip mut', tr('tpls.fitsNot', passtNicht));
|
|
487
|
+
cl.title = tr('tpls.needsRowHint');
|
|
488
|
+
ph2.appendChild(cl);
|
|
489
|
+
}
|
|
468
490
|
pk.appendChild(ph2);
|
|
469
491
|
var pb2 = el('div');
|
|
470
492
|
pb2.style.padding = '9px 12px 11px';
|
|
@@ -473,14 +495,35 @@ export function zeichneVorlagenBlatt(host) {
|
|
|
473
495
|
} else {
|
|
474
496
|
treffer.slice(0, 40).forEach(function (t) {
|
|
475
497
|
var r = el('div', 'slot');
|
|
476
|
-
|
|
498
|
+
/* Die Kennung darf schrumpfen, die Marke rechts nicht: ihr Text
|
|
499
|
+
ist nicht umbrechbar (`.chip` steht auf nowrap) und wuerde in
|
|
500
|
+
einer festen Spalte abgeschnitten. */
|
|
501
|
+
r.style.gridTemplateColumns = 'minmax(0,1fr) 72px 64px auto';
|
|
477
502
|
var idl = el('span', 'sn', t.id);
|
|
478
503
|
idl.style.fontFamily = 'var(--mono)';
|
|
504
|
+
idl.style.wordBreak = 'break-all';
|
|
479
505
|
r.appendChild(idl);
|
|
480
506
|
r.appendChild(el('span', 'rx', tr('tpls.evidence', t.eigen.belege)));
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
507
|
+
/* Wie viele Zeilen dort wirklich entstehen. Die Belege sagen, wie
|
|
508
|
+
gut die Erkennung greift; erst die Zeilen sagen, was dabei
|
|
509
|
+
herauskommt. */
|
|
510
|
+
r.appendChild(el('span', 'rx',
|
|
511
|
+
t.zeilen === 1 ? tr('tpls.rowBuilt') : tr('tpls.rowsBuilt', t.zeilen)));
|
|
512
|
+
/* Drei Ausgaenge, nicht zwei: gewonnen und getragen, duenn
|
|
513
|
+
gewonnen, verloren. Der mittlere sah ohne eigene Marke aus wie
|
|
514
|
+
ein Erfolg. */
|
|
515
|
+
if (!t.greift) {
|
|
516
|
+
/* Passt hier nicht — und es steht dabei, woran es liegt. Ohne
|
|
517
|
+
den Namen der Zeile raet man, welches Feld am Geraet fehlt. */
|
|
518
|
+
var cn = el('span', 'chip bad', t.fehlendeZeile
|
|
519
|
+
? tr('tpls.needsRow', t.fehlendeZeile) : tr('tpls.buildsNone'));
|
|
520
|
+
cn.title = tr('tpls.needsRowHint');
|
|
521
|
+
r.appendChild(cn);
|
|
522
|
+
} else {
|
|
523
|
+
r.appendChild(el('span', 'chip ' + (t.gewinnt ? 'ok' : 'bad'),
|
|
524
|
+
t.gewinnt ? (t.gegner ? tr('tpls.beats', textIn(t.gegner.vorlage.name, sprache)) : tr('tpls.onlyOne'))
|
|
525
|
+
: tr('tpls.losesTo', textIn(t.gegner.vorlage.name, sprache))));
|
|
526
|
+
}
|
|
484
527
|
pb2.appendChild(r);
|
|
485
528
|
});
|
|
486
529
|
if (treffer.length > 40) {
|
|
@@ -1540,16 +1583,41 @@ var probeGeholt = {};
|
|
|
1540
1583
|
|
|
1541
1584
|
function probeWerteHolen(v, danach) {
|
|
1542
1585
|
var erk = v.erkennung || {};
|
|
1543
|
-
if (!erk.inhalt) { return false; }
|
|
1544
1586
|
var ph = v.mehrfach ? (v.mehrfach.platzhalter || '%N%') : null;
|
|
1587
|
+
|
|
1588
|
+
/* Nicht nur die Erkennungspunkte, sondern auch die, aus denen die
|
|
1589
|
+
ZEILEN lesen — samt ihrer Ersatzwege.
|
|
1590
|
+
|
|
1591
|
+
Sonst urteilt der Probelauf ueber Felder in Werten, die er gar nicht
|
|
1592
|
+
hat: am `Brenner` lag `tele.STATE` im Speicher und `tele.SENSOR`
|
|
1593
|
+
nicht, und derselbe Probelauf meldete je nachdem, was man vorher
|
|
1594
|
+
angeklickt hatte, „1 statt 11" oder „1 statt 20" (Ricardo,
|
|
1595
|
+
14.09.2026). Eine Zahl, die von der Vorgeschichte abhaengt, ist
|
|
1596
|
+
keine. Hier soll dasselbe herauskommen wie beim „Alias anlegen",
|
|
1597
|
+
und dort sind alle Werte des Geraets geladen. */
|
|
1598
|
+
var pfade = Object.keys(erk.inhalt || {});
|
|
1599
|
+
(v.zustaende || []).forEach(function (z) {
|
|
1600
|
+
if (z.absolut) { return; }
|
|
1601
|
+
if (z.lesen) { pfade.push(z.lesen); }
|
|
1602
|
+
var sonst = z.lesenSonst;
|
|
1603
|
+
if (!sonst) { return; }
|
|
1604
|
+
(Array.isArray(sonst) ? sonst : [sonst]).forEach(function (x) {
|
|
1605
|
+
var pf = (typeof x === 'string') ? x : x.punkt;
|
|
1606
|
+
if (pf) { pfade.push(pf); }
|
|
1607
|
+
});
|
|
1608
|
+
});
|
|
1609
|
+
pfade = pfade.filter(function (x, i, a) { return x && a.indexOf(x) === i; });
|
|
1610
|
+
if (!pfade.length) { return false; }
|
|
1611
|
+
|
|
1545
1612
|
var fehlt = [];
|
|
1546
1613
|
kandidatenFuer(v).forEach(function (kanal) {
|
|
1547
|
-
|
|
1614
|
+
pfade.forEach(function (p0) {
|
|
1548
1615
|
var pfad = ph ? String(p0).split(ph).join('1') : p0;
|
|
1549
1616
|
var id = hatPunkt(kanal, pfad);
|
|
1550
1617
|
if (id && S.werte[id] === undefined && !probeGeholt[id]) { fehlt.push(id); }
|
|
1551
1618
|
});
|
|
1552
1619
|
});
|
|
1620
|
+
fehlt = fehlt.filter(function (x, i, a) { return a.indexOf(x) === i; });
|
|
1553
1621
|
if (!fehlt.length) { return false; }
|
|
1554
1622
|
var offen = fehlt.length;
|
|
1555
1623
|
fehlt.forEach(function (id) {
|
|
@@ -1581,19 +1649,57 @@ export function probelauf(vorlage, alsEigene) {
|
|
|
1581
1649
|
|
|
1582
1650
|
/* Wer gewinnt hier heute, mit dem Bestand wie er ist? Beim
|
|
1583
1651
|
Aktualisieren ist das oft die Vorlage selbst — dann wechselt
|
|
1584
|
-
nichts, auch wenn sie den Vergleich gegen alle anderen gewinnt.
|
|
1585
|
-
|
|
1652
|
+
nichts, auch wenn sie den Vergleich gegen alle anderen gewinnt.
|
|
1653
|
+
|
|
1654
|
+
Die Punktbeste ist nicht zwingend die Gewinnerin: greift sie am
|
|
1655
|
+
Geraet nicht, ruecken die naechsten nach — genau wie in
|
|
1656
|
+
`vorschlag`. Vorher stand hier nur DIE eine punktbeste andere, und
|
|
1657
|
+
griff die nicht, erklaerte sich die gepruefte Vorlage zur
|
|
1658
|
+
Siegerin. An `PC_Bastelzimmer` meldete deshalb „Reiner Messpunkt"
|
|
1659
|
+
einen Sieg, obwohl `tasmota-steckdose` dazwischenliegt und dort
|
|
1660
|
+
wirklich greift (Ricardo, 14.09.2026). */
|
|
1661
|
+
var rang = [];
|
|
1586
1662
|
S.VORLAGEN.forEach(function (v) {
|
|
1587
1663
|
var x = pruefeVorlage(v, p);
|
|
1588
|
-
if (
|
|
1589
|
-
if (!jetzt || x.punkte > jetzt.punkte) { jetzt = x; }
|
|
1590
|
-
if (v.id !== neu.id && (!andere || x.punkte > andere.punkte)) { andere = x; }
|
|
1664
|
+
if (x.passt) { rang.push(x); }
|
|
1591
1665
|
});
|
|
1666
|
+
rang.sort(function (a, b) { return b.punkte - a.punkte; });
|
|
1667
|
+
var ersteGreifende = function (ausser) {
|
|
1668
|
+
for (var i = 0; i < rang.length; i++) {
|
|
1669
|
+
if (ausser && rang[i].vorlage.id === ausser) { continue; }
|
|
1670
|
+
var x = wendeAn(rang[i].vorlage, p, rang[i].gruende, rang[i].instanz, true);
|
|
1671
|
+
if (x && x.states.length && !x.pflichtFehlt) { return rang[i]; }
|
|
1672
|
+
}
|
|
1673
|
+
return null;
|
|
1674
|
+
};
|
|
1675
|
+
var jetzt = ersteGreifende(null);
|
|
1676
|
+
var andere = ersteGreifende(neu.id);
|
|
1677
|
+
/* Gewinnen ist nicht dasselbe wie greifen.
|
|
1678
|
+
|
|
1679
|
+
`pruefeVorlage` fragt, ob die genannten Punkte da sind; `wendeAn`
|
|
1680
|
+
braucht bestimmte Felder darin. Eine Vorlage, deren Zeilen
|
|
1681
|
+
`SML.Total_Summe` lesen, gewinnt an jeder Tasmota mit
|
|
1682
|
+
`tele.SENSOR` — und baut dort dann keine einzige Zeile.
|
|
1683
|
+
|
|
1684
|
+
Das laesst sich nicht vorhersagen, ohne zu raten. Also wird es
|
|
1685
|
+
gemessen: derselbe Aufruf, der beim Anlegen auch laeuft. Kostet
|
|
1686
|
+
nichts, weil die Kandidatenliste ohnehin schon durchgegangen
|
|
1687
|
+
wird (gefunden 14.09.2026, C3). */
|
|
1688
|
+
/* Mit `auchOhneTreffer`, damit auch der gescheiterte Fall zurueckkommt
|
|
1689
|
+
— sonst wuesste man nur „null", nicht warum. */
|
|
1690
|
+
var gebaut = wendeAn(neu, p, t.gruende, t.instanz, true);
|
|
1691
|
+
var greift = !!(gebaut && gebaut.states.length && !gebaut.pflichtFehlt);
|
|
1692
|
+
|
|
1693
|
+
/* Gewonnen hat nur, wer hier auch greift — und `andere` ist bereits
|
|
1694
|
+
die beste, die das tut. */
|
|
1695
|
+
var vorn = greift && (!andere || t.punkte > andere.punkte);
|
|
1696
|
+
|
|
1592
1697
|
raus.push({
|
|
1593
1698
|
id: p, eigen: t, gegner: andere,
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1699
|
+
greift: greift, fehlendeZeile: gebaut ? gebaut.pflichtFehlt : null,
|
|
1700
|
+
zeilen: gebaut ? gebaut.states.length : 0,
|
|
1701
|
+
gewinnt: vorn,
|
|
1702
|
+
wechsel: !!(jetzt && jetzt.vorlage.id !== neu.id && vorn)
|
|
1597
1703
|
});
|
|
1598
1704
|
});
|
|
1599
1705
|
return raus;
|
|
@@ -1738,7 +1844,17 @@ export function zeichneVorlagenDialog() {
|
|
|
1738
1844
|
"- keiner -", obwohl "blind" gesetzt war. */
|
|
1739
1845
|
(D ? Object.keys(D.patterns).sort() : []).forEach(function (m) {
|
|
1740
1846
|
var typ = D.patterns[m].type || m;
|
|
1741
|
-
|
|
1847
|
+
/* Beschriftet wird nach dem TYP, nicht nach dem Musterschluessel.
|
|
1848
|
+
|
|
1849
|
+
Der Wert stand seit dem 11.09.2026 richtig da, die Beschriftung
|
|
1850
|
+
nicht: `musterZeile(m)` schlug den Schluessel nach, und fuer die
|
|
1851
|
+
drei Muster, die anders heissen als ihr Typ (`blinds`,
|
|
1852
|
+
`mediaPlayer`, `levelSlider`), kennt der Admin keinen Beinamen.
|
|
1853
|
+
Im Feld stand am Rollladen deshalb „blinds" statt „Jalousien ·
|
|
1854
|
+
blind" - der Musterschluessel, den der Nutzer nirgends sonst
|
|
1855
|
+
sieht. Die Musterwahl (musterwahl.js) geht seit jeher ueber den
|
|
1856
|
+
Typ. Gefunden produktiv am 15.09.2026 (I37). */
|
|
1857
|
+
var o = opt(typ, musterZeile(typ));
|
|
1742
1858
|
if (typ === k.geraetetyp) { o.selected = true; }
|
|
1743
1859
|
st.appendChild(o);
|
|
1744
1860
|
});
|
|
@@ -2026,6 +2142,7 @@ export function zeichneVorlagenDialog() {
|
|
|
2026
2142
|
r.style.gridTemplateColumns = '1fr 150px 130px';
|
|
2027
2143
|
var idl = el('span', 'sn', t.id);
|
|
2028
2144
|
idl.style.fontFamily = 'var(--mono)';
|
|
2145
|
+
idl.style.wordBreak = 'break-all';
|
|
2029
2146
|
if (t.id === (z.e.kanal || '')) { idl.style.fontWeight = '700'; }
|
|
2030
2147
|
r.appendChild(idl);
|
|
2031
2148
|
r.appendChild(el('span', 'rx', tr('tpls.evidence', t.eigen.belege)));
|
|
@@ -2058,19 +2175,81 @@ export function zeichneVorlagenDialog() {
|
|
|
2058
2175
|
|
|
2059
2176
|
var b = $('#btn-tpl-save');
|
|
2060
2177
|
if (b) {
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2178
|
+
/* Fuenf Gruende sperren den Knopf - und jeder von ihnen gehoert
|
|
2179
|
+
hingeschrieben.
|
|
2180
|
+
|
|
2181
|
+
Bisher wurde nur `disabled` gesetzt, `title` blieb leer. Bei
|
|
2182
|
+
fehlenden Pflichtpunkten stand die Erklaerung weiter unten in der
|
|
2183
|
+
Probelauf-Karte, fuer die man scrollen musste; bei den anderen
|
|
2184
|
+
vier stand sie nirgends. Ricardo stiess darauf, als er fuer den
|
|
2185
|
+
Objektdump eine eigene Vorlage anlegen wollte - grauer Knopf,
|
|
2186
|
+
kein Wort dazu (I32, 11.09.2026). Ein Knopf, der nicht sagt warum,
|
|
2187
|
+
ist derselbe Fehler wie eine Sperre ohne Begruendung (U28). */
|
|
2188
|
+
var gruende = sperrgruende(z);
|
|
2189
|
+
b.disabled = gruende.length > 0;
|
|
2190
|
+
b.title = gruende.join(' · ');
|
|
2064
2191
|
b.textContent = k.modus === 'update' ? tr('tpls.saveUpdate') : tr('tpls.saveNew');
|
|
2192
|
+
if (gruende.length) {
|
|
2193
|
+
var sk = el('div', 'aside w');
|
|
2194
|
+
sk.style.borderLeftColor = 'var(--bad)';
|
|
2195
|
+
sk.appendChild(el('b', null, tr('tpls.lockedWhy')));
|
|
2196
|
+
var sul = el('ul');
|
|
2197
|
+
gruende.forEach(function (x) { sul.appendChild(el('li', null, x)); });
|
|
2198
|
+
sk.appendChild(sul);
|
|
2199
|
+
body.appendChild(sk);
|
|
2200
|
+
}
|
|
2065
2201
|
}
|
|
2066
2202
|
var tt = $('#tpl-titel');
|
|
2067
2203
|
if (tt) { tt.textContent = tr('tpls.title', z.e.kanal.split('.').pop()); }
|
|
2068
2204
|
}
|
|
2069
2205
|
|
|
2206
|
+
/* Die fuenf Gruende, die das Speichern sperren - an einer Stelle.
|
|
2207
|
+
|
|
2208
|
+
Gebraucht werden sie zweimal: beim Zeichnen, um Knopf und Kasten zu
|
|
2209
|
+
beschriften, und beim Speichern selbst. Das zweite ist keine
|
|
2210
|
+
Doppelung, sondern die eigentliche Sicherung: Der Knopf wird beim
|
|
2211
|
+
VERLASSEN eines Feldes neu bewertet, nicht beim Tippen. Wer eine
|
|
2212
|
+
offene Klammer in den Namenshinweis schreibt und ohne Umweg auf
|
|
2213
|
+
„Vorlage speichern" klickt, hat einen blauen Knopf vor sich - und die
|
|
2214
|
+
Vorlage wurde mit `namenshinweis: "("` gespeichert (Ricardo,
|
|
2215
|
+
12.09.2026, am Testsystem nachgestellt; `hinweisTrifft` faengt den
|
|
2216
|
+
kaputten Ausdruck zwar ab, aber gespeichert gehoert er trotzdem
|
|
2217
|
+
nicht). */
|
|
2218
|
+
export function sperrgruende(z) {
|
|
2219
|
+
if (!z) { return []; }
|
|
2220
|
+
var k = z.kopf;
|
|
2221
|
+
var neu = baueVorlage(z);
|
|
2222
|
+
var gruende = [];
|
|
2223
|
+
if (!neu.erkennung.erforderlich.length) { gruende.push(tr('tpls.lockNoRequired')); }
|
|
2224
|
+
if (!k.id) { gruende.push(tr('tpls.lockNoId')); }
|
|
2225
|
+
var fremd = fremdeMitId(k);
|
|
2226
|
+
if (fremd) { gruende.push(tr('tpls.lockIdTaken', fremd.id)); }
|
|
2227
|
+
if (!hinweisTaugt(k.namenshinweis)) { gruende.push(tr('tpls.lockBadHint')); }
|
|
2228
|
+
if (!z.zeilen.filter(function (r) { return !r.weg; }).length) { gruende.push(tr('tpls.lockNoRows')); }
|
|
2229
|
+
return gruende;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2070
2232
|
export function speichereVorlageAusDialog() {
|
|
2071
2233
|
if (!S.tplZ) { return; }
|
|
2072
2234
|
var neu = baueVorlage(S.tplZ);
|
|
2073
2235
|
|
|
2236
|
+
/* Letzte Instanz, unabhaengig vom Zustand des Knopfes. */
|
|
2237
|
+
var gesperrt = sperrgruende(S.tplZ);
|
|
2238
|
+
if (gesperrt.length) {
|
|
2239
|
+
var bodyS = $('#tpl-body');
|
|
2240
|
+
var altS = bodyS.querySelector('.sperrgrund-kasten');
|
|
2241
|
+
if (altS) { altS.remove(); }
|
|
2242
|
+
var wS = el('div', 'aside w sperrgrund-kasten');
|
|
2243
|
+
wS.style.borderLeftColor = 'var(--bad)';
|
|
2244
|
+
wS.appendChild(el('b', null, tr('tpls.lockedWhy')));
|
|
2245
|
+
var ulS = el('ul');
|
|
2246
|
+
gesperrt.forEach(function (x) { ulS.appendChild(el('li', null, x)); });
|
|
2247
|
+
wS.appendChild(ulS);
|
|
2248
|
+
bodyS.insertBefore(wS, bodyS.firstChild);
|
|
2249
|
+
bodyS.scrollTop = 0;
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2074
2253
|
/* Widerspricht sie sich, wird nicht gespeichert — eine Vorlage, die
|
|
2075
2254
|
auf nichts passen kann, hilft niemandem und ist spaeter schwer zu
|
|
2076
2255
|
durchschauen. */
|
package/admin/js/vorschlagen.js
CHANGED
|
@@ -96,7 +96,7 @@ function kernWoerter(name) {
|
|
|
96
96
|
ueberhaupt etwas anzubieten haette. */
|
|
97
97
|
function rateLage(e) {
|
|
98
98
|
var mu = musterVon(e.want);
|
|
99
|
-
if (!mu) { return
|
|
99
|
+
if (!mu) { return { grund: 'keinMuster', frei: [], handWeg: 0 }; }
|
|
100
100
|
|
|
101
101
|
/* 1. Was schon belegt ist, bleibt unangetastet - in beide
|
|
102
102
|
Richtungen: weder der Platz noch die Zeile. */
|
|
@@ -120,7 +120,11 @@ function rateLage(e) {
|
|
|
120
120
|
gesehen[pl.name] = true;
|
|
121
121
|
freiePlaetze.push(pl);
|
|
122
122
|
});
|
|
123
|
-
|
|
123
|
+
/* Die Namen der freien Plaetze werden auch dann gebraucht, wenn nichts
|
|
124
|
+
zu holen ist - die Meldung soll sagen, WAS frei bleibt, statt nur
|
|
125
|
+
„nichts vorzuschlagen". */
|
|
126
|
+
var freiNamen = freiePlaetze.map(function (pl) { return pl.name; });
|
|
127
|
+
if (!freiePlaetze.length) { return { grund: 'voll', frei: [], handWeg: 0 }; }
|
|
124
128
|
|
|
125
129
|
/* Ein Quellpunkt gehoert an genau eine Stelle.
|
|
126
130
|
|
|
@@ -142,30 +146,58 @@ function rateLage(e) {
|
|
|
142
146
|
if (st.srcW) { quelleBelegt[st.srcW] = true; }
|
|
143
147
|
});
|
|
144
148
|
|
|
149
|
+
/* Was von Hand entschieden ist, bleibt liegen (U15, Ricardo,
|
|
150
|
+
15.09.2026).
|
|
151
|
+
|
|
152
|
+
Vorher fand der zweite Druck den Musterplatz wieder frei - die Rolle
|
|
153
|
+
stand ja nicht mehr auf dem, was das Muster verlangt - und bog
|
|
154
|
+
DIESELBE Zeile zurueck: aus der Handentscheidung `level.speed` wurde
|
|
155
|
+
wieder das vermutete `level.mode.fan`, samt Marke. Die Zeile war
|
|
156
|
+
damit doppelt bezeichnet („geaendert" UND „vermutet"), was sich
|
|
157
|
+
ausschliesst: was frisch geraten ist, kann nicht vorher geaendert
|
|
158
|
+
worden sein.
|
|
159
|
+
|
|
160
|
+
Eine zweite Zeile daneben zu legen waere die Alternative gewesen -
|
|
161
|
+
dann haette derselbe Quellpunkt aber zwei Plaetze, und die achte
|
|
162
|
+
Pruefung („Kein Punkt doppelt") meldete es zu Recht. Also bleibt der
|
|
163
|
+
Platz frei. Die Meldung unter der Leiste sagt, dass und warum. */
|
|
164
|
+
var handWeg = 0;
|
|
145
165
|
var freieZeilen = e.states.filter(function (st) {
|
|
146
166
|
if (!st.n || !st.srcR || zeileBelegt[st.n]) { return false; }
|
|
147
167
|
if (quelleBelegt[st.srcR]) { return false; }
|
|
148
168
|
if (st.srcW && quelleBelegt[st.srcW]) { return false; }
|
|
169
|
+
if (st.geaendert) { handWeg++; return false; }
|
|
149
170
|
return true;
|
|
150
171
|
});
|
|
151
|
-
if (!freieZeilen.length) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/* Gibt es ueberhaupt etwas, das der Knopf vorschlagen koennte? Die
|
|
157
|
-
komplette Rechnung, nur ohne anzuwenden - eine grobe Vorpruefung
|
|
158
|
-
liess den Knopf stehen, obwohl die Schwelle hinterher alles verwarf
|
|
159
|
-
(Ricardos Thermostat: 3 Zeilen, 22 freie Plaetze, nichts passt).
|
|
160
|
-
Der Preis ist ein zusaetzlicher Bewertungslauf je Zeichnen; die
|
|
161
|
-
teuren Erkennungslaeufe macht das Zeichnen ohnehin mehrfach. */
|
|
162
|
-
export function rateMoeglich(e) {
|
|
163
|
-
return ratePlaetze(e, true) > 0;
|
|
172
|
+
if (!freieZeilen.length) {
|
|
173
|
+
return { grund: 'vergeben', frei: freiNamen, handWeg: handWeg };
|
|
174
|
+
}
|
|
175
|
+
return { grund: null, mu: mu, namenDa: namenDa, freiePlaetze: freiePlaetze,
|
|
176
|
+
freieZeilen: freieZeilen, frei: freiNamen, handWeg: handWeg };
|
|
164
177
|
}
|
|
165
178
|
|
|
179
|
+
/* Der Rateschritt - mit `trocken` rechnet er nur, ohne den Entwurf
|
|
180
|
+
anzufassen. Zurueck kommt, wie viele Plaetze belegt wurden (`anzahl`),
|
|
181
|
+
und wenn keiner: warum nicht.
|
|
182
|
+
|
|
183
|
+
`grund` ist einer von vier Faellen - `keinMuster`, `voll` (kein Platz
|
|
184
|
+
frei), `vergeben` (Plaetze frei, aber kein uebriger Punkt mehr da),
|
|
185
|
+
`keinTreffer` (beides da, nichts kam ueber die Schwelle). `frei` nennt
|
|
186
|
+
die Plaetze, die frei bleiben, `handWeg` die Zeilen, die als
|
|
187
|
+
Handentscheidung liegenblieben. Der Text dazu wird dort gebaut, wo die
|
|
188
|
+
Sprachdatei liegt (musterwahl.js); hier steht nur der Befund.
|
|
189
|
+
|
|
190
|
+
Der Trockenlauf rechnet alles bis zur Schwelle durch. Eine grobe
|
|
191
|
+
Vorpruefung genuegte nicht: sie liess den Knopf stehen, obwohl die
|
|
192
|
+
Schwelle hinterher alles verwarf (Ricardos Thermostat, 3 Zeilen, 22
|
|
193
|
+
freie Plaetze, nichts passt). Seit dem 15.09.2026 laeuft er nur noch
|
|
194
|
+
einmal je Zeichnen - vorher fragte erst `rateMoeglich`, ob der Knopf
|
|
195
|
+
ueberhaupt hingehoert, und der Klick rechnete dasselbe noch einmal. */
|
|
166
196
|
export function ratePlaetze(e, trocken) {
|
|
167
197
|
var lage = rateLage(e);
|
|
168
|
-
if (
|
|
198
|
+
if (lage.grund) {
|
|
199
|
+
return { anzahl: 0, grund: lage.grund, frei: lage.frei, handWeg: lage.handWeg };
|
|
200
|
+
}
|
|
169
201
|
var namenDa = lage.namenDa;
|
|
170
202
|
var freiePlaetze = lage.freiePlaetze, freieZeilen = lage.freieZeilen;
|
|
171
203
|
|
|
@@ -355,7 +387,8 @@ export function ratePlaetze(e, trocken) {
|
|
|
355
387
|
st.geratenPartner = partner.n;
|
|
356
388
|
}
|
|
357
389
|
});
|
|
358
|
-
return gesetzt
|
|
390
|
+
return { anzahl: gesetzt, grund: gesetzt ? null : 'keinTreffer',
|
|
391
|
+
frei: lage.frei, handWeg: lage.handWeg };
|
|
359
392
|
}
|
|
360
393
|
|
|
361
394
|
/* Eine einzelne Vermutung zuruecknehmen. */
|
package/admin/js/werte.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { S } from './zustand.js';
|
|
8
8
|
import { socket } from './verbindung.js';
|
|
9
|
-
import { tr } from './sprache.js';
|
|
9
|
+
import { tr, txt } from './sprache.js';
|
|
10
10
|
import './entwurf.js';
|
|
11
11
|
|
|
12
12
|
/* Direkt nachschlagen — die Vorlagen nennen den Pfad ja vollstaendig
|
|
@@ -76,10 +76,23 @@ export function mitVorspann(pre) {
|
|
|
76
76
|
return raus;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/* Wie der Baum: POWER2 vor POWER10.
|
|
80
|
+
|
|
81
|
+
`keysSorted` MUSS byteweise sortiert bleiben - die Binaersuche in
|
|
82
|
+
`mitVorspann` haengt daran. Fuer die Anzeige ist das die falsche
|
|
83
|
+
Ordnung: an einer Mehrfachsteckdose stand `POWER10` vor `POWER2`,
|
|
84
|
+
waehrend der Baum daneben richtig zaehlte (er nimmt `Intl.Collator`
|
|
85
|
+
mit `numeric: true`). Also wird hier nachsortiert, wo die Liste
|
|
86
|
+
entsteht - je Kanal ein paar Dutzend Eintraege, nicht der
|
|
87
|
+
Gesamtindex (gemessen 12.09.2026). */
|
|
88
|
+
var ZEILEN_VERGLEICH = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
|
89
|
+
|
|
79
90
|
export function kindZustaende(kanal) {
|
|
80
91
|
var pre = kanal + '.';
|
|
81
92
|
return mitVorspann(pre).filter(function (k) {
|
|
82
93
|
return S.objects[k] && S.objects[k].type === 'state';
|
|
94
|
+
}).sort(function (a, b) {
|
|
95
|
+
return ZEILEN_VERGLEICH.compare(a.slice(pre.length), b.slice(pre.length));
|
|
83
96
|
});
|
|
84
97
|
}
|
|
85
98
|
|
|
@@ -112,6 +125,60 @@ export function schreibQuelle(o) {
|
|
|
112
125
|
return (c.write === true || typeof a.write === 'string') ? (q.write || '') : '';
|
|
113
126
|
}
|
|
114
127
|
|
|
128
|
+
/* Aus einem Alias-Objekt die Felder einer Entwurfszeile.
|
|
129
|
+
|
|
130
|
+
Dieselbe Umrechnung stand fuenfmal ausprogrammiert - dreimal in
|
|
131
|
+
`entwurf.js`, zweimal in `zuordnung.js` -, jedes Mal mit derselben
|
|
132
|
+
Schreibquellenregel und leicht anderem Drumherum. Die Kommentare an
|
|
133
|
+
jenen Stellen dokumentieren vier Fehler, die genau daraus entstanden
|
|
134
|
+
sind: eine verlorene Schreibquelle am Rollladen, ein schaltender Punkt
|
|
135
|
+
der zum lesenden wurde, eine fehlende Beschriftung an Solar.Netz und
|
|
136
|
+
eine Abweichung, die die Werkbank selbst erzeugt hatte (Y5).
|
|
137
|
+
|
|
138
|
+
Was hier steht, ist die ABLEITUNG - nicht, was damit geschieht. Die
|
|
139
|
+
eine Stelle vergleicht nur, die zweite legt eine Zeile an, die beiden
|
|
140
|
+
anderen ueberschreiben eine bestehende; das bleibt bei den Aufrufern.
|
|
141
|
+
|
|
142
|
+
`baueEntwurf` ruft sie bewusst NICHT: die Funktion baut Zeilen fuer
|
|
143
|
+
zwei Faelle, Alias und rohe Quelle, und drei der Felder haengen dort
|
|
144
|
+
an dieser Unterscheidung. Sie mit Schaltern hereinzuholen waere
|
|
145
|
+
schlechter als zwei ehrliche Stellen.
|
|
146
|
+
|
|
147
|
+
Die Beschriftung folgt der Regel „leer, wenn sie dem Zeilennamen
|
|
148
|
+
gleicht" - so halten es `vomAliasUebernehmen` und der Zweig, der neue
|
|
149
|
+
Zeilen anlegt. `bestandVorrang` will sie anders (uebernehmen, sobald
|
|
150
|
+
`common.name` gesetzt ist) und behaelt dafuer eine eigene Zeile.
|
|
151
|
+
|
|
152
|
+
Und `role`/`typ` bleiben bei den beiden Stellen, die eine BESTEHENDE
|
|
153
|
+
Zeile ueberschreiben (`bestandVorrang`, `vomAliasUebernehmen`), an
|
|
154
|
+
ihrer `!== undefined`-Frage: dort steht schon der Vorlagenwert, und
|
|
155
|
+
ein Alias, der gar keine Rolle nennt, soll ihn behalten statt ihn
|
|
156
|
+
gegen den Leerstring zu tauschen, den diese Funktion liefert. Wer das
|
|
157
|
+
wegraeumt, nimmt jedem vorlagenbasierten Alias seine Rollen. */
|
|
158
|
+
export function zeileAusAlias(o, zeilenname) {
|
|
159
|
+
var c = (o && o.common) || {};
|
|
160
|
+
var a = c.alias || {};
|
|
161
|
+
var q = aliasQuellen(o);
|
|
162
|
+
var nm = txt(c.name);
|
|
163
|
+
return {
|
|
164
|
+
role: c.role || '',
|
|
165
|
+
typ: c.type || '',
|
|
166
|
+
unit: c.unit || '',
|
|
167
|
+
states: c.states || undefined,
|
|
168
|
+
wr: !!c.write,
|
|
169
|
+
/* Ein Taster meldet nichts, und der Alias sagt es mit `read: false`.
|
|
170
|
+
Die Kennung steht trotzdem in `alias.id` — ioBroker verlangt sie —,
|
|
171
|
+
aber als Lesequelle gilt sie nicht: sonst zeigte „Alias bearbeiten"
|
|
172
|
+
eine Quelle, die „Alias anlegen" nie eingetragen hat, und beim
|
|
173
|
+
naechsten Aktualisieren kippte `read` still auf `true` zurueck. */
|
|
174
|
+
srcR: (c.read === false && q.einfach) ? '' : (q.read || ''),
|
|
175
|
+
srcW: schreibQuelle(o),
|
|
176
|
+
f: (typeof a.read === 'string') ? a.read : '',
|
|
177
|
+
fw: (typeof a.write === 'string') ? a.write : '',
|
|
178
|
+
caption: (nm && nm !== zeilenname) ? nm : ''
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
115
182
|
export function aliasQuellen(obj) {
|
|
116
183
|
var a = obj && obj.common && obj.common.alias;
|
|
117
184
|
if (!a) { return {}; }
|
|
@@ -286,21 +353,58 @@ export function fmt(s) {
|
|
|
286
353
|
`JSON.parse(val)?.ENERGY?.Power ?? null`. Beide Schreibweisen muessen
|
|
287
354
|
als Feldzugriff durchgehen - sonst zeigt die Feldauswahl an einem
|
|
288
355
|
Punkt, den die Werkbank selbst gebaut hat, „eigene Formel". */
|
|
289
|
-
|
|
356
|
+
/* ---- Feldpfade: Anfang (die Vorpruefung schneidet diesen Block heraus) ---- */
|
|
357
|
+
|
|
358
|
+
/* Ein Schluessel darf nur dann hinter den Punkt, wenn er ein gueltiger
|
|
359
|
+
JS-Bezeichner ist. Sonst entsteht kein Zugriff, sondern etwas anderes:
|
|
360
|
+
aus `?.DS18B20-1` wird die Rechnung `?.DS18B20 - 1?.…` (ergibt NaN),
|
|
361
|
+
aus `?./dev/shm` und `?.1111740` wird ein Syntaxfehler. Gemessen an
|
|
362
|
+
einem Frigate-Telegramm: von 233 angebotenen Feldern liessen sich so
|
|
363
|
+
nur 27 benutzen. Deshalb Klammern, wo der Name keiner ist. */
|
|
364
|
+
var NAME_OK = /^[A-Za-z_$][\w$]*$/;
|
|
365
|
+
|
|
366
|
+
function feldSegment(name) {
|
|
367
|
+
if (NAME_OK.test(name)) { return '?.' + name; }
|
|
368
|
+
return "?.['" + String(name).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + "']";
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* Ein Wegstueck ist entweder `?.name` oder `?.['name']` — die zweite
|
|
372
|
+
Form auch mit doppelten Anfuehrungszeichen, falls sie jemand von Hand
|
|
373
|
+
so geschrieben hat. */
|
|
374
|
+
var FELDTEIL = "\\??\\.(?:[A-Za-z_$][\\w$]*|\\[(?:'(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\")\\])";
|
|
375
|
+
var FELDPFAD = new RegExp('^JSON\\.parse\\(val\\)((?:' + FELDTEIL + ')+)(?:\\s*\\?\\?\\s*null)?$');
|
|
376
|
+
var FELDSTUECK = new RegExp(FELDTEIL, 'g');
|
|
290
377
|
|
|
291
378
|
export function feldAusFormel(f) {
|
|
292
379
|
var m = FELDPFAD.exec(f || '');
|
|
293
|
-
|
|
294
|
-
|
|
380
|
+
if (!m) { return ''; }
|
|
381
|
+
var teile = [];
|
|
382
|
+
var t;
|
|
383
|
+
FELDSTUECK.lastIndex = 0;
|
|
384
|
+
while ((t = FELDSTUECK.exec(m[1])) !== null) {
|
|
385
|
+
var s = t[0].slice(t[0].indexOf('.') + 1);
|
|
386
|
+
if (s.charAt(0) === '[') {
|
|
387
|
+
/* Nur die Fluchtzeichen zurueckdrehen, die feldSegment setzt. */
|
|
388
|
+
s = s.slice(2, -2).replace(/\\(.)/g, '$1');
|
|
389
|
+
/* Ein Punkt im Schluessel selbst laesst sich im Pfadformat von
|
|
390
|
+
`jsonFelder` nicht wieder auseinanderhalten — dann lieber
|
|
391
|
+
„eigene Formel" zeigen als einen Pfad, der nicht zurueckfuehrt. */
|
|
392
|
+
if (s.indexOf('.') > -1) { return ''; }
|
|
393
|
+
}
|
|
394
|
+
teile.push(s);
|
|
395
|
+
}
|
|
396
|
+
return teile.join('.');
|
|
295
397
|
}
|
|
296
398
|
|
|
297
399
|
/* Der abgesicherte Zugriff auf ein JSON-Feld. An einer Stelle, damit die
|
|
298
400
|
Vorlagen-Abkuerzung und die Feldauswahl im Detail dasselbe bauen. */
|
|
299
401
|
export function feldFormel(feld) {
|
|
300
402
|
if (!feld) { return ''; }
|
|
301
|
-
return 'JSON.parse(val)
|
|
403
|
+
return 'JSON.parse(val)' + String(feld).split('.').map(feldSegment).join('') + ' ?? null';
|
|
302
404
|
}
|
|
303
405
|
|
|
406
|
+
/* ---- Feldpfade: Ende ---- */
|
|
407
|
+
|
|
304
408
|
export function jsonFelder(id) {
|
|
305
409
|
var st = S.werte[id];
|
|
306
410
|
if (!st || typeof st.val !== 'string') { return null; }
|
|
@@ -318,3 +422,51 @@ export function jsonFelder(id) {
|
|
|
318
422
|
})(o, '', 0);
|
|
319
423
|
return raus;
|
|
320
424
|
}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
/* ---- Was das Ziel kann ---------------------------------------------
|
|
428
|
+
|
|
429
|
+
Die Werkbank schreibt auf fremde Punkte und liest aus fremden Punkten.
|
|
430
|
+
Ob die koennen, was von ihnen verlangt wird, stand nirgends: weder
|
|
431
|
+
`common.read` noch `common.write` noch der Typ des Ziels wurden je
|
|
432
|
+
angesehen (Ricardo, 15.09.2026).
|
|
433
|
+
|
|
434
|
+
Drei Arten, wie es auseinandergeht, alle drei still:
|
|
435
|
+
|
|
436
|
+
`formelBlind` Die Schreibformel liefert Text, das Ziel nimmt keinen.
|
|
437
|
+
Der js-controller rechnet ihn auf den Zieltyp zurueck,
|
|
438
|
+
die Formel war umsonst. Aus `"ON"` wird wieder `true`.
|
|
439
|
+
`nichtSchreibbar` Das Ziel traegt `write: false`. ioBroker laesst den
|
|
440
|
+
Schreibvorgang trotzdem durch — gemessen —, der Adapter
|
|
441
|
+
dahinter reicht ihn aber nicht ans Geraet weiter.
|
|
442
|
+
Ein stiller Blindgaenger.
|
|
443
|
+
`meldetNichts` Die Lesequelle traegt `read: false`. Ein Tastendruck
|
|
444
|
+
hat keinen Zustand; gelesen wird dort nie etwas.
|
|
445
|
+
|
|
446
|
+
Gibt eine Liste zurueck, nicht ein Urteil — wer sie anzeigt, entscheidet
|
|
447
|
+
ueber die Schaerfe. */
|
|
448
|
+
export function zielKann(s) {
|
|
449
|
+
var raus = [];
|
|
450
|
+
if (!s) { return raus; }
|
|
451
|
+
|
|
452
|
+
if (s.srcW) {
|
|
453
|
+
var w = S.objects[s.srcW];
|
|
454
|
+
var wc = (w && w.common) || null;
|
|
455
|
+
if (wc) {
|
|
456
|
+
if (wc.write === false) { raus.push({ art: 'nichtSchreibbar', punkt: s.srcW }); }
|
|
457
|
+
/* Anfuehrungszeichen im Ausdruck sind der Beleg, dass die Formel Text
|
|
458
|
+
liefert; ohne sie rechnet sie mit Zahlen oder Wahrheitswerten und
|
|
459
|
+
passt zum Ziel. */
|
|
460
|
+
if (s.fw && /['"`]/.test(s.fw) && (wc.type === 'boolean' || wc.type === 'number')) {
|
|
461
|
+
raus.push({ art: 'formelBlind', punkt: s.srcW, typ: wc.type });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (s.srcR) {
|
|
467
|
+
var r = S.objects[s.srcR];
|
|
468
|
+
var rc = (r && r.common) || null;
|
|
469
|
+
if (rc && rc.read === false) { raus.push({ art: 'meldetNichts', punkt: s.srcR }); }
|
|
470
|
+
}
|
|
471
|
+
return raus;
|
|
472
|
+
}
|