iobroker.alias-workbench 0.8.3 → 0.9.7
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 +146 -3
- package/admin/css/werkbank.css +57 -8
- package/admin/detector.js +1 -1
- package/admin/geraetewissen/tasmota-befehle.json +3 -1
- package/admin/i18n/de.json +30 -6
- package/admin/i18n/en.json +30 -6
- package/admin/js/aufzaehlungen.js +11 -1
- package/admin/js/baum.js +118 -37
- package/admin/js/detail.js +76 -9
- package/admin/js/entwurf.js +313 -43
- package/admin/js/enums.js +50 -7
- package/admin/js/ergebnis.js +161 -37
- package/admin/js/erkennung.js +54 -5
- package/admin/js/katalog.js +12 -3
- package/admin/js/mqtt.js +76 -13
- package/admin/js/musternamen.js +91 -26
- package/admin/js/musterwahl.js +10 -0
- package/admin/js/objekte.js +29 -3
- package/admin/js/pruefungen.js +10 -2
- package/admin/js/quellentausch.js +31 -2
- package/admin/js/schreiben.js +209 -24
- package/admin/js/sprache.js +54 -7
- package/admin/js/start.js +32 -2
- package/admin/js/vorlagen.js +114 -6
- package/admin/js/vorlagenblatt.js +195 -26
- package/admin/js/werte.js +113 -6
- package/admin/js/zuordnung.js +71 -17
- package/admin/js/zustand.js +7 -1
- package/admin/js/zustandsliste.js +157 -26
- package/admin/tab.html +25 -3
- package/admin/vorlagen/hm-dimmer.json +1 -1
- package/admin/vorlagen/hm-schaltaktor-lampe.json +2 -2
- package/admin/vorlagen/hm-schaltaktor-mehrfach.json +2 -2
- package/admin/vorlagen/hm-schaltaktor-steckdose.json +2 -2
- package/io-package.json +105 -1
- package/package.json +1 -1
package/admin/js/vorlagen.js
CHANGED
|
@@ -12,7 +12,7 @@ import { tasmotaBefehle } from './mqtt.js';
|
|
|
12
12
|
import { socket } from './verbindung.js';
|
|
13
13
|
|
|
14
14
|
import { tr, sprachtext } from './sprache.js';
|
|
15
|
-
import { musterVon } from './erkennung.js';
|
|
15
|
+
import { musterVon, rolleTrifft } from './erkennung.js';
|
|
16
16
|
|
|
17
17
|
/* Welche Instanz — der Reiter wird mit ?<instanz>&noFooter geladen. */
|
|
18
18
|
var INSTANZ = (function () {
|
|
@@ -29,6 +29,24 @@ export function zeigeObjektzahl() {
|
|
|
29
29
|
if (z) { z.textContent = tr('app.objectsCount', S.keysSorted.length); }
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/* Wie viele Wertabos laufen gerade? Steht neben der Objektzahl.
|
|
33
|
+
|
|
34
|
+
Nicht aus Neugier: die Abos werden beim Wechsel des Knotens abgemeldet
|
|
35
|
+
und neu gesetzt, und ob dabei wirklich alle wieder verschwinden, sieht
|
|
36
|
+
man sonst nirgends. Bleibt die Zahl beim Durchklicken stabil, stimmt
|
|
37
|
+
es; waechst sie, sammelt sich etwas an (Ricardo, 08.09.2026). */
|
|
38
|
+
export function zeigeAbozahl() {
|
|
39
|
+
var z = $('#meta-abos');
|
|
40
|
+
if (!z) { return; }
|
|
41
|
+
var n = (S.abo || []).length;
|
|
42
|
+
z.textContent = ' \u00b7 ' + (n === 1 ? tr('app.subsCount1') : tr('app.subsCount', n));
|
|
43
|
+
/* Die Muster gehoeren dazu, sonst ist die Zahl ein Raetsel: „2 Abos"
|
|
44
|
+
bei zwanzig sichtbaren Werten liest sich falsch, bis man sieht, dass
|
|
45
|
+
ein Muster einen ganzen Zweig deckt (Ricardo, 08.09.2026). */
|
|
46
|
+
z.title = tr('app.subsHint') + String.fromCharCode(10) +
|
|
47
|
+
(S.abo || []).map(function (mu) { return ' ' + mu; }).join(String.fromCharCode(10));
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
export function setzeMeta() {
|
|
33
51
|
var m = $('#meta');
|
|
34
52
|
if (!m) { return; }
|
|
@@ -41,6 +59,8 @@ export function setzeMeta() {
|
|
|
41
59
|
steht sie neben ihresgleichen; ein eigener Span, damit das Abo sie
|
|
42
60
|
nachfuehren kann, ohne die ganze Zeile neu zu bauen. */
|
|
43
61
|
m.appendChild(el('span', null, tr('app.objectsCount', S.keysSorted.length))).id = 'meta-objekte';
|
|
62
|
+
m.appendChild(el('span', null, '')).id = 'meta-abos';
|
|
63
|
+
zeigeAbozahl();
|
|
44
64
|
if (S.vorlagenUnvollstaendig) {
|
|
45
65
|
var w = el('span', 'chip bad', tr('tpls.loadFailed'));
|
|
46
66
|
w.style.marginLeft = '8px';
|
|
@@ -92,6 +112,15 @@ export function ladeVorlagen(fertig) {
|
|
|
92
112
|
sie behauptet dann nicht, es gaebe keine eigenen Vorlagen. */
|
|
93
113
|
setTimeout(function () { schluss([], true); }, 6000);
|
|
94
114
|
socket.emit('getObject', INSTANZ_ID, function (err, o) {
|
|
115
|
+
/* Denselben Weg wie der Timeout eine Zeile darueber: ein Fehler
|
|
116
|
+
heisst nicht „keine eigenen Vorlagen".
|
|
117
|
+
|
|
118
|
+
`err` wurde vorher nicht einmal angesehen. Zwei Folgen: in der
|
|
119
|
+
Kopfzeile fehlte der Warnchip, und `freierSchluessel` hielt
|
|
120
|
+
vergebene Kennungen fuer frei — beim naechsten Speichern haette
|
|
121
|
+
`aendereVorlagen` eine vorhandene Vorlage mit Version 1
|
|
122
|
+
ersetzt. */
|
|
123
|
+
if (err) { return schluss([], true); }
|
|
95
124
|
schluss((o && o.native && o.native.vorlagen) || []);
|
|
96
125
|
});
|
|
97
126
|
}
|
|
@@ -275,6 +304,47 @@ function punktErfuellt(kanal, pfad) {
|
|
|
275
304
|
}
|
|
276
305
|
|
|
277
306
|
/* Passt die Vorlage auf diesen Kanal? Liefert null oder eine Bewertung. */
|
|
307
|
+
/* Der Namenshinweis als Ausdruck — und der wirft nicht.
|
|
308
|
+
|
|
309
|
+
Der Text kommt frei aus dem Vorlagenblatt und aus dem
|
|
310
|
+
Speichern-Dialog; keins der beiden Felder prueft ihn. Ein `Lampe (Bad`
|
|
311
|
+
liess `pruefeVorlage` bei JEDER Geraeteauswahl werfen, kein Aufrufer
|
|
312
|
+
fing das, und die rechte Seite blieb leer — zu reparieren nur ueber
|
|
313
|
+
die Instanzkonfiguration von Hand (gemessen 09.09.2026). `rolleTrifft`
|
|
314
|
+
in `erkennung.js` faengt denselben Fall laengst ab; hier fehlte es.
|
|
315
|
+
|
|
316
|
+
Ein unbrauchbarer Ausdruck heisst „trifft nicht": der Hinweis ist nur
|
|
317
|
+
ein Bonus in der Bewertung, keine Bedingung. Die Vorlage bleibt damit
|
|
318
|
+
brauchbar, statt das ganze Werkzeug mitzureissen. */
|
|
319
|
+
export function hinweisTrifft(hinweis, name) {
|
|
320
|
+
if (!hinweisTaugt(hinweis)) { return false; }
|
|
321
|
+
try { return new RegExp(hinweis, 'i').test(name); }
|
|
322
|
+
catch { return false; }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Verschachtelte Wiederholungen — `(a+)+`, `(a*)*`, `(a+){2,}`.
|
|
326
|
+
|
|
327
|
+
Die klassische Form, an der eine RegExp-Maschine in die Knie geht:
|
|
328
|
+
`(a+)+$` gegen neunundzwanzig "a" brauchte hier gemessene 3196 ms,
|
|
329
|
+
und der ganze Reiter stand solange (09.09.2026). Ein try/catch faengt
|
|
330
|
+
das nicht — die Syntax ist ja in Ordnung, es dauert nur ewig.
|
|
331
|
+
|
|
332
|
+
Das ist eine Heuristik, kein Beweis: Sie deckt die Form ab, die man
|
|
333
|
+
sich versehentlich baut, nicht jeden denkbaren Fall. Fuer einen
|
|
334
|
+
Namenshinweis reicht das — er soll ein Wort im Geraetenamen finden,
|
|
335
|
+
nicht eine Sprache parsen. */
|
|
336
|
+
function verschachtelt(hinweis) {
|
|
337
|
+
return /\([^()]*[+*][^()]*\)\s*([+*]|\{\s*[0-9]+\s*,)/.test(String(hinweis));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* Taugt der Text als Ausdruck? Fuer die Felder, die ihn aufnehmen. */
|
|
341
|
+
export function hinweisTaugt(hinweis) {
|
|
342
|
+
if (!hinweis) { return true; }
|
|
343
|
+
if (verschachtelt(hinweis)) { return false; }
|
|
344
|
+
try { new RegExp(hinweis, 'i'); return true; }
|
|
345
|
+
catch { return false; }
|
|
346
|
+
}
|
|
347
|
+
|
|
278
348
|
export function pruefeVorlage(v, kanal, instanz) {
|
|
279
349
|
var erk = v.erkennung || {};
|
|
280
350
|
var gruende = [];
|
|
@@ -347,7 +417,7 @@ export function pruefeVorlage(v, kanal, instanz) {
|
|
|
347
417
|
var belege = (erk.erforderlich || []).length *
|
|
348
418
|
(v.mehrfach ? Math.max(1, ausgaenge.length) : 1) +
|
|
349
419
|
Object.keys(erk.inhalt || {}).length;
|
|
350
|
-
var namenstreffer = !!(erk.namenshinweis &&
|
|
420
|
+
var namenstreffer = !!(erk.namenshinweis && hinweisTrifft(erk.namenshinweis, name));
|
|
351
421
|
if (namenstreffer) { gruende.push(tr('tpl.nameHint', sprachtext(v.name))); }
|
|
352
422
|
var eigen = v._quelle === 'benutzer';
|
|
353
423
|
var punkte = belege * 1000000 +
|
|
@@ -449,6 +519,34 @@ export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
|
|
|
449
519
|
break;
|
|
450
520
|
}
|
|
451
521
|
|
|
522
|
+
/* Ein Tastendruck hat keinen Zustand zum Lesen.
|
|
523
|
+
|
|
524
|
+
`STOP` am Rollladen traegt nur `schreiben` — genau so steht es in
|
|
525
|
+
der Vorlage, mit Begruendung. Diesen Fall kannte das Anwenden
|
|
526
|
+
nicht: Ohne `lesen` fiel der Punkt mit „STOP: undefined fehlt"
|
|
527
|
+
heraus, und KEIN EINZIGER Rollladen-Alias bekam je einen STOP
|
|
528
|
+
(gefunden 09.09.2026). Der Platz im Muster verlangt auch nichts
|
|
529
|
+
weiter als `write: true`. */
|
|
530
|
+
var nurSchreiben = !z.lesen && !!z.schreiben;
|
|
531
|
+
if (nurSchreiben && !quelle) {
|
|
532
|
+
var ziel0 = schreibZiel(z, kanal, auf);
|
|
533
|
+
if (!ziel0) {
|
|
534
|
+
e.uebersprungen.push(tr('tpl.skippedSource', z.name, auf(z.schreiben)));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
e.states.push({
|
|
538
|
+
n: z.name, on: !z.vorgabeAus, role: z.rolle || '', typ: z.typ || '',
|
|
539
|
+
unit: z.einheit || '', wr: true,
|
|
540
|
+
srcR: '', srcW: ziel0,
|
|
541
|
+
f: '', fw: z.schreibformel ? auf(z.schreibformel) : '',
|
|
542
|
+
caption: z.beschriftung || '',
|
|
543
|
+
dec: z.nachkommastellen === undefined ? undefined : String(z.nachkommastellen),
|
|
544
|
+
states: z.werteliste || undefined,
|
|
545
|
+
manuell: false, ausVorlage: z.name, hinweis: z.hinweis || '', wege: []
|
|
546
|
+
});
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
452
550
|
if (!quelle) {
|
|
453
551
|
/* Beide Wege nennen, nicht nur den letzten — sonst sucht man an der
|
|
454
552
|
einen Stelle und die Vorlage hatte an zwei geschaut. */
|
|
@@ -461,7 +559,15 @@ export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
|
|
|
461
559
|
/* Auch in der Formel steht der Platzhalter — ohne Ersetzung suchte
|
|
462
560
|
der Alias nach einem Feld namens POWER%N%. */
|
|
463
561
|
var formel = leseformel ? auf(leseformel) : '';
|
|
464
|
-
|
|
562
|
+
/* Wer eine Formel hinschreibt, bekommt sie auch.
|
|
563
|
+
|
|
564
|
+
`tasmota-rgb` fuehrt an FADE beides: `feld` UND `leseformel`. Die
|
|
565
|
+
Formel wurde verworfen und aus dem Feld eine neue gebaut — bei
|
|
566
|
+
FADE faellt es nicht auf, weil der Zieltyp die Umwandlung
|
|
567
|
+
nachholt, an der naechsten Zeile mit eigener Rechnung aber schon
|
|
568
|
+
(gefunden 09.09.2026). Das Feld bleibt der bequeme Weg; die
|
|
569
|
+
ausdrueckliche Angabe schlaegt ihn. */
|
|
570
|
+
if (feld && !formel) {
|
|
465
571
|
feld = auf(feld);
|
|
466
572
|
/* Mit Fragezeichen-Zugriff: `tele.SENSOR` ist ein Sammeltopf, und
|
|
467
573
|
nicht jede Nachricht darin fuehrt jedes Feld. Ohne Schutz wirft
|
|
@@ -543,8 +649,10 @@ export function vorschlag(kanal, vorlagenId, instanz) {
|
|
|
543
649
|
e.erzwungen = true;
|
|
544
650
|
e.grund = e.grund.concat(gewaehlt.fehler.map(function (f) { return tr('tpl.wouldNeed', f); }));
|
|
545
651
|
}
|
|
546
|
-
e.andere
|
|
547
|
-
|
|
652
|
+
/* Hier stand `e.andere` — die Liste der unterlegenen Vorlagen. Sie
|
|
653
|
+
wurde bei jedem Vorschlag gebaut und von niemandem gelesen
|
|
654
|
+
(gefunden 09.09.2026). Was der Nutzer wirklich braucht, steht im
|
|
655
|
+
Musterwahl-Feld und im Vorlagenblatt. */
|
|
548
656
|
if (vorlagenId) { e.vonHandGewaehlt = true; }
|
|
549
657
|
return merkeHakenVorgabe(e);
|
|
550
658
|
}
|
|
@@ -559,7 +667,7 @@ export function anMusterAnpassen(e, neuTyp) {
|
|
|
559
667
|
var s = null;
|
|
560
668
|
muster.states.forEach(function (st) {
|
|
561
669
|
if (s || !st.role) { return; }
|
|
562
|
-
|
|
670
|
+
if (rolleTrifft(st.role, rolle)) { s = st; } /* eine kaputte Rolle macht die Vorlage nicht passend */
|
|
563
671
|
});
|
|
564
672
|
return s;
|
|
565
673
|
}
|
|
@@ -9,9 +9,9 @@ import { D, $, el, kurz } from './basis.js';
|
|
|
9
9
|
import { tr, sprachtext, sprache } from './sprache.js';
|
|
10
10
|
import { katalogFehlt } from './katalog.js';
|
|
11
11
|
import { funktionsAuswahl, kennungZuFunktion } from './aufzaehlungen.js';
|
|
12
|
-
import { musterVon } from './erkennung.js';
|
|
12
|
+
import { musterVon, rolleTrifft } from './erkennung.js';
|
|
13
13
|
import { musterZeile, musterName } from './musternamen.js';
|
|
14
|
-
import { ladeVorlagen, aendereVorlagen, setzeMeta, pruefeVorlage, INSTANZ_ID } from './vorlagen.js';
|
|
14
|
+
import { ladeVorlagen, aendereVorlagen, setzeMeta, pruefeVorlage, hinweisTaugt, INSTANZ_ID } from './vorlagen.js';
|
|
15
15
|
import { kindZustaende, hatPunkt, feldAusFormel } from './werte.js';
|
|
16
16
|
import { opt } from './entwurf.js';
|
|
17
17
|
import { zeichneErgebnis } from './ergebnis.js';
|
|
@@ -326,6 +326,15 @@ export function zeichneVorlagenBlatt(host) {
|
|
|
326
326
|
nb.style.padding = '0 12px 11px';
|
|
327
327
|
nb.appendChild(feld(tr('tpls.nameHint'), (v.erkennung || {}).namenshinweis || '',
|
|
328
328
|
function (x, w) { x.erkennung = x.erkennung || {}; if (w) { x.erkennung.namenshinweis = w; } else { delete x.erkennung.namenshinweis; } }, '260px'));
|
|
329
|
+
/* Der Text ist ein Ausdruck, und ein unbrauchbarer Ausdruck faellt
|
|
330
|
+
sonst nirgends auf: Er wird still zu „trifft nicht", und die Vorlage
|
|
331
|
+
verliert leise ihren Namensbonus. Also hier sagen, wo er steht. */
|
|
332
|
+
if (!hinweisTaugt((v.erkennung || {}).namenshinweis || '')) {
|
|
333
|
+
var nbw = el('div', 'hint');
|
|
334
|
+
nbw.style.color = 'var(--bad)';
|
|
335
|
+
nbw.textContent = tr('tpls.nameHintBad');
|
|
336
|
+
nb.appendChild(nbw);
|
|
337
|
+
}
|
|
329
338
|
nb.appendChild(el('div', 'hint', tr('tv.nameHintShort')));
|
|
330
339
|
kk.appendChild(nb);
|
|
331
340
|
host.appendChild(kk);
|
|
@@ -551,8 +560,7 @@ function rollenBlock(rolle, geraetetyp, setzen) {
|
|
|
551
560
|
if (must) {
|
|
552
561
|
var treffer = null;
|
|
553
562
|
must.states.forEach(function (pp) {
|
|
554
|
-
if (!treffer && pp.role && rolle
|
|
555
|
-
new RegExp(pp.role.source ? pp.role.source : pp.role).test(rolle)) { treffer = pp; }
|
|
563
|
+
if (!treffer && pp.role && rolleTrifft(pp.role, rolle)) { treffer = pp; }
|
|
556
564
|
});
|
|
557
565
|
var hin = el('div', 'sugg');
|
|
558
566
|
if (treffer) {
|
|
@@ -910,12 +918,61 @@ export function vEinlesenDatei(datei) {
|
|
|
910
918
|
leser.readAsText(datei);
|
|
911
919
|
}
|
|
912
920
|
|
|
921
|
+
/* Was eingelesen wird, wird gespeichert — und danach an jedem Geraet
|
|
922
|
+
ausgewertet. Geprueft wurden bisher nur `id` und `zustaende`.
|
|
923
|
+
|
|
924
|
+
Zwei Wege in den Totalschaden: `erkennung.erforderlich` als Text
|
|
925
|
+
statt als Liste laesst `pruefeVorlage` an JEDEM Geraet werfen
|
|
926
|
+
(„map is not a function", gemessen 09.09.2026), ein fehlendes `name`
|
|
927
|
+
wirft schon beim Oeffnen im Blatt. Die Knopfleiste „Einlesen und
|
|
928
|
+
speichern" steht zu diesem Zeitpunkt laengst im DOM — man konnte
|
|
929
|
+
also eine Vorlage speichern, die den Reiter danach unbrauchbar
|
|
930
|
+
machte.
|
|
931
|
+
|
|
932
|
+
Listenfelder werden nicht abgewiesen, wenn ein einzelner Text
|
|
933
|
+
gemeint sein kann: `erforderlich: "tele.STATE"` ist eindeutig und
|
|
934
|
+
wird zur Liste gemacht. Abgewiesen wird nur, was keinen Sinn ergibt. */
|
|
913
935
|
function vPruefeEingelesen(o) {
|
|
914
936
|
if (!o || typeof o !== 'object' || Array.isArray(o)) { return tr('tv.badNotObject'); }
|
|
915
937
|
if (!o.id || typeof o.id !== 'string') { return tr('tv.badNoId'); }
|
|
916
938
|
if (!Array.isArray(o.zustaende) || !o.zustaende.length) { return tr('tv.badNoStates'); }
|
|
917
939
|
var schlecht = o.zustaende.filter(function (z) { return !z || !z.name || !z.lesen; });
|
|
918
940
|
if (schlecht.length) { return tr('tv.badState', schlecht.length); }
|
|
941
|
+
|
|
942
|
+
if (o.name !== undefined &&
|
|
943
|
+
!(typeof o.name === 'string' ||
|
|
944
|
+
(o.name && typeof o.name === 'object' && !Array.isArray(o.name)))) {
|
|
945
|
+
return tr('tv.badName');
|
|
946
|
+
}
|
|
947
|
+
if (o.erkennung !== undefined &&
|
|
948
|
+
(!o.erkennung || typeof o.erkennung !== 'object' || Array.isArray(o.erkennung))) {
|
|
949
|
+
return tr('tv.badRecog');
|
|
950
|
+
}
|
|
951
|
+
var erk = o.erkennung || {};
|
|
952
|
+
var listen = ['erforderlich', 'verboten'];
|
|
953
|
+
for (var i = 0; i < listen.length; i++) {
|
|
954
|
+
var w = erk[listen[i]];
|
|
955
|
+
if (w === undefined) { continue; }
|
|
956
|
+
if (typeof w === 'string') { erk[listen[i]] = [w]; continue; }
|
|
957
|
+
if (!Array.isArray(w) || w.some(function (x) { return typeof x !== 'string'; })) {
|
|
958
|
+
return tr('tv.badList', listen[i]);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (erk.inhalt !== undefined &&
|
|
962
|
+
(!erk.inhalt || typeof erk.inhalt !== 'object' || Array.isArray(erk.inhalt))) {
|
|
963
|
+
return tr('tv.badContent');
|
|
964
|
+
}
|
|
965
|
+
if (erk.namenshinweis !== undefined &&
|
|
966
|
+
(typeof erk.namenshinweis !== 'string' || !hinweisTaugt(erk.namenshinweis))) {
|
|
967
|
+
return tr('tv.badNameHint');
|
|
968
|
+
}
|
|
969
|
+
/* Ein Musternamen, den dieser Detector nicht kennt, wirft spaeter in
|
|
970
|
+
der Musterwahl — und der Entwurf wird dann gar nicht erst
|
|
971
|
+
gezeichnet. Lieber hier abweisen, wo noch jemand zusieht. */
|
|
972
|
+
if (o.geraetetyp !== undefined &&
|
|
973
|
+
(typeof o.geraetetyp !== 'string' || (o.geraetetyp && !musterVon(o.geraetetyp)))) {
|
|
974
|
+
return tr('tv.badType', String(o.geraetetyp));
|
|
975
|
+
}
|
|
919
976
|
return null;
|
|
920
977
|
}
|
|
921
978
|
|
|
@@ -999,35 +1056,96 @@ function relZu(id, kanal) {
|
|
|
999
1056
|
/* stat.POWER1 → stat.POWER%N%. Ersetzt wird nur am Ende eines
|
|
1000
1057
|
Abschnitts und nur, wenn davor noch etwas steht — sonst wuerde aus
|
|
1001
1058
|
ENERGY.Power beim Speichern Unsinn. */
|
|
1002
|
-
function zuPlatzhalter(pfad, inst) {
|
|
1059
|
+
function zuPlatzhalter(pfad, inst, vorspaenne) {
|
|
1003
1060
|
var i = (inst === null || inst === undefined) ? '' : String(inst);
|
|
1004
1061
|
if (!i) { return pfad; }
|
|
1005
1062
|
return String(pfad).split('.').map(function (t) {
|
|
1006
|
-
|
|
1063
|
+
if (!(t.length > i.length && t.slice(-i.length) === i)) { return t; }
|
|
1064
|
+
/* Nicht jede Endziffer ist eine Ausgangsnummer.
|
|
1065
|
+
|
|
1066
|
+
Ersetzt wurde rein nach Endziffer: an einem Mehrfachgeraet mit
|
|
1067
|
+
Ausgang 2 wurde damit auch die IP-Zeile aus `tele.INFO2` zu
|
|
1068
|
+
`tele.INFO%N%` (gemessen 09.09.2026). Der Schaden zeigt sich erst
|
|
1069
|
+
am naechsten Geraet — und an Ausgang 1 meldete `andereAusgaenge`
|
|
1070
|
+
dieselbe Zeile als fremden Ausgang, „Andere weglassen" warf sie
|
|
1071
|
+
hinaus. `INFO2` heisst bei Tasmota die zweite Info-Seite, an
|
|
1072
|
+
jedem Ausgang gleich. */
|
|
1073
|
+
return (vorspaenne.indexOf(t.slice(0, -i.length)) === -1)
|
|
1074
|
+
? t : t.slice(0, -i.length) + '%N%';
|
|
1007
1075
|
}).join('.');
|
|
1008
1076
|
}
|
|
1009
1077
|
|
|
1078
|
+
/* Welche Vorspaenne tragen wirklich die Ausgangsnummer?
|
|
1079
|
+
|
|
1080
|
+
Drei Quellen, in dieser Reihenfolge belastbar: was die Vorlage im
|
|
1081
|
+
Original schon mit `%N%` fuehrte, der Vorspann aus `kanalname`
|
|
1082
|
+
(`POWER%N%` → `POWER`), und was mit derselben Nummer mehrfach
|
|
1083
|
+
vorkommt — `cmnd.POWER2` und `stat.POWER2` sagen gemeinsam mehr als
|
|
1084
|
+
ein einzelnes `tele.INFO2`. */
|
|
1085
|
+
function ausgangsVorspaenne(zeilen, k) {
|
|
1086
|
+
var i = (k.instanz === null || k.instanz === undefined) ? '' : String(k.instanz);
|
|
1087
|
+
var raus = [];
|
|
1088
|
+
var dazu = function (v) { if (v && raus.indexOf(v) === -1) { raus.push(v); } };
|
|
1089
|
+
|
|
1090
|
+
var kn = String(k.kanalname || 'POWER%N%');
|
|
1091
|
+
if (kn.indexOf('%N%') !== -1) { dazu(kn.split('%N%')[0]); }
|
|
1092
|
+
|
|
1093
|
+
((k.quelle && k.quelle.zustaende) || []).forEach(function (z) {
|
|
1094
|
+
[z.lesen, z.schreiben].forEach(function (pf) {
|
|
1095
|
+
String(pf || '').split('.').forEach(function (t) {
|
|
1096
|
+
var n = t.indexOf('%N%');
|
|
1097
|
+
if (n > 0) { dazu(t.slice(0, n)); }
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
if (i) {
|
|
1103
|
+
var wie = {};
|
|
1104
|
+
(zeilen || []).forEach(function (r) {
|
|
1105
|
+
if (r.weg) { return; }
|
|
1106
|
+
[r.lesenAbs ? '' : r.lesenRoh, r.schreibenAbs ? '' : r.schreibenRoh].forEach(function (pf) {
|
|
1107
|
+
String(pf || '').split('.').forEach(function (t) {
|
|
1108
|
+
if (t.length > i.length && t.slice(-i.length) === i) {
|
|
1109
|
+
var v = t.slice(0, -i.length);
|
|
1110
|
+
wie[v] = (wie[v] || 0) + 1;
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
});
|
|
1115
|
+
Object.keys(wie).forEach(function (v) { if (wie[v] > 1) { dazu(v); } });
|
|
1116
|
+
}
|
|
1117
|
+
return raus;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1010
1120
|
/* Welcher Pfad steht am Ende in der Vorlage? Bei einem Mehrfachgeraet
|
|
1011
1121
|
tritt der Platzhalter an die Stelle der Ausgangsnummer. */
|
|
1012
|
-
function zPfad(r, k, schreibend) {
|
|
1122
|
+
function zPfad(r, k, schreibend, zeilen) {
|
|
1013
1123
|
var roh = schreibend ? r.schreibenRoh : r.lesenRoh;
|
|
1014
1124
|
var abs = schreibend ? r.schreibenAbs : r.lesenAbs;
|
|
1015
1125
|
if (!roh) { return ''; }
|
|
1016
1126
|
if (!k.mehrfach || abs || !k.instanz) { return roh; }
|
|
1017
|
-
return zuPlatzhalter(roh, k.instanz
|
|
1127
|
+
return zuPlatzhalter(roh, k.instanz,
|
|
1128
|
+
ausgangsVorspaenne(zeilen || (S.tplZ && S.tplZ.zeilen) || [], k));
|
|
1018
1129
|
}
|
|
1019
1130
|
|
|
1020
1131
|
/* Endet die Nummer eines Ausgangs in den Pfaden? Haeufigste Zahl am
|
|
1021
1132
|
Ende eines Abschnitts gewinnt — cmnd.POWER1, stat.POWER1 sagen 1. */
|
|
1022
|
-
function rateInstanz(zeilen, geraetName) {
|
|
1023
|
-
var zaehler = {};
|
|
1133
|
+
function rateInstanz(zeilen, geraetName, vorspann) {
|
|
1134
|
+
var zaehler = {}, nurVorspann = {};
|
|
1024
1135
|
zeilen.forEach(function (r) {
|
|
1025
1136
|
if (r.lesenAbs) { return; }
|
|
1026
1137
|
String(r.lesenRoh).split('.').forEach(function (t) {
|
|
1027
1138
|
var m = /^(.*[^0-9])([0-9]{1,2})$/.exec(t);
|
|
1028
|
-
if (m) {
|
|
1139
|
+
if (!m) { return; }
|
|
1140
|
+
zaehler[m[2]] = (zaehler[m[2]] || 0) + 1;
|
|
1141
|
+
if (vorspann && m[1] === vorspann) { nurVorspann[m[2]] = (nurVorspann[m[2]] || 0) + 1; }
|
|
1029
1142
|
});
|
|
1030
1143
|
});
|
|
1144
|
+
/* Zaehlt der Vorspann des Ausgangs mit (`POWER%N%` → `POWER`), gilt
|
|
1145
|
+
allein er: sonst hob ein `tele.INFO2` die Nummer eines Geraets an,
|
|
1146
|
+
das gar keinen zweiten Ausgang hat. Findet sich damit nichts,
|
|
1147
|
+
bleibt es beim alten Weg — besser eine geratene Nummer als keine. */
|
|
1148
|
+
if (Object.keys(nurVorspann).length) { zaehler = nurVorspann; }
|
|
1031
1149
|
var mn = /^(.*[^0-9])([0-9]{1,2})$/.exec(geraetName || '');
|
|
1032
1150
|
if (mn) { zaehler[mn[2]] = (zaehler[mn[2]] || 0) + 1; }
|
|
1033
1151
|
var beste = '', wie = 0;
|
|
@@ -1043,15 +1161,19 @@ function rateInstanz(zeilen, geraetName) {
|
|
|
1043
1161
|
entstehen daraus. Zeigt der Entwurf noch auf POWER2, POWER3 …, waere
|
|
1044
1162
|
die Vorlage an dieses eine Geraet gefesselt. Also einsammeln und
|
|
1045
1163
|
sagen, statt still etwas Falsches zu speichern. */
|
|
1046
|
-
function andereAusgaenge(zeilen, instanz) {
|
|
1164
|
+
function andereAusgaenge(zeilen, instanz, vorspaenne) {
|
|
1047
1165
|
var raus = { punkte: [], nummern: [] };
|
|
1048
1166
|
if (!instanz) { return raus; }
|
|
1167
|
+
var vs = vorspaenne || [];
|
|
1049
1168
|
zeilen.forEach(function (r) {
|
|
1050
1169
|
if (r.weg || r.lesenAbs) { return; }
|
|
1051
1170
|
var fremd = null;
|
|
1052
1171
|
String(r.lesenRoh).split('.').forEach(function (t) {
|
|
1053
1172
|
var m = /^(.*[^0-9])([0-9]{1,2})$/.exec(t);
|
|
1054
|
-
|
|
1173
|
+
/* Dieselbe Regel wie beim Platzhalter: nur was den Ausgang traegt.
|
|
1174
|
+
Sonst stand `tele.INFO2` an Ausgang 1 als fremder Ausgang da,
|
|
1175
|
+
und „Andere weglassen" warf die IP-Zeile hinaus. */
|
|
1176
|
+
if (m && m[2] !== String(instanz) && vs.indexOf(m[1]) !== -1) { fremd = m[2]; }
|
|
1055
1177
|
});
|
|
1056
1178
|
if (fremd) {
|
|
1057
1179
|
raus.punkte.push(r.name);
|
|
@@ -1168,7 +1290,8 @@ export function vorlagenVorbereiten(e) {
|
|
|
1168
1290
|
maxRang: maxRang,
|
|
1169
1291
|
mehrfach: mehrfach,
|
|
1170
1292
|
instanz: mehrfach ? String(inst === null || inst === undefined ? '' : inst)
|
|
1171
|
-
: rateInstanz(zeilen, geraetName
|
|
1293
|
+
: rateInstanz(zeilen, geraetName,
|
|
1294
|
+
String((quelle && quelle.kanalname) || 'POWER%N%').split('%N%')[0]),
|
|
1172
1295
|
kanalname: (quelle && quelle.kanalname) ? quelle.kanalname : 'POWER%N%',
|
|
1173
1296
|
verboten: quelle ? (((quelle.erkennung || {}).verboten) || []).join(', ') : '',
|
|
1174
1297
|
geraetName: geraetName
|
|
@@ -1192,8 +1315,8 @@ export function baueVorlage(z) {
|
|
|
1192
1315
|
var erf = [];
|
|
1193
1316
|
z.zeilen.forEach(function (r) {
|
|
1194
1317
|
if (r.weg || !r.pflicht || r.lesenAbs) { return; }
|
|
1195
|
-
var lp = zPfad(r, k, false);
|
|
1196
|
-
var sp = zPfad(r, k, true);
|
|
1318
|
+
var lp = zPfad(r, k, false, z.zeilen);
|
|
1319
|
+
var sp = zPfad(r, k, true, z.zeilen);
|
|
1197
1320
|
if (erf.indexOf(lp) === -1) { erf.push(lp); }
|
|
1198
1321
|
if (sp && !r.schreibenAbs && erf.indexOf(sp) === -1) { erf.push(sp); }
|
|
1199
1322
|
});
|
|
@@ -1226,10 +1349,10 @@ export function baueVorlage(z) {
|
|
|
1226
1349
|
rolle: s.role || undefined,
|
|
1227
1350
|
typ: s.typ || undefined,
|
|
1228
1351
|
einheit: s.unit || undefined,
|
|
1229
|
-
lesen: zPfad(r, k, false)
|
|
1352
|
+
lesen: zPfad(r, k, false, z.zeilen)
|
|
1230
1353
|
};
|
|
1231
1354
|
if (r.lesenAbs) { zu.absolut = true; }
|
|
1232
|
-
if (r.schreibenRoh) { zu.schreiben = zPfad(r, k, true); }
|
|
1355
|
+
if (r.schreibenRoh) { zu.schreiben = zPfad(r, k, true, z.zeilen); }
|
|
1233
1356
|
if (f) { zu.feld = f; } else if (s.f) { zu.leseformel = s.f; }
|
|
1234
1357
|
if (s.fw && r.schreibenRoh) { zu.schreibformel = s.fw; }
|
|
1235
1358
|
if (!r.pflicht) { zu.optional = true; }
|
|
@@ -1245,7 +1368,15 @@ export function baueVorlage(z) {
|
|
|
1245
1368
|
.map(function (w) {
|
|
1246
1369
|
var o = { punkt: w.punkt };
|
|
1247
1370
|
if (w.feld) { o.feld = w.feld; }
|
|
1248
|
-
|
|
1371
|
+
/* `leseformel`, nicht `formel`. `wendeAn` legt die Wege mit
|
|
1372
|
+
dem Schluessel `formel` ab, jeder Leser sucht in
|
|
1373
|
+
`lesenSonst` aber `leseformel` (vorlagen.js:462,
|
|
1374
|
+
vorlagenblatt.js:720). Die Formel jeder Ersatzquelle ging
|
|
1375
|
+
beim Aktualisieren still verloren: bei tasmota-steckdose
|
|
1376
|
+
landete der boolesche Alias an Geraeten ohne `stat.POWER`
|
|
1377
|
+
danach auf dem rohen JSON-Text von `tele.STATE` statt auf
|
|
1378
|
+
`POWER` (gemessen 09.09.2026). */
|
|
1379
|
+
if (w.formel) { o.leseformel = w.formel; }
|
|
1249
1380
|
return o;
|
|
1250
1381
|
});
|
|
1251
1382
|
if (rest.length) { zu.lesenSonst = (rest.length === 1) ? rest[0] : rest; }
|
|
@@ -1451,6 +1582,24 @@ export function zeigeVorlagenDialog() {
|
|
|
1451
1582
|
$('#dlg-tpl').showModal();
|
|
1452
1583
|
}
|
|
1453
1584
|
|
|
1585
|
+
/* Zeigt die Kennung auf eine fremde Vorlage?
|
|
1586
|
+
|
|
1587
|
+
`freierSchluessel` laeuft beim Radio-Wechsel und beim Vorbereiten,
|
|
1588
|
+
nicht nach der Handeingabe im Kennungsfeld. `aendereVorlagen` ersetzt
|
|
1589
|
+
bei gleicher Kennung ohne Rueckfrage (`liste[i] = vorlage`), im Modus
|
|
1590
|
+
„neu" sogar mit Version 1: Wer die Kennung einer anderen eigenen
|
|
1591
|
+
Vorlage eintippte, ueberschrieb sie still — gemessen 09.09.2026, aus
|
|
1592
|
+
„I30 Opfer" v7 mit einem Zustand wurde „Tasmota-Lampe" v1 mit
|
|
1593
|
+
vierzehn. Die eigene Quelle ist ausgenommen: sie zu ersetzen ist ja
|
|
1594
|
+
gerade der Sinn von „aktualisieren". */
|
|
1595
|
+
function fremdeMitId(k) {
|
|
1596
|
+
if (!k || !k.id) { return null; }
|
|
1597
|
+
var t = vorlageMitId(k.id);
|
|
1598
|
+
if (!t) { return null; }
|
|
1599
|
+
if (k.modus === 'update' && k.quelle && k.quelle.id === k.id) { return null; }
|
|
1600
|
+
return t;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1454
1603
|
function tplFeld(label, wert, aendern, breit) {
|
|
1455
1604
|
var l = el('label', 'feld');
|
|
1456
1605
|
l.appendChild(el('span', 'feldlabel', label));
|
|
@@ -1532,7 +1681,16 @@ export function zeichneVorlagenDialog() {
|
|
|
1532
1681
|
|
|
1533
1682
|
kb.appendChild(tplFeld(tr('tpls.nameDe'), k.nameDe, function (x) { k.nameDe = x; }, '150px'));
|
|
1534
1683
|
kb.appendChild(tplFeld(tr('tpls.nameEn'), k.nameEn, function (x) { k.nameEn = x; }, '150px'));
|
|
1535
|
-
|
|
1684
|
+
var kf = tplFeld(tr('tpls.id'), k.id, function (x) { k.id = schluesselAus(x); }, '150px');
|
|
1685
|
+
var stoert = fremdeMitId(k);
|
|
1686
|
+
if (stoert) {
|
|
1687
|
+
var kw = el('span', 'hint');
|
|
1688
|
+
kw.style.color = 'var(--bad)';
|
|
1689
|
+
kw.style.flexBasis = '100%';
|
|
1690
|
+
kw.textContent = tr('tpls.idTaken', textIn(stoert.name, sprache), stoert.version || 1);
|
|
1691
|
+
kf.appendChild(kw);
|
|
1692
|
+
}
|
|
1693
|
+
kb.appendChild(kf);
|
|
1536
1694
|
|
|
1537
1695
|
var lt = el('label', 'feld');
|
|
1538
1696
|
lt.appendChild(el('span', 'feldlabel', tr('tpls.deviceType')));
|
|
@@ -1578,6 +1736,13 @@ export function zeichneVorlagenDialog() {
|
|
|
1578
1736
|
var nh = el('div');
|
|
1579
1737
|
nh.style.padding = '0 12px 11px';
|
|
1580
1738
|
var nf = tplFeld(tr('tpls.nameHint'), k.namenshinweis, function (x) { k.namenshinweis = x; }, '260px');
|
|
1739
|
+
if (!hinweisTaugt(k.namenshinweis)) {
|
|
1740
|
+
var nw = el('span', 'hint');
|
|
1741
|
+
nw.style.color = 'var(--bad)';
|
|
1742
|
+
nw.style.flexBasis = '100%';
|
|
1743
|
+
nw.textContent = tr('tpls.nameHintBad');
|
|
1744
|
+
nf.appendChild(nw);
|
|
1745
|
+
}
|
|
1581
1746
|
nh.appendChild(nf);
|
|
1582
1747
|
var nt = el('div', 'hint', tr('tpls.nameHintHelp', k.geraetName));
|
|
1583
1748
|
nt.style.marginTop = '4px';
|
|
@@ -1595,7 +1760,10 @@ export function zeichneVorlagenDialog() {
|
|
|
1595
1760
|
mcb.checked = !!k.mehrfach;
|
|
1596
1761
|
mcb.addEventListener('change', function () {
|
|
1597
1762
|
k.mehrfach = mcb.checked;
|
|
1598
|
-
if (k.mehrfach && !k.instanz) {
|
|
1763
|
+
if (k.mehrfach && !k.instanz) {
|
|
1764
|
+
k.instanz = rateInstanz(z.zeilen, k.geraetName,
|
|
1765
|
+
String(k.kanalname || 'POWER%N%').split('%N%')[0]);
|
|
1766
|
+
}
|
|
1599
1767
|
zeichneVorlagenDialog();
|
|
1600
1768
|
});
|
|
1601
1769
|
ml.appendChild(mcb);
|
|
@@ -1615,7 +1783,7 @@ export function zeichneVorlagenDialog() {
|
|
|
1615
1783
|
mf.appendChild(mz);
|
|
1616
1784
|
mf.appendChild(el('div', 'hint',
|
|
1617
1785
|
k.instanz ? tr('tpls.multipleHelp', k.instanz) : tr('tpls.multipleNoNumber')));
|
|
1618
|
-
var fremd = andereAusgaenge(z.zeilen, k.instanz);
|
|
1786
|
+
var fremd = andereAusgaenge(z.zeilen, k.instanz, ausgangsVorspaenne(z.zeilen, k));
|
|
1619
1787
|
if (fremd.punkte.length) {
|
|
1620
1788
|
var fw2 = el('div', 'aside w');
|
|
1621
1789
|
fw2.style.margin = '9px 0 0';
|
|
@@ -1741,9 +1909,9 @@ export function zeichneVorlagenDialog() {
|
|
|
1741
1909
|
|
|
1742
1910
|
var pw = el('span', 'rx pf');
|
|
1743
1911
|
pw.style.fontFamily = 'var(--mono)';
|
|
1744
|
-
pw.title = zPfad(r, k, false) + (r.schreibenRoh ? ' → ' + zPfad(r, k, true) : '');
|
|
1745
|
-
pw.appendChild(document.createTextNode(zPfad(r, k, false)));
|
|
1746
|
-
if (r.schreibenRoh) { pw.appendChild(el('span', 'chip mut', '→ ' + zPfad(r, k, true))); }
|
|
1912
|
+
pw.title = zPfad(r, k, false, z.zeilen) + (r.schreibenRoh ? ' → ' + zPfad(r, k, true, z.zeilen) : '');
|
|
1913
|
+
pw.appendChild(document.createTextNode(zPfad(r, k, false, z.zeilen)));
|
|
1914
|
+
if (r.schreibenRoh) { pw.appendChild(el('span', 'chip mut', '→ ' + zPfad(r, k, true, z.zeilen))); }
|
|
1747
1915
|
if (r.lesenAbs) { pw.appendChild(el('span', 'chip warn', tr('tpls.absolute'))); }
|
|
1748
1916
|
row.appendChild(pw);
|
|
1749
1917
|
|
|
@@ -1849,7 +2017,8 @@ export function zeichneVorlagenDialog() {
|
|
|
1849
2017
|
|
|
1850
2018
|
var b = $('#btn-tpl-save');
|
|
1851
2019
|
if (b) {
|
|
1852
|
-
b.disabled = fehlerErk || !k.id ||
|
|
2020
|
+
b.disabled = fehlerErk || !k.id || !!fremdeMitId(k) ||
|
|
2021
|
+
!hinweisTaugt(k.namenshinweis) ||
|
|
1853
2022
|
!z.zeilen.filter(function (r) { return !r.weg; }).length;
|
|
1854
2023
|
b.textContent = k.modus === 'update' ? tr('tpls.saveUpdate') : tr('tpls.saveNew');
|
|
1855
2024
|
}
|