#include "background_capture_service.h" #include "board.h" #include "camera.h" #include #include #include #define TAG "BgCapture" BackgroundCaptureService::BackgroundCaptureService() = default; BackgroundCaptureService::~BackgroundCaptureService() { Stop(); } void BackgroundCaptureService::Start() { #if CONFIG_BACKGROUND_CAPTURE_ENABLE if (running_.exchange(true)) { return; } auto result = xTaskCreate( &BackgroundCaptureService::TaskEntry, "bg_capture", CONFIG_BACKGROUND_CAPTURE_TASK_STACK_SIZE, this, CONFIG_BACKGROUND_CAPTURE_TASK_PRIORITY, &task_handle_); if (result != pdPASS) { running_.store(false); task_handle_ = nullptr; ESP_LOGE(TAG, "Failed to create background capture task"); } #endif } void BackgroundCaptureService::Stop() { #if CONFIG_BACKGROUND_CAPTURE_ENABLE if (!running_.exchange(false)) { return; } while (task_handle_ != nullptr) { vTaskDelay(pdMS_TO_TICKS(20)); } #endif } void BackgroundCaptureService::TaskEntry(void* arg) { #if CONFIG_BACKGROUND_CAPTURE_ENABLE auto* service = static_cast(arg); service->Run(); service->task_handle_ = nullptr; #else (void)arg; #endif vTaskDelete(nullptr); } void BackgroundCaptureService::Run() { #if CONFIG_BACKGROUND_CAPTURE_ENABLE ESP_LOGI(TAG, "Background capture task started"); while (running_.load()) { if (!CaptureAndSendFrame()) { consecutive_failures_++; auto delay_ms = GetFailureDelayMs(); ESP_LOGW(TAG, "Background capture retry in %u ms, failures=%u", delay_ms, consecutive_failures_); vTaskDelay(pdMS_TO_TICKS(delay_ms)); continue; } consecutive_failures_ = 0; vTaskDelay(pdMS_TO_TICKS(CONFIG_BACKGROUND_CAPTURE_FRAME_INTERVAL_MS)); } ESP_LOGI(TAG, "Background capture task stopped"); #endif } bool BackgroundCaptureService::CaptureAndSendFrame() { #if CONFIG_BACKGROUND_CAPTURE_ENABLE const size_t free_internal_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); if (free_internal_heap < CONFIG_BACKGROUND_CAPTURE_MIN_FREE_INTERNAL_HEAP) { ESP_LOGW(TAG, "Skip background capture, low internal heap: free=%u threshold=%u", static_cast(free_internal_heap), static_cast(CONFIG_BACKGROUND_CAPTURE_MIN_FREE_INTERNAL_HEAP)); return false; } auto camera = Board::GetInstance().GetCamera(); if (camera == nullptr) { ESP_LOGW(TAG, "No camera available for background capture"); return false; } std::string jpeg_data; if (!camera->CaptureToJpeg(jpeg_data, false)) { ESP_LOGW(TAG, "Failed to capture background frame"); return false; } if (jpeg_data.empty()) { ESP_LOGW(TAG, "Captured empty background frame"); return false; } return UploadJpegFrame(jpeg_data); #else return false; #endif } uint32_t BackgroundCaptureService::GetFailureDelayMs() const { #if CONFIG_BACKGROUND_CAPTURE_ENABLE const uint32_t base_delay_ms = CONFIG_BACKGROUND_CAPTURE_RETRY_INTERVAL_MS; const uint32_t max_delay_ms = CONFIG_BACKGROUND_CAPTURE_MAX_BACKOFF_MS; const uint32_t shift = std::min(consecutive_failures_ - 1, 4); return std::min(base_delay_ms << shift, max_delay_ms); #else return 0; #endif } bool BackgroundCaptureService::UploadJpegFrame(const std::string& jpeg_data) { #if CONFIG_BACKGROUND_CAPTURE_ENABLE const std::string url = CONFIG_BACKGROUND_CAPTURE_UPLOAD_URL; if (url.empty()) { ESP_LOGI(TAG, "Captured background frame: %u bytes", static_cast(jpeg_data.size())); return true; } auto network = Board::GetInstance().GetNetwork(); if (network == nullptr) { ESP_LOGW(TAG, "No network available for background upload"); return false; } const std::string boundary = "----XIAOZHI_BACKGROUND_CAPTURE_BOUNDARY"; auto http = network->CreateHttp(3); http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary); if (!http->Open("POST", url)) { ESP_LOGW(TAG, "Failed to open background upload URL: %s", url.c_str()); return false; } std::string file_header; file_header += "--" + boundary + "\r\n"; file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"frame.jpg\"\r\n"; file_header += "Content-Type: image/jpeg\r\n\r\n"; http->Write(file_header.c_str(), file_header.size()); http->Write(jpeg_data.data(), jpeg_data.size()); std::string footer; footer += "\r\n--" + boundary + "--\r\n"; http->Write(footer.c_str(), footer.size()); http->Write("", 0); const int status_code = http->GetStatusCode(); http->Close(); if (status_code < 200 || status_code >= 300) { ESP_LOGW(TAG, "Background upload failed, status=%d", status_code); return false; } ESP_LOGI(TAG, "Uploaded background frame: %u bytes", static_cast(jpeg_data.size())); return true; #else (void)jpeg_data; return false; #endif }