| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- #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));
- }
- }
|