Skip to content
The archive
Architecture

The hardware abstraction layer

One interface between the decision system and a physical signal controller — and why emergency override lives at this layer rather than above it.

AI-assisted draftReviewed 2 Aug 2026 by Vihaan Vaghela

VECTOR archive › Architecture › Hardware abstraction

Everything above this layer reasons about traffic. Below it, something changes a light. The boundary between those two is the point where a decision stops being a representation and becomes an action in the world, and it is drawn deliberately narrow.

Problem

A decision system coupled to a specific controller cannot be tested without that controller, cannot be deployed onto different hardware, and cannot be run in simulation. The coupling also spreads: once one component knows the device, others start assuming its behaviour.

Context

The system must run against simulated controllers during development, against a simulation environment for training, and eventually against physical infrastructure — without the decision path knowing which.

Constraints

The interface has to be small enough that implementing it for new hardware is tractable, and expressive enough to carry an emergency stop. It must be synchronous at the point of action: a signal change is not something to be queued.

Alternatives considered

Direct control from the decision engine. Fastest path, no indirection, and it makes the decision engine untestable without hardware.

A general device abstraction. More flexible, considerably larger, and generality that no second device type has yet demanded is generality bought on speculation.

Chosen architecture

SignalController in hardware/controller_interface.py — an abstract base class with three operations:

  • set_phase(phase, duration) — the entire ordinary control surface
  • get_state() — what the controller currently reports
  • emergency_override() — a distinct operation, not a special case of set_phase

The narrowness is the design. Three methods is a small enough contract that simulated_controller.py is a faithful stand-in rather than an approximation, which is what makes testing against it meaningful.

Emergency override is a separate method, and that is the most consequential decision here. Expressing an emergency as a phase change with particular arguments would route it through the same validation, queuing and interpretation as an ordinary command — the path most likely to be congested or degraded when it is needed. A distinct operation can take a distinct path. This is the device-level counterpart of Hyperion: the emergency mechanism is deliberately not the ordinary mechanism with an urgent flag.

controller_worker.py and control_loop.py sit around this interface, isolating the timing of physical control from the decision cycle above it.

Subsystem relationships

Consumes decisions from the decision engine in core/. Implemented by simulated_controller.py for development, and paired with the simulation layer for training. Its get_state() output is part of what Sentinel ultimately observes.

Data flow

text
decision → control_loop → controller_worker → SignalController.set_phase()
                                            ↘ emergency_override()  (separate path)
device state ← SignalController.get_state() ← ─────────────────────────────────

Tradeoffs

A narrow interface constrains what can be expressed. Anything a controller can do beyond phase and duration is not reachable. Accepted deliberately: a wider interface is harder to implement faithfully, and an unfaithful simulated implementation makes every test that uses it misleading.

Abstraction hides device-specific failure. A controller that fails in a way the interface has no vocabulary for surfaces as a generic error, if at all.

Failure modes

The controller does not respond. The decision was made and the world did not change. Whether the layer distinguishes "command accepted" from "command took effect" is not evident from the interface, and it is the distinction that matters most here.

State drift. The reported state and the physical state diverge, and every layer above is reasoning about a controller that does not exist as described.

Override unavailable. The emergency path is the one that must work when the ordinary path does not; that it is separate is the mitigation, and it is not a guarantee.

Future evolution

Command acknowledgement — confirming effect rather than acceptance — is the most valuable addition, and it would give the layers above a real signal for the difference between a decision made and a decision applied.