mirror of
https://github.com/esphome/esphome.git
synced 2025-09-06 21:32:21 +01:00
Merge branch 'hash_avoid_temp_heap_std_string' into integration
This commit is contained in:
@@ -89,6 +89,7 @@ esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/bytebuffer/* @clydebarrow
|
||||
esphome/components/camera/* @DT-art1 @bdraco
|
||||
esphome/components/camera_encoder/* @DT-art1
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @mreditor97
|
||||
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 };
|
||||
|
||||
/// 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.
|
||||
* Encapsulates the JPEG encoded data and it is shared among
|
||||
* all connected clients.
|
||||
@@ -43,6 +63,29 @@ class 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.
|
||||
* 1) API server starts and installs callback (add_image_callback)
|
||||
* 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,
|
||||
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,
|
||||
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 reg = addr | static_cast<uint8_t>(Mcp4461Commands::READ);
|
||||
uint16_t buf;
|
||||
if (!this->read_byte_16(reg, &buf)) {
|
||||
if (!this->read_16_(reg, &buf)) {
|
||||
this->error_code_ = MCP4461_STATUS_REGISTER_ERROR;
|
||||
this->mark_failed();
|
||||
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 >> 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 addr;
|
||||
@@ -205,7 +219,7 @@ uint16_t Mcp4461Component::read_wiper_level_(uint8_t wiper_idx) {
|
||||
}
|
||||
}
|
||||
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->status_set_warning();
|
||||
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);
|
||||
reg |= static_cast<uint8_t>(Mcp4461Commands::READ);
|
||||
uint16_t buf;
|
||||
if (this->read_byte_16(reg, &buf)) {
|
||||
if (this->read_16_(reg, &buf)) {
|
||||
return static_cast<uint8_t>(buf & 0x00ff);
|
||||
} else {
|
||||
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)) {
|
||||
return 0;
|
||||
}
|
||||
if (!this->read_byte_16(reg, &buf)) {
|
||||
if (!this->read_16_(reg, &buf)) {
|
||||
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
|
||||
this->status_set_warning();
|
||||
ESP_LOGW(TAG, "Error fetching EEPROM location value");
|
||||
|
@@ -96,6 +96,7 @@ class Mcp4461Component : public Component, public i2c::I2CDevice {
|
||||
|
||||
protected:
|
||||
friend class Mcp4461Wiper;
|
||||
bool read_16_(uint8_t address, uint16_t *buf);
|
||||
void update_write_protection_status_();
|
||||
uint8_t get_wiper_address_(uint8_t wiper);
|
||||
uint16_t read_wiper_level_(uint8_t wiper);
|
||||
|
@@ -2,7 +2,7 @@
|
||||
# Various configuration constants for MIPI displays
|
||||
# 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.display import CONF_SHOW_TEST_CARD, display_ns
|
||||
@@ -222,7 +222,7 @@ def delay(ms):
|
||||
|
||||
|
||||
class DriverChip:
|
||||
models = {}
|
||||
models: dict[str, Self] = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@@ -16,7 +16,6 @@ DriverChip(
|
||||
lane_bit_rate="750Mbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
reset_pin=27,
|
||||
initsequence=[
|
||||
(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),
|
||||
|
@@ -2,10 +2,13 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.zephyr import (
|
||||
copy_files as zephyr_copy_files,
|
||||
zephyr_add_pm_static,
|
||||
zephyr_add_prj_conf,
|
||||
zephyr_data,
|
||||
zephyr_set_core_data,
|
||||
zephyr_to_code,
|
||||
)
|
||||
@@ -18,6 +21,8 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_ID,
|
||||
CONF_RESET_PIN,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
@@ -90,18 +95,43 @@ def _detect_bootloader(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
nrf52_ns = cg.esphome_ns.namespace("nrf52")
|
||||
DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component)
|
||||
|
||||
CONF_DFU = "dfu"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
_detect_bootloader,
|
||||
set_core_data,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BOARD): cv.string_strict,
|
||||
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)
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
"""Convert the configuration to code."""
|
||||
@@ -136,6 +166,19 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
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:
|
||||
"""Copy files to the build directory."""
|
||||
|
@@ -2,6 +2,7 @@ BOOTLOADER_ADAFRUIT = "adafruit"
|
||||
BOOTLOADER_ADAFRUIT_NRF52_SD132 = "adafruit_nrf52_sd132"
|
||||
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6 = "adafruit_nrf52_sd140_v6"
|
||||
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7 = "adafruit_nrf52_sd140_v7"
|
||||
|
||||
EXTRA_ADC = [
|
||||
"VDD",
|
||||
"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";
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
SNTPComponent *SNTPComponent::instance = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
#endif
|
||||
|
||||
void SNTPComponent::setup() {
|
||||
#if defined(USE_ESP32)
|
||||
SNTPComponent::instance = this;
|
||||
if (esp_sntp_enabled()) {
|
||||
esp_sntp_stop();
|
||||
}
|
||||
@@ -25,6 +30,11 @@ void SNTPComponent::setup() {
|
||||
esp_sntp_setservername(i++, server.c_str());
|
||||
}
|
||||
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();
|
||||
#else
|
||||
sntp_stop();
|
||||
@@ -34,6 +44,14 @@ void SNTPComponent::setup() {
|
||||
for (auto &server : this->servers_) {
|
||||
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();
|
||||
#endif
|
||||
}
|
||||
@@ -46,7 +64,8 @@ void SNTPComponent::dump_config() {
|
||||
}
|
||||
void SNTPComponent::update() {
|
||||
#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()) {
|
||||
sntp_stop();
|
||||
this->has_time_ = false;
|
||||
@@ -55,23 +74,31 @@ void SNTPComponent::update() {
|
||||
#endif
|
||||
}
|
||||
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_)
|
||||
return;
|
||||
|
||||
this->time_synced();
|
||||
}
|
||||
|
||||
void SNTPComponent::time_synced() {
|
||||
auto time = this->now();
|
||||
if (!time.is_valid())
|
||||
this->has_time_ = time.is_valid();
|
||||
if (!this->has_time_)
|
||||
return;
|
||||
|
||||
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);
|
||||
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
|
||||
|
@@ -26,9 +26,16 @@ class SNTPComponent : public time::RealTimeClock {
|
||||
void update() override;
|
||||
void loop() override;
|
||||
|
||||
void time_synced();
|
||||
|
||||
protected:
|
||||
std::vector<std::string> servers_;
|
||||
bool has_time_{false};
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
private:
|
||||
static SNTPComponent *instance;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace sntp
|
||||
|
@@ -654,12 +654,14 @@ const char *get_disconnect_reason_str(uint8_t reason) {
|
||||
return "Association comeback time too long";
|
||||
case WIFI_REASON_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:
|
||||
return "No AP found with compatible security";
|
||||
case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD:
|
||||
return "No AP found in auth mode threshold";
|
||||
case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD:
|
||||
return "No AP found in RSSI threshold";
|
||||
#endif
|
||||
case WIFI_REASON_UNSPECIFIED:
|
||||
default:
|
||||
return "Unspecified";
|
||||
|
@@ -240,6 +240,10 @@
|
||||
#define USE_SOCKET_SELECT_SUPPORT
|
||||
#endif
|
||||
|
||||
#ifdef USE_NRF52
|
||||
#define USE_NRF52_DFU
|
||||
#endif
|
||||
|
||||
// Disabled feature flags
|
||||
// #define USE_BSEC // 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
|
||||
}
|
||||
|
||||
// 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
|
||||
std::string EntityBase::get_object_id() const {
|
||||
// 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.
|
||||
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 {
|
||||
static constexpr auto EMPTY_STRING = StringRef::from_lit("");
|
||||
// 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;
|
||||
}
|
||||
// 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
|
||||
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_; }
|
||||
|
||||
|
@@ -126,6 +126,9 @@ class EntityBase {
|
||||
virtual uint32_t hash_base() { return 0L; }
|
||||
void calc_object_id_();
|
||||
|
||||
/// Check if the object_id is dynamic (changes with MAC suffix)
|
||||
bool is_object_id_dynamic_() const;
|
||||
|
||||
StringRef name_;
|
||||
const char *object_id_c_str_{nullptr};
|
||||
#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;
|
||||
}
|
||||
|
||||
uint32_t fnv1_hash(const std::string &str) {
|
||||
uint32_t fnv1_hash(const char *str) {
|
||||
uint32_t hash = 2166136261UL;
|
||||
for (char c : str) {
|
||||
hash *= 16777619UL;
|
||||
hash ^= c;
|
||||
if (str) {
|
||||
while (*str) {
|
||||
hash *= 16777619UL;
|
||||
hash ^= *str++;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
/// 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.
|
||||
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