22 Commits

Author SHA1 Message Date
a665993a7c feat(da07): decode ~P channel-serial frames; verify ~H on hardware; refit channel columns on device switch
Second hardware capture (saved as tests/da07/fixtures/capture-2026-06-12-
steady-state.txt, now with "> "-prefixed outbound lines) settles the remaining
protocol questions:

- ~P is a per-channel CT sensor serial (P + device + channel + 16-hex), matching
  the E-frame tails byte-for-byte - decoded as ChannelSerial and applied to the
  channel model. It is NOT a custom-name echo: a ~D field-11 name write is
  Z1-ACKed but never reported back, so channel names are write-only by protocol
  (a typed Tag reverts on Refresh, same as the legacy). BL-E5 part 2 closed.
- The ~H realtime layout is verified against 68 real frames: counters, LE
  buffered count, LE time, then 16 per-device status nibbles that matched the
  live Devices tab (devices 2,3 COM, rest OK). Indicator triples remain
  unobserved (no active alarm groups on the test station).
- The write queue is hardware-confirmed: 26 writes drew 25 ACKs with one
  observed idle-retransmit recovery; both device toggles survived a Refresh.

UI fix: the Channels tab now refits its columns after a device switch. TableTab
auto-fits only when the row count changes, but switching devices swaps the whole
content at the same row count, leaving Serial/Model sized for the previous pod
(fitted to empty serials, truncating 16-char ones).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:58:47 -04:00
ec6d2578ae fix(da07): decode the E-frame tail as the CT serial - drop the phantom disp field (BL-E5 part 1)
Verified against the repo's first real DA-07 capture (157 frames, saved as
tests/da07/fixtures/capture-2026-06-12-refresh.txt): the ~E payload ends at the
alarm byte, followed only by an optional 8-byte CT sensor serial - there is no
disp field and no name field on the wire. The phantom disp read (inherited from
the VB6, which had the same bug feeding its hidden Disp column) ate the serial's
first byte, producing the reported "Tag column cuts off the first characters"
symptom.

Removed disp everywhere: decoder E branch, ChannelRecord, the Channels-tab Disp
column (later columns shift left one), controller.set_channel_disp, encoder
CH_DISP (ICD §11 has no ~D field 9), the simulator's disp byte, repo snapshot,
and help text. The simulator now emits the wire-faithful E layout (serial-only
tail). The Tag column shows name -> serial -> catalog default, matching legacy
precedence; a written name lives only in the local model until ~P (the presumed
custom-name frame, absent from this capture) is decoded - BL-E5 part 2 stays
open, needs-capture.

The capture also re-confirmed no ~H arrives during a refresh (it streams
periodically afterwards), which is what made the stale-model revert fire every
second once live. Docs updated: BL-E5 part 1 done, HW-VERIFICATION item 8
resolved, field-notes entry added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:42:57 -04:00
53ab24284f fix(da07): apply settings writes to the local model optimistically
Found on real hardware (first DA-07 + one pod): every edit reverted within
seconds once the STATUS column populated. The DA-07 never echoes a settings
write back (the legacy VB6 grid WAS the model and kept the edited cell), but
the controller only updated its models from inbound frames - so the periodic
~H/~G/~F frames rebuilt the tabs from the stale model and reverted the edit.
The simulator masked it: it only sends ~H during a refresh, never periodically.

Every controller set_* now applies the value to its local model right after
sending and emits the matching *Changed signal (set_channel_active also emits
devicesChanged for the Devices-tab roll-up; remove_device drops the device and
its channels locally). The station stays the source of truth - the next
Refresh overwrites local state with whatever it actually stored.

Hardware findings are now logged in docs/DA07-FIELD-NOTES.md (newest first),
cross-linked from HARDWARE-VERIFICATION.md and CLAUDE.md. Open follow-ups
recorded there: confirm writes survive a Refresh on hardware, and check
whether DA-12 has the same latent bug.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:58:54 -04:00
5c0bcc3a05 polish(repository): export honors active filter; note station config-only HW item
- SettingHistoryDialog.export_to now writes the currently-shown (filtered)
  rows instead of always the full set, matching what the user sees.
- Flag the station-settings wholesale-record assumption in
  HARDWARE-VERIFICATION.md for confirmation against real D/E/B/C frames.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:03:49 -04:00
c00cffc848 feat(da07): decode ~H indicator live state, wire Alarm tab (BL-E7)
The ~H tail carries, after the device-status nibbles, one triple per active
alarm-indicator group: index(byte) + local(nibble) + server(nibble), states
OK/WARN/ALARM/ERROR (ICD §5.H/§8.4). It was kept as an opaque tail and the
Alarm tab's Local/Server columns were hard-coded "—".

- decoder: pure parse_indicator_states(tail, device_count) helper (the
  controller supplies device_count = config.max_devices, since the pure
  decoder can't know it)
- messages: AlarmIndicator gains local/server; new IndicatorState
- models: AlarmIndicatorTable.set_live_state, preserved across ~M upserts
- controller._apply_status: apply triples after the device nibbles
- simulator _p_status: emit capacity device nibbles + a triple per active
  indicator (seeded local OK / server WARN)
- alarm_tab: render real live state, severity-tinted -- closes the BL-E2
  deferred-polish (a) placeholder
- load-bearing decoder test pins the nibble-vs-byte boundary + state enum

HW-pending (flagged): device-nibble count = MAX_DEVICES(16), and the §8.4
nibble being a single-valued enum.

606 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:49:35 -04:00
17dc83fea9 feat(da07): refresh-completion on ~H + traffic keepalive (BL-E11)
(a) Traffic-capture keepalive: while a capture is active the Traffic tab
arms a 20s QTimer that calls controller.send_keepalive() -> writes a ~Z idle
frame, staying under the firmware's >25s bus-silence auto-disable (ICD
§4.2/§10.1). Tied to watching state (not tab visibility); send_keepalive
no-ops when the link isn't open (warm-cache suspend race).

(b) Deterministic refresh completion: the load now ends on the first ~H
frame (SVC_POLL phase, ICD §4.3) -- stop the settle timer, fire loadFinished
immediately -- with the 1.5s idle-settle timer kept as a fallback. Moved the
per-frame load bookkeeping before the ACK so the synchronous simulator
recursion still reports progress in arrival order and H-completion fires on
the true last frame.

HW-pending (flagged in HARDWARE-VERIFICATION.md): the H-termination path is
sim-validated but the only real capture showed no ~H (settle-timer fallback
covers it); the keepalive's effect on the >25s auto-disable is needs-confirm.

601 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:39:45 -04:00
5b10cd68a7 fix(da07): correct ~F/~G command semantics (BL-E6)
Outbound ~F/~G were mislabeled: ~F = request server config update (not
"read averages"), ~G = ResetHistory() which erases the station's outgoing
buffer (destructive, not "read inputs"). Live values arrive unsolicited via
the idle-poll loop, so there is no "request values" command at all.

- encoder: request_averages->request_server_config, request_inputs->
  erase_outgoing_buffer (docstrings cite ICD 11; ~G flagged destructive)
- controller: matching renamed methods
- simulator _dispatch: ~F accept/no-echo stub, ~G clears _buffered_records
  backlog; removed orphaned _emit_averages
- docs: amend HARDWARE-VERIFICATION item 7 and the M-frame guess
- tests: assert ~F/~G emit no value frames and ~G clears the backlog

595 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:22:38 -04:00
42e637d241 docs(da07): note blank-type-on-empty-slot HW-verification edge case
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:19:30 -04:00
d8dca0729e feat(da07): simulate spare device slots; record HW-verification items
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:12:29 -04:00
6089ba1913 polish(da12): auto-pull history on open; framer-cap HW note; seam tests (review)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:17:50 -04:00
a3e2407b0f docs(da12): BL-D5 done — sensor history/trend view; HW-verify + export notes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:02:12 -04:00
ddd5f03041 docs: close out BL-D6 ({F} station-health signals)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:23:04 -04:00
afd598f090 fix(da07): add E-frame capture hook; document Tag-column truncation (BL-E5)
Real-hardware report: the Channels tab Tag (channel-name) column drops the
first 1-2 characters of a 16-char serial-style name, and widening the column
doesn't reveal them. A systematic-debugging pass ruled out display clipping
(current source renders 16-char values fully under every style/scale) and
isolated the cause to the decoder: the name is parsed as r.rest() after the
fixed-width E-frame fields, so a real frame with an extra field/byte before the
name makes rest() start mid-name. This is the same HW-unverified region as the
per-channel-serial layout question; the simulator never reproduces it.

Fixing it correctly needs a real E-frame capture (the repo has none yet), so
this commit adds the means to get one rather than guessing at offsets:

- controller.py: env-gated raw-frame capture (CIM_DA07_CAPTURE), off by
  default, best-effort (never stalls the load). Appends every inbound frame
  body so real E frames can be saved as decoder fixtures.
- tests: cover the capture hook on/off.
- docs: record the symptom + copy-paste capture recipe under
  HARDWARE-VERIFICATION item 8, and track the bug as BACKLOG BL-E5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 11:49:38 -04:00
062c928ecd docs(da07): record polish pass (alarm/traffic un-deferred, export_sheets, HW-verification)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 10:36:27 -04:00
8c0dbb4324 docs(iomodbus): record module #3 in CLAUDE.md, status, backlog, HW-verification
CLAUDE.md gains a Module #3 paragraph (config-driven Modbus RTU master; catalog
parser; request/response poll machine). REBUILD-STATUS adds Phase 9 + updated counts
(408 tests). BACKLOG adds BL-S4 (DONE) and an IOModbus section (BL-I1 HW-verify,
BL-I2 deferred features, BL-I3 picklist dropdowns). HARDWARE-VERIFICATION gains an
IOModbus section (CRC, type-3 offset, 32/64-bit binary, float word order, FC
selection/exceptions, CI-13/15/18 xCalMult).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:53:16 -04:00
717fe47463 docs: record DA-12 server-connection status (flagged for HW capture)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 16:49:21 -04:00
12285f8524 docs: BL-D1 done -- serial recognition + channel grouping
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 15:20:45 -04:00
2926c498e0 fix(da07): drive the polled ACK handshake so settings actually load + add loading overlay
The DA-07 rebuild loaded nothing from real hardware: unlike the DA-12 (which
streams its reply unsolicited), a real DA-07 runs a polled handshake -- it sends
a refresh one frame per ACK and interleaves Z2 idle polls, advancing only when
the tool answers both. The rebuild sent "A" once and answered nothing, so the
station stopped after frame one. The simulator hid this by dumping every frame
at once.

Fix (hardware-verified on a real DA-07, COM5):
- controller._process now ACKs (Z1) every inbound data frame to pull the next.
- controller._handle_poll answers the station's Z2 idle polls (and Z0 NAK ->
  resend). Idle-polling turned out to be required for the initial load, not just
  live mode -- an ACK-only build deadlocked partway through the stream.
- SimulatedStation models the frame-per-ACK stream (handshake=True); the
  frame-content round-trip tests opt out with handshake=False.

A full Refresh now loads config + 45 device types + 28 settings + present
devices + channels (~120 frames over ~20s) where the broken build loaded 1.

Loading overlay (requested): a blocking, dimmed, gradient-blue panel covers the
DA-07 window during the multi-second refresh and clears when it completes.
- New reusable core/ui/loading_overlay.py (LoadingOverlay + animated gradient bar,
  colors from theme tokens) and a SCRIM token + QSS rules.
- Controller emits loadStarted/loadProgress/loadFinished; completion is inferred
  by a 1.5s idle-settle timer (no end marker on the wire; 1.5s clears the real
  ~0.55s max inter-frame gaps with margin). Verified: clears 1.5s after the last
  frame, no premature clear.

233 tests pass (new test_handshake.py, test_loading.py), ruff clean. Docs updated
(CLAUDE.md, HARDWARE-VERIFICATION.md, BACKLOG.md, REBUILD-STATUS.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:46:08 -04:00
e3909c216f docs(da07): record module #2 rebuild in BACKLOG/STATUS/HW-VERIFICATION/CLAUDE 2026-06-02 17:42:30 -04:00
f32dd81d88 docs: point RUNNING/HARDWARE-VERIFICATION/DESIGN-SYSTEM at the suite shell entry 2026-06-02 12:15:06 -04:00
49ab67b5f9 relocate DA-12 python package into cim_suite/modules/da12
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 11:14:37 -04:00
88b101da69 build+docs: PyInstaller spec (built & verified), Inno Setup installer, guides
Phase 4:
- packaging/da12_service.spec: one-folder PyInstaller build (AV-friendly), trims
  unused PySide6 modules. Built successfully; DA12-Service.exe launches standalone.
- packaging/installer.iss: Inno Setup script -> per-machine install, Start-menu
  shortcut, no runtime/OCX deps (signing hook noted).
- docs/RUNNING.md: dev setup, run (--simulate/--port), tests, build, data locations.
- docs/HARDWARE-VERIFICATION.md: checklist for the 5 hardware-only unknowns,
  each mapped to its fix-up location.
- docs/REBUILD-STATUS.md: overnight session summary / handoff.
- .gitattributes: normalize line endings (legacy VB6 kept CRLF).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 17:22:30 -04:00