How to Interact with External Devices

Wire a real piece of hardware into the CoreEngine: streams, drivers, and matching the device's memory layout.

This guide covers connecting the CoreEngine to a real external device — a piece of hardware, another system, or a network endpoint that speaks its own wire protocol.

1. The Concept: Streams and Drivers

Three distinct layers carry data between your models and a device, each with one job:

LayerJob
StreamThe interface with the ICD structures — translates raw bytes into ICD-compliant blocks your models and states consume, and back again.
DriverThe bridge between a stream and the hardware — the CDriverBase subclass a stream talks to (Send()/Recv()).
API layerThe actual low-level interface to the hardware vendor's SDK, built as its own shared library the driver loads at runtime.

See Drivers and Streams for the full folder layout and the driver/API-layer split, and streams.json for how a stream and its driver are configured together.

2. Matching the Device's Memory Layout (Raw ↔ Eng)

A real external device rarely uses the same in-memory layout as your C++ structs — it may pack bits MSB-first, use a different byte order, or have no binary representation for some fields at all. APIC handles this with the raw ↔ eng conversion: every ICD block has a raw form (the exact bytes the device expects on the wire) and an eng form (the ordinary C++ value your model code uses). Your code always works with the eng side; the raw side is what actually gets sent to/received from the device.

ConventionUse it when the device...
NoRaw...doesn't apply — the block has no physical/binary form (internal/software-only data).
LElsb...uses Little-Endian bytes with bits numbered from the LSB (the natural C/C++ convention).
LEmsb...uses Little-Endian bytes with bits numbered from the MSB.
BElsb...uses Big-Endian ("network order") bytes with bits numbered from the LSB.
BEmsb...uses Big-Endian bytes with bits numbered from the MSB — common in avionics/MIL-STD ICDs.
Pick the convention that matches the device's actual documented wire format for each block — not what's convenient in C++. DBSimGenerator then generates the matching Raw2Eng()/Eng2Raw() code for you; see Raw ↔ Eng Conversion for how these calls are used (once per LRU) inside OnRun().

3. Set It Up

  1. Define the device's data layout as an ICD CSV, choosing the raw convention for each block to match the device's documented format — see DBSimGenerator for how to set this up.
  2. Generate the DBSim code from the ICD with DBSimGenerator.
  3. Define the stream and its driver in streams.json.
  4. If a driver for this device doesn't exist yet, implement its driver and API layer — see Drivers and Streams for the folder layout and the exact pattern to follow.
  5. Convert raw ↔ eng in your model's OnRun() and work with the eng-side values as usual.
Once wired up, use System Layout to see the device's data flow, rates, and errors live, and DIPanel to monitor or inject individual elements.