ESPHome  2024.4.1
camera_web_server.cpp
Go to the documentation of this file.
1 #ifdef USE_ESP32
2 
3 #include "camera_web_server.h"
5 #include "esphome/core/hal.h"
6 #include "esphome/core/helpers.h"
7 #include "esphome/core/log.h"
8 #include "esphome/core/util.h"
9 
10 #include <cstdlib>
11 #include <esp_http_server.h>
12 #include <utility>
13 
14 namespace esphome {
15 namespace esp32_camera_web_server {
16 
17 static const int IMAGE_REQUEST_TIMEOUT = 5000;
18 static const char *const TAG = "esp32_camera_web_server";
19 
20 #define PART_BOUNDARY "123456789000000000000987654321"
21 #define CONTENT_TYPE "image/jpeg"
22 #define CONTENT_LENGTH "Content-Length"
23 
24 static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
25  "Access-Control-Allow-Origin: *\r\n"
26  "Connection: close\r\n"
27  "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
28  "\r\n"
29  "--" PART_BOUNDARY "\r\n";
30 static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
31  "\r\n"
32  "No frames send.\r\n"
33  "--" PART_BOUNDARY "\r\n";
34 static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
35 static const char *const STREAM_BOUNDARY = "\r\n"
36  "--" PART_BOUNDARY "\r\n";
37 
39 
41 
44  this->mark_failed();
45  return;
46  }
47 
48  this->semaphore_ = xSemaphoreCreateBinary();
49 
50  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
51  config.server_port = this->port_;
52  config.ctrl_port = this->port_;
53  config.max_open_sockets = 1;
54  config.backlog_conn = 2;
55  config.lru_purge_enable = true;
56 
57  if (httpd_start(&this->httpd_, &config) != ESP_OK) {
58  mark_failed();
59  return;
60  }
61 
62  httpd_uri_t uri = {
63  .uri = "/",
64  .method = HTTP_GET,
65  .handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); },
66  .user_ctx = this};
67 
68  httpd_register_uri_handler(this->httpd_, &uri);
69 
70  esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
71  if (this->running_ && image->was_requested_by(esp32_camera::WEB_REQUESTER)) {
72  this->image_ = std::move(image);
73  xSemaphoreGive(this->semaphore_);
74  }
75  });
76 }
77 
79  this->running_ = false;
80  this->image_ = nullptr;
81  httpd_stop(this->httpd_);
82  this->httpd_ = nullptr;
83  vSemaphoreDelete(this->semaphore_);
84  this->semaphore_ = nullptr;
85 }
86 
88  ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:");
89  ESP_LOGCONFIG(TAG, " Port: %d", this->port_);
90  if (this->mode_ == STREAM) {
91  ESP_LOGCONFIG(TAG, " Mode: stream");
92  } else {
93  ESP_LOGCONFIG(TAG, " Mode: snapshot");
94  }
95 
96  if (this->is_failed()) {
97  ESP_LOGE(TAG, " Setup Failed");
98  }
99 }
100 
102 
104  if (!this->running_) {
105  this->image_ = nullptr;
106  }
107 }
108 
109 std::shared_ptr<esphome::esp32_camera::CameraImage> CameraWebServer::wait_for_image_() {
110  std::shared_ptr<esphome::esp32_camera::CameraImage> image;
111  image.swap(this->image_);
112 
113  if (!image) {
114  // retry as we might still be fetching image
115  xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS);
116  image.swap(this->image_);
117  }
118 
119  return image;
120 }
121 
122 esp_err_t CameraWebServer::handler_(struct httpd_req *req) {
123  esp_err_t res = ESP_FAIL;
124 
125  this->image_ = nullptr;
126  this->running_ = true;
127 
128  switch (this->mode_) {
129  case STREAM:
130  res = this->streaming_handler_(req);
131  break;
132 
133  case SNAPSHOT:
134  res = this->snapshot_handler_(req);
135  break;
136  }
137 
138  this->running_ = false;
139  this->image_ = nullptr;
140  return res;
141 }
142 
143 static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) {
144  int ret;
145 
146  while (buf_len > 0) {
147  ret = httpd_send(r, buf, buf_len);
148  if (ret < 0) {
149  return ESP_FAIL;
150  }
151  buf += ret;
152  buf_len -= ret;
153  }
154  return ESP_OK;
155 }
156 
157 esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
158  esp_err_t res = ESP_OK;
159  char part_buf[64];
160 
161  // This manually constructs HTTP response to avoid chunked encoding
162  // which is not supported by some clients
163 
164  res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER));
165  if (res != ESP_OK) {
166  ESP_LOGW(TAG, "STREAM: failed to set HTTP header");
167  return res;
168  }
169 
170  uint32_t last_frame = millis();
171  uint32_t frames = 0;
172 
174 
175  while (res == ESP_OK && this->running_) {
176  auto image = this->wait_for_image_();
177 
178  if (!image) {
179  ESP_LOGW(TAG, "STREAM: failed to acquire frame");
180  res = ESP_FAIL;
181  }
182  if (res == ESP_OK) {
183  size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
184  res = httpd_send_all(req, part_buf, hlen);
185  }
186  if (res == ESP_OK) {
187  res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
188  }
189  if (res == ESP_OK) {
190  res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
191  }
192  if (res == ESP_OK) {
193  frames++;
194  int64_t frame_time = millis() - last_frame;
195  last_frame = millis();
196 
197  ESP_LOGD(TAG, "MJPG: %" PRIu32 "B %" PRIu32 "ms (%.1ffps)", (uint32_t) image->get_data_length(),
198  (uint32_t) frame_time, 1000.0 / (uint32_t) frame_time);
199  }
200  }
201 
202  if (!frames) {
203  res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
204  }
205 
207 
208  ESP_LOGI(TAG, "STREAM: closed. Frames: %" PRIu32, frames);
209 
210  return res;
211 }
212 
213 esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
214  esp_err_t res = ESP_OK;
215 
217 
218  auto image = this->wait_for_image_();
219 
220  if (!image) {
221  ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame");
222  httpd_resp_send_500(req);
223  res = ESP_FAIL;
224  return res;
225  }
226 
227  res = httpd_resp_set_type(req, CONTENT_TYPE);
228  if (res != ESP_OK) {
229  ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type");
230  return res;
231  }
232 
233  httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
234 
235  if (res == ESP_OK) {
236  res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
237  }
238  return res;
239 }
240 
241 } // namespace esp32_camera_web_server
242 } // namespace esphome
243 
244 #endif // USE_ESP32
void request_image(CameraRequester requester)
void start_stream(CameraRequester requester)
esp_err_t streaming_handler_(struct httpd_req *req)
const float LATE
For components that should be initialized at the very end of the setup process.
Definition: component.cpp:28
void add_image_callback(std::function< void(std::shared_ptr< CameraImage >)> &&callback)
std::shared_ptr< esphome::esp32_camera::CameraImage > image_
void stop_stream(CameraRequester requester)
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
ESP32Camera * global_esp32_camera
std::shared_ptr< esphome::esp32_camera::CameraImage > wait_for_image_()
esp_err_t snapshot_handler_(struct httpd_req *req)
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7