iobroker.alias-workbench 0.9.11 → 0.10.0

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/admin/js/mqtt.js CHANGED
@@ -48,9 +48,24 @@ export function mqttEinstellung(id) {
48
48
  publish: true }` galt als sendefaehig, obwohl die Instanz diesen
49
49
  Punkt nicht bedient. Fehlt das Feld, gilt der Eintrag als an — so
50
50
  legt der Admin es an, wenn man die Verknuepfung setzt. */
51
+ var aus = null;
51
52
  Object.keys(cu).forEach(function (x) {
52
- if (!k && /^mqtt/.test(x) && cu[x] && cu[x].enabled !== false) { k = x; }
53
+ if (!/^mqtt/.test(x) || !cu[x]) { return; }
54
+ if (cu[x].enabled !== false) { if (!k) { k = x; } }
55
+ else if (!aus) { aus = x; }
53
56
  });
57
+ /* Eine abgeschaltete Verknuepfung ist eine Auskunft, kein Nichtwissen.
58
+
59
+ Steht das Thema zusaetzlich in `native` — bei mqtt-client der
60
+ Normalfall —, fiel der Punkt vorher in den Zweig darunter und galt
61
+ als „Thema da, Senden unbekannt"; die Karte zeigte dafuer gruen
62
+ „angelegt, sendet", obwohl die Instanz ihn nicht bedient (Ricardo,
63
+ 20.09.2026; Lauf vom 19.09.2026, H13). Wer den Schalter ausdruecklich
64
+ auf aus gestellt hat, bekommt das jetzt gesagt. */
65
+ if (!k && aus) {
66
+ return { instanz: aus, topic: cu[aus].topic || (o.native || {}).topic || '',
67
+ publish: false, abgeschaltet: true, roh: cu[aus] };
68
+ }
54
69
  if (!k) {
55
70
  /* Manche Objekte tragen das Thema nur in native — ioBroker.mqtt und
56
71
  sonoff tun das. Fuer sie ist `publish` schlicht UNBEKANNT, nicht
@@ -149,6 +164,38 @@ export function befehlsWissen(name) {
149
164
  return null;
150
165
  }
151
166
 
167
+ /* Welche Zeilen haengen ueberhaupt am Telemetrietakt?
168
+
169
+ Betroffen ist nur die eine Bauform: aus `tele/...` lesen und nach
170
+ `cmnd/...` schreiben. Wer aus `stat/POWER1` liest, bekommt die Antwort
171
+ bei jedem Befehl sofort — SetOption59 aendert daran nichts.
172
+
173
+ Ricardos Steckdosenleiste hat keine einzige solche Zeile: seine Aliase
174
+ lesen alle aus `stat.POWERn`. Trotzdem stand der SetOption59-Block an
175
+ der Karte und die Pruefung meldete "alle in Ordnung", als waere etwas
176
+ geprueft worden. Beides ist jetzt an diese Frage gebunden
177
+ (Ricardo, 16.09.2026). */
178
+ export function telegesteuert(e) {
179
+ if (!e || !e.states) { return []; }
180
+ return e.states.filter(function (x) {
181
+ if (!x.on || !x.srcW || !x.srcR) { return false; }
182
+ return /\.tele\./.test(x.srcR) && /\.cmnd\./.test(x.srcW);
183
+ });
184
+ }
185
+
186
+ /* Wie oft meldet das Geraet von sich aus? Steht in tele/STATE.
187
+
188
+ Ohne diese Zahl stand im Hinweis "minutenlang" — das unterstellt die
189
+ Tasmota-Vorgabe von 300 Sekunden. An Ricardos Leiste sind es zehn
190
+ (gemessen 16.09.2026: tele/STATE im Zehnsekundentakt). Wer eine Zahl
191
+ nennt, soll die richtige nennen. */
192
+ export function telePeriode(kanal) {
193
+ var id = hatPunkt(kanal, 'tele.STATE');
194
+ var j = id ? jsonVon(id) : null;
195
+ var n = j && Number(j.TelePeriod);
196
+ return (n && isFinite(n)) ? n : null;
197
+ }
198
+
152
199
  /* Meldet das Geraet Aenderungen sofort? Das haengt an SetOption59.
153
200
 
154
201
  Die Bitmaske aus StatusLOG zu entziffern waere moeglich, ist aber
@@ -229,16 +276,20 @@ export function mqttLage(kanal) {
229
276
  var g = mqttGeraet(kanal);
230
277
  if (!g) { return null; }
231
278
  var b = tasmotaBefehle(kanal);
232
- var fehlen = [], stumm = [], da = [];
279
+ var fehlen = [], stumm = [], da = [], unklar = [];
233
280
  if (b) {
234
281
  b.befehle.forEach(function (x) {
235
282
  var id = hatPunkt(kanal, 'cmnd.' + x.name);
236
283
  if (!id) { fehlen.push(x); return; }
237
284
  var m = mqttEinstellung(id);
238
- /* `publish === null` heisst unbekannt (Thema nur in `native`) —
239
- so ein Punkt kommt zu den vorhandenen, nicht zu den stummen.
240
- „Senden erlauben" haette an ihm ohnehin nichts zu schreiben. */
241
- if (m && m.publish === false) { stumm.push({ name: x.name, id: id }); } else { da.push(x.name); }
285
+ /* `publish === null` heisst unbekannt (Thema nur in `native`, ohne
286
+ Verknuepfung — ioBroker.mqtt und sonoff). Frueher kam so ein
287
+ Punkt zu den vorhandenen und stand gruen als „angelegt, sendet"
288
+ da; behauptet ist damit mehr, als die Werkbank weiss. Er steht
289
+ jetzt eigens (Ricardo, 20.09.2026). */
290
+ if (m && m.publish === false) { stumm.push({ name: x.name, id: id }); }
291
+ else if (m && m.publish === null) { unklar.push(x.name); }
292
+ else { da.push(x.name); }
242
293
  });
243
294
  }
244
295
  /* Auch cmnd-Punkte, die es schon gibt, ohne dass sie im Zustand
@@ -251,8 +302,9 @@ export function mqttLage(kanal) {
251
302
  if (da.indexOf(name) > -1) { return; }
252
303
  var m = mqttEinstellung(id);
253
304
  if (m && m.publish === false) { stumm.push({ name: name, id: id }); }
305
+ else if (m && m.publish === null && unklar.indexOf(name) === -1) { unklar.push(name); }
254
306
  });
255
- return { geraet: g, befehle: b, fehlen: fehlen, stumm: stumm, da: da,
307
+ return { geraet: g, befehle: b, fehlen: fehlen, stumm: stumm, da: da, unklar: unklar,
256
308
  sofort: sofortRueckmeldung(kanal) };
257
309
  }
258
310
 
@@ -281,7 +333,12 @@ export function mqttKarte(host, kanal) {
281
333
  ch.appendChild(th);
282
334
  if (l.fehlen.length) { ch.appendChild(el('span', 'chip warn', tr('mq.missing', l.fehlen.length))); }
283
335
  if (l.stumm.length) { ch.appendChild(el('span', 'chip bad', tr('mq.mute', l.stumm.length))); }
284
- if (l.sofort === false) { ch.appendChild(el('span', 'chip warn', tr('mq.instantOffShort'))); }
336
+ /* Nur warnen, wo es jemanden trifft: Ohne eine Zeile, die aus tele
337
+ liest und nach cmnd schreibt, ist SetOption59 folgenlos. */
338
+ var telZ = telegesteuert(S.entwurf);
339
+ if (l.sofort === false && telZ.length) {
340
+ ch.appendChild(el('span', 'chip warn', tr('mq.instantOffShort')));
341
+ }
285
342
  ch.insertBefore(klappZeichen(mqttAuf), ch.firstChild);
286
343
  ch.classList.add('klappbar');
287
344
  ch.addEventListener('click', function () { mqttAuf = !mqttAuf; zeichneErgebnis(); });
@@ -309,7 +366,11 @@ export function mqttKarte(host, kanal) {
309
366
  sah die Zahlen, aber nicht, welcher Befehl welcher Fall ist. */
310
367
  var zustand = {};
311
368
  l.fehlen.forEach(function (x) { zustand[x.name] = 'fehlt'; });
312
- l.stumm.forEach(function (x) { zustand[x.name] = 'stumm'; });
369
+ l.stumm.forEach(function (x) {
370
+ var m = mqttEinstellung(x.id);
371
+ zustand[x.name] = (m && m.abgeschaltet) ? 'aus' : 'stumm';
372
+ });
373
+ (l.unklar || []).forEach(function (n) { zustand[n] = 'unklar'; });
313
374
  l.da.forEach(function (n) { zustand[n] = 'da'; });
314
375
 
315
376
  var reihen = l.befehle.befehle.map(function (x) { return x.name; });
@@ -342,6 +403,8 @@ export function mqttKarte(host, kanal) {
342
403
  var c = el('span', 'mchip');
343
404
  if (z === 'fehlt') { c.appendChild(el('span', 'chip warn', tr('mq.stMissing'))); }
344
405
  else if (z === 'stumm') { c.appendChild(el('span', 'chip bad', tr('mq.stMute'))); }
406
+ else if (z === 'aus') { c.appendChild(el('span', 'chip bad', tr('mq.stOff'))); }
407
+ else if (z === 'unklar') { c.appendChild(el('span', 'chip mut', tr('mq.stUnknown'))); }
345
408
  else { c.appendChild(el('span', 'chip ok', tr('mq.stFine'))); }
346
409
  r.appendChild(c);
347
410
 
@@ -352,7 +415,7 @@ export function mqttKarte(host, kanal) {
352
415
  var bn = el('button', 'btn schmal', tr('mq.rowCreate'));
353
416
  bn.addEventListener('click', function () { mqttEinzeln(kanal, n, 'neu', bn); });
354
417
  h.appendChild(bn);
355
- } else if (z === 'stumm') {
418
+ } else if (z === 'stumm' || z === 'aus') {
356
419
  var bs2 = el('button', 'btn schmal', tr('mq.rowAllow'));
357
420
  bs2.addEventListener('click', function () { mqttEinzeln(kanal, n, 'frei', bs2); });
358
421
  h.appendChild(bs2);
@@ -379,57 +442,14 @@ export function mqttKarte(host, kanal) {
379
442
  }
380
443
  b.appendChild(leiste);
381
444
 
382
- /* Sofortige Rueckmeldung — Zustand und, wenn noetig, der Schalter. */
383
- var sk = el('div');
384
- sk.style.marginTop = '11px';
385
- sk.style.paddingTop = '10px';
386
- sk.style.borderTop = '1px solid var(--line)';
387
- var sz = el('div');
388
- sz.style.fontSize = '11.5px';
389
- sz.appendChild(el('b', null, tr('mq.instant') + ' '));
390
- if (l.sofort === true) {
391
- sz.appendChild(el('span', 'chip ok', tr('mq.instantOn')));
392
- } else if (l.sofort === false) {
393
- sz.appendChild(el('span', 'chip warn', tr('mq.instantOff')));
394
- } else {
395
- sz.appendChild(el('span', 'chip mut', tr('mq.instantUnknown')));
396
- }
397
- sk.appendChild(sz);
398
- if (so59Stumm[kanal] && l.sofort === null) {
399
- var st59 = el('div', 'hint');
400
- st59.style.marginTop = '4px';
401
- st59.style.color = 'var(--warn)';
402
- st59.textContent = tr('mq.askNoAnswer');
403
- sk.appendChild(st59);
404
- }
445
+ /* Sofortige Rueckmeldung — Zustand und, wenn noetig, der Schalter.
405
446
 
406
- var sl = el('div');
407
- sl.style.marginTop = '7px';
408
- sl.style.display = 'flex';
409
- sl.style.gap = '8px';
410
- sl.style.flexWrap = 'wrap';
411
- if (l.sofort !== true) {
412
- /* Beide gleich gross - schmal neben normal las sich wie zwei
413
- verschiedene Schriften (Ricardo, 25.08.2026). */
414
- var lauf = so59Laeuft[kanal];
415
- var bp = el('button', 'btn schmal' + (lauf === 'pruefen' ? ' laedt' : ''),
416
- lauf === 'pruefen' ? tr('mq.instantChecking') : tr('mq.instantCheck'));
417
- bp.addEventListener('click', function () { so59(kanal, bp, ''); });
418
- sl.appendChild(bp);
419
- var be = el('button', 'btn schmal' + (lauf === 'einschalten' ? ' laedt' : ''),
420
- lauf === 'einschalten' ? tr('mq.instantEnabling') : tr('mq.instantEnable'));
421
- be.addEventListener('click', function () { so59(kanal, be, '1'); });
422
- sl.appendChild(be);
423
- if (lauf) { bp.disabled = true; be.disabled = true; }
424
- }
425
- sk.appendChild(sl);
426
- var hinweis = l.sofort === true ? tr('mq.instantHintOn')
427
- : (l.sofort === false ? tr('mq.instantHintOff') : tr('mq.instantHintUnknown'));
428
- /* Woher der Befund stammt, gehoert dazu — sonst weiss niemand, ob er
429
- von eben ist oder von vorgestern. */
430
- var alter = (l.sofort === null) ? '' : so59Alter(kanal);
431
- sk.appendChild(el('div', 'hint', hinweis + (alter ? ' · ' + alter : '')));
432
- b.appendChild(sk);
447
+ Nur, wenn ueberhaupt eine Zeile am Telemetrietakt haengt. An einem
448
+ Geraet, dessen Aliase alle aus `stat/...` lesen, ist SetOption59
449
+ folgenlos, und ein Block darueber ist bloss Betrieb. */
450
+ /* Frueher stand hier ein vorzeitiges `return` — es haengte die Karte
451
+ an, bevor `b` in ihr steckte, und aufgeklappt blieb sie leer. */
452
+ if (telZ.length) { so59Kasten(b, kanal, l, telZ); }
433
453
 
434
454
  /* Nachfragen liefert dasselbe wie tele/STATE — gemessen, Feld fuer
435
455
  Feld. Der Knopf erscheint deshalb nur, wenn es etwas zu holen gibt:
@@ -474,6 +494,75 @@ export function mqttKarte(host, kanal) {
474
494
  host.appendChild(k);
475
495
  }
476
496
 
497
+ /* Der Block zur sofortigen Rueckmeldung (SetOption59) — nur gerufen,
498
+ wenn eine Zeile am Telemetrietakt haengt. */
499
+ function so59Kasten(b, kanal, l, telZ) {
500
+
501
+ var sk = el('div');
502
+ sk.style.marginTop = '11px';
503
+ sk.style.paddingTop = '10px';
504
+ sk.style.borderTop = '1px solid var(--line)';
505
+ var sz = el('div');
506
+ sz.style.fontSize = '11.5px';
507
+ sz.appendChild(el('b', null, tr('mq.instant') + ' '));
508
+ if (l.sofort === true) {
509
+ sz.appendChild(el('span', 'chip ok', tr('mq.instantOn')));
510
+ } else if (l.sofort === false) {
511
+ sz.appendChild(el('span', 'chip warn', tr('mq.instantOff')));
512
+ } else {
513
+ sz.appendChild(el('span', 'chip mut', tr('mq.instantUnknown')));
514
+ }
515
+ sk.appendChild(sz);
516
+ /* Auch wenn noch ein alter Wert dasteht: die Frage eben blieb
517
+ unbeantwortet, und das gehoert gesagt. */
518
+ if (so59Stumm[kanal]) {
519
+ var st59 = el('div', 'hint');
520
+ st59.style.marginTop = '4px';
521
+ st59.style.color = 'var(--warn)';
522
+ st59.textContent = tr('mq.askNoAnswer');
523
+ sk.appendChild(st59);
524
+ }
525
+
526
+ var sl = el('div');
527
+ sl.style.marginTop = '7px';
528
+ sl.style.display = 'flex';
529
+ sl.style.gap = '8px';
530
+ sl.style.flexWrap = 'wrap';
531
+ /* „Nachfragen" steht immer da. Ein „eingeschaltet" kann von
532
+ vorgestern stammen — ein liegengebliebenes `stat.RESULT` —, und
533
+ ohne den Knopf liess es sich nicht nachpruefen (H15).
534
+ „Einschalten" nur, wo es noch nicht an ist oder die Frage eben
535
+ unbeantwortet blieb. Beide gleich gross - schmal neben normal las
536
+ sich wie zwei verschiedene Schriften (Ricardo, 25.08.2026). */
537
+ var lauf = so59Laeuft[kanal];
538
+ var bp = el('button', 'btn schmal' + (lauf === 'pruefen' ? ' laedt' : ''),
539
+ lauf === 'pruefen' ? tr('mq.instantChecking') : tr('mq.instantCheck'));
540
+ bp.addEventListener('click', function () { so59(kanal, bp, ''); });
541
+ sl.appendChild(bp);
542
+ if (lauf) { bp.disabled = true; }
543
+ if (l.sofort !== true || so59Stumm[kanal]) {
544
+ var be = el('button', 'btn schmal' + (lauf === 'einschalten' ? ' laedt' : ''),
545
+ lauf === 'einschalten' ? tr('mq.instantEnabling') : tr('mq.instantEnable'));
546
+ be.addEventListener('click', function () { so59(kanal, be, '1'); });
547
+ sl.appendChild(be);
548
+ if (lauf) { be.disabled = true; }
549
+ }
550
+ sk.appendChild(sl);
551
+ /* Der Hinweis nennt die betroffenen Zeilen und den wirklichen Takt,
552
+ statt pauschal "minutenlang" zu behaupten. */
553
+ var telNamen = telZ.map(function (x) { return x.n; }).join(', ');
554
+ var takt = telePeriode(kanal);
555
+ var hinweis = l.sofort === true ? tr('mq.instantHintOn')
556
+ : (l.sofort === false
557
+ ? (takt ? tr('mq.instantHintOffTakt', telNamen, takt) : tr('mq.instantHintOff', telNamen))
558
+ : tr('mq.instantHintUnknown'));
559
+ /* Woher der Befund stammt, gehoert dazu — sonst weiss niemand, ob er
560
+ von eben ist oder von vorgestern. */
561
+ var alter = (l.sofort === null) ? '' : so59Alter(kanal);
562
+ sk.appendChild(el('div', 'hint', hinweis + (alter ? ' · ' + alter : '')));
563
+ b.appendChild(sk);
564
+ }
565
+
477
566
  var mqttStand = null;
478
567
  var mqttAuswahl = {};
479
568
  /* Bleibt ueber das Neuzeichnen hinweg stehen — sonst klappte die Karte
@@ -522,6 +611,13 @@ function so59(kanal, knopf, wert) {
522
611
  if (!g) { return; }
523
612
  so59Laeuft[kanal] = wert ? 'einschalten' : 'pruefen';
524
613
  delete so59Stumm[kanal];
614
+ /* Wie alt ist die letzte Antwort JETZT? Gewertet wird nur, was nach
615
+ dem Senden kam — wie beim Abfrageweg. Vorher reichte ein
616
+ `stat.RESULT` von vorgestern mit `SetOption59` darin fuer
617
+ „eingeschaltet · gerade gefragt", auch wenn das Geraet gar nicht
618
+ antwortete (H15, gemessen 17.09.2026). */
619
+ var rid = hatPunkt(kanal, 'stat.RESULT');
620
+ var vorTs = (rid && S.werte[rid]) ? (S.werte[rid].ts || 0) : 0;
525
621
  /* Sofort neu zeichnen: erst damit sperren sich BEIDE Knoepfe und die
526
622
  alte Antwortzeile verschwindet - vorher passierte das erst mit dem
527
623
  naechsten Nachzieher, Sekunden spaeter (Ricardo, 25.08.2026). */
@@ -544,7 +640,13 @@ function so59(kanal, knopf, wert) {
544
640
  setTimeout(function () {
545
641
  holeZweig(kanal + '.', function () {
546
642
  delete so59Laeuft[kanal];
547
- so59Stumm[kanal] = (sofortRueckmeldung(kanal, true) === null);
643
+ var rid2 = hatPunkt(kanal, 'stat.RESULT');
644
+ var jetzt = (rid2 && S.werte[rid2]) ? (S.werte[rid2].ts || 0) : 0;
645
+ /* Nichts Neues: nicht merken — sonst stuende der alte
646
+ Wert mit frischer Uhrzeit da. */
647
+ so59Stumm[kanal] = jetzt > vorTs
648
+ ? (sofortRueckmeldung(kanal, true) === null)
649
+ : true;
548
650
  zeichneErgebnis();
549
651
  });
550
652
  }, 4000);
@@ -706,7 +808,7 @@ function mqttPunktBauen(kanal, name, was) {
706
808
  if (!alt0) { return null; }
707
809
  obj = JSON.parse(JSON.stringify(alt0));
708
810
  var cu0 = obj.common.custom || {};
709
- Object.keys(cu0).forEach(function (i) { if (/^mqtt/.test(i) && cu0[i]) { cu0[i].publish = true; } });
811
+ Object.keys(cu0).forEach(function (i) { if (/^mqtt/.test(i) && cu0[i]) { cu0[i].publish = true; cu0[i].enabled = true; } });
710
812
  obj.common.custom = cu0;
711
813
  } else {
712
814
  var w0 = befehlsWissen(name) || {};
@@ -740,7 +842,7 @@ export function mqttEinzeln(kanal, name, was, knopf) {
740
842
  if (!alt) { return; }
741
843
  obj = JSON.parse(JSON.stringify(alt));
742
844
  var cu = obj.common.custom || {};
743
- Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i) && cu[i]) { cu[i].publish = true; } });
845
+ Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i) && cu[i]) { cu[i].publish = true; cu[i].enabled = true; } });
744
846
  obj.common.custom = cu;
745
847
  } else {
746
848
  var w = befehlsWissen(name) || {};
@@ -912,10 +1014,18 @@ function mqttZuSchreiben() {
912
1014
  if (!mqttAuswahl['neu:' + x.name]) { return; }
913
1015
  var w = befehlsWissen(x.name) || {};
914
1016
  var thema = mqttThema(l.geraet, 'cmnd', x.name);
1017
+ /* Derselbe Typ wie auf dem Einzelweg — `punktTyp`, nicht `w.typ`.
1018
+
1019
+ Die Regel „Schreibformel heisst mixed" stand seit dem 15.09.2026 in
1020
+ `punktTyp` und wurde in `mqttPunktBauen` benutzt; hier blieb die
1021
+ alte Fassung stehen. Wer den Sammeldialog nahm, bekam `POWER`,
1022
+ `Fade` und `LedTable` als `boolean` statt `mixed` — der
1023
+ js-controller rechnete den Formelwert auf den Zieltyp zurueck, und
1024
+ auf der Leitung stand `true` statt `ON` (H18, gemessen 20.09.2026). */
915
1025
  var common = {
916
1026
  name: x.name,
917
1027
  role: w.rolle || 'state',
918
- type: w.typ || 'mixed',
1028
+ type: punktTyp(w),
919
1029
  read: true, write: true,
920
1030
  desc: tr('mq.createdBy'),
921
1031
  custom: mqttCustom(l.geraet, thema)
@@ -930,7 +1040,7 @@ function mqttZuSchreiben() {
930
1040
  if (!alt) { return; }
931
1041
  var kopie = JSON.parse(JSON.stringify(alt));
932
1042
  var cu = kopie.common.custom || {};
933
- Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i) && cu[i]) { cu[i].publish = true; } });
1043
+ Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i) && cu[i]) { cu[i].publish = true; cu[i].enabled = true; } });
934
1044
  kopie.common.custom = cu;
935
1045
  raus.push({ id: x.id, obj: kopie, neu: false });
936
1046
  });
@@ -2,7 +2,7 @@
2
2
  Muster (passt / n von m belegt) und der Knopf, der freie Plaetze
3
3
  vorschlaegt. Gebaut wird beides hier, eingehaengt woanders: der Block
4
4
  je nach Ansicht im Vorlagenkasten oder in der Leiste ueber der Liste,
5
- der Knopf immer in der Leiste.
5
+ der Knopf mit ihm (seit 19.09.2026; vorher immer in der Leiste).
6
6
 
7
7
  Herausgeloest aus zeichneErgebnis - der Abschnitt hing an `e`, dem
8
8
  Erkennungsergebnis und sonst an nichts. */
@@ -10,11 +10,84 @@
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, typKonflikte } from './erkennung.js';
13
+ import { VORSCHAU, musterVon, erkenneEntwurf, platzFuerRolle, typenFuerRolle, typKonflikte, fehlendePflicht } from './erkennung.js';
14
14
  import { anMusterAnpassen } from './vorlagen.js';
15
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
+ import { nurWasInsMusterPasst } from './zustandsliste.js';
19
+ import { kindZustaende } from './werte.js';
20
+
21
+ /* Musterwechsel ohne Vorlage: die Haken folgen dem neuen Muster (C54).
22
+
23
+ Vorher lief die Vorbelegung nur einmal, beim Aufbau des Entwurfs; wer
24
+ danach das Muster wechselte, behielt die Haken des alten. Jetzt gilt
25
+ dieselbe Regel wie beim Aufbau — angehakt ist, was im gewaehlten
26
+ Muster einen Platz hat —, dazu jede Zeile, an der schon etwas von Hand
27
+ eingestellt wurde (Ricardo, 19.09.2026).
28
+
29
+ Nicht mit Vorlage: die bestimmt selbst, was an ist. Nicht an einem
30
+ Alias, den es schon gibt: dort ist jeder Haken eine getroffene
31
+ Entscheidung, abwaehlen hiesse loeschen — dieselben Ausnahmen wie bei
32
+ der Vorbelegung in `berechnePlaetze`. */
33
+ function hakenNachMuster(e) {
34
+ if (e.vorlage) { return; }
35
+ if (S.current && S.current.indexOf('alias.') === 0) { return; }
36
+ if (e.ziel && kindZustaende(e.ziel).length > 0) { return; }
37
+ /* Greift das neue Muster gar nicht — auch nicht mit allen Zeilen an —,
38
+ gibt es nichts, dem die Haken folgen koennten. Vorher fielen dann
39
+ alle bis auf die geaenderten: an der Klimaanlage verschwanden mit
40
+ dem Wechsel auf airCondition (MODE noch nicht da) ausgerechnet
41
+ Sollwert und Schalter, und nach dem Raten fehlte der Sollwert, den
42
+ das Muster verlangt (U13, 19.09.2026). Die Haken bleiben also, wie
43
+ sie sind; das Raten kann darauf aufbauen. */
44
+ var probe = JSON.parse(JSON.stringify({ kanal: e.kanal, states: e.states }));
45
+ probe.states.forEach(function (s) { s.on = true; });
46
+ if (!erkenneEntwurf(probe, e.want).length) { return; }
47
+ nurWasInsMusterPasst(e, true);
48
+ /* Wie beim Aufbau: der Schalter jedes weiteren Kanals kommt mit, damit
49
+ „als ein Geraet" keinen Ausgang heimlich verliert. */
50
+ (e.kanalGeraete || []).forEach(function (k) {
51
+ if (k.typ === 'info') { return; } /* Wartungskanal: kein Ausgang */
52
+ (k.pflicht || []).forEach(function (pid) {
53
+ var kurz = pid.slice(S.current.length + 1).replace(/\./g, '_');
54
+ e.states.forEach(function (st) { if (st.n === kurz) { st.on = true; } });
55
+ });
56
+ });
57
+ }
58
+
59
+ /* Die Meldung, wenn der Vorschlag nichts belegt hat - Begruendung dazu
60
+ in `baueMusterwahl`. Auf Modulebene, seit auch die Legende vorschlagen
61
+ kann (19.09.2026). */
62
+ function plaetzeText(frei) {
63
+ var kurz = frei.slice(0, 6).join(', ');
64
+ return frei.length > 6 ? tr('guess.slotsMore', kurz, frei.length - 6) : kurz;
65
+ }
66
+ function meldungZu(p) {
67
+ if (p.anzahl > 0) { return null; }
68
+ var satz = p.grund === 'voll' ? tr('guess.noneFull')
69
+ : p.grund === 'vergeben' ? tr('guess.noneUsed', plaetzeText(p.frei))
70
+ : p.grund === 'keinTreffer' ? tr('guess.noneFit', plaetzeText(p.frei))
71
+ : null;
72
+ /* Der Zusatz zur Handentscheidung (U15): Steht ein Platz nur deshalb
73
+ leer, weil die Zeile dazu von Hand entschieden ist, muss genau das
74
+ dastehen - sonst sieht es aus wie ein Fehler des Ratens. */
75
+ /* Mit Namen und in der richtigen Zahl — „1 Zeilen sind …" sagte weder
76
+ richtig Deutsch noch welche (Ricardo, 19.09.2026). */
77
+ if (satz && p.handWeg) {
78
+ var hn = (p.handNamen || []).slice(0, 4).join(', ') +
79
+ ((p.handNamen || []).length > 4 ? ' …' : '');
80
+ satz += ' ' + (p.handWeg === 1 ? tr('guess.noneHandOne', hn) : tr('guess.noneHand', p.handWeg, hn));
81
+ }
82
+ return satz;
83
+ }
84
+
85
+ /* Vorschlagen und die Meldung dazu in einem Schritt. Gebraucht vom
86
+ Knopf, vom Pfeil hinter „… wuerde evtl. besser passen" und von der
87
+ orangen Legende, wenn das Muster erst nach dem Vorschlag greift. */
88
+ export function rateUndMelde(e) {
89
+ e.rateMeldung = meldungZu(ratePlaetze(e));
90
+ }
18
91
 
19
92
  export function baueMusterwahl(e, haupt, pflichtFehlt) {
20
93
  var vw = el('div', 'verdictwrap' + (haupt && !pflichtFehlt.length ? '' : ' bad'));
@@ -49,9 +122,13 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
49
122
  als `thermostat` erkannt, man wechselt also gar nichts, und ohne
50
123
  Knopf bliebe es dabei.
51
124
 
52
- Er wird hier gebaut, weil er zum Muster gehoert - haengt aber unten
53
- in der Leiste ueber der Zustandsliste. Neben dem Musterfeld stand er
54
- am Rand; die Liste, die er veraendert, faengt darunter an. */
125
+ Er wird hier gebaut, weil er zum Muster gehoert. Seit 19.09.2026
126
+ haengt er im blauen Vorlagenkasten, gleich hinter dem Musterfeld -
127
+ dort, wo die Erkennung steht, zu der er gehoert. Vorher stand er in
128
+ der Leiste ueber der Liste, und den Knopf, der den ganzen Bestand
129
+ des Geraets laedt, fand man im Kasten; beide standen am jeweils
130
+ falschen Ort (Ricardo). Nur im Aliasmodus, wo es keinen Kasten gibt,
131
+ bleibt er in der Leiste. */
55
132
  /* Der Trockenlauf, der beides beantwortet: ob der Knopf etwas
56
133
  anzubieten haette, und - wenn nicht - warum nicht. Frueher lief er
57
134
  zweimal (einmal fuer `rateMoeglich`, einmal fuer den Klick); einmal
@@ -66,22 +143,6 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
66
143
  Gezeigt wird sie, solange keine Vermutung offen ist: dann fuehrt die
67
144
  blaue Leiste das Wort, und zwei Meldungen uebereinander widersprechen
68
145
  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
146
  /* Ungefragt gezeigt wird sie nur, wo sie etwas erklaert: dass kein
86
147
  Knopf dasteht, obwohl Plaetze frei sind. Der Fall „alle Plaetze
87
148
  besetzt" bleibt stumm - er sagt „alles gut, nichts zu tun", und das
@@ -131,7 +192,7 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
131
192
  tr('guess.button'));
132
193
  rateKnopf.title = beilaeufig ? tr('guess.tplHint') : tr('guess.hint');
133
194
  var rateLos = function () {
134
- e.rateMeldung = meldungZu(ratePlaetze(e));
195
+ rateUndMelde(e);
135
196
  entwurfAngefasst();
136
197
  S.openRow = null;
137
198
  zeichneErgebnis();
@@ -178,8 +239,15 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
178
239
  }
179
240
  var r = erkenneEntwurf(pruefE, m), mark;
180
241
  if (!r.length) {
181
- var fehlend = musterVon(m).states.filter(function (x) { return x.required; })
182
- .map(function (x) { return x.name; });
242
+ /* Genannt wird, was wirklich fehlt — nicht jeder Pflichtplatz. An
243
+ der Klimaanlage stand „✕ MODE fehlt" neben einem bestaetigten
244
+ MODE; gefehlt hat der Sollwert (U13, 19.09.2026). Findet die
245
+ Grobpruefung nichts, bleibt die alte Auskunft. */
246
+ var fehlend = fehlendePflicht(pruefE.states, m);
247
+ if (!fehlend.length) {
248
+ fehlend = musterVon(m).states.filter(function (x) { return x.required; })
249
+ .map(function (x) { return x.name; });
250
+ }
183
251
  /* Ein Pflichtplatz kann auch deshalb leer sein, weil die Zeile
184
252
  dafuer im falschen Datentyp steht - die Rolle stimmt, der Typ
185
253
  nicht, und der Punkt faellt aus dem Muster. „SET fehlt" ist dann
@@ -211,7 +279,14 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
211
279
  }
212
280
  } else {
213
281
  var g = r[0].states.filter(function (x) { return x.id; }).length;
214
- mark = tr('pattern.slotsTaken', g, r[0].states.length);
282
+ /* Ein Platz, der mehrfach vergeben wird (`multiple`, bei `info` das
283
+ ACTUAL), bekommt je Punkt einen Eintrag — die Gesamtzahl wuchs mit
284
+ jedem Haken, und „24/31“ klang, als fehlten sieben. Wo es keine
285
+ Hoechstzahl gibt, steht auch keine: nur die Zahl der Punkte
286
+ (Ricardo, 19.09.2026). Betrifft info, mediaPlayer, weatherForecast. */
287
+ var mehrfach = r[0].states.some(function (x) { return x.multiple; });
288
+ mark = mehrfach ? tr('pattern.pointsTaken', g)
289
+ : tr('pattern.slotsTaken', g, r[0].states.length);
215
290
  /* Hier stand frueher „(Rollen werden angepasst)" hinter der
216
291
  Belegung. Der Vermerk erschien an jeder Zeile ausser der gerade
217
292
  gewaehlten — an einem Fensterkontakt also an sieben von acht.
@@ -316,6 +391,7 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
316
391
  } else {
317
392
  e.want = neuTyp;
318
393
  }
394
+ hakenNachMuster(e);
319
395
  S.openRow = null;
320
396
  /* Kein Fokus zurueck ins Feld: der Fokus oeffnete die Liste sofort
321
397
  wieder, und nach der Wahl stand sie erneut offen — man musste
@@ -475,7 +551,8 @@ export function baueMusterwahl(e, haupt, pflichtFehlt) {
475
551
  /* Beim Vorschlag entscheidet nicht das Muster, sondern die
476
552
  Rollen, die das Auswaehlen setzt - genau wie in `mWaehlen`. */
477
553
  if (e.vorschlag && !e.roh) { anMusterAnpassen(e, bestes); } else { e.want = bestes; }
478
- e.rateMeldung = meldungZu(ratePlaetze(e));
554
+ hakenNachMuster(e);
555
+ rateUndMelde(e);
479
556
  entwurfAngefasst();
480
557
  S.openRow = null;
481
558
  zeichneErgebnis();
@@ -151,12 +151,26 @@ export function horcheAufObjekte() {
151
151
  return;
152
152
  }
153
153
 
154
+ /* Nur die vier Arten, aus denen der Baum besteht.
155
+
156
+ Beim Laden holt `ladeObjekte` gezielt `ARTEN`, beim Nachziehen
157
+ wurde dagegen alles aufgenommen: eine geaenderte Instanz, ein
158
+ Adapter-Update, `system.config` nach dem Umstellen der Sprache.
159
+ Der Zaehler in der Kopfzeile ging dadurch von 3145 auf 3146, und
160
+ mit jeder Stunde offenem Reiter wurden es mehr (A4, 17.09.2026).
161
+ Wechselt ein Objekt seine Art, faellt es hier ebenfalls heraus. */
162
+ if (obj && ARTEN.indexOf(obj.type) === -1) {
163
+ if (S.objects[id] === undefined) { return; }
164
+ obj = null;
165
+ }
166
+
154
167
  /* Alles Uebrige: nur was zaehlt, nicht jeder Schreibvorgang. */
155
168
  var vorher = kern(S.objects[id]);
156
169
  if (vorher === nachher) { return; }
157
170
 
158
171
  var gabEs = S.objects[id] !== undefined;
159
172
  if (obj) { S.objects[id] = obj; } else { delete S.objects[id]; }
173
+ S.objektStand++;
160
174
  if (gabEs !== (S.objects[id] !== undefined)) { nachziehBaum = true; }
161
175
 
162
176
  var betrifft = S.current && (id === S.current || id.indexOf(S.current + '.') === 0);
@@ -201,6 +215,7 @@ export function horcheAufObjekte() {
201
215
  export function uebernimmObjekt(id, o) {
202
216
  var gabEs = S.objects[id] !== undefined;
203
217
  if (o) { S.objects[id] = o; } else { delete S.objects[id]; }
218
+ S.objektStand++;
204
219
  return gabEs !== (S.objects[id] !== undefined);
205
220
  }
206
221
 
@@ -237,6 +252,7 @@ export function kaputteAliaseNeu() {
237
252
  }
238
253
 
239
254
  export function indexNeu() {
255
+ S.objektStand++;
240
256
  S.keysSorted = Object.keys(S.objects).sort();
241
257
  S.kleinIndex = {};
242
258
  kaputteAliaseNeu();
@@ -5,7 +5,7 @@ import { $, el } from './basis.js';
5
5
  import { tr } from './sprache.js';
6
6
  import { wertVon, zielKann } from './werte.js';
7
7
  import './erkennung.js';
8
- import { sofortRueckmeldung, mqttEinstellung } from './mqtt.js';
8
+ import { sofortRueckmeldung, mqttEinstellung, telegesteuert } from './mqtt.js';
9
9
 
10
10
 
11
11
  /* Kommt die Rueckmeldung sofort oder erst mit der naechsten Telemetrie?
@@ -21,11 +21,14 @@ import { sofortRueckmeldung, mqttEinstellung } from './mqtt.js';
21
21
  da, tele/STATE kam erst nach der naechsten Telemetrie — bei
22
22
  TelePeriod 300 also bis zu fuenf Minuten spaeter. */
23
23
  export function pruefungRueckmeldung(e) {
24
- var lahm = e.states.filter(function (x) {
25
- if (!x.on || !x.srcW || !x.srcR) { return false; }
26
- return /\.tele\./.test(x.srcR) && /\.cmnd\./.test(x.srcW);
27
- });
28
- if (!lahm.length) { return { s: 'ok', t: tr('check.feedback'), d: tr('check.feedbackFine') }; }
24
+ /* Dieselbe Frage wie die MQTT-Karte, damit beide nicht auseinander
25
+ laufen (Ricardo, 16.09.2026). */
26
+ var lahm = telegesteuert(e);
27
+ /* Kein Haken, sondern ein Punkt: Hier wurde nichts geprueft, weil es
28
+ nichts zu pruefen gab. „alle in Ordnung" las sich, als haette die
29
+ Werkbank etwas nachgesehen - an Ricardos Steckdosenleiste lesen alle
30
+ Zeilen aus stat/..., SetOption59 ist dort folgenlos. */
31
+ if (!lahm.length) { return { s: 'mut', t: tr('check.feedback'), d: tr('check.feedbackNone') }; }
29
32
  /* Nur warnen, wenn es wirklich langsam ist. Mit SetOption59 meldet
30
33
  das Geraet jede Aenderung sofort — dann ist die Bauform zwar
31
34
  dieselbe, aber kein Problem. Gemessen: drei Sekunden. */
@@ -113,8 +116,7 @@ export function pruefungZielKann(e) {
113
116
  if (!treffer.length) { return { s: 'ok', t: tr('check.targetCan'), d: tr('check.targetCanFine') }; }
114
117
  var text = treffer.map(function (x) {
115
118
  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);
119
+ return tr('check.targetNoWrite', x.n);
118
120
  }).join(', ');
119
121
  return { s: 'warn', t: tr('check.targetCan'), d: text };
120
122
  }