#include "ui/apps/iotdis/bridge/bridge.h" #include "esp_err.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #define TAG "IotDisBridge" #define MUTE_COMMAND "MUTE" #define STATUS_COMMAND "STATUS" #define MUTED_RESPONSE "MUTED" #define UNMUTED_RESPONSE "UNMUTED" IotDisBridge::~IotDisBridge() { stop_polling_task(); } void IotDisBridge::start_polling_task() { if (poll_task_handle_) { ESP_LOGW(TAG, "Polling task already running"); return; } udp_client_.init(setting_handler_->get_local_port()); udp_client_.configure( setting_handler_->get_remote_ip(), setting_handler_->get_remote_port() ); stop_polling_ = false; xTaskCreate(poll_task_, "discord_poll", 4096, this, 5, &poll_task_handle_); } void IotDisBridge::stop_polling_task() { if (!poll_task_handle_) { if (udp_client_.is_configured()) { udp_client_.close(); } return; } ESP_LOGI(TAG, "Stopping polling task"); stop_polling_ = true; // Wait for task to finish (max 2 seconds) int wait_count = 0; while (poll_task_handle_ && wait_count < 20) { vTaskDelay(pdMS_TO_TICKS(100)); wait_count++; } if (poll_task_handle_) { ESP_LOGW(TAG, "Force deleting polling task"); vTaskDelete(poll_task_handle_); poll_task_handle_ = nullptr; } if (udp_client_.is_configured()) { udp_client_.close(); } on_status_update_callback_ = nullptr; status_event_user_data_ = nullptr; consecutive_failures_ = 0; } esp_err_t IotDisBridge::send_mute_command() { if (!setting_handler_->is_configured()) { ESP_LOGW(TAG, "Cannot send command: not configured"); return ESP_FAIL; } ESP_LOGI(TAG, "Sending MUTE command"); esp_err_t err = udp_client_.send_command(MUTE_COMMAND); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send MUTE command"); return err; } return ESP_OK; } bool IotDisBridge::test_connection(const std::string& ip, uint16_t port, uint16_t local_port) { ESP_LOGI(TAG, "Testing connection to %s:%u (local port: %u)", ip.c_str(), port, local_port); // Create temporary UDP client for testing UDPClient test_client; esp_err_t err = test_client.init(local_port); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize test UDP client"); return false; } err = test_client.configure(ip, port); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to configure test UDP client"); return false; } err = test_client.send_command(STATUS_COMMAND); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send STATUS command"); return false; } ESP_LOGI(TAG, "STATUS command sent, waiting for response (timeout: %dms)", RESPONSE_TIMEOUT_MS); std::string response; err = test_client.receive_response(response, RESPONSE_TIMEOUT_MS); if (err == ESP_OK) { ESP_LOGI(TAG, "Received response: %s", response.c_str()); bool valid = (response == MUTED_RESPONSE || response == UNMUTED_RESPONSE); if (!valid) { ESP_LOGW(TAG, "Unexpected response (expected MUTED or UNMUTED)"); } test_client.close(); return valid; } else if (err == ESP_ERR_TIMEOUT) { ESP_LOGW(TAG, "Timeout waiting for response"); } else { ESP_LOGE(TAG, "Error receiving response: %d", err); } test_client.close(); return false; } // // private methods // void IotDisBridge::poll_task_(void* param) { IotDisBridge* bridge = static_cast(param); ESP_LOGI(TAG, "Polling task started"); while (!bridge->stop_polling_) { ESP_LOGI(TAG, "Polling for status update..."); bridge->poll_status_(); // Yield to allow display updates to complete taskYIELD(); // Use longer interval if in error state int interval = (bridge->consecutive_failures_ >= MAX_FAILURES_BEFORE_ERROR) ? ERROR_POLL_INTERVAL_MS : POLL_INTERVAL_MS; ESP_LOGI(TAG, "Next poll in %d ms", interval); vTaskDelay(pdMS_TO_TICKS(interval)); } ESP_LOGI(TAG, "Polling task stopped"); vTaskDelete(nullptr); } void IotDisBridge::poll_status_() { if (!setting_handler_->is_configured()) { // Don't poll if not configured return; } // First check for any unsolicited push messages (non-blocking) std::string push_message; esp_err_t err = udp_client_.receive_response(push_message, 0); // 0 = non-blocking if (err == ESP_OK && !push_message.empty()) { // Received push update from remote ESP_LOGI(TAG, "Received push update: %s", push_message.c_str()); StatusUpdateEventData event_data { .state = StatusUpdateEventData::VoiceState::UNKNOWN }; if (push_message == MUTED_RESPONSE) { event_data.state = StatusUpdateEventData::VoiceState::MUTED; } else if (push_message == UNMUTED_RESPONSE) { event_data.state = StatusUpdateEventData::VoiceState::UNMUTED; } if (on_status_update_callback_) { on_status_update_callback_(event_data, status_event_user_data_); } // VoiceState new_state = VoiceState::UNKNOWN; // if (push_message == MUTED_RESPONSE) { // new_state = VoiceState::MUTED; // } else if (push_message == UNMUTED_RESPONSE) { // new_state = VoiceState::UNMUTED; // } // if (new_state != VoiceState::UNKNOWN) { // consecutive_failures_ = 0; // if (main_ui_ && current_page_ == Page::MAIN) { // main_ui_->show_error_notification(false); // } // if (state_mutex_ && xSemaphoreTake(state_mutex_, pdMS_TO_TICKS(100)) == pdTRUE) { // current_state_ = new_state; // xSemaphoreGive(state_mutex_); // } // update_main_ui(); // return; // Got push update, skip polling // } } // Send STATUS command for polling err = udp_client_.send_command(STATUS_COMMAND); if (err != ESP_OK) { ESP_LOGW(TAG, "Failed to send STATUS command"); consecutive_failures_++; if (consecutive_failures_ >= MAX_FAILURES_BEFORE_ERROR) { if (on_status_update_callback_) { on_status_update_callback_( StatusUpdateEventData { .state = StatusUpdateEventData::VoiceState::ERROR }, status_event_user_data_ ); } } return; } // Wait for response to STATUS command std::string response; err = udp_client_.receive_response(response, RESPONSE_TIMEOUT_MS); if (err == ESP_OK) { // Success - reset failure counter consecutive_failures_ = 0; StatusUpdateEventData event_data { .state = StatusUpdateEventData::VoiceState::UNKNOWN }; if (response == MUTED_RESPONSE) { event_data.state = StatusUpdateEventData::VoiceState::MUTED; } else if (response == UNMUTED_RESPONSE) { event_data.state = StatusUpdateEventData::VoiceState::UNMUTED; } if (on_status_update_callback_) { on_status_update_callback_(event_data, status_event_user_data_); } } else { // Timeout or error consecutive_failures_++; ESP_LOGW(TAG, "No response to STATUS (failures: %d)", consecutive_failures_); if (consecutive_failures_ >= MAX_FAILURES_BEFORE_ERROR) { if (on_status_update_callback_) { on_status_update_callback_( StatusUpdateEventData { .state = StatusUpdateEventData::VoiceState::ERROR }, status_event_user_data_ ); } } } }