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

VariantFieldsDescription
Ackcommand_id: u64Command executed successfully
Failcommand_id: u64, reason: StringCommand 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.