Anolis Automation Layer
Automation system for orchestrating device control using behavior trees.
Overview
The automation layer adds configurable machine behavior on top of Anolisβs stable IO primitives. It provides:
- Behavior Trees β Composable, reactive control logic that orchestrates device calls
- Runtime Modes β State machine governing when automation runs and how manual control interacts
- Parameter System β Config-tunable setpoints and limits
Build-Time Flag
BehaviorTree automation can be compiled out with:
cmake -DANOLIS_ENABLE_AUTOMATION=OFF ...
When compiled out, BT-specific components are not built (anolis_automation, bt_nodes_sanity), and BT endpoints
return UNAVAILABLE with a build-time-disabled message.
Architecture Constraints
The automation layer is a consumer of kernel services, NOT a replacement for core IO:
| Constraint | Implementation |
|---|---|
| BT nodes read via StateCache | No direct provider access; kernel remains single source of truth |
| BT nodes act via CallRouter | All device calls go through validated control path |
| No new provider protocol features | Automation uses existing ADPP v1 capabilities |
| No device-specific logic in BT engine | BT runtime is capability-agnostic |
The BT engine sits above the kernel, not beneath it.
BT Service Context Contract
Custom BT nodes consume a typed blackboard payload populated by BTRuntime:
- BlackBoard key:
anolis.bt_service_context - Payload type:
BTServiceContext
BTServiceContext fields:
state_cache(required)call_router(required)provider_registry(required)parameter_manager(optional)
BTRuntime refreshes this context before each tick() (including direct/test tick() calls),
so threaded and direct tick paths use the same node-service contract.
Runtime Modes
Mode State Machine
Anolis supports four runtime modes:
| Mode | Description | BT State | Manual Calls | Control Operations |
|---|---|---|---|---|
| IDLE | Safe startup/standby mode | Stopped | β Blocked | Safe default state |
| MANUAL | Operator control active | Stopped | β Allowed | Testing/manual ops |
| AUTO | Automated control active | Running | Gated by policy | Normal operation |
| FAULT | Error recovery state | Stopped | β Blocked | Requires recovery |
Operation Types:
- Control Operations:
CallDevice(actuates hardware) - Read-Only Operations: Health, status,
ReadSignals, discovery
IDLE vs MANUAL:
- IDLE: System is safe but inactive (default at startup). Control operations blocked. Read-only queries allowed. Operator must explicitly transition to MANUAL to enable control.
- MANUAL: System ready for operator control. All manual operations allowed. Common mode for testing and calibration.
Mode Transitions
Valid transitions:
ββββββββββββ
ββββββββ€ MANUAL ββββββββ
β ββββββ¬ββββββ β
β β β
βΌ βΌ βΌ
ββββββββ ββββββββ ββββββββ
β IDLE β β AUTO β βFAULT β
ββββ¬ββββ ββββββββ βββββ¬βββ
β β
ββββββββββββββββββββββββββββ
(Any β FAULT allowed)
Valid transitions:
- β IDLE β MANUAL
- β MANUAL β AUTO
- β Any β FAULT
- β FAULT β MANUAL (recovery path)
Invalid transitions:
- β FAULT β AUTO β Must recover through MANUAL first
- β FAULT β IDLE β Must recover through MANUAL first
- β AUTO β IDLE β Must go through MANUAL first (prevents automation bypass)
Rationale:
- Fault recovery requires explicit operator acknowledgment before resuming automation
- IDLE enforces safe startup by requiring explicit transition through MANUAL to AUTO
- No shortcuts from AUTO to IDLE prevents operators from accidentally killing automation without proper shutdown
Default Mode
- Startup: IDLE (safe default, control operations blocked)
- After fault: FAULT (requires explicit recovery)
- Runtime mode: System starts in IDLE (not configurable). Use
POST /v0/modeto transition modes
Safe Startup Sequence:
- Runtime starts in IDLE (control blocked)
- Providers initialize with safe device defaults
- Operator verifies state using read-only queries
- Operator transitions to MANUAL (enables control)
- Operator performs verification/calibration
- Operator transitions to AUTO (enables automation)
Manual/Auto Contention Policy
When in AUTO mode, manual device calls are gated by the configured policy:
BLOCK Policy (Default)
Manual calls are rejected when automation is active.
automation:
manual_gating_policy: BLOCK
Behavior:
- POST /v0/call returns
FAILED_PRECONDITIONerror - Logged as warning: βManual call blocked in AUTO modeβ
- BT continues running uninterrupted
Use case: Prevent accidental operator interference during automated sequences.
OVERRIDE Policy
Manual calls are allowed when automation is active.
automation:
manual_gating_policy: OVERRIDE
Behavior:
- POST /v0/call succeeds normally
- Logged as info: βManual call overriding AUTO modeβ
- BT continues running (may conflict with manual action)
Use case: Allow expert operators to override automation when needed.
HTTP API for Mode Control
GET /v0/mode
Get current automation mode.
Request:
GET /v0/mode HTTP/1.1
Response (automation enabled):
{
"status": { "code": "OK", "message": "ok" },
"mode": "MANUAL"
}
Response (automation disabled):
{
"status": {
"code": "UNAVAILABLE",
"message": "Automation layer not enabled"
}
}
POST /v0/mode
Set automation mode.
Request:
POST /v0/mode HTTP/1.1
Content-Type: application/json
{
"mode": "AUTO"
}
Response (success):
{
"status": { "code": "OK", "message": "ok" },
"mode": "AUTO"
}
Response (invalid transition):
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
{
"status": {
"code": "FAILED_PRECONDITION",
"message": "Invalid mode transition: FAULT -> AUTO"
}
}
Response (invalid mode string):
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"status": {
"code": "INVALID_ARGUMENT",
"message": "Invalid mode: 'BADMODE' (must be MANUAL, AUTO, IDLE, or FAULT)"
}
}
Mode Change Events
Mode transitions emit telemetry events for observability.
Event Type: mode_change
Fields:
previous_mode(string): Mode before transitionnew_mode(string): Mode after transitiontimestamp_ms(int64): Unix timestamp in milliseconds
InfluxDB Line Protocol:
mode_change previous_mode="MANUAL",new_mode="AUTO" 1706918400000
Grafana Query (Flux):
from(bucket: "anolis")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "mode_change")
|> yield(name: "mode_transitions")
Parameters
Anolis supports runtime parameters that can be declared in YAML and updated at runtime via HTTP. Parameters are read-only from Behavior Trees (exposed via the blackboard) and can be validated with min/max ranges or enum allowed values.
YAML Declaration
Example:
automation:
parameters:
- name: temp_setpoint
type: double
default: 25.0
min: 10.0
max: 50.0
- name: control_enabled
type: bool
default: true
- name: operating_mode
type: string
default: "normal"
allowed_values: ["normal", "test", "emergency"]
Notes:
- Supported types:
double,int64,bool,string. min/maxapply to numeric types only.allowed_valuesapplies to string enums.- Persistence (writing changes back to YAML) is not implemented yet.
- The intent is to keep this opt-in behind a future
automation.persist_parametersflag.
HTTP API for Parameters
GET /v0/parameters
- Returns the list of declared parameters with current values and constraints.
POST /v0/parameters
- Update a parameter at runtime.
- Body:
{"name": "temp_setpoint", "value": 30.0} - Server validates type and constraints and replies with 200 OK on success or 400/INVALID_ARGUMENT on validation failure.
Example (update):
POST /v0/parameters HTTP/1.1
Content-Type: application/json
{"name": "temp_setpoint", "value": 30.0}
Example (response):
{
"status": { "code": "OK", "message": "ok" },
"parameter": { "name": "temp_setpoint", "value": 30.0 }
}
BT Access
Behavior Trees can access parameters via the GetParameter node.
The node is available in the default node registry and reads a parameter by name from the blackboard using the param input port.
It returns SUCCESS with the value available on the value output port only for numeric parameter types (double, int64).
For bool and string parameters, GetParameter returns FAILURE by design.
Example:
<GetParameter param="temp_setpoint" value="{target_temp}"/>
BT Node Reference
Anolis provides four custom BehaviorTree.CPP action nodes for interacting with devices and runtime parameters.
CallDevice
Invokes a device function through the CallRouter with validated arguments.
Ports:
| Port | Type | Direction | Description |
|---|---|---|---|
| device_handle | string | input | Device handle (format: provider_id/device_id) |
| function_name | string | input | Function identifier |
| args | string | input | Arguments as JSON object (default: {}) |
| success | bool | output | Call result (true/false) |
| error | string | output | Error message if call failed |
Args JSON Format:
The args port accepts a JSON object string containing function arguments:
<!-- Single argument -->
<CallDevice device_handle="sim0/tempctl0"
function_name="set_target_temp"
args='{"target":30.0}'/>
<!-- Multiple arguments -->
<CallDevice device_handle="sim0/motorctl0"
function_name="set_motor"
args='{"mode":"PWM","duty":75,"frequency":1000}'/>
<!-- No arguments -->
<CallDevice device_handle="sim0/relayio0"
function_name="reset"/>
Supported JSON Types:
| JSON Type | Protobuf Value Type | Example |
|---|---|---|
| number (float) | double_value | {"target":25.5} |
| number (int) | int64_value | {"count":100} |
| boolean | bool_value | {"enabled":true} |
| string | string_value | {"mode":"AUTO"} |
Error Messages:
"args must be a JSON object"β args is not a valid JSON object"JSON parse error: ..."β Malformed JSON syntax"Unsupported JSON type for arg 'X'"β null, array, or nested object passed
Validation:
Arguments are validated by CallRouter using ArgSpec metadata from the device registry:
- Required arguments must be present
- Type must match expected type
- Numeric values must be within min/max range
ReadSignal
Reads a signal value from the StateCache.
Ports:
| Port | Type | Direction | Description |
|---|---|---|---|
| device_handle | string | input | Device handle (provider_id/device_id) |
| signal_id | string | input | Signal identifier |
| value | double | output | Signal value (as double) |
| quality | string | output | Signal quality (OK/STALE/UNAVAILABLE/FAULT) |
Example:
<ReadSignal device_handle="sim0/tempctl0"
signal_id="temperature"
value="{current_temp}"
quality="{temp_quality}"/>
CheckQuality
Verifies that a signalβs quality meets expectations (condition node).
Ports:
| Port | Type | Direction | Description |
|---|---|---|---|
| device_handle | string | input | Device handle (provider_id/device_id) |
| signal_id | string | input | Signal identifier |
| expected_quality | string | input | Expected quality (default: OK) |
Returns:
SUCCESSβ Signal quality matches expectedFAILUREβ Quality mismatch or signal not found
Example:
<!-- Gate control on sensor availability -->
<Sequence>
<CheckQuality device_handle="sim0/tempctl0"
signal_id="temperature"
expected_quality="OK"/>
<CallDevice device_handle="sim0/tempctl0"
function_name="set_target_temp"
args='{"target":30.0}'/>
</Sequence>
GetParameter
Reads a runtime parameter from the ParameterManager.
Ports:
| Port | Type | Direction | Description |
|---|---|---|---|
| param | string | input | Parameter name |
| value | double | output | Parameter value (numeric only) |
Returns:
SUCCESSβ Parameter read successfullyFAILUREβ Parameter not found, ParameterManager unavailable, or non-numeric parameter type (bool/string)
Example:
<GetParameter param="temp_setpoint" value="{target_temp}"/>
Configuration
Enabling Automation
automation:
enabled: true
behavior_tree: ./behaviors/demo.xml
tick_rate_hz: 10 # 1-1000 Hz
manual_gating_policy: BLOCK # BLOCK or OVERRIDE
Configuration Fields
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false | Enable/disable automation layer |
behavior_tree |
string | required | Path to BT XML file |
tick_rate_hz |
int | 10 | BT execution rate (1-1000) |
manual_gating_policy |
string | BLOCK | Manual call policy (BLOCK or OVERRIDE) |
parameters |
list | [] | Parameter definitions |
BT Execution Gating
The BT tick loop only runs when mode == AUTO:
while (running) {
if (mode != AUTO) {
sleep(tick_period);
continue; // Skip tick
}
tree.tick();
sleep(tick_period);
}
Behavior:
- MANUAL/IDLE/FAULT β BT paused cleanly (no state corruption)
- AUTO β BT ticks normally
- Transitioning to AUTO restarts BT from root node
Dependency Notes
BehaviorTree.CPP is pulled via vcpkg (behaviortree-cpp). If vcpkg lags, the fallback is to vendor a known-good release from GitHub.
Link it in core/CMakeLists.txt and keep the same include paths to avoid code changes.
Keep the same include paths to avoid code changes.
Usage Examples
Starting Automated Control
# 1. Verify current mode
curl http://localhost:8080/v0/mode
# 2. Switch to AUTO mode
curl -X POST http://localhost:8080/v0/mode \
-H "Content-Type: application/json" \
-d '{"mode":"AUTO"}'
# 3. Monitor mode transitions in telemetry
# (Check Grafana or query InfluxDB)
Emergency Stop
# Put system in FAULT mode (stops automation)
curl -X POST http://localhost:8080/v0/mode \
-H "Content-Type: application/json" \
-d '{"mode":"FAULT"}'
# Manually control devices
curl -X POST http://localhost:8080/v0/call \
-H "Content-Type: application/json" \
-d '{
"provider_id": "sim0",
"device_id": "tempctl0",
"function_name": "set_target_temp",
"arguments": {"target": 20.0}
}'
Recovery from FAULT
# 1. Recover to MANUAL
curl -X POST http://localhost:8080/v0/mode \
-H "Content-Type: application/json" \
-d '{"mode":"MANUAL"}'
# 2. Verify system state
curl http://localhost:8080/v0/state
# 3. Resume automation
curl -X POST http://localhost:8080/v0/mode \
-H "Content-Type: application/json" \
-d '{"mode":"AUTO"}'
Operator UI Integration (Planned)
The Operator UI does not yet expose mode or parameter controls. When UI work begins, use these endpoints:
GET /v0/modeandPOST /v0/modefor mode controlGET /v0/parametersandPOST /v0/parametersfor runtime parameter tuning
Testing
Automated tests for automation features:
# Run automation tests
python -m pytest tests/integration/test_integration.py -k automation_suite
# Test with custom executable paths
python -m pytest tests/integration/test_integration.py -k automation_suite \
--runtime path/to/anolis-runtime --provider path/to/anolis-provider-sim
Test coverage:
- Mode transitions (valid and invalid)
- Manual/auto contention policy
- BT execution gating
- Mode change events
- Automation disabled behavior
Safety Disclaimer
β οΈ Automation is a control policy layer, not a safety-rated system.
External safety systems (e.g., E-stops, interlocks) are still required for real hardware.
- FAULT mode is policy, not a certified safety mechanism
- Mode transitions are not safety-rated
- Manual override capability must not replace proper safety interlocks
For production deployment:
- Integrate with certified safety PLCs
- Implement hardware E-stops independent of Anolis
- Add watchdog timers for automation health
- Design BTs with safe failure modes