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
package/admin/js/erkennung.js
CHANGED
|
@@ -61,11 +61,25 @@ export function rolleTrifft(r, rolle) {
|
|
|
61
61
|
Ableitung bekam so ein Punkt gar keine: er belegte seinen Platz nie,
|
|
62
62
|
liess sich beliebig oft hinzufuegen, und die Rolle fehlte auch in der
|
|
63
63
|
Auswahlliste — der Platz war auf keinem Weg zu befuellen. */
|
|
64
|
+
/* Der fehlende Anfangsanker zaehlt nicht gegen den Platz.
|
|
65
|
+
|
|
66
|
+
`vacuumCleaner.MAP_URL` schreibt `/vacuum\.map\.url$/` - ohne `^`.
|
|
67
|
+
Gemeint ist derselbe eine Rollenname wie bei den anderen; der Platz
|
|
68
|
+
blieb aber ohne Rolle, weil hier beide Anker verlangt wurden
|
|
69
|
+
(gemessen 12.09.2026 ueber alle 53 Muster: ausser MAP_URL traf es
|
|
70
|
+
nur `media.COVER`, und dessen Ausdruck `/^media\.cover(\..*)$/`
|
|
71
|
+
beschreibt wirklich keinen einen Namen - der Platz hat dafuer eine
|
|
72
|
+
`defaultRole`).
|
|
73
|
+
|
|
74
|
+
Das Endezeichen bleibt Pflicht: ohne `$` waere `/^level\.dimmer/`
|
|
75
|
+
auch von `level.dimmer.irgendwas` erfuellt, und dann ist es kein
|
|
76
|
+
Name mehr, sondern ein Praefix. */
|
|
64
77
|
export function rolleAusAusdruck(r) {
|
|
65
78
|
var q = ausdruckText(r);
|
|
66
79
|
if (!q) { return ''; }
|
|
67
|
-
if (q.
|
|
68
|
-
|
|
80
|
+
if (q.slice(-1) !== '$') { return ''; }
|
|
81
|
+
if (q.charAt(0) === '^') { q = q.slice(1); }
|
|
82
|
+
var t = q.slice(0, -1).split(String.fromCharCode(92)).join('');
|
|
69
83
|
return /^[A-Za-z0-9_]+([.][A-Za-z0-9_]+)*$/.test(t) ? t : '';
|
|
70
84
|
}
|
|
71
85
|
|
|
@@ -260,6 +274,36 @@ export function typenFuerRolle(musterName, rolle) {
|
|
|
260
274
|
return raus;
|
|
261
275
|
}
|
|
262
276
|
|
|
277
|
+
/* Welche angehakten Zeilen treffen ihren Platz in der Rolle, aber nicht
|
|
278
|
+
im Datentyp?
|
|
279
|
+
|
|
280
|
+
Ein solcher Punkt faellt lautlos aus dem Muster - und wenn es ein
|
|
281
|
+
Pflichtplatz war, greift das Muster gar nicht mehr. Dann hat auf
|
|
282
|
+
einmal KEINE Zeile einen Platz, und die Oberflaeche beschuldigte alle:
|
|
283
|
+
am Bastelzimmer_Licht standen nach einem Typwechsel an SET alle acht
|
|
284
|
+
Zeilen getoent da, das Musterfeld sagte „✕ SET fehlt", und die
|
|
285
|
+
Wahrheit stand nur in der aufgeklappten SET-Zeile (Ricardo,
|
|
286
|
+
12.09.2026).
|
|
287
|
+
|
|
288
|
+
Hier steht die Frage einmal, damit Musterfeld, Legende und Zeile
|
|
289
|
+
dieselbe Antwort geben. `pflicht` sagt, ob der betroffene Platz ein
|
|
290
|
+
Pflichtplatz ist - nur dann kippt das ganze Muster. */
|
|
291
|
+
export function typKonflikte(states, musterName) {
|
|
292
|
+
var mu = musterName && musterVon(musterName);
|
|
293
|
+
if (!mu) { return []; }
|
|
294
|
+
var raus = [];
|
|
295
|
+
(states || []).forEach(function (s) {
|
|
296
|
+
if (!s.on || !s.role || !s.typ) { return; }
|
|
297
|
+
var moegliche = typenFuerRolle(musterName, s.role);
|
|
298
|
+
if (!moegliche.length || moegliche.indexOf(s.typ) > -1) { return; }
|
|
299
|
+
var platz = platzFuerRolle(musterName, s.role);
|
|
300
|
+
if (!platz) { return; }
|
|
301
|
+
raus.push({ n: s.n, typ: s.typ, erwartet: moegliche, platz: platz.name,
|
|
302
|
+
pflicht: !!platz.required });
|
|
303
|
+
});
|
|
304
|
+
return raus;
|
|
305
|
+
}
|
|
306
|
+
|
|
263
307
|
/* Die Datentypen, die ein Platz zulaesst - als Liste.
|
|
264
308
|
|
|
265
309
|
Drei Faelle, gemessen am type-detector 6.0.1: 622 Plaetze nennen
|
package/admin/js/mqtt.js
CHANGED
|
@@ -655,6 +655,27 @@ function mqttCustom(g, thema) {
|
|
|
655
655
|
zurueckzulesen ist ein Wettlauf: die Sicht hinkt dem frisch
|
|
656
656
|
geschriebenen Objekt einen Wimpernschlag hinterher, und der
|
|
657
657
|
Trockenlauf sperrte weiter wegen eines Punktes, den es laengst gab. */
|
|
658
|
+
/* Der Typ eines Sendepunktes: `mixed`, sobald eine Schreibformel im
|
|
659
|
+
Spiel ist.
|
|
660
|
+
|
|
661
|
+
Sonst rechnet der js-controller den Formelwert beim Schreiben auf den
|
|
662
|
+
Typ des Ziels zurueck, und die Formel war umsonst: `POWER` traegt im
|
|
663
|
+
Befehlswissen `typ: boolean` UND `schreibformel: val ? "ON" : "OFF"`.
|
|
664
|
+
Aus dem "ON" wurde am boolean-Ziel wieder true, auf der Leitung stand
|
|
665
|
+
true statt ON. Geschaltet hat die Tasmota trotzdem — sie nimmt beides —,
|
|
666
|
+
aber die Formel stand nutzlos im Alias, und der von der Werkbank
|
|
667
|
+
angelegte Punkt sah anders aus als jeder von mqtt-client angelegte
|
|
668
|
+
(die sind alle `mixed`). Am Produktivsystem war es genau EINER von 196
|
|
669
|
+
(Ricardo, 15.09.2026, an alias.0.Bastelzimmer.Deckenlicht).
|
|
670
|
+
|
|
671
|
+
Keine Ausnahme fuer POWER, sondern eine Regel, die sich selbst
|
|
672
|
+
durchsetzt — sie gilt ebenso fuer `Fade` und `LedTable`. Wo keine
|
|
673
|
+
Formel steht, bleibt der genaue Typ: aus `number` zieht der Admin
|
|
674
|
+
Min, Max und Einheit. */
|
|
675
|
+
function punktTyp(w) {
|
|
676
|
+
return w.schreibformel ? 'mixed' : (w.typ || 'mixed');
|
|
677
|
+
}
|
|
678
|
+
|
|
658
679
|
export function mqttEinzelnStill(kanal, name, fertig) {
|
|
659
680
|
var bau = mqttPunktBauen(kanal, name, 'neu');
|
|
660
681
|
if (!bau) { return fertig && fertig(); }
|
|
@@ -694,7 +715,7 @@ function mqttPunktBauen(kanal, name, was) {
|
|
|
694
715
|
var common0 = {
|
|
695
716
|
name: name,
|
|
696
717
|
role: w0.rolle || 'state',
|
|
697
|
-
type: w0
|
|
718
|
+
type: punktTyp(w0),
|
|
698
719
|
read: true, write: true,
|
|
699
720
|
desc: tr('mq.createdBy'),
|
|
700
721
|
custom: mqttCustom(g, thema0)
|
|
@@ -728,7 +749,7 @@ export function mqttEinzeln(kanal, name, was, knopf) {
|
|
|
728
749
|
var common = {
|
|
729
750
|
name: name,
|
|
730
751
|
role: w.rolle || 'state',
|
|
731
|
-
type: w
|
|
752
|
+
type: punktTyp(w),
|
|
732
753
|
read: true, write: true,
|
|
733
754
|
desc: tr('mq.createdBy'),
|
|
734
755
|
custom: mqttCustom(g, thema)
|
package/admin/js/musterwahl.js
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
import { S } from './zustand.js';
|
|
11
11
|
import { D, el, $ } from './basis.js';
|
|
12
12
|
import { tr, sprachtext } from './sprache.js';
|
|
13
|
-
import { VORSCHAU, musterVon, erkenneEntwurf, platzFuerRolle, typenFuerRolle } from './erkennung.js';
|
|
13
|
+
import { VORSCHAU, musterVon, erkenneEntwurf, platzFuerRolle, typenFuerRolle, typKonflikte } from './erkennung.js';
|
|
14
14
|
import { anMusterAnpassen } from './vorlagen.js';
|
|
15
|
-
import { ratePlaetze,
|
|
15
|
+
import { ratePlaetze, rateAnzahl } from './vorschlagen.js';
|
|
16
16
|
import { zeichneErgebnis, entwurfAngefasst, knopfFrisch } from './ergebnis.js';
|
|
17
17
|
import { musterName, musterZeile } from './musternamen.js';
|
|
18
18
|
|
|
@@ -52,51 +52,93 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
52
52
|
Er wird hier gebaut, weil er zum Muster gehoert - haengt aber unten
|
|
53
53
|
in der Leiste ueber der Zustandsliste. Neben dem Musterfeld stand er
|
|
54
54
|
am Rand; die Liste, die er veraendert, faengt darunter an. */
|
|
55
|
+
/* Der Trockenlauf, der beides beantwortet: ob der Knopf etwas
|
|
56
|
+
anzubieten haette, und - wenn nicht - warum nicht. Frueher lief er
|
|
57
|
+
zweimal (einmal fuer `rateMoeglich`, einmal fuer den Klick); einmal
|
|
58
|
+
reicht, das Ergebnis traegt den Grund gleich mit. */
|
|
59
|
+
var probe = ratePlaetze(e, true);
|
|
60
|
+
|
|
61
|
+
/* Was frueher ein Sammelsatz war („entweder alle Plaetze besetzt, oder
|
|
62
|
+
kein Datenpunkt passt"), steht jetzt als der Fall da, der wirklich
|
|
63
|
+
vorliegt - mit den Namen der Plaetze, die frei bleiben. Ohne die
|
|
64
|
+
Namen war die Meldung nicht nachpruefbar.
|
|
65
|
+
|
|
66
|
+
Gezeigt wird sie, solange keine Vermutung offen ist: dann fuehrt die
|
|
67
|
+
blaue Leiste das Wort, und zwei Meldungen uebereinander widersprechen
|
|
68
|
+
sich nur. */
|
|
69
|
+
function plaetzeText(frei) {
|
|
70
|
+
var kurz = frei.slice(0, 6).join(', ');
|
|
71
|
+
return frei.length > 6 ? tr('guess.slotsMore', kurz, frei.length - 6) : kurz;
|
|
72
|
+
}
|
|
73
|
+
function meldungZu(p) {
|
|
74
|
+
if (p.anzahl > 0) { return null; }
|
|
75
|
+
var satz = p.grund === 'voll' ? tr('guess.noneFull')
|
|
76
|
+
: p.grund === 'vergeben' ? tr('guess.noneUsed', plaetzeText(p.frei))
|
|
77
|
+
: p.grund === 'keinTreffer' ? tr('guess.noneFit', plaetzeText(p.frei))
|
|
78
|
+
: null;
|
|
79
|
+
/* Der Zusatz zur Handentscheidung (U15): Steht ein Platz nur deshalb
|
|
80
|
+
leer, weil die Zeile dazu von Hand entschieden ist, muss genau das
|
|
81
|
+
dastehen - sonst sieht es aus wie ein Fehler des Ratens. */
|
|
82
|
+
if (satz && p.handWeg) { satz += ' ' + tr('guess.noneHand', p.handWeg); }
|
|
83
|
+
return satz;
|
|
84
|
+
}
|
|
85
|
+
/* Ungefragt gezeigt wird sie nur, wo sie etwas erklaert: dass kein
|
|
86
|
+
Knopf dasteht, obwohl Plaetze frei sind. Der Fall „alle Plaetze
|
|
87
|
+
besetzt" bleibt stumm - er sagt „alles gut, nichts zu tun", und das
|
|
88
|
+
stuende dann an fast jedem Geraet (dieselbe Ueberlegung wie C27).
|
|
89
|
+
Ueber den Klick ist er weiter erreichbar. */
|
|
90
|
+
e.rateMeldung = (rateAnzahl(e) || probe.grund === 'voll')
|
|
91
|
+
? null : meldungZu(probe);
|
|
92
|
+
|
|
55
93
|
/* Und nur, wenn er etwas anzubieten haette: ohne freie Plaetze oder
|
|
56
94
|
freie Zeilen ist der Knopf ein leeres Versprechen — dann faellt er
|
|
57
|
-
weg, statt beim Klick „nichts gefunden" zu melden. Zuruecknehmen
|
|
58
|
-
|
|
95
|
+
weg, statt beim Klick „nichts gefunden" zu melden. Das Zuruecknehmen
|
|
96
|
+
steht seit dem 15.09.2026 nicht mehr auch hier: derselbe Knopf hiess
|
|
97
|
+
je nach Lage „Freie Plaetze vorschlagen" oder „Vorschlaege
|
|
98
|
+
verwerfen", und daneben stand in der blauen Leiste ein zweiter mit
|
|
99
|
+
demselben Wort. Verworfen wird dort - bei dem, was es betrifft. */
|
|
100
|
+
/* Der Rueckfrage-Dialog, wenn eine Vorlage greift (U27). Er sitzt hier
|
|
101
|
+
als eigene Funktion, weil ihn zwei Knoepfe brauchen: der in der
|
|
102
|
+
Leiste und der kleine neben dem Hinweis, der Muster wechselt und
|
|
103
|
+
gleich vorschlaegt. */
|
|
104
|
+
function mitVorlagenfrage(dann) {
|
|
105
|
+
var vq = null;
|
|
106
|
+
S.VORLAGEN.forEach(function (v) { if (v.id === e.vorlage) { vq = v; } });
|
|
107
|
+
var body = $('#guess-body');
|
|
108
|
+
body.textContent = '';
|
|
109
|
+
body.appendChild(el('div', 'aside w',
|
|
110
|
+
tr('guess.tplAsk', vq ? sprachtext(vq.name) : e.vorlage)));
|
|
111
|
+
/* onclick statt addEventListener: der Dialog ist einer fuer alle
|
|
112
|
+
Geraete, der Griff muss beim Oeffnen den alten ersetzen. */
|
|
113
|
+
$('#btn-guess-go').onclick = function () {
|
|
114
|
+
$('#dlg-guess').close();
|
|
115
|
+
dann();
|
|
116
|
+
};
|
|
117
|
+
$('#dlg-guess').showModal();
|
|
118
|
+
}
|
|
119
|
+
|
|
59
120
|
var rateKnopf = null;
|
|
60
|
-
if (
|
|
121
|
+
if (probe.anzahl > 0) {
|
|
61
122
|
/* Mit passender Vorlage ist Raten nicht der normale Weg - die
|
|
62
123
|
Zuordnung steht ja schon. Der Knopf bleibt, tritt aber leise auf
|
|
63
124
|
und fragt vor dem Lauf nach (Ricardo, 25.08.2026). */
|
|
64
125
|
var beilaeufig = !!e.vorlage && !rateAnzahl(e);
|
|
65
|
-
rateKnopf = el('button', 'btn mini'
|
|
126
|
+
rateKnopf = el('button', 'btn mini'
|
|
66
127
|
+ (beilaeufig ? ' leise' : '')
|
|
67
128
|
/* Ein leiser Knopf pocht nicht - das Pochen wuerde genau die
|
|
68
129
|
Aufmerksamkeit holen, die er nicht verdient. */
|
|
69
|
-
+ (!beilaeufig && knopfFrisch('raten:' + S.current + ':
|
|
70
|
-
|
|
130
|
+
+ (!beilaeufig && knopfFrisch('raten:' + S.current + ':vor') ? ' frisch' : ''),
|
|
131
|
+
tr('guess.button'));
|
|
71
132
|
rateKnopf.title = beilaeufig ? tr('guess.tplHint') : tr('guess.hint');
|
|
72
133
|
var rateLos = function () {
|
|
73
|
-
e.rateMeldung = ratePlaetze(e)
|
|
134
|
+
e.rateMeldung = meldungZu(ratePlaetze(e));
|
|
74
135
|
entwurfAngefasst();
|
|
75
136
|
S.openRow = null;
|
|
76
137
|
zeichneErgebnis();
|
|
77
138
|
};
|
|
78
139
|
rateKnopf.addEventListener('click', function () {
|
|
79
|
-
if (rateAnzahl(e)) {
|
|
80
|
-
rateZurueck(e); e.rateMeldung = null;
|
|
81
|
-
entwurfAngefasst();
|
|
82
|
-
S.openRow = null;
|
|
83
|
-
zeichneErgebnis();
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
140
|
if (!beilaeufig) { rateLos(); return; }
|
|
87
|
-
|
|
88
|
-
S.VORLAGEN.forEach(function (v) { if (v.id === e.vorlage) { vq = v; } });
|
|
89
|
-
var body = $('#guess-body');
|
|
90
|
-
body.textContent = '';
|
|
91
|
-
body.appendChild(el('div', 'aside w',
|
|
92
|
-
tr('guess.tplAsk', vq ? sprachtext(vq.name) : e.vorlage)));
|
|
93
|
-
/* onclick statt addEventListener: der Dialog ist einer fuer alle
|
|
94
|
-
Geraete, der Griff muss beim Oeffnen den alten ersetzen. */
|
|
95
|
-
$('#btn-guess-go').onclick = function () {
|
|
96
|
-
$('#dlg-guess').close();
|
|
97
|
-
rateLos();
|
|
98
|
-
};
|
|
99
|
-
$('#dlg-guess').showModal();
|
|
141
|
+
mitVorlagenfrage(rateLos);
|
|
100
142
|
});
|
|
101
143
|
}
|
|
102
144
|
|
|
@@ -138,7 +180,17 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
138
180
|
if (!r.length) {
|
|
139
181
|
var fehlend = musterVon(m).states.filter(function (x) { return x.required; })
|
|
140
182
|
.map(function (x) { return x.name; });
|
|
141
|
-
|
|
183
|
+
/* Ein Pflichtplatz kann auch deshalb leer sein, weil die Zeile
|
|
184
|
+
dafuer im falschen Datentyp steht - die Rolle stimmt, der Typ
|
|
185
|
+
nicht, und der Punkt faellt aus dem Muster. „SET fehlt" ist dann
|
|
186
|
+
die falsche Auskunft: SET ist da. Der Typ-Grund geht vor, wo es
|
|
187
|
+
einen gibt (Ricardo, 12.09.2026, am Bastelzimmer_Licht). */
|
|
188
|
+
var konflikt = typKonflikte(pruefE.states, m)
|
|
189
|
+
.filter(function (x) { return fehlend.indexOf(x.platz) > -1; })[0];
|
|
190
|
+
if (konflikt) {
|
|
191
|
+
mark = tr('pattern.fitsNotType', konflikt.platz, konflikt.typ,
|
|
192
|
+
konflikt.erwartet.join(tr('pattern.typeOr')));
|
|
193
|
+
} else if (fehlend.length) {
|
|
142
194
|
mark = '✕ ' + tr('pattern.missing', fehlend.join(', '));
|
|
143
195
|
} else {
|
|
144
196
|
/* Kein Pflichtplatz fehlt und trotzdem passt nichts - dann liegt
|
|
@@ -174,6 +226,58 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
174
226
|
}
|
|
175
227
|
function markeFuer(m) { return bewerte(m).mark; }
|
|
176
228
|
|
|
229
|
+
/* ---- Was nach dem Raten passen wuerde ----------------------------
|
|
230
|
+
|
|
231
|
+
Ricardos Klimaanlage ist der Fall: `thermostat` greift und belegt 3
|
|
232
|
+
von 24 Plaetzen, `airCondition` faellt durch, weil sein einziger
|
|
233
|
+
Pflichtplatz MODE die Rolle nicht traegt. Nach dem Raten stuenden
|
|
234
|
+
dort 9 von 29 statt 8 von 24 - nur sieht man das nirgends, weil
|
|
235
|
+
„passt nicht" das letzte Wort behaelt. Wer nicht weiss, dass es den
|
|
236
|
+
Knopf gibt, waehlt `airCondition` nie aus.
|
|
237
|
+
|
|
238
|
+
Also wird einmal durchgerechnet, was jedes Muster nach dem Raten
|
|
239
|
+
ergaebe. Gerechnet wird auf einer Kopie - am Entwurf aendert das
|
|
240
|
+
nichts. Das Raten liest nur Namen, Werteliste, Einheit und Typ,
|
|
241
|
+
keine Messwerte; das Ergebnis haengt also allein an den Zeilen und
|
|
242
|
+
wird beim blossen Neuzeichnen nicht ungueltig. Gemerkt wird es
|
|
243
|
+
deshalb an `e` und faellt erst, wenn eine Zeile sich aendert
|
|
244
|
+
(`entwurfAngefasst`) oder ein anderes Muster gewaehlt ist.
|
|
245
|
+
|
|
246
|
+
Gemessen am `midea.AC` (43 Punkte): ein Trockenlauf 1,2 ms, alle 51
|
|
247
|
+
Muster 35 ms - einmal je Geraeteauswahl, gegen rund 200 ms fuers
|
|
248
|
+
Auswaehlen selbst. */
|
|
249
|
+
function probeMuster(m) {
|
|
250
|
+
if (!musterVon(m)) { return null; }
|
|
251
|
+
var kopie = JSON.parse(JSON.stringify({ kanal: e.kanal, states: e.states, want: m }));
|
|
252
|
+
/* Dieselbe Vorbereitung wie in `bewerte`: beim Vorschlag entscheidet
|
|
253
|
+
nicht das Muster, sondern die Rollen, die das Auswaehlen setzen
|
|
254
|
+
wuerde. */
|
|
255
|
+
if (e.vorschlag && !e.roh && m !== e.want) { anMusterAnpassen(kopie, m); }
|
|
256
|
+
ratePlaetze(kopie);
|
|
257
|
+
var r = erkenneEntwurf(kopie, m);
|
|
258
|
+
if (!r.length) { return { passt: false }; }
|
|
259
|
+
return { passt: true, gesamt: r[0].states.length,
|
|
260
|
+
belegt: r[0].states.filter(function (x) { return x.id; }).length };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function aussicht() {
|
|
264
|
+
if (e.rateAussicht && e.rateAussicht.want === e.want) { return e.rateAussicht; }
|
|
265
|
+
var proM = {}, neuPassend = [];
|
|
266
|
+
if (e.states && e.states.length) {
|
|
267
|
+
alleTypen.forEach(function (m) {
|
|
268
|
+
var p = probeMuster(m);
|
|
269
|
+
if (!p) { return; }
|
|
270
|
+
proM[m] = p;
|
|
271
|
+
/* „Neu" heisst: passt jetzt nicht, nach dem Raten schon. Das
|
|
272
|
+
gewaehlte Muster gehoert nicht dazu - es ist kein anderes. */
|
|
273
|
+
if (p.passt && m !== e.want && !bewerte(m).passt) { neuPassend.push(m); }
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
e.rateAussicht = { want: e.want, proM: proM, neu: neuPassend,
|
|
277
|
+
selbst: proM[e.want] || null };
|
|
278
|
+
return e.rateAussicht;
|
|
279
|
+
}
|
|
280
|
+
|
|
177
281
|
/* Welche Muster ohne Suchtext angeboten werden. Das erkannte und das
|
|
178
282
|
gerade gewaehlte gehoeren immer dazu - sonst fiel rgbSingle heraus,
|
|
179
283
|
sobald man einmal auf light gewechselt hatte, und der Weg zurueck
|
|
@@ -252,6 +356,7 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
252
356
|
return a.rang - b.rang;
|
|
253
357
|
});
|
|
254
358
|
mEintraege = treffer;
|
|
359
|
+
var auL = aussicht();
|
|
255
360
|
|
|
256
361
|
mlist.textContent = '';
|
|
257
362
|
if (!treffer.length) {
|
|
@@ -263,8 +368,13 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
263
368
|
var z = el('div', 'vz' + (n === mMarkiert ? ' an' : '')
|
|
264
369
|
+ (t0.m === e.want ? ' gewaehlt' : ''));
|
|
265
370
|
z.appendChild(el('span', null, musterZeile(t0.m)));
|
|
371
|
+
/* Hinter „✕ MODE fehlt" steht, ob das Muster nach dem Raten doch
|
|
372
|
+
greift - sonst liest man nur, dass es nicht passt, und haelt es
|
|
373
|
+
fuer erledigt. */
|
|
374
|
+
var nachRaten = (!bewerte(t0.m).passt && auL.proM[t0.m] && auL.proM[t0.m].passt)
|
|
375
|
+
? ' ' + tr('guess.alsoRow') : '';
|
|
266
376
|
z.appendChild(el('span', 'tiefer',
|
|
267
|
-
(t0.via ? tr('pattern.viaRole', t0.via) + ' ' : '') + markeFuer(t0.m)));
|
|
377
|
+
(t0.via ? tr('pattern.viaRole', t0.via) + ' ' : '') + markeFuer(t0.m) + nachRaten));
|
|
268
378
|
/* mousedown statt click: sonst greift der Fokusverlust zuerst und
|
|
269
379
|
die Liste ist weg, bevor der Klick ankommt. */
|
|
270
380
|
z.addEventListener('mousedown', function (ev) {
|
|
@@ -311,6 +421,73 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
311
421
|
};
|
|
312
422
|
mBeschriften();
|
|
313
423
|
|
|
424
|
+
/* Der Hinweis neben dem Feld: genannt wird EIN Muster - das beste -
|
|
425
|
+
und nur, wenn es wirklich besser ist als das gewaehlte.
|
|
426
|
+
|
|
427
|
+
Der erste Anlauf zaehlte bloss, wie viele Muster nach dem Vorschlag
|
|
428
|
+
dazukaemen, und schrieb bei mehreren „12 weitere Muster" hin. Am
|
|
429
|
+
`midea.AC` sind das tatsaechlich zwoelf - aber elf davon sind
|
|
430
|
+
Beifang: `blind` 4/19, `illuminance` 3/7, `windowTilt` 3/7 an einer
|
|
431
|
+
Klimaanlage. Eine Zahl, die Beifang mitzaehlt, verdeckt genau die
|
|
432
|
+
eine Auskunft, um die es geht (Ricardo, 15.09.2026): `airCondition`
|
|
433
|
+
9/29 gegen `thermostat` 8/24.
|
|
434
|
+
|
|
435
|
+
Gemessen wird an den belegten Plaetzen, nicht am Anteil. 9 von 29
|
|
436
|
+
ist besser als 8 von 24, weil neun Punkte des Geraets einen Platz
|
|
437
|
+
bekommen statt acht - der Anteil waere hier sogar knapp kleiner.
|
|
438
|
+
Beide Zahlen stehen NACH dem Vorschlag; „3/24" ist der Stand jetzt
|
|
439
|
+
und gehoert nicht daneben.
|
|
440
|
+
|
|
441
|
+
Ist nichts besser, steht hier nichts - der haeufigste Fall bleibt
|
|
442
|
+
damit stumm. */
|
|
443
|
+
var auF = aussicht();
|
|
444
|
+
var bestes = null;
|
|
445
|
+
auF.neu.forEach(function (m) {
|
|
446
|
+
if (!bestes || auF.proM[m].belegt > auF.proM[bestes].belegt) { bestes = m; }
|
|
447
|
+
});
|
|
448
|
+
/* Der Vergleichswert ist der Stand des gewaehlten Musters NACH dem
|
|
449
|
+
Vorschlag. Passt es auch dann nicht, ist jedes andere besser. */
|
|
450
|
+
var jetzt = (auF.selbst && auF.selbst.passt) ? auF.selbst.belegt : -1;
|
|
451
|
+
if (bestes && auF.proM[bestes].belegt > jetzt) {
|
|
452
|
+
var pB = auF.proM[bestes], nB = musterName(bestes) || bestes;
|
|
453
|
+
var hw = el('span', 'ratAussicht');
|
|
454
|
+
var htx = el('span', null, '· ' + tr('guess.better', nB));
|
|
455
|
+
htx.title = jetzt >= 0
|
|
456
|
+
? tr('guess.betterHint', nB, pB.belegt + '/' + pB.gesamt,
|
|
457
|
+
musterName(e.want) || e.want, auF.selbst.belegt + '/' + auF.selbst.gesamt)
|
|
458
|
+
: tr('guess.betterHintNone', nB, pB.belegt + '/' + pB.gesamt,
|
|
459
|
+
musterName(e.want) || e.want);
|
|
460
|
+
hw.appendChild(htx);
|
|
461
|
+
|
|
462
|
+
/* Der Weg dorthin in einem Griff: wechseln UND vorschlagen.
|
|
463
|
+
Sonst waere der Hinweis eine Ansage ohne Handlung - man muesste
|
|
464
|
+
das Muster in der Liste suchen und danach noch den Knopf in der
|
|
465
|
+
Leiste finden (Ricardo, 16.09.2026).
|
|
466
|
+
|
|
467
|
+
Ein Zeichen statt eines Wortes: neben dem Satz bleiben rund 55 px,
|
|
468
|
+
„wechseln" braucht 58. Dieselbe Bauart wie die Uebernahmeknoepfe
|
|
469
|
+
im Detail (`←`, `⟲`) - kleines Zeichen, ausfuehrlicher Tooltip. */
|
|
470
|
+
var hgo = el('button', 'btn winzig rtgo', '→');
|
|
471
|
+
hgo.title = tr('guess.betterGoHint', nB, tr('guess.button'));
|
|
472
|
+
hgo.addEventListener('click', function (ev) {
|
|
473
|
+
ev.stopPropagation();
|
|
474
|
+
var los = function () {
|
|
475
|
+
/* Beim Vorschlag entscheidet nicht das Muster, sondern die
|
|
476
|
+
Rollen, die das Auswaehlen setzt - genau wie in `mWaehlen`. */
|
|
477
|
+
if (e.vorschlag && !e.roh) { anMusterAnpassen(e, bestes); } else { e.want = bestes; }
|
|
478
|
+
e.rateMeldung = meldungZu(ratePlaetze(e));
|
|
479
|
+
entwurfAngefasst();
|
|
480
|
+
S.openRow = null;
|
|
481
|
+
zeichneErgebnis();
|
|
482
|
+
};
|
|
483
|
+
/* Greift eine Vorlage, fragt auch dieser Weg nach (U27) - er raet
|
|
484
|
+
ja genauso ueber eine fertige Zuordnung hinweg. */
|
|
485
|
+
if (e.vorlage && !rateAnzahl(e)) { mitVorlagenfrage(los); } else { los(); }
|
|
486
|
+
});
|
|
487
|
+
hw.appendChild(hgo);
|
|
488
|
+
vw.appendChild(hw);
|
|
489
|
+
}
|
|
490
|
+
|
|
314
491
|
sel.addEventListener('focus', function () {
|
|
315
492
|
sel.value = '';
|
|
316
493
|
mMarkiert = -1;
|
package/admin/js/objekte.js
CHANGED
|
@@ -41,6 +41,7 @@ export function ladeObjekte(danach) {
|
|
|
41
41
|
if (--offen === 0) {
|
|
42
42
|
S.keysSorted = Object.keys(S.objects).sort();
|
|
43
43
|
S.kleinIndex = {};
|
|
44
|
+
kaputteAliaseNeu();
|
|
44
45
|
ladeEnums();
|
|
45
46
|
/* Erst die Einstellungen, dann die Vorlagen - sonst holt die
|
|
46
47
|
Werkbank sie auch dann, wenn sie abgeschaltet sind. */
|
|
@@ -206,9 +207,39 @@ export function uebernimmObjekt(id, o) {
|
|
|
206
207
|
/* Der Baum liest aus S.keysSorted, nicht aus S.objects. Wer einen Punkt
|
|
207
208
|
hinzufuegt oder entfernt, muss den Index neu bauen — sonst kennt der
|
|
208
209
|
Baum ihn nicht, egal wie oft man zeichnet. */
|
|
210
|
+
/* Aliase, bei denen mindestens ein Punkt auf eine Quelle zeigt, die es
|
|
211
|
+
nicht (mehr) gibt.
|
|
212
|
+
|
|
213
|
+
Der teuerste Fehler am Alias: der js-controller merkt sich „Quelle
|
|
214
|
+
fehlt" dauerhaft und korrigiert das nie - nur loeschen und neu anlegen
|
|
215
|
+
hilft (siehe schreiben.js). Bis zum 14.09.2026 stand das nur im
|
|
216
|
+
aufgeklappten Detail und im Pruefungen-Kasten; die Zeile in der Liste
|
|
217
|
+
schwieg, weil sie „ist eine Quelle eingetragen" fragte statt „gibt es
|
|
218
|
+
sie".
|
|
219
|
+
|
|
220
|
+
Einmal gerechnet und gemerkt, nicht bei jedem Zeichnen: den Baum baut
|
|
221
|
+
die Werkbank an sechzehn Stellen neu. Gemessen am Produktivsystem:
|
|
222
|
+
531 Alias-Punkte in 5,2 ms. */
|
|
223
|
+
export function kaputteAliaseNeu() {
|
|
224
|
+
var raus = {};
|
|
225
|
+
Object.keys(S.objects).forEach(function (id) {
|
|
226
|
+
if (id.indexOf('alias.') !== 0) { return; }
|
|
227
|
+
var ob = S.objects[id];
|
|
228
|
+
if (!ob || ob.type !== 'state') { return; }
|
|
229
|
+
var a = (ob.common || {}).alias;
|
|
230
|
+
if (!a || !a.id) { return; }
|
|
231
|
+
var ziele = (typeof a.id === 'string') ? [a.id] : [a.id.read, a.id.write];
|
|
232
|
+
if (!ziele.some(function (q) { return q && !S.objects[q]; })) { return; }
|
|
233
|
+
var kanal = id.split('.').slice(0, -1).join('.');
|
|
234
|
+
raus[kanal] = (raus[kanal] || 0) + 1;
|
|
235
|
+
});
|
|
236
|
+
S.aliasKaputt = raus;
|
|
237
|
+
}
|
|
238
|
+
|
|
209
239
|
export function indexNeu() {
|
|
210
240
|
S.keysSorted = Object.keys(S.objects).sort();
|
|
211
241
|
S.kleinIndex = {};
|
|
242
|
+
kaputteAliaseNeu();
|
|
212
243
|
zeigeObjektzahl();
|
|
213
244
|
}
|
|
214
245
|
|
package/admin/js/pruefungen.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/* Die
|
|
1
|
+
/* Die acht Pruefungen - laufen immer mit, fassen nichts an. */
|
|
2
2
|
|
|
3
3
|
import { S } from './zustand.js';
|
|
4
4
|
import { $, el } from './basis.js';
|
|
5
5
|
import { tr } from './sprache.js';
|
|
6
|
-
import { wertVon } from './werte.js';
|
|
6
|
+
import { wertVon, zielKann } from './werte.js';
|
|
7
7
|
import './erkennung.js';
|
|
8
8
|
import { sofortRueckmeldung, mqttEinstellung } from './mqtt.js';
|
|
9
9
|
|
|
@@ -94,6 +94,31 @@ export function pruefungSenden(e) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
/* Kann das Ziel, was der Punkt von ihm verlangt?
|
|
98
|
+
|
|
99
|
+
Drei Fragen an fremde Objekte, die die Werkbank vorher nie gestellt hat:
|
|
100
|
+
Nimmt das Schreibziel den Wert der Formel an? Laesst es sich ueberhaupt
|
|
101
|
+
beschreiben? Meldet die Lesequelle etwas? Was dabei schiefgeht, faellt
|
|
102
|
+
nirgends auf — geschrieben wird trotzdem, gelesen auch, es kommt nur
|
|
103
|
+
nichts dabei heraus (Ricardo, 15.09.2026).
|
|
104
|
+
|
|
105
|
+
Hinweis, nicht Fehler: Ob am Ende wirklich nichts passiert, entscheidet
|
|
106
|
+
der Adapter hinter dem Punkt, und den kennt die Werkbank nicht. */
|
|
107
|
+
export function pruefungZielKann(e) {
|
|
108
|
+
var treffer = [];
|
|
109
|
+
e.states.forEach(function (s) {
|
|
110
|
+
if (!s.on) { return; }
|
|
111
|
+
zielKann(s).forEach(function (x) { treffer.push({ n: s.n, art: x.art, typ: x.typ }); });
|
|
112
|
+
});
|
|
113
|
+
if (!treffer.length) { return { s: 'ok', t: tr('check.targetCan'), d: tr('check.targetCanFine') }; }
|
|
114
|
+
var text = treffer.map(function (x) {
|
|
115
|
+
if (x.art === 'formelBlind') { return tr('check.targetVoidFormula', x.n, x.typ); }
|
|
116
|
+
if (x.art === 'nichtSchreibbar') { return tr('check.targetNoWrite', x.n); }
|
|
117
|
+
return tr('check.targetNoRead', x.n);
|
|
118
|
+
}).join(', ');
|
|
119
|
+
return { s: 'warn', t: tr('check.targetCan'), d: text };
|
|
120
|
+
}
|
|
121
|
+
|
|
97
122
|
export function baueChecks(e, haupt, pflichtFehlt) {
|
|
98
123
|
var an = e.states.filter(function (s) { return s.on; });
|
|
99
124
|
var ohneQuelle = an.filter(function (s) { return s.srcR && !S.objects[s.srcR]; });
|
|
@@ -113,6 +138,7 @@ export function baueChecks(e, haupt, pflichtFehlt) {
|
|
|
113
138
|
: haupt.type) : tr('check.noPatternFits') },
|
|
114
139
|
pruefungSenden(e),
|
|
115
140
|
pruefungRueckmeldung(e),
|
|
141
|
+
pruefungZielKann(e),
|
|
116
142
|
pruefungDopplung(e)
|
|
117
143
|
];
|
|
118
144
|
|