hardless-core
The sans-IO core of Hardless, an open-source POS hardware emulator.
It defines what a device is and keeps track of the devices that exist. It does no I/O and
reads no clock: devices are pure state machines that receive bytes, control commands and a
now timestamp from the caller. Transports and the server own all I/O and time.
| Piece | Requirement | Types |
|---|---|---|
| Device plugin interface | FR-01 | DevicePlugin, Device, DeviceEvent, ControlCommand, Job |
| Device registry and event bus | FR-02 | Registry, BusEvent, DeviceSummary, RegistryError |
| Event log | FR-03 | EventLog, EventRecord, EventKind |
| Faults | section 10 | FaultId, FaultOptions, FaultSpec, catalogue() |
| Scenario runner | FR-04 | Scenario, Step, ScenarioRun, ScenarioReport |
| Deterministic clock | FR-05 | Clock, FakeClock, Millis |
Usage
use ;
/// A device that answers every byte it receives with `0x06` (ACK).
;
;
let clock = new;
let mut registry = new;
registry.register.unwrap;
let id = registry.create.unwrap;
clock.advance;
let events = registry.host_bytes.unwrap;
assert!;
// Everything that happened is in the device's event log, stamped by the injected clock.
let log = registry.events.unwrap;
assert_eq!;
// Observers (dashboard, MCP) read the event bus.
let bus = registry.drain_bus;
assert_eq!; // device:added
Scenarios (FR-04)
A Scenario is a JSON list of steps: scan, tap, wait (ms), setFault, clearFault
and expect. An expect checks a device: state (a subset match), receiptContains and
jobs, optionally waiting up to withinMs for them to become true. A run stops at the first
failing step; the rest are reported as skipped.
ScenarioRun::advance runs every step it can at the registry's time and returns either
Progress::WaitUntil(t) or Progress::Done(report), plus device events the caller should
deliver (wedge scans, bytes for the app). It never sleeps: the server waits in real time,
tests move a FakeClock.
Design notes
- Sans-IO. The registry reads time only through the
Clockit was given; the server passes a system clock, tests passFakeClock. No API in this crate blocks or sleeps. - Two outputs per call. Methods that feed a device return its raw
DeviceEvents so the caller (a transport) can routeToHostbytes andWedgescans back to the app. The same events are recorded in the event log, and the ones observers care about are put on the event bus. - Timestamps are monotonic. If an injected clock goes backwards, the registry keeps the last timestamp it handed out.
- Faults are open.
FaultIdis a string so plugins can define their own faults; the IDs in PRD section 10 are provided as constants and incatalogue().
Features
ts: derives TypeScript bindings withts-rs. Used bycargo xtask gen-types, which writespackages/types.
License
Apache-2.0