iobroker.alias-workbench 0.9.10 → 0.9.12

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/enums.js CHANGED
@@ -40,8 +40,8 @@ export function ladeEnums(danach) {
40
40
  /* Den alten Stand behalten. Er ist vielleicht veraltet, aber
41
41
  veraltet ist naeher an der Wahrheit als leer. */
42
42
  unbekannt = true;
43
- console.warn('[alias-workbench] Aufzaehlungen nicht geladen: ' +
44
- (err || 'keine Antwort'));
43
+ console.warn('[alias-workbench] Enumerations not loaded: ' +
44
+ (err || 'no answer'));
45
45
  if (versuch < 3) {
46
46
  versuch++;
47
47
  wiederTimer = setTimeout(function () {
@@ -1,7 +1,7 @@
1
1
  import { S } from './zustand.js';
2
2
  import './start.js';
3
3
  import { socket } from './verbindung.js';
4
- import { D, $, el, klappZeichen } from './basis.js';
4
+ import { D, $, el } from './basis.js';
5
5
  import { tr, sprachtext } from './sprache.js';
6
6
  import './einstellungen.js';
7
7
  import './enums.js';
@@ -27,17 +27,22 @@ import {
27
27
  mitNachfrage,
28
28
  geraeteDarunter,
29
29
  aliasFuer,
30
+ ausgangsQuellen,
30
31
  quellenVerteilung,
31
32
  quelleHier,
32
33
  tipptGerade,
33
34
  opt,
34
35
  vorlagenAbweichung,
35
36
  vorlageAnwenden,
36
- bestandVorrang
37
+ bestandVorrang,
38
+ kanalGruppen,
39
+ aufteilBehaelter,
40
+ rohEntwurf
37
41
  } from './entwurf.js';
38
42
  import { setzeEnums, setzeZiel, unterschiede } from './zuordnung.js';
39
43
  import './detail.js';
40
44
  import { zeigeTausch, tauscheAus } from './quellentausch.js';
45
+ import { baueVerwendungKarte } from './verwendung.js';
41
46
 
42
47
  import { baueChecks } from './pruefungen.js';
43
48
  import {
@@ -141,7 +146,15 @@ var warumAuf = false;
141
146
  }
142
147
 
143
148
  export function entwurfAngefasst() {
144
- if (S.entwurf) { S.entwurf.angefasst = true; }
149
+ if (S.entwurf) {
150
+ S.entwurf.angefasst = true;
151
+ /* Die Aussicht „was passt nach dem Raten" haengt allein an den
152
+ Zeilen. Sie wird einmal gerechnet und gemerkt; eine Zeilen-
153
+ aenderung ist der einzige Anlass, sie fallen zu lassen. Beim
154
+ blossen Neuzeichnen bleibt sie stehen - das Raten liest keine
155
+ Messwerte, es kaeme dasselbe heraus. */
156
+ S.entwurf.rateAussicht = null;
157
+ }
145
158
  }
146
159
 
147
160
  export function neuZeichnenOderAufbauen(danach) {
@@ -323,6 +336,21 @@ var warumAuf = false;
323
336
  will, musste ihn bisher im Quellenbaum von Hand wiederfinden.
324
337
  Umgekehrt gilt dasselbe: steht man am Geraet und will nachsehen,
325
338
  was daraus geworden ist, fuehrt der zweite Knopf hin. */
339
+ /* Zeigt ein Punkt auf eine Quelle, die es nicht gibt, gehoert das
340
+ nach oben - nicht nur in die Zeile und in den Pruefungen-Kasten.
341
+ Der js-controller repariert diesen Zustand nie von allein. */
342
+ var insLeere = (S.entwurf && S.entwurf.states || []).filter(function (s) {
343
+ return s.on && s.srcR && !S.objects[s.srcR];
344
+ });
345
+ if (insLeere.length) {
346
+ sub.appendChild(document.createTextNode(' '));
347
+ var ml = el('span', 'chip bad', insLeere.length === 1
348
+ ? tr('result.sourceGone1')
349
+ : tr('result.sourceGoneN', insLeere.length));
350
+ ml.title = insLeere.map(function (s) { return s.n + ' → ' + s.srcR; }).join('\n');
351
+ sub.appendChild(ml);
352
+ }
353
+
326
354
  var sprung = null, sprungZiel = null, sprungModus = null;
327
355
  if (S.current.indexOf('alias.') === 0) {
328
356
  sprungZiel = quelleVon(S.current);
@@ -488,15 +516,21 @@ var warumAuf = false;
488
516
  sg.style.borderColor = 'var(--line-strong)';
489
517
  sg.style.background = 'var(--surface-2)';
490
518
  }
491
- var hd = el('div', 'hd', e.vorschlag
519
+ var hd = el('div', 'hd');
520
+ /* Der Name steht in einer eigenen Spanne, damit er sich kuerzen
521
+ laesst. Die Kopfzeile bricht nicht mehr um: vorher nahm sich der
522
+ Hinweis rechts eine eigene Zeile, sobald Marken dazukamen und der
523
+ Name lang wurde („Homematic Rollladenaktor" + von Hand gewaehlt +
524
+ erzwungen). Jetzt weicht stattdessen der Name zurueck — vollstaendig
525
+ steht er im Tooltip und im Auswahlfeld darunter (Ricardo, 17.09.2026). */
526
+ var hdText = e.vorschlag
492
527
  ? tr('tpl.identifiedAs', e.tplName)
493
- : tr('tpl.noneDetected'));
528
+ : tr('tpl.noneDetected');
529
+ var hdName = el('span', 'tname', hdText);
530
+ hdName.title = hdText;
531
+ hd.appendChild(hdName);
494
532
  if (e.vorlageVersion) {
495
- var vv2 = el('span', null, ' · ' + e.vorlage + ' v' + e.vorlageVersion);
496
- vv2.style.fontFamily = 'var(--mono)';
497
- vv2.style.fontSize = '10.5px';
498
- vv2.style.fontWeight = '400';
499
- vv2.style.color = 'var(--ink-3)';
533
+ var vv2 = el('span', 'tid', ' · ' + e.vorlage + ' v' + e.vorlageVersion);
500
534
  hd.appendChild(vv2);
501
535
  }
502
536
  if (e.vonHandGewaehlt) {
@@ -547,13 +581,22 @@ var warumAuf = false;
547
581
  Breite, die eine Liste braucht. */
548
582
  var warumKopf = null, warumLeib = null;
549
583
  if (gruende.length || weggelassen.length) {
550
- warumKopf = el('div', 'wkopf');
551
584
  /* Ohne Vorlage waere „Warum diese Vorlage" die falsche Frage —
552
585
  dann steht hier, warum keine greift. */
553
- warumKopf.appendChild(el('span', null, weggelassen.length
554
- ? tr('tpl.skippedHead', weggelassen.length)
555
- : tr(e.vorschlag ? 'tpl.whyThis' : 'tpl.whyNone')));
556
- warumKopf.appendChild(klappZeichen(warumAuf));
586
+ var warumText = tr(e.vorschlag ? 'tpl.whyThis' : 'tpl.whyNone');
587
+ warumKopf = el('div', 'wkopf' + (warumAuf ? ' auf' : ''));
588
+ /* Statt „Warum diese Vorlage +" nur ein Fragezeichen: der Satz
589
+ steht im Tooltip und spart die halbe Kopfzeile. Was weggelassen
590
+ wurde, bleibt als kurze Marke stehen — dass Punkte fehlen, soll
591
+ niemand erst suchen muessen (Ricardo, 17.09.2026). */
592
+ if (weggelassen.length) {
593
+ warumKopf.appendChild(el('span', 'chip mut',
594
+ tr('tpl.skippedChip', weggelassen.length)));
595
+ warumText = tr('tpl.skippedHead', weggelassen.length) + ' — ' + warumText;
596
+ }
597
+ warumKopf.appendChild(el('span', 'fragez', '?'));
598
+ warumKopf.title = warumText;
599
+ warumKopf.setAttribute('aria-label', warumText);
557
600
  warumKopf.tabIndex = 0;
558
601
  var um2 = function () { warumAuf = !warumAuf; zeichneErgebnis(); };
559
602
  warumKopf.addEventListener('click', um2);
@@ -610,6 +653,25 @@ var warumAuf = false;
610
653
  v2.zielOrdner = e.zielOrdner;
611
654
  v2.zielName = ausgangName(v2, selA.value);
612
655
  v2.ziel = v2.zielOrdner + '.' + v2.zielName;
656
+ /* Dieser Weg fragte bisher gar nicht nach dem Bestand -
657
+ deshalb sah derselbe Ausgang je nach Weg anders aus:
658
+ ueber den Baum kamen die Haken eines fremden Alias, hier
659
+ die Vorgaben der Vorlage. Jetzt stellen alle drei Wege
660
+ dieselbe Frage (C13, 16.09.2026).
661
+
662
+ Und die Zielzeile folgt derselben Antwort. Sie wird hier von
663
+ Hand zusammengesetzt und laeuft nicht durch `setzeZiel`;
664
+ ohne diese Zeilen zeigte sie beim Umschalten weiter auf
665
+ `…POWER2` und meldete „wird neu angelegt", waehrend
666
+ `SUNLU` daneben stand. */
667
+ var vorhA2 = aliasFuer(S.current, ausgangsQuellen(v2));
668
+ if (vorhA2) {
669
+ var t2 = vorhA2.split('.');
670
+ v2.zielName = t2.pop();
671
+ v2.zielOrdner = t2.join('.');
672
+ v2.ziel = vorhA2;
673
+ }
674
+ if (vorhA2 && S.objects[vorhA2]) { bestandVorrang(v2, vorhA2); }
613
675
  S.entwurf = v2; S.openRow = null; zeichneErgebnis();
614
676
  }
615
677
  });
@@ -626,7 +688,10 @@ var warumAuf = false;
626
688
  das Ausgangsfeld daneben blieb ungestylt in der Browserschrift. */
627
689
  var selV = el('select', 'tx');
628
690
 
629
- selV.appendChild(opt('', e.vorschlag ? tr('tpl.switch') : tr('tpl.choose')));
691
+ /* Greift eine Vorlage, ist der erste Eintrag kein Titel, sondern
692
+ eine Wahl: sie wieder loswerden. „— Vorlage wechseln —" stand
693
+ wie eine Handlung da, tat aber nichts (Ricardo, 17.09.2026). */
694
+ selV.appendChild(opt('', e.vorschlag ? tr('tpl.none') : tr('tpl.choose')));
630
695
  S.VORLAGEN.forEach(function (v) {
631
696
  var t = pruefeVorlage(v, S.current);
632
697
  /* Auch nicht passende bleiben waehlbar — mit Grund daneben. Sonst
@@ -639,7 +704,35 @@ var warumAuf = false;
639
704
  selV.appendChild(o);
640
705
  });
641
706
  selV.addEventListener('change', function () {
642
- if (!selV.value) { return; }
707
+ if (!selV.value) {
708
+ /* „keine Vorlage": den Rohentwurf nehmen. Ziel, Raum und
709
+ Funktion bleiben — die hat der Mensch eingestellt, nicht die
710
+ Vorlage. `angefasst` verhindert, dass der naechste
711
+ eintreffende Messwert die Vorlage wieder heranzieht. */
712
+ if (!e.vorschlag) { return; }
713
+ var r = rohEntwurf(S.current);
714
+ if (!r) { selV.value = e.vorlage || ''; return; }
715
+ r.ziel = e.ziel; r.zielOrdner = e.zielOrdner; r.zielName = e.zielName;
716
+ r.raum = e.raum; r.funktion = e.funktion;
717
+ r.raumHer = e.raumHer; r.funktionHer = e.funktionHer;
718
+ /* Der Ausgang wird NICHT uebernommen. Er ist keine Eigenschaft
719
+ des Geraets, sondern des `mehrfach`-Blocks der Vorlage:
720
+ `instanzenVon` gibt ohne ihn `[null]` zurueck. Wurde die
721
+ Vorlage abgewaehlt, gibt es keinen Platzhalter mehr und damit
722
+ nichts zu waehlen. Bis 17.09.2026 erbte der Rohentwurf die
723
+ Ausgaenge der alten Vorlage: der Kasten blieb stehen, und wer
724
+ ihn benutzte, holte die abgewaehlte Vorlage kommentarlos
725
+ zurueck — der Ausgang-Zweig ruft `vorschlag` mit leerem
726
+ `e.vorlage` und laesst neu erkennen (gemessen an
727
+ `hm.ZWEIKANAL`: nach dem Umschalten auf `Licht_Esstisch`
728
+ stand `hm-schaltaktor-mehrfach` wieder im Feld). Die beiden
729
+ Kanaele laufen ohne Vorlage ueber „Unterordner je Kanal". */
730
+ S.entwurf = r;
731
+ S.openRow = null;
732
+ entwurfAngefasst();
733
+ zeichneErgebnis();
734
+ return;
735
+ }
643
736
  var v = vorschlag(S.current, selV.value);
644
737
  if (v) {
645
738
  v.ziel = e.ziel;
@@ -727,7 +820,11 @@ var warumAuf = false;
727
820
  neuE.zielName = e.zielName;
728
821
  neuE.ordnerVonHand = e.ordnerVonHand;
729
822
  if (fest) { neuE.vonHandGewaehlt = false; neuE.ausBestand = true; }
730
- if (vorh && S.objects[vorh]) { bestandVorrang(neuE, vorh); }
823
+ /* Wie beim ersten Aufbau: am Mehrfachgeraet zaehlt der Alias
824
+ DIESES Ausgangs (C13). */
825
+ var vorhW = aliasFuer(S.current, ausgangsQuellen(neuE)) ||
826
+ ((neuE.instanz === null || neuE.instanz === undefined) ? vorh : null);
827
+ if (vorhW && S.objects[vorhW]) { bestandVorrang(neuE, vorhW); }
731
828
  S.entwurf = neuE; S.openRow = null; zeichneErgebnis();
732
829
  });
733
830
  /* An der Quelle bleibt die Musterwahl im Vorlagenkasten, wo sie
@@ -820,7 +917,22 @@ var warumAuf = false;
820
917
  var bd = $('#btn-dry');
821
918
  var ba = $('#btn-dry-alle');
822
919
  var mehr = e.instanzen && e.instanzen.length > 1;
823
- var mehrK = !mehr && (e.kanalGeraete || []).length > 1;
920
+ /* Welche Unterordner entstuenden — nach den Haken, dieselbe Rechnung
921
+ wie der Schreibweg (C51). */
922
+ var grp = mehr ? [] : kanalGruppen(e).filter(function (g) { return !g.eigen; });
923
+ /* Zwei Kanaele mit Haken genuegen — auch wenn einer davon der
924
+ Wartungskanal ist.
925
+
926
+ Zwischenzeitlich zaehlte `.0` hier nicht mit, damit der Knopf nicht
927
+ an jedem Fuehler steht. Das ergab aber eine Unstimmigkeit, die beim
928
+ Benutzen auffaellt: fuer den INHALT zaehlte der Kanal (er bekommt
929
+ seinen Ordner), fuer den ANLASS nicht — man hakt etwas an und
930
+ nichts passiert. Ricardo am 17.09.2026: „zählt ist ja eher ne
931
+ Expertenfunktion, da weiss man schon was man macht." Also eine
932
+ Regel statt zweier. */
933
+ var typVonK = {};
934
+ (e.kanalGeraete || []).forEach(function (k) { typVonK[k.id] = k.typ; });
935
+ var mehrK = !mehr && grp.length > 1;
824
936
  /* Die Knoepfe gehoeren zum Modus, nicht nur zum Objekt: Im Modus
825
937
  „Alias anlegen" wird angelegt, nicht verwaltet — Verlegen, Quelle
826
938
  tauschen und Entfernen haben dort nichts zu suchen. Im Modus
@@ -867,13 +979,25 @@ var warumAuf = false;
867
979
  return (e.zielOrdner || '') + '.' + nm;
868
980
  }).join(String.fromCharCode(10));
869
981
  } else if (mehrK) {
870
- ba.textContent = tr('write.createPerChannel', e.kanalGeraete.length);
982
+ ba.textContent = tr('write.createPerChannel', grp.length);
871
983
  /* Im Hinweis stehen die Namen, die entstehen wuerden — sonst
872
984
  klickt man auf eine Zahl und weiss nicht, was danach dasteht. */
873
- ba.title = e.kanalGeraete.map(function (k) {
985
+ /* Dieselbe Rechnung wie der Schreibweg (schreiben.js, `ordner`):
986
+ ein Behaelter fuer das Geraet, darunter je Kanal einer. Hier
987
+ stand nur `zielOrdner` — also eine Ebene zu wenig. Am
988
+ Geschirrspueler versprach der Hinweis `alias.0.Befehle`,
989
+ entstanden waere `alias.0.Geschirrspueler.Befehle`; am
990
+ Wohnzimmer_Dimmer las man `alias.0.1`, `alias.0.2`, `alias.0.3`
991
+ — drei Aliase, nach blanken Kanalnummern benannt, direkt unter
992
+ alias.0. Gefunden im Lauf vom 16.09.2026 (C15). */
993
+ var ordnerK = aufteilBehaelter(e);
994
+ ba.title = grp.map(function (g) {
874
995
  /* Die Kennung, nicht den Anzeigenamen - so heisst das Objekt
875
- hinterher wirklich (G31). */
876
- return (e.zielOrdner || '') + '.' + (k.kennung || k.name) + ' (' + k.typ + ')';
996
+ hinterher wirklich (G31). Dahinter, was hineinkommt: die
997
+ Zahl der angehakten Zeilen — daran sieht man vor dem Klick,
998
+ dass nichts Erfundenes dabei ist. */
999
+ return ordnerK + '.' + (g.kennung || g.name)
1000
+ + ' (' + g.zeilen.length + (typVonK[g.id] ? ', ' + typVonK[g.id] : '') + ')';
877
1001
  }).join(String.fromCharCode(10));
878
1002
  }
879
1003
  ba.disabled = false;
@@ -971,6 +1095,15 @@ var warumAuf = false;
971
1095
  /* --- Änderungen gegenüber dem, was gespeichert ist --- */
972
1096
  baueDiffKarte(host, e);
973
1097
 
1098
+ /* --- Wo wird das benutzt? ---
1099
+
1100
+ Nur an einem Alias, den es schon gibt: an einem Entwurf, der noch
1101
+ nicht geschrieben ist, kann ihn niemand nennen. */
1102
+ var vAlias = e.ziel || e.kanal;
1103
+ if (vAlias && String(vAlias).indexOf('alias.') === 0 && S.objects[vAlias]) {
1104
+ baueVerwendungKarte(host, vAlias, quelleVon(vAlias));
1105
+ }
1106
+
974
1107
  /* --- Prüfungen, Fusszeile, Knopfleiste --- */
975
1108
  setzeKnoepfe(e, haupt, pflichtFehlt);
976
1109
  }
@@ -226,6 +226,13 @@ export function musterVon(typ) {
226
226
  statt als `electricity` nur noch als `info` erkannt. Am Bildschirm
227
227
  stand dazu nichts weiter als „passt nicht".
228
228
 
229
+ Zur Rolle selbst: `value.power.consumption` ist in der Rollenliste von
230
+ ioBroker durchgestrichen, der type-detector 6.0.x erkennt CONSUMPTION
231
+ aber NUR mit genau dieser Rolle (typePatterns.js, Muster `consumption`).
232
+ Die mitgelieferten Vorlagen behalten sie deshalb bewusst; jede traegt
233
+ den Grund im Feld `_rolle`. Role note for reviewers: the deprecated role
234
+ is kept on purpose because the type-detector requires it.
235
+
229
236
  Dieselbe Suche stand vorher zweimal im Code (detail.js und
230
237
  vorlagenblatt.js), beide Male ohne den Typ anzusehen. */
231
238
  export function plaetzeFuerRolle(musterName, rolle) {
package/admin/js/mqtt.js CHANGED
@@ -149,6 +149,38 @@ export function befehlsWissen(name) {
149
149
  return null;
150
150
  }
151
151
 
152
+ /* Welche Zeilen haengen ueberhaupt am Telemetrietakt?
153
+
154
+ Betroffen ist nur die eine Bauform: aus `tele/...` lesen und nach
155
+ `cmnd/...` schreiben. Wer aus `stat/POWER1` liest, bekommt die Antwort
156
+ bei jedem Befehl sofort — SetOption59 aendert daran nichts.
157
+
158
+ Ricardos Steckdosenleiste hat keine einzige solche Zeile: seine Aliase
159
+ lesen alle aus `stat.POWERn`. Trotzdem stand der SetOption59-Block an
160
+ der Karte und die Pruefung meldete "alle in Ordnung", als waere etwas
161
+ geprueft worden. Beides ist jetzt an diese Frage gebunden
162
+ (Ricardo, 16.09.2026). */
163
+ export function telegesteuert(e) {
164
+ if (!e || !e.states) { return []; }
165
+ return e.states.filter(function (x) {
166
+ if (!x.on || !x.srcW || !x.srcR) { return false; }
167
+ return /\.tele\./.test(x.srcR) && /\.cmnd\./.test(x.srcW);
168
+ });
169
+ }
170
+
171
+ /* Wie oft meldet das Geraet von sich aus? Steht in tele/STATE.
172
+
173
+ Ohne diese Zahl stand im Hinweis "minutenlang" — das unterstellt die
174
+ Tasmota-Vorgabe von 300 Sekunden. An Ricardos Leiste sind es zehn
175
+ (gemessen 16.09.2026: tele/STATE im Zehnsekundentakt). Wer eine Zahl
176
+ nennt, soll die richtige nennen. */
177
+ export function telePeriode(kanal) {
178
+ var id = hatPunkt(kanal, 'tele.STATE');
179
+ var j = id ? jsonVon(id) : null;
180
+ var n = j && Number(j.TelePeriod);
181
+ return (n && isFinite(n)) ? n : null;
182
+ }
183
+
152
184
  /* Meldet das Geraet Aenderungen sofort? Das haengt an SetOption59.
153
185
 
154
186
  Die Bitmaske aus StatusLOG zu entziffern waere moeglich, ist aber
@@ -281,7 +313,12 @@ export function mqttKarte(host, kanal) {
281
313
  ch.appendChild(th);
282
314
  if (l.fehlen.length) { ch.appendChild(el('span', 'chip warn', tr('mq.missing', l.fehlen.length))); }
283
315
  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'))); }
316
+ /* Nur warnen, wo es jemanden trifft: Ohne eine Zeile, die aus tele
317
+ liest und nach cmnd schreibt, ist SetOption59 folgenlos. */
318
+ var telZ = telegesteuert(S.entwurf);
319
+ if (l.sofort === false && telZ.length) {
320
+ ch.appendChild(el('span', 'chip warn', tr('mq.instantOffShort')));
321
+ }
285
322
  ch.insertBefore(klappZeichen(mqttAuf), ch.firstChild);
286
323
  ch.classList.add('klappbar');
287
324
  ch.addEventListener('click', function () { mqttAuf = !mqttAuf; zeichneErgebnis(); });
@@ -379,7 +416,13 @@ export function mqttKarte(host, kanal) {
379
416
  }
380
417
  b.appendChild(leiste);
381
418
 
382
- /* Sofortige Rueckmeldung — Zustand und, wenn noetig, der Schalter. */
419
+ /* Sofortige Rueckmeldung — Zustand und, wenn noetig, der Schalter.
420
+
421
+ Nur, wenn ueberhaupt eine Zeile am Telemetrietakt haengt. An einem
422
+ Geraet, dessen Aliase alle aus `stat/...` lesen, ist SetOption59
423
+ folgenlos, und ein Block darueber ist bloss Betrieb. */
424
+ if (!telZ.length) { host.appendChild(k); return; }
425
+
383
426
  var sk = el('div');
384
427
  sk.style.marginTop = '11px';
385
428
  sk.style.paddingTop = '10px';
@@ -423,8 +466,14 @@ export function mqttKarte(host, kanal) {
423
466
  if (lauf) { bp.disabled = true; be.disabled = true; }
424
467
  }
425
468
  sk.appendChild(sl);
469
+ /* Der Hinweis nennt die betroffenen Zeilen und den wirklichen Takt,
470
+ statt pauschal "minutenlang" zu behaupten. */
471
+ var telNamen = telZ.map(function (x) { return x.n; }).join(', ');
472
+ var takt = telePeriode(kanal);
426
473
  var hinweis = l.sofort === true ? tr('mq.instantHintOn')
427
- : (l.sofort === false ? tr('mq.instantHintOff') : tr('mq.instantHintUnknown'));
474
+ : (l.sofort === false
475
+ ? (takt ? tr('mq.instantHintOffTakt', telNamen, takt) : tr('mq.instantHintOff', telNamen))
476
+ : tr('mq.instantHintUnknown'));
428
477
  /* Woher der Befund stammt, gehoert dazu — sonst weiss niemand, ob er
429
478
  von eben ist oder von vorgestern. */
430
479
  var alter = (l.sofort === null) ? '' : so59Alter(kanal);
@@ -655,6 +704,27 @@ function mqttCustom(g, thema) {
655
704
  zurueckzulesen ist ein Wettlauf: die Sicht hinkt dem frisch
656
705
  geschriebenen Objekt einen Wimpernschlag hinterher, und der
657
706
  Trockenlauf sperrte weiter wegen eines Punktes, den es laengst gab. */
707
+ /* Der Typ eines Sendepunktes: `mixed`, sobald eine Schreibformel im
708
+ Spiel ist.
709
+
710
+ Sonst rechnet der js-controller den Formelwert beim Schreiben auf den
711
+ Typ des Ziels zurueck, und die Formel war umsonst: `POWER` traegt im
712
+ Befehlswissen `typ: boolean` UND `schreibformel: val ? "ON" : "OFF"`.
713
+ Aus dem "ON" wurde am boolean-Ziel wieder true, auf der Leitung stand
714
+ true statt ON. Geschaltet hat die Tasmota trotzdem — sie nimmt beides —,
715
+ aber die Formel stand nutzlos im Alias, und der von der Werkbank
716
+ angelegte Punkt sah anders aus als jeder von mqtt-client angelegte
717
+ (die sind alle `mixed`). Am Produktivsystem war es genau EINER von 196
718
+ (Ricardo, 15.09.2026, an alias.0.Bastelzimmer.Deckenlicht).
719
+
720
+ Keine Ausnahme fuer POWER, sondern eine Regel, die sich selbst
721
+ durchsetzt — sie gilt ebenso fuer `Fade` und `LedTable`. Wo keine
722
+ Formel steht, bleibt der genaue Typ: aus `number` zieht der Admin
723
+ Min, Max und Einheit. */
724
+ function punktTyp(w) {
725
+ return w.schreibformel ? 'mixed' : (w.typ || 'mixed');
726
+ }
727
+
658
728
  export function mqttEinzelnStill(kanal, name, fertig) {
659
729
  var bau = mqttPunktBauen(kanal, name, 'neu');
660
730
  if (!bau) { return fertig && fertig(); }
@@ -694,7 +764,7 @@ function mqttPunktBauen(kanal, name, was) {
694
764
  var common0 = {
695
765
  name: name,
696
766
  role: w0.rolle || 'state',
697
- type: w0.typ || 'mixed',
767
+ type: punktTyp(w0),
698
768
  read: true, write: true,
699
769
  desc: tr('mq.createdBy'),
700
770
  custom: mqttCustom(g, thema0)
@@ -728,7 +798,7 @@ export function mqttEinzeln(kanal, name, was, knopf) {
728
798
  var common = {
729
799
  name: name,
730
800
  role: w.rolle || 'state',
731
- type: w.typ || 'mixed',
801
+ type: punktTyp(w),
732
802
  read: true, write: true,
733
803
  desc: tr('mq.createdBy'),
734
804
  custom: mqttCustom(g, thema)