This guide walks you through structuring a CoreEngine project: organizing folders, separating generated code from developer code, wiring streams and drivers through configuration, and applying the engine's state-machine best practices.
1. Understand the Core Principles
Before laying out a project, follow these architectural rules:
- Keep a strict separation between generated code and developer code.
- Give each model explicit ownership of its own state.
- Wire streams and drivers through configuration, not hard-coded connections.
- Design for deterministic startup and runtime behavior.
2. Create the Root Folder Structure
Set up your project root with the following top-level folders:
Root
├── DBSim
├── interfaces
│ ├── Drivers
│ └── Streams
├── Models
├── OGI
├── Setups
└── config.json
| Folder | Responsibility |
|---|---|
DBSim/ | Auto-generated ICD C++ sources and headers |
interfaces/ | Container for the project's Drivers and Streams implementations |
interfaces/Drivers/ | Project-specific hardware or protocol drivers, one subfolder per driver |
interfaces/Streams/ | Logical data streams connecting blocks to drivers, one subfolder per stream |
Models/ | Logical system models implementing behavior and states |
OGI/ | Operator Graphical Interface logic and event handling |
Setups/ | System configuration, layouts, streams, and test data |
api/ subfolder — a separate shared library implementing the device-specific API. See Driver & Stream Folder Layout for the full breakdown.
3. Organize Models and States
Each Model represents a logical subsystem or functional domain. A model may own one or more state-machine controllers, and each controller holds one or more States. Give each model responsibility for:
- Internal state transitions in its state controllers
- Processing of incoming streams
- Generating and manipulating LRU block elements
Inside a model folder, create a states/ directory containing state controllers, each grouping related states:
Models/
└── NavigationModel/
├── NavigationModel.cpp
├── NavigationModel.h
└── states/
├── Alignment/
│ ├── IdleState.cpp
│ ├── AligningState.cpp
│ └── AlignedState.cpp
└── FaultHandling/
├── FaultState.cpp
└── RecoveryState.cpp
4. Wire Drivers and Streams Through Configuration
Drivers (under interfaces/Drivers/) implement the physical or protocol-level interface (bus, socket, file, or simulated I/O), split into a driver layer and a separate API shared library. Streams (under interfaces/Streams/) bind blocks or model endpoints to these drivers using configuration.
- Define streams in
streams.json. - Specify for each stream: source block or model output, target driver, direction and rate, and optional transformation/filtering.
5. Configure the Setups Folder
The Setups/ folder defines how the engine is instantiated for a specific system or project.
| File | Description |
|---|---|
LRU-<project>.json | LRUs, blocks, elements, layout, and properties |
systemconfig.json | Application-specific configuration |
streams.json | Stream-to-driver bindings |
Optionally add a tests/ folder under Setups containing CSV-based test vectors for unit tests, standard compliance tests, and ATP (Acceptance Test Procedures).
6. Build the OGI Layer
The OGI (Operator Graphical Interface) layer implements the operator-facing application. It should handle:
- User interactions and commands
- Visualization of system state
- Translation of UI events into core engine actions
7. Understand the Runtime Sequences
Keep these three runtime flows in mind while integrating components:
- Application Startup: Main App loads
config.json, the Core Engine loadsstreams.json, then resets/inits Models and Drivers/Streams, then starts realtime execution. - Stream Execution: a Model publishes data to a Stream, the Stream forwards it to a Driver (per configured rate/direction), the Driver returns receive/status, and the Stream delivers data back to the Model.
- State Lifecycle: a Model resets/inits its State Controller, which creates and sets the initial State; on each run cycle the Model runs the Controller, which executes the State, which returns either "stay" or a transition to the next state.
8. Apply Best Practices
- Keep generated code (
DBSim) read-only. - Give each state one responsibility.
- Use explicit state transitions only.
- Favor configuration over hard-coded behavior.
- Fail fast on invalid configuration.