How to Build an APIC System for Software Developers

A fast-start, code-first tour of where logic lives and the capabilities available to you.

This guide is for developers who want to start writing APIC code as fast as possible. If you're looking for the broader system-engineering process instead (requirements → tests), see How to Build an APIC System for Software Engineers. This page assumes that process is already underway and focuses on where your code actually goes and what's available to you while writing it.

1. Your Logic Lives in Models and States

Almost all of your application logic belongs in two places: Models and States. Everything else — DBSim, drivers, streams — exists to feed data into and out of these two.

Start here: read the Models guide and the States guide before anything else. Almost every other capability on this page is used from inside a model or state.

2. External Interfaces: Streams, Drivers, and Vendor APIs

Data gets in and out of your models through three distinct layers, each with a specific 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 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 that the driver loads at runtime.

Both streams and drivers are defined per project under interfaces/ and wired together in streams.json. See Drivers and Streams for the full folder layout and the driver/API-layer split, and streams.json for the configuration format.

3. Raw ↔ Eng Conversion and Bit/Byte Conventions

Every ICD block has a raw form (exact wire bytes) and an eng (engineering) form — the plain C++ value your code reads and writes. The conversion call is made once per LRU (it converts every block belonging to that LRU in one call), not per individual block. Convert explicitly in OnRun():

CLRUsProj::{{LRUName}}->Raw2Eng();   // after new raw data arrived, converts all of this LRU's blocks
// ... work with eng-side elements ...
CLRUsProj::{{LRUName}}->Eng2Raw();   // if you modified them and need to send

Which conversion is used for a given block is a per-block property set once in DBSimGenerator:

ConventionMeaning
NoRawNo physical/binary raw representation — nothing to convert.
LElsbLittle-Endian bytes, bits numbered from the LSB (natural C/C++ convention).
LEmsbLittle-Endian bytes, bits numbered from the MSB.
BElsbBig-Endian bytes (wire order), bits numbered from the LSB.
BEmsbBig-Endian bytes, bits numbered from the MSB — common in avionics/MIL-STD ICDs.
DBSimGenerator generates the correct conversion code for you — see Raw ↔ Eng Conversion in the Models guide for the full explanation and where this fits in OnRun().

4. Command-Line Arguments

Read any name="value" argument passed to the executable from a model's OnInit():

// Usage: projectApp.exe myArg="value"
std::string myArgValue;
getCmdOption(SystemConfig.arguments, "myArg", myArgValue);

if (cmdOptionExists(SystemConfig.arguments, "?"))
{
    // print usage/help and exit
}
This is the exact same mechanism behind the built-in config argument (projectApp.exe config="[path]/config.json") — see Command-Line Argument Utilities for details.

5. Component Versioning

Every model and driver passes its own version string to its base constructor:

C{{ModelName}}::C{{ModelName}}() : CModelBase("{{ModelName}}", "1.00", {{{CoreAffinityArray}}}) {}
CSampleDeviceDriver::CSampleDeviceDriver(const char* streamName)
    : CDriverBase(streamName, "1.00", "SampleDeviceAPI", "") {}

There's no separate build step — to "release" a new version of a model or driver, update that string literal. It's shown live per-component in DIPanel's Core States and System Layout, so mismatched versions across a deployment are easy to spot.

This is per-component versioning. For the version of the application as a whole, see How to Update the Application Version.

6. Fast-Start Toolkit

A quick map of the capabilities available from inside a model, and where to read more:

CapabilityUse it forDetails
CMinorFrameGateRun a shared code path at most once per minor frame, however many times it's called.Utilities
CValueChangeGateSkip processing when an element hasn't changed since last check.Utilities
Async tasks + PhaseCoordinatorFire-and-forget background work, or a thread synced to the frame cycle via a barrier.Async Commands
Scope/manual profilingLightweight timing of a task in the hot path.Profiling
Stream triggers & callbacksRun a model only when specific data arrives; hook pre-send/post-receive/receive.Streams, IO & Recording
Direct stream send/receiveCDrivers::SendBlock("StreamName", pRaw, rawSize) to send raw bytes straight to a stream's driver, or CStreams::GetStream("StreamName") to get the stream object itself and call its own send/receive methods directly.Drivers and Streams
Recorder APISet recording path, flush cadence, and per-element recording properties from code.Recording
Custom keyboard handlingReact to key presses inside the CoreEngine process itself.Models guide
Memory monitorDetect leaks/unusual allocations (enable from exactly one model).Utilities
EngineSyncAlign frame progression to an external hardware timing reference.EngineSync
OGI initInitialize the operator interface (call once from exactly one model).OGI