iobroker.alias-workbench 0.9.10 → 0.9.11
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 +14 -0
- package/admin/css/werkbank.css +80 -0
- package/admin/js/baum.js +8 -0
- package/admin/js/detail.js +53 -6
- package/admin/js/entwurf.js +13 -3
- package/admin/js/ergebnis.js +34 -1
- package/admin/js/mqtt.js +23 -2
- package/admin/js/musterwahl.js +196 -29
- package/admin/js/objekte.js +31 -0
- package/admin/js/pruefungen.js +28 -2
- package/admin/js/schreiben.js +96 -49
- package/admin/js/verwendung.js +478 -0
- package/admin/js/vorlagen.js +75 -6
- package/admin/js/vorlagenblatt.js +134 -17
- package/admin/js/vorschlagen.js +50 -17
- package/admin/js/werte.js +95 -5
- package/admin/js/zuordnung.js +3 -1
- package/admin/js/zustand.js +3 -0
- package/admin/js/zustandsliste.js +22 -3
- package/admin/sprachen/de.json +54 -3
- package/admin/sprachen/en.json +54 -3
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -564,6 +564,20 @@ imported, and that both language files carry the same keys.
|
|
|
564
564
|
|
|
565
565
|
## Changelog
|
|
566
566
|
|
|
567
|
+
### 0.9.11 (2026-09-16)
|
|
568
|
+
* After saving, the next click in the tree asked **“unsaved changes”** — about an alias that had just been written. The “something unwritten is here” mark was only ever set, never cleared; it went away solely when the draft itself was dropped, i.e. on delete and on move. It now falls after every successful write — and only then: if something went wrong, the change really is not in yet, and the question stays justified. **There is a second effect that had been missing for a long time**: while the mark stood, the workbench deliberately did not rebuild the draft. After a write the old one therefore stayed, instead of rebuilding from the freshly written alias.
|
|
569
|
+
* A second press of **“Suggest free slots”** bent back a row you had set yourself. On the air conditioner: guess nine slots, set the role of the SPEED row to `level.speed` by hand, discard — and the next suggestion put the guessed `level.mode.fan` back. The pattern slot looked free, so the same row was guessed a second time; it then carried **both** marks, “changed” and “guessed”, which exclude each other — what was just guessed cannot have been changed before. Guessing now leaves any row marked “changed” alone. The slot stays free, and the note below says why. Putting a second row next to it was the alternative — the same source point would then hold two slots, and the eighth check would rightly report it.
|
|
570
|
+
* As soon as guesses were pending, the button in the pattern bar renamed itself to **“Discard suggestions”** — and a second one with the same word and the same effect stood in the blue bar below it. Two buttons, one handle. The bar now always says “Suggest free slots”, and only while something is left to guess; once everything is guessed it disappears. Discarding happens where it belongs: in the blue bar, next to “keep all”.
|
|
571
|
+
* Instead of „either every slot is taken, or no remaining datapoint fits" the note now states the case that actually applies — with the **names** of the slots that stay free: “SPEED, SWING … stays free. Every remaining datapoint already serves as a read or write source in a filled row”, or “… No remaining datapoint fits”. If a hand decision holds a slot open, a sentence is appended: “1 rows were decided by hand and are left untouched.” The “every slot taken” case stays silent unless asked — it says “nothing to do”, and would otherwise appear on nearly every device.
|
|
572
|
+
* Ricardo’s air conditioner is detected as a **thermostat** and fills 3 of 24 slots. **airCondition** fails because its only required slot, MODE, does not carry the role — and that settles it, since “does not fit” has the last word. With automatic suggestions it would reach **9 of 29** against 8 of 24. That now stands next to the pattern field: “· airCondition might fit better” — a single line, both numbers in the tooltip. **One** pattern is named — the best one — and only when it really is better; measured in filled slots, not in ratio. Next to it sits a small **→** button that does both in one go: switch to that pattern and press “Suggest free slots” right away — otherwise the note would be an announcement without an action. In the pattern list every affected row carries “with suggestions: fits” behind its reason. It is computed once when the device is selected and after that only on a row change — suggesting reads names, value lists, units and types, no measured values.
|
|
573
|
+
* In the **template sheet** and in the „save as template" dialog the device type showed its **pattern key** instead of its name: a blind read „blinds", a media player „mediaPlayer", a slider „levelSlider" — words that appear nowhere else in the interface. The stored value was right all along (the type, since 11.09.2026); only the label came from the wrong field, and since the Admin does not know those three keys, the translated name fell away with them. It now says the same as the pattern picker next to it: „Jalousien · blind".
|
|
574
|
+
* A **button** can now be created. `STOP` on a blind only writes — a key press has no state to read — and that used to defeat the **whole** alias: the dry run said “STOP: no source chosen”, “create now” stayed greyed out, and not a single blind alias ever got a STOP. The first diagnosis was wrong, and that deserves saying: it was not the write check being too strict. **ioBroker rejects an alias without a read source** — `{ id: { write: … } }` yields “Alias id is invalid: The id is empty!”. The correct shape is the one Ricardo has long been using on his ten hand-built STOPs: `alias.id` as plain text pointing at the command point, plus `common.read: false` on the alias point. That tells vis, Alexa and matter to draw a **button** rather than a toggle stuck at “off” forever. What decides is the **target**, not the role: `read: false` is written by the adapter itself (measured on a real `hm-rpc` STOP). If the target is readable the block stays — then the missing read source really is a defect. The button also survives being read back: all three places that reconstruct an alias now know it, otherwise `read` would quietly flip to `true` on the next update. **The same question grew into a new check**: “target can do what is asked”. The workbench writes to foreign points and reads from foreign points without ever checking whether they can do that. Three ways it silently falls apart — the write formula produces text and the target takes none; the target carries `write: false` (ioBroker stores the value anyway but the adapter passes nothing on: a silent dud); the read source carries `read: false` and never reports anything. All three now appear in the checks panel and as a chip on the ID in the detail view, right where the formula is typed
|
|
575
|
+
* The workbench creates MQTT **send points** itself — a `cmnd` point only comes into existence in ioBroker once something has been published to its topic; before that the alias would have nothing to write to. It took the type from the command knowledge, and `POWER` is listed there as `boolean` — right next to the write formula `val ? "ON" : "OFF"`. The two do not add up: the js-controller converts the formula result back to the type of the **target** on write, the `"ON"` becomes `true` again, and the formula sits in the alias without effect. Switching still works — Tasmota takes `on` as well as `true` — but the point looked different from every point mqtt-client creates, and the formula claimed something it did not do. On the production system it was **one of 196** alias points with an MQTT write target, and it came from us. The rule now is: **where a write formula exists, the point is created as `mixed`** — no exception for `POWER` but a rule that also covers `Fade` and `LedTable`. Without a formula the exact type stays, because the admin derives min, max and unit from `number`. **Plus two places where it becomes visible**: the target point's type now shows as a chip behind its ID in the detail view — right where the formula is typed — in warning colour when the two do not match. And the checks panel has an eighth check, "write formula takes effect", that names it instead of keeping quiet
|
|
576
|
+
* A template now counts as fitting only where its **mandatory rows** actually find their source. Until now detection alone decided, and detection asks a different question than application: `pruefeVorlage` checks whether the named **points** exist, the rows need particular **fields inside them**. A hand-built power-meter template whose mandatory row reads `tele.SENSOR · SML.Total_Summe` thus won on **17** ordinary Tasmota devices and delivered exactly one — and unchecked — row there, where the socket template would have built eleven. The device said "identified as: Tasmota power meter", and the draft was empty. Now it does not even enter the race there and the next best one takes the place; what was skipped is stated in the "why". Optional rows do not count, that is what optional means — measured against the stock, **none** of the fifteen other templates loses any of its 51 wins. **And the try-out in the template sheet**: it now also fetches the values of the points the rows read. Without them it judged fields it could not see, and the same template reported different numbers depending on what had been clicked before. It now shows per device how many rows would really come out, and on "does not fit" **which mandatory row is missing** — turning "22 devices matched, 17 of them won" into the honest "22 matched, 1 won · 21× does not fit". **And the try-out now computes like the creation does**: at first it compared only against the single highest-scoring other template — if that one did not fit the device, the template under test declared itself the winner. "Plain measuring point" thus reported a win on a Tasmota although the socket template sits in between and really fits there. It now walks the match list like `vorschlag` does; measured afterwards, all 150 pairs of template and device agree
|
|
577
|
+
* An alias whose source has vanished from the system is now visible without clicking it. Until now it was only said in the expanded detail and in the checks panel — the row in the list stayed quiet because it asked whether a source was **entered**, not whether it **exists**. The row now carries the red `!` with the note "source does not exist", the header a chip "1 point points nowhere" with the list in its tooltip, and the alias tree a red `!` on the affected channel. This is not cosmetic: the js-controller remembers "source missing" permanently and never corrects it — only deleting and recreating helps. It is computed once when the objects load, not on every redraw; on the production system 531 alias points in 5.2 ms. Two were found straight away: a window contact still pointing at the long-replaced `zigbee` adapter, and an NSPanel point with a missing `0_userdata` source for both reading and writing. A row with **no** source at all keeps its own note — it is unfinished, not broken
|
|
578
|
+
* New: **"Referenced in"** on an existing alias. The box below the change comparison searches on click for whatever else mentions the ID — the **scripts** of the javascript adapter with folder, name, state and line numbers, the **files** of the instance folders (that is, the vis and vis-2 views), every loaded **object** (where the matter bridges keep their members, for instance), the **enumerations** and the entries on the point itself (`SET → influxdb.0`) — and the same again for the **source**, including further aliases on it. That answers the question of what was left behind when moving to the alias. Measured on the production system with 24,706 objects and 66 files: the first look takes 1.3 seconds, every further alias after that 0.17. The web interfaces of the adapters are not searched — 160 of the 177 MB of text in the file tree, identical on every ioBroker, and they carry examples like `alias.0.Floor.Room.MotionSensor`; without that boundary the workbench would find its own source files as users of itself. Deliberately without a verdict: a source that is still mentioned is not a fault — a script that feeds it belongs exactly there (of 376 aliases with a source, 61 still had the source mentioned directly, a good share of them rightly so). **And the delete dialog now searches the scripts too** — that only existed when moving, although deleting is the irreversible one
|
|
579
|
+
* The field picker on a JSON point now copes with every key. Until now the workbench glued the field name onto the access with dots — which only works as long as every part is a valid JavaScript name. `gpu_usages.amd-vaapi.gpu` turned into an arithmetic expression (`?.amd - vaapi?.gpu`), `service.storage./dev/shm.free` and `cpu_usages.1111740.mem` into a syntax error. Measured on a Frigate telegram: of 233 offered fields **27** were usable, the other 206 ended in the red "formula broken" message. `feldFormel` now uses brackets where the key is not a name — `JSON.parse(val)?.gpu_usages?.['amd-vaapi']?.gpu ?? null` — and reads them back again, so the picker stays on the chosen field instead of falling back to "own formula". For ordinary names **not a single character** changes, so the shipped templates are untouched. A dot inside the key itself (`frigate.full_system`) remains unresolvable and still shows "own formula"
|
|
580
|
+
|
|
567
581
|
### 0.9.10 (2026-09-13)
|
|
568
582
|
* Four things from the full test run. **A channel identifier** was not sanitised in the multi-output path: from the channel name “Licht, Bar” the js-controller quietly made `Licht_ Bar`, while the workbench displayed “Licht, Bar” and wrote it into the enumerations — an identifier that never existed, and after deleting the alias it stayed there as a leftover. `ausgangName` now sanitises itself, and where two channels produce the same name the second gets a `_2`. **The caption** differed between the two views: where the point's name equals the row name, “Edit alias” showed it and “Create alias” an empty field. **The row list** now counts like the tree — `POWER2` before `POWER10`. And **an enumeration the workbench created itself** now also disappears when it becomes empty through a change of function, not only when the alias is deleted
|
|
569
583
|
* Tidying: "turn an alias object into a draft row" was written out five times — each with the same write-source rule and slightly different surroundings. Four bugs in this version came from exactly that: the lost write source on the roller shutter, a switching point that became a read-only one, the missing caption on `alias.0.Solar.Netz`, and a deviation the workbench had produced itself. The reading now lives in one place; what the four callers do with it — compare, create, overwrite — stays with them. Behaviour is unchanged
|
package/admin/css/werkbank.css
CHANGED
|
@@ -377,6 +377,50 @@
|
|
|
377
377
|
.aside.w { border-left-color:var(--warn); }
|
|
378
378
|
.aside b { color:var(--ink); }
|
|
379
379
|
|
|
380
|
+
/* Der Kasten „Wird genannt in“.
|
|
381
|
+
|
|
382
|
+
Zuerst mit den Klassen der Zustandsliste gebaut (.slot, .sn, .typ) -
|
|
383
|
+
die sind durchgehend Schreibmaschinenschrift, und der Kasten sah aus
|
|
384
|
+
wie Quelltext statt wie Text. Beschriftung links deshalb wie im
|
|
385
|
+
Detail (.k), Inhalt als gewoehnlicher Satz, und nur Kennungen in
|
|
386
|
+
Schreibmaschine. */
|
|
387
|
+
/* Der Blockkopf muss sich von den Zeilenbeschriftungen darunter
|
|
388
|
+
abheben - stand er in derselben grauen Versalschrift, las sich
|
|
389
|
+
„DER ALIAS“ wie eine weitere Zeile statt wie die Ueberschrift
|
|
390
|
+
darueber (Ricardo, 14.09.2026). Deshalb eigene Leiste mit Grund,
|
|
391
|
+
Akzentfarbe und etwas groesser. */
|
|
392
|
+
.vwkopf {
|
|
393
|
+
display:flex; align-items:baseline; gap:10px; flex-wrap:wrap;
|
|
394
|
+
padding:7px 13px; background:var(--surface-2);
|
|
395
|
+
border-top:1px solid var(--line); border-bottom:1px solid var(--line);
|
|
396
|
+
}
|
|
397
|
+
.vwkopf .t {
|
|
398
|
+
font-family:var(--sans); font-size:10px; text-transform:uppercase;
|
|
399
|
+
letter-spacing:.08em; color:var(--accent); font-weight:700;
|
|
400
|
+
}
|
|
401
|
+
.vwkopf .id { font-family:var(--mono); font-size:12px; color:var(--ink); word-break:break-all; }
|
|
402
|
+
.vwzeile {
|
|
403
|
+
display:grid; grid-template-columns:148px minmax(0,1fr); gap:10px;
|
|
404
|
+
align-items:start; padding:4px 13px; font-size:12px; color:var(--ink);
|
|
405
|
+
}
|
|
406
|
+
.vwzeile .k {
|
|
407
|
+
font-family:var(--sans); font-size:9px; text-transform:uppercase;
|
|
408
|
+
letter-spacing:.06em; color:var(--ink-3); font-weight:700; padding-top:3px;
|
|
409
|
+
}
|
|
410
|
+
.vwzeile .leer { color:var(--ink-3); }
|
|
411
|
+
.vwzeile .id { font-family:var(--mono); font-size:11.5px; word-break:break-all; }
|
|
412
|
+
.vwzeile ul { margin:0; padding-left:15px; }
|
|
413
|
+
.vwzeile li { margin:1px 0; line-height:1.5; }
|
|
414
|
+
.vwzeile li .id { font-weight:600; }
|
|
415
|
+
.vwzeile li .ord { color:var(--ink-3); }
|
|
416
|
+
.vwnote { padding:10px 13px 0; }
|
|
417
|
+
.vwnote:last-child { padding-bottom:12px; }
|
|
418
|
+
|
|
419
|
+
/* Der Alias im Baum, dessen Quelle es nicht mehr gibt. Klein und rot,
|
|
420
|
+
neben Geraetepunkt und Zahl - kein Kasten, keine Toenung: die Zeile
|
|
421
|
+
soll lesbar bleiben. */
|
|
422
|
+
.kaputt { color:var(--bad); font-weight:700; }
|
|
423
|
+
|
|
380
424
|
.empty { color:var(--ink-3); font-size:13px; padding:36px 8px; text-align:center; }
|
|
381
425
|
/* Der Erklaertext der leeren Seite: lesbar breit, nicht ueber die
|
|
382
426
|
ganze Flaeche gezogen, zweiter Absatz gedaempft. */
|
|
@@ -460,6 +504,29 @@
|
|
|
460
504
|
padding:2px 24px 2px 6px; width:290px; max-width:290px;
|
|
461
505
|
}
|
|
462
506
|
.vmeta { display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
|
|
507
|
+
/* „mit Raten: Klimaanlage 9/29, Thermostat 8/24" - der Hinweis neben
|
|
508
|
+
dem Musterfeld. Kein Chip: es ist keine Marke am Geraet, sondern ein
|
|
509
|
+
Weg, den man gehen kann. Deshalb Text in der Farbe der Vermutung,
|
|
510
|
+
nicht in der von Gut oder Schlecht. */
|
|
511
|
+
/* Die Breite ist auf die des Feldes darueber gedeckelt, und der Satz
|
|
512
|
+
darf umbrechen.
|
|
513
|
+
|
|
514
|
+
Mit `nowrap` war er eine 315 px breite Zeile, und weil der Block
|
|
515
|
+
seine Breite aus dem breitesten Kind nimmt, wurde der ganze
|
|
516
|
+
Musterblock breiter. Im Vorlagenkasten passte die Knopfleiste damit
|
|
517
|
+
nicht mehr in dieselbe Zeile, rutschte in die naechste - und deren
|
|
518
|
+
`margin-top:16px` plus der Zeilenabstand von 18 px standen dann als
|
|
519
|
+
34 px Leere zwischen Hinweis und Knopf (Ricardo, 15.09.2026). Es
|
|
520
|
+
fehlten drei Pixel; jede Wortaenderung haette es wieder kippen
|
|
521
|
+
koennen. Ein Deckel ist verlaesslicher als ein knapper Umbruch. */
|
|
522
|
+
.ratAussicht { font-size:11px; color:var(--accent); max-width:290px;
|
|
523
|
+
display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
|
|
524
|
+
/* Der Knopf traegt die Farbe des Hinweises, nicht die eines
|
|
525
|
+
gewoehnlichen Knopfes - er gehoert zum Satz daneben. Reicht die
|
|
526
|
+
Breite nicht, rutscht er unter den Text statt den Block zu
|
|
527
|
+
verbreitern (siehe den Deckel darueber). */
|
|
528
|
+
.rtgo { color:var(--accent); border-color:var(--accent); flex:none;
|
|
529
|
+
font-size:12px; line-height:1; padding:1px 6px; }
|
|
463
530
|
|
|
464
531
|
/* ---- Geraetekarte ---- */
|
|
465
532
|
|
|
@@ -715,6 +782,14 @@
|
|
|
715
782
|
.tree .geraetepunkt { margin-left:auto; font-size:8px; color:var(--accent); }
|
|
716
783
|
.tree .node.sel .geraetepunkt { color:var(--accent-ink); }
|
|
717
784
|
.tree .geraetepunkt + .cnt { margin-left:5px; }
|
|
785
|
+
/* Die Warnung steht in derselben rechten Gruppe wie Geraetepunkt und
|
|
786
|
+
Zahl - und VOR dem Punkt, damit Punkt und Zahl ihr Paar behalten.
|
|
787
|
+
Stand sie dazwischen, verlor `.geraetepunkt + .cnt` seinen Griff und
|
|
788
|
+
die Zahl rutschte allein an den rechten Rand (Ricardo, 14.09.2026). */
|
|
789
|
+
.tree .kaputt { margin-left:auto; font-size:10px; line-height:1; }
|
|
790
|
+
.tree .kaputt + .geraetepunkt { margin-left:6px; }
|
|
791
|
+
.tree .kaputt + .cnt { margin-left:6px; }
|
|
792
|
+
.tree .node.sel .kaputt { color:var(--accent-ink); }
|
|
718
793
|
|
|
719
794
|
.erow .fehltnoch {
|
|
720
795
|
margin-left:7px; font-style:normal; font-size:10px; font-weight:700;
|
|
@@ -814,6 +889,7 @@
|
|
|
814
889
|
/* Gleiche Strichstaerke wie Vorlage und Ausgang daneben - halbfett
|
|
815
890
|
wirkte wie eine andere Schrift in der Zeile. */
|
|
816
891
|
.suggestbox .verdictwrap .musterwahl { font-weight:400; width:250px; max-width:250px; }
|
|
892
|
+
.suggestbox .ratAussicht { max-width:250px; }
|
|
817
893
|
.suggestbox .vmeta { font-size:10.5px; color:var(--ink-3); gap:6px; }
|
|
818
894
|
.suggestbox .luecke { flex:1 1 20px; }
|
|
819
895
|
.suggestbox .knoepfe { display:flex; gap:7px; align-items:flex-end; }
|
|
@@ -936,6 +1012,10 @@
|
|
|
936
1012
|
}
|
|
937
1013
|
.listbar .verdictwrap.inbar .musterwahl { font-size:12px; width:230px; max-width:230px; }
|
|
938
1014
|
.listbar .verdictwrap.inbar .vmeta { margin:0; }
|
|
1015
|
+
/* In der Leiste steht der Hinweis NEBEN dem Feld (Reihe statt Spalte)
|
|
1016
|
+
und hat die ganze Breite der Leiste zur Verfuegung - dort ist eine
|
|
1017
|
+
Zeile richtig; reicht sie nicht, bricht die Leiste selbst um. */
|
|
1018
|
+
.listbar .verdictwrap.inbar .ratAussicht { max-width:none; white-space:nowrap; flex-wrap:nowrap; }
|
|
939
1019
|
|
|
940
1020
|
.feldwrap { position:relative; display:block; min-width:0; }
|
|
941
1021
|
/* Das Musterfeld ist ein Eingabefeld, sieht aber aus wie das Auswahlfeld
|
package/admin/js/baum.js
CHANGED
|
@@ -412,6 +412,14 @@ function renderKinder(knoten, filter, tiefe) {
|
|
|
412
412
|
ueberschreibt ihn die Zeile darueber wieder. */
|
|
413
413
|
if (leer) { d.title = (d.title ? d.title + ' · ' : '') + tr('tree.emptyNode'); }
|
|
414
414
|
if (waehlbar) {
|
|
415
|
+
/* Ein Alias, dessen Quelle es nicht mehr gibt, faellt sonst nur
|
|
416
|
+
auf, wenn man ihn anklickt und die Zeile aufklappt. Die Menge
|
|
417
|
+
steht fertig in S.aliasKaputt - hier wird nur nachgeschlagen. */
|
|
418
|
+
if (S.aliasKaputt[k.id]) {
|
|
419
|
+
var kp = el('span', 'kaputt', '!');
|
|
420
|
+
kp.title = tr('tree.sourceGone', S.aliasKaputt[k.id]);
|
|
421
|
+
d.appendChild(kp);
|
|
422
|
+
}
|
|
415
423
|
if (wohlGeraet(k)) {
|
|
416
424
|
var pk = el('span', 'geraetepunkt', '●');
|
|
417
425
|
pk.title = tr('tree.looksLikeDevice');
|
package/admin/js/detail.js
CHANGED
|
@@ -4,7 +4,7 @@ import { S } from './zustand.js';
|
|
|
4
4
|
import { socket } from './verbindung.js';
|
|
5
5
|
import { el } from './basis.js';
|
|
6
6
|
import { tr } from './sprache.js';
|
|
7
|
-
import { wertVon, jsonFelder, feldAusFormel, feldFormel, feldWert, jsonVon } from './werte.js';
|
|
7
|
+
import { wertVon, jsonFelder, feldAusFormel, feldFormel, feldWert, jsonVon, zielKann } from './werte.js';
|
|
8
8
|
import { musterVon, erkenneEntwurf, platzFuerRolle, typenFuerRolle } from './erkennung.js';
|
|
9
9
|
import { musterName } from './musternamen.js';
|
|
10
10
|
import { rollenFeld } from './rollenwahl.js';
|
|
@@ -71,15 +71,59 @@ export function detailZeile(e, s, idx) {
|
|
|
71
71
|
steht die volle Kennung als eigene Zeile unter dem Feld. Dieselbe
|
|
72
72
|
Bauart wie „Die Vorlage will hier: …": sie darf umbrechen und hat
|
|
73
73
|
damit keine Breitengrenze. */
|
|
74
|
-
function mitVollId(felder, id) {
|
|
74
|
+
function mitVollId(felder, id, zusatz) {
|
|
75
75
|
var w = el('div', 'feldstapel');
|
|
76
76
|
w.appendChild(felder);
|
|
77
77
|
var v = el('div', 'vollid');
|
|
78
78
|
if (id) { v.textContent = id; v.title = id; }
|
|
79
|
+
/* Beim Schreibziel steht hinter der Kennung sein Typ. Er entscheidet,
|
|
80
|
+
ob die Schreibformel ueberhaupt ankommt - der js-controller rechnet
|
|
81
|
+
den Formelwert auf den Typ des ZIELS zurueck. Man tippt die Formel
|
|
82
|
+
also an einer Stelle, an der man bisher nicht sehen konnte, ob sie
|
|
83
|
+
etwas bewirkt (Ricardo, 15.09.2026). */
|
|
84
|
+
if (zusatz) { v.appendChild(zusatz); }
|
|
79
85
|
w.appendChild(v);
|
|
80
86
|
return w;
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
/* Was der fremde Punkt kann, als kleine Marke hinter seiner Kennung.
|
|
90
|
+
|
|
91
|
+
Beide Seiten zeigen dasselbe, nur aus verschiedener Richtung: am
|
|
92
|
+
Schreibziel seinen Typ (er entscheidet, was von der Formel ankommt)
|
|
93
|
+
und ob es sich ueberhaupt beschreiben laesst; an der Lesequelle, ob
|
|
94
|
+
sie etwas meldet. `zielKann` in werte.js ist die eine Stelle, die
|
|
95
|
+
darueber urteilt - die Pruefungen-Karte fragt dieselbe Funktion. */
|
|
96
|
+
function zielMarke(s, seite) {
|
|
97
|
+
var id = seite === 'w' ? s.srcW : s.srcR;
|
|
98
|
+
if (!id) { return null; }
|
|
99
|
+
var o = S.objects[id];
|
|
100
|
+
if (!o || !o.common) { return null; }
|
|
101
|
+
var c = o.common;
|
|
102
|
+
|
|
103
|
+
var maengel = zielKann(s).filter(function (x) {
|
|
104
|
+
return seite === 'w' ? x.art !== 'meldetNichts' : x.art === 'meldetNichts';
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
var text, hinweis;
|
|
108
|
+
if (seite === 'w') {
|
|
109
|
+
if (!c.type) { return null; }
|
|
110
|
+
text = c.type;
|
|
111
|
+
var nw = maengel.some(function (x) { return x.art === 'nichtSchreibbar'; });
|
|
112
|
+
var bl = maengel.some(function (x) { return x.art === 'formelBlind'; });
|
|
113
|
+
hinweis = nw ? tr('detail.targetNoWrite')
|
|
114
|
+
: (bl ? tr('detail.targetTypeVoid', c.type) : tr('detail.targetTypeHint', c.type));
|
|
115
|
+
if (nw) { text = c.type + ' \u00b7 ' + tr('detail.notWritable'); }
|
|
116
|
+
} else {
|
|
117
|
+
if (!maengel.length) { return null; }
|
|
118
|
+
text = tr('detail.reportsNothing');
|
|
119
|
+
hinweis = tr('detail.targetNoRead');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var m = el('span', 'chip ' + (maengel.length ? 'warn' : 'mut'), text);
|
|
123
|
+
m.style.marginLeft = '7px';
|
|
124
|
+
m.title = hinweis;
|
|
125
|
+
return m;
|
|
126
|
+
}
|
|
83
127
|
/* ---- Wo die Vorlage etwas anderes will --------------------------
|
|
84
128
|
|
|
85
129
|
Die zugeklappte Zeile sagt nur, DASS etwas abweicht. Welches Feld,
|
|
@@ -282,7 +326,7 @@ export function detailZeile(e, s, idx) {
|
|
|
282
326
|
lF.appendChild(selF);
|
|
283
327
|
qf.appendChild(lF);
|
|
284
328
|
}
|
|
285
|
-
zeigeBeide(zeile(tr('detail.readsFrom'), mitVollId(qf, s.srcR)), ['srcR']);
|
|
329
|
+
zeigeBeide(zeile(tr('detail.readsFrom'), mitVollId(qf, s.srcR, zielMarke(s, 'r'))), ['srcR']);
|
|
286
330
|
|
|
287
331
|
if (s.frei) {
|
|
288
332
|
var iFrei = el('input', 'tx w');
|
|
@@ -322,7 +366,7 @@ export function detailZeile(e, s, idx) {
|
|
|
322
366
|
if (s.srcW && s.srcW !== s.srcR) {
|
|
323
367
|
wf.appendChild(el('div', 'sugg', tr('detail.separateHint')));
|
|
324
368
|
}
|
|
325
|
-
zeigeBeide(zeile(tr('detail.writesTo'), mitVollId(wf, s.srcW)), ['srcW']);
|
|
369
|
+
zeigeBeide(zeile(tr('detail.writesTo'), mitVollId(wf, s.srcW, zielMarke(s, 'w'))), ['srcW']);
|
|
326
370
|
|
|
327
371
|
/* --- Leseformel --- */
|
|
328
372
|
var fb = el('div');
|
|
@@ -436,7 +480,10 @@ export function detailZeile(e, s, idx) {
|
|
|
436
480
|
iU.value = s.unit || '';
|
|
437
481
|
iU.placeholder = '—';
|
|
438
482
|
iU.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
439
|
-
|
|
483
|
+
/* `rateBehalten` gehoert zu jedem `geaendert` dazu - sonst traegt die
|
|
484
|
+
Zeile beim naechsten Zeichnen beide Marken („geaendert" UND
|
|
485
|
+
„vermutet"), und die schliessen sich aus (Ricardo, 15.09.2026). */
|
|
486
|
+
iU.addEventListener('input', function () { s.unit = iU.value; s.geaendert = true; rateBehalten(s); });
|
|
440
487
|
iU.addEventListener('change', function () { neu(); });
|
|
441
488
|
var lU = el('label', 'fld');
|
|
442
489
|
lU.appendChild(el('span', null, tr('detail.unit')));
|
|
@@ -461,7 +508,7 @@ export function detailZeile(e, s, idx) {
|
|
|
461
508
|
iC.value = s.caption || '';
|
|
462
509
|
iC.placeholder = s.n;
|
|
463
510
|
iC.addEventListener('click', function (ev) { ev.stopPropagation(); });
|
|
464
|
-
iC.addEventListener('input', function () { s.caption = iC.value; s.geaendert = true; });
|
|
511
|
+
iC.addEventListener('input', function () { s.caption = iC.value; s.geaendert = true; rateBehalten(s); });
|
|
465
512
|
/* Was das Feld tut, stand nirgends - man tippte und sah keine
|
|
466
513
|
Wirkung. Der Wert wird der Anzeigename des Punkts (common.name)
|
|
467
514
|
und erscheint als Zusatz in der Zeile darueber. */
|
package/admin/js/entwurf.js
CHANGED
|
@@ -102,8 +102,14 @@ export function baueEntwurf(kanal) {
|
|
|
102
102
|
states: c.states || undefined,
|
|
103
103
|
wr: !!c.write,
|
|
104
104
|
/* Ohne common.alias ist der Punkt seine eigene Quelle. Vorher stand
|
|
105
|
-
hier nichts und man musste jede Zeile von Hand zuweisen.
|
|
106
|
-
|
|
105
|
+
hier nichts und man musste jede Zeile von Hand zuweisen.
|
|
106
|
+
|
|
107
|
+
Ein Taster hat keine Lesequelle, obwohl `alias.id` eine Kennung
|
|
108
|
+
nennt: ioBroker verlangt sie, `common.read: false` sagt, dass dort
|
|
109
|
+
nichts abzuholen ist (K23). Dieselbe Ablesung wie in
|
|
110
|
+
`zeileAusAlias` — sonst zeigte „Alias bearbeiten" eine Quelle,
|
|
111
|
+
die „Alias anlegen" nie eingetragen hat. */
|
|
112
|
+
srcR: istAlias ? ((c.read === false && q.einfach) ? '' : (q.read || '')) : id,
|
|
107
113
|
/* Steht in `common.alias.id` ein einzelner Text, gilt er fuer Lesen
|
|
108
114
|
UND Schreiben - dann ist die Schreibquelle dieselbe wie die
|
|
109
115
|
Lesequelle. Hier stand frueher eine leere Zeichenkette, gedacht
|
|
@@ -1214,7 +1220,11 @@ export function bestandVorrang(e, aliasId) {
|
|
|
1214
1220
|
if (c.name !== undefined) { s.caption = ist.caption; }
|
|
1215
1221
|
s.f = ist.f;
|
|
1216
1222
|
s.fw = ist.fw;
|
|
1217
|
-
|
|
1223
|
+
/* `ist.srcR`, nicht `q.read`: bei einem Taster laesst
|
|
1224
|
+
`zeileAusAlias` die Lesequelle bewusst leer, obwohl `alias.id` eine
|
|
1225
|
+
Kennung nennt (K23). Die Wache auf `q.read` bleibt — sie verhindert,
|
|
1226
|
+
dass ein Alias ohne Quellangabe die vorhandene ueberschreibt. */
|
|
1227
|
+
if (q.read) { s.srcR = ist.srcR; }
|
|
1218
1228
|
s.srcW = ist.srcW;
|
|
1219
1229
|
s.on = true;
|
|
1220
1230
|
s.ausBestand = true;
|
package/admin/js/ergebnis.js
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { setzeEnums, setzeZiel, unterschiede } from './zuordnung.js';
|
|
39
39
|
import './detail.js';
|
|
40
40
|
import { zeigeTausch, tauscheAus } from './quellentausch.js';
|
|
41
|
+
import { baueVerwendungKarte } from './verwendung.js';
|
|
41
42
|
|
|
42
43
|
import { baueChecks } from './pruefungen.js';
|
|
43
44
|
import {
|
|
@@ -141,7 +142,15 @@ var warumAuf = false;
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
export function entwurfAngefasst() {
|
|
144
|
-
if (S.entwurf) {
|
|
145
|
+
if (S.entwurf) {
|
|
146
|
+
S.entwurf.angefasst = true;
|
|
147
|
+
/* Die Aussicht „was passt nach dem Raten" haengt allein an den
|
|
148
|
+
Zeilen. Sie wird einmal gerechnet und gemerkt; eine Zeilen-
|
|
149
|
+
aenderung ist der einzige Anlass, sie fallen zu lassen. Beim
|
|
150
|
+
blossen Neuzeichnen bleibt sie stehen - das Raten liest keine
|
|
151
|
+
Messwerte, es kaeme dasselbe heraus. */
|
|
152
|
+
S.entwurf.rateAussicht = null;
|
|
153
|
+
}
|
|
145
154
|
}
|
|
146
155
|
|
|
147
156
|
export function neuZeichnenOderAufbauen(danach) {
|
|
@@ -323,6 +332,21 @@ var warumAuf = false;
|
|
|
323
332
|
will, musste ihn bisher im Quellenbaum von Hand wiederfinden.
|
|
324
333
|
Umgekehrt gilt dasselbe: steht man am Geraet und will nachsehen,
|
|
325
334
|
was daraus geworden ist, fuehrt der zweite Knopf hin. */
|
|
335
|
+
/* Zeigt ein Punkt auf eine Quelle, die es nicht gibt, gehoert das
|
|
336
|
+
nach oben - nicht nur in die Zeile und in den Pruefungen-Kasten.
|
|
337
|
+
Der js-controller repariert diesen Zustand nie von allein. */
|
|
338
|
+
var insLeere = (S.entwurf && S.entwurf.states || []).filter(function (s) {
|
|
339
|
+
return s.on && s.srcR && !S.objects[s.srcR];
|
|
340
|
+
});
|
|
341
|
+
if (insLeere.length) {
|
|
342
|
+
sub.appendChild(document.createTextNode(' '));
|
|
343
|
+
var ml = el('span', 'chip bad', insLeere.length === 1
|
|
344
|
+
? tr('result.sourceGone1')
|
|
345
|
+
: tr('result.sourceGoneN', insLeere.length));
|
|
346
|
+
ml.title = insLeere.map(function (s) { return s.n + ' → ' + s.srcR; }).join('\n');
|
|
347
|
+
sub.appendChild(ml);
|
|
348
|
+
}
|
|
349
|
+
|
|
326
350
|
var sprung = null, sprungZiel = null, sprungModus = null;
|
|
327
351
|
if (S.current.indexOf('alias.') === 0) {
|
|
328
352
|
sprungZiel = quelleVon(S.current);
|
|
@@ -971,6 +995,15 @@ var warumAuf = false;
|
|
|
971
995
|
/* --- Änderungen gegenüber dem, was gespeichert ist --- */
|
|
972
996
|
baueDiffKarte(host, e);
|
|
973
997
|
|
|
998
|
+
/* --- Wo wird das benutzt? ---
|
|
999
|
+
|
|
1000
|
+
Nur an einem Alias, den es schon gibt: an einem Entwurf, der noch
|
|
1001
|
+
nicht geschrieben ist, kann ihn niemand nennen. */
|
|
1002
|
+
var vAlias = e.ziel || e.kanal;
|
|
1003
|
+
if (vAlias && String(vAlias).indexOf('alias.') === 0 && S.objects[vAlias]) {
|
|
1004
|
+
baueVerwendungKarte(host, vAlias, quelleVon(vAlias));
|
|
1005
|
+
}
|
|
1006
|
+
|
|
974
1007
|
/* --- Prüfungen, Fusszeile, Knopfleiste --- */
|
|
975
1008
|
setzeKnoepfe(e, haupt, pflichtFehlt);
|
|
976
1009
|
}
|
package/admin/js/mqtt.js
CHANGED
|
@@ -655,6 +655,27 @@ function mqttCustom(g, thema) {
|
|
|
655
655
|
zurueckzulesen ist ein Wettlauf: die Sicht hinkt dem frisch
|
|
656
656
|
geschriebenen Objekt einen Wimpernschlag hinterher, und der
|
|
657
657
|
Trockenlauf sperrte weiter wegen eines Punktes, den es laengst gab. */
|
|
658
|
+
/* Der Typ eines Sendepunktes: `mixed`, sobald eine Schreibformel im
|
|
659
|
+
Spiel ist.
|
|
660
|
+
|
|
661
|
+
Sonst rechnet der js-controller den Formelwert beim Schreiben auf den
|
|
662
|
+
Typ des Ziels zurueck, und die Formel war umsonst: `POWER` traegt im
|
|
663
|
+
Befehlswissen `typ: boolean` UND `schreibformel: val ? "ON" : "OFF"`.
|
|
664
|
+
Aus dem "ON" wurde am boolean-Ziel wieder true, auf der Leitung stand
|
|
665
|
+
true statt ON. Geschaltet hat die Tasmota trotzdem — sie nimmt beides —,
|
|
666
|
+
aber die Formel stand nutzlos im Alias, und der von der Werkbank
|
|
667
|
+
angelegte Punkt sah anders aus als jeder von mqtt-client angelegte
|
|
668
|
+
(die sind alle `mixed`). Am Produktivsystem war es genau EINER von 196
|
|
669
|
+
(Ricardo, 15.09.2026, an alias.0.Bastelzimmer.Deckenlicht).
|
|
670
|
+
|
|
671
|
+
Keine Ausnahme fuer POWER, sondern eine Regel, die sich selbst
|
|
672
|
+
durchsetzt — sie gilt ebenso fuer `Fade` und `LedTable`. Wo keine
|
|
673
|
+
Formel steht, bleibt der genaue Typ: aus `number` zieht der Admin
|
|
674
|
+
Min, Max und Einheit. */
|
|
675
|
+
function punktTyp(w) {
|
|
676
|
+
return w.schreibformel ? 'mixed' : (w.typ || 'mixed');
|
|
677
|
+
}
|
|
678
|
+
|
|
658
679
|
export function mqttEinzelnStill(kanal, name, fertig) {
|
|
659
680
|
var bau = mqttPunktBauen(kanal, name, 'neu');
|
|
660
681
|
if (!bau) { return fertig && fertig(); }
|
|
@@ -694,7 +715,7 @@ function mqttPunktBauen(kanal, name, was) {
|
|
|
694
715
|
var common0 = {
|
|
695
716
|
name: name,
|
|
696
717
|
role: w0.rolle || 'state',
|
|
697
|
-
type: w0
|
|
718
|
+
type: punktTyp(w0),
|
|
698
719
|
read: true, write: true,
|
|
699
720
|
desc: tr('mq.createdBy'),
|
|
700
721
|
custom: mqttCustom(g, thema0)
|
|
@@ -728,7 +749,7 @@ export function mqttEinzeln(kanal, name, was, knopf) {
|
|
|
728
749
|
var common = {
|
|
729
750
|
name: name,
|
|
730
751
|
role: w.rolle || 'state',
|
|
731
|
-
type: w
|
|
752
|
+
type: punktTyp(w),
|
|
732
753
|
read: true, write: true,
|
|
733
754
|
desc: tr('mq.createdBy'),
|
|
734
755
|
custom: mqttCustom(g, thema)
|