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
package/admin/js/musterwahl.js
CHANGED
|
@@ -12,7 +12,7 @@ import { D, el, $ } from './basis.js';
|
|
|
12
12
|
import { tr, sprachtext } from './sprache.js';
|
|
13
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
|
|
|
@@ -184,6 +226,58 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
184
226
|
}
|
|
185
227
|
function markeFuer(m) { return bewerte(m).mark; }
|
|
186
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
|
+
|
|
187
281
|
/* Welche Muster ohne Suchtext angeboten werden. Das erkannte und das
|
|
188
282
|
gerade gewaehlte gehoeren immer dazu - sonst fiel rgbSingle heraus,
|
|
189
283
|
sobald man einmal auf light gewechselt hatte, und der Weg zurueck
|
|
@@ -262,6 +356,7 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
262
356
|
return a.rang - b.rang;
|
|
263
357
|
});
|
|
264
358
|
mEintraege = treffer;
|
|
359
|
+
var auL = aussicht();
|
|
265
360
|
|
|
266
361
|
mlist.textContent = '';
|
|
267
362
|
if (!treffer.length) {
|
|
@@ -273,8 +368,13 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
273
368
|
var z = el('div', 'vz' + (n === mMarkiert ? ' an' : '')
|
|
274
369
|
+ (t0.m === e.want ? ' gewaehlt' : ''));
|
|
275
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') : '';
|
|
276
376
|
z.appendChild(el('span', 'tiefer',
|
|
277
|
-
(t0.via ? tr('pattern.viaRole', t0.via) + ' ' : '') + markeFuer(t0.m)));
|
|
377
|
+
(t0.via ? tr('pattern.viaRole', t0.via) + ' ' : '') + markeFuer(t0.m) + nachRaten));
|
|
278
378
|
/* mousedown statt click: sonst greift der Fokusverlust zuerst und
|
|
279
379
|
die Liste ist weg, bevor der Klick ankommt. */
|
|
280
380
|
z.addEventListener('mousedown', function (ev) {
|
|
@@ -321,6 +421,73 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
|
|
|
321
421
|
};
|
|
322
422
|
mBeschriften();
|
|
323
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
|
+
|
|
324
491
|
sel.addEventListener('focus', function () {
|
|
325
492
|
sel.value = '';
|
|
326
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
|
|
package/admin/js/schreiben.js
CHANGED
|
@@ -28,6 +28,7 @@ import { zeichneErgebnis } from './ergebnis.js';
|
|
|
28
28
|
import { uebernehmeBestand , enumAenderungen, setzeZiel } from './zuordnung.js';
|
|
29
29
|
|
|
30
30
|
import { mqttEinzelnStill } from './mqtt.js';
|
|
31
|
+
import { sucheInSkripten, sucheInSkriptenMehrere, skriptListe } from './verwendung.js';
|
|
31
32
|
|
|
32
33
|
import { rateBehalten } from './vorschlagen.js';
|
|
33
34
|
|
|
@@ -270,20 +271,33 @@ export function baueObjekte(e) {
|
|
|
270
271
|
} });
|
|
271
272
|
|
|
272
273
|
e.states.forEach(function (s) {
|
|
273
|
-
if (!s.on || !s.n
|
|
274
|
+
if (!s.on || !s.n) { return; }
|
|
275
|
+
/* Ohne Lesequelle faellt die Zeile heraus - ausser sie ist ein
|
|
276
|
+
Taster, der nur schreibt (K23). */
|
|
277
|
+
if (!s.srcR && !taster(s)) { return; }
|
|
274
278
|
var id = ziel + '.' + s.n;
|
|
275
279
|
|
|
276
280
|
var alias = {};
|
|
277
|
-
|
|
281
|
+
/* ioBroker verlangt immer eine `alias.id`; ein reines `{ write: … }`
|
|
282
|
+
weist der js-controller ab („Alias id is invalid: The id is
|
|
283
|
+
empty!", gemessen 15.09.2026). Beim Taster steht deshalb das
|
|
284
|
+
Schreibziel darin, und `common.read: false` sagt, dass dort nichts
|
|
285
|
+
abzuholen ist. */
|
|
286
|
+
if (!s.srcR) { alias.id = s.srcW; }
|
|
287
|
+
else if (s.srcW && s.srcW !== s.srcR) { alias.id = { read: s.srcR, write: s.srcW }; }
|
|
278
288
|
else { alias.id = s.srcR; }
|
|
279
289
|
if (s.f) { alias.read = s.f; }
|
|
280
290
|
if (s.fw && s.srcW) { alias.write = s.fw; }
|
|
281
291
|
|
|
292
|
+
/* Ein Taster meldet nichts — dann sagt der Alias-Punkt das auch.
|
|
293
|
+
Sonst malen vis, Alexa und matter einen Kippschalter, der ewig auf
|
|
294
|
+
„aus" steht, statt eines Knopfes. */
|
|
295
|
+
var istTaster = taster(s);
|
|
282
296
|
var common = {
|
|
283
297
|
name: s.caption || s.n,
|
|
284
298
|
role: s.role,
|
|
285
299
|
type: s.typ || 'mixed',
|
|
286
|
-
read:
|
|
300
|
+
read: !istTaster,
|
|
287
301
|
write: !!s.srcW,
|
|
288
302
|
alias: alias
|
|
289
303
|
};
|
|
@@ -349,6 +363,19 @@ export function verwaiste(e) {
|
|
|
349
363
|
}
|
|
350
364
|
|
|
351
365
|
/* Was dem Schreiben im Weg steht. */
|
|
366
|
+
/* Ein Taster: keine Lesequelle, ein Schreibziel, und das Ziel sagt
|
|
367
|
+
selbst, dass es nichts zu melden hat (`common.read === false`).
|
|
368
|
+
|
|
369
|
+
Nicht ueber die Rolle entschieden: `button.stop` ist Konvention, aber
|
|
370
|
+
nur der Adapter weiss wirklich, ob am Punkt etwas abzulesen ist. An
|
|
371
|
+
`hm-rpc.2.NEQ1795694.1.STOP` steht `read: false` — hingeschrieben von
|
|
372
|
+
hm-rpc, nicht von uns. */
|
|
373
|
+
export function taster(s) {
|
|
374
|
+
if (!s || s.srcR || !s.srcW) { return false; }
|
|
375
|
+
var o = S.objects[s.srcW];
|
|
376
|
+
return !!(o && o.common && o.common.read === false);
|
|
377
|
+
}
|
|
378
|
+
|
|
352
379
|
export function pruefeSchreiben(e) {
|
|
353
380
|
var probleme = [];
|
|
354
381
|
var an = e.states.filter(function (s) { return s.on; });
|
|
@@ -361,7 +388,20 @@ export function pruefeSchreiben(e) {
|
|
|
361
388
|
namen[s.n] = 1;
|
|
362
389
|
|
|
363
390
|
if (!s.srcR) {
|
|
364
|
-
|
|
391
|
+
/* Ein Taster hat keinen Zustand zum Lesen.
|
|
392
|
+
|
|
393
|
+
`STOP` am Rollladen schreibt nur; am Geraet traegt der Punkt
|
|
394
|
+
`read: false`, der hm-rpc-Adapter setzt das selbst. Die Sperre
|
|
395
|
+
hier war deshalb richtig UND falsch zugleich: richtig, weil
|
|
396
|
+
ioBroker ohne `alias.id` keinen Punkt anlegt — falsch, weil sie
|
|
397
|
+
damit jeden Rollladen-Alias mit STOP unmoeglich machte. Kein
|
|
398
|
+
einziger ist je entstanden (gefunden 14.09.2026, K23).
|
|
399
|
+
|
|
400
|
+
Massgeblich ist, was das ZIEL kann, nicht was die Rolle
|
|
401
|
+
nahelegt: `read: false` ist eine Tatsache am Objekt, die der
|
|
402
|
+
Adapter hinschreibt. Beim Schreiben nimmt `alias.id` dann das
|
|
403
|
+
Schreibziel, und der Alias-Punkt bekommt selbst `read: false`. */
|
|
404
|
+
if (!(s.srcW && taster(s))) { probleme.push(tr('write.noSourceChosen', s.n)); }
|
|
365
405
|
} else if (!S.objects[s.srcR]) {
|
|
366
406
|
/* Der teuerste Fehler: der js-controller merkt sich „Quelle fehlt"
|
|
367
407
|
dauerhaft und korrigiert das nie. Nur loeschen und neu anlegen
|
|
@@ -1069,6 +1109,32 @@ export function zeichneLoeschen() {
|
|
|
1069
1109
|
body.appendChild(ew);
|
|
1070
1110
|
}
|
|
1071
1111
|
|
|
1112
|
+
/* Wer nennt diese Kennungen sonst noch?
|
|
1113
|
+
|
|
1114
|
+
Beim Verlegen steht die Liste seit jeher da, beim Loeschen fehlte
|
|
1115
|
+
sie - dabei ist die Folge hier endgueltig: ein Skript, das den
|
|
1116
|
+
Alias anspricht, laeuft danach ins Leere. Gesucht wird nur im
|
|
1117
|
+
Klartext und nur in den Skripten; der Hinweis darunter sagt das. */
|
|
1118
|
+
var skriptFeld = el('div');
|
|
1119
|
+
skriptFeld.style.marginTop = '11px';
|
|
1120
|
+
body.appendChild(skriptFeld);
|
|
1121
|
+
var wurzeln = [ziel].concat(S.loeschAlleAusgaenge ? geschwisterVon(ziel) : []);
|
|
1122
|
+
sucheInSkriptenMehrere(wurzeln, function (treffer) {
|
|
1123
|
+
if (S.loeschZiel !== ziel) { return; }
|
|
1124
|
+
skriptFeld.textContent = '';
|
|
1125
|
+
if (!treffer.length) {
|
|
1126
|
+
skriptFeld.appendChild(el('div', 'aside', tr('move.noScripts')));
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
var w = el('div', 'aside w');
|
|
1130
|
+
w.appendChild(el('b', null, treffer.length === 1
|
|
1131
|
+
? tr('del.scriptsHead1')
|
|
1132
|
+
: tr('del.scriptsHead', treffer.length)));
|
|
1133
|
+
w.appendChild(skriptListe(treffer));
|
|
1134
|
+
w.appendChild(el('div', 'hint', tr('del.scriptsHint')));
|
|
1135
|
+
skriptFeld.appendChild(w);
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1072
1138
|
var b = $('#btn-del-go');
|
|
1073
1139
|
b.disabled = false;
|
|
1074
1140
|
b.hidden = false;
|
|
@@ -1226,41 +1292,6 @@ function verlegeUmfang(alt) {
|
|
|
1226
1292
|
});
|
|
1227
1293
|
}
|
|
1228
1294
|
|
|
1229
|
-
/* Wer nennt diese Kennung sonst noch?
|
|
1230
|
-
|
|
1231
|
-
Die Skripte des javascript-Adapters lassen sich durchsuchen; sie sind
|
|
1232
|
-
der haeufigste Ort, an dem eine Kennung im Klartext steht. vis-
|
|
1233
|
-
Ansichten und fremde Adapter kann die Werkbank nicht durchsehen -
|
|
1234
|
-
darauf weist der Dialog eigens hin, statt Vollstaendigkeit
|
|
1235
|
-
vorzutaeuschen. */
|
|
1236
|
-
export function sucheInSkripten(kennung, fertig) {
|
|
1237
|
-
var treffer = [];
|
|
1238
|
-
var fertigMit = function () { fertig(treffer); };
|
|
1239
|
-
try {
|
|
1240
|
-
socket.emit('getObjectView', 'script', 'javascript',
|
|
1241
|
-
{ startkey: 'script.js.', endkey: 'script.js.\u9999' }, function (err, doc) {
|
|
1242
|
-
if (err || !doc || !doc.rows) { return fertigMit(); }
|
|
1243
|
-
doc.rows.forEach(function (r) {
|
|
1244
|
-
var o = r.value;
|
|
1245
|
-
var q = o && o.common && o.common.source;
|
|
1246
|
-
if (!q || q.indexOf(kennung) === -1) { return; }
|
|
1247
|
-
var zeilen = String(q).split('\n');
|
|
1248
|
-
var stellen = [];
|
|
1249
|
-
zeilen.forEach(function (z, i) {
|
|
1250
|
-
if (z.indexOf(kennung) > -1) { stellen.push(i + 1); }
|
|
1251
|
-
});
|
|
1252
|
-
treffer.push({
|
|
1253
|
-
id: r.id,
|
|
1254
|
-
name: txt((o.common || {}).name) || r.id.split('.').pop(),
|
|
1255
|
-
an: !!(o.common && o.common.enabled),
|
|
1256
|
-
zeilen: stellen
|
|
1257
|
-
});
|
|
1258
|
-
});
|
|
1259
|
-
fertigMit();
|
|
1260
|
-
});
|
|
1261
|
-
} catch { fertigMit(); }
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
1295
|
export function zeigeVerlegen(altVorgabe, zielVorgabe) {
|
|
1265
1296
|
/* Der Knopf steht auch an der Quelle, sobald es den Alias gibt (T1).
|
|
1266
1297
|
Dort ist S.current die Quelle, nicht der Alias - gemeint ist immer
|
|
@@ -1531,14 +1562,7 @@ export function zeigeVerlegen(altVorgabe, zielVorgabe) {
|
|
|
1531
1562
|
w3.appendChild(el('b', null, treffer.length === 1
|
|
1532
1563
|
? tr('move.scriptsHead1')
|
|
1533
1564
|
: tr('move.scriptsHead', treffer.length)));
|
|
1534
|
-
|
|
1535
|
-
treffer.forEach(function (t) {
|
|
1536
|
-
ul3.appendChild(el('li', null, t.name + (t.an ? '' : ' (' + tr('move.scriptOff') + ')') +
|
|
1537
|
-
' \u2014 ' + (t.zeilen.length === 1
|
|
1538
|
-
? tr('move.scriptLines', t.zeilen[0])
|
|
1539
|
-
: tr('move.scriptLinesN', t.zeilen.join(', ')))));
|
|
1540
|
-
});
|
|
1541
|
-
w3.appendChild(ul3);
|
|
1565
|
+
w3.appendChild(skriptListe(treffer));
|
|
1542
1566
|
skriptFeld.appendChild(w3);
|
|
1543
1567
|
});
|
|
1544
1568
|
};
|
|
@@ -1849,9 +1873,32 @@ export function schreibeObjekte() {
|
|
|
1849
1873
|
stehen, bis man den Reiter neu lud, und behauptete damit eine
|
|
1850
1874
|
Abweichung, die es nicht mehr gibt. Nur bei Erfolg loeschen: ging
|
|
1851
1875
|
etwas schief, ist die Aenderung tatsaechlich noch nicht drin. */
|
|
1852
|
-
if (!fehler.length && S.entwurf
|
|
1853
|
-
S.entwurf.states
|
|
1876
|
+
if (!fehler.length && S.entwurf) {
|
|
1877
|
+
if (S.entwurf.states) {
|
|
1878
|
+
S.entwurf.states.forEach(function (s) { s.geaendert = false; rateBehalten(s); });
|
|
1879
|
+
}
|
|
1854
1880
|
S.entwurf.rateMeldung = null;
|
|
1881
|
+
/* Und dasselbe eine Ebene hoeher (G51, Ricardo, 15.09.2026).
|
|
1882
|
+
|
|
1883
|
+
`angefasst` sagt „hier steht etwas Ungeschriebenes". Die Marke
|
|
1884
|
+
wurde im ganzen Programm nur gesetzt, nie geloescht - weg war
|
|
1885
|
+
sie allein, wenn der Entwurf ganz wegfiel (Loeschen, Verlegen).
|
|
1886
|
+
Nach einem gelungenen Schreibvorgang blieb sie also stehen, und
|
|
1887
|
+
der naechste Klick im Baum fragte „Ungespeicherte Aenderungen …
|
|
1888
|
+
Wechselst du jetzt, sind sie weg" - ueber einen Alias, der
|
|
1889
|
+
gerade eben geschrieben worden war.
|
|
1890
|
+
|
|
1891
|
+
Die Marke faellt nur bei Erfolg. Ging etwas schief, ist die
|
|
1892
|
+
Aenderung tatsaechlich noch nicht drin, und die Frage ist dann
|
|
1893
|
+
berechtigt.
|
|
1894
|
+
|
|
1895
|
+
Sie hat eine zweite Aufgabe, und die erledigt sich mit: Solange
|
|
1896
|
+
`angefasst` steht, baut `neuZeichnenOderAufbauen` den Entwurf
|
|
1897
|
+
absichtlich NICHT neu auf - es soll ja nichts Ungeschriebenes
|
|
1898
|
+
ueberbuegeln. Das `holeZweig` unten laeuft gleich danach; jetzt
|
|
1899
|
+
baut es den Entwurf am frisch geschriebenen Alias neu auf,
|
|
1900
|
+
statt den alten stehenzulassen. */
|
|
1901
|
+
S.entwurf.angefasst = false;
|
|
1855
1902
|
}
|
|
1856
1903
|
|
|
1857
1904
|
/* Nur der Aliaszweig, nicht die ganze Datenbank. */
|