test/hardware validation suite#4
Conversation
bonev-st
commented
May 22, 2026
- Add CLAUDE.md, requirements files, and How-to-use quickstart
- Fix every Modbus transaction failing on pymodbus 3.13
- Add live-hardware validation test suite (opt-in)
CLAUDE.md captures the five-layer architecture, the model ↔ branch ↔ PRODUCT_CODE invariant, and the per-model register quirks so future sessions don't have to re-derive them from the source. requirements.txt / requirements-dev.txt mirror the runtime and [dev] extras in pyproject.toml for bare environments that don't install the package itself; pyproject.toml remains the source of truth. README gains a top-level "How to use" quickstart that links into the existing Library usage / CLI / Auto-detection sections instead of duplicating them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pymodbus 3.13 added `exception_code` as a default attribute on every
response object — initialised to 0 on the success path. The transport
guard checked `getattr(response, "exception_code", None) is not None`,
which now matches a successful response, falls through every code
branch in `_raise_exception_response`, and raises a generic
`ModbusCommError("Modbus exception response 0x00")`.
The fix is to check truthiness: valid Modbus exception codes are
0x01..0x04, so any zero value means "no exception".
Confirmed live: before the fix, `smartpower-cli read INPUT_REG_IN_V`
against a real SmartPowerSolo failed with the 0x00 error even though
pymodbus had decoded a healthy response. After the fix the same read
returns the register value cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A new tests/hardware/ tree exercises the Modbus client end-to-end against a real SmartPower module over RS-485. Default `pytest -q` skips the whole suite — the gate is a marker (`hardware`, `hardware_write`, `hardware_fault`) plus a matching CLI opt-in (`--hardware`, `--allow-writes`, `--allow-fault-injection`) declared in tests/conftest.py. Coverage: - test_smoke.py: port open, FC 0x2B identify, product-code ↔ model agreement, probe_model candidates, baseline read, HEAT safety bit. - test_register_sweep.py: full dump() of the resolved model, type + signed/unsigned range sanity per register, repeat-sweep determinism, temperature plausibility (logged, non-fatal). - test_writes.py: round-trip writes on an allowlist of non-actuating registers (HS_RATIO, REQ_PROFILE, TIMER_SP) and the capacitance pair. Every test reads + prints the original, target, readback, and restored value, and a try/finally guarantees restoration. - test_fault_recovery.py: illegal-address via 0x3FFF, illegal-function via FC 0x2B read_code=0x04 (firmware ships with MEI_DEV_ONE_OBJ_ENA disabled), sub-ms timeout injection, retry-exhaustion check, interleaved-fault recovery sweep. PLAN.md and README.md in tests/hardware/ document the design (gating, safety rules, allowlists) and the operator workflow. Markers are registered in pyproject.toml so pytest does not warn about unknowns. Verified against a real SmartPowerSolo (FW 2.54, /dev/ttyUSB0, slave 1): 17 passed + 2 xfailed (1 ms timeout against a slave fast enough to beat the deadline — the documented escape hatch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3791c36eff
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if not hardware: | ||
| item.add_marker(skip_no_hardware) | ||
| continue |
There was a problem hiding this comment.
Honor implied hardware flags before global hardware skip
pytest_addoption advertises --allow-writes and --allow-fault-injection as implying --hardware, but pytest_collection_modifyitems checks if not hardware first and skips all marked tests before those flags are considered. In practice, running pytest --allow-writes --port=... tests/hardware/test_writes.py silently skips the write suite, which can give false confidence in automation because the command exits without exercising hardware tests.
Useful? React with 👍 / 👎.