Bidirectional Command Channel
The server can send commands back to edge devices over QUIC bidirectional streams. Devices register a CommandHandler to receive and execute commands, then return acknowledgements or failure reasons.
graph LR
OP["Operator / Agent"] --> BUS["ControlBus"]
BUS --> SRV["Tetrapus Server"]
SRV -->|bidi QUIC| DEV["Edge Device<br/><small>CommandHandler</small>"]
DEV -->|Ack / Fail| SRV
SRV --> AUDIT["Audit Trail"]
Implementing a Command Handler
Rust
use nerve_sdk::{CommandHandler, CommandEnvelope, CommandResult, FieldWrite};
struct MyHandler {
// your device state
}
impl CommandHandler for MyHandler {
fn handle(&mut self, envelope: CommandEnvelope) -> CommandResult {
for write in &envelope.writes {
match write.field.as_str() {
"target_temp" => {
set_thermostat(write.entity_id.clone(), write.value);
println!("Set {} temp to {}", write.entity_id, write.value);
}
_ => {
return CommandResult::Fail {
command_id: envelope.command_id,
reason: format!("Unknown field: {}", write.field),
};
}
}
}
CommandResult::Ack { command_id: envelope.command_id }
}
}
// Register on the client
client.set_command_handler(MyHandler { /* state */ }); Wire Types
FieldWrite
yaml FieldWrite auto-generated
| Field | Type | Default | Description |
|---|---|---|---|
| entity_id* | String | — | The target entity ID (e.g., `"hvac-unit-42"`). |
| field* | String | — | The field name from the YAML `command_schema` (e.g., `"target_temp"`). |
| value* | f64 | — | The numeric value to write. For boolean fields, use `0.0` (false) / `1.0` (true). For enum fields, use the numeric code from the schema variants map. |
CommandEnvelope
yaml CommandEnvelope auto-generated
| Field | Type | Default | Description |
|---|---|---|---|
| command_id* | u64 | — | Unique command ID matching the `ControlBus::IssuedCommand::id`. Used to correlate the [`CommandResult`] back to the originating audit entry. |
| label* | String | — | Human-readable label for handler-side logging and audit display. |
| writes* | Vec<FieldWrite> | — | The expanded list of entity-level field writes that comprise this command. |
CommandResult
| Variant | Fields | Description |
|---|---|---|
| Ack | command_id: u64 | Command executed successfully |
| Fail | command_id: u64, reason: String | Command failed — reason shown in audit trail |
Command Schema (YAML)
Define commandable fields in command_schema.yaml. Presence of this file enables the command router.
YAML
command_schema:
fields:
- name: target_temp
description: "Thermostat setpoint"
value_type: float
range: [16.0, 30.0]
- name: fan_mode
description: "Fan operating mode"
value_type: enum
variants:
"0": "off"
"1": "low"
"2": "high"
"3": "auto"
- name: emergency_stop
description: "Emergency shutdown"
value_type: bool Wire Protocol
Both directions use the same framing on the QUIC bidirectional stream:
Text
[length: u32 BE][bincode-encoded payload] Server sends CommandEnvelope, device responds with CommandResult. The handler runs on a dedicated OS thread ("sdk-command-handler") to avoid blocking the tokio runtime.
Questions?
Reach out for help with integration, deployment, or custom domain codecs.