iobroker.alias-workbench 0.8.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/LICENSE +21 -0
- package/README.md +516 -0
- package/admin/alias-workbench.png +0 -0
- package/admin/css/werkbank.css +916 -0
- package/admin/detector.js +1 -0
- package/admin/geraetewissen/tasmota-befehle.json +116 -0
- package/admin/i18n/de.json +552 -0
- package/admin/i18n/en.json +552 -0
- package/admin/js/aufzaehlungen.js +332 -0
- package/admin/js/basis.js +36 -0
- package/admin/js/baum.js +453 -0
- package/admin/js/detail.js +335 -0
- package/admin/js/einstellungen.js +40 -0
- package/admin/js/entwurf.js +908 -0
- package/admin/js/enums.js +23 -0
- package/admin/js/ergebnis.js +934 -0
- package/admin/js/erkennung.js +126 -0
- package/admin/js/katalog.js +313 -0
- package/admin/js/mqtt.js +885 -0
- package/admin/js/musternamen.js +94 -0
- package/admin/js/musterwahl.js +334 -0
- package/admin/js/objekte.js +243 -0
- package/admin/js/pruefungen.js +130 -0
- package/admin/js/quellentausch.js +570 -0
- package/admin/js/rollenwahl.js +199 -0
- package/admin/js/schreiben.js +1537 -0
- package/admin/js/sprache.js +137 -0
- package/admin/js/start.js +39 -0
- package/admin/js/verbindung.js +10 -0
- package/admin/js/vorlagen.js +572 -0
- package/admin/js/vorlagenblatt.js +1907 -0
- package/admin/js/vorschlagen.js +391 -0
- package/admin/js/werte.js +193 -0
- package/admin/js/zielleiste.js +339 -0
- package/admin/js/zuordnung.js +695 -0
- package/admin/js/zustand.js +47 -0
- package/admin/js/zustandsliste.js +515 -0
- package/admin/jsonConfig.json +249 -0
- package/admin/tab.html +148 -0
- package/admin/vorlagen/hm-dimmer.json +204 -0
- package/admin/vorlagen/hm-fensterkontakt-hm.json +176 -0
- package/admin/vorlagen/hm-fensterkontakt-hmip.json +191 -0
- package/admin/vorlagen/hm-heizgruppe.json +214 -0
- package/admin/vorlagen/hm-heizkoerper.json +236 -0
- package/admin/vorlagen/hm-rollladen.json +190 -0
- package/admin/vorlagen/hm-schaltaktor-lampe.json +197 -0
- package/admin/vorlagen/hm-schaltaktor-mehrfach.json +203 -0
- package/admin/vorlagen/hm-schaltaktor-steckdose.json +197 -0
- package/admin/vorlagen/hm-temperatur-feuchte.json +166 -0
- package/admin/vorlagen/hm-wandthermostat.json +228 -0
- package/admin/vorlagen/index.json +21 -0
- package/admin/vorlagen/messpunkt.json +225 -0
- package/admin/vorlagen/tasmota-lampe.json +263 -0
- package/admin/vorlagen/tasmota-mehrfach.json +203 -0
- package/admin/vorlagen/tasmota-rgb.json +239 -0
- package/admin/vorlagen/tasmota-steckdose.json +265 -0
- package/io-package.json +122 -0
- package/package.json +48 -0
package/admin/js/mqtt.js
ADDED
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
/* Die Tasmota-Werkzeuge: Befehlswissen, Sendepunkte, SetOption59.
|
|
2
|
+
|
|
3
|
+
Ein Tasmota veroeffentlicht nie nach cmnd - was das Geraet an Befehlen
|
|
4
|
+
kennt, steht in dem, was es sendet. Daraus baut die Werkbank auf
|
|
5
|
+
Wunsch die Sendepunkte. */
|
|
6
|
+
|
|
7
|
+
import { S } from './zustand.js';
|
|
8
|
+
import { socket } from './verbindung.js';
|
|
9
|
+
import { $, el, klappZeichen } from './basis.js';
|
|
10
|
+
import { tr } from './sprache.js';
|
|
11
|
+
import { hatPunkt, jsonVon, kindZustaende } from './werte.js';
|
|
12
|
+
import './entwurf.js';
|
|
13
|
+
import { zeichneErgebnis } from './ergebnis.js';
|
|
14
|
+
import { holeZweig, holeObjekt, uebernimmObjekt } from './objekte.js';
|
|
15
|
+
|
|
16
|
+
/* ================== MQTT-Geraete ==================
|
|
17
|
+
Ein Tasmota sendet nur nach stat und tele — cmnd ist sein Posteingang,
|
|
18
|
+
dort veroeffentlicht es nie. Ein frisch angelegtes Geraet hat deshalb
|
|
19
|
+
keine cmnd-Punkte, und ohne die greift keine Vorlage und kann kein
|
|
20
|
+
Alias schalten. Was das Geraet an Befehlen kennt, steht aber in dem,
|
|
21
|
+
was es sendet: die JSON-Schluessel in tele/STATE beziehungsweise
|
|
22
|
+
StatusSTS sind die Befehlsnamen. */
|
|
23
|
+
|
|
24
|
+
var TASMOTA_BEFEHLE = null; /* Geraetewissen, aus einer Datei */
|
|
25
|
+
|
|
26
|
+
export function ladeBefehlswissen(fertig) {
|
|
27
|
+
fetch('./geraetewissen/tasmota-befehle.json')
|
|
28
|
+
.then(function (r) { return r.json(); })
|
|
29
|
+
.then(function (j) { TASMOTA_BEFEHLE = j; fertig(); })
|
|
30
|
+
.catch(function () { TASMOTA_BEFEHLE = { befehle: {}, keineBefehle: [] }; fertig(); });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Die MQTT-Einstellung eines Objekts — sie sitzt je Objekt, nicht an der
|
|
34
|
+
Instanz. Dort steht das Thema und, entscheidend, ob gesendet werden
|
|
35
|
+
darf. */
|
|
36
|
+
export function mqttEinstellung(id) {
|
|
37
|
+
var o = S.objects[id];
|
|
38
|
+
if (!o || !o.common) { return null; }
|
|
39
|
+
var cu = o.common.custom || {};
|
|
40
|
+
var k = null;
|
|
41
|
+
Object.keys(cu).forEach(function (x) { if (!k && /^mqtt/.test(x)) { k = x; } });
|
|
42
|
+
if (!k) {
|
|
43
|
+
/* Manche Objekte tragen das Thema nur in native. */
|
|
44
|
+
if (o.native && o.native.topic) { return { instanz: null, topic: o.native.topic, publish: false }; }
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return { instanz: k, topic: cu[k].topic || (o.native || {}).topic || '', publish: !!cu[k].publish,
|
|
48
|
+
roh: cu[k] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Ist der Kanal ein MQTT-Geraet, und wie heisst sein Thema? Alles aus
|
|
52
|
+
dem Bestand abgelesen — auch die Reihenfolge von Praefix und Thema,
|
|
53
|
+
denn FullTopic ist frei einstellbar. */
|
|
54
|
+
export function mqttGeraet(kanal) {
|
|
55
|
+
var kinder = kindZustaende(kanal);
|
|
56
|
+
if (!kinder.length) { return null; }
|
|
57
|
+
var fund = null;
|
|
58
|
+
kinder.forEach(function (id) {
|
|
59
|
+
if (fund) { return; }
|
|
60
|
+
var e = mqttEinstellung(id);
|
|
61
|
+
if (!e || !e.topic) { return; }
|
|
62
|
+
var rel = id.slice(kanal.length + 1).split('.');
|
|
63
|
+
if (rel.length < 2) { return; }
|
|
64
|
+
var praefix = rel[0]; /* stat | tele | cmnd */
|
|
65
|
+
if (!/^(stat|tele|cmnd)$/i.test(praefix)) { return; }
|
|
66
|
+
var teile = e.topic.split('/');
|
|
67
|
+
var pi = -1;
|
|
68
|
+
teile.forEach(function (t, i) { if (t.toLowerCase() === praefix.toLowerCase()) { pi = i; } });
|
|
69
|
+
if (pi === -1) { return; }
|
|
70
|
+
/* Alles ausser Praefix und dem letzten Stueck ist die Basis. */
|
|
71
|
+
var ohne = teile.slice(0, teile.length - 1);
|
|
72
|
+
ohne.splice(pi, 1);
|
|
73
|
+
fund = {
|
|
74
|
+
instanz: e.instanz,
|
|
75
|
+
basis: ohne.join('/'),
|
|
76
|
+
praefixIndex: pi,
|
|
77
|
+
vorlage: teile.slice(0, teile.length - 1), /* Muster mit Praefix an Stelle pi */
|
|
78
|
+
beispiel: e.topic
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
return fund;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Das Thema fuer einen bestimmten Punkt bauen, mit der abgelesenen
|
|
85
|
+
Reihenfolge. */
|
|
86
|
+
function mqttThema(g, praefix, name) {
|
|
87
|
+
var t = g.vorlage.slice();
|
|
88
|
+
t[g.praefixIndex] = praefix;
|
|
89
|
+
return t.join('/') + '/' + name;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Welche Befehle kennt das Geraet? Aus dem, was schon da ist. */
|
|
93
|
+
export function tasmotaBefehle(kanal) {
|
|
94
|
+
var quellen = ['stat.STATUS11', 'tele.STATE', 'stat.RESULT'];
|
|
95
|
+
var aus = null, woher = null;
|
|
96
|
+
quellen.forEach(function (q) {
|
|
97
|
+
if (aus) { return; }
|
|
98
|
+
var id = hatPunkt(kanal, q);
|
|
99
|
+
if (!id) { return; }
|
|
100
|
+
var j = jsonVon(id);
|
|
101
|
+
if (!j) { return; }
|
|
102
|
+
if (j.StatusSTS) { j = j.StatusSTS; }
|
|
103
|
+
var k = Object.keys(j);
|
|
104
|
+
if (!k.length) { return; }
|
|
105
|
+
aus = j; woher = q;
|
|
106
|
+
});
|
|
107
|
+
if (!aus) { return null; }
|
|
108
|
+
|
|
109
|
+
var weg = (TASMOTA_BEFEHLE && TASMOTA_BEFEHLE.keineBefehle) || [];
|
|
110
|
+
var liste = [];
|
|
111
|
+
Object.keys(aus).forEach(function (f) {
|
|
112
|
+
if (weg.indexOf(f) > -1) { return; }
|
|
113
|
+
if (aus[f] !== null && typeof aus[f] === 'object' && !Array.isArray(aus[f])) { return; }
|
|
114
|
+
liste.push({ name: f, wert: aus[f] });
|
|
115
|
+
});
|
|
116
|
+
return { woher: woher, befehle: liste };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Was das Geraetewissen zu einem Befehl sagt — mit %N% fuer POWER1..n. */
|
|
120
|
+
export function befehlsWissen(name) {
|
|
121
|
+
var b = (TASMOTA_BEFEHLE && TASMOTA_BEFEHLE.befehle) || {};
|
|
122
|
+
if (b[name]) { return b[name]; }
|
|
123
|
+
var m = /^([A-Za-z_]+?)(\d{1,2})$/.exec(name);
|
|
124
|
+
if (m && b[m[1] + '%N%']) { return b[m[1] + '%N%']; }
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Meldet das Geraet Aenderungen sofort? Das haengt an SetOption59.
|
|
129
|
+
|
|
130
|
+
Die Bitmaske aus StatusLOG zu entziffern waere moeglich, ist aber
|
|
131
|
+
versionsabhaengig und fehleranfaellig — eine Pruefung, die luegt, ist
|
|
132
|
+
schlimmer als keine. Deshalb wird das Geraet gefragt: eine leere
|
|
133
|
+
Nutzlast an cmnd/SetOption59 ist eine Abfrage, und die Antwort steht
|
|
134
|
+
danach in stat/RESULT.
|
|
135
|
+
|
|
136
|
+
Gemessen: mit SetOption59 ON kam tele/STATE drei Sekunden nach einer
|
|
137
|
+
Scheme-Aenderung — also wirkt es auch fuer Licht-Befehle, nicht nur
|
|
138
|
+
fuer POWER. */
|
|
139
|
+
/* Steht die Antwort noch in stat.RESULT, ist sie frisch — dann wird sie
|
|
140
|
+
zugleich gemerkt. Sonst gilt die gemerkte.
|
|
141
|
+
|
|
142
|
+
Vorher wurde nur stat.RESULT gelesen, und dort ueberschreibt der
|
|
143
|
+
naechste Befehl die Antwort. Gemessen: nachfragen → „eingeschaltet",
|
|
144
|
+
einmal schalten → wieder „unbekannt", obwohl sich am Geraet nichts
|
|
145
|
+
geaendert hatte. Der Befund war praktisch nie stabil zu sehen. */
|
|
146
|
+
export function sofortRueckmeldung(kanal) {
|
|
147
|
+
var id = hatPunkt(kanal, 'stat.RESULT');
|
|
148
|
+
if (id) {
|
|
149
|
+
var j = jsonVon(id);
|
|
150
|
+
if (j && j.SetOption59 !== undefined) {
|
|
151
|
+
var w = String(j.SetOption59).toUpperCase() === 'ON';
|
|
152
|
+
merkeSo59(kanal, w);
|
|
153
|
+
return w;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
var g = gemerktesSo59(kanal);
|
|
157
|
+
return g ? g.wert : null; /* unbekannt, nicht "aus" */
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Gemerkt wird am cmnd-Punkt selbst — dort, wo die Frage gestellt wurde.
|
|
161
|
+
Kein eigenes Objekt, und beim Aufraeumen verschwindet es mit. */
|
|
162
|
+
export function gemerktesSo59(kanal) {
|
|
163
|
+
var id = hatPunkt(kanal, 'cmnd.SetOption59');
|
|
164
|
+
var n = id && S.objects[id] && S.objects[id].native;
|
|
165
|
+
if (!n || n.so59 === undefined) { return null; }
|
|
166
|
+
return { wert: !!n.so59, zeit: n.so59Zeit || 0 };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function merkeSo59(kanal, wert) {
|
|
170
|
+
var id = hatPunkt(kanal, 'cmnd.SetOption59');
|
|
171
|
+
if (!id || !S.objects[id]) { return; }
|
|
172
|
+
var n = S.objects[id].native || (S.objects[id].native = {});
|
|
173
|
+
if (n.so59 === wert) { return; }
|
|
174
|
+
n.so59 = wert;
|
|
175
|
+
n.so59Zeit = neueZeit();
|
|
176
|
+
var kopie = JSON.parse(JSON.stringify(S.objects[id]));
|
|
177
|
+
socket.emit('setObject', id, kopie, function () {});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Zeitstempel ohne Date.now — das Skript laeuft im Browser, hier ist es
|
|
181
|
+
unbedenklich, aber einheitlich an einer Stelle. */
|
|
182
|
+
function neueZeit() { return new Date().getTime(); }
|
|
183
|
+
|
|
184
|
+
function so59Alter(kanal) {
|
|
185
|
+
var g = gemerktesSo59(kanal);
|
|
186
|
+
if (!g || !g.zeit) { return ''; }
|
|
187
|
+
var min = Math.round((neueZeit() - g.zeit) / 60000);
|
|
188
|
+
if (min < 1) { return tr('mq.justAsked'); }
|
|
189
|
+
if (min < 60) { return tr('mq.askedMinutes', min); }
|
|
190
|
+
var std = Math.round(min / 60);
|
|
191
|
+
if (std < 48) { return tr('mq.askedHours', std); }
|
|
192
|
+
return tr('mq.askedDays', Math.round(std / 24));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Was fehlt dem Geraet an cmnd-Punkten, und welche koennen nicht senden? */
|
|
196
|
+
export function mqttLage(kanal) {
|
|
197
|
+
var g = mqttGeraet(kanal);
|
|
198
|
+
if (!g) { return null; }
|
|
199
|
+
var b = tasmotaBefehle(kanal);
|
|
200
|
+
var fehlen = [], stumm = [], da = [];
|
|
201
|
+
if (b) {
|
|
202
|
+
b.befehle.forEach(function (x) {
|
|
203
|
+
var id = hatPunkt(kanal, 'cmnd.' + x.name);
|
|
204
|
+
if (!id) { fehlen.push(x); return; }
|
|
205
|
+
var m = mqttEinstellung(id);
|
|
206
|
+
if (m && !m.publish) { stumm.push({ name: x.name, id: id }); } else { da.push(x.name); }
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/* Auch cmnd-Punkte, die es schon gibt, ohne dass sie im Zustand
|
|
210
|
+
auftauchen — Status etwa. Stumm sind sie genauso. */
|
|
211
|
+
kindZustaende(kanal).forEach(function (id) {
|
|
212
|
+
var rel = id.slice(kanal.length + 1);
|
|
213
|
+
if (rel.indexOf('cmnd.') !== 0) { return; }
|
|
214
|
+
var name = rel.slice(5);
|
|
215
|
+
if (stumm.some(function (x) { return x.name === name; })) { return; }
|
|
216
|
+
if (da.indexOf(name) > -1) { return; }
|
|
217
|
+
var m = mqttEinstellung(id);
|
|
218
|
+
if (m && !m.publish) { stumm.push({ name: name, id: id }); }
|
|
219
|
+
});
|
|
220
|
+
return { geraet: g, befehle: b, fehlen: fehlen, stumm: stumm, da: da,
|
|
221
|
+
sofort: sofortRueckmeldung(kanal) };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function mqttKarte(host, kanal) {
|
|
225
|
+
var l = mqttLage(kanal);
|
|
226
|
+
if (!l) { return; }
|
|
227
|
+
mqttStand = l;
|
|
228
|
+
/* Ohne den Kanal baut der offene Dialog seine IDs aus „undefined" und
|
|
229
|
+
legt die Punkte an einer Stelle an, die es nicht gibt. Der Dialog
|
|
230
|
+
setzte ihn, das Neuzeichnen der Karte nicht — und seit die Werkbank
|
|
231
|
+
auf Objektaenderungen horcht, wird waehrend eines offenen Dialogs
|
|
232
|
+
oft neu gezeichnet. */
|
|
233
|
+
mqttStand.kanal = kanal;
|
|
234
|
+
|
|
235
|
+
var k = el('div', 'card');
|
|
236
|
+
k.style.marginBottom = '11px';
|
|
237
|
+
var ch = el('div', 'ch');
|
|
238
|
+
ch.style.cursor = 'pointer';
|
|
239
|
+
ch.appendChild(el('span', 'typ', tr('mq.title')));
|
|
240
|
+
/* Der Basispfad ist eine Angabe, keine Statusmarke - als Chip stand
|
|
241
|
+
er wie „fehlt" daneben und wurde obendrein grossgeschrieben. */
|
|
242
|
+
var th = el('span', 'leise');
|
|
243
|
+
th.textContent = l.geraet.basis;
|
|
244
|
+
th.style.fontFamily = 'var(--mono)';
|
|
245
|
+
th.style.fontSize = '11px';
|
|
246
|
+
ch.appendChild(th);
|
|
247
|
+
if (l.fehlen.length) { ch.appendChild(el('span', 'chip warn', tr('mq.missing', l.fehlen.length))); }
|
|
248
|
+
if (l.stumm.length) { ch.appendChild(el('span', 'chip bad', tr('mq.mute', l.stumm.length))); }
|
|
249
|
+
if (l.sofort === false) { ch.appendChild(el('span', 'chip warn', tr('mq.instantOffShort'))); }
|
|
250
|
+
ch.insertBefore(klappZeichen(mqttAuf), ch.firstChild);
|
|
251
|
+
ch.classList.add('klappbar');
|
|
252
|
+
ch.addEventListener('click', function () { mqttAuf = !mqttAuf; zeichneErgebnis(); });
|
|
253
|
+
k.appendChild(ch);
|
|
254
|
+
|
|
255
|
+
if (!mqttAuf) {
|
|
256
|
+
/* Zugeklappt bleibt die Warnung in der Kopfzeile stehen — man
|
|
257
|
+
sieht, dass etwas ist, ohne die ganze Liste vor sich zu haben. */
|
|
258
|
+
host.appendChild(k);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
var b = el('div');
|
|
263
|
+
b.style.padding = '9px 12px 11px';
|
|
264
|
+
var einl = el('div', 'hint');
|
|
265
|
+
einl.style.marginBottom = '9px';
|
|
266
|
+
einl.textContent = tr('mq.intro');
|
|
267
|
+
b.appendChild(einl);
|
|
268
|
+
|
|
269
|
+
if (l.befehle) {
|
|
270
|
+
b.appendChild(el('div', 'hint', tr('mq.commandsFrom', l.befehle.woher)));
|
|
271
|
+
|
|
272
|
+
/* Je Befehl eine Zeile mit seinem Zustand. Vorher stand hier eine
|
|
273
|
+
Aufzaehlung und darueber „2 fehlen, 1 kann nicht senden" — man
|
|
274
|
+
sah die Zahlen, aber nicht, welcher Befehl welcher Fall ist. */
|
|
275
|
+
var zustand = {};
|
|
276
|
+
l.fehlen.forEach(function (x) { zustand[x.name] = 'fehlt'; });
|
|
277
|
+
l.stumm.forEach(function (x) { zustand[x.name] = 'stumm'; });
|
|
278
|
+
l.da.forEach(function (n) { zustand[n] = 'da'; });
|
|
279
|
+
|
|
280
|
+
var reihen = l.befehle.befehle.map(function (x) { return x.name; });
|
|
281
|
+
/* cmnd-Punkte, die es gibt, ohne im Zustand aufzutauchen — Status
|
|
282
|
+
etwa. Die gehoeren mit in die Liste, sonst fehlt in der Zeile,
|
|
283
|
+
was die Zahl oben mitzaehlt. */
|
|
284
|
+
Object.keys(zustand).forEach(function (n) {
|
|
285
|
+
if (reihen.indexOf(n) === -1) { reihen.push(n); }
|
|
286
|
+
});
|
|
287
|
+
/* Nach Namen, nicht nach Zustand. Sortiert man nach Zustand,
|
|
288
|
+
rutscht jede Zeile beim Anlegen ans Ende — und wer mehrere
|
|
289
|
+
nacheinander anlegt, klickt jedes Mal woanders hin. Welcher Fall
|
|
290
|
+
vorliegt, sagt ohnehin die Spalte daneben. */
|
|
291
|
+
reihen.sort(function (x, y) { return x < y ? -1 : (x > y ? 1 : 0); });
|
|
292
|
+
|
|
293
|
+
var tab = el('div');
|
|
294
|
+
tab.style.marginTop = '7px';
|
|
295
|
+
reihen.forEach(function (n) {
|
|
296
|
+
var r = el('div', 'slot');
|
|
297
|
+
/* Feste Spalten: die Pillen fuellen ihre Spalte (alle gleich
|
|
298
|
+
breit, sauber untereinander), die Knopfspalte ist breit genug
|
|
299
|
+
fuer „senden erlauben" in einer Zeile (Ricardo, 25.08.2026). */
|
|
300
|
+
r.style.gridTemplateColumns = '1fr 160px 132px';
|
|
301
|
+
r.style.padding = '3px 0';
|
|
302
|
+
var nm = el('span', 'sn', 'cmnd.' + n);
|
|
303
|
+
nm.style.fontFamily = 'var(--mono)';
|
|
304
|
+
nm.style.fontWeight = '400';
|
|
305
|
+
r.appendChild(nm);
|
|
306
|
+
var z = zustand[n];
|
|
307
|
+
var c = el('span', 'mchip');
|
|
308
|
+
if (z === 'fehlt') { c.appendChild(el('span', 'chip warn', tr('mq.stMissing'))); }
|
|
309
|
+
else if (z === 'stumm') { c.appendChild(el('span', 'chip bad', tr('mq.stMute'))); }
|
|
310
|
+
else { c.appendChild(el('span', 'chip ok', tr('mq.stFine'))); }
|
|
311
|
+
r.appendChild(c);
|
|
312
|
+
|
|
313
|
+
/* Handeln, wo es etwas zu tun gibt — je Zeile einzeln, ohne
|
|
314
|
+
Umweg ueber den Sammeldialog. */
|
|
315
|
+
var h = el('span', 'mknopf');
|
|
316
|
+
if (z === 'fehlt') {
|
|
317
|
+
var bn = el('button', 'btn schmal', tr('mq.rowCreate'));
|
|
318
|
+
bn.addEventListener('click', function () { mqttEinzeln(kanal, n, 'neu', bn); });
|
|
319
|
+
h.appendChild(bn);
|
|
320
|
+
} else if (z === 'stumm') {
|
|
321
|
+
var bs2 = el('button', 'btn schmal', tr('mq.rowAllow'));
|
|
322
|
+
bs2.addEventListener('click', function () { mqttEinzeln(kanal, n, 'frei', bs2); });
|
|
323
|
+
h.appendChild(bs2);
|
|
324
|
+
}
|
|
325
|
+
r.appendChild(h);
|
|
326
|
+
tab.appendChild(r);
|
|
327
|
+
});
|
|
328
|
+
b.appendChild(tab);
|
|
329
|
+
} else {
|
|
330
|
+
b.appendChild(el('div', 'hint', tr('mq.nothingYet')));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
var leiste = el('div');
|
|
334
|
+
leiste.style.marginTop = '10px';
|
|
335
|
+
leiste.style.display = 'flex';
|
|
336
|
+
leiste.style.gap = '8px';
|
|
337
|
+
leiste.style.flexWrap = 'wrap';
|
|
338
|
+
leiste.style.alignItems = 'center';
|
|
339
|
+
|
|
340
|
+
if (l.fehlen.length || l.stumm.length) {
|
|
341
|
+
var ba = el('button', 'btn primary', tr('mq.fix'));
|
|
342
|
+
ba.addEventListener('click', function () { zeigeMqttDialog(kanal); });
|
|
343
|
+
leiste.appendChild(ba);
|
|
344
|
+
}
|
|
345
|
+
b.appendChild(leiste);
|
|
346
|
+
|
|
347
|
+
/* Sofortige Rueckmeldung — Zustand und, wenn noetig, der Schalter. */
|
|
348
|
+
var sk = el('div');
|
|
349
|
+
sk.style.marginTop = '11px';
|
|
350
|
+
sk.style.paddingTop = '10px';
|
|
351
|
+
sk.style.borderTop = '1px solid var(--line)';
|
|
352
|
+
var sz = el('div');
|
|
353
|
+
sz.style.fontSize = '11.5px';
|
|
354
|
+
sz.appendChild(el('b', null, tr('mq.instant') + ' '));
|
|
355
|
+
if (l.sofort === true) {
|
|
356
|
+
sz.appendChild(el('span', 'chip ok', tr('mq.instantOn')));
|
|
357
|
+
} else if (l.sofort === false) {
|
|
358
|
+
sz.appendChild(el('span', 'chip warn', tr('mq.instantOff')));
|
|
359
|
+
} else {
|
|
360
|
+
sz.appendChild(el('span', 'chip mut', tr('mq.instantUnknown')));
|
|
361
|
+
}
|
|
362
|
+
sk.appendChild(sz);
|
|
363
|
+
if (so59Stumm[kanal] && l.sofort === null) {
|
|
364
|
+
var st59 = el('div', 'hint');
|
|
365
|
+
st59.style.marginTop = '4px';
|
|
366
|
+
st59.style.color = 'var(--warn)';
|
|
367
|
+
st59.textContent = tr('mq.askNoAnswer');
|
|
368
|
+
sk.appendChild(st59);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
var sl = el('div');
|
|
372
|
+
sl.style.marginTop = '7px';
|
|
373
|
+
sl.style.display = 'flex';
|
|
374
|
+
sl.style.gap = '8px';
|
|
375
|
+
sl.style.flexWrap = 'wrap';
|
|
376
|
+
if (l.sofort !== true) {
|
|
377
|
+
/* Beide gleich gross - schmal neben normal las sich wie zwei
|
|
378
|
+
verschiedene Schriften (Ricardo, 25.08.2026). */
|
|
379
|
+
var lauf = so59Laeuft[kanal];
|
|
380
|
+
var bp = el('button', 'btn schmal' + (lauf === 'pruefen' ? ' laedt' : ''),
|
|
381
|
+
lauf === 'pruefen' ? tr('mq.instantChecking') : tr('mq.instantCheck'));
|
|
382
|
+
bp.addEventListener('click', function () { so59(kanal, bp, ''); });
|
|
383
|
+
sl.appendChild(bp);
|
|
384
|
+
var be = el('button', 'btn schmal' + (lauf === 'einschalten' ? ' laedt' : ''),
|
|
385
|
+
lauf === 'einschalten' ? tr('mq.instantEnabling') : tr('mq.instantEnable'));
|
|
386
|
+
be.addEventListener('click', function () { so59(kanal, be, '1'); });
|
|
387
|
+
sl.appendChild(be);
|
|
388
|
+
if (lauf) { bp.disabled = true; be.disabled = true; }
|
|
389
|
+
}
|
|
390
|
+
sk.appendChild(sl);
|
|
391
|
+
var hinweis = l.sofort === true ? tr('mq.instantHintOn')
|
|
392
|
+
: (l.sofort === false ? tr('mq.instantHintOff') : tr('mq.instantHintUnknown'));
|
|
393
|
+
/* Woher der Befund stammt, gehoert dazu — sonst weiss niemand, ob er
|
|
394
|
+
von eben ist oder von vorgestern. */
|
|
395
|
+
var alter = (l.sofort === null) ? '' : so59Alter(kanal);
|
|
396
|
+
sk.appendChild(el('div', 'hint', hinweis + (alter ? ' · ' + alter : '')));
|
|
397
|
+
b.appendChild(sk);
|
|
398
|
+
|
|
399
|
+
/* Nachfragen liefert dasselbe wie tele/STATE — gemessen, Feld fuer
|
|
400
|
+
Feld. Der Knopf erscheint deshalb nur, wenn es etwas zu holen gibt:
|
|
401
|
+
gar nichts gelesen, oder nur stat.RESULT, das meist bloss die
|
|
402
|
+
Antwort auf den letzten Befehl enthaelt und nicht den ganzen
|
|
403
|
+
Zustand. Steht die Liste vollstaendig da, gibt es den Knopf nicht —
|
|
404
|
+
er koennte nichts hinzufuegen. */
|
|
405
|
+
var luecke = !l.befehle || l.befehle.woher === 'stat.RESULT';
|
|
406
|
+
if (luecke) {
|
|
407
|
+
var kasten = el('div');
|
|
408
|
+
kasten.style.marginTop = '11px';
|
|
409
|
+
kasten.style.paddingTop = '10px';
|
|
410
|
+
kasten.style.borderTop = '1px solid var(--line)';
|
|
411
|
+
var bf = el('button', 'btn' + (l.befehle ? '' : ' primary')
|
|
412
|
+
+ (abfrageLaeuft[kanal] ? ' laedt' : ''),
|
|
413
|
+
abfrageLaeuft[kanal] ? tr('mq.asking') : tr('mq.ask'));
|
|
414
|
+
if (abfrageLaeuft[kanal]) { bf.disabled = true; }
|
|
415
|
+
bf.addEventListener('click', function () { mqttFragen(kanal, bf); });
|
|
416
|
+
kasten.appendChild(bf);
|
|
417
|
+
var warum = el('div', 'hint');
|
|
418
|
+
warum.style.marginTop = '6px';
|
|
419
|
+
warum.textContent = (l.befehle ? tr('mq.onlyResult') : tr('mq.neverReported')) +
|
|
420
|
+
' ' + tr('mq.askHint');
|
|
421
|
+
kasten.appendChild(warum);
|
|
422
|
+
if (mqttAbfrage[kanal] !== undefined) {
|
|
423
|
+
var ah = el('div', 'hint');
|
|
424
|
+
ah.style.marginTop = '6px';
|
|
425
|
+
var ab = mqttAbfrage[kanal];
|
|
426
|
+
if (ab && ab.fehler) {
|
|
427
|
+
ah.textContent = tr('mq.askError', ab.fehler);
|
|
428
|
+
ah.style.color = 'var(--bad)';
|
|
429
|
+
} else {
|
|
430
|
+
ah.textContent = ab ? tr('mq.askGotAnswer', ab) : tr('mq.askNoAnswer');
|
|
431
|
+
ah.style.color = ab ? 'var(--ok)' : 'var(--warn)';
|
|
432
|
+
}
|
|
433
|
+
kasten.appendChild(ah);
|
|
434
|
+
}
|
|
435
|
+
b.appendChild(kasten);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
k.appendChild(b);
|
|
439
|
+
host.appendChild(k);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
var mqttStand = null;
|
|
443
|
+
var mqttAuswahl = {};
|
|
444
|
+
/* Bleibt ueber das Neuzeichnen hinweg stehen — sonst klappte die Karte
|
|
445
|
+
nach jeder Handlung wieder zu. */
|
|
446
|
+
/* Was die letzte Befehlsabfrage ergeben hat, je Geraet. Ohne diese
|
|
447
|
+
Notiz klickte man „Befehle abfragen" und sah hinterher nichts —
|
|
448
|
+
weder Erfolg noch Fehlschlag. */
|
|
449
|
+
var mqttAbfrage = {};
|
|
450
|
+
|
|
451
|
+
/* Ein abgelehntes setObject/setState darf nicht stumm bleiben: vorher
|
|
452
|
+
blieb der Knopf ausgegraut auf „frage nach …“ stehen, und man sah
|
|
453
|
+
weder Erfolg noch Fehlschlag. Der Fehler landet im Abfrage-Vermerk
|
|
454
|
+
der Karte und ueberlebt so auch das Neuzeichnen. */
|
|
455
|
+
/* Hat die letzte SetOption59-Nachfrage eine Antwort gebracht? Ohne den
|
|
456
|
+
Merker blieb bei einem stummen Geraet einfach die Marke UNBEKANNT
|
|
457
|
+
stehen, und der Klick sah aus wie ein Klick ins Leere (Ricardos
|
|
458
|
+
RGB-Licht, offline). */
|
|
459
|
+
var so59Stumm = {};
|
|
460
|
+
|
|
461
|
+
/* Laeuft gerade eine Nachfrage? Der Klick sperrt zwar den Knopf, aber
|
|
462
|
+
das Anlegen des Abfragepunkts loest selbst ein Neuzeichnen aus - und
|
|
463
|
+
das baute einen frischen, aktiven Knopf, waehrend die Kette noch
|
|
464
|
+
lief. Erst kam der Knopf wieder, Sekunden spaeter die Antwortzeile:
|
|
465
|
+
genau die Verwirrung. Der Laufzustand haelt je Kanal fest, was
|
|
466
|
+
gedrueckt wurde, und das Zeichnen sperrt entsprechend. */
|
|
467
|
+
var so59Laeuft = {};
|
|
468
|
+
|
|
469
|
+
/* Dasselbe fuer die Status-Abfrage. */
|
|
470
|
+
var abfrageLaeuft = {};
|
|
471
|
+
|
|
472
|
+
function mqttFehlgeschlagen(kanal, err) {
|
|
473
|
+
delete abfrageLaeuft[kanal];
|
|
474
|
+
delete so59Laeuft[kanal];
|
|
475
|
+
mqttAbfrage[kanal] = { fehler: String(err) };
|
|
476
|
+
zeichneErgebnis();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* Ueberlebt das Neuzeichnen — wie bei der MQTT-Karte. */
|
|
480
|
+
|
|
481
|
+
var mqttAuf = false;
|
|
482
|
+
|
|
483
|
+
/* Leere Nutzlast fragt nur, "1" schaltet ein. Beides ueber denselben
|
|
484
|
+
Weg, damit es nur eine Stelle gibt, die das Objekt anlegt. */
|
|
485
|
+
function so59(kanal, knopf, wert) {
|
|
486
|
+
var g = mqttGeraet(kanal);
|
|
487
|
+
if (!g) { return; }
|
|
488
|
+
so59Laeuft[kanal] = wert ? 'einschalten' : 'pruefen';
|
|
489
|
+
delete so59Stumm[kanal];
|
|
490
|
+
/* Sofort neu zeichnen: erst damit sperren sich BEIDE Knoepfe und die
|
|
491
|
+
alte Antwortzeile verschwindet - vorher passierte das erst mit dem
|
|
492
|
+
naechsten Nachzieher, Sekunden spaeter (Ricardo, 25.08.2026). */
|
|
493
|
+
zeichneErgebnis();
|
|
494
|
+
var thema = mqttThema(g, 'cmnd', 'SetOption59');
|
|
495
|
+
var id = hatPunkt(kanal, 'cmnd.SetOption59') || (kanal + '.cmnd.SetOption59');
|
|
496
|
+
var obj = {
|
|
497
|
+
type: 'state',
|
|
498
|
+
common: { name: 'SetOption59', type: 'mixed', read: true, write: true, role: 'text',
|
|
499
|
+
desc: tr('mq.createdBy'), custom: mqttCustom(g, thema) },
|
|
500
|
+
native: { topic: thema }
|
|
501
|
+
};
|
|
502
|
+
socket.emit('setObject', id, obj, function (err) {
|
|
503
|
+
if (err) { delete so59Laeuft[kanal]; return mqttFehlgeschlagen(kanal, err); }
|
|
504
|
+
setTimeout(function () {
|
|
505
|
+
socket.emit('setState', id, { val: wert, ack: false }, function (err2) {
|
|
506
|
+
if (err2) { delete so59Laeuft[kanal]; return mqttFehlgeschlagen(kanal, err2); }
|
|
507
|
+
setTimeout(function () {
|
|
508
|
+
socket.emit('setState', id, { val: wert, ack: false }, function () {
|
|
509
|
+
setTimeout(function () {
|
|
510
|
+
holeZweig(kanal + '.', function () {
|
|
511
|
+
delete so59Laeuft[kanal];
|
|
512
|
+
so59Stumm[kanal] = (sofortRueckmeldung(kanal) === null);
|
|
513
|
+
zeichneErgebnis();
|
|
514
|
+
});
|
|
515
|
+
}, 4000);
|
|
516
|
+
});
|
|
517
|
+
}, 4000);
|
|
518
|
+
});
|
|
519
|
+
}, 2000);
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/* Status 11 aktiv anfordern. Eine Abfrage — sie schaltet nichts.
|
|
524
|
+
Antwortet das Geraet nicht, muss das dranstehen: vorher klickte man
|
|
525
|
+
und es passierte sichtbar gar nichts. */
|
|
526
|
+
function mqttFragen(kanal, _knopf) {
|
|
527
|
+
var g = mqttGeraet(kanal);
|
|
528
|
+
if (!g) { return; }
|
|
529
|
+
abfrageLaeuft[kanal] = true;
|
|
530
|
+
delete mqttAbfrage[kanal];
|
|
531
|
+
zeichneErgebnis();
|
|
532
|
+
var thema = mqttThema(g, 'cmnd', 'Status');
|
|
533
|
+
var id = kanal + '.cmnd.Status';
|
|
534
|
+
var vorhanden = hatPunkt(kanal, 'cmnd.Status');
|
|
535
|
+
if (vorhanden) { id = vorhanden; }
|
|
536
|
+
|
|
537
|
+
var obj = {
|
|
538
|
+
type: 'state',
|
|
539
|
+
common: {
|
|
540
|
+
name: 'Status', type: 'mixed', read: true, write: true, role: 'text',
|
|
541
|
+
desc: tr('mq.createdBy'),
|
|
542
|
+
custom: mqttCustom(g, thema)
|
|
543
|
+
},
|
|
544
|
+
native: { topic: thema }
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
socket.emit('setObject', id, obj, function (err) {
|
|
548
|
+
if (err) { return mqttFehlgeschlagen(kanal, err); }
|
|
549
|
+
/* Beim ersten Schreiben nach dem Anlegen legt der mqtt-client nur
|
|
550
|
+
die Antwortobjekte an und verwirft die Werte — gemessen. Deshalb
|
|
551
|
+
zweimal fragen. */
|
|
552
|
+
setTimeout(function () {
|
|
553
|
+
socket.emit('setState', id, { val: '11', ack: false }, function (err2) {
|
|
554
|
+
if (err2) { return mqttFehlgeschlagen(kanal, err2); }
|
|
555
|
+
setTimeout(function () {
|
|
556
|
+
/* Status 5 gleich mit: dort steht die IP, und die fehlt an
|
|
557
|
+
jedem Geraet, das seit dem Einbinden nicht neu gestartet
|
|
558
|
+
ist — tele/INFO2 kommt nur beim Start. */
|
|
559
|
+
socket.emit('setState', id, { val: '5', ack: false }, function () {});
|
|
560
|
+
socket.emit('setState', id, { val: '11', ack: false }, function () {
|
|
561
|
+
setTimeout(function () {
|
|
562
|
+
/* Kam eine Antwort? Nur dann hat die Abfrage etwas
|
|
563
|
+
gebracht. Ohne diese Rueckmeldung klickte man und sah
|
|
564
|
+
nichts — weder Erfolg noch Fehlschlag. */
|
|
565
|
+
/* Das Ergebnis gehoert in den Zustand, nicht ins DOM:
|
|
566
|
+
das Nachladen zeichnet die Karte neu, und ein Hinweis,
|
|
567
|
+
den man an den Knopf gehaengt hat, waere danach
|
|
568
|
+
verwaist. */
|
|
569
|
+
holeZweig(kanal + '.', function () {
|
|
570
|
+
delete abfrageLaeuft[kanal];
|
|
571
|
+
var bb = tasmotaBefehle(kanal);
|
|
572
|
+
mqttAbfrage[kanal] = (bb && bb.befehle.length) ? bb.befehle.length : 0;
|
|
573
|
+
zeichneErgebnis();
|
|
574
|
+
});
|
|
575
|
+
}, 5000);
|
|
576
|
+
});
|
|
577
|
+
}, 5000);
|
|
578
|
+
});
|
|
579
|
+
}, 2000);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function mqttCustom(g, thema) {
|
|
584
|
+
var c = {};
|
|
585
|
+
var inst = g.instanz || 'mqtt-client.0';
|
|
586
|
+
c[inst] = {
|
|
587
|
+
enabled: true, topic: thema,
|
|
588
|
+
publish: true, pubChangesOnly: false, pubAsObject: false,
|
|
589
|
+
qos: 0, retain: false,
|
|
590
|
+
subscribe: true, subChangesOnly: false, subAsObject: false, subQos: 0,
|
|
591
|
+
setAck: true
|
|
592
|
+
};
|
|
593
|
+
return c;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/* Einen einzelnen Punkt anlegen oder ihm das Senden erlauben. Klein
|
|
597
|
+
genug, dass ein Trockenlauf nur im Weg staende — die Zeile sagt ja,
|
|
598
|
+
was passiert. Der Sammeldialog bleibt fuer mehrere auf einmal. */
|
|
599
|
+
/* Denselben Punkt anlegen, aber ohne Knopf — fuer den Trockenlauf, der
|
|
600
|
+
mehrere auf einmal nachzieht. */
|
|
601
|
+
/* Ohne eigenes Nachladen und ohne Ruecklesen: was gerade geschrieben
|
|
602
|
+
wurde, ist bekannt — es kommt direkt in S.objects. Ueber getObjectView
|
|
603
|
+
zurueckzulesen ist ein Wettlauf: die Sicht hinkt dem frisch
|
|
604
|
+
geschriebenen Objekt einen Wimpernschlag hinterher, und der
|
|
605
|
+
Trockenlauf sperrte weiter wegen eines Punktes, den es laengst gab. */
|
|
606
|
+
export function mqttEinzelnStill(kanal, name, fertig) {
|
|
607
|
+
var bau = mqttPunktBauen(kanal, name, 'neu');
|
|
608
|
+
if (!bau) { return fertig && fertig(); }
|
|
609
|
+
socket.emit('setObject', bau.id, bau.obj, function () {
|
|
610
|
+
uebernimmObjekt(bau.id, bau.obj);
|
|
611
|
+
if (fertig) { fertig(); }
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function mqttPunktBauen(kanal, name, was) {
|
|
616
|
+
var g = mqttGeraet(kanal);
|
|
617
|
+
if (!g) { return null; }
|
|
618
|
+
var id, obj;
|
|
619
|
+
if (was === 'frei') {
|
|
620
|
+
id = hatPunkt(kanal, 'cmnd.' + name);
|
|
621
|
+
var alt0 = S.objects[id];
|
|
622
|
+
if (!alt0) { return null; }
|
|
623
|
+
obj = JSON.parse(JSON.stringify(alt0));
|
|
624
|
+
var cu0 = obj.common.custom || {};
|
|
625
|
+
Object.keys(cu0).forEach(function (i) { if (/^mqtt/.test(i)) { cu0[i].publish = true; } });
|
|
626
|
+
obj.common.custom = cu0;
|
|
627
|
+
} else {
|
|
628
|
+
var w0 = befehlsWissen(name) || {};
|
|
629
|
+
var thema0 = mqttThema(g, 'cmnd', name);
|
|
630
|
+
id = kanal + '.cmnd.' + name;
|
|
631
|
+
var common0 = {
|
|
632
|
+
name: name,
|
|
633
|
+
role: w0.rolle || 'state',
|
|
634
|
+
type: w0.typ || 'mixed',
|
|
635
|
+
read: true, write: true,
|
|
636
|
+
desc: tr('mq.createdBy'),
|
|
637
|
+
custom: mqttCustom(g, thema0)
|
|
638
|
+
};
|
|
639
|
+
if (w0.einheit) { common0.unit = w0.einheit; }
|
|
640
|
+
if (w0.werteliste) { common0.states = w0.werteliste; }
|
|
641
|
+
obj = { type: 'state', common: common0, native: { topic: thema0 } };
|
|
642
|
+
}
|
|
643
|
+
return { id: id, obj: obj };
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function mqttEinzeln(kanal, name, was, knopf) {
|
|
647
|
+
var g = mqttGeraet(kanal);
|
|
648
|
+
if (!g) { return; }
|
|
649
|
+
knopf.disabled = true;
|
|
650
|
+
knopf.textContent = tr('mq.rowWorking');
|
|
651
|
+
|
|
652
|
+
var id, obj;
|
|
653
|
+
if (was === 'frei') {
|
|
654
|
+
id = hatPunkt(kanal, 'cmnd.' + name);
|
|
655
|
+
var alt = S.objects[id];
|
|
656
|
+
if (!alt) { return; }
|
|
657
|
+
obj = JSON.parse(JSON.stringify(alt));
|
|
658
|
+
var cu = obj.common.custom || {};
|
|
659
|
+
Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i)) { cu[i].publish = true; } });
|
|
660
|
+
obj.common.custom = cu;
|
|
661
|
+
} else {
|
|
662
|
+
var w = befehlsWissen(name) || {};
|
|
663
|
+
var thema = mqttThema(g, 'cmnd', name);
|
|
664
|
+
id = kanal + '.cmnd.' + name;
|
|
665
|
+
var common = {
|
|
666
|
+
name: name,
|
|
667
|
+
role: w.rolle || 'state',
|
|
668
|
+
type: w.typ || 'mixed',
|
|
669
|
+
read: true, write: true,
|
|
670
|
+
desc: tr('mq.createdBy'),
|
|
671
|
+
custom: mqttCustom(g, thema)
|
|
672
|
+
};
|
|
673
|
+
if (w.einheit) { common.unit = w.einheit; }
|
|
674
|
+
if (w.werteliste) { common.states = w.werteliste; }
|
|
675
|
+
obj = { type: 'state', common: common, native: { topic: thema } };
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
socket.emit('setObject', id, obj, function (err) {
|
|
679
|
+
if (err) {
|
|
680
|
+
knopf.disabled = false;
|
|
681
|
+
knopf.textContent = tr('write.retry');
|
|
682
|
+
knopf.title = String(err);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
holeObjekt(id);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export function zeigeMqttDialog(kanal) {
|
|
690
|
+
var l = mqttLage(kanal);
|
|
691
|
+
if (!l) { return; }
|
|
692
|
+
mqttStand = l;
|
|
693
|
+
mqttStand.kanal = kanal;
|
|
694
|
+
mqttAuswahl = {};
|
|
695
|
+
l.fehlen.forEach(function (x) {
|
|
696
|
+
var w = befehlsWissen(x.name);
|
|
697
|
+
/* Vorgehakt nur, was eine Geraetefunktion ist: bekannt, keine
|
|
698
|
+
Dopplung und keine blosse Einstellung des Geraets. */
|
|
699
|
+
mqttAuswahl['neu:' + x.name] = !!w && !w.vorgabeAus && !istDopplung(x.name, l);
|
|
700
|
+
});
|
|
701
|
+
l.stumm.forEach(function (x) { mqttAuswahl['fix:' + x.name] = true; });
|
|
702
|
+
zeichneMqttDialog();
|
|
703
|
+
$('#dlg-mqtt').showModal();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* HSBColor und Channel beschreiben dieselbe Farbe wie Color. Alle drei
|
|
707
|
+
anzulegen ergibt drei Punkte, die sich gegenseitig ueberschreiben. */
|
|
708
|
+
function istDopplung(name, l) {
|
|
709
|
+
var w = befehlsWissen(name);
|
|
710
|
+
if (!w || !w.dopplung) { return false; }
|
|
711
|
+
return l.fehlen.some(function (x) { return x.name === w.dopplung; }) ||
|
|
712
|
+
l.da.indexOf(w.dopplung) > -1;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function zeichneMqttDialog() {
|
|
716
|
+
var l = mqttStand;
|
|
717
|
+
var body = $('#mqtt-body');
|
|
718
|
+
body.textContent = '';
|
|
719
|
+
|
|
720
|
+
var kopf = el('div');
|
|
721
|
+
kopf.style.marginBottom = '11px';
|
|
722
|
+
kopf.style.fontSize = '12.5px';
|
|
723
|
+
kopf.appendChild(el('b', null, l.geraet.basis));
|
|
724
|
+
if (l.befehle) {
|
|
725
|
+
kopf.appendChild(el('div', 'hint', tr('mq.commandsFrom', l.befehle.woher)));
|
|
726
|
+
}
|
|
727
|
+
body.appendChild(kopf);
|
|
728
|
+
|
|
729
|
+
if (l.fehlen.length) {
|
|
730
|
+
var nk = el('div', 'card');
|
|
731
|
+
nk.style.marginBottom = '11px';
|
|
732
|
+
var nh = el('div', 'ch');
|
|
733
|
+
nh.appendChild(el('span', 'typ', tr('mq.newPoints')));
|
|
734
|
+
/* Vorher stand hier die Zahl der fehlenden Punkte, angelegt wurden
|
|
735
|
+
aber nur die vorausgewaehlten — „10 Sendepunkte fehlen" ueber
|
|
736
|
+
einem Knopf „Anlegen (4)". */
|
|
737
|
+
var gewaehlt = l.fehlen.filter(function (y) { return mqttAuswahl['neu:' + y.name]; }).length;
|
|
738
|
+
nh.appendChild(el('span', 'chip ' + (gewaehlt ? 'warn' : 'mut'),
|
|
739
|
+
tr('mq.selectedOf', gewaehlt, l.fehlen.length)));
|
|
740
|
+
nk.appendChild(nh);
|
|
741
|
+
l.fehlen.forEach(function (x) {
|
|
742
|
+
var w = befehlsWissen(x.name);
|
|
743
|
+
var r = el('div', 'slot');
|
|
744
|
+
r.style.gridTemplateColumns = '24px 1fr 1.2fr 1fr';
|
|
745
|
+
var cw = el('span');
|
|
746
|
+
var cb = document.createElement('input');
|
|
747
|
+
cb.type = 'checkbox';
|
|
748
|
+
cb.checked = !!mqttAuswahl['neu:' + x.name];
|
|
749
|
+
cb.addEventListener('change', function () {
|
|
750
|
+
mqttAuswahl['neu:' + x.name] = cb.checked; zeichneMqttDialog();
|
|
751
|
+
});
|
|
752
|
+
cw.appendChild(cb);
|
|
753
|
+
r.appendChild(cw);
|
|
754
|
+
var nm = el('span', 'sn', 'cmnd.' + x.name);
|
|
755
|
+
nm.style.fontFamily = 'var(--mono)';
|
|
756
|
+
r.appendChild(nm);
|
|
757
|
+
r.appendChild(el('span', 'rx', w ? (w.rolle || '—') + (w.einheit ? ' ' + w.einheit : '') : tr('mq.unknownCommand')));
|
|
758
|
+
var wz = el('span', 'rx', w && w.was ? w.was : (tr('mq.nowValue') + ' ' + JSON.stringify(x.wert)));
|
|
759
|
+
if (w && w.dopplung) { wz.appendChild(el('span', 'chip warn', tr('mq.duplicate', w.dopplung))); }
|
|
760
|
+
r.appendChild(wz);
|
|
761
|
+
nk.appendChild(r);
|
|
762
|
+
});
|
|
763
|
+
body.appendChild(nk);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (l.stumm.length) {
|
|
767
|
+
var sk = el('div', 'card');
|
|
768
|
+
sk.style.marginBottom = '11px';
|
|
769
|
+
var sh = el('div', 'ch');
|
|
770
|
+
sh.appendChild(el('span', 'typ', tr('mq.repair')));
|
|
771
|
+
sh.appendChild(el('span', 'chip bad', tr('mq.mute', l.stumm.length)));
|
|
772
|
+
sk.appendChild(sh);
|
|
773
|
+
sk.appendChild(el('div', 'hint', tr('mq.repairHint')));
|
|
774
|
+
l.stumm.forEach(function (x) {
|
|
775
|
+
var r = el('div', 'slot');
|
|
776
|
+
r.style.gridTemplateColumns = '24px 1fr';
|
|
777
|
+
var cw = el('span');
|
|
778
|
+
var cb = document.createElement('input');
|
|
779
|
+
cb.type = 'checkbox';
|
|
780
|
+
cb.checked = !!mqttAuswahl['fix:' + x.name];
|
|
781
|
+
cb.addEventListener('change', function () {
|
|
782
|
+
mqttAuswahl['fix:' + x.name] = cb.checked; zeichneMqttDialog();
|
|
783
|
+
});
|
|
784
|
+
cw.appendChild(cb);
|
|
785
|
+
r.appendChild(cw);
|
|
786
|
+
var nm = el('span', 'sn', 'cmnd.' + x.name);
|
|
787
|
+
nm.style.fontFamily = 'var(--mono)';
|
|
788
|
+
r.appendChild(nm);
|
|
789
|
+
sk.appendChild(r);
|
|
790
|
+
});
|
|
791
|
+
body.appendChild(sk);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/* Trockenlauf: das fertige Objekt, bevor es entsteht. */
|
|
795
|
+
var liste = mqttZuSchreiben();
|
|
796
|
+
var jk = el('div', 'card');
|
|
797
|
+
var jh = el('div', 'ch');
|
|
798
|
+
jh.appendChild(el('span', 'typ', tr('write.dryTitleCreate')));
|
|
799
|
+
jh.appendChild(el('span', 'chip mut', tr('app.objectsCount', liste.length)));
|
|
800
|
+
jk.appendChild(jh);
|
|
801
|
+
liste.slice(0, 3).forEach(function (x) {
|
|
802
|
+
var pre = el('div', 'raw');
|
|
803
|
+
pre.style.margin = '9px 12px 0';
|
|
804
|
+
pre.style.whiteSpace = 'pre';
|
|
805
|
+
pre.style.maxHeight = 'none';
|
|
806
|
+
pre.textContent = x.id + '\n' + JSON.stringify(x.obj, null, 2);
|
|
807
|
+
jk.appendChild(pre);
|
|
808
|
+
});
|
|
809
|
+
if (liste.length > 3) {
|
|
810
|
+
jk.appendChild(el('div', 'hint', tr('mq.andMore', liste.length - 3)));
|
|
811
|
+
}
|
|
812
|
+
jk.appendChild(el('div', null, ' '));
|
|
813
|
+
body.appendChild(jk);
|
|
814
|
+
|
|
815
|
+
var bs = $('#btn-mqtt-write');
|
|
816
|
+
if (bs) {
|
|
817
|
+
bs.disabled = !liste.length;
|
|
818
|
+
bs.textContent = tr('mq.write', liste.length);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function mqttZuSchreiben() {
|
|
823
|
+
var l = mqttStand;
|
|
824
|
+
if (!l) { return []; }
|
|
825
|
+
var kanal = l.kanal;
|
|
826
|
+
var raus = [];
|
|
827
|
+
l.fehlen.forEach(function (x) {
|
|
828
|
+
if (!mqttAuswahl['neu:' + x.name]) { return; }
|
|
829
|
+
var w = befehlsWissen(x.name) || {};
|
|
830
|
+
var thema = mqttThema(l.geraet, 'cmnd', x.name);
|
|
831
|
+
var common = {
|
|
832
|
+
name: x.name,
|
|
833
|
+
role: w.rolle || 'state',
|
|
834
|
+
type: w.typ || 'mixed',
|
|
835
|
+
read: true, write: true,
|
|
836
|
+
desc: tr('mq.createdBy'),
|
|
837
|
+
custom: mqttCustom(l.geraet, thema)
|
|
838
|
+
};
|
|
839
|
+
if (w.einheit) { common.unit = w.einheit; }
|
|
840
|
+
if (w.werteliste) { common.states = w.werteliste; }
|
|
841
|
+
raus.push({ id: kanal + '.cmnd.' + x.name, obj: { type: 'state', common: common, native: { topic: thema } }, neu: true });
|
|
842
|
+
});
|
|
843
|
+
l.stumm.forEach(function (x) {
|
|
844
|
+
if (!mqttAuswahl['fix:' + x.name]) { return; }
|
|
845
|
+
var alt = S.objects[x.id];
|
|
846
|
+
if (!alt) { return; }
|
|
847
|
+
var kopie = JSON.parse(JSON.stringify(alt));
|
|
848
|
+
var cu = kopie.common.custom || {};
|
|
849
|
+
Object.keys(cu).forEach(function (i) { if (/^mqtt/.test(i)) { cu[i].publish = true; } });
|
|
850
|
+
kopie.common.custom = cu;
|
|
851
|
+
raus.push({ id: x.id, obj: kopie, neu: false });
|
|
852
|
+
});
|
|
853
|
+
return raus;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export function mqttSchreiben() {
|
|
857
|
+
var liste = mqttZuSchreiben();
|
|
858
|
+
if (!liste.length) { return; }
|
|
859
|
+
var b = $('#btn-mqtt-write');
|
|
860
|
+
if (b) { b.disabled = true; b.textContent = tr('write.writing'); }
|
|
861
|
+
var offen = liste.length, fehler = [];
|
|
862
|
+
liste.forEach(function (x) {
|
|
863
|
+
socket.emit('setObject', x.id, x.obj, function (err) {
|
|
864
|
+
if (err) { fehler.push(x.id + ': ' + err); }
|
|
865
|
+
if (--offen === 0) {
|
|
866
|
+
var body = $('#mqtt-body');
|
|
867
|
+
body.textContent = '';
|
|
868
|
+
var m = el('div');
|
|
869
|
+
m.style.fontSize = '13px';
|
|
870
|
+
if (fehler.length) {
|
|
871
|
+
m.appendChild(el('b', null, tr('write.failed', fehler.length)));
|
|
872
|
+
var ul = el('ul');
|
|
873
|
+
fehler.forEach(function (t) { ul.appendChild(el('li', null, t)); });
|
|
874
|
+
m.appendChild(ul);
|
|
875
|
+
} else {
|
|
876
|
+
m.appendChild(el('b', null, tr('mq.done', liste.length)));
|
|
877
|
+
m.appendChild(el('div', 'hint', tr('mq.doneHint')));
|
|
878
|
+
}
|
|
879
|
+
body.appendChild(m);
|
|
880
|
+
if (b) { b.hidden = true; }
|
|
881
|
+
holeZweig((mqttStand && mqttStand.kanal ? mqttStand.kanal + '.' : ''));
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
}
|