ESPHome 2026.1.0-dev
Loading...
Searching...
No Matches
esp32_camera.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
3#include "esp32_camera.h"
5#include "esphome/core/hal.h"
6#include "esphome/core/log.h"
7
8#include <freertos/task.h>
9
10namespace esphome {
11namespace esp32_camera {
12
13static const char *const TAG = "esp32_camera";
14static constexpr size_t FRAMEBUFFER_TASK_STACK_SIZE = 1792;
15#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE
16static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000;
17#endif
18
19/* ---------------- public API (derivated) ---------------- */
21#ifdef USE_I2C
22 if (this->i2c_bus_ != nullptr) {
23 this->config_.sccb_i2c_port = this->i2c_bus_->get_port();
24 }
25#endif
26
27 /* initialize time to now */
28 this->last_update_ = millis();
29
30 /* initialize camera */
31 esp_err_t err = esp_camera_init(&this->config_);
32 if (err != ESP_OK) {
33 ESP_LOGE(TAG, "esp_camera_init failed: %s", esp_err_to_name(err));
34 this->init_error_ = err;
35 this->mark_failed();
36 return;
37 }
38
39 /* initialize camera parameters */
41
42 /* initialize RTOS */
43 this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
44 this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
45 xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
46 "framebuffer_task", // name
47 FRAMEBUFFER_TASK_STACK_SIZE, // stack size
48 this, // task pv params
49 1, // priority
50 nullptr, // handle
51 1 // core
52 );
53}
54
56 auto conf = this->config_;
57 ESP_LOGCONFIG(TAG,
58 "ESP32 Camera:\n"
59 " Name: %s\n"
60 " Internal: %s\n"
61 " Data Pins: D0:%d D1:%d D2:%d D3:%d D4:%d D5:%d D6:%d D7:%d\n"
62 " VSYNC Pin: %d\n"
63 " HREF Pin: %d\n"
64 " Pixel Clock Pin: %d\n"
65 " External Clock: Pin:%d Frequency:%u\n"
66 " I2C Pins: SDA:%d SCL:%d\n"
67 " Reset Pin: %d",
68 this->name_.c_str(), YESNO(this->is_internal()), conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3,
69 conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7, conf.pin_vsync, conf.pin_href, conf.pin_pclk,
70 conf.pin_xclk, conf.xclk_freq_hz, conf.pin_sccb_sda, conf.pin_sccb_scl, conf.pin_reset);
71 switch (this->config_.frame_size) {
72 case FRAMESIZE_QQVGA:
73 ESP_LOGCONFIG(TAG, " Resolution: 160x120 (QQVGA)");
74 break;
75 case FRAMESIZE_QCIF:
76 ESP_LOGCONFIG(TAG, " Resolution: 176x155 (QCIF)");
77 break;
78 case FRAMESIZE_HQVGA:
79 ESP_LOGCONFIG(TAG, " Resolution: 240x176 (HQVGA)");
80 break;
81 case FRAMESIZE_QVGA:
82 ESP_LOGCONFIG(TAG, " Resolution: 320x240 (QVGA)");
83 break;
84 case FRAMESIZE_CIF:
85 ESP_LOGCONFIG(TAG, " Resolution: 400x296 (CIF)");
86 break;
87 case FRAMESIZE_VGA:
88 ESP_LOGCONFIG(TAG, " Resolution: 640x480 (VGA)");
89 break;
90 case FRAMESIZE_SVGA:
91 ESP_LOGCONFIG(TAG, " Resolution: 800x600 (SVGA)");
92 break;
93 case FRAMESIZE_XGA:
94 ESP_LOGCONFIG(TAG, " Resolution: 1024x768 (XGA)");
95 break;
96 case FRAMESIZE_SXGA:
97 ESP_LOGCONFIG(TAG, " Resolution: 1280x1024 (SXGA)");
98 break;
99 case FRAMESIZE_UXGA:
100 ESP_LOGCONFIG(TAG, " Resolution: 1600x1200 (UXGA)");
101 break;
102 case FRAMESIZE_FHD:
103 ESP_LOGCONFIG(TAG, " Resolution: 1920x1080 (FHD)");
104 break;
105 case FRAMESIZE_P_HD:
106 ESP_LOGCONFIG(TAG, " Resolution: 720x1280 (P_HD)");
107 break;
108 case FRAMESIZE_P_3MP:
109 ESP_LOGCONFIG(TAG, " Resolution: 864x1536 (P_3MP)");
110 break;
111 case FRAMESIZE_QXGA:
112 ESP_LOGCONFIG(TAG, " Resolution: 2048x1536 (QXGA)");
113 break;
114 case FRAMESIZE_QHD:
115 ESP_LOGCONFIG(TAG, " Resolution: 2560x1440 (QHD)");
116 break;
117 case FRAMESIZE_WQXGA:
118 ESP_LOGCONFIG(TAG, " Resolution: 2560x1600 (WQXGA)");
119 break;
120 case FRAMESIZE_P_FHD:
121 ESP_LOGCONFIG(TAG, " Resolution: 1080x1920 (P_FHD)");
122 break;
123 case FRAMESIZE_QSXGA:
124 ESP_LOGCONFIG(TAG, " Resolution: 2560x1920 (QSXGA)");
125 break;
126 default:
127 break;
128 }
129
130 if (this->is_failed()) {
131 ESP_LOGE(TAG, " Setup Failed: %s", esp_err_to_name(this->init_error_));
132 return;
133 }
134
135 sensor_t *s = esp_camera_sensor_get();
136 auto st = s->status;
137 ESP_LOGCONFIG(TAG,
138 " JPEG Quality: %u\n"
139 " Framebuffer Count: %u\n"
140 " Framebuffer Location: %s\n"
141 " Contrast: %d\n"
142 " Brightness: %d\n"
143 " Saturation: %d\n"
144 " Vertical Flip: %s\n"
145 " Horizontal Mirror: %s\n"
146 " Special Effect: %u\n"
147 " White Balance Mode: %u",
148 st.quality, conf.fb_count, this->config_.fb_location == CAMERA_FB_IN_PSRAM ? "PSRAM" : "DRAM",
149 st.contrast, st.brightness, st.saturation, ONOFF(st.vflip), ONOFF(st.hmirror), st.special_effect,
150 st.wb_mode);
151 // ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb);
152 // ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain);
153 ESP_LOGCONFIG(TAG,
154 " Auto Exposure Control: %u\n"
155 " Auto Exposure Control 2: %u\n"
156 " Auto Exposure Level: %d\n"
157 " Auto Exposure Value: %u\n"
158 " AGC: %u\n"
159 " AGC Gain: %u\n"
160 " Gain Ceiling: %u",
161 st.aec, st.aec2, st.ae_level, st.aec_value, st.agc, st.agc_gain, st.gainceiling);
162 // ESP_LOGCONFIG(TAG, " BPC: %u", st.bpc);
163 // ESP_LOGCONFIG(TAG, " WPC: %u", st.wpc);
164 // ESP_LOGCONFIG(TAG, " RAW_GMA: %u", st.raw_gma);
165 // ESP_LOGCONFIG(TAG, " Lens Correction: %u", st.lenc);
166 // ESP_LOGCONFIG(TAG, " DCW: %u", st.dcw);
167 ESP_LOGCONFIG(TAG, " Test Pattern: %s", YESNO(st.colorbar));
168}
169
171 // Fast path: skip all work when truly idle
172 // (no current image, no pending requests, and not time for idle request yet)
173 const uint32_t now = App.get_loop_component_start_time();
174 if (!this->current_image_ && !this->has_requested_image_()) {
175 // Only check idle interval when we're otherwise idle
176 if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
177 this->last_idle_request_ = now;
179 } else {
180 return;
181 }
182 }
183
184 // check if we can return the image
185 if (this->can_return_image_()) {
186 // return image
187 auto *fb = this->current_image_->get_raw_buffer();
188 xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
189 this->current_image_.reset();
190 }
191
192 // Check if we should fetch a new image
193 if (!this->has_requested_image_())
194 return;
195 if (this->current_image_.use_count() > 1) {
196 // image is still in use
197 return;
198 }
200 return;
201
202 // request new image
203 camera_fb_t *fb;
204 if (xQueueReceive(this->framebuffer_get_queue_, &fb, 0L) != pdTRUE) {
205 // no frame ready
206 ESP_LOGVV(TAG, "No frame ready");
207 return;
208 }
209
210 if (fb == nullptr) {
211 ESP_LOGW(TAG, "Got invalid frame from camera!");
212 xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
213 return;
214 }
215 this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
216
217#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
218 ESP_LOGV(TAG, "Got Image: len=%u", fb->len);
219#else
220 // Initialize log time on first frame to ensure accurate interval measurement
221 if (this->frame_count_ == 0) {
222 this->last_log_time_ = now;
223 }
224 this->frame_count_++;
225 if (now - this->last_log_time_ >= FRAME_LOG_INTERVAL_MS) {
226 ESP_LOGD(TAG, "Received %u images in last %us", this->frame_count_, FRAME_LOG_INTERVAL_MS / 1000);
227 this->last_log_time_ = now;
228 this->frame_count_ = 0;
229 }
230#endif
231 for (auto *listener : this->listeners_) {
232 listener->on_camera_image(this->current_image_);
233 }
234 this->last_update_ = now;
235 this->single_requesters_ = 0;
236}
237
239
240/* ---------------- constructors ---------------- */
242 this->config_.pin_pwdn = -1;
243 this->config_.pin_reset = -1;
244 this->config_.pin_xclk = -1;
245 this->config_.ledc_timer = LEDC_TIMER_0;
246 this->config_.ledc_channel = LEDC_CHANNEL_0;
247 this->config_.pixel_format = PIXFORMAT_JPEG;
248 this->config_.frame_size = FRAMESIZE_VGA; // 640x480
249 this->config_.jpeg_quality = 10;
250 this->config_.fb_count = 1;
251 this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
252 this->config_.fb_location = CAMERA_FB_IN_PSRAM;
253}
254
255/* ---------------- setters ---------------- */
256/* set pin assignment */
257void ESP32Camera::set_data_pins(std::array<uint8_t, 8> pins) {
258 this->config_.pin_d0 = pins[0];
259 this->config_.pin_d1 = pins[1];
260 this->config_.pin_d2 = pins[2];
261 this->config_.pin_d3 = pins[3];
262 this->config_.pin_d4 = pins[4];
263 this->config_.pin_d5 = pins[5];
264 this->config_.pin_d6 = pins[6];
265 this->config_.pin_d7 = pins[7];
266}
267void ESP32Camera::set_vsync_pin(uint8_t pin) { this->config_.pin_vsync = pin; }
268void ESP32Camera::set_href_pin(uint8_t pin) { this->config_.pin_href = pin; }
269void ESP32Camera::set_pixel_clock_pin(uint8_t pin) { this->config_.pin_pclk = pin; }
270void ESP32Camera::set_external_clock(uint8_t pin, uint32_t frequency) {
271 this->config_.pin_xclk = pin;
272 this->config_.xclk_freq_hz = frequency;
273}
274void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) {
275 this->config_.pin_sccb_sda = sda;
276 this->config_.pin_sccb_scl = scl;
277}
278#ifdef USE_I2C
280 this->i2c_bus_ = i2c_bus;
281 this->config_.pin_sccb_sda = -1;
282 this->config_.pin_sccb_scl = -1;
283}
284#endif // USE_I2C
285void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; }
286void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; }
287
288/* set image parameters */
290 switch (size) {
292 this->config_.frame_size = FRAMESIZE_QQVGA;
293 break;
295 this->config_.frame_size = FRAMESIZE_QCIF;
296 break;
298 this->config_.frame_size = FRAMESIZE_HQVGA;
299 break;
301 this->config_.frame_size = FRAMESIZE_QVGA;
302 break;
304 this->config_.frame_size = FRAMESIZE_CIF;
305 break;
307 this->config_.frame_size = FRAMESIZE_VGA;
308 break;
310 this->config_.frame_size = FRAMESIZE_SVGA;
311 break;
313 this->config_.frame_size = FRAMESIZE_XGA;
314 break;
316 this->config_.frame_size = FRAMESIZE_SXGA;
317 break;
319 this->config_.frame_size = FRAMESIZE_UXGA;
320 break;
322 this->config_.frame_size = FRAMESIZE_FHD;
323 break;
325 this->config_.frame_size = FRAMESIZE_P_HD;
326 break;
328 this->config_.frame_size = FRAMESIZE_P_3MP;
329 break;
331 this->config_.frame_size = FRAMESIZE_QXGA;
332 break;
334 this->config_.frame_size = FRAMESIZE_QHD;
335 break;
337 this->config_.frame_size = FRAMESIZE_WQXGA;
338 break;
340 this->config_.frame_size = FRAMESIZE_P_FHD;
341 break;
343 this->config_.frame_size = FRAMESIZE_QSXGA;
344 break;
345 }
346}
347void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; }
348void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; }
349void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; }
350void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; }
351void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; }
352void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; }
354/* set exposure parameters */
356void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; }
357void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; }
358void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; }
359/* set gains parameters */
361void ESP32Camera::set_agc_value(uint8_t agc_value) { this->agc_value_ = agc_value; }
363/* set white balance */
365/* set test mode */
366void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; }
367/* set fps */
368void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) {
369 this->max_update_interval_ = max_update_interval;
370}
371void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) {
372 this->idle_update_interval_ = idle_update_interval;
373}
374/* set frame buffer parameters */
375void ESP32Camera::set_frame_buffer_mode(camera_grab_mode_t mode) { this->config_.grab_mode = mode; }
377 this->config_.fb_count = fb_count;
378 this->set_frame_buffer_mode(fb_count > 1 ? CAMERA_GRAB_LATEST : CAMERA_GRAB_WHEN_EMPTY);
379}
380void ESP32Camera::set_frame_buffer_location(camera_fb_location_t fb_location) {
381 this->config_.fb_location = fb_location;
382}
383
384/* ---------------- public API (specific) ---------------- */
386 for (auto *listener : this->listeners_) {
387 listener->on_stream_start();
388 }
389 this->stream_requesters_ |= (1U << requester);
390}
392 for (auto *listener : this->listeners_) {
393 listener->on_stream_stop();
394 }
395 this->stream_requesters_ &= ~(1U << requester);
396}
397void ESP32Camera::request_image(camera::CameraRequester requester) { this->single_requesters_ |= (1U << requester); }
400 sensor_t *s = esp_camera_sensor_get();
401 /* update image */
402 s->set_vflip(s, this->vertical_flip_);
403 s->set_hmirror(s, this->horizontal_mirror_);
404 s->set_contrast(s, this->contrast_);
405 s->set_brightness(s, this->brightness_);
406 s->set_saturation(s, this->saturation_);
407 s->set_special_effect(s, (int) this->special_effect_); // 0 to 6
408 /* update exposure */
409 s->set_exposure_ctrl(s, (bool) this->aec_mode_);
410 s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable
411 s->set_ae_level(s, this->ae_level_); // -2 to 2
412 s->set_aec_value(s, this->aec_value_); // 0 to 1200
413 /* update gains */
414 s->set_gain_ctrl(s, (bool) this->agc_mode_);
415 s->set_agc_gain(s, (int) this->agc_value_); // 0 to 30
416 s->set_gainceiling(s, (gainceiling_t) this->agc_gain_ceiling_);
417 /* update white balance mode */
418 s->set_wb_mode(s, (int) this->wb_mode_); // 0 to 4
419 /* update test pattern */
420 s->set_colorbar(s, this->test_pattern_);
421}
422
423/* ---------------- Internal methods ---------------- */
425bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
427 ESP32Camera *that = (ESP32Camera *) pv;
428 while (true) {
429 camera_fb_t *framebuffer = esp_camera_fb_get();
430 xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
431 // Only wake the main loop if there's a pending request to consume the frame
432 if (that->has_requested_image_()) {
434 }
435 // return is no-op for config with 1 fb
436 xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
437 esp_camera_fb_return(framebuffer);
438 }
439}
440
441/* ---------------- ESP32CameraImageReader class ----------- */
442void ESP32CameraImageReader::set_image(std::shared_ptr<camera::CameraImage> image) {
443 this->image_ = std::static_pointer_cast<ESP32CameraImage>(image);
444 this->offset_ = 0;
445}
447 if (!this->image_)
448 return 0;
449
450 return this->image_->get_data_length() - this->offset_;
451}
453void ESP32CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; }
454uint8_t *ESP32CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; }
455
456/* ---------------- ESP32CameraImage class ----------- */
457ESP32CameraImage::ESP32CameraImage(camera_fb_t *buffer, uint8_t requesters)
458 : buffer_(buffer), requesters_(requesters) {}
459
460camera_fb_t *ESP32CameraImage::get_raw_buffer() { return this->buffer_; }
461uint8_t *ESP32CameraImage::get_data_buffer() { return this->buffer_->buf; }
462size_t ESP32CameraImage::get_data_length() { return this->buffer_->len; }
464 return (this->requesters_ & (1 << requester)) != 0;
465}
466
467} // namespace esp32_camera
468} // namespace esphome
469
470#endif
BedjetMode mode
BedJet operating mode.
uint16_le_t frequency
Definition bl0942.h:6
void wake_loop_threadsafe()
Wake the main event loop from a FreeRTOS task Thread-safe, can be called from task context to immedia...
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
bool is_internal() const
Definition entity_base.h:64
constexpr const char * c_str() const
Definition string_ref.h:69
Abstract image reader base class.
Definition camera.h:71
void set_i2c_pins(uint8_t sda, uint8_t scl)
void set_agc_value(uint8_t agc_value)
void set_test_pattern(bool test_pattern)
void set_jpeg_quality(uint8_t quality)
void set_wb_mode(ESP32WhiteBalanceMode mode)
ESP32AgcGainCeiling agc_gain_ceiling_
void set_vertical_flip(bool vertical_flip)
void set_aec_value(uint32_t aec_value)
void set_special_effect(ESP32SpecialEffect effect)
void set_aec_mode(ESP32GainControlMode mode)
float get_setup_priority() const override
void set_data_pins(std::array< uint8_t, 8 > pins)
void stop_stream(camera::CameraRequester requester) override
std::vector< camera::CameraListener * > listeners_
void set_i2c_id(i2c::InternalI2CBus *i2c_bus)
void set_frame_size(ESP32CameraFrameSize size)
void set_external_clock(uint8_t pin, uint32_t frequency)
void set_max_update_interval(uint32_t max_update_interval)
std::shared_ptr< ESP32CameraImage > current_image_
std::atomic< uint8_t > single_requesters_
void set_horizontal_mirror(bool horizontal_mirror)
camera::CameraImageReader * create_image_reader() override
void set_frame_buffer_count(uint8_t fb_count)
void set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling)
void set_agc_mode(ESP32GainControlMode mode)
void set_frame_buffer_location(camera_fb_location_t fb_location)
void set_frame_buffer_mode(camera_grab_mode_t mode)
void set_idle_update_interval(uint32_t idle_update_interval)
void start_stream(camera::CameraRequester requester) override
void request_image(camera::CameraRequester requester) override
std::atomic< uint8_t > stream_requesters_
static void framebuffer_task(void *pv)
bool was_requested_by(camera::CameraRequester requester) const override
ESP32CameraImage(camera_fb_t *buffer, uint8_t requester)
void set_image(std::shared_ptr< camera::CameraImage > image) override
std::shared_ptr< ESP32CameraImage > image_
void consume_data(size_t consumed) override
virtual int get_port() const =0
Returns the I2C port number.
CameraRequester
Different sources for filtering.
Definition camera.h:16
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:81
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.