🚧 Change LED indicator alignments

This commit is contained in:
Jonas Bengtsson
2026-10-04 11:07:26 +02:00
parent d55a8b4718
commit d9b87c2caf
5 changed files with 119 additions and 16 deletions
+76
View File
@@ -837,6 +837,82 @@
], ],
"isBackground": false, "isBackground": false,
"problemMatcher": [] "problemMatcher": []
},
{
"label": "wago-sim: pytest vertical",
"type": "shell",
"command": ".venv\\Scripts\\python.exe",
"args": [
"-m",
"pytest",
"tests",
"-q",
"--tb=short"
],
"isBackground": false,
"problemMatcher": []
},
{
"label": "wago-sim: serve vertical",
"type": "shell",
"command": ".venv\\Scripts\\python.exe",
"args": [
"-m",
"wago_sim.cli",
"serve",
"--node",
"config/pump_node.yaml",
"--host",
"127.0.0.1",
"--port",
"5020",
"--web-port",
"8088"
],
"isBackground": true,
"problemMatcher": []
},
{
"label": "kill ports 3",
"type": "shell",
"command": ".venv\\Scripts\\python.exe",
"args": [
"-c",
"import subprocess; subprocess.run(['powershell','-NoProfile','-Command','Get-NetTCPConnection -LocalPort 5020,8088 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique | ForEach-Object { Stop-Process -Id $_ -Force }'], capture_output=True); print('killed')"
],
"isBackground": false,
"problemMatcher": []
},
{
"label": "wago-sim: serve vertical2",
"type": "shell",
"command": ".venv\\Scripts\\python.exe",
"args": [
"-m",
"wago_sim.cli",
"serve",
"--node",
"config/pump_node.yaml",
"--host",
"127.0.0.1",
"--port",
"5020",
"--web-port",
"8088"
],
"isBackground": true,
"problemMatcher": []
},
{
"label": "stop vertical serve",
"type": "shell",
"command": ".venv\\Scripts\\python.exe",
"args": [
"-c",
"import subprocess; subprocess.run(['powershell','-NoProfile','-Command','Get-NetTCPConnection -LocalPort 5020,8088 -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique | ForEach-Object { Stop-Process -Id $_ -Force }'], capture_output=True); print('stopped')"
],
"isBackground": false,
"problemMatcher": []
} }
] ]
} }
+2 -2
View File
@@ -347,8 +347,8 @@ requirement ID for each item in parentheses.
Signed support (2026-10-04): range bounds and simulated values Signed support (2026-10-04): range bounds and simulated values
may be negative (-65534–65535; stored as 16-bit two's may be negative (-65534–65535; stored as 16-bit two's
complement), for signed plant quantities. DI/DO channels render complement), for signed plant quantities. DI/DO channels render
in two rows (0–7 upper, 8–15 lower), mirroring two-deck WAGO in vertical columns of eight (0–7 left, 8–15 next) on thin,
modules. tall module bezels mirroring real DIN-rail housings.
- [ ] M6: scenario run view + alias-ownership rejection (FR-10.2.6, - [ ] M6: scenario run view + alias-ownership rejection (FR-10.2.6,
FR-10.2.9) — deferred. FR-10.2.9) — deferred.
- [x] Tests: 46 web-UI tests (`tests/test_webui.py`) via httpx - [x] Tests: 46 web-UI tests (`tests/test_webui.py`) via httpx
+5 -3
View File
@@ -897,9 +897,11 @@ Terminal monitors (§9) suffice for single engineers, but FAT benches are
the channel indicators carry only the 0-based channel number: below the channel indicators carry only the 0-based channel number: below
each DI/DO LED, and on analog modules outside the indicator stack, each DI/DO LED, and on analog modules outside the indicator stack,
above the overflow and below the underflow LED (FR-10.2.14) so the above the overflow and below the underflow LED (FR-10.2.14) so the
limit LEDs stay adjacent to the bar. DI/DO channels lay out in **two limit LEDs stay adjacent to the bar. DI/DO channels lay out in
rows** — channels 0–7 on the upper row, 8–15 on the lower row — **vertical columns of eight** — channels 0–7 in the left column,
mirroring the physical two-deck layout of 16-channel WAGO modules. 8–15 in the next — and modules are drawn as **thin, tall bezels**
mirroring the proportions of real WAGO DIN-rail housings (narrow
width, vertically stacked LEDs).
- FR-10.2.3: Values shall update live (WebSocket push or short-polling), - FR-10.2.3: Values shall update live (WebSocket push or short-polling),
including changes made by the PLC/master over Modbus, by scenarios, by including changes made by the PLC/master over Modbus, by scenarios, by
the console monitor, or by other UI clients — one shared source of truth. the console monitor, or by other UI clients — one shared source of truth.
+15 -7
View File
@@ -271,9 +271,16 @@ function renderRail() {
const el = document.createElement("div"); const el = document.createElement("div");
el.className = "module"; el.className = "module";
el.dataset.index = mod.index; el.dataset.index = mod.index;
// width: 8 channels per row (two-deck layout), min 110px const isDigital = mod.channels[0] &&
const perRow = Math.min(mod.channels.length, 8); (mod.channels[0].kind === "di" || mod.channels[0].kind === "do");
el.style.width = `${Math.max(110, 30 * perRow)}px`; // thin, tall bezels: digital modules get one 26px column per
// group of eight; analog modules keep their indicator-stack width
const digitalCount = mod.channels.filter(
(c) => c.kind === "di" || c.kind === "do").length;
const columns = Math.max(1, Math.ceil(digitalCount / 8));
el.style.width = isDigital
? `${Math.max(46, 26 * columns)}px`
: `${Math.max(90, 30 * mod.channels.length)}px`;
const kind = mod.channels[0] ? mod.channels[0].kind : "di"; const kind = mod.channels[0] ? mod.channels[0].kind : "di";
el.classList.add(`mod-${kind.startsWith("d") ? kind : "analog"}`); el.classList.add(`mod-${kind.startsWith("d") ? kind : "analog"}`);
@@ -283,14 +290,15 @@ function renderRail() {
const designation = firstCode.slice(0, firstCode.indexOf(".")); const designation = firstCode.slice(0, firstCode.indexOf("."));
let html = `<div class="mod-head" title="${mod.description}">${mod.article}</div>`; let html = `<div class="mod-head" title="${mod.description}">${mod.article}</div>`;
html += `<div class="mod-channels">`; html += `<div class="mod-channels ${isDigital ? "mod-channels-v" : ""}">`;
let chIndex = 0; // digital channels: two rows, 0-7 then 8-15 let chIndex = 0;
for (const ch of mod.channels) { for (const ch of mod.channels) {
const id = `ch-${ch.kind}-${mod.index}-${channelNo(ch.address)}`; const id = `ch-${ch.kind}-${mod.index}-${channelNo(ch.address)}`;
const chNo = String(channelNo(ch.address)); // 0-based channel only const chNo = String(channelNo(ch.address)); // 0-based channel only
if (ch.kind === "di" || ch.kind === "do") { if (isDigital) {
if (chIndex > 0 && chIndex % 8 === 0) { if (chIndex > 0 && chIndex % 8 === 0) {
html += `<span class="row-break"></span>`; // start the 8-15 row // new vertical column for channels 8-15
html += `</div><div class="mod-channels mod-channels-v col2">`;
} }
html += `<span class="led-cell"> html += `<span class="led-cell">
<span class="led" id="${id}" title="${ch.name} (${ch.address}, MB ${ch.modbus})" <span class="led" id="${id}" title="${ch.name} (${ch.address}, MB ${ch.modbus})"
+21 -4
View File
@@ -116,15 +116,32 @@ h2 { margin: 4px 0 14px; }
background: #4caf50; background: #4caf50;
box-shadow: 0 0 7px 2px rgba(76, 175, 80, 0.85); box-shadow: 0 0 7px 2px rgba(76, 175, 80, 0.85);
} }
.module { display: flex; flex-direction: column; justify-content: flex-end; } .module {
/* thin, tall bezel: the module's channel columns sit side by side */
flex-shrink: 0; /* keep the computed width — do not squeeze rows */
display: flex;
flex-direction: row;
align-items: flex-end;
justify-content: flex-start;
}
/* digital modules: tall enough for 8 vertically stacked LEDs + labels */
.module:has(.mod-channels-v) { min-height: 248px; }
.module { min-height: 124px; }
.mod-channels { .mod-channels {
display: flex; gap: 5px; flex-wrap: wrap; align-items: flex-end; display: flex; gap: 5px; flex-wrap: wrap; align-items: flex-end;
padding: 8px 6px 16px; /* bottom room for the designation label */ padding: 8px 6px 16px; /* bottom room for the designation label */
} }
/* vertical digital columns: LEDs stacked top-to-bottom, 8 per column */
.mod-channels-v {
flex-direction: column;
flex-wrap: nowrap;
align-items: center;
padding: 20px 4px 16px; /* top room for the article header */
gap: 4px;
}
.row-break { .row-break {
/* two-deck layout: force channels 8-15 onto a second row */ /* legacy two-row layout marker — vertical columns replace it */
flex-basis: 100%; display: none;
height: 0;
} }
/* DI/DO LED cells with channel-number labels (FR-10.2.2) */ /* DI/DO LED cells with channel-number labels (FR-10.2.2) */