diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d068363..f1dba98 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -913,6 +913,96 @@ ], "isBackground": false, "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": [] } ] } \ No newline at end of file diff --git a/docs/spec/software-spec.md b/docs/spec/software-spec.md index cba7f85..34d475f 100644 --- a/docs/spec/software-spec.md +++ b/docs/spec/software-spec.md @@ -910,6 +910,10 @@ Terminal monitors (ยง9) suffice for single engineers, but FAT benches are defined); `pulse` (with duration) and `ramp` (target + duration) actions 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. + 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 block (0x1029, with reset button), module diagnosis (0x1050), LED error registers (0x1020/0x1021) โ€” mirroring WBM pages of the real device โ€” diff --git a/src/wago_sim/webui/static/app.js b/src/wago_sim/webui/static/app.js index 5d51431..293a638 100644 --- a/src/wago_sim/webui/static/app.js +++ b/src/wago_sim/webui/static/app.js @@ -312,7 +312,8 @@ function renderRail() { html += ` ${chNo} - + ${chNo} @@ -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 ---------------- async function renderDiagnostics(diag) { diff --git a/src/wago_sim/webui/static/style.css b/src/wago_sim/webui/static/style.css index 1fbed41..e3e5050 100644 --- a/src/wago_sim/webui/static/style.css +++ b/src/wago_sim/webui/static/style.css @@ -190,6 +190,7 @@ h2 { margin: 4px 0 14px; } border-radius: 3px; overflow: hidden; /* clip the fill inside the rounded bar */ display: inline-block; + cursor: pointer; /* clickable: sets value at click height */ } .abar-fill { position: absolute; bottom: 0; left: 0; right: 0; height: 0%;