# 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) ```{mermaid} 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) ```{mermaid} 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. ```c 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. ```c 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) ```{mermaid} flowchart LR v4l2["v4l2src device=/dev/video0"] --> conv["videoconvert"] conv --> sink["livekitwebrtcsink (VP8/H264)"] sink --> lk["LiveKit Server"] ``` ### Audio ```{mermaid} 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 | |------|--------------| | `main.c` | Entry point, 100 Hz main loop | | `app_context/app_context.c` | Global config singleton | | `config/config.c` | YAML config parser | | `control/control.c` | UDP to firmware, nanopb serialization | | `media/publisher.c` | Video/audio capture, LiveKit streaming | | `media/subscriber.c` | Audio playback, DataChannel commands | | `media/video_track.c` | Camera capture pipeline | | `media/mic_track.c` | Microphone capture pipeline | | `media/speaker_track.c` | Speaker playback pipeline | | `teleop/teleop.c` | Command/state binary packing | | `telemetry/telemetry.c` | Prometheus metrics |