🚧 Make analog indicators clickable

This commit is contained in:
Jonas Bengtsson
2026-10-04 11:22:55 +02:00
parent 4452f75433
commit 7948b794f5
4 changed files with 119 additions and 1 deletions
+90
View File
@@ -913,6 +913,96 @@
], ],
"isBackground": false, "isBackground": false,
"problemMatcher": [] "problemMatcher": []
},
{
"label": "wago-sim: pytest barclick",
"type": "shell",
"command": ".venv\\Scripts\\python.exe",
"args": [
"-m",
"pytest",
"tests",
"-q",
"--tb=short"
],
"isBackground": false,
"problemMatcher": []
},
{
"label": "wago-sim: serve barclick",
"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 4",
"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 barclick2",
"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": "wago-sim: pytest webui final",
"type": "shell",
"command": ".venv\\Scripts\\python.exe",
"args": [
"-m",
"pytest",
"tests/test_webui.py",
"-q",
"--tb=short"
],
"isBackground": false,
"problemMatcher": []
},
{
"label": "stop barclick 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": []
} }
] ]
} }
+4
View File
@@ -910,6 +910,10 @@ Terminal monitors (§9) suffice for single engineers, but FAT benches are
defined); `pulse` (with duration) and `ramp` (target + duration) actions defined); `pulse` (with duration) and `ramp` (target + duration) actions
on DI/DO/AI. All writes go through the datastore's Modbus-equivalent on DI/DO/AI. All writes go through the datastore's Modbus-equivalent
paths (FR-9.2.6) so mirrors, watchdog, and statistics stay consistent. paths (FR-9.2.6) so mirrors, watchdog, and statistics stay consistent.
Clicking an analog **bar indicator** (requested 2026-10-04) sets the
value proportionally to the click position within the channel's
display range (FR-10.2.14) — AI = field side, AO = master-side force;
exact values remain enterable via the channel list.
- FR-10.2.5: **Diagnostics view**: watchdog status (0x1006), statistics - FR-10.2.5: **Diagnostics view**: watchdog status (0x1006), statistics
block (0x1029, with reset button), module diagnosis (0x1050), LED error block (0x1029, with reset button), module diagnosis (0x1050), LED error
registers (0x1020/0x1021) — mirroring WBM pages of the real device — registers (0x1020/0x1021) — mirroring WBM pages of the real device —
+24 -1
View File
@@ -312,7 +312,8 @@ function renderRail() {
html += `<span class="abar-cell"> html += `<span class="abar-cell">
<span class="abar-label top-label">${chNo}</span> <span class="abar-label top-label">${chNo}</span>
<span class="lim-led lim-over" id="${id}-over" title="overflow (&gt; ${rg.max})"></span> <span class="lim-led lim-over" id="${id}-over" title="overflow (&gt; ${rg.max})"></span>
<span class="abar" id="${id}" title="${ch.name} (${ch.address}, MB ${ch.modbus})"> <span class="abar" id="${id}" title="${ch.name} (${ch.address}, MB ${ch.modbus})"
onclick="clickBar('${ch.kind}', ${mod.index}, ${channelNo(ch.address)}, event)">
<span class="abar-fill"></span></span> <span class="abar-fill"></span></span>
<span class="lim-led lim-under" id="${id}-under" title="underflow (&lt; ${rg.min})"></span> <span class="lim-led lim-under" id="${id}-under" title="underflow (&lt; ${rg.min})"></span>
<span class="abar-label bottom-label">${chNo}</span> <span class="abar-label bottom-label">${chNo}</span>
@@ -392,6 +393,28 @@ async function clickChannel(kind, module, channel) {
} }
} }
async function clickBar(kind, module, channel, ev) {
if (READONLY) return;
const bar = ev.currentTarget;
const rect = bar.getBoundingClientRect();
// click height -> fraction of the bar: bottom = range min, top = max
const frac = Math.min(1, Math.max(0, 1 - (ev.clientY - rect.top) / rect.height));
const chInfo = findChannel(kind, module, channel);
const rg = (chInfo && chInfo.range) || {min: 0, max: 4095};
const value = Math.round(rg.min + frac * (rg.max - rg.min));
try {
await api(`/api/channels/${kind}/${module}/${channel}`, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({action: "set", value}),
});
const res = await api("/api/values");
applyValues(res.values);
} catch (e) {
console.warn("write rejected:", e.message);
}
}
// ---------------- diagnostics ---------------- // ---------------- diagnostics ----------------
async function renderDiagnostics(diag) { async function renderDiagnostics(diag) {
+1
View File
@@ -190,6 +190,7 @@ h2 { margin: 4px 0 14px; }
border-radius: 3px; border-radius: 3px;
overflow: hidden; /* clip the fill inside the rounded bar */ overflow: hidden; /* clip the fill inside the rounded bar */
display: inline-block; display: inline-block;
cursor: pointer; /* clickable: sets value at click height */
} }
.abar-fill { .abar-fill {
position: absolute; bottom: 0; left: 0; right: 0; height: 0%; position: absolute; bottom: 0; left: 0; right: 0; height: 0%;