Architecture
Modules
asimov-manager/
├── main.c # Entry point, 100 Hz main loop
├── app_context/ # Global config singleton
├── config/ # YAML config parser
├── control/ # UDP to asimov-firmware
├── media/ # GStreamer pipelines
│ ├── publisher.c # Video/audio → LiveKit
│ ├── subscriber.c # LiveKit → speaker, DataChannel → commands
│ ├── video_track.c # Camera capture (v4l2src)
│ ├── mic_track.c # Microphone capture (alsasrc)
│ └── speaker_track.c # Speaker playback (alsasink)
├── teleop/ # Command/state binary packing
└── telemetry/ # Prometheus metrics
Command Flow (Operator → Robot)
flowchart TD
op["Operator UI"] -->|"TeleopCommand_S (22 bytes)"| dc["LiveKit DataChannel (reliable)"]
dc --> sub["subscriber.c::on_data_received()"]
sub --> teleop["TELEOP_handle_command()"]
teleop -->|"Convert to RobotCommand_S"| ctrl["CONTROL_send_command()"]
ctrl -->|"Serialize to nanopb"| udp["UDP :8888"]
udp --> fw["asimov-firmware"]
State Flow (Robot → Operator @ 50 Hz)
flowchart TD
fw["asimov-firmware"] -->|"RobotState (nanopb)"| udp["UDP :8889"]
udp --> ctrl["CONTROL_process()::deserialize_state()"]
ctrl --> teleop["TELEOP_send_state()"]
teleop -->|"Pack to TeleopState_S"| pub["MEDIA_publisher_send_data()"]
pub --> dc["LiveKit DataChannel (lossy)"]
dc --> op["Operator UI"]
Binary Formats
TeleopCommand_S (22 bytes)
Operator → Robot. Reliable delivery.
struct {
uint8_t mode; // ControlMode_E
float vx; // Forward velocity [m/s]
float vy; // Lateral velocity [m/s]
float vyaw; // Yaw rate [rad/s]
uint8_t gait_mode; // GaitMode_E
uint8_t enable; // Motors enabled
uint8_t e_stop; // Emergency stop
} __attribute__((packed));
TeleopState_S (~170 bytes)
Robot → Operator @ 50 Hz. Lossy delivery.
struct {
uint64_t timestamp_us;
uint32_t sequence;
uint8_t mode;
uint8_t motors_enabled;
uint8_t emergency_stop;
float joint_pos[12];
float joint_vel[12];
float base_ang_vel[3];
float projected_gravity[3];
float gait_phase;
float battery_voltage;
uint8_t battery_percent;
} __attribute__((packed));
GStreamer Pipelines
Video (per camera)
flowchart LR
v4l2["v4l2src device=/dev/video0"] --> conv["videoconvert"]
conv --> sink["livekitwebrtcsink (VP8/H264)"]
sink --> lk["LiveKit Server"]
Audio
flowchart LR
subgraph capture["Mic Capture"]
alsa1["alsasrc device=hw:0,0"] --> conv1["audioconvert"]
conv1 --> sink1["livekitwebrtcsink"]
end
subgraph playback["Speaker Playback"]
src2["livekitwebrtcsrc"] --> conv2["audioconvert"]
conv2 --> alsa2["alsasink device=plughw:0,0"]
end
Bandwidth
Stream |
Rate |
Bandwidth |
|---|---|---|
Video (1920x1200 VP8) |
50 fps |
~2-4 Mbps |
Audio (Opus) |
48 kHz |
~64 kbps |
State |
50 Hz |
~8.5 KB/s |
Commands |
On demand |
Negligible |
Total upstream: ~3-5 Mbps per camera.
Source Files
File |
What it does |
|---|---|
|
Entry point, 100 Hz main loop |
|
Global config singleton |
|
YAML config parser |
|
UDP to firmware, nanopb serialization |
|
Video/audio capture, LiveKit streaming |
|
Audio playback, DataChannel commands |
|
Camera capture pipeline |
|
Microphone capture pipeline |
|
Speaker playback pipeline |
|
Command/state binary packing |
|
Prometheus metrics |