ESPHome 2025.5.0
Loading...
Searching...
No Matches
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"
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
14namespace esphome {
15namespace esp32_camera_web_server {
16
17static const int IMAGE_REQUEST_TIMEOUT = 5000;
18static 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
24static 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";
30static 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";
34static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
35static 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) {
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
109std::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
122esp_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
143static 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
157esp_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
213esp_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
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void add_image_callback(std::function< void(std::shared_ptr< CameraImage >)> &&callback)
void request_image(CameraRequester requester)
void stop_stream(CameraRequester requester)
void start_stream(CameraRequester requester)
std::shared_ptr< esphome::esp32_camera::CameraImage > image_
std::shared_ptr< esphome::esp32_camera::CameraImage > wait_for_image_()
ESP32Camera * global_esp32_camera
const float LATE
For components that should be initialized at the very end of the setup process.
Definition component.cpp:28
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27