C++
archec gen main.arche --target cpp writes C++17 from the model, for a hosted
target: Linux, macOS, or an RTOS with a C++ standard library and threads. The
c target is the one for a microcontroller with no heap; this one is for the
computer that runs the bigger parts, and is written the way C++ is written there.
archec gen examples/autonomous-car/main.arche --target cpp -o generated
cmake -S generated/cpp -B build && cmake --build build
ARCHE_RUN_MS=500 ./build/adas
For McCOFFEE's component framework, and for the same component shape on the
STL, the built-in McCOFFEE plugin has its own C++ generator: --target mccoffee-cpp (its README).
What it writes, in cpp/#
| File | What it is |
|---|---|
arche/rt.hpp |
What a part needs from the platform: arche::now_ns() (steady clock), wait_ns, log; the little-endian put/get the codecs use; min, max, clamp in the common type of their arguments; arche::Worker, a thread of the model. |
arche_types.hpp/.cpp |
In namespace model: constants as inline constexpr (durations in ns as std::uint64_t), enums as enum class, unions as std::variant (a case with no payload is std::monostate), optional fields as std::optional, arrays as std::array (std::vector when unsized), structs and messages as structs. A message that is a value (message PING : uint32) holds it in value. Each message with a fixed layout has kSize, encode(buf) and decode(buf, len) on the same wire layout as the C, the DBC and the Rust: the same bytes. |
<part>.hpp/.cpp |
A class per component that behaves (not the hardware). |
<binary>/main.cpp |
With a deployment: the program of a binary. |
<binary>/hooks.hpp, hooks.cpp |
What leaves the binary, and what your drivers call. hooks.cpp is written once: yours. |
CMakeLists.txt |
A library of the parts, an executable per binary, -Wall -Wextra -Werror (-DARCHE_WERROR=OFF to relax). |
Names: a type or a message is PascalCase (BRAKE_CMD → BrakeCmd; a message
whose name would be a type's, as kacao's POINTING beside Pointing, gets
Msg), a field or a member snake_case, a constant, an enumerator or an event
SCREAMING_SNAKE, a state as written.
A component#
model::Adcu adcu;
adcu.send_can_brake_brake_cmd = [](const model::BrakeCmd &m) { /* to the bus */ };
adcu.start(); // watchdogs armed, the initial state entered
model::EngageReq req{true};
adcu.fire(model::Adcu::Event::ENGAGE_REQ, &req);
if (adcu.in(model::Adcu::State::Autonomous)) { … }
- Its variables are public members, with the model's initial values.
- Its functions are member functions. A ```arche body is translated; a Python body is translated when it says what the action language says (most simple ones do), else it is a TODO with the Python under it.
- What it sends is a
std::functionmember per port and message (send_<port>, orsend_<port>_<message>when the port sends several); one that is not set sends nothing. - Its state machine is an
enum class Stateand a switch, the C's algorithm in a class, in the order the specification gives:fire(event, msg)takes the transition the event triggers, innermost state first (the message's fields are the names its guards and actions read),go_to(state)runs the on-exits and on-enters,in(state)is true in a state or a state inside it. - Timers: each
aftertransition and each watchdog is a timer;next_due()says when the next runs out,timeouts()runs those that did,kick(WD_X)restarts a watchdog,on_timer(when set) is told each time one is armed. - Deadlines:
lateis how late the trigger being handled is; a transition'sdeadlinereads it and runs itson-deadlineinstead.
A deployment#
With a deployment, each binary that says language cpp (each binary, when none
does) gets a main.cpp:
- an instance of each part the binary runs;
- an
arche::Worker— astd::thread— per thread of the model: it runs its parts'mainand the functions itrunsonce, then each period itsstep, the messages other threads post to it, and its parts' timers when they run out; - each link between two parts of the binary: the receiver's thread delivers the message (its transition, the port's on-receive, its handlers);
- each link out of the binary: the message encoded, then
arche_external_send(link, msg, id, buf, len)inhooks.cpp(the frame id is the route'sid, as in the C); a message from outside comes in througharche_receive(link, msg, buf, len), which your driver calls; mainruns until SIGINT or SIGTERM, or forARCHE_RUN_MSmilliseconds.
A thread's priority and policy are written as a comment beside its worker:
set them on native_handle() in arche_hooks_start() if they matter (that is
pthread_setschedparam on Linux).
Verified#
test/cppgen.test.js builds examples/autonomous-car with CMake and
-std=c++17 -Wall -Wextra -Werror and runs it; compiles every example and the
difftest fixtures with -Werror; encodes a message with an enum, a union, an
optional field and an array in C++ and in C and compares the bytes (and decodes
the C's); runs door.arche's machine in C and in C++ message by message and
compares the state and the variables after each one; and drives a rover's
nested machine, guards, action, on-enter, handler and watchdog.
Not done#
- A sensor's noise and an actuator's lag and clamping (realism.ts) are in the C only.
- A message that says its wire format bit by bit (
at,bits,scale) has its struct but no C++ codec: the C (--target corwire) has it. --traceand--monitorsare C and Rust only.- Thread priorities, policies and cores are comments, not applied.
- An AUTOSAR Adaptive flavour (ara::com proxies and skeletons) is not written.