178 lines
5.2 KiB
C++
178 lines
5.2 KiB
C++
#include "background_capture_service.h"
|
|
|
|
#include "board.h"
|
|
#include "camera.h"
|
|
|
|
#include <algorithm>
|
|
#include <esp_heap_caps.h>
|
|
#include <esp_log.h>
|
|
|
|
#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<BackgroundCaptureService*>(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<unsigned>(free_internal_heap),
|
|
static_cast<unsigned>(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<uint32_t>(consecutive_failures_ - 1, 4);
|
|
return std::min<uint32_t>(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<unsigned>(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<unsigned>(jpeg_data.size()));
|
|
return true;
|
|
#else
|
|
(void)jpeg_data;
|
|
return false;
|
|
#endif
|
|
}
|