#include "panel_uart.h" #include "dac_njw1195.h" #include "board_common.h" #include "playback_control.h" #include "settings.h" #include "dacp_client.h" #include "rtsp_events.h" #include "audio_prompt.h" #ifdef CONFIG_BT_A2DP_ENABLE #include "a2dp_sink.h" #endif #include "driver/uart.h" #include "driver/gpio.h" #include "esp_log.h" #include "esp_timer.h" #include "esp_wifi.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" static const char TAG[] = "PanelUart"; #define UART_PORT UART_NUM_2 #define UART_TX_GPIO 4 #define UART_RX_GPIO 5 #define UART_BAUD_RATE 115200 #define UART_BUF_SIZE 256 // 协议定义 #define FRAME_HEAD_1 0x55 #define FRAME_HEAD_2 0xAA #define CMD_READ 0x01 #define CMD_WRITE 0x02 #define CMD_REPORT 0x81 // 功能监控项定义 #define ITEM_SOURCE 0x01 #define ITEM_BT_CONN 0x02 #define ITEM_BT_AUDIO 0x03 #define ITEM_DAC_MUTE 0x04 #define ITEM_AUTO_RECONN 0x05 #define ITEM_BATTERY 0x09 #define ITEM_CHG_FULL 0x0A #define ITEM_CHG_ING 0x0B #define ITEM_VOL_LR 0x0C #define ITEM_VOL_BASS 0x0D #define ITEM_RESET 0x12 #define ITEM_BASS_MUTE 0x13 // 协议状态值定义 (归一化为 0 和 1) #define PROTO_VAL_OFF 0x00 #define PROTO_VAL_ON 0x01 #define CHG_FULL_GPIO 34 #define CHG_ING_GPIO 35 static TaskHandle_t s_charge_task_handle = NULL; static volatile bool s_bt_connected = false; static volatile bool s_bt_streaming = false; static volatile uint8_t s_battery_percent = 0; static volatile bool s_chg_full = false; static volatile bool s_chg_ing = false; static volatile bool s_chg_error = false; static uint8_t s_bass_before_mute = 100; extern bool s_in_panel_update; // 定义在 dac_njw1195.c 中 // ---------------------------------------------------------------------------- // 内部辅助函数 // ---------------------------------------------------------------------------- static const char* get_item_name(uint8_t item) { switch (item) { case ITEM_SOURCE: return "SOURCE"; case ITEM_BT_CONN: return "BT_CONN"; case ITEM_BT_AUDIO: return "BT_AUDIO"; case ITEM_DAC_MUTE: return "DAC_MUTE"; case ITEM_AUTO_RECONN: return "AUTO_RECONN"; case ITEM_BATTERY: return "BATTERY"; case ITEM_CHG_FULL: return "CHG_FULL"; case ITEM_CHG_ING: return "CHG_ING"; case ITEM_VOL_LR: return "VOL_LR"; case ITEM_VOL_BASS: return "VOL_BASS"; case ITEM_RESET: return "RESET"; case ITEM_BASS_MUTE: return "BASS_MUTE"; default: return "UNKNOWN"; } } static void send_frame(uint8_t cmd, uint8_t item, uint8_t value) { uint8_t len = 2; uint8_t crc = cmd ^ len ^ item ^ value; uint8_t buf[7] = { FRAME_HEAD_1, FRAME_HEAD_2, cmd, len, item, value, crc }; uart_write_bytes(UART_PORT, buf, 7); ESP_LOGI(TAG, "TX [%s]: %02X %02X %02X %02X %02X %02X %02X", get_item_name(item), buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); } static void send_ack(void) { uint8_t ack = 0xA0; // 成功响应应答,区别于帧头 0xAA uart_write_bytes(UART_PORT, &ack, 1); ESP_LOGI(TAG, "RX ACK: %02X", ack); } static void send_report_frame(uint8_t *items, uint8_t *values, uint8_t count) { uint8_t len = 1 + count * 2; uint8_t buf[5 + len]; // Header(2) + Cmd(1) + Len(1) + Payload(len) + CRC(1) int idx = 0; buf[idx++] = FRAME_HEAD_1; buf[idx++] = FRAME_HEAD_2; buf[idx++] = CMD_REPORT; buf[idx++] = len; uint8_t crc = CMD_REPORT ^ len ^ count; buf[idx++] = count; for (int i = 0; i < count; i++) { buf[idx++] = items[i]; buf[idx++] = values[i]; crc ^= items[i] ^ values[i]; } buf[idx++] = crc; uart_write_bytes(UART_PORT, buf, idx); char hex_str[128] = {0}; int offset = 0; for (int i = 0; i < idx; i++) { offset += snprintf(hex_str + offset, sizeof(hex_str) - offset, "%02X ", buf[i]); } ESP_LOGI(TAG, "TX_RAW: %s", hex_str); } void panel_uart_report(uint8_t item, uint8_t value) { send_frame(CMD_REPORT, item, value); } // ---------------------------------------------------------------------------- // GPIO 充电状态中断处理与控制任务 // ---------------------------------------------------------------------------- static void IRAM_ATTR charge_isr_handler(void *arg) { BaseType_t need_yield = pdFALSE; if (s_charge_task_handle != NULL) { vTaskNotifyGiveFromISR(s_charge_task_handle, &need_yield); } if (need_yield) { portYIELD_FROM_ISR(); } } static void read_charge_status(void) { int full = gpio_get_level(CHG_FULL_GPIO); int ing = gpio_get_level(CHG_ING_GPIO); // 新三极管电路逻辑 (平时上拉为1,触发有效为0): // 充满: full=0, ing=1 // 充电中: full=1, ing=0 // 异常: full=0, ing=0 // 未接充电器: full=1, ing=1 s_chg_full = (full == 0 && ing == 1); s_chg_ing = (full == 1 && ing == 0); s_chg_error = (full == 0 && ing == 0); } static void charge_status_task(void *arg) { vTaskDelay(pdMS_TO_TICKS(100)); read_charge_status(); // 面板在上报完整数据前会舍弃单项指令。 // 因此开机不在这里单独上报,由 report_status_task 延迟 2s 后的 report_all_status 统一下发初始状态。 int64_t last_edge_time = 0; int toggle_count = 0; while (1) { // 600ms 超时:若 600ms 内无引脚跳变,说明电平稳定,重置翻转计数器 uint32_t notified = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(600)); if (notified > 0) { vTaskDelay(pdMS_TO_TICKS(30)); // 消抖 int64_t now = esp_timer_get_time(); int64_t delta_ms = (now - last_edge_time) / 1000; last_edge_time = now; // 芯片异常时引脚会以 250ms 周期反复翻转 (周期约 150ms ~ 400ms) if (delta_ms > 120 && delta_ms < 450) { toggle_count++; } else if (delta_ms > 600) { toggle_count = 0; } bool old_full = s_chg_full; bool old_ing = s_chg_ing; bool old_err = s_chg_error; if (toggle_count >= 2) { // 连续监测到 250ms 快速翻转,判定为充电异常 s_chg_error = true; s_chg_full = false; s_chg_ing = false; } else if (toggle_count == 0) { read_charge_status(); } if (s_chg_full != old_full || s_chg_ing != old_ing || s_chg_error != old_err) { if (s_chg_error) { send_frame(CMD_REPORT, ITEM_CHG_ING, 0xFE); // 充电异常错误码 ESP_LOGE(TAG, "Chg: Error (250ms Toggling Detected)"); } else if (s_chg_full) { send_frame(CMD_REPORT, ITEM_CHG_FULL, 1); ESP_LOGI(TAG, "Chg: Full"); } else if (s_chg_ing) { send_frame(CMD_REPORT, ITEM_CHG_ING, 1); ESP_LOGI(TAG, "Chg: Charging"); } else { send_frame(CMD_REPORT, ITEM_CHG_ING, 0); ESP_LOGI(TAG, "Chg: Discharging"); } } } else { // 600ms 超时无新中断,判定电平已经稳定 if (toggle_count > 0 || s_chg_error) { toggle_count = 0; bool old_full = s_chg_full; bool old_ing = s_chg_ing; bool old_err = s_chg_error; read_charge_status(); if (s_chg_full != old_full || s_chg_ing != old_ing || s_chg_error != old_err) { if (s_chg_full) { send_frame(CMD_REPORT, ITEM_CHG_FULL, 1); ESP_LOGI(TAG, "Chg: Full (Recovered)"); } else if (s_chg_ing) { send_frame(CMD_REPORT, ITEM_CHG_ING, 1); ESP_LOGI(TAG, "Chg: Charging (Recovered)"); } else { send_frame(CMD_REPORT, ITEM_CHG_ING, 0); ESP_LOGI(TAG, "Chg: Discharging (Recovered)"); } } } } } } // ---------------------------------------------------------------------------- // 全部状态主动上报 // ---------------------------------------------------------------------------- static void report_all_status(void) { // 上报全部 10 项面板状态参数 uint8_t rpt_items[] = { ITEM_SOURCE, ITEM_BT_CONN, ITEM_BT_AUDIO, ITEM_DAC_MUTE, ITEM_AUTO_RECONN, ITEM_VOL_LR, ITEM_VOL_BASS, ITEM_BATTERY, ITEM_CHG_FULL, ITEM_CHG_ING }; uint8_t rpt_values[] = { (dac_njw1195_get_source() == NJW_SOURCE_BT) ? PROTO_VAL_ON : PROTO_VAL_OFF, s_bt_connected ? PROTO_VAL_ON : PROTO_VAL_OFF, s_bt_streaming ? PROTO_VAL_ON : PROTO_VAL_OFF, PROTO_VAL_OFF, // DAC 硬件静音状态(默认不静音,为 0) PROTO_VAL_ON, // 自动连接默认开启 dac_njw1195_get_vol_main(), dac_njw1195_get_vol_bass(), s_battery_percent, s_chg_full ? 1 : 0, s_chg_ing ? 1 : 0 }; send_report_frame(rpt_items, rpt_values, 10); ESP_LOGI(TAG, "主动上报 (10 项)"); } // ---------------------------------------------------------------------------- // 电池电量定期自动上报任务 // ---------------------------------------------------------------------------- static void report_status_task(void *arg) { // 开机等待 2 秒(等待各模块初始化稳定后),发送包含电量、连接等 10 项的完整状态报告 vTaskDelay(pdMS_TO_TICKS(2000)); int pct = 0; bool chg = false; // 初始化并读取真实电量 if (board_battery_read(&pct, &chg)) { s_battery_percent = pct; } // 开机 2s 后主动推送全局初始化状态(包含正确的初始电量) report_all_status(); uint8_t last_sent_pct = s_battery_percent; bool low_battery_played = false; while (1) { if (board_battery_read(&pct, &chg)) { s_chg_ing = chg; // 充电中只增不减,放电中只减不增 if (chg) { if (pct > s_battery_percent) { s_battery_percent = pct; } } else { // 放电中:只允许容量值单调减少 if (pct < s_battery_percent) { s_battery_percent = pct; } } } if (s_battery_percent != last_sent_pct) { panel_uart_report(ITEM_BATTERY, s_battery_percent); #ifdef CONFIG_BT_A2DP_ENABLE bt_a2dp_send_battery_level(s_battery_percent); #endif last_sent_pct = s_battery_percent; ESP_LOGI(TAG, "Bat: %d%%", s_battery_percent); } // 低电量报警逻辑 if (s_battery_percent <= 10 && !low_battery_played && !chg) { ESP_LOGW(TAG, "触发低电量警告!"); audio_prompt_play(PROMPT_LOW_BATTERY); low_battery_played = true; } else if (s_battery_percent > 15 || chg) { low_battery_played = false; } vTaskDelay(pdMS_TO_TICKS(2000)); } } // ---------------------------------------------------------------------------- // 协议接收和处理逻辑 // ---------------------------------------------------------------------------- static void protocol_handler(uint8_t *data, int len) { for (int i = 0; i < len; i++) { if (data[i] != FRAME_HEAD_1) continue; if (i + 1 < len && data[i+1] != FRAME_HEAD_2) continue; if (i + 4 > len) break; // 至少包含 55 AA CMD LEN uint8_t cmd = data[i+2]; uint8_t pkt_len = data[i+3]; if (i + 4 + pkt_len + 1 > len) break; // 等待完整包 (含 CRC) uint8_t *payload = &data[i+4]; uint8_t expected_crc = cmd ^ pkt_len; for (int j = 0; j < pkt_len; j++) { expected_crc ^= payload[j]; } uint8_t actual_crc = data[i + 4 + pkt_len]; if (actual_crc != expected_crc) { ESP_LOGW(TAG, "CRC 校验错误: 预期 %02X, 实际 %02X", expected_crc, actual_crc); continue; } if (cmd == CMD_READ) { // Read expect LEN=2: [ITEM] [VALUE] if (pkt_len == 2) { uint8_t item = payload[0]; if (item == 0x00) { report_all_status(); } else { switch (item) { case ITEM_SOURCE: send_frame(CMD_READ, ITEM_SOURCE, (dac_njw1195_get_source() == NJW_SOURCE_BT) ? PROTO_VAL_ON : PROTO_VAL_OFF); break; case ITEM_BT_CONN: send_frame(CMD_READ, ITEM_BT_CONN, s_bt_connected ? PROTO_VAL_ON : PROTO_VAL_OFF); break; case ITEM_BT_AUDIO: send_frame(CMD_READ, ITEM_BT_AUDIO, s_bt_streaming ? PROTO_VAL_ON : PROTO_VAL_OFF); break; case ITEM_VOL_LR: send_frame(CMD_READ, ITEM_VOL_LR, dac_njw1195_get_vol_main()); break; case ITEM_VOL_BASS: send_frame(CMD_READ, ITEM_VOL_BASS, dac_njw1195_get_vol_bass()); break; case ITEM_DAC_MUTE: send_frame(CMD_READ, ITEM_DAC_MUTE, dac_njw1195_get_mute() ? PROTO_VAL_OFF : PROTO_VAL_ON); break; case ITEM_AUTO_RECONN: send_frame(CMD_READ, ITEM_AUTO_RECONN, PROTO_VAL_ON); break; case ITEM_BATTERY: send_frame(CMD_READ, ITEM_BATTERY, s_battery_percent); break; case ITEM_CHG_FULL: send_frame(CMD_READ, ITEM_CHG_FULL, s_chg_full ? 1 : 0); break; case ITEM_CHG_ING: send_frame(CMD_READ, ITEM_CHG_ING, s_chg_ing ? 1 : 0); break; default: send_frame(CMD_READ, item, 0xFF); break; } } } } else if (cmd == CMD_WRITE) { if (pkt_len == 2) { uint8_t item = payload[0]; uint8_t value = payload[1]; switch (item) { case ITEM_SOURCE: if (value == PROTO_VAL_ON) { dac_njw1195_set_source(NJW_SOURCE_BT); send_ack(); } else if (value == PROTO_VAL_OFF) { dac_njw1195_set_source(NJW_SOURCE_AUX); send_ack(); } else { send_frame(CMD_WRITE, ITEM_SOURCE, 0xFF); } break; case ITEM_BT_CONN: if (value == PROTO_VAL_OFF) { ESP_LOGI(TAG, "RX: Reset"); #ifdef CONFIG_BT_A2DP_ENABLE settings_clear_last_bt_bda(); bt_a2dp_sink_disconnect_current(); #endif send_ack(); } else { send_frame(CMD_WRITE, ITEM_BT_CONN, 0xFF); } break; case ITEM_DAC_MUTE: if (value == PROTO_VAL_ON || value == PROTO_VAL_OFF) { dac_njw1195_set_mute(value == PROTO_VAL_OFF); send_ack(); } else { send_frame(CMD_WRITE, ITEM_DAC_MUTE, 0xFF); } break; case ITEM_AUTO_RECONN: if (value == PROTO_VAL_ON || value == PROTO_VAL_OFF) { // 如果有开启/关闭自动回连的需求,在这里处理 send_ack(); } else { send_frame(CMD_WRITE, ITEM_AUTO_RECONN, 0xFF); } break; case ITEM_VOL_LR: ESP_LOGD(TAG, "面板音量设置为: %d", value); s_in_panel_update = true; dac_njw1195_set_vol_main(value); ESP_LOGD(TAG, "同步音量到播放器..."); playback_control_set_volume_percent(value); s_in_panel_update = false; ESP_LOGD(TAG, "音量已同步,发送 ACK 应答..."); send_ack(); break; case ITEM_VOL_BASS: dac_njw1195_set_vol_bass(value); send_ack(); break; case ITEM_RESET: send_ack(); vTaskDelay(pdMS_TO_TICKS(50)); settings_clear_wifi_credentials(); esp_wifi_restore(); // 清除 WiFi 信息 esp_restart(); break; case ITEM_BASS_MUTE: if (value == PROTO_VAL_ON) { dac_njw1195_set_vol_bass(s_bass_before_mute); send_ack(); } else if (value == PROTO_VAL_OFF) { s_bass_before_mute = dac_njw1195_get_vol_bass(); dac_njw1195_set_vol_bass(0); send_ack(); } else { send_frame(CMD_WRITE, ITEM_BASS_MUTE, 0xFF); } break; default: send_frame(CMD_WRITE, item, 0xFF); break; } } } i += 4 + pkt_len; // 跳过已处理的字节帧 } } // ---------------------------------------------------------------------------- // 串口接收读取任务 // ---------------------------------------------------------------------------- static void uart_task(void *arg) { uint8_t rx_buf[UART_BUF_SIZE]; while (1) { int len = uart_read_bytes(UART_PORT, rx_buf, sizeof(rx_buf), pdMS_TO_TICKS(50)); if (len > 0) { char hex_str[256] = {0}; int offset = 0; for (int i = 0; i < len && offset < sizeof(hex_str) - 4; i++) { offset += snprintf(hex_str + offset, sizeof(hex_str) - offset, "%02X ", rx_buf[i]); } ESP_LOGI(TAG, "RX_RAW: %s", hex_str); protocol_handler(rx_buf, len); } } } // RTSP 事件回调,统一处理蓝牙和 AirPlay 的连接与播放状态 static void on_rtsp_event(rtsp_event_t event, const rtsp_event_data_t *data, void *user_data) { (void)data; (void)user_data; switch (event) { case RTSP_EVENT_CLIENT_CONNECTED: panel_uart_set_bt_state(true, s_bt_streaming); break; case RTSP_EVENT_DISCONNECTED: panel_uart_set_bt_state(false, false); break; case RTSP_EVENT_PLAYING: panel_uart_set_bt_state(s_bt_connected, true); break; case RTSP_EVENT_PAUSED: panel_uart_set_bt_state(s_bt_connected, false); break; default: break; } } // ---------------------------------------------------------------------------- // 公共对外 API 接口 // ---------------------------------------------------------------------------- esp_err_t panel_uart_init(void) { // 1. 初始化串口端口 2 uart_config_t uart_config = { .baud_rate = UART_BAUD_RATE, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT, }; ESP_ERROR_CHECK(uart_param_config(UART_PORT, &uart_config)); ESP_ERROR_CHECK(uart_set_pin(UART_PORT, UART_TX_GPIO, UART_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); ESP_ERROR_CHECK(uart_driver_install(UART_PORT, UART_BUF_SIZE * 2, UART_BUF_SIZE * 2, 0, NULL, 0)); // 2. 初始化充电状态检测中断 GPIO gpio_config_t io_conf = { .pin_bit_mask = (1ULL << CHG_FULL_GPIO) | (1ULL << CHG_ING_GPIO), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_ANYEDGE, }; ESP_ERROR_CHECK(gpio_config(&io_conf)); // 全局安装中断服务,如果已安装则安全跳过 (兼容 IDF) gpio_install_isr_service(0); ESP_ERROR_CHECK(gpio_isr_handler_add(CHG_FULL_GPIO, charge_isr_handler, NULL)); ESP_ERROR_CHECK(gpio_isr_handler_add(CHG_ING_GPIO, charge_isr_handler, NULL)); // 注册 RTSP 事件监听器 (自动上报蓝牙和 AirPlay 的连接及播放状态) rtsp_events_register(on_rtsp_event, NULL); // 3. 创建执行任务 xTaskCreate(uart_task, "uart_task", 4096, NULL, 5, NULL); xTaskCreate(charge_status_task, "chg_stat", 4096, NULL, 3, &s_charge_task_handle); xTaskCreate(report_status_task, "rpt_stat", 4096, NULL, 2, NULL); ESP_LOGI(TAG, "UART init OK, TX=%d RX=%d", UART_TX_GPIO, UART_RX_GPIO); return ESP_OK; } void panel_uart_set_bt_state(bool connected, bool streaming) { if (s_bt_connected != connected) { s_bt_connected = connected; panel_uart_report(ITEM_BT_CONN, s_bt_connected ? PROTO_VAL_ON : PROTO_VAL_OFF); } if (s_bt_streaming != streaming) { s_bt_streaming = streaming; panel_uart_report(ITEM_BT_AUDIO, s_bt_streaming ? PROTO_VAL_ON : PROTO_VAL_OFF); } } uint8_t panel_uart_get_battery_percent(void) { return s_battery_percent; } bool panel_uart_get_charging_status(void) { return s_chg_ing; }