|
Home IO Control
ESPHome add-on for IO-Homecontrol devices
|
Status: Accepted · Recorded: 2026-08
The protocol has enough undocumented, device- and chip-specific edge cases that hand-written fixtures only ever cover what someone already thought to write. A byte sequence someone invented to look plausible proves nothing when the question is what real hardware actually sends.
A frame captured off the air — including an awkward one, a device refusal, a length edge case — is evidence. It happened.
Real captured frames are committed as git-tracked YAML at tests/corpus/captures/<phase>/<id>.yaml, one file per scenario, with filename == id and the id naming its subject and protocol phase (<subject>_<phase>[_<scenario>][_<chip>]). Nothing in the tooling reads the path — it is grouped by phase purely because that is the axis the corpus is reached from; every other axis is a one-line ls/grep, and issue provenance is carried in source.issue, not a directory. Tooling scaffolds a capture from a pasted on-air log, validates it (including filename == id, that each capture sits in its phase directory, and that every capture path cited across the tree resolves), and compiles it into test fixtures.
flowchart LR
Log["On-air log<br/><i>own hardware or a GitHub issue</i>"]
Ingest["ingest.py<br/><i>scaffolds YAML,<br/>--rekey anonymizes</i>"]
YAML[("captures/**.yaml<br/><b>single source of truth</b><br/>git-tracked")]
Val["validate.py — make corpus-validate<br/><i>schema · CRC/length self-consistency ·<br/>crypto promises</i>"]
Build["build.py<br/><i>renders C++ fixture header</i>"]
Gen["build/corpus/corpus_generated.h<br/><i>build artifact, git-ignored</i>"]
Tests["Host suites: frame · crypto · decode ·<br/>classification · exchange replay"]
Log --> Ingest --> YAML
YAML --> Val
YAML --> Build --> Gen --> Tests
Val -.->|"part of make lint"| CI["CI"]
Tests -.->|"part of make unit-test"| CI
The YAML is the only stored copy of the data. The C++ header is regenerated on every test build and git-ignored, so there is no second copy that can drift and nothing to keep in sync by hand.
validate.py enforces more than shape: CRC and length fields must be self-consistent with the frame bytes, and a capture claiming its crypto verifies under the public corpus key must actually do so.