iobroker.alias-workbench 0.9.12 → 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/README.md +54 -0
- package/admin/css/werkbank.css +8 -0
- package/admin/js/aufzaehlungen.js +24 -8
- package/admin/js/baum.js +13 -2
- package/admin/js/detail.js +302 -87
- package/admin/js/entwurf.js +44 -14
- package/admin/js/enums.js +7 -0
- package/admin/js/ergebnis.js +26 -6
- package/admin/js/erkennung.js +45 -0
- package/admin/js/mqtt.js +128 -67
- package/admin/js/musterwahl.js +103 -26
- package/admin/js/objekte.js +3 -0
- package/admin/js/pruefungen.js +1 -2
- package/admin/js/quellentausch.js +59 -12
- package/admin/js/schreiben.js +22 -18
- package/admin/js/vorlagen.js +28 -5
- package/admin/js/vorlagenblatt.js +49 -7
- package/admin/js/vorschlagen.js +60 -7
- package/admin/js/werte.js +98 -20
- package/admin/js/zuordnung.js +39 -16
- package/admin/js/zustand.js +5 -1
- package/admin/js/zustandsliste.js +185 -25
- package/admin/sprachen/de.json +35 -11
- package/admin/sprachen/en.json +35 -11
- package/admin/vorlagen/hm-rollladen.json +3 -2
- package/admin/vorlagen/messpunkt.json +6 -2
- package/admin/vorlagen/tasmota-lampe.json +6 -2
- package/admin/vorlagen/tasmota-mehrfach.json +6 -2
- package/admin/vorlagen/tasmota-rgb.json +6 -2
- package/admin/vorlagen/tasmota-steckdose.json +6 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/admin/js/entwurf.js
CHANGED
|
@@ -9,7 +9,7 @@ import { txt, tr } from './sprache.js';
|
|
|
9
9
|
import './enums.js';
|
|
10
10
|
import { zusatzName, setzeModus, zeichneBaum, merkeKlappstand } from './baum.js';
|
|
11
11
|
import { kindZustaende, aliasQuellen, holeWerte,
|
|
12
|
-
mitVorspann, schreibQuelle, zeileAusAlias
|
|
12
|
+
mitVorspann, schreibQuelle, zeileAusAlias, istTaster as istTasterObjekt
|
|
13
13
|
} from './werte.js';
|
|
14
14
|
import { erkenneEntwurf } from './erkennung.js';
|
|
15
15
|
import { vorschlag , pruefeVorlage, zeigeAbozahl, setzeInstanz } from './vorlagen.js';
|
|
@@ -89,9 +89,7 @@ export function baueEntwurf(kanal) {
|
|
|
89
89
|
QUELLE direkt auf 30 gesetzt — der Alias zeigte 30. `read: false`
|
|
90
90
|
sperrt das Lesen also nicht, es sagt nur, dass von allein nichts
|
|
91
91
|
kommt. Wer den Wert setzt, will ihn auch sehen. */
|
|
92
|
-
var
|
|
93
|
-
var istTaster = (c.read === false && !!c.write) &&
|
|
94
|
-
(nat.TYPE === 'ACTION' || /^button/.test(String(c.role || '')));
|
|
92
|
+
var istTaster = istTasterObjekt(o);
|
|
95
93
|
e.states.push({
|
|
96
94
|
/* Bei einer Quelle wird aus stat.POWER der Zustand stat_POWER —
|
|
97
95
|
ein Punkt im Namen wuerde sonst eine Unterebene im Alias
|
|
@@ -110,7 +108,12 @@ export function baueEntwurf(kanal) {
|
|
|
110
108
|
dann „keine Rolle" und „nichts erkannt". Typ und Einheit hat sie
|
|
111
109
|
von jeher uebernommen; die Rolle auszunehmen war nicht zu
|
|
112
110
|
begruenden. Ueberschreiben laesst sie sich weiterhin. */
|
|
113
|
-
|
|
111
|
+
/* Ein Tastendruck laut Geraetebeschreibung bekommt die Knopfrolle,
|
|
112
|
+
wenn der Adapter ihm keine gegeben hat - an ihr haengt `read:
|
|
113
|
+
false` am Alias (`knopfZeile`). hm-rpc gibt `OLD_LEVEL`
|
|
114
|
+
`value.dimmer` und `INSTALL_TEST` `indicator`; die uebrigen
|
|
115
|
+
(PRESS_SHORT, STOP, RAMP_STOP) tragen schon `button…`. */
|
|
116
|
+
role: (istTaster && !/^button/.test(String(c.role || ''))) ? 'button' : (c.role || ''),
|
|
114
117
|
typ: c.type || '',
|
|
115
118
|
unit: c.unit || '',
|
|
116
119
|
/* Ohne die Werteliste bleibt jeder Platz mit statesDefined leer —
|
|
@@ -150,8 +153,13 @@ export function baueEntwurf(kanal) {
|
|
|
150
153
|
Schalters, der ewig auf „aus" steht. Kann der Punkt auch nicht
|
|
151
154
|
schreiben, bleibt er, wie er ist: dann ist die fehlende Meldung
|
|
152
155
|
wirklich ein Mangel, und die Marke sagt es. */
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
/* Seit 19.09.2026 bekommt auch der Taster seine Lesequelle: der
|
|
157
|
+
Alias sieht dann jeden Druck, auch einen von aussen. Zum Knopf
|
|
158
|
+
wird er trotzdem - `read: false` haengt jetzt an der Rolle
|
|
159
|
+
(`knopfZeile` in werte.js), nicht mehr an der leeren Lesequelle.
|
|
160
|
+
Was oben ueber `read: false` an der Quelle steht, ist damit
|
|
161
|
+
Geschichte: gemessen liest ein solcher Alias tadellos. */
|
|
162
|
+
srcR: istAlias ? (q.read || '') : id,
|
|
155
163
|
/* Steht in `common.alias.id` ein einzelner Text, gilt er fuer Lesen
|
|
156
164
|
UND Schreiben - dann ist die Schreibquelle dieselbe wie die
|
|
157
165
|
Lesequelle. Hier stand frueher eine leere Zeichenkette, gedacht
|
|
@@ -406,7 +414,11 @@ export function quelleVon(aliasId) {
|
|
|
406
414
|
kindZustaende(aliasId).forEach(function (id) {
|
|
407
415
|
var qq = aliasQuellen(S.objects[id]);
|
|
408
416
|
[qq.read, qq.write].forEach(function (x) {
|
|
409
|
-
|
|
417
|
+
/* Nur Quellen, die es gibt. Zeigt jeder Punkt ins Leere, fand der
|
|
418
|
+
Rueckfall darunter („eine Ebene hoeher") den naechsten VORHANDENEN
|
|
419
|
+
Ordner - und „zur Quelle" sprang auf `0_userdata.0.wbfake` mit 31
|
|
420
|
+
fremden Geraeten statt auf die verschwundene Quelle (D10). */
|
|
421
|
+
if (!x || !S.objects[x]) { return; }
|
|
410
422
|
var t = String(x).split('.');
|
|
411
423
|
t.pop();
|
|
412
424
|
var eltern = t.join('.');
|
|
@@ -1204,9 +1216,13 @@ export function geraeteDarunter(wurzel) {
|
|
|
1204
1216
|
}
|
|
1205
1217
|
});
|
|
1206
1218
|
|
|
1207
|
-
|
|
1219
|
+
/* Angezeigt wird wie im Baum: `seg_inf.10` hinter `seg_inf.9`, nicht
|
|
1220
|
+
hinter `seg_inf.1` (E45). Die Liste dient nur der Anzeige — die
|
|
1221
|
+
byteweise Ordnung von `keysSorted` bleibt unberuehrt (E42). */
|
|
1222
|
+
geraete.sort(function (a, b) { return GERAETE_VERGLEICH.compare(a.id, b.id); });
|
|
1208
1223
|
return geraete;
|
|
1209
1224
|
}
|
|
1225
|
+
var GERAETE_VERGLEICH = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
|
1210
1226
|
|
|
1211
1227
|
/* Gibt es fuer diese Quelle schon einen Alias?
|
|
1212
1228
|
|
|
@@ -1225,11 +1241,17 @@ export function geraeteDarunter(wurzel) {
|
|
|
1225
1241
|
`aliasFuer` lief zweimal ueber alle Kennungen — und wird je
|
|
1226
1242
|
Neuzeichnen mehrfach gerufen. Die Karte entsteht bei der ersten Frage
|
|
1227
1243
|
nach einem Indexwechsel und beantwortet alle weiteren aus dem
|
|
1228
|
-
Gedaechtnis.
|
|
1229
|
-
|
|
1230
|
-
|
|
1244
|
+
Gedaechtnis.
|
|
1245
|
+
|
|
1246
|
+
Erkannt wurde der Wechsel bis 19.09.2026 an Laenge und erster Kennung.
|
|
1247
|
+
Das reicht nicht: Verlegen loescht acht Objekte und legt acht neue an,
|
|
1248
|
+
die Zahl bleibt gleich, und ein Quellentausch aendert nur `alias.id`.
|
|
1249
|
+
Die Karte zeigte danach auf den geloeschten Alias, an der Quelle stand
|
|
1250
|
+
„wird neu angelegt", und ein Klick haette einen zweiten Alias erzeugt
|
|
1251
|
+
(T22). Jetzt zaehlt `S.objektStand` jede Aenderung an den Objekten. */
|
|
1252
|
+
var alsQuelleKarte = null, alsQuelleStand = -1;
|
|
1231
1253
|
function alsQuelleKarten() {
|
|
1232
|
-
var stand = S.
|
|
1254
|
+
var stand = S.objektStand;
|
|
1233
1255
|
if (alsQuelleKarte && alsQuelleStand === stand) { return alsQuelleKarte; }
|
|
1234
1256
|
var fest = {}, zweig = {};
|
|
1235
1257
|
mitVorspann('alias.').forEach(function (id) {
|
|
@@ -1513,7 +1535,15 @@ export function vorlageAnwenden(e) {
|
|
|
1513
1535
|
var anders = vorlagenAbweichung(s).length > 0;
|
|
1514
1536
|
s.role = v.role; s.typ = v.typ; s.unit = v.unit;
|
|
1515
1537
|
s.f = v.f; s.fw = v.fw; s.srcR = v.srcR; s.srcW = v.srcW;
|
|
1516
|
-
|
|
1538
|
+
/* Die Werteliste nur, wenn die Vorlage selbst eine mitbringt. Sagt sie
|
|
1539
|
+
nichts dazu, gilt der Bestand — dieselbe Regel wie bei Rolle und Typ
|
|
1540
|
+
in `bestandVorrang`. Vorher stand hier `s.states = v.states`, und an
|
|
1541
|
+
einer Vorlagenzeile ohne Werteliste hiess das `undefined`: am
|
|
1542
|
+
Rollladen verlor DIRECTION {0: NONE, 1: UP, 2: DOWN}, ohne dass
|
|
1543
|
+
Zaehler oder Marke es zeigten — `vorlagenAbweichung` vergleicht die
|
|
1544
|
+
Werteliste nicht, und die Werkbank hat kein Feld dafuer (Lauf vom
|
|
1545
|
+
19.09.2026, G10). */
|
|
1546
|
+
if (v.states !== undefined) { s.states = v.states; }
|
|
1517
1547
|
/* caption bleibt: der Anzeigename gehoert dem Nutzer. */
|
|
1518
1548
|
if (anders) { s.geaendert = true; }
|
|
1519
1549
|
});
|
package/admin/js/enums.js
CHANGED
|
@@ -32,6 +32,11 @@ export function enumsUnbekannt() { return unbekannt; }
|
|
|
32
32
|
var versuch = 0;
|
|
33
33
|
var wiederTimer = null;
|
|
34
34
|
|
|
35
|
+
/* Wer wissen will, wann ein Nachversuch geklappt hat - die Felder fuer
|
|
36
|
+
Raum und Funktion zeichnen sich dann neu (P8). */
|
|
37
|
+
var geladenHoerer = [];
|
|
38
|
+
export function beiEnumsGeladen(fn) { geladenHoerer.push(fn); }
|
|
39
|
+
|
|
35
40
|
export function ladeEnums(danach) {
|
|
36
41
|
if (wiederTimer) { clearTimeout(wiederTimer); wiederTimer = null; }
|
|
37
42
|
socket.emit('getObjectView', 'system', 'enum',
|
|
@@ -59,8 +64,10 @@ export function ladeEnums(danach) {
|
|
|
59
64
|
if (r.value && r.id) { frisch[r.id] = r.value; }
|
|
60
65
|
});
|
|
61
66
|
enums = frisch;
|
|
67
|
+
var warUnbekannt = unbekannt;
|
|
62
68
|
unbekannt = false;
|
|
63
69
|
versuch = 0;
|
|
70
|
+
if (warUnbekannt) { geladenHoerer.forEach(function (fn) { fn(); }); }
|
|
64
71
|
if (typeof danach === 'function') { danach(null); }
|
|
65
72
|
});
|
|
66
73
|
}
|
package/admin/js/ergebnis.js
CHANGED
|
@@ -358,6 +358,15 @@ var warumAuf = false;
|
|
|
358
358
|
if (!sprungZiel) {
|
|
359
359
|
var vermerkt = S.objects[S.current] && S.objects[S.current].native && S.objects[S.current].native.quelle;
|
|
360
360
|
if (vermerkt) { sub.appendChild(el('span', 'chip warn', tr('result.sourceGone', vermerkt))); }
|
|
361
|
+
/* Der Knopf bleibt sichtbar, aber gesperrt: verschwaende er, suchte
|
|
362
|
+
man ihn; klickbar sprang er frueher irgendwohin (D10). */
|
|
363
|
+
var wohin = vermerkt || (insLeere[0] && insLeere[0].srcR) || '';
|
|
364
|
+
if (wohin) {
|
|
365
|
+
sprung = el('button', 'btn schmal sprungknopf', tr('result.toSource') + ' →');
|
|
366
|
+
sprung.disabled = true;
|
|
367
|
+
sprung.title = tr('result.sourceGone', wohin);
|
|
368
|
+
namensZeile.appendChild(sprung);
|
|
369
|
+
}
|
|
361
370
|
}
|
|
362
371
|
} else {
|
|
363
372
|
sprungZiel = aliasFuer(S.current);
|
|
@@ -509,7 +518,13 @@ var warumAuf = false;
|
|
|
509
518
|
/* Der Vorlagenkasten: welche Vorlage gegriffen hat, warum, ihre
|
|
510
519
|
Wahl und Knoepfe. Nimmt die Musterwahl bei sich auf; ohne Kasten
|
|
511
520
|
bleibt sie im Kopf - bis die Leiste sie sich holt. */
|
|
512
|
-
|
|
521
|
+
/* Zurueck kommt, was noch keinen Platz hat: der Musterblock (ohne
|
|
522
|
+
Kasten) und der Knopf fuer die Leiste ueber der Liste. Im Kasten
|
|
523
|
+
sitzt seit 19.09.2026 „Freie Plaetze vorschlagen"; der Knopf, der
|
|
524
|
+
den ganzen Bestand des Geraets laedt, wandert dafuer in die Leiste -
|
|
525
|
+
er aendert die Liste, nicht die Erkennung (Ricardo). */
|
|
526
|
+
function baueVorlagenkasten(host, head, e, istQuelle, musterBlock, rateKnopf) {
|
|
527
|
+
var leistenKnopf = rateKnopf;
|
|
513
528
|
if (e.vorschlag || istQuelle) {
|
|
514
529
|
var sg = el('div', 'suggestbox');
|
|
515
530
|
if (!e.vorschlag) {
|
|
@@ -760,7 +775,6 @@ var warumAuf = false;
|
|
|
760
775
|
der nur die vorgeschlagenen Zeilen ein- und ausblendet. Deshalb
|
|
761
776
|
steht das Geraet im Namen und der Rest im Hinweis. */
|
|
762
777
|
bRoh.title = e.roh ? tr('tpl.showProposalHelp') : tr('tpl.showAllPointsHelp');
|
|
763
|
-
if (!e.vorschlag) { bRoh.disabled = true; }
|
|
764
778
|
bRoh.addEventListener('click', function () {
|
|
765
779
|
var rohJetzt = !e.roh;
|
|
766
780
|
/* Dieselben zwei Schritte wie in `waehle`: die Vorlage, aus der der
|
|
@@ -855,8 +869,12 @@ var warumAuf = false;
|
|
|
855
869
|
knoepfe.appendChild(bTpl);
|
|
856
870
|
}
|
|
857
871
|
}
|
|
858
|
-
knoepfe.appendChild(
|
|
859
|
-
acts.appendChild(knoepfe);
|
|
872
|
+
if (rateKnopf) { knoepfe.appendChild(rateKnopf); }
|
|
873
|
+
if (knoepfe.childNodes.length) { acts.appendChild(knoepfe); }
|
|
874
|
+
/* Ohne Vorlagenvorschlag gibt es keinen Bestand, zu dem er
|
|
875
|
+
umschalten koennte - dann steht er gar nicht erst da, statt
|
|
876
|
+
ausgegraut in der Leiste. */
|
|
877
|
+
leistenKnopf = e.vorschlag ? bRoh : null;
|
|
860
878
|
sg.appendChild(acts);
|
|
861
879
|
host.appendChild(sg);
|
|
862
880
|
}
|
|
@@ -865,7 +883,7 @@ var warumAuf = false;
|
|
|
865
883
|
if (musterBlock) {
|
|
866
884
|
head.appendChild(musterBlock);
|
|
867
885
|
}
|
|
868
|
-
return musterBlock;
|
|
886
|
+
return { musterBlock: musterBlock, leistenKnopf: leistenKnopf };
|
|
869
887
|
}
|
|
870
888
|
|
|
871
889
|
/* Die Karte „Gehoert nicht zum <Muster>" stand hier bis zum
|
|
@@ -1067,7 +1085,9 @@ var warumAuf = false;
|
|
|
1067
1085
|
|
|
1068
1086
|
var istQuelle = baueZielleiste(host, e);
|
|
1069
1087
|
|
|
1070
|
-
|
|
1088
|
+
var kasten = baueVorlagenkasten(host, head, e, istQuelle, musterBlock, rateKnopf);
|
|
1089
|
+
musterBlock = kasten.musterBlock;
|
|
1090
|
+
rateKnopf = kasten.leistenKnopf;
|
|
1071
1091
|
|
|
1072
1092
|
/* Gleich unter die Erkennung: was hier fehlt, entscheidet mit
|
|
1073
1093
|
darueber, ob eine Vorlage ueberhaupt greift. */
|
package/admin/js/erkennung.js
CHANGED
|
@@ -32,6 +32,11 @@ import { D } from './basis.js';
|
|
|
32
32
|
export function ausdruckText(r) {
|
|
33
33
|
if (!r) { return ''; }
|
|
34
34
|
if (r.source !== undefined) { return r.source; }
|
|
35
|
+
/* Ein Objekt ohne `source` ist kein Ausdruck. `ignoreRole` kommt im
|
|
36
|
+
Browser als leeres `{}` an; `String({})` gab „[object Object]", und
|
|
37
|
+
das ist eine Zeichenklasse, die jede Rolle mit einem „e" trifft —
|
|
38
|
+
jeder Sollwert der Klimaanlage galt als ausgenommen (19.09.2026). */
|
|
39
|
+
if (typeof r === 'object') { return ''; }
|
|
35
40
|
var q = String(r);
|
|
36
41
|
var m = /^\/(.*)\/[a-z]*$/.exec(q);
|
|
37
42
|
return m ? m[1] : q;
|
|
@@ -311,6 +316,46 @@ export function typKonflikte(states, musterName) {
|
|
|
311
316
|
return raus;
|
|
312
317
|
}
|
|
313
318
|
|
|
319
|
+
/* Welche Pflicht eines Musters der Entwurf wirklich nicht erfuellt.
|
|
320
|
+
|
|
321
|
+
Bis 19.09.2026 nannte das Musterfeld schlicht alle `required`-Plaetze:
|
|
322
|
+
an der Klimaanlage stand „✕ MODE fehlt", obwohl MODE angehakt und
|
|
323
|
+
bestaetigt war — gefehlt hat der Sollwert. Der steht im Muster nicht
|
|
324
|
+
als `required`, sondern als Gruppe (`requiredOneOf: 'setpoint'`):
|
|
325
|
+
einer aus SET und den beiden Doppel-Sollwerten muss da sein, und die
|
|
326
|
+
Gruppe kam in der Auskunft gar nicht vor (U13).
|
|
327
|
+
|
|
328
|
+
Geprueft wird grob, wie der Detektor: angehakte Zeile mit passender
|
|
329
|
+
Rolle, schreibbar wo der Platz es verlangt, im zugelassenen Typ.
|
|
330
|
+
Zurueck kommen Namen — eine Gruppe als „SET / SET_HEAT / …". Findet
|
|
331
|
+
die Grobpruefung nichts, gibt sie [] zurueck, und der Aufrufer faellt
|
|
332
|
+
auf die alte Auskunft zurueck. */
|
|
333
|
+
export function fehlendePflicht(states, musterName) {
|
|
334
|
+
var mu = musterName && musterVon(musterName);
|
|
335
|
+
if (!mu) { return []; }
|
|
336
|
+
function erfuellt(pl) {
|
|
337
|
+
return (states || []).some(function (s) {
|
|
338
|
+
if (!s.on || !s.role || !rolleTrifft(pl.role, s.role)) { return false; }
|
|
339
|
+
if (pl.ignoreRole && rolleTrifft(pl.ignoreRole, s.role)) { return false; }
|
|
340
|
+
if (pl.write === true && !(s.wr || s.srcW)) { return false; }
|
|
341
|
+
return typPasstZuPlatz(pl, s.typ);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
var raus = [], gruppen = {};
|
|
345
|
+
mu.states.forEach(function (pl) {
|
|
346
|
+
if (pl.required && !erfuellt(pl)) { raus.push(pl.name); }
|
|
347
|
+
if (pl.requiredOneOf) {
|
|
348
|
+
var g = gruppen[pl.requiredOneOf] = gruppen[pl.requiredOneOf] || { namen: [], da: false };
|
|
349
|
+
if (g.namen.indexOf(pl.name) === -1) { g.namen.push(pl.name); }
|
|
350
|
+
if (erfuellt(pl)) { g.da = true; }
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
Object.keys(gruppen).forEach(function (k) {
|
|
354
|
+
if (!gruppen[k].da) { raus.push(gruppen[k].namen.join(' / ')); }
|
|
355
|
+
});
|
|
356
|
+
return raus;
|
|
357
|
+
}
|
|
358
|
+
|
|
314
359
|
/* Die Datentypen, die ein Platz zulaesst - als Liste.
|
|
315
360
|
|
|
316
361
|
Drei Faelle, gemessen am type-detector 6.0.1: 622 Plaetze nennen
|
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 (
|
|
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
|
|
@@ -261,16 +276,20 @@ export function mqttLage(kanal) {
|
|
|
261
276
|
var g = mqttGeraet(kanal);
|
|
262
277
|
if (!g) { return null; }
|
|
263
278
|
var b = tasmotaBefehle(kanal);
|
|
264
|
-
var fehlen = [], stumm = [], da = [];
|
|
279
|
+
var fehlen = [], stumm = [], da = [], unklar = [];
|
|
265
280
|
if (b) {
|
|
266
281
|
b.befehle.forEach(function (x) {
|
|
267
282
|
var id = hatPunkt(kanal, 'cmnd.' + x.name);
|
|
268
283
|
if (!id) { fehlen.push(x); return; }
|
|
269
284
|
var m = mqttEinstellung(id);
|
|
270
|
-
/* `publish === null` heisst unbekannt (Thema nur in `native
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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); }
|
|
274
293
|
});
|
|
275
294
|
}
|
|
276
295
|
/* Auch cmnd-Punkte, die es schon gibt, ohne dass sie im Zustand
|
|
@@ -283,8 +302,9 @@ export function mqttLage(kanal) {
|
|
|
283
302
|
if (da.indexOf(name) > -1) { return; }
|
|
284
303
|
var m = mqttEinstellung(id);
|
|
285
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); }
|
|
286
306
|
});
|
|
287
|
-
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,
|
|
288
308
|
sofort: sofortRueckmeldung(kanal) };
|
|
289
309
|
}
|
|
290
310
|
|
|
@@ -346,7 +366,11 @@ export function mqttKarte(host, kanal) {
|
|
|
346
366
|
sah die Zahlen, aber nicht, welcher Befehl welcher Fall ist. */
|
|
347
367
|
var zustand = {};
|
|
348
368
|
l.fehlen.forEach(function (x) { zustand[x.name] = 'fehlt'; });
|
|
349
|
-
l.stumm.forEach(function (x) {
|
|
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'; });
|
|
350
374
|
l.da.forEach(function (n) { zustand[n] = 'da'; });
|
|
351
375
|
|
|
352
376
|
var reihen = l.befehle.befehle.map(function (x) { return x.name; });
|
|
@@ -379,6 +403,8 @@ export function mqttKarte(host, kanal) {
|
|
|
379
403
|
var c = el('span', 'mchip');
|
|
380
404
|
if (z === 'fehlt') { c.appendChild(el('span', 'chip warn', tr('mq.stMissing'))); }
|
|
381
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'))); }
|
|
382
408
|
else { c.appendChild(el('span', 'chip ok', tr('mq.stFine'))); }
|
|
383
409
|
r.appendChild(c);
|
|
384
410
|
|
|
@@ -389,7 +415,7 @@ export function mqttKarte(host, kanal) {
|
|
|
389
415
|
var bn = el('button', 'btn schmal', tr('mq.rowCreate'));
|
|
390
416
|
bn.addEventListener('click', function () { mqttEinzeln(kanal, n, 'neu', bn); });
|
|
391
417
|
h.appendChild(bn);
|
|
392
|
-
} else if (z === 'stumm') {
|
|
418
|
+
} else if (z === 'stumm' || z === 'aus') {
|
|
393
419
|
var bs2 = el('button', 'btn schmal', tr('mq.rowAllow'));
|
|
394
420
|
bs2.addEventListener('click', function () { mqttEinzeln(kanal, n, 'frei', bs2); });
|
|
395
421
|
h.appendChild(bs2);
|
|
@@ -421,7 +447,56 @@ export function mqttKarte(host, kanal) {
|
|
|
421
447
|
Nur, wenn ueberhaupt eine Zeile am Telemetrietakt haengt. An einem
|
|
422
448
|
Geraet, dessen Aliase alle aus `stat/...` lesen, ist SetOption59
|
|
423
449
|
folgenlos, und ein Block darueber ist bloss Betrieb. */
|
|
424
|
-
|
|
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); }
|
|
453
|
+
|
|
454
|
+
/* Nachfragen liefert dasselbe wie tele/STATE — gemessen, Feld fuer
|
|
455
|
+
Feld. Der Knopf erscheint deshalb nur, wenn es etwas zu holen gibt:
|
|
456
|
+
gar nichts gelesen, oder nur stat.RESULT, das meist bloss die
|
|
457
|
+
Antwort auf den letzten Befehl enthaelt und nicht den ganzen
|
|
458
|
+
Zustand. Steht die Liste vollstaendig da, gibt es den Knopf nicht —
|
|
459
|
+
er koennte nichts hinzufuegen. */
|
|
460
|
+
var luecke = !l.befehle || l.befehle.woher === 'stat.RESULT';
|
|
461
|
+
if (luecke) {
|
|
462
|
+
var kasten = el('div');
|
|
463
|
+
kasten.style.marginTop = '11px';
|
|
464
|
+
kasten.style.paddingTop = '10px';
|
|
465
|
+
kasten.style.borderTop = '1px solid var(--line)';
|
|
466
|
+
var bf = el('button', 'btn' + (l.befehle ? '' : ' primary')
|
|
467
|
+
+ (abfrageLaeuft[kanal] ? ' laedt' : ''),
|
|
468
|
+
abfrageLaeuft[kanal] ? tr('mq.asking') : tr('mq.ask'));
|
|
469
|
+
if (abfrageLaeuft[kanal]) { bf.disabled = true; }
|
|
470
|
+
bf.addEventListener('click', function () { mqttFragen(kanal, bf); });
|
|
471
|
+
kasten.appendChild(bf);
|
|
472
|
+
var warum = el('div', 'hint');
|
|
473
|
+
warum.style.marginTop = '6px';
|
|
474
|
+
warum.textContent = (l.befehle ? tr('mq.onlyResult') : tr('mq.neverReported')) +
|
|
475
|
+
' ' + tr('mq.askHint');
|
|
476
|
+
kasten.appendChild(warum);
|
|
477
|
+
if (mqttAbfrage[kanal] !== undefined) {
|
|
478
|
+
var ah = el('div', 'hint');
|
|
479
|
+
ah.style.marginTop = '6px';
|
|
480
|
+
var ab = mqttAbfrage[kanal];
|
|
481
|
+
if (ab && ab.fehler) {
|
|
482
|
+
ah.textContent = tr('mq.askError', ab.fehler);
|
|
483
|
+
ah.style.color = 'var(--bad)';
|
|
484
|
+
} else {
|
|
485
|
+
ah.textContent = ab ? tr('mq.askGotAnswer', ab) : tr('mq.askNoAnswer');
|
|
486
|
+
ah.style.color = ab ? 'var(--ok)' : 'var(--warn)';
|
|
487
|
+
}
|
|
488
|
+
kasten.appendChild(ah);
|
|
489
|
+
}
|
|
490
|
+
b.appendChild(kasten);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
k.appendChild(b);
|
|
494
|
+
host.appendChild(k);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* Der Block zur sofortigen Rueckmeldung (SetOption59) — nur gerufen,
|
|
498
|
+
wenn eine Zeile am Telemetrietakt haengt. */
|
|
499
|
+
function so59Kasten(b, kanal, l, telZ) {
|
|
425
500
|
|
|
426
501
|
var sk = el('div');
|
|
427
502
|
sk.style.marginTop = '11px';
|
|
@@ -438,7 +513,9 @@ export function mqttKarte(host, kanal) {
|
|
|
438
513
|
sz.appendChild(el('span', 'chip mut', tr('mq.instantUnknown')));
|
|
439
514
|
}
|
|
440
515
|
sk.appendChild(sz);
|
|
441
|
-
|
|
516
|
+
/* Auch wenn noch ein alter Wert dasteht: die Frage eben blieb
|
|
517
|
+
unbeantwortet, und das gehoert gesagt. */
|
|
518
|
+
if (so59Stumm[kanal]) {
|
|
442
519
|
var st59 = el('div', 'hint');
|
|
443
520
|
st59.style.marginTop = '4px';
|
|
444
521
|
st59.style.color = 'var(--warn)';
|
|
@@ -451,19 +528,24 @@ export function mqttKarte(host, kanal) {
|
|
|
451
528
|
sl.style.display = 'flex';
|
|
452
529
|
sl.style.gap = '8px';
|
|
453
530
|
sl.style.flexWrap = 'wrap';
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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]) {
|
|
462
544
|
var be = el('button', 'btn schmal' + (lauf === 'einschalten' ? ' laedt' : ''),
|
|
463
545
|
lauf === 'einschalten' ? tr('mq.instantEnabling') : tr('mq.instantEnable'));
|
|
464
546
|
be.addEventListener('click', function () { so59(kanal, be, '1'); });
|
|
465
547
|
sl.appendChild(be);
|
|
466
|
-
if (lauf) {
|
|
548
|
+
if (lauf) { be.disabled = true; }
|
|
467
549
|
}
|
|
468
550
|
sk.appendChild(sl);
|
|
469
551
|
/* Der Hinweis nennt die betroffenen Zeilen und den wirklichen Takt,
|
|
@@ -479,48 +561,6 @@ export function mqttKarte(host, kanal) {
|
|
|
479
561
|
var alter = (l.sofort === null) ? '' : so59Alter(kanal);
|
|
480
562
|
sk.appendChild(el('div', 'hint', hinweis + (alter ? ' · ' + alter : '')));
|
|
481
563
|
b.appendChild(sk);
|
|
482
|
-
|
|
483
|
-
/* Nachfragen liefert dasselbe wie tele/STATE — gemessen, Feld fuer
|
|
484
|
-
Feld. Der Knopf erscheint deshalb nur, wenn es etwas zu holen gibt:
|
|
485
|
-
gar nichts gelesen, oder nur stat.RESULT, das meist bloss die
|
|
486
|
-
Antwort auf den letzten Befehl enthaelt und nicht den ganzen
|
|
487
|
-
Zustand. Steht die Liste vollstaendig da, gibt es den Knopf nicht —
|
|
488
|
-
er koennte nichts hinzufuegen. */
|
|
489
|
-
var luecke = !l.befehle || l.befehle.woher === 'stat.RESULT';
|
|
490
|
-
if (luecke) {
|
|
491
|
-
var kasten = el('div');
|
|
492
|
-
kasten.style.marginTop = '11px';
|
|
493
|
-
kasten.style.paddingTop = '10px';
|
|
494
|
-
kasten.style.borderTop = '1px solid var(--line)';
|
|
495
|
-
var bf = el('button', 'btn' + (l.befehle ? '' : ' primary')
|
|
496
|
-
+ (abfrageLaeuft[kanal] ? ' laedt' : ''),
|
|
497
|
-
abfrageLaeuft[kanal] ? tr('mq.asking') : tr('mq.ask'));
|
|
498
|
-
if (abfrageLaeuft[kanal]) { bf.disabled = true; }
|
|
499
|
-
bf.addEventListener('click', function () { mqttFragen(kanal, bf); });
|
|
500
|
-
kasten.appendChild(bf);
|
|
501
|
-
var warum = el('div', 'hint');
|
|
502
|
-
warum.style.marginTop = '6px';
|
|
503
|
-
warum.textContent = (l.befehle ? tr('mq.onlyResult') : tr('mq.neverReported')) +
|
|
504
|
-
' ' + tr('mq.askHint');
|
|
505
|
-
kasten.appendChild(warum);
|
|
506
|
-
if (mqttAbfrage[kanal] !== undefined) {
|
|
507
|
-
var ah = el('div', 'hint');
|
|
508
|
-
ah.style.marginTop = '6px';
|
|
509
|
-
var ab = mqttAbfrage[kanal];
|
|
510
|
-
if (ab && ab.fehler) {
|
|
511
|
-
ah.textContent = tr('mq.askError', ab.fehler);
|
|
512
|
-
ah.style.color = 'var(--bad)';
|
|
513
|
-
} else {
|
|
514
|
-
ah.textContent = ab ? tr('mq.askGotAnswer', ab) : tr('mq.askNoAnswer');
|
|
515
|
-
ah.style.color = ab ? 'var(--ok)' : 'var(--warn)';
|
|
516
|
-
}
|
|
517
|
-
kasten.appendChild(ah);
|
|
518
|
-
}
|
|
519
|
-
b.appendChild(kasten);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
k.appendChild(b);
|
|
523
|
-
host.appendChild(k);
|
|
524
564
|
}
|
|
525
565
|
|
|
526
566
|
var mqttStand = null;
|
|
@@ -571,6 +611,13 @@ function so59(kanal, knopf, wert) {
|
|
|
571
611
|
if (!g) { return; }
|
|
572
612
|
so59Laeuft[kanal] = wert ? 'einschalten' : 'pruefen';
|
|
573
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;
|
|
574
621
|
/* Sofort neu zeichnen: erst damit sperren sich BEIDE Knoepfe und die
|
|
575
622
|
alte Antwortzeile verschwindet - vorher passierte das erst mit dem
|
|
576
623
|
naechsten Nachzieher, Sekunden spaeter (Ricardo, 25.08.2026). */
|
|
@@ -593,7 +640,13 @@ function so59(kanal, knopf, wert) {
|
|
|
593
640
|
setTimeout(function () {
|
|
594
641
|
holeZweig(kanal + '.', function () {
|
|
595
642
|
delete so59Laeuft[kanal];
|
|
596
|
-
|
|
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;
|
|
597
650
|
zeichneErgebnis();
|
|
598
651
|
});
|
|
599
652
|
}, 4000);
|
|
@@ -755,7 +808,7 @@ function mqttPunktBauen(kanal, name, was) {
|
|
|
755
808
|
if (!alt0) { return null; }
|
|
756
809
|
obj = JSON.parse(JSON.stringify(alt0));
|
|
757
810
|
var cu0 = obj.common.custom || {};
|
|
758
|
-
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; } });
|
|
759
812
|
obj.common.custom = cu0;
|
|
760
813
|
} else {
|
|
761
814
|
var w0 = befehlsWissen(name) || {};
|
|
@@ -789,7 +842,7 @@ export function mqttEinzeln(kanal, name, was, knopf) {
|
|
|
789
842
|
if (!alt) { return; }
|
|
790
843
|
obj = JSON.parse(JSON.stringify(alt));
|
|
791
844
|
var cu = obj.common.custom || {};
|
|
792
|
-
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; } });
|
|
793
846
|
obj.common.custom = cu;
|
|
794
847
|
} else {
|
|
795
848
|
var w = befehlsWissen(name) || {};
|
|
@@ -961,10 +1014,18 @@ function mqttZuSchreiben() {
|
|
|
961
1014
|
if (!mqttAuswahl['neu:' + x.name]) { return; }
|
|
962
1015
|
var w = befehlsWissen(x.name) || {};
|
|
963
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). */
|
|
964
1025
|
var common = {
|
|
965
1026
|
name: x.name,
|
|
966
1027
|
role: w.rolle || 'state',
|
|
967
|
-
type: w
|
|
1028
|
+
type: punktTyp(w),
|
|
968
1029
|
read: true, write: true,
|
|
969
1030
|
desc: tr('mq.createdBy'),
|
|
970
1031
|
custom: mqttCustom(l.geraet, thema)
|
|
@@ -979,7 +1040,7 @@ function mqttZuSchreiben() {
|
|
|
979
1040
|
if (!alt) { return; }
|
|
980
1041
|
var kopie = JSON.parse(JSON.stringify(alt));
|
|
981
1042
|
var cu = kopie.common.custom || {};
|
|
982
|
-
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; } });
|
|
983
1044
|
kopie.common.custom = cu;
|
|
984
1045
|
raus.push({ id: x.id, obj: kopie, neu: false });
|
|
985
1046
|
});
|