#include #include "app_context.h" #include "config.h" #include "data_model.h" #include "dd3_legacy_core.h" #include "display_ui.h" #include "lora_transport.h" #include "mqtt_client.h" #include "payload_codec.h" #include "power_manager.h" #include "receiver_pipeline.h" #include "sd_logger.h" #include "sender_state_machine.h" #include "time_manager.h" #include "web_server.h" #include "wifi_manager.h" #include #ifdef ARDUINO_ARCH_ESP32 #include #include #endif static DeviceRole g_role = DeviceRole::Sender; static uint16_t g_short_id = 0; static char g_device_id[16] = ""; static WifiMqttConfig g_cfg; static ReceiverSharedState g_receiver_shared = {}; static SenderStateMachine g_sender_state_machine; static ReceiverPipeline g_receiver_pipeline; static void serial_debug_printf(const char *fmt, ...) { if (!SERIAL_DEBUG_MODE) { return; } char buf[256]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); Serial.println(buf); } #ifdef ARDUINO_ARCH_ESP32 static void watchdog_init() { esp_task_wdt_deinit(); esp_task_wdt_config_t config = {}; config.timeout_ms = WATCHDOG_TIMEOUT_SEC * 1000; config.idle_core_mask = 0; config.trigger_panic = true; esp_task_wdt_init(&config); esp_task_wdt_add(nullptr); } static void watchdog_kick() { esp_task_wdt_reset(); } #else static void watchdog_init() {} static void watchdog_kick() {} #endif void setup() { Serial.begin(115200); delay(200); dd3_legacy_core_force_link(); #ifdef PAYLOAD_CODEC_TEST payload_codec_self_test(); #endif watchdog_init(); g_role = detect_role(); init_device_ids(g_short_id, g_device_id, sizeof(g_device_id)); display_set_role(g_role); if (SERIAL_DEBUG_MODE) { #ifdef ARDUINO_ARCH_ESP32 serial_debug_printf("boot: reset_reason=%d", static_cast(esp_reset_reason())); #endif serial_debug_printf("boot: role=%s short_id=%04X dev=%s", g_role == DeviceRole::Sender ? "sender" : "receiver", g_short_id, g_device_id); } lora_init(); display_init(); display_set_self_ids(g_short_id, g_device_id); if (g_role == DeviceRole::Sender) { SenderStateMachineConfig sender_cfg = {}; sender_cfg.short_id = g_short_id; sender_cfg.device_id = g_device_id; g_sender_state_machine.begin(sender_cfg); return; } power_receiver_init(); lora_receive_continuous(); pinMode(PIN_ROLE, INPUT); // release pulldown before SD uses GPIO14 as SCK sd_logger_init(); wifi_manager_init(); ReceiverPipelineConfig receiver_cfg = {}; receiver_cfg.short_id = g_short_id; receiver_cfg.device_id = g_device_id; receiver_cfg.shared = &g_receiver_shared; g_receiver_pipeline.begin(receiver_cfg); display_set_sender_statuses(g_receiver_shared.sender_statuses, NUM_SENDERS); bool has_cfg = wifi_load_config(g_cfg); // Store WiFi config in shared state for later reconnection attempts g_receiver_shared.wifi_config = g_cfg; g_receiver_shared.last_wifi_reconnect_attempt_ms = 0; if (has_cfg && wifi_connect_sta(g_cfg)) { g_receiver_shared.ap_mode = false; time_receiver_init(g_cfg.ntp_server_1.c_str(), g_cfg.ntp_server_2.c_str()); mqtt_init(g_cfg, g_device_id); web_server_set_config(g_cfg); web_server_set_sender_faults(g_receiver_shared.sender_faults_remote, g_receiver_shared.sender_last_error_remote); web_server_begin_sta(g_receiver_shared.sender_statuses, NUM_SENDERS); } else { g_receiver_shared.ap_mode = true; char ap_ssid[32]; snprintf(ap_ssid, sizeof(ap_ssid), "%s%04X", AP_SSID_PREFIX, g_short_id); wifi_start_ap(ap_ssid, AP_PASSWORD); // Store AP credentials in shared state for potential reconnection fallback strncpy(g_receiver_shared.ap_ssid, ap_ssid, sizeof(g_receiver_shared.ap_ssid) - 1); g_receiver_shared.ap_ssid[sizeof(g_receiver_shared.ap_ssid) - 1] = '\0'; strncpy(g_receiver_shared.ap_password, AP_PASSWORD, sizeof(g_receiver_shared.ap_password) - 1); g_receiver_shared.ap_password[sizeof(g_receiver_shared.ap_password) - 1] = '\0'; if (g_cfg.ntp_server_1.isEmpty()) { g_cfg.ntp_server_1 = "pool.ntp.org"; } if (g_cfg.ntp_server_2.isEmpty()) { g_cfg.ntp_server_2 = "time.nist.gov"; } web_server_set_config(g_cfg); web_server_set_sender_faults(g_receiver_shared.sender_faults_remote, g_receiver_shared.sender_last_error_remote); web_server_begin_ap(g_receiver_shared.sender_statuses, NUM_SENDERS); } } void loop() { if (g_role == DeviceRole::Sender) { g_sender_state_machine.loop(); } else { g_receiver_pipeline.loop(); } }