Task architecture: Four FreeRTOS tasks (Radio, Mesh, Probe, Display) run concurrently on Core 0. Each task can block on events (DIO1 ISR, timer, socket). Message Router is singleton — all inter-task communication flows through it. No polling loops; all wake via ISR/event. Heartbeat is event-driven via `kick()` calls, not periodic ticks. Arduino `loop()` stays empty — FreeRTOS scheduler owns the CPU.
Segment 3: Incoming Data — Commands & Sensor Readings
Data ingress: Commands arrive from six transport layers (LoRa, BLE GATT, WiFi HTTP API, MQTT subscribe, Serial CLI, GPIO button) → CommandManager deserialized them (converts bytes → ControlPacket) → Routes to correct handler (RelayManager, ScheduleManager, etc.) → Handler executes action (relay state, schedule task) or publishes telemetry. All transport handlers are bidirectional; responses flow back out same transport.
Segment 4: Outgoing Data — Telemetry & Status Broadcasting
Data egress: StatusBuilder, HeartbeatBuilder, ConfigSnapshot construct JSON/MessagePack telemetry snapshots → Message Serializer encodes per-transport format → Broadcasts simultaneously to all active transports (LoRa TX, BLE advertising, WiFi `/api/status`, MQTT publish, Serial output). Broadcast happens every heartbeat interval (2s when active, 5min idle). If a transport is down (WiFi disconnected, BLE not connected), that path is skipped — no error.
Complete cycle (latency ~20ms): User command arrives via BLE (or any transport) → BLE layer deserializes → CommandManager routes to handler (RelayManager, ScheduleManager, etc.) → Handler executes action (set relay GPIO, schedule task) → Parallel: StatusBuilder captures new state → Message Serializer broadcasts new status to all transports (LoRa, BLE advertising, WiFi `/api/status`, MQTT, Serial) within ~20ms. User sees state change reflected immediately in app, dashboard, or third-party systems listening on MQTT.