mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'integration' into memory_api
This commit is contained in:
		| @@ -89,6 +89,7 @@ esphome/components/bp5758d/* @Cossid | |||||||
| esphome/components/button/* @esphome/core | esphome/components/button/* @esphome/core | ||||||
| esphome/components/bytebuffer/* @clydebarrow | esphome/components/bytebuffer/* @clydebarrow | ||||||
| esphome/components/camera/* @DT-art1 @bdraco | esphome/components/camera/* @DT-art1 @bdraco | ||||||
|  | esphome/components/camera_encoder/* @DT-art1 | ||||||
| esphome/components/canbus/* @danielschramm @mvturnho | esphome/components/canbus/* @danielschramm @mvturnho | ||||||
| esphome/components/cap1188/* @mreditor97 | esphome/components/cap1188/* @mreditor97 | ||||||
| esphome/components/captive_portal/* @esphome/core | esphome/components/captive_portal/* @esphome/core | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								esphome/components/camera/buffer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/camera/buffer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <cinttypes> | ||||||
|  | #include <cstddef> | ||||||
|  |  | ||||||
|  | namespace esphome::camera { | ||||||
|  |  | ||||||
|  | /// Interface for a generic buffer that stores image data. | ||||||
|  | class Buffer { | ||||||
|  |  public: | ||||||
|  |   /// Returns a pointer to the buffer's data. | ||||||
|  |   virtual uint8_t *get_data_buffer() = 0; | ||||||
|  |   /// Returns the length of the buffer in bytes. | ||||||
|  |   virtual size_t get_data_length() = 0; | ||||||
|  |   virtual ~Buffer() = default; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome::camera | ||||||
							
								
								
									
										20
									
								
								esphome/components/camera/buffer_impl.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/camera/buffer_impl.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | #include "buffer_impl.h" | ||||||
|  |  | ||||||
|  | namespace esphome::camera { | ||||||
|  |  | ||||||
|  | BufferImpl::BufferImpl(size_t size) { | ||||||
|  |   this->data_ = this->allocator_.allocate(size); | ||||||
|  |   this->size_ = size; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BufferImpl::BufferImpl(CameraImageSpec *spec) { | ||||||
|  |   this->data_ = this->allocator_.allocate(spec->bytes_per_image()); | ||||||
|  |   this->size_ = spec->bytes_per_image(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BufferImpl::~BufferImpl() { | ||||||
|  |   if (this->data_ != nullptr) | ||||||
|  |     this->allocator_.deallocate(this->data_, this->size_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace esphome::camera | ||||||
							
								
								
									
										26
									
								
								esphome/components/camera/buffer_impl.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/camera/buffer_impl.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "buffer.h" | ||||||
|  | #include "camera.h" | ||||||
|  |  | ||||||
|  | namespace esphome::camera { | ||||||
|  |  | ||||||
|  | /// Default implementation of Buffer Interface. | ||||||
|  | /// Uses a RAMAllocator for memory reservation. | ||||||
|  | class BufferImpl : public Buffer { | ||||||
|  |  public: | ||||||
|  |   explicit BufferImpl(size_t size); | ||||||
|  |   explicit BufferImpl(CameraImageSpec *spec); | ||||||
|  |   // -------- Buffer -------- | ||||||
|  |   uint8_t *get_data_buffer() override { return data_; } | ||||||
|  |   size_t get_data_length() override { return size_; } | ||||||
|  |   // ------------------------ | ||||||
|  |   ~BufferImpl() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   RAMAllocator<uint8_t> allocator_; | ||||||
|  |   size_t size_{}; | ||||||
|  |   uint8_t *data_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome::camera | ||||||
| @@ -15,6 +15,26 @@ namespace camera { | |||||||
|  */ |  */ | ||||||
| enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER }; | enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER }; | ||||||
|  |  | ||||||
|  | /// Enumeration of different pixel formats. | ||||||
|  | enum PixelFormat : uint8_t { | ||||||
|  |   PIXEL_FORMAT_GRAYSCALE = 0,  ///< 8-bit grayscale. | ||||||
|  |   PIXEL_FORMAT_RGB565,         ///< 16-bit RGB (5-6-5). | ||||||
|  |   PIXEL_FORMAT_BGR888,         ///< RGB pixel data in 8-bit format, stored as B, G, R (1 byte each). | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Returns string name for a given PixelFormat. | ||||||
|  | inline const char *to_string(PixelFormat format) { | ||||||
|  |   switch (format) { | ||||||
|  |     case PIXEL_FORMAT_GRAYSCALE: | ||||||
|  |       return "PIXEL_FORMAT_GRAYSCALE"; | ||||||
|  |     case PIXEL_FORMAT_RGB565: | ||||||
|  |       return "PIXEL_FORMAT_RGB565"; | ||||||
|  |     case PIXEL_FORMAT_BGR888: | ||||||
|  |       return "PIXEL_FORMAT_BGR888"; | ||||||
|  |   } | ||||||
|  |   return "PIXEL_FORMAT_UNKNOWN"; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** Abstract camera image base class. | /** Abstract camera image base class. | ||||||
|  *  Encapsulates the JPEG encoded data and it is shared among |  *  Encapsulates the JPEG encoded data and it is shared among | ||||||
|  *  all connected clients. |  *  all connected clients. | ||||||
| @@ -43,6 +63,29 @@ class CameraImageReader { | |||||||
|   virtual ~CameraImageReader() {} |   virtual ~CameraImageReader() {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /// Specification of a caputured camera image. | ||||||
|  | /// This struct defines the format and size details for images captured | ||||||
|  | /// or processed by a camera component. | ||||||
|  | struct CameraImageSpec { | ||||||
|  |   uint16_t width; | ||||||
|  |   uint16_t height; | ||||||
|  |   PixelFormat format; | ||||||
|  |   size_t bytes_per_pixel() { | ||||||
|  |     switch (format) { | ||||||
|  |       case PIXEL_FORMAT_GRAYSCALE: | ||||||
|  |         return 1; | ||||||
|  |       case PIXEL_FORMAT_RGB565: | ||||||
|  |         return 2; | ||||||
|  |       case PIXEL_FORMAT_BGR888: | ||||||
|  |         return 3; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return 1; | ||||||
|  |   } | ||||||
|  |   size_t bytes_per_row() { return bytes_per_pixel() * width; } | ||||||
|  |   size_t bytes_per_image() { return bytes_per_pixel() * width * height; } | ||||||
|  | }; | ||||||
|  |  | ||||||
| /** Abstract camera base class. Collaborates with API. | /** Abstract camera base class. Collaborates with API. | ||||||
|  *  1) API server starts and installs callback (add_image_callback) |  *  1) API server starts and installs callback (add_image_callback) | ||||||
|  *     which is called by the camera when a new image is available. |  *     which is called by the camera when a new image is available. | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								esphome/components/camera/encoder.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								esphome/components/camera/encoder.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "buffer.h" | ||||||
|  | #include "camera.h" | ||||||
|  |  | ||||||
|  | namespace esphome::camera { | ||||||
|  |  | ||||||
|  | /// Result codes from the encoder used to control camera pipeline flow. | ||||||
|  | enum EncoderError : uint8_t { | ||||||
|  |   ENCODER_ERROR_SUCCESS = 0,   ///< Encoding succeeded, continue pipeline normally. | ||||||
|  |   ENCODER_ERROR_SKIP_FRAME,    ///< Skip current frame, try again on next frame. | ||||||
|  |   ENCODER_ERROR_RETRY_FRAME,   ///< Retry current frame, after buffer growth or for incremental encoding. | ||||||
|  |   ENCODER_ERROR_CONFIGURATION  ///< Fatal config error, shut down pipeline. | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Converts EncoderError to string. | ||||||
|  | inline const char *to_string(EncoderError error) { | ||||||
|  |   switch (error) { | ||||||
|  |     case ENCODER_ERROR_SUCCESS: | ||||||
|  |       return "ENCODER_ERROR_SUCCESS"; | ||||||
|  |     case ENCODER_ERROR_SKIP_FRAME: | ||||||
|  |       return "ENCODER_ERROR_SKIP_FRAME"; | ||||||
|  |     case ENCODER_ERROR_RETRY_FRAME: | ||||||
|  |       return "ENCODER_ERROR_RETRY_FRAME"; | ||||||
|  |     case ENCODER_ERROR_CONFIGURATION: | ||||||
|  |       return "ENCODER_ERROR_CONFIGURATION"; | ||||||
|  |   } | ||||||
|  |   return "ENCODER_ERROR_INVALID"; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Interface for an encoder buffer supporting resizing and variable-length data. | ||||||
|  | class EncoderBuffer { | ||||||
|  |  public: | ||||||
|  |   ///  Sets logical buffer size, reallocates if needed. | ||||||
|  |   ///  @param size Required size in bytes. | ||||||
|  |   ///  @return true on success, false on allocation failure. | ||||||
|  |   virtual bool set_buffer_size(size_t size) = 0; | ||||||
|  |  | ||||||
|  |   /// Returns a pointer to the buffer data. | ||||||
|  |   virtual uint8_t *get_data() const = 0; | ||||||
|  |  | ||||||
|  |   /// Returns number of bytes currently used. | ||||||
|  |   virtual size_t get_size() const = 0; | ||||||
|  |  | ||||||
|  |   ///  Returns total allocated buffer size. | ||||||
|  |   virtual size_t get_max_size() const = 0; | ||||||
|  |  | ||||||
|  |   virtual ~EncoderBuffer() = default; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// Interface for image encoders used in a camera pipeline. | ||||||
|  | class Encoder { | ||||||
|  |  public: | ||||||
|  |   /// Encodes pixel data from a previous camera pipeline stage. | ||||||
|  |   /// @param spec Specification of the input pixel data. | ||||||
|  |   /// @param pixels Image pixels in RGB or grayscale format, as specified in @p spec. | ||||||
|  |   /// @return EncoderError Indicating the result of the encoding operation. | ||||||
|  |   virtual EncoderError encode_pixels(CameraImageSpec *spec, Buffer *pixels) = 0; | ||||||
|  |  | ||||||
|  |   /// Returns the encoder's output buffer. | ||||||
|  |   /// @return Pointer to an EncoderBuffer containing encoded data. | ||||||
|  |   virtual EncoderBuffer *get_output_buffer() = 0; | ||||||
|  |  | ||||||
|  |   ///  Prints the encoder's configuration to the log. | ||||||
|  |   virtual void dump_config() = 0; | ||||||
|  |   virtual ~Encoder() = default; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome::camera | ||||||
							
								
								
									
										62
									
								
								esphome/components/camera_encoder/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								esphome/components/camera_encoder/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components.esp32 import add_idf_component | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TYPE | ||||||
|  | from esphome.core import CORE | ||||||
|  | from esphome.types import ConfigType | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@DT-art1"] | ||||||
|  |  | ||||||
|  | AUTO_LOAD = ["camera"] | ||||||
|  |  | ||||||
|  | CONF_BUFFER_EXPAND_SIZE = "buffer_expand_size" | ||||||
|  | CONF_ENCODER_BUFFER_ID = "encoder_buffer_id" | ||||||
|  | CONF_QUALITY = "quality" | ||||||
|  |  | ||||||
|  | ESP32_CAMERA_ENCODER = "esp32_camera" | ||||||
|  |  | ||||||
|  | camera_ns = cg.esphome_ns.namespace("camera") | ||||||
|  | camera_encoder_ns = cg.esphome_ns.namespace("camera_encoder") | ||||||
|  |  | ||||||
|  | Encoder = camera_ns.class_("Encoder") | ||||||
|  | EncoderBufferImpl = camera_encoder_ns.class_("EncoderBufferImpl") | ||||||
|  |  | ||||||
|  | ESP32CameraJPEGEncoder = camera_encoder_ns.class_("ESP32CameraJPEGEncoder", Encoder) | ||||||
|  |  | ||||||
|  | MAX_JPEG_BUFFER_SIZE_2MB = 2 * 1024 * 1024 | ||||||
|  |  | ||||||
|  | ESP32_CAMERA_ENCODER_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(ESP32CameraJPEGEncoder), | ||||||
|  |         cv.Optional(CONF_QUALITY, default=80): cv.int_range(1, 100), | ||||||
|  |         cv.Optional(CONF_BUFFER_SIZE, default=4096): cv.int_range( | ||||||
|  |             1024, MAX_JPEG_BUFFER_SIZE_2MB | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_BUFFER_EXPAND_SIZE, default=1024): cv.int_range( | ||||||
|  |             0, MAX_JPEG_BUFFER_SIZE_2MB | ||||||
|  |         ), | ||||||
|  |         cv.GenerateID(CONF_ENCODER_BUFFER_ID): cv.declare_id(EncoderBufferImpl), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.typed_schema( | ||||||
|  |     { | ||||||
|  |         ESP32_CAMERA_ENCODER: ESP32_CAMERA_ENCODER_SCHEMA, | ||||||
|  |     }, | ||||||
|  |     default_type=ESP32_CAMERA_ENCODER, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config: ConfigType) -> None: | ||||||
|  |     buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID]) | ||||||
|  |     cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE])) | ||||||
|  |     if config[CONF_TYPE] == ESP32_CAMERA_ENCODER: | ||||||
|  |         if CORE.using_esp_idf: | ||||||
|  |             add_idf_component(name="espressif/esp32-camera", ref="2.1.0") | ||||||
|  |         cg.add_build_flag("-DUSE_ESP32_CAMERA_JPEG_ENCODER") | ||||||
|  |         var = cg.new_Pvariable( | ||||||
|  |             config[CONF_ID], | ||||||
|  |             config[CONF_QUALITY], | ||||||
|  |             buffer, | ||||||
|  |         ) | ||||||
|  |         cg.add(var.set_buffer_expand_size(config[CONF_BUFFER_EXPAND_SIZE])) | ||||||
							
								
								
									
										23
									
								
								esphome/components/camera_encoder/encoder_buffer_impl.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/camera_encoder/encoder_buffer_impl.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | #include "encoder_buffer_impl.h" | ||||||
|  |  | ||||||
|  | namespace esphome::camera_encoder { | ||||||
|  |  | ||||||
|  | bool EncoderBufferImpl::set_buffer_size(size_t size) { | ||||||
|  |   if (size > this->capacity_) { | ||||||
|  |     uint8_t *p = this->allocator_.reallocate(this->data_, size); | ||||||
|  |     if (p == nullptr) | ||||||
|  |       return false; | ||||||
|  |  | ||||||
|  |     this->data_ = p; | ||||||
|  |     this->capacity_ = size; | ||||||
|  |   } | ||||||
|  |   this->size_ = size; | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | EncoderBufferImpl::~EncoderBufferImpl() { | ||||||
|  |   if (this->data_ != nullptr) | ||||||
|  |     this->allocator_.deallocate(this->data_, this->capacity_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace esphome::camera_encoder | ||||||
							
								
								
									
										25
									
								
								esphome/components/camera_encoder/encoder_buffer_impl.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/camera_encoder/encoder_buffer_impl.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/camera/encoder.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome::camera_encoder { | ||||||
|  |  | ||||||
|  | class EncoderBufferImpl : public camera::EncoderBuffer { | ||||||
|  |  public: | ||||||
|  |   // --- EncoderBuffer  --- | ||||||
|  |   bool set_buffer_size(size_t size) override; | ||||||
|  |   uint8_t *get_data() const override { return this->data_; } | ||||||
|  |   size_t get_size() const override { return this->size_; } | ||||||
|  |   size_t get_max_size() const override { return this->capacity_; } | ||||||
|  |   // ---------------------- | ||||||
|  |   ~EncoderBufferImpl() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   RAMAllocator<uint8_t> allocator_; | ||||||
|  |   size_t capacity_{}; | ||||||
|  |   size_t size_{}; | ||||||
|  |   uint8_t *data_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome::camera_encoder | ||||||
| @@ -0,0 +1,82 @@ | |||||||
|  | #ifdef USE_ESP32_CAMERA_JPEG_ENCODER | ||||||
|  |  | ||||||
|  | #include "esp32_camera_jpeg_encoder.h" | ||||||
|  |  | ||||||
|  | namespace esphome::camera_encoder { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "camera_encoder"; | ||||||
|  |  | ||||||
|  | ESP32CameraJPEGEncoder::ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output) { | ||||||
|  |   this->quality_ = quality; | ||||||
|  |   this->output_ = output; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | camera::EncoderError ESP32CameraJPEGEncoder::encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) { | ||||||
|  |   this->bytes_written_ = 0; | ||||||
|  |   this->out_of_output_memory_ = false; | ||||||
|  |   bool success = fmt2jpg_cb(pixels->get_data_buffer(), pixels->get_data_length(), spec->width, spec->height, | ||||||
|  |                             to_internal_(spec->format), this->quality_, callback_, this); | ||||||
|  |  | ||||||
|  |   if (!success) | ||||||
|  |     return camera::ENCODER_ERROR_CONFIGURATION; | ||||||
|  |  | ||||||
|  |   if (this->out_of_output_memory_) { | ||||||
|  |     if (this->buffer_expand_size_ <= 0) | ||||||
|  |       return camera::ENCODER_ERROR_SKIP_FRAME; | ||||||
|  |  | ||||||
|  |     size_t current_size = this->output_->get_max_size(); | ||||||
|  |     size_t new_size = this->output_->get_max_size() + this->buffer_expand_size_; | ||||||
|  |     if (!this->output_->set_buffer_size(new_size)) { | ||||||
|  |       ESP_LOGE(TAG, "Failed to expand output buffer."); | ||||||
|  |       this->buffer_expand_size_ = 0; | ||||||
|  |       return camera::ENCODER_ERROR_SKIP_FRAME; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ESP_LOGD(TAG, "Output buffer expanded (%u -> %u).", current_size, this->output_->get_max_size()); | ||||||
|  |     return camera::ENCODER_ERROR_RETRY_FRAME; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->output_->set_buffer_size(this->bytes_written_); | ||||||
|  |   return camera::ENCODER_ERROR_SUCCESS; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ESP32CameraJPEGEncoder::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, | ||||||
|  |                 "ESP32 Camera JPEG Encoder:\n" | ||||||
|  |                 "  Size: %zu\n" | ||||||
|  |                 "  Quality: %d\n" | ||||||
|  |                 "  Expand: %d\n", | ||||||
|  |                 this->output_->get_max_size(), this->quality_, this->buffer_expand_size_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t ESP32CameraJPEGEncoder::callback_(void *arg, size_t index, const void *data, size_t len) { | ||||||
|  |   ESP32CameraJPEGEncoder *that = reinterpret_cast<ESP32CameraJPEGEncoder *>(arg); | ||||||
|  |   uint8_t *buffer = that->output_->get_data(); | ||||||
|  |   size_t buffer_length = that->output_->get_max_size(); | ||||||
|  |   if (index + len > buffer_length) { | ||||||
|  |     that->out_of_output_memory_ = true; | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::memcpy(&buffer[index], data, len); | ||||||
|  |   that->bytes_written_ += len; | ||||||
|  |   return len; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pixformat_t ESP32CameraJPEGEncoder::to_internal_(camera::PixelFormat format) { | ||||||
|  |   switch (format) { | ||||||
|  |     case camera::PIXEL_FORMAT_GRAYSCALE: | ||||||
|  |       return PIXFORMAT_GRAYSCALE; | ||||||
|  |     case camera::PIXEL_FORMAT_RGB565: | ||||||
|  |       return PIXFORMAT_RGB565; | ||||||
|  |     // Internal representation for RGB is in byte order: B, G, R | ||||||
|  |     case camera::PIXEL_FORMAT_BGR888: | ||||||
|  |       return PIXFORMAT_RGB888; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return PIXFORMAT_GRAYSCALE; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace esphome::camera_encoder | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32_CAMERA_JPEG_ENCODER | ||||||
|  |  | ||||||
|  | #include <esp_camera.h> | ||||||
|  |  | ||||||
|  | #include "esphome/components/camera/encoder.h" | ||||||
|  |  | ||||||
|  | namespace esphome::camera_encoder { | ||||||
|  |  | ||||||
|  | /// Encoder that uses the software-based JPEG implementation from Espressif's esp32-camera component. | ||||||
|  | class ESP32CameraJPEGEncoder : public camera::Encoder { | ||||||
|  |  public: | ||||||
|  |   /// Constructs a ESP32CameraJPEGEncoder instance. | ||||||
|  |   /// @param quality Sets the quality of the encoded image (1-100). | ||||||
|  |   /// @param output Pointer to preallocated output buffer. | ||||||
|  |   ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output); | ||||||
|  |   /// Sets the number of bytes to expand the output buffer on underflow during encoding. | ||||||
|  |   /// @param buffer_expand_size Number of bytes to expand the buffer. | ||||||
|  |   void set_buffer_expand_size(size_t buffer_expand_size) { this->buffer_expand_size_ = buffer_expand_size; } | ||||||
|  |   // -------- Encoder -------- | ||||||
|  |   camera::EncoderError encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) override; | ||||||
|  |   camera::EncoderBuffer *get_output_buffer() override { return output_; } | ||||||
|  |   void dump_config() override; | ||||||
|  |   // ------------------------- | ||||||
|  |  protected: | ||||||
|  |   static size_t callback_(void *arg, size_t index, const void *data, size_t len); | ||||||
|  |   pixformat_t to_internal_(camera::PixelFormat format); | ||||||
|  |  | ||||||
|  |   camera::EncoderBuffer *output_{}; | ||||||
|  |   size_t buffer_expand_size_{}; | ||||||
|  |   size_t bytes_written_{}; | ||||||
|  |   uint8_t quality_{}; | ||||||
|  |   bool out_of_output_memory_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome::camera_encoder | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -176,7 +176,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args): | |||||||
|     DisplayPageShowNextAction, |     DisplayPageShowNextAction, | ||||||
|     maybe_simple_id( |     maybe_simple_id( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)), |             cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)), | ||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
| @@ -190,7 +190,7 @@ async def display_page_show_next_to_code(config, action_id, template_arg, args): | |||||||
|     DisplayPageShowPrevAction, |     DisplayPageShowPrevAction, | ||||||
|     maybe_simple_id( |     maybe_simple_id( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)), |             cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)), | ||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -122,7 +122,7 @@ uint8_t Mcp4461Component::get_status_register_() { | |||||||
|   uint8_t addr = static_cast<uint8_t>(Mcp4461Addresses::MCP4461_STATUS); |   uint8_t addr = static_cast<uint8_t>(Mcp4461Addresses::MCP4461_STATUS); | ||||||
|   uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::READ); |   uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::READ); | ||||||
|   uint16_t buf; |   uint16_t buf; | ||||||
|   if (!this->read_byte_16(reg, &buf)) { |   if (!this->read_16_(reg, &buf)) { | ||||||
|     this->error_code_ = MCP4461_STATUS_REGISTER_ERROR; |     this->error_code_ = MCP4461_STATUS_REGISTER_ERROR; | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return 0; |     return 0; | ||||||
| @@ -148,6 +148,20 @@ void Mcp4461Component::read_status_register_to_log() { | |||||||
|            ((status_register_value >> 3) & 0x01), ((status_register_value >> 2) & 0x01), |            ((status_register_value >> 3) & 0x01), ((status_register_value >> 2) & 0x01), | ||||||
|            ((status_register_value >> 1) & 0x01), ((status_register_value >> 0) & 0x01)); |            ((status_register_value >> 1) & 0x01), ((status_register_value >> 0) & 0x01)); | ||||||
| } | } | ||||||
|  | bool Mcp4461Component::read_16_(uint8_t address, uint16_t *buf) { | ||||||
|  |   // read 16 bits and convert from big endian to host, | ||||||
|  |   // Do this as two separate operations to ensure a stop condition between the write and read | ||||||
|  |   i2c::ErrorCode err = this->write(&address, 1); | ||||||
|  |   if (err != i2c::ERROR_OK) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   err = this->read(reinterpret_cast<uint8_t *>(buf), 2); | ||||||
|  |   if (err != i2c::ERROR_OK) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   *buf = convert_big_endian(*buf); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| uint8_t Mcp4461Component::get_wiper_address_(uint8_t wiper) { | uint8_t Mcp4461Component::get_wiper_address_(uint8_t wiper) { | ||||||
|   uint8_t addr; |   uint8_t addr; | ||||||
| @@ -205,7 +219,7 @@ uint16_t Mcp4461Component::read_wiper_level_(uint8_t wiper_idx) { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   uint16_t buf = 0; |   uint16_t buf = 0; | ||||||
|   if (!(this->read_byte_16(reg, &buf))) { |   if (!(this->read_16_(reg, &buf))) { | ||||||
|     this->error_code_ = MCP4461_STATUS_I2C_ERROR; |     this->error_code_ = MCP4461_STATUS_I2C_ERROR; | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     ESP_LOGW(TAG, "Error fetching %swiper %u value", (wiper_idx > 3) ? "nonvolatile " : "", wiper_idx); |     ESP_LOGW(TAG, "Error fetching %swiper %u value", (wiper_idx > 3) ? "nonvolatile " : "", wiper_idx); | ||||||
| @@ -392,7 +406,7 @@ uint8_t Mcp4461Component::get_terminal_register_(Mcp4461TerminalIdx terminal_con | |||||||
|                                                               : static_cast<uint8_t>(Mcp4461Addresses::MCP4461_TCON1); |                                                               : static_cast<uint8_t>(Mcp4461Addresses::MCP4461_TCON1); | ||||||
|   reg |= static_cast<uint8_t>(Mcp4461Commands::READ); |   reg |= static_cast<uint8_t>(Mcp4461Commands::READ); | ||||||
|   uint16_t buf; |   uint16_t buf; | ||||||
|   if (this->read_byte_16(reg, &buf)) { |   if (this->read_16_(reg, &buf)) { | ||||||
|     return static_cast<uint8_t>(buf & 0x00ff); |     return static_cast<uint8_t>(buf & 0x00ff); | ||||||
|   } else { |   } else { | ||||||
|     this->error_code_ = MCP4461_STATUS_I2C_ERROR; |     this->error_code_ = MCP4461_STATUS_I2C_ERROR; | ||||||
| @@ -517,7 +531,7 @@ uint16_t Mcp4461Component::get_eeprom_value(Mcp4461EepromLocation location) { | |||||||
|   if (!this->is_eeprom_ready_for_writing_(true)) { |   if (!this->is_eeprom_ready_for_writing_(true)) { | ||||||
|     return 0; |     return 0; | ||||||
|   } |   } | ||||||
|   if (!this->read_byte_16(reg, &buf)) { |   if (!this->read_16_(reg, &buf)) { | ||||||
|     this->error_code_ = MCP4461_STATUS_I2C_ERROR; |     this->error_code_ = MCP4461_STATUS_I2C_ERROR; | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     ESP_LOGW(TAG, "Error fetching EEPROM location value"); |     ESP_LOGW(TAG, "Error fetching EEPROM location value"); | ||||||
|   | |||||||
| @@ -96,6 +96,7 @@ class Mcp4461Component : public Component, public i2c::I2CDevice { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   friend class Mcp4461Wiper; |   friend class Mcp4461Wiper; | ||||||
|  |   bool read_16_(uint8_t address, uint16_t *buf); | ||||||
|   void update_write_protection_status_(); |   void update_write_protection_status_(); | ||||||
|   uint8_t get_wiper_address_(uint8_t wiper); |   uint8_t get_wiper_address_(uint8_t wiper); | ||||||
|   uint16_t read_wiper_level_(uint8_t wiper); |   uint16_t read_wiper_level_(uint8_t wiper); | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| # Various configuration constants for MIPI displays | # Various configuration constants for MIPI displays | ||||||
| # Various utility functions for MIPI DBI configuration | # Various utility functions for MIPI DBI configuration | ||||||
|  |  | ||||||
| from typing import Any | from typing import Any, Self | ||||||
|  |  | ||||||
| from esphome.components.const import CONF_COLOR_DEPTH | from esphome.components.const import CONF_COLOR_DEPTH | ||||||
| from esphome.components.display import CONF_SHOW_TEST_CARD, display_ns | from esphome.components.display import CONF_SHOW_TEST_CARD, display_ns | ||||||
| @@ -222,7 +222,7 @@ def delay(ms): | |||||||
|  |  | ||||||
|  |  | ||||||
| class DriverChip: | class DriverChip: | ||||||
|     models = {} |     models: dict[str, Self] = {} | ||||||
|  |  | ||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ DriverChip( | |||||||
|     lane_bit_rate="750Mbps", |     lane_bit_rate="750Mbps", | ||||||
|     swap_xy=cv.UNDEFINED, |     swap_xy=cv.UNDEFINED, | ||||||
|     color_order="RGB", |     color_order="RGB", | ||||||
|     reset_pin=27, |  | ||||||
|     initsequence=[ |     initsequence=[ | ||||||
|         (0x30, 0x00), (0xF7, 0x49, 0x61, 0x02, 0x00), (0x30, 0x01), (0x04, 0x0C), (0x05, 0x00), (0x06, 0x00), |         (0x30, 0x00), (0xF7, 0x49, 0x61, 0x02, 0x00), (0x30, 0x01), (0x04, 0x0C), (0x05, 0x00), (0x06, 0x00), | ||||||
|         (0x0B, 0x11), (0x17, 0x00), (0x20, 0x04), (0x1F, 0x05), (0x23, 0x00), (0x25, 0x19), (0x28, 0x18), (0x29, 0x04), (0x2A, 0x01), |         (0x0B, 0x11), (0x17, 0x00), (0x20, 0x04), (0x1F, 0x05), (0x23, 0x00), (0x25, 0x19), (0x28, 0x18), (0x29, 0x04), (0x2A, 0x01), | ||||||
|   | |||||||
| @@ -2,10 +2,13 @@ from __future__ import annotations | |||||||
|  |  | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
|  | from esphome import pins | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components.zephyr import ( | from esphome.components.zephyr import ( | ||||||
|     copy_files as zephyr_copy_files, |     copy_files as zephyr_copy_files, | ||||||
|     zephyr_add_pm_static, |     zephyr_add_pm_static, | ||||||
|  |     zephyr_add_prj_conf, | ||||||
|  |     zephyr_data, | ||||||
|     zephyr_set_core_data, |     zephyr_set_core_data, | ||||||
|     zephyr_to_code, |     zephyr_to_code, | ||||||
| ) | ) | ||||||
| @@ -18,6 +21,8 @@ import esphome.config_validation as cv | |||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_BOARD, |     CONF_BOARD, | ||||||
|     CONF_FRAMEWORK, |     CONF_FRAMEWORK, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_RESET_PIN, | ||||||
|     KEY_CORE, |     KEY_CORE, | ||||||
|     KEY_FRAMEWORK_VERSION, |     KEY_FRAMEWORK_VERSION, | ||||||
|     KEY_TARGET_FRAMEWORK, |     KEY_TARGET_FRAMEWORK, | ||||||
| @@ -90,18 +95,43 @@ def _detect_bootloader(config: ConfigType) -> ConfigType: | |||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | nrf52_ns = cg.esphome_ns.namespace("nrf52") | ||||||
|  | DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component) | ||||||
|  |  | ||||||
|  | CONF_DFU = "dfu" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|  |     _detect_bootloader, | ||||||
|  |     set_core_data, | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_BOARD): cv.string_strict, |             cv.Required(CONF_BOARD): cv.string_strict, | ||||||
|             cv.Optional(KEY_BOOTLOADER): cv.one_of(*BOOTLOADERS, lower=True), |             cv.Optional(KEY_BOOTLOADER): cv.one_of(*BOOTLOADERS, lower=True), | ||||||
|  |             cv.Optional(CONF_DFU): cv.Schema( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(): cv.declare_id(DeviceFirmwareUpdate), | ||||||
|  |                     cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
|     _detect_bootloader, |  | ||||||
|     set_core_data, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_mcumgr(config): | ||||||
|  |     bootloader = zephyr_data()[KEY_BOOTLOADER] | ||||||
|  |     if bootloader == BOOTLOADER_MCUBOOT: | ||||||
|  |         raise cv.Invalid(f"'{bootloader}' bootloader does not support DFU") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _final_validate(config): | ||||||
|  |     if CONF_DFU in config: | ||||||
|  |         _validate_mcumgr(config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = _final_validate | ||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(1000) | @coroutine_with_priority(1000) | ||||||
| async def to_code(config: ConfigType) -> None: | async def to_code(config: ConfigType) -> None: | ||||||
|     """Convert the configuration to code.""" |     """Convert the configuration to code.""" | ||||||
| @@ -136,6 +166,19 @@ async def to_code(config: ConfigType) -> None: | |||||||
|  |  | ||||||
|     zephyr_to_code(config) |     zephyr_to_code(config) | ||||||
|  |  | ||||||
|  |     if dfu_config := config.get(CONF_DFU): | ||||||
|  |         CORE.add_job(_dfu_to_code, dfu_config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @coroutine_with_priority(90) | ||||||
|  | async def _dfu_to_code(dfu_config): | ||||||
|  |     cg.add_define("USE_NRF52_DFU") | ||||||
|  |     var = cg.new_Pvariable(dfu_config[CONF_ID]) | ||||||
|  |     pin = await cg.gpio_pin_expression(dfu_config[CONF_RESET_PIN]) | ||||||
|  |     cg.add(var.set_reset_pin(pin)) | ||||||
|  |     zephyr_add_prj_conf("CDC_ACM_DTE_RATE_CALLBACK_SUPPORT", True) | ||||||
|  |     await cg.register_component(var, dfu_config) | ||||||
|  |  | ||||||
|  |  | ||||||
| def copy_files() -> None: | def copy_files() -> None: | ||||||
|     """Copy files to the build directory.""" |     """Copy files to the build directory.""" | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ BOOTLOADER_ADAFRUIT = "adafruit" | |||||||
| BOOTLOADER_ADAFRUIT_NRF52_SD132 = "adafruit_nrf52_sd132" | BOOTLOADER_ADAFRUIT_NRF52_SD132 = "adafruit_nrf52_sd132" | ||||||
| BOOTLOADER_ADAFRUIT_NRF52_SD140_V6 = "adafruit_nrf52_sd140_v6" | BOOTLOADER_ADAFRUIT_NRF52_SD140_V6 = "adafruit_nrf52_sd140_v6" | ||||||
| BOOTLOADER_ADAFRUIT_NRF52_SD140_V7 = "adafruit_nrf52_sd140_v7" | BOOTLOADER_ADAFRUIT_NRF52_SD140_V7 = "adafruit_nrf52_sd140_v7" | ||||||
|  |  | ||||||
| EXTRA_ADC = [ | EXTRA_ADC = [ | ||||||
|     "VDD", |     "VDD", | ||||||
|     "VDDHDIV5", |     "VDDHDIV5", | ||||||
|   | |||||||
							
								
								
									
										51
									
								
								esphome/components/nrf52/dfu.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								esphome/components/nrf52/dfu.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | #include "dfu.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_NRF52_DFU | ||||||
|  |  | ||||||
|  | #include <zephyr/device.h> | ||||||
|  | #include <zephyr/drivers/uart.h> | ||||||
|  | #include <zephyr/drivers/uart/cdc_acm.h> | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace nrf52 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "dfu"; | ||||||
|  |  | ||||||
|  | volatile bool goto_dfu = false;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|  |  | ||||||
|  | static const uint32_t DFU_DBL_RESET_MAGIC = 0x5A1AD5;  // SALADS | ||||||
|  |  | ||||||
|  | #define DEVICE_AND_COMMA(node_id) DEVICE_DT_GET(node_id), | ||||||
|  |  | ||||||
|  | static void cdc_dte_rate_callback(const struct device * /*unused*/, uint32_t rate) { | ||||||
|  |   if (rate == 1200) { | ||||||
|  |     goto_dfu = true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void DeviceFirmwareUpdate::setup() { | ||||||
|  |   this->reset_pin_->setup(); | ||||||
|  |   const struct device *cdc_dev[] = {DT_FOREACH_STATUS_OKAY(zephyr_cdc_acm_uart, DEVICE_AND_COMMA)}; | ||||||
|  |   for (auto &idx : cdc_dev) { | ||||||
|  |     cdc_acm_dte_rate_callback_set(idx, cdc_dte_rate_callback); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DeviceFirmwareUpdate::loop() { | ||||||
|  |   if (goto_dfu) { | ||||||
|  |     goto_dfu = false; | ||||||
|  |     volatile uint32_t *dbl_reset_mem = (volatile uint32_t *) 0x20007F7C; | ||||||
|  |     (*dbl_reset_mem) = DFU_DBL_RESET_MAGIC; | ||||||
|  |     this->reset_pin_->digital_write(true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DeviceFirmwareUpdate::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "DFU:"); | ||||||
|  |   LOG_PIN("  RESET Pin: ", this->reset_pin_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace nrf52 | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										24
									
								
								esphome/components/nrf52/dfu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/nrf52/dfu.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #ifdef USE_NRF52_DFU | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/gpio.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace nrf52 { | ||||||
|  | class DeviceFirmwareUpdate : public Component { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void loop() override; | ||||||
|  |   void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   GPIOPin *reset_pin_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace nrf52 | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -14,8 +14,13 @@ namespace sntp { | |||||||
|  |  | ||||||
| static const char *const TAG = "sntp"; | static const char *const TAG = "sntp"; | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) | ||||||
|  | SNTPComponent *SNTPComponent::instance = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|  | #endif | ||||||
|  |  | ||||||
| void SNTPComponent::setup() { | void SNTPComponent::setup() { | ||||||
| #if defined(USE_ESP32) | #if defined(USE_ESP32) | ||||||
|  |   SNTPComponent::instance = this; | ||||||
|   if (esp_sntp_enabled()) { |   if (esp_sntp_enabled()) { | ||||||
|     esp_sntp_stop(); |     esp_sntp_stop(); | ||||||
|   } |   } | ||||||
| @@ -25,6 +30,11 @@ void SNTPComponent::setup() { | |||||||
|     esp_sntp_setservername(i++, server.c_str()); |     esp_sntp_setservername(i++, server.c_str()); | ||||||
|   } |   } | ||||||
|   esp_sntp_set_sync_interval(this->get_update_interval()); |   esp_sntp_set_sync_interval(this->get_update_interval()); | ||||||
|  |   esp_sntp_set_time_sync_notification_cb([](struct timeval *tv) { | ||||||
|  |     if (SNTPComponent::instance != nullptr) { | ||||||
|  |       SNTPComponent::instance->defer([]() { SNTPComponent::instance->time_synced(); }); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|   esp_sntp_init(); |   esp_sntp_init(); | ||||||
| #else | #else | ||||||
|   sntp_stop(); |   sntp_stop(); | ||||||
| @@ -34,6 +44,14 @@ void SNTPComponent::setup() { | |||||||
|   for (auto &server : this->servers_) { |   for (auto &server : this->servers_) { | ||||||
|     sntp_setservername(i++, server.c_str()); |     sntp_setservername(i++, server.c_str()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP8266) | ||||||
|  |   settimeofday_cb([this](bool from_sntp) { | ||||||
|  |     if (from_sntp) | ||||||
|  |       this->time_synced(); | ||||||
|  |   }); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   sntp_init(); |   sntp_init(); | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| @@ -46,7 +64,8 @@ void SNTPComponent::dump_config() { | |||||||
| } | } | ||||||
| void SNTPComponent::update() { | void SNTPComponent::update() { | ||||||
| #if !defined(USE_ESP32) | #if !defined(USE_ESP32) | ||||||
|   // force resync |   // Some platforms currently cannot set the sync interval at runtime so we need | ||||||
|  |   // to do the re-sync by hand for now. | ||||||
|   if (sntp_enabled()) { |   if (sntp_enabled()) { | ||||||
|     sntp_stop(); |     sntp_stop(); | ||||||
|     this->has_time_ = false; |     this->has_time_ = false; | ||||||
| @@ -55,23 +74,31 @@ void SNTPComponent::update() { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
| void SNTPComponent::loop() { | void SNTPComponent::loop() { | ||||||
|  | // The loop is used to infer whether we have valid time on platforms where we | ||||||
|  | // cannot tell whether SNTP has succeeded. | ||||||
|  | // One limitation of this approach is that we cannot tell if it was the SNTP | ||||||
|  | // component that set the time. | ||||||
|  | // ESP-IDF and ESP8266 use callbacks from the SNTP task to trigger the | ||||||
|  | // `on_time_sync` trigger on successful sync events. | ||||||
|  | #if defined(USE_ESP32) || defined(USE_ESP8266) | ||||||
|  |   this->disable_loop(); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   if (this->has_time_) |   if (this->has_time_) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|  |   this->time_synced(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SNTPComponent::time_synced() { | ||||||
|   auto time = this->now(); |   auto time = this->now(); | ||||||
|   if (!time.is_valid()) |   this->has_time_ = time.is_valid(); | ||||||
|  |   if (!this->has_time_) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, |   ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, | ||||||
|            time.minute, time.second); |            time.minute, time.second); | ||||||
|   this->time_sync_callback_.call(); |   this->time_sync_callback_.call(); | ||||||
|   this->has_time_ = true; |  | ||||||
|  |  | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
|   // On ESP-IDF, time sync is permanent and update() doesn't force resync |  | ||||||
|   // Time is now synchronized, no need to check anymore |  | ||||||
|   this->disable_loop(); |  | ||||||
| #endif |  | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace sntp | }  // namespace sntp | ||||||
|   | |||||||
| @@ -26,9 +26,16 @@ class SNTPComponent : public time::RealTimeClock { | |||||||
|   void update() override; |   void update() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|  |   void time_synced(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::vector<std::string> servers_; |   std::vector<std::string> servers_; | ||||||
|   bool has_time_{false}; |   bool has_time_{false}; | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) | ||||||
|  |  private: | ||||||
|  |   static SNTPComponent *instance; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace sntp | }  // namespace sntp | ||||||
|   | |||||||
| @@ -654,12 +654,14 @@ const char *get_disconnect_reason_str(uint8_t reason) { | |||||||
|       return "Association comeback time too long"; |       return "Association comeback time too long"; | ||||||
|     case WIFI_REASON_SA_QUERY_TIMEOUT: |     case WIFI_REASON_SA_QUERY_TIMEOUT: | ||||||
|       return "SA query timeout"; |       return "SA query timeout"; | ||||||
|  | #if (ESP_IDF_VERSION_MAJOR >= 5) && (ESP_IDF_VERSION_MINOR >= 2) | ||||||
|     case WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY: |     case WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY: | ||||||
|       return "No AP found with compatible security"; |       return "No AP found with compatible security"; | ||||||
|     case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD: |     case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD: | ||||||
|       return "No AP found in auth mode threshold"; |       return "No AP found in auth mode threshold"; | ||||||
|     case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD: |     case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD: | ||||||
|       return "No AP found in RSSI threshold"; |       return "No AP found in RSSI threshold"; | ||||||
|  | #endif | ||||||
|     case WIFI_REASON_UNSPECIFIED: |     case WIFI_REASON_UNSPECIFIED: | ||||||
|     default: |     default: | ||||||
|       return "Unspecified"; |       return "Unspecified"; | ||||||
|   | |||||||
| @@ -240,6 +240,10 @@ | |||||||
| #define USE_SOCKET_SELECT_SUPPORT | #define USE_SOCKET_SELECT_SUPPORT | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_NRF52 | ||||||
|  | #define USE_NRF52_DFU | ||||||
|  | #endif | ||||||
|  |  | ||||||
| // Disabled feature flags | // Disabled feature flags | ||||||
| // #define USE_BSEC   // Requires a library with proprietary license | // #define USE_BSEC   // Requires a library with proprietary license | ||||||
| // #define USE_BSEC2  // Requires a library with proprietary license | // #define USE_BSEC2  // Requires a library with proprietary license | ||||||
|   | |||||||
| @@ -45,10 +45,15 @@ void EntityBase::set_icon(const char *icon) { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Check if the object_id is dynamic (changes with MAC suffix) | ||||||
|  | bool EntityBase::is_object_id_dynamic_() const { | ||||||
|  |   return !this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled(); | ||||||
|  | } | ||||||
|  |  | ||||||
| // Entity Object ID | // Entity Object ID | ||||||
| std::string EntityBase::get_object_id() const { | std::string EntityBase::get_object_id() const { | ||||||
|   // Check if `App.get_friendly_name()` is constant or dynamic. |   // Check if `App.get_friendly_name()` is constant or dynamic. | ||||||
|   if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) { |   if (this->is_object_id_dynamic_()) { | ||||||
|     // `App.get_friendly_name()` is dynamic. |     // `App.get_friendly_name()` is dynamic. | ||||||
|     return str_sanitize(str_snake_case(App.get_friendly_name())); |     return str_sanitize(str_snake_case(App.get_friendly_name())); | ||||||
|   } |   } | ||||||
| @@ -58,7 +63,7 @@ std::string EntityBase::get_object_id() const { | |||||||
| StringRef EntityBase::get_object_id_ref_for_api_() const { | StringRef EntityBase::get_object_id_ref_for_api_() const { | ||||||
|   static constexpr auto EMPTY_STRING = StringRef::from_lit(""); |   static constexpr auto EMPTY_STRING = StringRef::from_lit(""); | ||||||
|   // Return empty for dynamic case (MAC suffix) |   // Return empty for dynamic case (MAC suffix) | ||||||
|   if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) { |   if (this->is_object_id_dynamic_()) { | ||||||
|     return EMPTY_STRING; |     return EMPTY_STRING; | ||||||
|   } |   } | ||||||
|   // For static case, return the string or empty if null |   // For static case, return the string or empty if null | ||||||
| @@ -70,7 +75,10 @@ void EntityBase::set_object_id(const char *object_id) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Calculate Object ID Hash from Entity Name | // Calculate Object ID Hash from Entity Name | ||||||
| void EntityBase::calc_object_id_() { this->object_id_hash_ = fnv1_hash(this->get_object_id()); } | void EntityBase::calc_object_id_() { | ||||||
|  |   this->object_id_hash_ = | ||||||
|  |       fnv1_hash(this->is_object_id_dynamic_() ? this->get_object_id().c_str() : this->object_id_c_str_); | ||||||
|  | } | ||||||
|  |  | ||||||
| uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } | uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -126,6 +126,9 @@ class EntityBase { | |||||||
|   virtual uint32_t hash_base() { return 0L; } |   virtual uint32_t hash_base() { return 0L; } | ||||||
|   void calc_object_id_(); |   void calc_object_id_(); | ||||||
|  |  | ||||||
|  |   /// Check if the object_id is dynamic (changes with MAC suffix) | ||||||
|  |   bool is_object_id_dynamic_() const; | ||||||
|  |  | ||||||
|   StringRef name_; |   StringRef name_; | ||||||
|   const char *object_id_c_str_{nullptr}; |   const char *object_id_c_str_{nullptr}; | ||||||
| #ifdef USE_ENTITY_ICON | #ifdef USE_ENTITY_ICON | ||||||
|   | |||||||
| @@ -142,11 +142,13 @@ uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly, | |||||||
|   return refout ? (crc ^ 0xffff) : crc; |   return refout ? (crc ^ 0xffff) : crc; | ||||||
| } | } | ||||||
|  |  | ||||||
| uint32_t fnv1_hash(const std::string &str) { | uint32_t fnv1_hash(const char *str) { | ||||||
|   uint32_t hash = 2166136261UL; |   uint32_t hash = 2166136261UL; | ||||||
|   for (char c : str) { |   if (str) { | ||||||
|  |     while (*str) { | ||||||
|       hash *= 16777619UL; |       hash *= 16777619UL; | ||||||
|     hash ^= c; |       hash ^= *str++; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   return hash; |   return hash; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -155,7 +155,8 @@ uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc = 0, uint16_t p | |||||||
|                  bool refout = false); |                  bool refout = false); | ||||||
|  |  | ||||||
| /// Calculate a FNV-1 hash of \p str. | /// Calculate a FNV-1 hash of \p str. | ||||||
| uint32_t fnv1_hash(const std::string &str); | uint32_t fnv1_hash(const char *str); | ||||||
|  | inline uint32_t fnv1_hash(const std::string &str) { return fnv1_hash(str.c_str()); } | ||||||
|  |  | ||||||
| /// Return a random 32-bit unsigned integer. | /// Return a random 32-bit unsigned integer. | ||||||
| uint32_t random_uint32(); | uint32_t random_uint32(); | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								tests/components/camera_encoder/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/camera_encoder/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | camera_encoder: | ||||||
|  |   id: jpeg_encoder | ||||||
|  |   quality: 80 | ||||||
|  |   buffer_size: 4096 | ||||||
|  |   buffer_expand_size: 1024 | ||||||
							
								
								
									
										1
									
								
								tests/components/camera_encoder/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/camera_encoder/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/camera_encoder/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/camera_encoder/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										7
									
								
								tests/components/nrf52/test.nrf52-adafruit.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/components/nrf52/test.nrf52-adafruit.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | nrf52: | ||||||
|  |   dfu: | ||||||
|  |     reset_pin: | ||||||
|  |       number: 14 | ||||||
|  |       inverted: true | ||||||
|  |       mode: | ||||||
|  |         output: true | ||||||
		Reference in New Issue
	
	Block a user