iobroker.alias-workbench 0.9.10 → 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/README.md +14 -0
- package/admin/css/werkbank.css +80 -0
- package/admin/js/baum.js +8 -0
- package/admin/js/detail.js +53 -6
- package/admin/js/entwurf.js +13 -3
- package/admin/js/ergebnis.js +34 -1
- package/admin/js/mqtt.js +23 -2
- package/admin/js/musterwahl.js +196 -29
- package/admin/js/objekte.js +31 -0
- package/admin/js/pruefungen.js +28 -2
- package/admin/js/schreiben.js +96 -49
- package/admin/js/verwendung.js +478 -0
- package/admin/js/vorlagen.js +75 -6
- package/admin/js/vorlagenblatt.js +134 -17
- package/admin/js/vorschlagen.js +50 -17
- package/admin/js/werte.js +95 -5
- package/admin/js/zuordnung.js +3 -1
- package/admin/js/zustand.js +3 -0
- package/admin/js/zustandsliste.js +22 -3
- package/admin/sprachen/de.json +54 -3
- package/admin/sprachen/en.json +54 -3
- package/io-package.json +14 -14
- 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)));
|
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
|
@@ -166,7 +166,12 @@ export function zeileAusAlias(o, zeilenname) {
|
|
|
166
166
|
unit: c.unit || '',
|
|
167
167
|
states: c.states || undefined,
|
|
168
168
|
wr: !!c.write,
|
|
169
|
-
|
|
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 || ''),
|
|
170
175
|
srcW: schreibQuelle(o),
|
|
171
176
|
f: (typeof a.read === 'string') ? a.read : '',
|
|
172
177
|
fw: (typeof a.write === 'string') ? a.write : '',
|
|
@@ -348,21 +353,58 @@ export function fmt(s) {
|
|
|
348
353
|
`JSON.parse(val)?.ENERGY?.Power ?? null`. Beide Schreibweisen muessen
|
|
349
354
|
als Feldzugriff durchgehen - sonst zeigt die Feldauswahl an einem
|
|
350
355
|
Punkt, den die Werkbank selbst gebaut hat, „eigene Formel". */
|
|
351
|
-
|
|
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');
|
|
352
377
|
|
|
353
378
|
export function feldAusFormel(f) {
|
|
354
379
|
var m = FELDPFAD.exec(f || '');
|
|
355
|
-
|
|
356
|
-
|
|
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('.');
|
|
357
397
|
}
|
|
358
398
|
|
|
359
399
|
/* Der abgesicherte Zugriff auf ein JSON-Feld. An einer Stelle, damit die
|
|
360
400
|
Vorlagen-Abkuerzung und die Feldauswahl im Detail dasselbe bauen. */
|
|
361
401
|
export function feldFormel(feld) {
|
|
362
402
|
if (!feld) { return ''; }
|
|
363
|
-
return 'JSON.parse(val)
|
|
403
|
+
return 'JSON.parse(val)' + String(feld).split('.').map(feldSegment).join('') + ' ?? null';
|
|
364
404
|
}
|
|
365
405
|
|
|
406
|
+
/* ---- Feldpfade: Ende ---- */
|
|
407
|
+
|
|
366
408
|
export function jsonFelder(id) {
|
|
367
409
|
var st = S.werte[id];
|
|
368
410
|
if (!st || typeof st.val !== 'string') { return null; }
|
|
@@ -380,3 +422,51 @@ export function jsonFelder(id) {
|
|
|
380
422
|
})(o, '', 0);
|
|
381
423
|
return raus;
|
|
382
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
|
+
}
|
package/admin/js/zuordnung.js
CHANGED
|
@@ -610,7 +610,9 @@ export function uebernehmeBestand(e) {
|
|
|
610
610
|
Gleicht die Beschriftung dem Zeilennamen, bleibt sie leer - so
|
|
611
611
|
haelt es der Zweig darunter, der neue Zeilen anlegt, auch. */
|
|
612
612
|
z.caption = ist.caption;
|
|
613
|
-
|
|
613
|
+
/* Wie im Bestandsvorrang: `ist.srcR` kennt den Taster, `q2.read`
|
|
614
|
+
nicht. */
|
|
615
|
+
if (q2.read) { z.srcR = ist.srcR; }
|
|
614
616
|
/* Und dieselbe Schreibregel wie im Bestandsvorrang.
|
|
615
617
|
|
|
616
618
|
Hier stand die Schreibquelle nur fuer das getrennte Paar; sonst
|
package/admin/js/zustand.js
CHANGED
|
@@ -14,6 +14,9 @@ export var S = {
|
|
|
14
14
|
objects: {},
|
|
15
15
|
keysSorted: [],
|
|
16
16
|
kleinIndex: {},
|
|
17
|
+
/* Alias-Kanal -> Zahl der Punkte, deren Quelle es nicht (mehr) gibt.
|
|
18
|
+
Einmal beim Laden gerechnet; der Baum schlaegt nur nach. */
|
|
19
|
+
aliasKaputt: {},
|
|
17
20
|
werte: {},
|
|
18
21
|
current: null,
|
|
19
22
|
openRow: null,
|
|
@@ -17,6 +17,7 @@ import { detailZeile } from './detail.js';
|
|
|
17
17
|
import { rateAnzahl, rateZurueck, rateZurueckEine, rateBehalten } from './vorschlagen.js';
|
|
18
18
|
import { mqttEinzeln } from './mqtt.js';
|
|
19
19
|
import { zeichneErgebnis, entwurfAngefasst } from './ergebnis.js';
|
|
20
|
+
import { taster } from './schreiben.js';
|
|
20
21
|
|
|
21
22
|
export function berechnePlaetze(e, haupt) {
|
|
22
23
|
/* --- welcher Punkt sitzt auf welchem Platz --- */
|
|
@@ -375,7 +376,22 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
375
376
|
/* --- Zustandsliste --- */
|
|
376
377
|
e.states.forEach(function (s, i) {
|
|
377
378
|
var pl = platzVon[s.n];
|
|
378
|
-
|
|
379
|
+
/* „Quelle eingetragen" und „Quelle vorhanden" sind zweierlei. Bis
|
|
380
|
+
zum 14.09.2026 fragte die Zeile nur das erste - ein Alias, dessen
|
|
381
|
+
Geraet aus dem System verschwunden ist (Ricardo: der Fensterkontakt
|
|
382
|
+
zeigte noch auf den laengst abgeloesten zigbee-Adapter), stand
|
|
383
|
+
deshalb ohne jedes Zeichen da. Gesagt wurde es nur im aufgeklappten
|
|
384
|
+
Detail und im Pruefungen-Kasten. */
|
|
385
|
+
var quelleWeg = !!(s.srcR && !S.objects[s.srcR]);
|
|
386
|
+
/* Ein Taster hat keine Lesequelle, und das ist kein Mangel: das Ziel
|
|
387
|
+
sagt selbst, dass dort nichts abzuholen ist (`read: false`). Ohne
|
|
388
|
+
diese Ausnahme stand an der STOP-Zeile weiter das rote `!` mit
|
|
389
|
+
„Quelle fehlt", obwohl der Alias sich laengst schreiben liess —
|
|
390
|
+
zwei Ansichten, zwei Urteile (Ricardo, 15.09.2026, K23). */
|
|
391
|
+
var istTaster = taster(s);
|
|
392
|
+
var unfertig = (!s.n || (!s.srcR && !istTaster) || quelleWeg);
|
|
393
|
+
var wasFehlt = !s.n ? tr('list.nameMissing')
|
|
394
|
+
: (quelleWeg ? tr('list.sourceGone') : tr('list.sourceMissing'));
|
|
379
395
|
/* Angehakt, vollstaendig, aber ohne Platz im Muster. Frueher stand
|
|
380
396
|
dazu in JEDER betroffenen Zeile derselbe Satz — bei einem
|
|
381
397
|
Thermostat mit 42 Punkten zwanzigmal dasselbe, und die Spalte
|
|
@@ -425,7 +441,7 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
425
441
|
Ueberfliegen wirklich sucht: wo klemmt es. */
|
|
426
442
|
var mk = el('span', 'mk2 bad');
|
|
427
443
|
mk.textContent = (s.on && unfertig) ? '!' : '';
|
|
428
|
-
if (mk.textContent) { mk.title =
|
|
444
|
+
if (mk.textContent) { mk.title = wasFehlt; }
|
|
429
445
|
row.appendChild(mk);
|
|
430
446
|
|
|
431
447
|
var n3 = el('span', 'nm3', s.n || tr('list.newPoint'));
|
|
@@ -567,7 +583,10 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
567
583
|
n3.appendChild(fb2);
|
|
568
584
|
}
|
|
569
585
|
if (s.on) {
|
|
570
|
-
if (unfertig) { n3.appendChild(el('em', null,
|
|
586
|
+
if (unfertig) { n3.appendChild(el('em', null, wasFehlt)); }
|
|
587
|
+
/* Beim Taster gehoert hin, WARUM keine Lesequelle dasteht — sonst
|
|
588
|
+
sieht die leere Spalte aus wie ein vergessener Eintrag. */
|
|
589
|
+
else if (istTaster) { n3.appendChild(el('em', null, tr('list.buttonOnly'))); }
|
|
571
590
|
else if (pl && pl !== s.n && platzAnzahl[pl] === 1) {
|
|
572
591
|
var pe = el('em', null, pl);
|
|
573
592
|
pe.title = tr('pattern.slotName', pl, musterName(e.want) || e.want || '?');
|