#include "audio_output.h" #include "audio_receiver.h" #include "buttons.h" #include "spiram_task.h" #include "display.h" #include "dns_server.h" #include "ethernet.h" #include "led.h" #include "hap.h" #include "mdns_airplay.h" #include "nvs_flash.h" #include "playback_control.h" #include "ptp_clock.h" #include "rtsp_server.h" #include "settings.h" #include "web_server.h" #include "log_stream.h" #include "wifi.h" #include "spiffs_storage.h" #ifdef CONFIG_BT_A2DP_ENABLE #include "a2dp_sink.h" #include "rtsp_events.h" #endif #include "iot_board.h" #include "panel_uart.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" static const char *TAG = "main"; // AP mode IP address (192.168.4.1 in network byte order) #define AP_IP_ADDR 0x0104A8C0 static bool s_airplay_started = false; static bool s_airplay_infrastructure_ready = false; static void start_airplay_services(void) { if (s_airplay_started) { return; } ESP_LOGI(TAG, "Starting AirPlay services..."); // One-time infrastructure init (PTP, HAP, audio receiver/output) if (!s_airplay_infrastructure_ready) { esp_err_t err = ptp_clock_init(); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { ESP_LOGE(TAG, "Failed to init PTP clock: %s", esp_err_to_name(err)); s_airplay_started = false; return; } ESP_ERROR_CHECK(hap_init()); ESP_ERROR_CHECK(audio_receiver_init()); mdns_airplay_init(); s_airplay_infrastructure_ready = true; } audio_output_start(); ESP_ERROR_CHECK(rtsp_server_start()); s_airplay_started = true; playback_control_set_source(PLAYBACK_SOURCE_AIRPLAY); ESP_LOGI(TAG, "AirPlay ready"); } #ifdef CONFIG_BT_A2DP_ENABLE static void stop_airplay_services(void) { if (!s_airplay_started) { return; } ESP_LOGI(TAG, "Stopping AirPlay services..."); rtsp_server_stop(); audio_output_stop(); s_airplay_started = false; playback_control_set_source(PLAYBACK_SOURCE_NONE); ESP_LOGI(TAG, "AirPlay stopped"); } #endif static void network_monitor_task(void *pvParameters) { (void)pvParameters; bool had_network = ethernet_is_connected() || wifi_is_connected(); bool dns_running = !had_network; bool wifi_started = wifi_is_connected() || !ethernet_is_connected(); bool had_eth = ethernet_is_connected(); // Start captive portal DNS if no network yet if (dns_running) { dns_server_start(AP_IP_ADDR); } while (1) { vTaskDelay(pdMS_TO_TICKS(2000)); bool eth_up = ethernet_is_connected(); bool wifi_up = wifi_is_connected(); bool has_network = eth_up || wifi_up; // Ethernet just came up — stop WiFi entirely if (eth_up && !had_eth && wifi_started) { ESP_LOGI(TAG, "Ethernet connected — stopping WiFi"); wifi_stop(); wifi_started = false; wifi_up = false; } // Ethernet dropped — bring up WiFi (AP + STA) if (!eth_up && had_eth) { ESP_LOGI(TAG, "Ethernet down — starting WiFi as fallback"); wifi_init_apsta(NULL, NULL); wifi_started = true; } had_eth = eth_up; has_network = eth_up || wifi_is_connected(); if (has_network == had_network) { continue; } if (has_network) { ESP_LOGI(TAG, "Network up (eth=%s, wifi=%s)", eth_up ? "yes" : "no", wifi_up ? "yes" : "no"); start_airplay_services(); if (dns_running) { dns_server_stop(); dns_running = false; } } else { if (!dns_running) { dns_server_start(AP_IP_ADDR); dns_running = true; } } had_network = has_network; } } #ifdef CONFIG_BT_A2DP_ENABLE static void on_bt_state_changed(bool connected) { panel_uart_set_bt_state(connected, false); if (connected) { ESP_LOGI(TAG, "BT connected — disabling AirPlay"); stop_airplay_services(); playback_control_set_source(PLAYBACK_SOURCE_BLUETOOTH); } else { ESP_LOGI(TAG, "BT disconnected — re-enabling AirPlay"); playback_control_set_source(PLAYBACK_SOURCE_NONE); if (ethernet_is_connected() || wifi_is_connected()) { start_airplay_services(); } } } static void on_airplay_client_event(rtsp_event_t event, const rtsp_event_data_t *data, void *user_data) { (void)data; (void)user_data; if (bt_a2dp_sink_is_connected()) { return; } switch (event) { case RTSP_EVENT_CLIENT_CONNECTED: ESP_LOGI(TAG, "AirPlay client connected — disabling BT"); bt_a2dp_sink_set_discoverable(false); break; case RTSP_EVENT_PAUSED: // V1 grace period active — keep BT hidden so the phone reconnects // to AirPlay rather than falling back to BT. ESP_LOGI(TAG, "AirPlay paused — keeping BT hidden"); break; case RTSP_EVENT_DISCONNECTED: ESP_LOGI(TAG, "AirPlay client disconnected — enabling BT"); bt_a2dp_sink_set_discoverable(true); break; default: break; } } #endif void app_main(void) { // Initialize NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); ESP_ERROR_CHECK(settings_init()); spiffs_storage_init(); log_stream_init(); ESP_ERROR_CHECK(playback_control_init()); led_init(); // Initialize board-specific hardware (includes I2C/SPI bus for display and // DAC) ESP_LOGI(TAG, "Board: %s", iot_board_get_info()); esp_err_t err = iot_board_init(); if (err != ESP_OK) { ESP_LOGE(TAG, "Board init failed: %s", esp_err_to_name(err)); } else { panel_uart_init(); } // 立即初始化音频硬件输出(包括 I2S 通道和 DAC),以便蓝牙上电即可在无网状态下使用 ESP_ERROR_CHECK(audio_output_init()); // Pass the board-owned bus to the display so it reuses it rather than // creating a duplicate bus on the same pins. #if defined(CONFIG_DISPLAY_BUS_SPI) display_init(iot_board_get_handle(BOARD_SPI_DISP_ID)); #else display_init(iot_board_get_handle(BOARD_I2C_DISP_ID)); #endif // Initialize LVGL-dependent board resources (e.g., touch input) after // display/LVGL port is ready. iot_board_init_lvgl_resources(); // Try ethernet first bool eth_available = false; err = ethernet_init(); if (err == ESP_OK) { // Wait for ethernet link + DHCP (up to 5s for link, then 10s more for DHCP) ESP_LOGI(TAG, "Waiting for ethernet..."); for (int i = 0; i < 25 && !ethernet_is_link_up(); i++) { vTaskDelay(pdMS_TO_TICKS(200)); } if (ethernet_is_link_up() && !ethernet_is_connected()) { ESP_LOGI(TAG, "Ethernet link up, waiting for DHCP..."); for (int i = 0; i < 50 && !ethernet_is_connected(); i++) { vTaskDelay(pdMS_TO_TICKS(200)); } } eth_available = ethernet_is_connected(); if (eth_available) { ESP_LOGI(TAG, "Ethernet connected"); } else { ESP_LOGI(TAG, "Ethernet not connected (cable?), will use WiFi"); } } else if (err != ESP_ERR_NOT_SUPPORTED) { ESP_LOGW(TAG, "Ethernet init failed: %s", esp_err_to_name(err)); } // Start WiFi only if ethernet is not available if (!eth_available) { wifi_init_apsta(NULL, NULL); // Wait for initial WiFi connection if credentials exist if (settings_has_wifi_credentials()) { if (!wifi_wait_connected(30000)) { ESP_LOGI(TAG, "Connect to 'ESP32-AirPlay-Setup' -> http://192.168.4.1"); } } else { ESP_LOGI(TAG, "Connect to 'ESP32-AirPlay-Setup' -> http://192.168.4.1"); } } else { ESP_LOGI(TAG, "Ethernet connected — skipping WiFi"); } // Start services that work on any interface web_server_start(80); task_create_spiram(network_monitor_task, "net_mon", 4096, NULL, 5, NULL, NULL); bool connected = eth_available || wifi_is_connected(); if (connected) { start_airplay_services(); } #ifdef CONFIG_BT_A2DP_ENABLE // Initialize Bluetooth A2DP Sink { char bt_name[65]; settings_get_device_name(bt_name, sizeof(bt_name)); esp_err_t bt_err = bt_a2dp_sink_init(bt_name, on_bt_state_changed); if (bt_err != ESP_OK) { ESP_LOGE(TAG, "BT A2DP init failed: %s", esp_err_to_name(bt_err)); } else { rtsp_events_register(on_airplay_client_event, NULL); } } #endif buttons_init(); while (1) { vTaskDelay(pdMS_TO_TICKS(10000)); } }