543 Commits

Author SHA1 Message Date
a227e87268 feat(iomodbus): config.supported_devices() over factory base + user layer 2026-06-05 22:52:39 -04:00
a88d70180b test(iomodbus): pin overridden as layer-level (version-blind); clarify wording
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 22:51:32 -04:00
2204a3c5c6 feat(iomodbus): catalog_view.build_device_rows (factory + user rows)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 22:46:08 -04:00
27bc8734dc docs(iomodbus): implementation plan for Supported Devices list
Four TDD tasks: pure catalog_view.build_device_rows, config.supported_devices
wrapper, the QTableWidget Supported Devices dialog (replacing Manage User
Devices) with user-only delete, and lint/suite/docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:40:33 -04:00
6751796bf5 docs(iomodbus): spec for Supported Devices list
Read-only browser of all catalog devices (factory + user), evolving the
Manage User Devices dialog into a sortable table: Name/ID/Manufacturer/
Min FW/Channels/Settings/Origin, with Delete enabled only for user rows
and overridden factory rows marked. Pure build_device_rows helper backs
it. Not the deferred Spec #2 editor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:36:24 -04:00
d4c20b463d Merge feat/iomodbus-catalog-device-management: catalog device management (Spec #1)
JSON user layer merged over the bundled IOModbus.txt factory catalog
(user devices shadow factory by id), import/export of the legacy text
format, lenient import validation, and a Catalog menu (import/export/
manage). Rich per-field editor deferred to Spec #2.
2026-06-05 22:26:59 -04:00
cc591a451b fix(iomodbus): clear stale discovery on catalog reload (wrong-index selection)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 22:24:44 -04:00
4aa99605eb docs(iomodbus): record catalog device management (Spec #1) in backlog/status
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 22:16:26 -04:00
8c7f293c17 fix(iomodbus): validate on import, guard bad files, fix export dir + alert order
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 22:14:18 -04:00
eca4e06b45 feat(iomodbus): Catalog menu — import / export / manage user devices
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 22:04:22 -04:00
c297831863 fix(iomodbus): harden user-layer JSON load against malformed root shapes
Raises ValueError (already caught by load_user_devices) for null and
non-list 'devices' shapes; adds parametrized degradation tests, a
user_only export test, and two minor config.py cleanups.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 22:01:54 -04:00
166b00edef feat(iomodbus): merged catalog load + import/add/delete/export ops
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 21:52:48 -04:00
b758c62c05 test(iomodbus): lock regfmt no-grid and label-only-setting shapes 2026-06-05 21:50:45 -04:00
810768ab01 feat(iomodbus): regfmt — Catalog->IOModbus.txt with round-trip fidelity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 21:38:52 -04:00
ea3f926726 fix(iomodbus): lenient validation tolerates descending bit ranges; lock real-catalog contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 21:31:56 -04:00
f81f0e5105 feat(iomodbus): device validation (strict editor / lenient import)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 21:21:58 -04:00
f0db0e84a5 test(iomodbus): lock mint_uid empty-description fallback; clarify shadowing docstring
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 21:18:40 -04:00
fb09d0aaea style(iomodbus): hoist Catalog import to top of user-layer test (E402) 2026-06-05 21:13:47 -04:00
000c2d8535 feat(iomodbus): merge_catalogs (user-first shadowing) + mint_uid
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 21:11:25 -04:00
11eea25b43 test(iomodbus): exercise non-default RegSpec fields in user-layer round-trip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 21:09:30 -04:00
871ad3544c feat(iomodbus): user-layer device JSON serialization
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 21:04:16 -04:00
336062b805 docs(iomodbus): implementation plan for catalog device management (Spec #1)
Seven TDD tasks: user-layer JSON model, merge_catalogs (user-first
shadowing) + mint_uid, strict/lenient validation, regfmt round-trip
formatter, config merge + import/add/delete/export ops, Catalog menu,
lint/suite/docs sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 21:00:23 -04:00
98ef7ad5a2 docs(iomodbus): spec for catalog device management (Spec #1 foundation)
Layered catalog: bundled IOModbus.txt stays the read-only factory baseline;
a validated JSON user layer (user_devices.json) merges on top, shadowing
factory devices by id. Text format demoted to import/export interchange.
Scope: storage + merge + validation + import + export + thin menu glue.
The rich add/edit/override editor UI is deferred to Spec #2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 20:51:47 -04:00
5b011f47e7 Merge feat/bl-d9p2-da12-humanize-disp-calc: humanize Disp/Calc columns (BL-D9 part 2) 2026-06-05 18:06:01 -04:00
2cd1f13dda feat(da12): humanize Sensors-tab Disp/Calc enum columns (BL-D9 part 2)
The disp (units) and calc (stats) bytes rendered as raw integers. They now
show legible labels: disp -> native/Fahrenheit/Kelvin (unknown =
conversion-table[n]); calc -> sigma/variance/MKT/rate-sec/... -- codes,
order, and fall-backs mirror docs/da12c_status.py (UNITS_CODES/STATS_TYPES).

- new pure da12/sensor_enums.py (code<->label maps + helpers)
- sensors_tab: a local _EnumBandDelegate extends GroupBandDelegate so the
  group band still paints on every column while the Disp/Calc columns get a
  strict dropdown; an off-list value (e.g. a custom conversion-table[n]) is
  inserted at the top so editing can't silently drop it; on_edit maps the
  chosen label back to the raw code the station expects

Part 1 (backfill MODEL_CHANNEL_MAP + {A} cross-check) stays open: it needs
the authoritative current product names (the firmware doc's CI-/CT- labels
are outdated vs our CP- names) and the type-code field is HW-flagged -- both
need SME/hardware input, not derivable from project files.

621 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 18:06:01 -04:00
18628be0f8 Merge feat/bl-e9-da07-maintenance-commands: expose ~I/~T/~L (BL-E9) 2026-06-05 17:55:30 -04:00
4efafcc96b feat(da07): expose ~I/~T/~L maintenance commands (BL-E9)
Firmware implements three maintenance commands the tool never exposed
(ICD §11):
- ~I  clear all channel disable flags (re-enable channels)
- ~T nn flag a device for reset/re-acquire without removing it
- ~L  force a server update of all sensors

Added encoder fns + controller methods. ~T is a per-device Devices-tab
context action ("Re-acquire Device…", confirm-guarded, beside Remove); ~I
and ~L are station-wide toolbar actions ("Re-enable Channels", "Force Server
Update"). Effects are needs-confirm on hardware; the wiring is fully tested.

613 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:55:30 -04:00
9ca454037e Merge feat/bl-e10-da07-display-message: handle ~R display-message (BL-E10) 2026-06-05 17:52:29 -04:00
7d00c8f6a9 feat(da07): handle ~R display-message, prompt Refresh (BL-E10)
~R msgID (SVC_MSG_REFRESH = 0x01) is the station asking the tool to refresh
after the server changed settings (ICD §5.R). It was decoded to None and
dropped.

- messages: DisplayMessage (bit-significant msg_id + refresh_requested)
- decoder: ~R branch
- controller: serverUpdateAvailable signal, emitted on the refresh bit
- main_window: non-modal status-bar hint (mirrors legacy vsStatus caption);
  chose a hint over auto-refresh so the station can't trigger a surprise
  multi-second reload

609 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:52:29 -04:00
87209e9dfc Merge feat/bl-e7-da07-indicator-live-state: ~H live indicator state (BL-E7) 2026-06-05 17:49:35 -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
def9bf3db7 Merge feat/bl-e11-da07-refresh-completion-keepalive: refresh completion + keepalive (BL-E11) 2026-06-05 17:39:45 -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
02cd99e05b Merge feat/bl-e12-da07-guard-o42: block ~O42 (BL-E12) 2026-06-05 17:29:31 -04:00
ed37d1c4a5 feat(da07): guarded set_special_option, block ~O42 (BL-E12)
Special option 0x42 is inert/harmful on DA-07 -- it freezes service comms
while bridging to an uninitialized DNT radio UART (ICD §1, BUG-ICD-02). It
is a DA-33-only diagnostic.

Added a guarded set_special_option(option) encoder (~O nn) plus a named
SPECIAL_OPTION_RADIO_BRIDGE = 0x42 constant; the builder raises ValueError
on 0x42 so any future "expose all options" work inherits the block instead
of hand-rolling a raw ~O frame.

599 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:29:31 -04:00
20e8e1e504 Merge feat/bl-e8-da07-chan-status-bits: surface ~G alarm high bits (BL-E8) 2026-06-05 17:28:06 -04:00
e121d296e6 feat(da07): surface ~G channel-status alarm high bits (BL-E8)
chan_status decoded only the low sensor-error bits, dropping the alarm-state
high bits (0x80 ALARM, 0x40 WARN, 0x20 acked, 0x10 trim) -- so a channel in
alarm read identically to a healthy one (ICD §8.2).

- codecs.chan_status: append the high bits as a readable suffix, mirroring
  device_status's !/© modifiers; plain-OK sensor in alarm shows just the
  alarm tokens (not "OK ALARM")
- channels_tab: status_severity() helper tints the Status cell red (fault /
  ALARM) vs amber (pure WARN) vs untinted, replacing "any non-OK -> red"
- left the legacy 5/6/7 enum entries in place, flagged in-code as VB6
  artifacts (deferred per "revisit when touching this")
- tests: chan_status high-bit cases + status_severity classification

597 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:28:00 -04:00
0bd8b63735 Merge feat/bl-e6-da07-fg-command-fix: correct ~F/~G semantics (BL-E6) 2026-06-05 17:22:47 -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
43221547c4 docs(da07): add firmware ICD + gap analysis, expand DA-07 backlog
Add the DA-07 firmware Interface Control Document (a from-source review of
the EOL DA-07 firmware) and a gap analysis comparing it against the module.

Cross-check surfaced several real-hardware issues the simulator can't catch:
- ~F/~G are mislabeled as value-reads; ~G is actually ResetHistory (erases
  the outgoing buffer). Encoder, controller, and simulator all agree on the
  wrong meaning. (BL-E6, P1)
- BL-E5 root cause found: the ~E decoder reads a phantom `disp` byte and
  sources the channel name from what is really the optional CT serial tail.
- Coverage gaps: ~P decoder, ~H alarm Local/Server state, ~M/~J Modbus
  passthrough, ~K diagnostics, and several outbound commands.

Categorize findings (Incorrect / Missing / Fragile-hardening / Doc /
Confirmed) with per-item [no-hw]/[needs-capture]/[needs-confirm] tags;
refine BL-E1/E2/E5 and add BL-E6..E12 in priority order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:13:05 -04:00
43c2acb248 Merge feat/bl-s3-bl-i3-migration-and-picklist: DA-12 data migration (BL-S3) + IOModbus pick-list dropdowns (BL-I3)
- BL-S3: one-time move of legacy DA-12 Roaming data (DACal.csv, DA-Logs\) into the
  Local data dir; no-clobber, flag-guarded, run once from Da12Module.create_widget.
- BL-I3: strict pick-list QComboBox delegate on the IOModbus register grids, preserving
  off-list device values; label routes through the existing write path (view-layer only).

593 tests pass; ruff clean.
2026-06-05 16:14:46 -04:00
c2a699d477 test: stop DA-12 migration from touching real user dirs in tests (BL-S3 review)
create_widget()/open_module() callers (test_module.py, test_shell.py) triggered the
real one-time migration against real AppData. Root autouse fixture stubs it everywhere
except the migration tests, which exercise the real impl against explicit temp dirs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:12:36 -04:00
b7d4bfe5e0 docs: mark BL-S3 + BL-I3 done
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 16:04:16 -04:00
1eb28f9c0c test(iomodbus): direct setModelData coverage + type/clarity nits (BL-I3 review)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 16:01:49 -04:00
662fd30fe5 feat(iomodbus): install pick-list delegate on register grids (BL-I3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 15:56:20 -04:00
ad347cefa8 feat(iomodbus): pick-list combo-box delegate (BL-I3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 15:55:31 -04:00
f7685d5d65 feat(iomodbus): add RegisterGrid.cell_at accessor (BL-I3) 2026-06-05 15:51:38 -04:00
fa9d1b003e fix(da12): guard migration traversal + add edge-case tests (BL-S3 review)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 15:47:56 -04:00
33e8c04bf1 feat(da12): run data migration once on module startup (BL-S3) 2026-06-05 15:39:19 -04:00
0017dd2801 feat(da12): one-time Roaming->Local data migration shim (BL-S3) 2026-06-05 15:38:47 -04:00