-
Notifications
You must be signed in to change notification settings - Fork 0
Commands
The Command subsystem provides async sequential orchestration — a way to queue and execute asynchronous operations one at a time, with lifecycle management, timeout enforcement, and result tracking.
Many embedded and automotive systems need to serialize operations that are individually async: you kick off a request, wait for a hardware acknowledgement, then proceed. Doing this ad-hoc with chains of callbacks is fragile. The Command subsystem gives you a reusable pattern:
- Wrap each async operation in a
serp::Commandsubclass. - Push commands into a
serp::CommandQueueorserp::CommandProcessor. - The queue executes exactly one command at a time; the next starts only after the current command finishes, is canceled, aborts, or hits the watchdog limit.
Base class for all commands. Subclass it and override the execution hooks.
| Method | When called | Purpose |
|---|---|---|
onExecute() |
When the command reaches the head of the queue | Begin async work. Call finish() when done. |
onPostProcessing() |
After finish() is called, before the next command starts |
Optional cleanup or transaction finalization. |
finish() |
Called by your code | Signals successful completion. |
cancel() |
Called by queue or externally | Signals that the command should stop. |
States:
Pending ──► Running ──► Finished
├──► Canceled
├──► Aborted
└──► Watchdog
| State | Meaning |
|---|---|
| Pending | Queued, waiting to execute. |
| Running |
onExecute() has been called. |
| Finished |
finish() was called successfully. |
| Canceled | The command was canceled before or during execution. |
| Aborted | The command signaled an unrecoverable error. |
| Watchdog | The watchdog timeout elapsed before finish() was called. |
Result tracking:
enum class Result { Success, Canceled, Aborted, Watchdog };
serp::Command::Result result = cmd->getResult();Delay before next command:
You can configure a short delay between finish() and the start of the next command — useful to let hardware or protocol state settle before the next operation begins:
cmd->setDelayBeforeNextCommand(std::chrono::milliseconds(50));Pure virtual polymorphic interface. Useful when you need to store or pass commands without knowing their concrete type.
A FIFO queue that executes commands sequentially. One command runs at a time; it does not start the next until the current one reaches a terminal state.
auto queue = std::make_shared<serp::CommandQueue>();
queue->enqueue(std::make_shared<MyCommand>());
queue->enqueue(std::make_shared<AnotherCommand>());Commands are executed in the order they are enqueued. Canceling the queue drains all pending commands.
A CommandQueue backed by a dedicated EventLoop with integrated watchdog enforcement. The processor owns its own thread; commands execute there regardless of which thread enqueues them.
auto processor = std::make_shared<serp::CommandProcessor>(
loop,
std::chrono::seconds(10) // watchdog timeout
);
processor->enqueue(std::make_shared<FetchCommand>());The watchdog fires if a command does not call finish() (or abort/cancel) within the configured timeout. The command transitions to the Watchdog state, and the queue continues with the next command.
A convenience class that wraps a lambda as a command — no subclass required for simple cases.
processor->enqueue(serp::LambdaCommand::create([](auto cmd) {
doSomeWork();
cmd->finish();
}));The lambda receives a reference to the command itself, allowing you to call finish(), abort(), or check for cancellation inside the lambda body.
A managed pool of reusable command objects. Avoids repeated allocation for high-frequency command patterns. Acquire a command from the pool, use it, and return it when done.
auto pool = std::make_shared<serp::CommandPool<FetchCommand>>(8 /*pool size*/);
auto cmd = pool->acquire();
// configure cmd...
processor->enqueue(cmd);Register a listener to be notified on state transitions:
cmd->addListener([](serp::Command& c) {
switch (c.getState()) {
case serp::Command::State::Finished:
// handle success
break;
case serp::Command::State::Watchdog:
// handle timeout
break;
default:
break;
}
});Listeners are called on the EventLoop of the processor that executed the command.
class FetchCommand : public serp::Command {
public:
Data result;
protected:
void onExecute() override {
// Begin async work. finish() will be called from the callback.
fetchData([this](Data d) {
result = d;
finish();
});
}
void onPostProcessing() override {
// Called after finish(), before the next command starts.
// Use for transaction cleanup, logging, or post-processing.
logInfo()->info("FetchCommand completed, result size: " +
std::to_string(result.size()));
}
};
auto processor = std::make_shared<serp::CommandProcessor>(
loop, std::chrono::seconds(10));
auto cmd = std::make_shared<FetchCommand>();
cmd->addListener([](serp::Command& c) {
if (c.getResult() == serp::Command::Result::Success) {
auto& fetch = static_cast<FetchCommand&>(c);
useData(fetch.result);
}
});
processor->enqueue(cmd);auto processor = std::make_shared<serp::CommandProcessor>(
loop, std::chrono::seconds(5));
// Simple fire-and-forget work item
processor->enqueue(serp::LambdaCommand::create([](auto cmd) {
sendConfigPacket();
cmd->finish();
}));
// Chained commands — they execute in order, one at a time
processor->enqueue(serp::LambdaCommand::create([](auto cmd) {
openConnection([cmd]() { cmd->finish(); });
}));
processor->enqueue(serp::LambdaCommand::create([](auto cmd) {
authenticate([cmd](bool ok) {
if (ok) cmd->finish();
else cmd->abort();
});
}));auto cmd = std::make_shared<LongRunningCommand>();
processor->enqueue(cmd);
// Later:
cmd->cancel();If the command is still pending, it is removed from the queue. If it is already running, cancel() sets a flag; your onExecute() should check isCanceled() and stop work gracefully.
The watchdog timeout is set per CommandProcessor. If a command does not reach a terminal state within that duration:
- The command transitions to
Watchdogstate. - Listeners are notified.
-
onPostProcessing()is skipped. - The queue advances to the next command.
This prevents a stalled async operation from blocking the entire queue indefinitely.
- Services and Lifecycle — thread model and event loop binding
- Methods — typed inter-service calls that commands can invoke
Getting Started
The Development Model
Architecture Language
Code Generator
- Generator Overview
- Generated Code Layout
- Deployment Configurations
- Lifecycle Backends
- CMake Integration
Framework Internals
- Core Concepts
- Services & Lifecycle
- Methods
- Properties
- Notifications
- Timers & Watchdog
- Promises & Async
- Streams
- Commands
- Logging
- Test Engine
- Transports
- Runtime & Debug Tools
VS Code Plugin
Examples