Merge branch 'dev' into heap_scheduler_stress_component

This commit is contained in:
J. Nick Koston 2025-07-06 22:56:18 -05:00 committed by GitHub
commit e8ea7825a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 396 additions and 265 deletions

View File

@ -87,6 +87,7 @@ esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid 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/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97 esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter

View File

@ -836,7 +836,7 @@ message ListEntitiesCameraResponse {
option (id) = 43; option (id) = 43;
option (base_class) = "InfoResponseProtoMessage"; option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ESP32_CAMERA"; option (ifdef) = "USE_CAMERA";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
@ -851,7 +851,7 @@ message ListEntitiesCameraResponse {
message CameraImageResponse { message CameraImageResponse {
option (id) = 44; option (id) = 44;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ESP32_CAMERA"; option (ifdef) = "USE_CAMERA";
fixed32 key = 1; fixed32 key = 1;
bytes data = 2; bytes data = 2;
@ -860,7 +860,7 @@ message CameraImageResponse {
message CameraImageRequest { message CameraImageRequest {
option (id) = 45; option (id) = 45;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_ESP32_CAMERA"; option (ifdef) = "USE_CAMERA";
option (no_delay) = true; option (no_delay) = true;
bool single = 1; bool single = 1;

View File

@ -38,8 +38,8 @@ static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2; static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
static const char *const TAG = "api.connection"; static const char *const TAG = "api.connection";
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
static const int ESP32_CAMERA_STOP_STREAM = 5000; static const int CAMERA_STOP_STREAM = 5000;
#endif #endif
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
@ -58,6 +58,11 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
#else #else
#error "No frame helper defined" #error "No frame helper defined"
#endif #endif
#ifdef USE_CAMERA
if (camera::Camera::instance() != nullptr) {
this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
}
#endif
} }
uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); } uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
@ -180,10 +185,10 @@ void APIConnection::loop() {
} }
} }
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) { if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
uint32_t to_send = std::min((size_t) MAX_PACKET_SIZE, this->image_reader_.available()); uint32_t to_send = std::min((size_t) MAX_PACKET_SIZE, this->image_reader_->available());
bool done = this->image_reader_.available() == to_send; bool done = this->image_reader_->available() == to_send;
uint32_t msg_size = 0; uint32_t msg_size = 0;
ProtoSize::add_fixed_field<4>(msg_size, 1, true); ProtoSize::add_fixed_field<4>(msg_size, 1, true);
// partial message size calculated manually since its a special case // partial message size calculated manually since its a special case
@ -193,18 +198,18 @@ void APIConnection::loop() {
auto buffer = this->create_buffer(msg_size); auto buffer = this->create_buffer(msg_size);
// fixed32 key = 1; // fixed32 key = 1;
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash()); buffer.encode_fixed32(1, camera::Camera::instance()->get_object_id_hash());
// bytes data = 2; // bytes data = 2;
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send); buffer.encode_bytes(2, this->image_reader_->peek_data_buffer(), to_send);
// bool done = 3; // bool done = 3;
buffer.encode_bool(3, done); buffer.encode_bool(3, done);
bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE); bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE);
if (success) { if (success) {
this->image_reader_.consume_data(to_send); this->image_reader_->consume_data(to_send);
if (done) { if (done) {
this->image_reader_.return_image(); this->image_reader_->return_image();
} }
} }
} }
@ -1112,36 +1117,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
} }
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
if (!this->flags_.state_subscription) if (!this->flags_.state_subscription)
return; return;
if (this->image_reader_.available()) if (!this->image_reader_)
return; return;
if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) || if (this->image_reader_->available())
image->was_requested_by(esphome::esp32_camera::IDLE)) return;
this->image_reader_.set_image(std::move(image)); if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE))
this->image_reader_->set_image(std::move(image));
} }
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
auto *camera = static_cast<esp32_camera::ESP32Camera *>(entity); auto *camera = static_cast<camera::Camera *>(entity);
ListEntitiesCameraResponse msg; ListEntitiesCameraResponse msg;
msg.unique_id = get_default_unique_id("camera", camera); msg.unique_id = get_default_unique_id("camera", camera);
fill_entity_info_base(camera, msg); fill_entity_info_base(camera, msg);
return encode_message_to_buffer(msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::camera_image(const CameraImageRequest &msg) { void APIConnection::camera_image(const CameraImageRequest &msg) {
if (esp32_camera::global_esp32_camera == nullptr) if (camera::Camera::instance() == nullptr)
return; return;
if (msg.single) if (msg.single)
esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER); camera::Camera::instance()->request_image(esphome::camera::API_REQUESTER);
if (msg.stream) { if (msg.stream) {
esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER); camera::Camera::instance()->start_stream(esphome::camera::API_REQUESTER);
App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() { App.scheduler.set_timeout(this->parent_, "api_camera_stop_stream", CAMERA_STOP_STREAM,
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER); []() { camera::Camera::instance()->stop_stream(esphome::camera::API_REQUESTER); });
});
} }
} }
#endif #endif

View File

@ -60,8 +60,8 @@ class APIConnection : public APIServerConnection {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor); bool send_text_sensor_state(text_sensor::TextSensor *text_sensor);
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image); void set_camera_state(std::shared_ptr<camera::CameraImage> image);
void camera_image(const CameraImageRequest &msg) override; void camera_image(const CameraImageRequest &msg) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
@ -425,7 +425,7 @@ class APIConnection : public APIServerConnection {
static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
#endif #endif
@ -455,8 +455,8 @@ class APIConnection : public APIServerConnection {
// These contain vectors/pointers internally, so putting them early ensures good alignment // These contain vectors/pointers internally, so putting them early ensures good alignment
InitialStateIterator initial_state_iterator_; InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_; ListEntitiesIterator list_entities_iterator_;
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
esp32_camera::CameraImageReader image_reader_; std::unique_ptr<camera::CameraImageReader> image_reader_;
#endif #endif
// Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned) // Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned)

View File

@ -614,20 +614,14 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::OK; return APIError::OK;
} }
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
// Resize to include MAC space (required for Noise encryption) // Resize to include MAC space (required for Noise encryption)
raw_buffer->resize(raw_buffer->size() + frame_footer_size_); buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
PacketInfo packet{type, 0,
// Use write_protobuf_packets with a single packet static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
std::vector<PacketInfo> packets; return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
packets.emplace_back(type, 0, payload_len);
return write_protobuf_packets(buffer, packets);
} }
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) { APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
APIError aerr = state_action_(); APIError aerr = state_action_();
if (aerr != APIError::OK) { if (aerr != APIError::OK) {
return aerr; return aerr;
@ -642,18 +636,15 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
} }
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
this->reusable_iovs_.clear(); this->reusable_iovs_.clear();
this->reusable_iovs_.reserve(packets.size()); this->reusable_iovs_.reserve(packets.size());
// We need to encrypt each packet in place // We need to encrypt each packet in place
for (const auto &packet : packets) { for (const auto &packet : packets) {
uint16_t type = packet.message_type;
uint16_t offset = packet.offset;
uint16_t payload_len = packet.payload_size;
uint16_t msg_len = 4 + payload_len; // type(2) + data_len(2) + payload
// The buffer already has padding at offset // The buffer already has padding at offset
uint8_t *buf_start = raw_buffer->data() + offset; uint8_t *buf_start = buffer_data + packet.offset;
// Write noise header // Write noise header
buf_start[0] = 0x01; // indicator buf_start[0] = 0x01; // indicator
@ -661,10 +652,10 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
// Write message header (to be encrypted) // Write message header (to be encrypted)
const uint8_t msg_offset = 3; const uint8_t msg_offset = 3;
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte buf_start[msg_offset + 2] = static_cast<uint8_t>(packet.payload_size >> 8); // data_len high byte
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte
// payload data is already in the buffer starting at offset + 7 // payload data is already in the buffer starting at offset + 7
// Make sure we have space for MAC // Make sure we have space for MAC
@ -673,7 +664,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
// Encrypt the message in place // Encrypt the message in place
NoiseBuffer mbuf; NoiseBuffer mbuf;
noise_buffer_init(mbuf); noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_); noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size,
4 + packet.payload_size + frame_footer_size_);
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf); int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
if (err != 0) { if (err != 0) {
@ -683,14 +675,12 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
} }
// Fill in the encrypted size // Fill in the encrypted size
buf_start[1] = (uint8_t) (mbuf.size >> 8); buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
buf_start[2] = (uint8_t) mbuf.size; buf_start[2] = static_cast<uint8_t>(mbuf.size);
// Add iovec for this encrypted packet // Add iovec for this encrypted packet
struct iovec iov; this->reusable_iovs_.push_back(
iov.iov_base = buf_start; {buf_start, static_cast<size_t>(3 + mbuf.size)}); // indicator + size + encrypted data
iov.iov_len = 3 + mbuf.size; // indicator + size + encrypted data
this->reusable_iovs_.push_back(iov);
} }
// Send all encrypted packets in one writev call // Send all encrypted packets in one writev call
@ -1029,18 +1019,11 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
return APIError::OK; return APIError::OK;
} }
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_); return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
// Use write_protobuf_packets with a single packet
std::vector<PacketInfo> packets;
packets.emplace_back(type, 0, payload_len);
return write_protobuf_packets(buffer, packets);
} }
APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
const std::vector<PacketInfo> &packets) {
if (state_ != State::DATA) { if (state_ != State::DATA) {
return APIError::BAD_STATE; return APIError::BAD_STATE;
} }
@ -1050,17 +1033,15 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
} }
std::vector<uint8_t> *raw_buffer = buffer.get_buffer(); std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
this->reusable_iovs_.clear(); this->reusable_iovs_.clear();
this->reusable_iovs_.reserve(packets.size()); this->reusable_iovs_.reserve(packets.size());
for (const auto &packet : packets) { for (const auto &packet : packets) {
uint16_t type = packet.message_type;
uint16_t offset = packet.offset;
uint16_t payload_len = packet.payload_size;
// Calculate varint sizes for header layout // Calculate varint sizes for header layout
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len)); uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type)); uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
uint8_t total_header_len = 1 + size_varint_len + type_varint_len; uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
// Calculate where to start writing the header // Calculate where to start writing the header
@ -1088,23 +1069,20 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
// //
// The message starts at offset + frame_header_padding_ // The message starts at offset + frame_header_padding_
// So we write the header starting at offset + frame_header_padding_ - total_header_len // So we write the header starting at offset + frame_header_padding_ - total_header_len
uint8_t *buf_start = raw_buffer->data() + offset; uint8_t *buf_start = buffer_data + packet.offset;
uint32_t header_offset = frame_header_padding_ - total_header_len; uint32_t header_offset = frame_header_padding_ - total_header_len;
// Write the plaintext header // Write the plaintext header
buf_start[header_offset] = 0x00; // indicator buf_start[header_offset] = 0x00; // indicator
// Encode size varint directly into buffer // Encode varints directly into buffer
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len); ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
ProtoVarInt(packet.message_type)
// Encode type varint directly into buffer .encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
// Add iovec for this packet (header + payload) // Add iovec for this packet (header + payload)
struct iovec iov; this->reusable_iovs_.push_back(
iov.iov_base = buf_start + header_offset; {buf_start + header_offset, static_cast<size_t>(total_header_len + packet.payload_size)});
iov.iov_len = total_header_len + payload_len;
this->reusable_iovs_.push_back(iov);
} }
// Send all packets in one writev call // Send all packets in one writev call

View File

@ -2,6 +2,7 @@
#include <cstdint> #include <cstdint>
#include <deque> #include <deque>
#include <limits> #include <limits>
#include <span>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -101,7 +102,7 @@ class APIFrameHelper {
// Write multiple protobuf packets in a single operation // Write multiple protobuf packets in a single operation
// packets contains (message_type, offset, length) for each message in the buffer // packets contains (message_type, offset, length) for each message in the buffer
// The buffer contains all messages with appropriate padding before each // The buffer contains all messages with appropriate padding before each
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) = 0; virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
// Get the frame header padding required by this protocol // Get the frame header padding required by this protocol
virtual uint8_t frame_header_padding() = 0; virtual uint8_t frame_header_padding() = 0;
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
@ -194,7 +195,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
// Get the frame header padding required by this protocol // Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; } uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
@ -248,7 +249,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
uint8_t frame_header_padding() override { return frame_header_padding_; } uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; } uint8_t frame_footer_size() override { return frame_footer_size_; }

View File

@ -2216,7 +2216,7 @@ void ExecuteServiceRequest::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false); ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_repeated_message(total_size, 1, this->args); ProtoSize::add_repeated_message(total_size, 1, this->args);
} }
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 5: { case 5: {

View File

@ -1273,7 +1273,7 @@ class ExecuteServiceRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
}; };
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
class ListEntitiesCameraResponse : public InfoResponseProtoMessage { class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint16_t MESSAGE_TYPE = 43; static constexpr uint16_t MESSAGE_TYPE = 43;

View File

@ -1890,7 +1890,7 @@ void ExecuteServiceRequest::dump_to(std::string &out) const {
} }
out.append("}"); out.append("}");
} }
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
void ListEntitiesCameraResponse::dump_to(std::string &out) const { void ListEntitiesCameraResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesCameraResponse {\n"); out.append("ListEntitiesCameraResponse {\n");

View File

@ -204,7 +204,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_execute_service_request(msg); this->on_execute_service_request(msg);
break; break;
} }
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
case 45: { case 45: {
CameraImageRequest msg; CameraImageRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
@ -682,7 +682,7 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest &
} }
} }
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
this->camera_image(msg); this->camera_image(msg);

View File

@ -71,7 +71,7 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
virtual void on_camera_image_request(const CameraImageRequest &value){}; virtual void on_camera_image_request(const CameraImageRequest &value){};
#endif #endif
@ -223,7 +223,7 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_BUTTON #ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0; virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
virtual void camera_image(const CameraImageRequest &msg) = 0; virtual void camera_image(const CameraImageRequest &msg) = 0;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
@ -340,7 +340,7 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_BUTTON #ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override; void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
void on_camera_image_request(const CameraImageRequest &msg) override; void on_camera_image_request(const CameraImageRequest &msg) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE

View File

@ -119,15 +119,14 @@ void APIServer::setup() {
} }
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback( camera::Camera::instance()->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) {
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) { for (auto &c : this->clients_) {
for (auto &c : this->clients_) { if (!c->flags_.remove)
if (!c->flags_.remove) c->set_camera_state(image);
c->set_camera_state(image); }
} });
});
} }
#endif #endif
} }

View File

@ -40,8 +40,8 @@ LIST_ENTITIES_HANDLER(lock, lock::Lock, ListEntitiesLockResponse)
#ifdef USE_VALVE #ifdef USE_VALVE
LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse) LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse)
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
LIST_ENTITIES_HANDLER(camera, esp32_camera::ESP32Camera, ListEntitiesCameraResponse) LIST_ENTITIES_HANDLER(camera, camera::Camera, ListEntitiesCameraResponse)
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
LIST_ENTITIES_HANDLER(climate, climate::Climate, ListEntitiesClimateResponse) LIST_ENTITIES_HANDLER(climate, climate::Climate, ListEntitiesClimateResponse)

View File

@ -45,8 +45,8 @@ class ListEntitiesIterator : public ComponentIterator {
bool on_text_sensor(text_sensor::TextSensor *entity) override; bool on_text_sensor(text_sensor::TextSensor *entity) override;
#endif #endif
bool on_service(UserServiceDescriptor *service) override; bool on_service(UserServiceDescriptor *service) override;
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
bool on_camera(esp32_camera::ESP32Camera *entity) override; bool on_camera(camera::Camera *entity) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool on_climate(climate::Climate *entity) override; bool on_climate(climate::Climate *entity) override;

View File

@ -170,7 +170,7 @@ int BluetoothProxy::get_bluetooth_connections_free() {
void BluetoothProxy::loop() { void BluetoothProxy::loop() {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) { if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->get_address() != 0) { if (connection->get_address() != 0 && !connection->disconnect_pending()) {
connection->disconnect(); connection->disconnect();
} }
} }

View File

@ -0,0 +1 @@
CODEOWNERS = ["@DT-art1", "@bdraco"]

View File

@ -0,0 +1,22 @@
#include "camera.h"
namespace esphome {
namespace camera {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Camera *Camera::global_camera = nullptr;
Camera::Camera() {
if (global_camera != nullptr) {
this->status_set_error("Multiple cameras are configured, but only one is supported.");
this->mark_failed();
return;
}
global_camera = this;
}
Camera *Camera::instance() { return global_camera; }
} // namespace camera
} // namespace esphome

View File

@ -0,0 +1,80 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace camera {
/** Different sources for filtering.
* IDLE: Camera requests to send an image to the API.
* API_REQUESTER: API requests a new image.
* WEB_REQUESTER: ESP32 web server request an image. Ignored by API.
*/
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
/** Abstract camera image base class.
* Encapsulates the JPEG encoded data and it is shared among
* all connected clients.
*/
class CameraImage {
public:
virtual uint8_t *get_data_buffer() = 0;
virtual size_t get_data_length() = 0;
virtual bool was_requested_by(CameraRequester requester) const = 0;
virtual ~CameraImage() {}
};
/** Abstract image reader base class.
* Keeps track of the data offset of the camera image and
* how many bytes are remaining to read. When the image
* is returned, the shared_ptr is reset and the camera can
* reuse the memory of the camera image.
*/
class CameraImageReader {
public:
virtual void set_image(std::shared_ptr<CameraImage> image) = 0;
virtual size_t available() const = 0;
virtual uint8_t *peek_data_buffer() = 0;
virtual void consume_data(size_t consumed) = 0;
virtual void return_image() = 0;
virtual ~CameraImageReader() {}
};
/** 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.
* 2) New API client connects and creates a new image reader (create_image_reader).
* 3) API connection receives protobuf CameraImageRequest and calls request_image.
* 3.a) API connection receives protobuf CameraImageRequest and calls start_stream.
* 4) Camera implementation provides JPEG data in the CameraImage and calls callback.
* 5) API connection sets the image in the image reader.
* 6) API connection consumes data from the image reader and returns the image when finished.
* 7.a) Camera captures a new image and continues with 4) until start_stream is called.
*/
class Camera : public EntityBase, public Component {
public:
Camera();
// Camera implementation invokes callback to publish a new image.
virtual void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) = 0;
/// Returns a new camera image reader that keeps track of the JPEG data in the camera image.
virtual CameraImageReader *create_image_reader() = 0;
// Connection, camera or web server requests one new JPEG image.
virtual void request_image(CameraRequester requester) = 0;
// Connection, camera or web server requests a stream of images.
virtual void start_stream(CameraRequester requester) = 0;
// Connection or web server stops the previously started stream.
virtual void stop_stream(CameraRequester requester) = 0;
virtual ~Camera() {}
/// The singleton instance of the camera implementation.
static Camera *instance();
protected:
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static Camera *global_camera;
};
} // namespace camera
} // namespace esphome

View File

@ -51,7 +51,7 @@ enum IoCapability {
IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, IO_CAP_KBDISP = ESP_IO_CAP_KBDISP,
}; };
enum BLEComponentState { enum BLEComponentState : uint8_t {
/** Nothing has been initialized yet. */ /** Nothing has been initialized yet. */
BLE_COMPONENT_STATE_OFF = 0, BLE_COMPONENT_STATE_OFF = 0,
/** BLE should be disabled on next loop. */ /** BLE should be disabled on next loop. */
@ -141,21 +141,31 @@ class ESP32BLE : public Component {
private: private:
template<typename... Args> friend void enqueue_ble_event(Args... args); template<typename... Args> friend void enqueue_ble_event(Args... args);
// Vectors (12 bytes each on 32-bit, naturally aligned to 4 bytes)
std::vector<GAPEventHandler *> gap_event_handlers_; std::vector<GAPEventHandler *> gap_event_handlers_;
std::vector<GAPScanEventHandler *> gap_scan_event_handlers_; std::vector<GAPScanEventHandler *> gap_scan_event_handlers_;
std::vector<GATTcEventHandler *> gattc_event_handlers_; std::vector<GATTcEventHandler *> gattc_event_handlers_;
std::vector<GATTsEventHandler *> gatts_event_handlers_; std::vector<GATTsEventHandler *> gatts_event_handlers_;
std::vector<BLEStatusEventHandler *> ble_status_event_handlers_; std::vector<BLEStatusEventHandler *> ble_status_event_handlers_;
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
// Large objects (size depends on template parameters, but typically aligned to 4 bytes)
esphome::LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_; esphome::LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
esphome::EventPool<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_event_pool_; esphome::EventPool<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_event_pool_;
BLEAdvertising *advertising_{};
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // optional<string> (typically 16+ bytes on 32-bit, aligned to 4 bytes)
uint32_t advertising_cycle_time_{};
bool enable_on_boot_{};
optional<std::string> name_; optional<std::string> name_;
uint16_t appearance_{0};
// 4-byte aligned members
BLEAdvertising *advertising_{}; // 4 bytes (pointer)
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum)
uint32_t advertising_cycle_time_{}; // 4 bytes
// 2-byte aligned members
uint16_t appearance_{0}; // 2 bytes
// 1-byte aligned members (grouped together to minimize padding)
BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; // 1 byte (uint8_t enum)
bool enable_on_boot_{}; // 1 byte
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -23,7 +23,7 @@ from esphome.core.entity_helpers import setup_entity
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["psram"] AUTO_LOAD = ["camera", "psram"]
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
@ -283,6 +283,7 @@ SETTERS = {
async def to_code(config): async def to_code(config):
cg.add_define("USE_CAMERA")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await setup_entity(var, config, "camera") await setup_entity(var, config, "camera")
await cg.register_component(var, config) await cg.register_component(var, config)

View File

@ -14,8 +14,6 @@ static const char *const TAG = "esp32_camera";
/* ---------------- public API (derivated) ---------------- */ /* ---------------- public API (derivated) ---------------- */
void ESP32Camera::setup() { void ESP32Camera::setup() {
global_esp32_camera = this;
#ifdef USE_I2C #ifdef USE_I2C
if (this->i2c_bus_ != nullptr) { if (this->i2c_bus_ != nullptr) {
this->config_.sccb_i2c_port = this->i2c_bus_->get_port(); this->config_.sccb_i2c_port = this->i2c_bus_->get_port();
@ -43,7 +41,7 @@ void ESP32Camera::setup() {
xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
"framebuffer_task", // name "framebuffer_task", // name
1024, // stack size 1024, // stack size
nullptr, // task pv params this, // task pv params
1, // priority 1, // priority
nullptr, // handle nullptr, // handle
1 // core 1 // core
@ -176,7 +174,7 @@ void ESP32Camera::loop() {
const uint32_t now = App.get_loop_component_start_time(); const uint32_t now = App.get_loop_component_start_time();
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
this->last_idle_request_ = now; this->last_idle_request_ = now;
this->request_image(IDLE); this->request_image(camera::IDLE);
} }
// Check if we should fetch a new image // Check if we should fetch a new image
@ -202,7 +200,7 @@ void ESP32Camera::loop() {
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY); xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
return; return;
} }
this->current_image_ = std::make_shared<CameraImage>(fb, this->single_requesters_ | this->stream_requesters_); this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
ESP_LOGD(TAG, "Got Image: len=%u", fb->len); ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
this->new_image_callback_.call(this->current_image_); this->new_image_callback_.call(this->current_image_);
@ -225,8 +223,6 @@ ESP32Camera::ESP32Camera() {
this->config_.fb_count = 1; this->config_.fb_count = 1;
this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY; this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
this->config_.fb_location = CAMERA_FB_IN_PSRAM; this->config_.fb_location = CAMERA_FB_IN_PSRAM;
global_esp32_camera = this;
} }
/* ---------------- setters ---------------- */ /* ---------------- setters ---------------- */
@ -356,7 +352,7 @@ void ESP32Camera::set_frame_buffer_count(uint8_t fb_count) {
} }
/* ---------------- public API (specific) ---------------- */ /* ---------------- public API (specific) ---------------- */
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) { void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<camera::CameraImage>)> &&callback) {
this->new_image_callback_.add(std::move(callback)); this->new_image_callback_.add(std::move(callback));
} }
void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) { void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) {
@ -365,15 +361,16 @@ void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) {
void ESP32Camera::add_stream_stop_callback(std::function<void()> &&callback) { void ESP32Camera::add_stream_stop_callback(std::function<void()> &&callback) {
this->stream_stop_callback_.add(std::move(callback)); this->stream_stop_callback_.add(std::move(callback));
} }
void ESP32Camera::start_stream(CameraRequester requester) { void ESP32Camera::start_stream(camera::CameraRequester requester) {
this->stream_start_callback_.call(); this->stream_start_callback_.call();
this->stream_requesters_ |= (1U << requester); this->stream_requesters_ |= (1U << requester);
} }
void ESP32Camera::stop_stream(CameraRequester requester) { void ESP32Camera::stop_stream(camera::CameraRequester requester) {
this->stream_stop_callback_.call(); this->stream_stop_callback_.call();
this->stream_requesters_ &= ~(1U << requester); this->stream_requesters_ &= ~(1U << requester);
} }
void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); } void ESP32Camera::request_image(camera::CameraRequester requester) { this->single_requesters_ |= (1U << requester); }
camera::CameraImageReader *ESP32Camera::create_image_reader() { return new ESP32CameraImageReader; }
void ESP32Camera::update_camera_parameters() { void ESP32Camera::update_camera_parameters() {
sensor_t *s = esp_camera_sensor_get(); sensor_t *s = esp_camera_sensor_get();
/* update image */ /* update image */
@ -402,39 +399,39 @@ void ESP32Camera::update_camera_parameters() {
bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; } bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; }
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; } bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
void ESP32Camera::framebuffer_task(void *pv) { void ESP32Camera::framebuffer_task(void *pv) {
ESP32Camera *that = (ESP32Camera *) pv;
while (true) { while (true) {
camera_fb_t *framebuffer = esp_camera_fb_get(); camera_fb_t *framebuffer = esp_camera_fb_get();
xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
// return is no-op for config with 1 fb // return is no-op for config with 1 fb
xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
esp_camera_fb_return(framebuffer); esp_camera_fb_return(framebuffer);
} }
} }
ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) /* ---------------- ESP32CameraImageReader class ----------- */
void ESP32CameraImageReader::set_image(std::shared_ptr<camera::CameraImage> image) {
/* ---------------- CameraImageReader class ---------------- */ this->image_ = std::static_pointer_cast<ESP32CameraImage>(image);
void CameraImageReader::set_image(std::shared_ptr<CameraImage> image) {
this->image_ = std::move(image);
this->offset_ = 0; this->offset_ = 0;
} }
size_t CameraImageReader::available() const { size_t ESP32CameraImageReader::available() const {
if (!this->image_) if (!this->image_)
return 0; return 0;
return this->image_->get_data_length() - this->offset_; return this->image_->get_data_length() - this->offset_;
} }
void CameraImageReader::return_image() { this->image_.reset(); } void ESP32CameraImageReader::return_image() { this->image_.reset(); }
void CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; } void ESP32CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; }
uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; } uint8_t *ESP32CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; }
/* ---------------- CameraImage class ---------------- */ /* ---------------- ESP32CameraImage class ----------- */
CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {} ESP32CameraImage::ESP32CameraImage(camera_fb_t *buffer, uint8_t requesters)
: buffer_(buffer), requesters_(requesters) {}
camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; } camera_fb_t *ESP32CameraImage::get_raw_buffer() { return this->buffer_; }
uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; } uint8_t *ESP32CameraImage::get_data_buffer() { return this->buffer_->buf; }
size_t CameraImage::get_data_length() { return this->buffer_->len; } size_t ESP32CameraImage::get_data_length() { return this->buffer_->len; }
bool CameraImage::was_requested_by(CameraRequester requester) const { bool ESP32CameraImage::was_requested_by(camera::CameraRequester requester) const {
return (this->requesters_ & (1 << requester)) != 0; return (this->requesters_ & (1 << requester)) != 0;
} }

View File

@ -7,7 +7,7 @@
#include <freertos/queue.h> #include <freertos/queue.h>
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/entity_base.h" #include "esphome/components/camera/camera.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#ifdef USE_I2C #ifdef USE_I2C
@ -19,9 +19,6 @@ namespace esp32_camera {
class ESP32Camera; class ESP32Camera;
/* ---------------- enum classes ---------------- */
enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER };
enum ESP32CameraFrameSize { enum ESP32CameraFrameSize {
ESP32_CAMERA_SIZE_160X120, // QQVGA ESP32_CAMERA_SIZE_160X120, // QQVGA
ESP32_CAMERA_SIZE_176X144, // QCIF ESP32_CAMERA_SIZE_176X144, // QCIF
@ -77,13 +74,13 @@ enum ESP32SpecialEffect {
}; };
/* ---------------- CameraImage class ---------------- */ /* ---------------- CameraImage class ---------------- */
class CameraImage { class ESP32CameraImage : public camera::CameraImage {
public: public:
CameraImage(camera_fb_t *buffer, uint8_t requester); ESP32CameraImage(camera_fb_t *buffer, uint8_t requester);
camera_fb_t *get_raw_buffer(); camera_fb_t *get_raw_buffer();
uint8_t *get_data_buffer(); uint8_t *get_data_buffer() override;
size_t get_data_length(); size_t get_data_length() override;
bool was_requested_by(CameraRequester requester) const; bool was_requested_by(camera::CameraRequester requester) const override;
protected: protected:
camera_fb_t *buffer_; camera_fb_t *buffer_;
@ -96,21 +93,21 @@ struct CameraImageData {
}; };
/* ---------------- CameraImageReader class ---------------- */ /* ---------------- CameraImageReader class ---------------- */
class CameraImageReader { class ESP32CameraImageReader : public camera::CameraImageReader {
public: public:
void set_image(std::shared_ptr<CameraImage> image); void set_image(std::shared_ptr<camera::CameraImage> image) override;
size_t available() const; size_t available() const override;
uint8_t *peek_data_buffer(); uint8_t *peek_data_buffer() override;
void consume_data(size_t consumed); void consume_data(size_t consumed) override;
void return_image(); void return_image() override;
protected: protected:
std::shared_ptr<CameraImage> image_; std::shared_ptr<ESP32CameraImage> image_;
size_t offset_{0}; size_t offset_{0};
}; };
/* ---------------- ESP32Camera class ---------------- */ /* ---------------- ESP32Camera class ---------------- */
class ESP32Camera : public EntityBase, public Component { class ESP32Camera : public camera::Camera {
public: public:
ESP32Camera(); ESP32Camera();
@ -162,14 +159,15 @@ class ESP32Camera : public EntityBase, public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
/* public API (specific) */ /* public API (specific) */
void start_stream(CameraRequester requester); void start_stream(camera::CameraRequester requester) override;
void stop_stream(CameraRequester requester); void stop_stream(camera::CameraRequester requester) override;
void request_image(CameraRequester requester); void request_image(camera::CameraRequester requester) override;
void update_camera_parameters(); void update_camera_parameters();
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback); void add_image_callback(std::function<void(std::shared_ptr<camera::CameraImage>)> &&callback) override;
void add_stream_start_callback(std::function<void()> &&callback); void add_stream_start_callback(std::function<void()> &&callback);
void add_stream_stop_callback(std::function<void()> &&callback); void add_stream_stop_callback(std::function<void()> &&callback);
camera::CameraImageReader *create_image_reader() override;
protected: protected:
/* internal methods */ /* internal methods */
@ -206,12 +204,12 @@ class ESP32Camera : public EntityBase, public Component {
uint32_t idle_update_interval_{15000}; uint32_t idle_update_interval_{15000};
esp_err_t init_error_{ESP_OK}; esp_err_t init_error_{ESP_OK};
std::shared_ptr<CameraImage> current_image_; std::shared_ptr<ESP32CameraImage> current_image_;
uint8_t single_requesters_{0}; uint8_t single_requesters_{0};
uint8_t stream_requesters_{0}; uint8_t stream_requesters_{0};
QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_get_queue_;
QueueHandle_t framebuffer_return_queue_; QueueHandle_t framebuffer_return_queue_;
CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_{}; CallbackManager<void(std::shared_ptr<camera::CameraImage>)> new_image_callback_{};
CallbackManager<void()> stream_start_callback_{}; CallbackManager<void()> stream_start_callback_{};
CallbackManager<void()> stream_stop_callback_{}; CallbackManager<void()> stream_stop_callback_{};
@ -222,13 +220,10 @@ class ESP32Camera : public EntityBase, public Component {
#endif // USE_I2C #endif // USE_I2C
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern ESP32Camera *global_esp32_camera;
class ESP32CameraImageTrigger : public Trigger<CameraImageData> { class ESP32CameraImageTrigger : public Trigger<CameraImageData> {
public: public:
explicit ESP32CameraImageTrigger(ESP32Camera *parent) { explicit ESP32CameraImageTrigger(ESP32Camera *parent) {
parent->add_image_callback([this](const std::shared_ptr<esp32_camera::CameraImage> &image) { parent->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) {
CameraImageData camera_image_data{}; CameraImageData camera_image_data{};
camera_image_data.length = image->get_data_length(); camera_image_data.length = image->get_data_length();
camera_image_data.data = image->get_data_buffer(); camera_image_data.data = image->get_data_buffer();

View File

@ -3,7 +3,8 @@ import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODE, CONF_PORT from esphome.const import CONF_ID, CONF_MODE, CONF_PORT
CODEOWNERS = ["@ayufan"] CODEOWNERS = ["@ayufan"]
DEPENDENCIES = ["esp32_camera", "network"] AUTO_LOAD = ["camera"]
DEPENDENCIES = ["network"]
MULTI_CONF = True MULTI_CONF = True
esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server")

View File

@ -40,7 +40,7 @@ CameraWebServer::CameraWebServer() {}
CameraWebServer::~CameraWebServer() {} CameraWebServer::~CameraWebServer() {}
void CameraWebServer::setup() { void CameraWebServer::setup() {
if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) { if (!camera::Camera::instance() || camera::Camera::instance()->is_failed()) {
this->mark_failed(); this->mark_failed();
return; return;
} }
@ -67,8 +67,8 @@ void CameraWebServer::setup() {
httpd_register_uri_handler(this->httpd_, &uri); httpd_register_uri_handler(this->httpd_, &uri);
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) { camera::Camera::instance()->add_image_callback([this](std::shared_ptr<camera::CameraImage> image) {
if (this->running_ && image->was_requested_by(esp32_camera::WEB_REQUESTER)) { if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) {
this->image_ = std::move(image); this->image_ = std::move(image);
xSemaphoreGive(this->semaphore_); xSemaphoreGive(this->semaphore_);
} }
@ -108,8 +108,8 @@ void CameraWebServer::loop() {
} }
} }
std::shared_ptr<esphome::esp32_camera::CameraImage> CameraWebServer::wait_for_image_() { std::shared_ptr<esphome::camera::CameraImage> CameraWebServer::wait_for_image_() {
std::shared_ptr<esphome::esp32_camera::CameraImage> image; std::shared_ptr<esphome::camera::CameraImage> image;
image.swap(this->image_); image.swap(this->image_);
if (!image) { if (!image) {
@ -172,7 +172,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
uint32_t last_frame = millis(); uint32_t last_frame = millis();
uint32_t frames = 0; uint32_t frames = 0;
esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::WEB_REQUESTER); camera::Camera::instance()->start_stream(esphome::camera::WEB_REQUESTER);
while (res == ESP_OK && this->running_) { while (res == ESP_OK && this->running_) {
auto image = this->wait_for_image_(); auto image = this->wait_for_image_();
@ -205,7 +205,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR)); res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
} }
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER); camera::Camera::instance()->stop_stream(esphome::camera::WEB_REQUESTER);
ESP_LOGI(TAG, "STREAM: closed. Frames: %" PRIu32, frames); ESP_LOGI(TAG, "STREAM: closed. Frames: %" PRIu32, frames);
@ -215,7 +215,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) { esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
esp_err_t res = ESP_OK; esp_err_t res = ESP_OK;
esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::WEB_REQUESTER); camera::Camera::instance()->request_image(esphome::camera::WEB_REQUESTER);
auto image = this->wait_for_image_(); auto image = this->wait_for_image_();

View File

@ -6,7 +6,7 @@
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/semphr.h> #include <freertos/semphr.h>
#include "esphome/components/esp32_camera/esp32_camera.h" #include "esphome/components/camera/camera.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
@ -32,7 +32,7 @@ class CameraWebServer : public Component {
void loop() override; void loop() override;
protected: protected:
std::shared_ptr<esphome::esp32_camera::CameraImage> wait_for_image_(); std::shared_ptr<camera::CameraImage> wait_for_image_();
esp_err_t handler_(struct httpd_req *req); esp_err_t handler_(struct httpd_req *req);
esp_err_t streaming_handler_(struct httpd_req *req); esp_err_t streaming_handler_(struct httpd_req *req);
esp_err_t snapshot_handler_(struct httpd_req *req); esp_err_t snapshot_handler_(struct httpd_req *req);
@ -40,7 +40,7 @@ class CameraWebServer : public Component {
uint16_t port_{0}; uint16_t port_{0};
void *httpd_{nullptr}; void *httpd_{nullptr};
SemaphoreHandle_t semaphore_; SemaphoreHandle_t semaphore_;
std::shared_ptr<esphome::esp32_camera::CameraImage> image_; std::shared_ptr<camera::CameraImage> image_;
bool running_{false}; bool running_{false};
Mode mode_{STREAM}; Mode mode_{STREAM};
}; };

View File

@ -252,7 +252,7 @@ class MQTTBackendESP32 final : public MQTTBackend {
#if defined(USE_MQTT_IDF_ENQUEUE) #if defined(USE_MQTT_IDF_ENQUEUE)
static void esphome_mqtt_task(void *params); static void esphome_mqtt_task(void *params);
EventPool<struct QueueElement, MQTT_QUEUE_LENGTH> mqtt_event_pool_; EventPool<struct QueueElement, MQTT_QUEUE_LENGTH> mqtt_event_pool_;
LockFreeQueue<struct QueueElement, MQTT_QUEUE_LENGTH> mqtt_queue_; NotifyingLockFreeQueue<struct QueueElement, MQTT_QUEUE_LENGTH> mqtt_queue_;
TaskHandle_t task_handle_{nullptr}; TaskHandle_t task_handle_{nullptr};
bool enqueue_(MqttQueueTypeT type, const char *topic, int qos = 0, bool retain = false, const char *payload = NULL, bool enqueue_(MqttQueueTypeT type, const char *topic, int qos = 0, bool retain = false, const char *payload = NULL,
size_t len = 0); size_t len = 0);

View File

@ -58,7 +58,7 @@ void SCD4XComponent::setup() {
} }
// If pressure compensation available use it, else use altitude // If pressure compensation available use it, else use altitude
if (this->ambient_pressure_compensation_) { if (this->ambient_pressure_) {
if (!this->update_ambient_pressure_compensation_(this->ambient_pressure_)) { if (!this->update_ambient_pressure_compensation_(this->ambient_pressure_)) {
ESP_LOGE(TAG, "Error setting ambient pressure compensation"); ESP_LOGE(TAG, "Error setting ambient pressure compensation");
this->error_code_ = MEASUREMENT_INIT_FAILED; this->error_code_ = MEASUREMENT_INIT_FAILED;
@ -137,7 +137,7 @@ void SCD4XComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Dynamic ambient pressure compensation using '%s'", ESP_LOGCONFIG(TAG, " Dynamic ambient pressure compensation using '%s'",
this->ambient_pressure_source_->get_name().c_str()); this->ambient_pressure_source_->get_name().c_str());
} else { } else {
if (this->ambient_pressure_compensation_) { if (this->ambient_pressure_) {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Altitude compensation disabled\n" " Altitude compensation disabled\n"
" Ambient pressure compensation: %dmBar", " Ambient pressure compensation: %dmBar",
@ -230,7 +230,7 @@ bool SCD4XComponent::perform_forced_calibration(uint16_t current_co2_concentrati
// frc takes 400 ms // frc takes 400 ms
// because this method will be used very rarly // because this method will be used very rarly
// the simple approach with delay is ok // the simple approach with delay is ok
delay(400); // NOLINT' delay(400); // NOLINT
if (!this->start_measurement_()) { if (!this->start_measurement_()) {
return false; return false;
} else { } else {
@ -267,8 +267,7 @@ bool SCD4XComponent::factory_reset() {
} }
void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_hpa) { void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_hpa) {
ambient_pressure_compensation_ = true; uint16_t new_ambient_pressure = static_cast<uint16_t>(pressure_in_hpa);
uint16_t new_ambient_pressure = (uint16_t) pressure_in_hpa;
if (!this->initialized_) { if (!this->initialized_) {
this->ambient_pressure_ = new_ambient_pressure; this->ambient_pressure_ = new_ambient_pressure;
return; return;

View File

@ -46,19 +46,17 @@ class SCD4XComponent : public PollingComponent, public sensirion_common::Sensiri
bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa);
bool start_measurement_(); bool start_measurement_();
uint16_t altitude_compensation_;
uint16_t ambient_pressure_;
bool initialized_{false};
bool ambient_pressure_compensation_;
bool enable_asc_;
float temperature_offset_;
ErrorCode error_code_;
MeasurementMode measurement_mode_{PERIODIC};
sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr};
// used for compensation sensor::Sensor *ambient_pressure_source_{nullptr}; // used for compensation
sensor::Sensor *ambient_pressure_source_{nullptr}; float temperature_offset_;
uint16_t altitude_compensation_{0};
uint16_t ambient_pressure_{0}; // Per datasheet, valid values are 700 to 1200 hPa; 0 is a valid sentinel value
bool initialized_{false};
bool enable_asc_{false};
ErrorCode error_code_;
MeasurementMode measurement_mode_{PERIODIC};
}; };
} // namespace scd4x } // namespace scd4x

View File

@ -318,24 +318,23 @@ void SX127x::loop() {
uint8_t addr = this->read_register_(REG_FIFO_RX_CURR_ADDR); uint8_t addr = this->read_register_(REG_FIFO_RX_CURR_ADDR);
uint8_t rssi = this->read_register_(REG_PKT_RSSI_VALUE); uint8_t rssi = this->read_register_(REG_PKT_RSSI_VALUE);
int8_t snr = (int8_t) this->read_register_(REG_PKT_SNR_VALUE); int8_t snr = (int8_t) this->read_register_(REG_PKT_SNR_VALUE);
std::vector<uint8_t> packet(bytes); this->packet_.resize(bytes);
this->write_register_(REG_FIFO_ADDR_PTR, addr); this->write_register_(REG_FIFO_ADDR_PTR, addr);
this->read_fifo_(packet); this->read_fifo_(this->packet_);
if (this->frequency_ > 700000000) { if (this->frequency_ > 700000000) {
this->call_listeners_(packet, (float) rssi - RSSI_OFFSET_HF, (float) snr / 4); this->call_listeners_(this->packet_, (float) rssi - RSSI_OFFSET_HF, (float) snr / 4);
} else { } else {
this->call_listeners_(packet, (float) rssi - RSSI_OFFSET_LF, (float) snr / 4); this->call_listeners_(this->packet_, (float) rssi - RSSI_OFFSET_LF, (float) snr / 4);
} }
} }
} else if (this->packet_mode_) { } else if (this->packet_mode_) {
std::vector<uint8_t> packet;
uint8_t payload_length = this->payload_length_; uint8_t payload_length = this->payload_length_;
if (payload_length == 0) { if (payload_length == 0) {
payload_length = this->read_register_(REG_FIFO); payload_length = this->read_register_(REG_FIFO);
} }
packet.resize(payload_length); this->packet_.resize(payload_length);
this->read_fifo_(packet); this->read_fifo_(this->packet_);
this->call_listeners_(packet, 0.0f, 0.0f); this->call_listeners_(this->packet_, 0.0f, 0.0f);
} }
} }
@ -407,18 +406,6 @@ void SX127x::dump_config() {
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" RST Pin: ", this->rst_pin_); LOG_PIN(" RST Pin: ", this->rst_pin_);
LOG_PIN(" DIO0 Pin: ", this->dio0_pin_); LOG_PIN(" DIO0 Pin: ", this->dio0_pin_);
const char *shaping = "NONE";
if (this->shaping_ == CUTOFF_BR_X_2) {
shaping = "CUTOFF_BR_X_2";
} else if (this->shaping_ == CUTOFF_BR_X_1) {
shaping = "CUTOFF_BR_X_1";
} else if (this->shaping_ == GAUSSIAN_BT_0_3) {
shaping = "GAUSSIAN_BT_0_3";
} else if (this->shaping_ == GAUSSIAN_BT_0_5) {
shaping = "GAUSSIAN_BT_0_5";
} else if (this->shaping_ == GAUSSIAN_BT_1_0) {
shaping = "GAUSSIAN_BT_1_0";
}
const char *pa_pin = "RFO"; const char *pa_pin = "RFO";
if (this->pa_pin_ == PA_PIN_BOOST) { if (this->pa_pin_ == PA_PIN_BOOST) {
pa_pin = "BOOST"; pa_pin = "BOOST";
@ -429,10 +416,9 @@ void SX127x::dump_config() {
" Bandwidth: %" PRIu32 " Hz\n" " Bandwidth: %" PRIu32 " Hz\n"
" PA Pin: %s\n" " PA Pin: %s\n"
" PA Power: %" PRIu8 " dBm\n" " PA Power: %" PRIu8 " dBm\n"
" PA Ramp: %" PRIu16 " us\n" " PA Ramp: %" PRIu16 " us",
" Shaping: %s",
TRUEFALSE(this->auto_cal_), this->frequency_, BW_HZ[this->bandwidth_], pa_pin, this->pa_power_, TRUEFALSE(this->auto_cal_), this->frequency_, BW_HZ[this->bandwidth_], pa_pin, this->pa_power_,
RAMP[this->pa_ramp_], shaping); RAMP[this->pa_ramp_]);
if (this->modulation_ == MOD_FSK) { if (this->modulation_ == MOD_FSK) {
ESP_LOGCONFIG(TAG, " Deviation: %" PRIu32 " Hz", this->deviation_); ESP_LOGCONFIG(TAG, " Deviation: %" PRIu32 " Hz", this->deviation_);
} }
@ -459,14 +445,31 @@ void SX127x::dump_config() {
ESP_LOGCONFIG(TAG, " Sync Value: 0x%02x", this->sync_value_[0]); ESP_LOGCONFIG(TAG, " Sync Value: 0x%02x", this->sync_value_[0]);
} }
} else { } else {
const char *shaping = "NONE";
if (this->modulation_ == MOD_FSK) {
if (this->shaping_ == GAUSSIAN_BT_0_3) {
shaping = "GAUSSIAN_BT_0_3";
} else if (this->shaping_ == GAUSSIAN_BT_0_5) {
shaping = "GAUSSIAN_BT_0_5";
} else if (this->shaping_ == GAUSSIAN_BT_1_0) {
shaping = "GAUSSIAN_BT_1_0";
}
} else {
if (this->shaping_ == CUTOFF_BR_X_2) {
shaping = "CUTOFF_BR_X_2";
} else if (this->shaping_ == CUTOFF_BR_X_1) {
shaping = "CUTOFF_BR_X_1";
}
}
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Shaping: %s\n"
" Modulation: %s\n" " Modulation: %s\n"
" Bitrate: %" PRIu32 "b/s\n" " Bitrate: %" PRIu32 "b/s\n"
" Bitsync: %s\n" " Bitsync: %s\n"
" Rx Start: %s\n" " Rx Start: %s\n"
" Rx Floor: %.1f dBm\n" " Rx Floor: %.1f dBm\n"
" Packet Mode: %s", " Packet Mode: %s",
this->modulation_ == MOD_FSK ? "FSK" : "OOK", this->bitrate_, TRUEFALSE(this->bitsync_), shaping, this->modulation_ == MOD_FSK ? "FSK" : "OOK", this->bitrate_, TRUEFALSE(this->bitsync_),
TRUEFALSE(this->rx_start_), this->rx_floor_, TRUEFALSE(this->packet_mode_)); TRUEFALSE(this->rx_start_), this->rx_floor_, TRUEFALSE(this->packet_mode_));
if (this->packet_mode_) { if (this->packet_mode_) {
ESP_LOGCONFIG(TAG, " CRC Enable: %s", TRUEFALSE(this->crc_enable_)); ESP_LOGCONFIG(TAG, " CRC Enable: %s", TRUEFALSE(this->crc_enable_));

View File

@ -96,6 +96,7 @@ class SX127x : public Component,
uint8_t read_register_(uint8_t reg); uint8_t read_register_(uint8_t reg);
Trigger<std::vector<uint8_t>, float, float> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, float>()}; Trigger<std::vector<uint8_t>, float, float> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, float>()};
std::vector<SX127xListener *> listeners_; std::vector<SX127xListener *> listeners_;
std::vector<uint8_t> packet_;
std::vector<uint8_t> sync_value_; std::vector<uint8_t> sync_value_;
InternalGPIOPin *dio0_pin_{nullptr}; InternalGPIOPin *dio0_pin_{nullptr};
InternalGPIOPin *rst_pin_{nullptr}; InternalGPIOPin *rst_pin_{nullptr};

View File

@ -158,16 +158,16 @@ void ComponentIterator::advance() {
} }
break; break;
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
case IteratorState::CAMERA: case IteratorState::CAMERA:
if (esp32_camera::global_esp32_camera == nullptr) { if (camera::Camera::instance() == nullptr) {
advance_platform = true; advance_platform = true;
} else { } else {
if (esp32_camera::global_esp32_camera->is_internal() && !this->include_internal_) { if (camera::Camera::instance()->is_internal() && !this->include_internal_) {
advance_platform = success = true; advance_platform = success = true;
break; break;
} else { } else {
advance_platform = success = this->on_camera(esp32_camera::global_esp32_camera); advance_platform = success = this->on_camera(camera::Camera::instance());
} }
} }
break; break;
@ -386,8 +386,8 @@ bool ComponentIterator::on_begin() { return true; }
#ifdef USE_API #ifdef USE_API
bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; } bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; }
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; } bool ComponentIterator::on_camera(camera::Camera *camera) { return true; }
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool ComponentIterator::on_media_player(media_player::MediaPlayer *media_player) { return true; } bool ComponentIterator::on_media_player(media_player::MediaPlayer *media_player) { return true; }

View File

@ -4,8 +4,8 @@
#include "esphome/core/controller.h" #include "esphome/core/controller.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
#include "esphome/components/esp32_camera/esp32_camera.h" #include "esphome/components/camera/camera.h"
#endif #endif
namespace esphome { namespace esphome {
@ -48,8 +48,8 @@ class ComponentIterator {
#ifdef USE_API #ifdef USE_API
virtual bool on_service(api::UserServiceDescriptor *service); virtual bool on_service(api::UserServiceDescriptor *service);
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
virtual bool on_camera(esp32_camera::ESP32Camera *camera); virtual bool on_camera(camera::Camera *camera);
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
virtual bool on_climate(climate::Climate *climate) = 0; virtual bool on_climate(climate::Climate *climate) = 0;
@ -125,7 +125,7 @@ class ComponentIterator {
#ifdef USE_API #ifdef USE_API
SERVICE, SERVICE,
#endif #endif
#ifdef USE_ESP32_CAMERA #ifdef USE_CAMERA
CAMERA, CAMERA,
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE

View File

@ -23,6 +23,7 @@
#define USE_AREAS #define USE_AREAS
#define USE_BINARY_SENSOR #define USE_BINARY_SENSOR
#define USE_BUTTON #define USE_BUTTON
#define USE_CAMERA
#define USE_CLIMATE #define USE_CLIMATE
#define USE_COVER #define USE_COVER
#define USE_DATETIME #define USE_DATETIME
@ -144,7 +145,6 @@
#define USE_ESP32_BLE #define USE_ESP32_BLE
#define USE_ESP32_BLE_CLIENT #define USE_ESP32_BLE_CLIENT
#define USE_ESP32_BLE_SERVER #define USE_ESP32_BLE_SERVER
#define USE_ESP32_CAMERA
#define USE_I2C #define USE_I2C
#define USE_IMPROV #define USE_IMPROV
#define USE_MICROPHONE #define USE_MICROPHONE

View File

@ -31,11 +31,20 @@
namespace esphome { namespace esphome {
// Base lock-free queue without task notification
template<class T, uint8_t SIZE> class LockFreeQueue { template<class T, uint8_t SIZE> class LockFreeQueue {
public: public:
LockFreeQueue() : head_(0), tail_(0), dropped_count_(0), task_to_notify_(nullptr) {} LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {}
bool push(T *element) { bool push(T *element) {
bool was_empty;
uint8_t old_tail;
return push_internal_(element, was_empty, old_tail);
}
protected:
// Internal push that reports queue state - for use by derived classes
bool push_internal_(T *element, bool &was_empty, uint8_t &old_tail) {
if (element == nullptr) if (element == nullptr)
return false; return false;
@ -51,34 +60,16 @@ template<class T, uint8_t SIZE> class LockFreeQueue {
return false; return false;
} }
// Check if queue was empty before push was_empty = (current_tail == head_before);
bool was_empty = (current_tail == head_before); old_tail = current_tail;
buffer_[current_tail] = element; buffer_[current_tail] = element;
tail_.store(next_tail, std::memory_order_release); tail_.store(next_tail, std::memory_order_release);
// Notify optimization: only notify if we need to
if (task_to_notify_ != nullptr) {
if (was_empty) {
// Queue was empty - consumer might be going to sleep, must notify
xTaskNotifyGive(task_to_notify_);
} else {
// Queue wasn't empty - check if consumer has caught up to previous tail
uint8_t head_after = head_.load(std::memory_order_acquire);
if (head_after == current_tail) {
// Consumer just caught up to where tail was - might go to sleep, must notify
// Note: There's a benign race here - between reading head_after and calling
// xTaskNotifyGive(), the consumer could advance further. This would result
// in an unnecessary wake-up, but is harmless and extremely rare in practice.
xTaskNotifyGive(task_to_notify_);
}
// Otherwise: consumer is still behind, no need to notify
}
}
return true; return true;
} }
public:
T *pop() { T *pop() {
uint8_t current_head = head_.load(std::memory_order_relaxed); uint8_t current_head = head_.load(std::memory_order_relaxed);
@ -108,11 +99,6 @@ template<class T, uint8_t SIZE> class LockFreeQueue {
return next_tail == head_.load(std::memory_order_acquire); return next_tail == head_.load(std::memory_order_acquire);
} }
// Set the FreeRTOS task handle to notify when items are pushed to the queue
// This enables efficient wake-up of a consumer task that's waiting for data
// @param task The FreeRTOS task handle to notify, or nullptr to disable notifications
void set_task_to_notify(TaskHandle_t task) { task_to_notify_ = task; }
protected: protected:
T *buffer_[SIZE]; T *buffer_[SIZE];
// Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset) // Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset)
@ -123,7 +109,40 @@ template<class T, uint8_t SIZE> class LockFreeQueue {
std::atomic<uint8_t> head_; std::atomic<uint8_t> head_;
// Atomic: written by producer (push), read by consumer (pop) to check if empty // Atomic: written by producer (push), read by consumer (pop) to check if empty
std::atomic<uint8_t> tail_; std::atomic<uint8_t> tail_;
// Task handle for notification (optional) };
// Extended queue with task notification support
template<class T, uint8_t SIZE> class NotifyingLockFreeQueue : public LockFreeQueue<T, SIZE> {
public:
NotifyingLockFreeQueue() : LockFreeQueue<T, SIZE>(), task_to_notify_(nullptr) {}
bool push(T *element) {
bool was_empty;
uint8_t old_tail;
bool result = this->push_internal_(element, was_empty, old_tail);
// Notify optimization: only notify if we need to
if (result && task_to_notify_ != nullptr &&
(was_empty || this->head_.load(std::memory_order_acquire) == old_tail)) {
// Notify in two cases:
// 1. Queue was empty - consumer might be going to sleep
// 2. Consumer just caught up to where tail was - might go to sleep
// Note: There's a benign race in case 2 - between reading head and calling
// xTaskNotifyGive(), the consumer could advance further. This would result
// in an unnecessary wake-up, but is harmless and extremely rare in practice.
xTaskNotifyGive(task_to_notify_);
}
// Otherwise: consumer is still behind, no need to notify
return result;
}
// Set the FreeRTOS task handle to notify when items are pushed to the queue
// This enables efficient wake-up of a consumer task that's waiting for data
// @param task The FreeRTOS task handle to notify, or nullptr to disable notifications
void set_task_to_notify(TaskHandle_t task) { task_to_notify_ = task; }
private:
TaskHandle_t task_to_notify_; TaskHandle_t task_to_notify_;
}; };

View File

@ -0,0 +1,18 @@
esphome:
includes:
- ../../../esphome/components/camera/
script:
- id: interface_compile_check
then:
- lambda: |-
using namespace esphome::camera;
class MockCamera : public Camera {
public:
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) override {}
CameraImageReader *create_image_reader() override { return 0; }
void request_image(CameraRequester requester) override {}
void start_stream(CameraRequester requester) override {}
void stop_stream(CameraRequester requester) override {}
};
MockCamera* camera = new MockCamera();

View File

@ -0,0 +1 @@
<<: !include common.yaml

View File

@ -0,0 +1 @@
<<: !include common.yaml