mirror of
https://github.com/esphome/esphome.git
synced 2026-02-12 10:42:03 +00:00
Compare commits
52 Commits
logger_rp2
...
ota_md5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73a3f16f36 | ||
|
|
d0673122a8 | ||
|
|
5cbef3ef95 | ||
|
|
a1e0121330 | ||
|
|
eb050ff13e | ||
|
|
45e61f100c | ||
|
|
5e99dd14ae | ||
|
|
a6097f4a0f | ||
|
|
f243e609a5 | ||
|
|
be0bf1e5b9 | ||
|
|
a275f37135 | ||
|
|
e9f2d75aab | ||
|
|
34067f8b15 | ||
|
|
bdc087148a | ||
|
|
5a2e0612a8 | ||
|
|
f1fecd22e3 | ||
|
|
0919017d49 | ||
|
|
963f594c9e | ||
|
|
4f70663658 | ||
|
|
958a35e262 | ||
|
|
0c566c6f00 | ||
|
|
ba73289b28 | ||
|
|
99f7e9aeb7 | ||
|
|
ebb6babb3d | ||
|
|
0922f240e0 | ||
|
|
c8fb694dcb | ||
|
|
6054685dae | ||
|
|
61ec3508ed | ||
|
|
086ec770ea | ||
|
|
b055f5b4bf | ||
|
|
726db746c8 | ||
|
|
1922455fa7 | ||
|
|
dc943d7e7a | ||
|
|
ffefa8929e | ||
|
|
7d5342bca5 | ||
|
|
b4c92dd8cb | ||
|
|
1b31253287 | ||
|
|
af0d4d2c2c | ||
|
|
f238f93312 | ||
|
|
bdbe72b7f1 | ||
|
|
c8b531ac06 | ||
|
|
918bc4b74f | ||
|
|
08c0f65f30 | ||
|
|
cd45fe0c3a | ||
|
|
84b5d9b21c | ||
|
|
6383fe4598 | ||
|
|
265ad9d264 | ||
|
|
1bdbc4cb85 | ||
|
|
1756fc31b0 | ||
|
|
74b075d3cf | ||
|
|
52eb08f48f | ||
|
|
0d993691d4 |
@@ -1 +1 @@
|
||||
4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9
|
||||
5ac05ac603766d76b86a05cdf6a43febcaae807fe9e2406d812c47d4b5fed91d
|
||||
|
||||
@@ -519,6 +519,7 @@ esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/tuya/text_sensor/* @dentra
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/uart/button/* @ssieb
|
||||
esphome/components/uart/event/* @eoasmxd
|
||||
esphome/components/uart/packet_transport/* @clydebarrow
|
||||
esphome/components/udp/* @clydebarrow
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
static const char *const TAG = "alarm_control_panel";
|
||||
|
||||
@@ -115,5 +114,4 @@ void AlarmControlPanel::disarm(optional<std::string> code) {
|
||||
call.perform();
|
||||
}
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "alarm_control_panel_call.h"
|
||||
#include "alarm_control_panel_state.h"
|
||||
|
||||
@@ -9,8 +7,7 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
enum AlarmControlPanelFeature : uint8_t {
|
||||
// Matches Home Assistant values
|
||||
@@ -141,5 +138,4 @@ class AlarmControlPanel : public EntityBase {
|
||||
LazyCallbackManager<void()> ready_callback_{};
|
||||
};
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
static const char *const TAG = "alarm_control_panel";
|
||||
|
||||
@@ -99,5 +98,4 @@ void AlarmControlPanelCall::perform() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
class AlarmControlPanel;
|
||||
|
||||
@@ -36,5 +35,4 @@ class AlarmControlPanelCall {
|
||||
void validate_();
|
||||
};
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "alarm_control_panel_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
|
||||
switch (state) {
|
||||
@@ -30,5 +29,4 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include <cstdint>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
enum AlarmControlPanelState : uint8_t {
|
||||
ACP_STATE_DISARMED = 0,
|
||||
@@ -25,5 +24,4 @@ enum AlarmControlPanelState : uint8_t {
|
||||
*/
|
||||
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state);
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "alarm_control_panel.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
/// Trigger on any state change
|
||||
class StateTrigger : public Trigger<> {
|
||||
@@ -165,5 +164,4 @@ template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts.
|
||||
AlarmControlPanel *parent_;
|
||||
};
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cinttypes>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
@@ -96,8 +97,7 @@ static const int CAMERA_STOP_STREAM = 5000;
|
||||
return;
|
||||
#endif // USE_DEVICES
|
||||
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) : parent_(parent) {
|
||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||
auto &noise_ctx = parent->get_noise_ctx();
|
||||
if (noise_ctx.has_psk()) {
|
||||
@@ -136,6 +136,7 @@ void APIConnection::start() {
|
||||
}
|
||||
|
||||
APIConnection::~APIConnection() {
|
||||
this->destroy_active_iterator_();
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
|
||||
@@ -148,6 +149,32 @@ APIConnection::~APIConnection() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIConnection::destroy_active_iterator_() {
|
||||
switch (this->active_iterator_) {
|
||||
case ActiveIterator::LIST_ENTITIES:
|
||||
this->iterator_storage_.list_entities.~ListEntitiesIterator();
|
||||
break;
|
||||
case ActiveIterator::INITIAL_STATE:
|
||||
this->iterator_storage_.initial_state.~InitialStateIterator();
|
||||
break;
|
||||
case ActiveIterator::NONE:
|
||||
break;
|
||||
}
|
||||
this->active_iterator_ = ActiveIterator::NONE;
|
||||
}
|
||||
|
||||
void APIConnection::begin_iterator_(ActiveIterator type) {
|
||||
this->destroy_active_iterator_();
|
||||
this->active_iterator_ = type;
|
||||
if (type == ActiveIterator::LIST_ENTITIES) {
|
||||
new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this);
|
||||
this->iterator_storage_.list_entities.begin();
|
||||
} else {
|
||||
new (&this->iterator_storage_.initial_state) InitialStateIterator(this);
|
||||
this->iterator_storage_.initial_state.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->flags_.next_close) {
|
||||
// requested a disconnect
|
||||
@@ -190,23 +217,35 @@ void APIConnection::loop() {
|
||||
this->process_batch_();
|
||||
}
|
||||
|
||||
if (!this->list_entities_iterator_.completed()) {
|
||||
this->process_iterator_batch_(this->list_entities_iterator_);
|
||||
} else if (!this->initial_state_iterator_.completed()) {
|
||||
this->process_iterator_batch_(this->initial_state_iterator_);
|
||||
|
||||
// If we've completed initial states, process any remaining and clear the flag
|
||||
if (this->initial_state_iterator_.completed()) {
|
||||
// Process any remaining batched messages immediately
|
||||
if (!this->deferred_batch_.empty()) {
|
||||
this->process_batch_();
|
||||
switch (this->active_iterator_) {
|
||||
case ActiveIterator::LIST_ENTITIES:
|
||||
if (this->iterator_storage_.list_entities.completed()) {
|
||||
this->destroy_active_iterator_();
|
||||
if (this->flags_.state_subscription) {
|
||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||
}
|
||||
} else {
|
||||
this->process_iterator_batch_(this->iterator_storage_.list_entities);
|
||||
}
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
// Release excess memory from buffers that grew during initial sync
|
||||
this->deferred_batch_.release_buffer();
|
||||
this->helper_->release_buffers();
|
||||
}
|
||||
break;
|
||||
case ActiveIterator::INITIAL_STATE:
|
||||
if (this->iterator_storage_.initial_state.completed()) {
|
||||
this->destroy_active_iterator_();
|
||||
// Process any remaining batched messages immediately
|
||||
if (!this->deferred_batch_.empty()) {
|
||||
this->process_batch_();
|
||||
}
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
// Release excess memory from buffers that grew during initial sync
|
||||
this->deferred_batch_.release_buffer();
|
||||
this->helper_->release_buffers();
|
||||
} else {
|
||||
this->process_iterator_batch_(this->iterator_storage_.initial_state);
|
||||
}
|
||||
break;
|
||||
case ActiveIterator::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->flags_.sent_ping) {
|
||||
@@ -231,33 +270,17 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
|
||||
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
||||
bool done = this->image_reader_->available() == to_send;
|
||||
|
||||
CameraImageResponse msg;
|
||||
msg.key = camera::Camera::instance()->get_object_id_hash();
|
||||
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
|
||||
msg.done = done;
|
||||
#ifdef USE_DEVICES
|
||||
msg.device_id = camera::Camera::instance()->get_device_id();
|
||||
#endif
|
||||
|
||||
if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
|
||||
this->image_reader_->consume_data(to_send);
|
||||
if (done) {
|
||||
this->image_reader_->return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
if (state_subs_at_ >= 0) {
|
||||
this->process_state_subscriptions_();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
// Process camera last - state updates are higher priority
|
||||
// (missing a frame is fine, missing a state update is not)
|
||||
this->try_send_camera_image_();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
|
||||
@@ -1060,6 +1083,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void APIConnection::try_send_camera_image_() {
|
||||
if (!this->image_reader_)
|
||||
return;
|
||||
|
||||
// Send as many chunks as possible without blocking
|
||||
while (this->image_reader_->available()) {
|
||||
if (!this->helper_->can_write_without_blocking())
|
||||
return;
|
||||
|
||||
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
||||
bool done = this->image_reader_->available() == to_send;
|
||||
|
||||
CameraImageResponse msg;
|
||||
msg.key = camera::Camera::instance()->get_object_id_hash();
|
||||
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
|
||||
msg.done = done;
|
||||
#ifdef USE_DEVICES
|
||||
msg.device_id = camera::Camera::instance()->get_device_id();
|
||||
#endif
|
||||
|
||||
if (!this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
|
||||
return; // Send failed, try again later
|
||||
}
|
||||
this->image_reader_->consume_data(to_send);
|
||||
if (done) {
|
||||
this->image_reader_->return_image();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
|
||||
if (!this->flags_.state_subscription)
|
||||
return;
|
||||
@@ -1067,8 +1120,11 @@ void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image)
|
||||
return;
|
||||
if (this->image_reader_->available())
|
||||
return;
|
||||
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE))
|
||||
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) {
|
||||
this->image_reader_->set_image(std::move(image));
|
||||
// Try to send immediately to reduce latency
|
||||
this->try_send_camera_image_();
|
||||
}
|
||||
}
|
||||
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
|
||||
@@ -208,10 +208,14 @@ class APIConnection final : public APIServerConnection {
|
||||
bool send_disconnect_response(const DisconnectRequest &msg) override;
|
||||
bool send_ping_response(const PingRequest &msg) override;
|
||||
bool send_device_info_response(const DeviceInfoRequest &msg) override;
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
|
||||
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
||||
this->flags_.state_subscription = true;
|
||||
this->initial_state_iterator_.begin();
|
||||
// Start initial state iterator only if no iterator is active
|
||||
// If list_entities is running, we'll start initial_state when it completes
|
||||
if (this->active_iterator_ == ActiveIterator::NONE) {
|
||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||
}
|
||||
}
|
||||
void subscribe_logs(const SubscribeLogsRequest &msg) override {
|
||||
this->flags_.log_subscription = msg.level;
|
||||
@@ -292,6 +296,10 @@ class APIConnection final : public APIServerConnection {
|
||||
// Helper function to handle authentication completion
|
||||
void complete_authentication_();
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void try_send_camera_image_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void process_state_subscriptions_();
|
||||
#endif
|
||||
@@ -315,17 +323,10 @@ class APIConnection final : public APIServerConnection {
|
||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||
// Set common fields that are shared by all entity types
|
||||
msg.key = entity->get_object_id_hash();
|
||||
// Try to use static reference first to avoid allocation
|
||||
StringRef static_ref = entity->get_object_id_ref_for_api_();
|
||||
// Store dynamic string outside the if-else to maintain lifetime
|
||||
std::string object_id;
|
||||
if (!static_ref.empty()) {
|
||||
msg.set_object_id(static_ref);
|
||||
} else {
|
||||
// Dynamic case - need to allocate
|
||||
object_id = entity->get_object_id();
|
||||
msg.set_object_id(StringRef(object_id));
|
||||
}
|
||||
// Get object_id with zero heap allocation
|
||||
// Static case returns direct reference, dynamic case uses buffer
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
msg.set_object_id(entity->get_object_id_to(object_id_buf));
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.set_name(entity->get_name());
|
||||
@@ -501,10 +502,22 @@ class APIConnection final : public APIServerConnection {
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
APIServer *parent_;
|
||||
|
||||
// Group 2: Larger objects (must be 4-byte aligned)
|
||||
// These contain vectors/pointers internally, so putting them early ensures good alignment
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
// Group 2: Iterator union (saves ~16 bytes vs separate iterators)
|
||||
// These iterators are never active simultaneously - list_entities runs to completion
|
||||
// before initial_state begins, so we use a union with explicit construction/destruction.
|
||||
enum class ActiveIterator : uint8_t { NONE, LIST_ENTITIES, INITIAL_STATE };
|
||||
|
||||
union IteratorUnion {
|
||||
ListEntitiesIterator list_entities;
|
||||
InitialStateIterator initial_state;
|
||||
// Constructor/destructor do nothing - use placement new/explicit destructor
|
||||
IteratorUnion() {}
|
||||
~IteratorUnion() {}
|
||||
} iterator_storage_;
|
||||
|
||||
// Helper methods for iterator lifecycle management
|
||||
void destroy_active_iterator_();
|
||||
void begin_iterator_(ActiveIterator type);
|
||||
#ifdef USE_CAMERA
|
||||
std::unique_ptr<camera::CameraImageReader> image_reader_;
|
||||
#endif
|
||||
@@ -619,7 +632,9 @@ class APIConnection final : public APIServerConnection {
|
||||
// 2-byte types immediately after flags_ (no padding between them)
|
||||
uint16_t client_api_version_major_{0};
|
||||
uint16_t client_api_version_minor_{0};
|
||||
// Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary
|
||||
// 1-byte type to fill padding
|
||||
ActiveIterator active_iterator_{ActiveIterator::NONE};
|
||||
// Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary
|
||||
|
||||
uint32_t get_batch_delay_ms_() const;
|
||||
// Message will use 8 more bytes than the minimum size, and typical
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "audio_reader.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "audio.h"
|
||||
#include "audio_transfer_buffer.h"
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import ble_client, climate
|
||||
from esphome.components import climate
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_HEAT_MODE,
|
||||
CONF_RECEIVE_TIMEOUT,
|
||||
CONF_TEMPERATURE_SOURCE,
|
||||
CONF_TIME_ID,
|
||||
)
|
||||
from esphome.const import CONF_HEAT_MODE, CONF_TEMPERATURE_SOURCE
|
||||
|
||||
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
|
||||
|
||||
@@ -38,22 +33,6 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(
|
||||
# TODO: remove compat layer.
|
||||
{
|
||||
cv.Optional(ble_client.CONF_BLE_CLIENT_ID): cv.invalid(
|
||||
"The 'ble_client_id' option has been removed. Please migrate "
|
||||
"to the new `bedjet_id` option in the `bedjet` component.\n"
|
||||
"See https://esphome.io/components/climate/bedjet/"
|
||||
),
|
||||
cv.Optional(CONF_TIME_ID): cv.invalid(
|
||||
"The 'time_id' option has been moved to the `bedjet` component."
|
||||
),
|
||||
cv.Optional(CONF_RECEIVE_TIMEOUT): cv.invalid(
|
||||
"The 'receive_timeout' option has been moved to the `bedjet` component."
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(BEDJET_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
@@ -20,16 +20,6 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional("resolution"): cv.invalid(
|
||||
"The 'resolution' option has been removed. The optimal value is now dynamically calculated."
|
||||
),
|
||||
cv.Optional("measurement_duration"): cv.invalid(
|
||||
"The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated."
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x23))
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def AUTO_LOAD() -> list[str]:
|
||||
auto_load = ["web_server_base", "ota.web_server"]
|
||||
if CORE.using_esp_idf:
|
||||
if CORE.is_esp32:
|
||||
auto_load.append("socket")
|
||||
return auto_load
|
||||
|
||||
@@ -97,10 +97,6 @@ async def to_code(config):
|
||||
cg.add_define("USE_CAPTIVE_PORTAL")
|
||||
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("ESP32 Async UDP", None)
|
||||
cg.add_library("DNSServer", None)
|
||||
cg.add_library("WiFi", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("DNSServer", None)
|
||||
if CORE.is_libretiny:
|
||||
@@ -110,6 +106,9 @@ async def to_code(config):
|
||||
# Only compile the ESP-IDF DNS server when using ESP-IDF framework
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
{
|
||||
"dns_server_esp32_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||
"dns_server_esp32_idf.cpp": {
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -69,12 +69,11 @@ void CaptivePortal::start() {
|
||||
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#if defined(USE_ESP32)
|
||||
// Create DNS server instance for ESP-IDF
|
||||
this->dns_server_ = make_unique<DNSServer>();
|
||||
this->dns_server_->start(ip);
|
||||
#endif
|
||||
#ifdef USE_ARDUINO
|
||||
#elif defined(USE_ARDUINO)
|
||||
this->dns_server_ = make_unique<DNSServer>();
|
||||
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
this->dns_server_->start(53, ESPHOME_F("*"), ip);
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_CAPTIVE_PORTAL
|
||||
#include <memory>
|
||||
#ifdef USE_ARDUINO
|
||||
#include <DNSServer.h>
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
#if defined(USE_ESP32)
|
||||
#include "dns_server_esp32_idf.h"
|
||||
#elif defined(USE_ARDUINO)
|
||||
#include <DNSServer.h>
|
||||
#endif
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -23,15 +22,14 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override {
|
||||
#ifdef USE_ARDUINO
|
||||
if (this->dns_server_ != nullptr) {
|
||||
this->dns_server_->processNextRequest();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
#if defined(USE_ESP32)
|
||||
if (this->dns_server_ != nullptr) {
|
||||
this->dns_server_->process_next_request();
|
||||
}
|
||||
#elif defined(USE_ARDUINO)
|
||||
if (this->dns_server_ != nullptr) {
|
||||
this->dns_server_->processNextRequest();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
float get_setup_priority() const override;
|
||||
@@ -64,7 +62,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
web_server_base::WebServerBase *base_;
|
||||
bool initialized_{false};
|
||||
bool active_{false};
|
||||
#if defined(USE_ARDUINO) || defined(USE_ESP_IDF)
|
||||
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||
std::unique_ptr<DNSServer> dns_server_{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "dns_server_esp32_idf.h"
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
@@ -202,4 +202,4 @@ void DNSServer::process_next_request() {
|
||||
|
||||
} // namespace esphome::captive_portal
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <memory>
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -24,4 +24,4 @@ class DNSServer {
|
||||
|
||||
} // namespace esphome::captive_portal
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -169,14 +169,16 @@ void CC1101Component::loop() {
|
||||
}
|
||||
|
||||
// Read packet
|
||||
uint8_t payload_length;
|
||||
uint8_t payload_length, expected_rx;
|
||||
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
|
||||
this->read_(Register::FIFO, &payload_length, 1);
|
||||
expected_rx = payload_length + 1;
|
||||
} else {
|
||||
payload_length = this->state_.PKTLEN;
|
||||
expected_rx = payload_length;
|
||||
}
|
||||
if (payload_length == 0 || payload_length > 64) {
|
||||
ESP_LOGW(TAG, "Invalid payload length: %u", payload_length);
|
||||
if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) {
|
||||
ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length);
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FRX);
|
||||
this->strobe_(Command::RX);
|
||||
@@ -186,13 +188,12 @@ void CC1101Component::loop() {
|
||||
this->packet_.resize(payload_length);
|
||||
this->read_(Register::FIFO, this->packet_.data(), payload_length);
|
||||
|
||||
// Read status and trigger
|
||||
uint8_t status[2];
|
||||
this->read_(Register::FIFO, status, 2);
|
||||
int8_t rssi_raw = static_cast<int8_t>(status[0]);
|
||||
float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET;
|
||||
bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0;
|
||||
uint8_t lqi = status[1] & STATUS_LQI_MASK;
|
||||
// Read status from registers (more reliable than FIFO status bytes due to timing issues)
|
||||
this->read_(Register::RSSI);
|
||||
this->read_(Register::LQI);
|
||||
float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET;
|
||||
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
|
||||
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
|
||||
if (this->state_.CRC_EN == 0 || crc_ok) {
|
||||
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
|
||||
}
|
||||
@@ -616,12 +617,15 @@ void CC1101Component::set_packet_mode(bool value) {
|
||||
this->state_.GDO0_CFG = 0x01;
|
||||
// Set max RX FIFO threshold to ensure we only trigger on end-of-packet
|
||||
this->state_.FIFO_THR = 15;
|
||||
// Don't append status bytes to FIFO - we read from registers instead
|
||||
this->state_.APPEND_STATUS = 0;
|
||||
} else {
|
||||
// Configure GDO0 for serial data (async serial mode)
|
||||
this->state_.GDO0_CFG = 0x0D;
|
||||
}
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTCTRL0);
|
||||
this->write_(Register::PKTCTRL1);
|
||||
this->write_(Register::IOCFG0);
|
||||
this->write_(Register::FIFOTHR);
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||
}
|
||||
|
||||
void Climate::save_state_() {
|
||||
#if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
|
||||
#if (defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
|
||||
!defined(CLANG_TIDY)
|
||||
#pragma GCC diagnostic ignored "-Wclass-memaccess"
|
||||
#define TEMP_IGNORE_MEMACCESS
|
||||
|
||||
@@ -200,7 +200,7 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
#ifdef USE_ARDUINO
|
||||
ESP_LOGD(TAG, "Framework: Arduino");
|
||||
device_info += "Arduino";
|
||||
#elif defined(USE_ESP_IDF)
|
||||
#elif defined(USE_ESP32)
|
||||
ESP_LOGD(TAG, "Framework: ESP-IDF");
|
||||
device_info += "ESP-IDF";
|
||||
#else
|
||||
|
||||
@@ -24,7 +24,9 @@ extern "C" {
|
||||
#include <nvs_flash.h>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal-bt.h>
|
||||
// Prevent Arduino from releasing BT memory at startup (esp32-hal-misc.c).
|
||||
// Without this, esp_bt_controller_init() fails with ESP_ERR_INVALID_STATE.
|
||||
extern "C" bool btInUse() { return true; } // NOLINT(readability-identifier-naming)
|
||||
#endif
|
||||
|
||||
namespace esphome::esp32_ble {
|
||||
@@ -165,12 +167,6 @@ void ESP32BLE::advertising_init_() {
|
||||
bool ESP32BLE::ble_setup_() {
|
||||
esp_err_t err;
|
||||
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
@@ -195,7 +191,6 @@ bool ESP32BLE::ble_setup_() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
#else
|
||||
@@ -256,8 +251,11 @@ bool ESP32BLE::ble_setup_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// BLE device names are limited to 20 characters
|
||||
// Buffer: 20 chars + null terminator
|
||||
constexpr size_t ble_name_max_len = 21;
|
||||
char name_buffer[ble_name_max_len];
|
||||
const char *device_name;
|
||||
std::string name_with_suffix;
|
||||
|
||||
if (this->name_ != nullptr) {
|
||||
if (App.is_name_add_mac_suffix_enabled()) {
|
||||
@@ -268,23 +266,28 @@ bool ESP32BLE::ble_setup_() {
|
||||
char mac_addr[mac_address_len];
|
||||
get_mac_address_into_buffer(mac_addr);
|
||||
const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len;
|
||||
name_with_suffix =
|
||||
make_name_with_suffix(this->name_, strlen(this->name_), '-', mac_suffix_ptr, mac_address_suffix_len);
|
||||
device_name = name_with_suffix.c_str();
|
||||
make_name_with_suffix_to(name_buffer, sizeof(name_buffer), this->name_, strlen(this->name_), '-', mac_suffix_ptr,
|
||||
mac_address_suffix_len);
|
||||
device_name = name_buffer;
|
||||
} else {
|
||||
device_name = this->name_;
|
||||
}
|
||||
} else {
|
||||
name_with_suffix = App.get_name();
|
||||
if (name_with_suffix.length() > 20) {
|
||||
const std::string &app_name = App.get_name();
|
||||
size_t name_len = app_name.length();
|
||||
if (name_len > 20) {
|
||||
if (App.is_name_add_mac_suffix_enabled()) {
|
||||
// Keep first 13 chars and last 7 chars (MAC suffix), remove middle
|
||||
name_with_suffix.erase(13, name_with_suffix.length() - 20);
|
||||
memcpy(name_buffer, app_name.c_str(), 13);
|
||||
memcpy(name_buffer + 13, app_name.c_str() + name_len - 7, 7);
|
||||
} else {
|
||||
name_with_suffix.resize(20);
|
||||
memcpy(name_buffer, app_name.c_str(), 20);
|
||||
}
|
||||
name_buffer[20] = '\0';
|
||||
} else {
|
||||
memcpy(name_buffer, app_name.c_str(), name_len + 1); // Include null terminator
|
||||
}
|
||||
device_name = name_with_suffix.c_str();
|
||||
device_name = name_buffer;
|
||||
}
|
||||
|
||||
err = esp_ble_gap_set_device_name(device_name);
|
||||
@@ -326,12 +329,6 @@ bool ESP32BLE::ble_dismantle_() {
|
||||
}
|
||||
|
||||
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStop()) {
|
||||
ESP_LOGE(TAG, "btStop failed: %d", esp_bt_controller_get_status());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
// stop bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
@@ -355,7 +352,6 @@ bool ESP32BLE::ble_dismantle_() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
if (esp_hosted_bt_controller_disable() != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_hosted_bt_controller_disable failed");
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components import i2c, socket
|
||||
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
|
||||
from esphome.components.psram import DOMAIN as psram_domain
|
||||
import esphome.config_validation as cv
|
||||
@@ -27,7 +27,7 @@ import esphome.final_validate as fv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AUTO_LOAD = ["camera"]
|
||||
AUTO_LOAD = ["camera", "socket"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
||||
@@ -324,6 +324,7 @@ SETTERS = {
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_CAMERA")
|
||||
socket.require_wake_loop_threadsafe()
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await setup_entity(var, config, "camera")
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace esphome {
|
||||
namespace esp32_camera {
|
||||
|
||||
static const char *const TAG = "esp32_camera";
|
||||
static constexpr size_t FRAMEBUFFER_TASK_STACK_SIZE = 1792;
|
||||
#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE
|
||||
static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000;
|
||||
#endif
|
||||
@@ -42,12 +43,12 @@ void ESP32Camera::setup() {
|
||||
this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
|
||||
this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
|
||||
xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
|
||||
"framebuffer_task", // name
|
||||
1024, // stack size
|
||||
this, // task pv params
|
||||
1, // priority
|
||||
nullptr, // handle
|
||||
1 // core
|
||||
"framebuffer_task", // name
|
||||
FRAMEBUFFER_TASK_STACK_SIZE, // stack size
|
||||
this, // task pv params
|
||||
1, // priority
|
||||
nullptr, // handle
|
||||
1 // core
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,6 +168,19 @@ void ESP32Camera::dump_config() {
|
||||
}
|
||||
|
||||
void ESP32Camera::loop() {
|
||||
// Fast path: skip all work when truly idle
|
||||
// (no current image, no pending requests, and not time for idle request yet)
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (!this->current_image_ && !this->has_requested_image_()) {
|
||||
// Only check idle interval when we're otherwise idle
|
||||
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
||||
this->last_idle_request_ = now;
|
||||
this->request_image(camera::IDLE);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check if we can return the image
|
||||
if (this->can_return_image_()) {
|
||||
// return image
|
||||
@@ -175,13 +189,6 @@ void ESP32Camera::loop() {
|
||||
this->current_image_.reset();
|
||||
}
|
||||
|
||||
// request idle image every idle_update_interval
|
||||
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_) {
|
||||
this->last_idle_request_ = now;
|
||||
this->request_image(camera::IDLE);
|
||||
}
|
||||
|
||||
// Check if we should fetch a new image
|
||||
if (!this->has_requested_image_())
|
||||
return;
|
||||
@@ -421,6 +428,10 @@ void ESP32Camera::framebuffer_task(void *pv) {
|
||||
while (true) {
|
||||
camera_fb_t *framebuffer = esp_camera_fb_get();
|
||||
xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
|
||||
// Only wake the main loop if there's a pending request to consume the frame
|
||||
if (that->has_requested_image_()) {
|
||||
App.wake_loop_threadsafe();
|
||||
}
|
||||
// return is no-op for config with 1 fb
|
||||
xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
|
||||
esp_camera_fb_return(framebuffer);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <atomic>
|
||||
#include <esp_camera.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
@@ -205,8 +206,8 @@ class ESP32Camera : public camera::Camera {
|
||||
|
||||
esp_err_t init_error_{ESP_OK};
|
||||
std::shared_ptr<ESP32CameraImage> current_image_;
|
||||
uint8_t single_requesters_{0};
|
||||
uint8_t stream_requesters_{0};
|
||||
std::atomic<uint8_t> single_requesters_{0};
|
||||
std::atomic<uint8_t> stream_requesters_{0};
|
||||
QueueHandle_t framebuffer_get_queue_;
|
||||
QueueHandle_t framebuffer_return_queue_;
|
||||
std::vector<camera::CameraListener *> listeners_;
|
||||
|
||||
@@ -191,7 +191,8 @@ async def to_code(config):
|
||||
cg.add_define(ThreadModel.SINGLE)
|
||||
|
||||
cg.add_platformio_option(
|
||||
"extra_scripts", ["pre:testing_mode.py", "post:post_build.py"]
|
||||
"extra_scripts",
|
||||
["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"],
|
||||
)
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
@@ -278,3 +279,8 @@ def copy_files():
|
||||
testing_mode_file,
|
||||
CORE.relative_build_path("testing_mode.py"),
|
||||
)
|
||||
exclude_updater_file = dir / "exclude_updater.py.script"
|
||||
copy_file_if_changed(
|
||||
exclude_updater_file,
|
||||
CORE.relative_build_path("exclude_updater.py"),
|
||||
)
|
||||
|
||||
21
esphome/components/esp8266/exclude_updater.py.script
Normal file
21
esphome/components/esp8266/exclude_updater.py.script
Normal file
@@ -0,0 +1,21 @@
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
import os
|
||||
|
||||
# Filter out Updater.cpp from the Arduino core build
|
||||
# This saves 228 bytes of .bss by not instantiating the global Update object
|
||||
# ESPHome uses its own native OTA backend instead
|
||||
|
||||
|
||||
def filter_updater_from_core(env, node):
|
||||
"""Filter callback to exclude Updater.cpp from framework build."""
|
||||
path = node.get_path()
|
||||
if path.endswith("Updater.cpp"):
|
||||
print(f"ESPHome: Excluding {os.path.basename(path)} from build (using native OTA backend)")
|
||||
return None
|
||||
return node
|
||||
|
||||
|
||||
# Apply the filter to framework sources
|
||||
env.AddBuildMiddleware(filter_updater_from_core, "**/cores/esp8266/Updater.cpp")
|
||||
@@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
cv.only_on_esp32,
|
||||
only_on_variant(supported=[VARIANT_ESP32P4]),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
#include "ota_esphome.h"
|
||||
#ifdef USE_OTA
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
#ifdef USE_OTA_MD5
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#endif
|
||||
#ifdef USE_OTA_SHA256
|
||||
#include "esphome/components/sha256/sha256.h"
|
||||
#endif
|
||||
#endif
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_libretiny.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
@@ -31,15 +26,6 @@ static constexpr size_t OTA_BUFFER_SIZE = 1024; // buffer size
|
||||
static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds for initial handshake
|
||||
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
|
||||
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
#ifdef USE_OTA_MD5
|
||||
static constexpr size_t MD5_HEX_SIZE = 32; // MD5 hash as hex string (16 bytes * 2)
|
||||
#endif
|
||||
#ifdef USE_OTA_SHA256
|
||||
static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32 bytes * 2)
|
||||
#endif
|
||||
#endif // USE_OTA_PASSWORD
|
||||
|
||||
void ESPHomeOTAComponent::setup() {
|
||||
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
if (this->server_ == nullptr) {
|
||||
|
||||
@@ -220,10 +220,6 @@ BASE_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA,
|
||||
cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional("enable_mdns"): cv.invalid(
|
||||
"This option has been removed. Please use the [disabled] option under the "
|
||||
"new mdns component instead."
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -644,6 +644,12 @@ void EthernetComponent::dump_connect_params_() {
|
||||
dns_ip2 = dns_getserver(1);
|
||||
}
|
||||
|
||||
// Use stack buffers for IP address formatting to avoid heap allocations
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" IP Address: %s\n"
|
||||
" Hostname: '%s'\n"
|
||||
@@ -651,9 +657,9 @@ void EthernetComponent::dump_connect_params_() {
|
||||
" Gateway: %s\n"
|
||||
" DNS1: %s\n"
|
||||
" DNS2: %s",
|
||||
network::IPAddress(&ip.ip).str().c_str(), App.get_name().c_str(),
|
||||
network::IPAddress(&ip.netmask).str().c_str(), network::IPAddress(&ip.gw).str().c_str(),
|
||||
network::IPAddress(dns_ip1).str().c_str(), network::IPAddress(dns_ip2).str().c_str());
|
||||
network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(),
|
||||
network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf),
|
||||
network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf));
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
@@ -665,12 +671,13 @@ void EthernetComponent::dump_connect_params_() {
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" MAC Address: %s\n"
|
||||
" Is Full Duplex: %s\n"
|
||||
" Link Speed: %u",
|
||||
this->get_eth_mac_address_pretty().c_str(), YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL),
|
||||
this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
|
||||
this->get_eth_mac_address_pretty_into_buffer(mac_buf),
|
||||
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
|
||||
}
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
@@ -711,11 +718,16 @@ void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||
}
|
||||
|
||||
std::string EthernetComponent::get_eth_mac_address_pretty() {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
return std::string(this->get_eth_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
|
||||
const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
|
||||
std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
|
||||
uint8_t mac[6];
|
||||
get_eth_mac_address_raw(mac);
|
||||
char buf[18];
|
||||
format_mac_addr_upper(mac, buf);
|
||||
return std::string(buf);
|
||||
format_mac_addr_upper(mac, buf.data());
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
@@ -93,6 +94,7 @@ class EthernetComponent : public Component {
|
||||
void set_use_address(const char *use_address);
|
||||
void get_eth_mac_address_raw(uint8_t *mac);
|
||||
std::string get_eth_mac_address_pretty();
|
||||
const char *get_eth_mac_address_pretty_into_buffer(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf);
|
||||
eth_duplex_t get_duplex_mode();
|
||||
eth_speed_t get_link_speed();
|
||||
bool powerdown();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/components/watchdog/watchdog.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ def _final_validate(config):
|
||||
full_config = fv.full_config.get()[CONF_I2C]
|
||||
if CORE.using_zephyr and len(full_config) > 1:
|
||||
raise cv.Invalid("Second i2c is not implemented on Zephyr yet")
|
||||
if CORE.using_esp_idf and get_esp32_variant() in ESP32_I2C_CAPABILITIES:
|
||||
if CORE.is_esp32 and get_esp32_variant() in ESP32_I2C_CAPABILITIES:
|
||||
variant = get_esp32_variant()
|
||||
max_num = ESP32_I2C_CAPABILITIES[variant]["NUM"]
|
||||
if len(full_config) > max_num:
|
||||
@@ -237,10 +237,6 @@ def i2c_device_schema(default_address):
|
||||
"""
|
||||
schema = {
|
||||
cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus),
|
||||
cv.Optional("multiplexer"): cv.invalid(
|
||||
"This option has been removed, please see "
|
||||
"the tca9584a docs for the updated way to use multiplexers"
|
||||
),
|
||||
}
|
||||
if default_address is None:
|
||||
schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address
|
||||
|
||||
@@ -232,6 +232,8 @@ def validate_use_legacy(value):
|
||||
if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino):
|
||||
raise cv.Invalid("Arduino supports only the legacy i2s driver")
|
||||
_set_use_legacy_driver(value[CONF_USE_LEGACY])
|
||||
elif CORE.using_arduino:
|
||||
_set_use_legacy_driver(True)
|
||||
return value
|
||||
|
||||
|
||||
@@ -261,8 +263,7 @@ def _final_validate(_):
|
||||
|
||||
|
||||
def use_legacy():
|
||||
legacy_driver = _get_use_legacy_driver()
|
||||
return not (CORE.using_esp_idf and not legacy_driver)
|
||||
return _get_use_legacy_driver()
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -26,7 +26,7 @@ def validate_logger(config):
|
||||
logger_conf = fv.full_config.get()[CONF_LOGGER]
|
||||
if logger_conf[CONF_BAUD_RATE] == 0:
|
||||
raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
|
||||
if CORE.using_esp_idf and (
|
||||
if CORE.is_esp32 and (
|
||||
logger_conf[CONF_HARDWARE_UART] == USB_CDC
|
||||
and get_esp32_variant() == VARIANT_ESP32S3
|
||||
):
|
||||
|
||||
@@ -65,8 +65,8 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
uint16_t buffer_at = 0; // Initialize buffer position
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
|
||||
MAX_CONSOLE_LOG_MSG_SIZE);
|
||||
// Add newline if platform needs it (ESP32 doesn't add via write_msg_)
|
||||
this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
|
||||
// Add newline before writing to console
|
||||
this->add_newline_to_buffer_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
|
||||
this->write_msg_(console_buffer, buffer_at);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,17 +117,6 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128;
|
||||
// "0x" + 2 hex digits per byte + '\0'
|
||||
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
|
||||
|
||||
// Platform-specific: does write_msg_ add its own newline?
|
||||
// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, LibreTiny)
|
||||
// Allows single write call with newline included for efficiency
|
||||
// true: write_msg_ adds newline itself via puts()/println() (other platforms)
|
||||
// Newline should NOT be added to buffer
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
||||
static constexpr bool WRITE_MSG_ADDS_NEWLINE = false;
|
||||
#else
|
||||
static constexpr bool WRITE_MSG_ADDS_NEWLINE = true;
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
/** Enum for logging UART selection
|
||||
*
|
||||
@@ -259,22 +248,20 @@ class Logger : public Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to add newline to buffer for platforms that need it
|
||||
// Helper to add newline to buffer before writing to console
|
||||
// Modifies buffer_at to include the newline
|
||||
inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
||||
if constexpr (!WRITE_MSG_ADDS_NEWLINE) {
|
||||
// Add newline - don't need to maintain null termination
|
||||
// write_msg_ now always receives explicit length, so we can safely overwrite the null terminator
|
||||
// This is safe because:
|
||||
// 1. Callbacks already received the message (before we add newline)
|
||||
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
|
||||
if (*buffer_at < buffer_size) {
|
||||
buffer[(*buffer_at)++] = '\n';
|
||||
} else if (buffer_size > 0) {
|
||||
// Buffer was full - replace last char with newline to ensure it's visible
|
||||
buffer[buffer_size - 1] = '\n';
|
||||
*buffer_at = buffer_size;
|
||||
}
|
||||
inline void HOT add_newline_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
||||
// Add newline - don't need to maintain null termination
|
||||
// write_msg_ receives explicit length, so we can safely overwrite the null terminator
|
||||
// This is safe because:
|
||||
// 1. Callbacks already received the message (before we add newline)
|
||||
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
|
||||
if (*buffer_at < buffer_size) {
|
||||
buffer[(*buffer_at)++] = '\n';
|
||||
} else if (buffer_size > 0) {
|
||||
// Buffer was full - replace last char with newline to ensure it's visible
|
||||
buffer[buffer_size - 1] = '\n';
|
||||
*buffer_at = buffer_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +270,7 @@ class Logger : public Component {
|
||||
inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) {
|
||||
if (this->baud_rate_ > 0) {
|
||||
uint16_t *len_ptr = length ? length : &this->tx_buffer_at_;
|
||||
this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
|
||||
this->add_newline_to_buffer_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
|
||||
this->write_msg_(this->tx_buffer_ + offset, *len_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,23 @@
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) {
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) {
|
||||
static constexpr size_t TIMESTAMP_LEN = 10; // "[HH:MM:SS]"
|
||||
// tx_buffer_size_ defaults to 512, so 768 covers default + headroom
|
||||
char buffer[TIMESTAMP_LEN + 768];
|
||||
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
|
||||
fputs(buffer, stdout);
|
||||
puts(msg);
|
||||
struct tm *timeinfo = localtime(&rawtime);
|
||||
size_t pos = strftime(buffer, TIMESTAMP_LEN + 1, "[%H:%M:%S]", timeinfo);
|
||||
|
||||
// Copy message (with newline already included by caller)
|
||||
size_t copy_len = std::min(len, sizeof(buffer) - pos);
|
||||
memcpy(buffer + pos, msg, copy_len);
|
||||
pos += copy_len;
|
||||
|
||||
// Single write for everything
|
||||
fwrite(buffer, 1, pos, stdout);
|
||||
}
|
||||
|
||||
void Logger::pre_setup() { global_logger = this; }
|
||||
|
||||
@@ -27,7 +27,10 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); }
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) {
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
this->hw_serial_->write(msg, len);
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/usb/usb_device.h>
|
||||
|
||||
namespace esphome::logger {
|
||||
@@ -14,7 +15,7 @@ static const char *const TAG = "logger";
|
||||
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
void Logger::loop() {
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) {
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
static bool opened = false;
|
||||
@@ -62,18 +63,17 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) {
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) {
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("%s\n", msg);
|
||||
k_str_out(const_cast<char *>(msg), len);
|
||||
#endif
|
||||
if (nullptr == this->uart_dev_) {
|
||||
if (this->uart_dev_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
while (*msg) {
|
||||
uart_poll_out(this->uart_dev_, *msg);
|
||||
++msg;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
uart_poll_out(this->uart_dev_, msg[i]);
|
||||
}
|
||||
uart_poll_out(this->uart_dev_, '\n');
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
|
||||
@@ -256,9 +256,11 @@ async def to_code(configs):
|
||||
True,
|
||||
type=lv_font_t.operator("ptr").operator("const"),
|
||||
)
|
||||
# static=False because LV_FONT_CUSTOM_DECLARE creates an extern declaration
|
||||
cg.new_variable(
|
||||
globfont_id,
|
||||
MockObj(await lvalid.lv_font.process(default_font), "->").get_lv_font(),
|
||||
static=False,
|
||||
)
|
||||
add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT)
|
||||
else:
|
||||
|
||||
@@ -5,7 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.const import CONF_ITEMS
|
||||
@@ -96,13 +96,9 @@ class LValidator:
|
||||
return None
|
||||
if isinstance(value, Lambda):
|
||||
# Local import to avoid circular import
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
args = args or get_lambda_context_args()
|
||||
return cg.RawExpression(
|
||||
call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import image
|
||||
@@ -404,14 +404,9 @@ class TextValidator(LValidator):
|
||||
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
|
||||
) -> Expression:
|
||||
# Local import to avoid circular import at module level
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
args = args or get_lambda_context_args()
|
||||
|
||||
if isinstance(value, dict):
|
||||
if format_str := value.get(CONF_FORMAT):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from esphome import codegen as cg
|
||||
from esphome.config import Config
|
||||
@@ -200,6 +201,21 @@ class LvContext(LambdaContext):
|
||||
return self.add(*args)
|
||||
|
||||
|
||||
def get_lambda_context_args() -> list[tuple[SafeExpType, str]]:
|
||||
"""Get automation parameters from the current lambda context if available.
|
||||
|
||||
When called from outside LVGL's context (e.g., from interval),
|
||||
CodeContext.code_context will be None, so return empty args.
|
||||
"""
|
||||
if CodeContext.code_context is None:
|
||||
return []
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext base class doesn't define get_automation_parameters(),
|
||||
# but LambdaContext and LvContext (the concrete implementations) do.
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
return CodeContext.code_context.get_automation_parameters()
|
||||
|
||||
|
||||
class LocalVariable(MockObj):
|
||||
"""
|
||||
Create a local variable and enclose the code using it within a block.
|
||||
@@ -337,7 +353,7 @@ def lv_Pvariable(type, name) -> MockObj:
|
||||
"""
|
||||
if isinstance(name, str):
|
||||
name = ID(name, True, type)
|
||||
decl = VariableDeclarationExpression(type, "*", name)
|
||||
decl = VariableDeclarationExpression(type, "*", name, static=True)
|
||||
CORE.add_global(decl)
|
||||
var = MockObj(name, "->")
|
||||
CORE.register_variable(name, var)
|
||||
@@ -353,7 +369,7 @@ def lv_variable(type, name) -> MockObj:
|
||||
"""
|
||||
if isinstance(name, str):
|
||||
name = ID(name, True, type)
|
||||
decl = VariableDeclarationExpression(type, "", name)
|
||||
decl = VariableDeclarationExpression(type, "", name, static=True)
|
||||
CORE.add_global(decl)
|
||||
var = MockObj(name, ".")
|
||||
CORE.register_variable(name, var)
|
||||
|
||||
@@ -133,7 +133,7 @@ async def to_code(config):
|
||||
value_type,
|
||||
)
|
||||
var = MockObj(varid, ".")
|
||||
decl = VariableDeclarationExpression(varid.type, "", varid)
|
||||
decl = VariableDeclarationExpression(varid.type, "", varid, static=True)
|
||||
add_global(decl)
|
||||
CORE.register_variable(varid, var)
|
||||
|
||||
|
||||
@@ -157,14 +157,12 @@ async def to_code(config):
|
||||
return
|
||||
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("ESPmDNS", None)
|
||||
elif CORE.is_esp8266:
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266mDNS", None)
|
||||
elif CORE.is_rp2040:
|
||||
cg.add_library("LEAmDNS", None)
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
if CORE.is_esp32:
|
||||
add_idf_component(name="espressif/mdns", ref="1.9.1")
|
||||
|
||||
cg.add_define("USE_MDNS")
|
||||
|
||||
@@ -368,7 +368,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_esp_idf,
|
||||
cv.only_on_esp32,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "micro_wake_word.h"
|
||||
#include "streaming_model.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
namespace esphome {
|
||||
namespace micro_wake_word {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "micro_wake_word.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
@@ -473,4 +473,4 @@ bool MicroWakeWord::update_model_probabilities_(const int8_t audio_features[PREP
|
||||
} // namespace micro_wake_word
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "preprocessor_settings.h"
|
||||
#include "streaming_model.h"
|
||||
@@ -140,4 +140,4 @@ class MicroWakeWord : public Component
|
||||
} // namespace micro_wake_word
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "streaming_model.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "preprocessor_settings.h"
|
||||
|
||||
|
||||
@@ -165,8 +165,8 @@ def model_schema(config):
|
||||
)
|
||||
return cv.All(
|
||||
schema,
|
||||
cv.only_on_esp32,
|
||||
only_on_variant(supported=[VARIANT_ESP32P4]),
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -224,8 +224,8 @@ def _config_schema(config):
|
||||
schema = model_schema(config)
|
||||
return cv.All(
|
||||
schema,
|
||||
cv.only_on_esp32,
|
||||
only_on_variant(supported=[VARIANT_ESP32S3]),
|
||||
cv.only_with_esp_idf,
|
||||
)(config)
|
||||
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ def model_schema(config):
|
||||
}
|
||||
)
|
||||
if bus_mode != TYPE_SINGLE:
|
||||
return cv.All(schema, cv.only_with_esp_idf)
|
||||
return cv.All(schema, cv.only_on_esp32)
|
||||
return schema
|
||||
|
||||
|
||||
|
||||
@@ -93,9 +93,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_NUM_CHANNELS): cv.int_range(min=1, max=2),
|
||||
cv.Optional(CONF_QUEUE_MODE, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_TASK_STACK_IN_PSRAM, esp32_idf=False): cv.All(
|
||||
cv.boolean, cv.only_with_esp_idf
|
||||
),
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.only_on([PLATFORM_ESP32]),
|
||||
|
||||
@@ -232,16 +232,6 @@ void MQTTBackendESP32::esphome_mqtt_task(void *params) {
|
||||
this_mqtt->mqtt_event_pool_.release(elem);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any remaining items in the queue
|
||||
struct QueueElement *elem;
|
||||
while ((elem = this_mqtt->mqtt_queue_.pop()) != nullptr) {
|
||||
this_mqtt->mqtt_event_pool_.release(elem);
|
||||
}
|
||||
|
||||
// Note: EventPool destructor will clean up the pool itself
|
||||
// Task will delete itself
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, bool retain, const char *payload,
|
||||
|
||||
@@ -156,7 +156,7 @@ async def to_code(config):
|
||||
"High performance networking disabled by user configuration (overriding component request)"
|
||||
)
|
||||
|
||||
if CORE.is_esp32 and CORE.using_esp_idf and should_enable:
|
||||
if CORE.is_esp32 and should_enable:
|
||||
# Check if PSRAM is guaranteed (set by psram component during final validation)
|
||||
psram_guaranteed = psram_is_guaranteed()
|
||||
|
||||
@@ -210,12 +210,12 @@ async def to_code(config):
|
||||
"USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
|
||||
)
|
||||
if CORE.is_esp32:
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6)
|
||||
else:
|
||||
if CORE.using_arduino:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True)
|
||||
else:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", enable_ipv6)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", enable_ipv6)
|
||||
elif enable_ipv6:
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6")
|
||||
cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/macros.h"
|
||||
|
||||
#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0)
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0)
|
||||
#include <lwip/ip_addr.h>
|
||||
#endif
|
||||
|
||||
@@ -40,6 +40,9 @@ using ip4_addr_t = in_addr;
|
||||
namespace esphome {
|
||||
namespace network {
|
||||
|
||||
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
|
||||
static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40;
|
||||
|
||||
struct IPAddress {
|
||||
public:
|
||||
#ifdef USE_HOST
|
||||
@@ -50,6 +53,10 @@ struct IPAddress {
|
||||
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
|
||||
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
|
||||
std::string str() const { return str_lower_case(inet_ntoa(ip_addr_)); }
|
||||
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
||||
char *str_to(char *buf) const {
|
||||
return const_cast<char *>(inet_ntop(AF_INET, &ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE));
|
||||
}
|
||||
#else
|
||||
IPAddress() { ip_addr_set_zero(&ip_addr_); }
|
||||
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
||||
@@ -128,6 +135,8 @@ struct IPAddress {
|
||||
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
||||
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
|
||||
/// Write IP address to buffer. Buffer must be at least IP_ADDRESS_BUFFER_SIZE bytes.
|
||||
char *str_to(char *buf) const { return ipaddr_ntoa_r(&ip_addr_, buf, IP_ADDRESS_BUFFER_SIZE); }
|
||||
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
IPAddress &operator+=(uint8_t increase) {
|
||||
|
||||
@@ -49,7 +49,8 @@ void OneWireBus::search() {
|
||||
break;
|
||||
auto *address8 = reinterpret_cast<uint8_t *>(&address);
|
||||
if (crc8(address8, 7) != address8[7]) {
|
||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
|
||||
char hex_buf[17];
|
||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex_to(hex_buf, address));
|
||||
} else {
|
||||
this->devices_.push_back(address);
|
||||
}
|
||||
@@ -82,8 +83,9 @@ void OneWireBus::dump_devices_(const char *tag) {
|
||||
ESP_LOGW(tag, " Found no devices!");
|
||||
} else {
|
||||
ESP_LOGCONFIG(tag, " Found devices:");
|
||||
char hex_buf[17]; // uint64_t = 16 hex chars + null
|
||||
for (auto &address : this->devices_) {
|
||||
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff)));
|
||||
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex_to(hex_buf, address), LOG_STR_ARG(get_model_str(address & 0xff)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
).extend(_CONNECTION_SCHEMA),
|
||||
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
||||
cv.only_with_esp_idf,
|
||||
only_on_variant(supported=[VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2]),
|
||||
_validate,
|
||||
_require_vfs_select,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#if defined(USE_OPENTHREAD) && defined(USE_ESP_IDF)
|
||||
#if defined(USE_OPENTHREAD) && defined(USE_ESP32)
|
||||
#include <openthread/logging.h>
|
||||
#include "openthread.h"
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"ota_backend_arduino_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"ota_backend_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"ota_backend_arduino_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"ota_backend_arduino_libretiny.cpp": {
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef USE_ESP8266
|
||||
#include "ota_backend_arduino_esp8266.h"
|
||||
#include "ota_backend.h"
|
||||
|
||||
#include "esphome/components/esp8266/preferences.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <Updater.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ota {
|
||||
|
||||
static const char *const TAG = "ota.arduino_esp8266";
|
||||
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP8266OTABackend>(); }
|
||||
|
||||
OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
|
||||
if (image_size == 0) {
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
image_size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
||||
}
|
||||
bool ret = Update.begin(image_size, U_FLASH);
|
||||
if (ret) {
|
||||
esp8266::preferences_prevent_write(true);
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
uint8_t error = Update.getError();
|
||||
if (error == UPDATE_ERROR_BOOTSTRAP)
|
||||
return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING;
|
||||
if (error == UPDATE_ERROR_NEW_FLASH_CONFIG)
|
||||
return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG;
|
||||
if (error == UPDATE_ERROR_FLASH_CONFIG)
|
||||
return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
|
||||
if (error == UPDATE_ERROR_SPACE)
|
||||
return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE;
|
||||
|
||||
ESP_LOGE(TAG, "Begin error: %d", error);
|
||||
|
||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
void ArduinoESP8266OTABackend::set_update_md5(const char *md5) {
|
||||
Update.setMD5(md5);
|
||||
this->md5_set_ = true;
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) {
|
||||
size_t written = Update.write(data, len);
|
||||
if (written == len) {
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
uint8_t error = Update.getError();
|
||||
ESP_LOGE(TAG, "Write error: %d", error);
|
||||
|
||||
return OTA_RESPONSE_ERROR_WRITING_FLASH;
|
||||
}
|
||||
|
||||
OTAResponseTypes ArduinoESP8266OTABackend::end() {
|
||||
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||
// This matches the behavior of the old web_server OTA implementation
|
||||
bool success = Update.end(!this->md5_set_);
|
||||
|
||||
// On ESP8266, Update.end() might return false even with error code 0
|
||||
// Check the actual error code to determine success
|
||||
uint8_t error = Update.getError();
|
||||
|
||||
if (success || error == UPDATE_ERROR_OK) {
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "End error: %d", error);
|
||||
return OTA_RESPONSE_ERROR_UPDATE_END;
|
||||
}
|
||||
|
||||
void ArduinoESP8266OTABackend::abort() {
|
||||
Update.end();
|
||||
esp8266::preferences_prevent_write(false);
|
||||
}
|
||||
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef USE_ESP8266
|
||||
#include "ota_backend.h"
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/macros.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ota {
|
||||
|
||||
class ArduinoESP8266OTABackend : public OTABackend {
|
||||
public:
|
||||
OTAResponseTypes begin(size_t image_size) override;
|
||||
void set_update_md5(const char *md5) override;
|
||||
OTAResponseTypes write(uint8_t *data, size_t len) override;
|
||||
OTAResponseTypes end() override;
|
||||
void abort() override;
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
|
||||
bool supports_compression() override { return true; }
|
||||
#else
|
||||
bool supports_compression() override { return false; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace ota
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif
|
||||
356
esphome/components/ota/ota_backend_esp8266.cpp
Normal file
356
esphome/components/ota/ota_backend_esp8266.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#ifdef USE_ESP8266
|
||||
#include "ota_backend_esp8266.h"
|
||||
#include "ota_backend.h"
|
||||
|
||||
#include "esphome/components/esp8266/preferences.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <Esp.h>
|
||||
#include <esp8266_peri.h>
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
extern "C" {
|
||||
#include <c_types.h>
|
||||
#include <eboot_command.h>
|
||||
#include <flash_hal.h>
|
||||
#include <spi_flash.h>
|
||||
#include <user_interface.h>
|
||||
}
|
||||
|
||||
// Note: FLASH_SECTOR_SIZE (0x1000) is already defined in spi_flash_geometry.h
|
||||
|
||||
// Flash header offsets
|
||||
static constexpr uint8_t FLASH_MODE_OFFSET = 2;
|
||||
|
||||
// Firmware magic bytes
|
||||
static constexpr uint8_t FIRMWARE_MAGIC = 0xE9;
|
||||
static constexpr uint8_t GZIP_MAGIC_1 = 0x1F;
|
||||
static constexpr uint8_t GZIP_MAGIC_2 = 0x8B;
|
||||
|
||||
// ESP8266 flash memory base address (memory-mapped flash starts here)
|
||||
static constexpr uint32_t FLASH_BASE_ADDRESS = 0x40200000;
|
||||
|
||||
// Boot mode extraction from GPI register (bits 16-19 contain boot mode)
|
||||
static constexpr int BOOT_MODE_SHIFT = 16;
|
||||
static constexpr int BOOT_MODE_MASK = 0xf;
|
||||
|
||||
// Boot mode indicating UART download mode (OTA not possible)
|
||||
static constexpr int BOOT_MODE_UART_DOWNLOAD = 1;
|
||||
|
||||
// Minimum buffer size when memory is constrained
|
||||
static constexpr size_t MIN_BUFFER_SIZE = 256;
|
||||
|
||||
namespace esphome::ota {
|
||||
|
||||
static const char *const TAG = "ota.esp8266";
|
||||
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ESP8266OTABackend>(); }
|
||||
|
||||
OTAResponseTypes ESP8266OTABackend::begin(size_t image_size) {
|
||||
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
|
||||
if (image_size == 0) {
|
||||
// Round down to sector boundary: subtract one sector, then mask to sector alignment
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
image_size = (ESP.getFreeSketchSpace() - FLASH_SECTOR_SIZE) & ~(FLASH_SECTOR_SIZE - 1);
|
||||
}
|
||||
|
||||
// Check boot mode - if boot mode is UART download mode,
|
||||
// we will not be able to reset into normal mode once update is done
|
||||
int boot_mode = (GPI >> BOOT_MODE_SHIFT) & BOOT_MODE_MASK;
|
||||
if (boot_mode == BOOT_MODE_UART_DOWNLOAD) {
|
||||
return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING;
|
||||
}
|
||||
|
||||
// Check flash configuration - real size must be >= configured size
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
if (!ESP.checkFlashConfig(false)) {
|
||||
return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
|
||||
}
|
||||
|
||||
// Get current sketch size
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
uint32_t sketch_size = ESP.getSketchSize();
|
||||
|
||||
// Size of current sketch rounded to sector boundary
|
||||
uint32_t current_sketch_size = (sketch_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
||||
|
||||
// Size of update rounded to sector boundary
|
||||
uint32_t rounded_size = (image_size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
||||
|
||||
// End of available space for sketch and update (start of filesystem)
|
||||
uint32_t update_end_address = FS_start - FLASH_BASE_ADDRESS;
|
||||
|
||||
// Calculate start address for the update (write from end backwards)
|
||||
this->start_address_ = (update_end_address > rounded_size) ? (update_end_address - rounded_size) : 0;
|
||||
|
||||
// Check if there's enough space for both current sketch and update
|
||||
if (this->start_address_ < current_sketch_size) {
|
||||
return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE;
|
||||
}
|
||||
|
||||
// Allocate buffer for sector writes (use smaller buffer if memory constrained)
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
this->buffer_size_ = (ESP.getFreeHeap() > 2 * FLASH_SECTOR_SIZE) ? FLASH_SECTOR_SIZE : MIN_BUFFER_SIZE;
|
||||
|
||||
// ESP8266's umm_malloc guarantees 4-byte aligned allocations, which is required
|
||||
// for spi_flash_write(). This is the same pattern used by Arduino's Updater class.
|
||||
this->buffer_ = make_unique<uint8_t[]>(this->buffer_size_);
|
||||
if (!this->buffer_) {
|
||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
this->current_address_ = this->start_address_;
|
||||
this->image_size_ = image_size;
|
||||
this->buffer_len_ = 0;
|
||||
this->md5_set_ = false;
|
||||
|
||||
// Disable WiFi sleep during update
|
||||
wifi_set_sleep_type(NONE_SLEEP_T);
|
||||
|
||||
// Prevent preference writes during update
|
||||
esp8266::preferences_prevent_write(true);
|
||||
|
||||
// Initialize MD5 computation
|
||||
this->md5_.init();
|
||||
|
||||
ESP_LOGD(TAG, "OTA begin: start=0x%08" PRIX32 ", size=%zu", this->start_address_, image_size);
|
||||
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
void ESP8266OTABackend::set_update_md5(const char *md5) {
|
||||
// Parse hex string to bytes
|
||||
if (parse_hex(md5, this->expected_md5_, 16)) {
|
||||
this->md5_set_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
OTAResponseTypes ESP8266OTABackend::write(uint8_t *data, size_t len) {
|
||||
if (!this->buffer_) {
|
||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
size_t written = 0;
|
||||
while (written < len) {
|
||||
// Calculate how much we can buffer
|
||||
size_t to_buffer = std::min(len - written, this->buffer_size_ - this->buffer_len_);
|
||||
memcpy(this->buffer_.get() + this->buffer_len_, data + written, to_buffer);
|
||||
this->buffer_len_ += to_buffer;
|
||||
written += to_buffer;
|
||||
|
||||
// If buffer is full, write to flash
|
||||
if (this->buffer_len_ == this->buffer_size_ && !this->write_buffer_()) {
|
||||
return OTA_RESPONSE_ERROR_WRITING_FLASH;
|
||||
}
|
||||
}
|
||||
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
bool ESP8266OTABackend::erase_sector_if_needed_() {
|
||||
if ((this->current_address_ % FLASH_SECTOR_SIZE) != 0) {
|
||||
return true; // Not at sector boundary
|
||||
}
|
||||
|
||||
App.feed_wdt();
|
||||
if (spi_flash_erase_sector(this->current_address_ / FLASH_SECTOR_SIZE) != SPI_FLASH_RESULT_OK) {
|
||||
ESP_LOGE(TAG, "Flash erase failed at 0x%08" PRIX32, this->current_address_);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESP8266OTABackend::flash_write_() {
|
||||
App.feed_wdt();
|
||||
if (spi_flash_write(this->current_address_, reinterpret_cast<uint32_t *>(this->buffer_.get()), this->buffer_len_) !=
|
||||
SPI_FLASH_RESULT_OK) {
|
||||
ESP_LOGE(TAG, "Flash write failed at 0x%08" PRIX32, this->current_address_);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESP8266OTABackend::write_buffer_() {
|
||||
if (this->buffer_len_ == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this->erase_sector_if_needed_()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Patch flash mode in first sector if needed
|
||||
// This is analogous to what esptool.py does when it receives a --flash_mode argument
|
||||
bool is_first_sector = (this->current_address_ == this->start_address_);
|
||||
uint8_t original_flash_mode = 0;
|
||||
bool patched_flash_mode = false;
|
||||
|
||||
// Only patch if we have enough bytes to access flash mode offset and it's not GZIP
|
||||
if (is_first_sector && this->buffer_len_ > FLASH_MODE_OFFSET && this->buffer_[0] != GZIP_MAGIC_1) {
|
||||
// Not GZIP compressed - check and patch flash mode
|
||||
uint8_t current_flash_mode = this->get_flash_chip_mode_();
|
||||
uint8_t buffer_flash_mode = this->buffer_[FLASH_MODE_OFFSET];
|
||||
|
||||
if (buffer_flash_mode != current_flash_mode) {
|
||||
original_flash_mode = buffer_flash_mode;
|
||||
this->buffer_[FLASH_MODE_OFFSET] = current_flash_mode;
|
||||
patched_flash_mode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->flash_write_()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore original flash mode for MD5 calculation
|
||||
if (patched_flash_mode) {
|
||||
this->buffer_[FLASH_MODE_OFFSET] = original_flash_mode;
|
||||
}
|
||||
|
||||
// Update MD5 with original (unpatched) data
|
||||
this->md5_.add(this->buffer_.get(), this->buffer_len_);
|
||||
|
||||
this->current_address_ += this->buffer_len_;
|
||||
this->buffer_len_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESP8266OTABackend::write_buffer_final_() {
|
||||
// Similar to write_buffer_(), but without flash mode patching or MD5 update (for final padded write)
|
||||
if (this->buffer_len_ == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this->erase_sector_if_needed_() || !this->flash_write_()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->current_address_ += this->buffer_len_;
|
||||
this->buffer_len_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OTAResponseTypes ESP8266OTABackend::end() {
|
||||
// Write any remaining buffered data
|
||||
if (this->buffer_len_ > 0) {
|
||||
// Add actual data to MD5 before padding
|
||||
this->md5_.add(this->buffer_.get(), this->buffer_len_);
|
||||
|
||||
// Pad to 4-byte alignment for flash write
|
||||
while (this->buffer_len_ % 4 != 0) {
|
||||
this->buffer_[this->buffer_len_++] = 0xFF;
|
||||
}
|
||||
if (!this->write_buffer_final_()) {
|
||||
this->abort();
|
||||
return OTA_RESPONSE_ERROR_WRITING_FLASH;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate actual bytes written
|
||||
size_t actual_size = this->current_address_ - this->start_address_;
|
||||
|
||||
// Check if any data was written
|
||||
if (actual_size == 0) {
|
||||
ESP_LOGE(TAG, "No data written");
|
||||
this->abort();
|
||||
return OTA_RESPONSE_ERROR_UPDATE_END;
|
||||
}
|
||||
|
||||
// Verify MD5 if set (strict mode), otherwise use lenient mode
|
||||
// In lenient mode (no MD5), we accept whatever was written
|
||||
if (this->md5_set_) {
|
||||
this->md5_.calculate();
|
||||
if (!this->md5_.equals_bytes(this->expected_md5_)) {
|
||||
ESP_LOGE(TAG, "MD5 mismatch");
|
||||
this->abort();
|
||||
return OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
}
|
||||
} else {
|
||||
// Lenient mode: adjust size to what was actually written
|
||||
// This matches Arduino's Update.end(true) behavior
|
||||
this->image_size_ = actual_size;
|
||||
}
|
||||
|
||||
// Verify firmware header
|
||||
if (!this->verify_end_()) {
|
||||
this->abort();
|
||||
return OTA_RESPONSE_ERROR_UPDATE_END;
|
||||
}
|
||||
|
||||
// Write eboot command to copy firmware on next boot
|
||||
eboot_command ebcmd;
|
||||
ebcmd.action = ACTION_COPY_RAW;
|
||||
ebcmd.args[0] = this->start_address_;
|
||||
ebcmd.args[1] = 0x00000; // Destination: start of flash
|
||||
ebcmd.args[2] = this->image_size_;
|
||||
eboot_command_write(&ebcmd);
|
||||
|
||||
ESP_LOGI(TAG, "OTA update staged: 0x%08" PRIX32 " -> 0x00000, size=%zu", this->start_address_, this->image_size_);
|
||||
|
||||
// Clean up
|
||||
this->buffer_.reset();
|
||||
esp8266::preferences_prevent_write(false);
|
||||
|
||||
return OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
void ESP8266OTABackend::abort() {
|
||||
this->buffer_.reset();
|
||||
this->buffer_len_ = 0;
|
||||
this->image_size_ = 0;
|
||||
esp8266::preferences_prevent_write(false);
|
||||
}
|
||||
|
||||
bool ESP8266OTABackend::verify_end_() {
|
||||
uint32_t buf;
|
||||
if (spi_flash_read(this->start_address_, &buf, 4) != SPI_FLASH_RESULT_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read firmware header");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *bytes = reinterpret_cast<uint8_t *>(&buf);
|
||||
|
||||
// Check for GZIP (compressed firmware)
|
||||
if (bytes[0] == GZIP_MAGIC_1 && bytes[1] == GZIP_MAGIC_2) {
|
||||
// GZIP compressed - can't verify further
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check firmware magic byte
|
||||
if (bytes[0] != FIRMWARE_MAGIC) {
|
||||
ESP_LOGE(TAG, "Invalid firmware magic: 0x%02X (expected 0x%02X)", bytes[0], FIRMWARE_MAGIC);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !FLASH_MAP_SUPPORT
|
||||
// Check if new firmware's flash size fits (only when auto-detection is disabled)
|
||||
// With FLASH_MAP_SUPPORT (modern cores), flash size is auto-detected from chip
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
uint32_t bin_flash_size = ESP.magicFlashChipSize((bytes[3] & 0xf0) >> 4);
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
if (bin_flash_size > ESP.getFlashChipRealSize()) {
|
||||
ESP_LOGE(TAG, "Firmware flash size (%" PRIu32 ") exceeds chip size (%" PRIu32 ")", bin_flash_size,
|
||||
ESP.getFlashChipRealSize());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t ESP8266OTABackend::get_flash_chip_mode_() {
|
||||
uint32_t data;
|
||||
if (spi_flash_read(0x0000, &data, 4) != SPI_FLASH_RESULT_OK) {
|
||||
return 0; // Default to QIO
|
||||
}
|
||||
return (reinterpret_cast<uint8_t *>(&data))[FLASH_MODE_OFFSET];
|
||||
}
|
||||
|
||||
} // namespace esphome::ota
|
||||
#endif // USE_ESP8266
|
||||
58
esphome/components/ota/ota_backend_esp8266.h
Normal file
58
esphome/components/ota/ota_backend_esp8266.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP8266
|
||||
#include "ota_backend.h"
|
||||
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace esphome::ota {
|
||||
|
||||
/// OTA backend for ESP8266 using native SDK functions.
|
||||
/// This implementation bypasses the Arduino Updater library to save ~228 bytes of RAM
|
||||
/// by not having a global Update object in .bss.
|
||||
class ESP8266OTABackend : public OTABackend {
|
||||
public:
|
||||
OTAResponseTypes begin(size_t image_size) override;
|
||||
void set_update_md5(const char *md5) override;
|
||||
OTAResponseTypes write(uint8_t *data, size_t len) override;
|
||||
OTAResponseTypes end() override;
|
||||
void abort() override;
|
||||
// Compression supported in all ESP8266 Arduino versions ESPHome supports (>= 2.7.0)
|
||||
bool supports_compression() override { return true; }
|
||||
|
||||
protected:
|
||||
/// Erase flash sector if current address is at sector boundary
|
||||
bool erase_sector_if_needed_();
|
||||
|
||||
/// Write buffer to flash (does not update address or clear buffer)
|
||||
bool flash_write_();
|
||||
|
||||
/// Write buffered data to flash and update MD5
|
||||
bool write_buffer_();
|
||||
|
||||
/// Write buffered data to flash without MD5 update (for final padded write)
|
||||
bool write_buffer_final_();
|
||||
|
||||
/// Verify the firmware header is valid
|
||||
bool verify_end_();
|
||||
|
||||
/// Get current flash chip mode from flash header
|
||||
uint8_t get_flash_chip_mode_();
|
||||
|
||||
std::unique_ptr<uint8_t[]> buffer_;
|
||||
size_t buffer_size_{0};
|
||||
size_t buffer_len_{0};
|
||||
|
||||
uint32_t start_address_{0};
|
||||
uint32_t current_address_{0};
|
||||
size_t image_size_{0};
|
||||
|
||||
md5::MD5Digest md5_{};
|
||||
uint8_t expected_md5_[16]; // Fixed-size buffer for 128-bit (16-byte) MD5 digest
|
||||
bool md5_set_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::ota
|
||||
#endif // USE_ESP8266
|
||||
@@ -162,14 +162,14 @@ void PIDClimate::start_autotune(std::unique_ptr<PIDAutotuner> &&autotune) {
|
||||
float min_value = this->supports_cool_() ? -1.0f : 0.0f;
|
||||
float max_value = this->supports_heat_() ? 1.0f : 0.0f;
|
||||
this->autotuner_->config(min_value, max_value);
|
||||
this->autotuner_->set_autotuner_id(this->get_object_id());
|
||||
this->autotuner_->set_autotuner_id(this->get_name());
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"%s: Autotune has started. This can take a long time depending on the "
|
||||
"responsiveness of your system. Your system "
|
||||
"output will be altered to deliberately oscillate above and below the setpoint multiple times. "
|
||||
"Until your sensor provides a reading, the autotuner may display \'nan\'",
|
||||
this->get_object_id().c_str());
|
||||
this->get_name().c_str());
|
||||
|
||||
this->set_interval("autotune-progress", 10000, [this]() {
|
||||
if (this->autotuner_ != nullptr && !this->autotuner_->is_finished())
|
||||
@@ -178,7 +178,7 @@ void PIDClimate::start_autotune(std::unique_ptr<PIDAutotuner> &&autotune) {
|
||||
|
||||
if (mode != climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!",
|
||||
this->get_object_id().c_str());
|
||||
this->get_name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,11 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
|
||||
|
||||
std::string PrometheusHandler::relabel_id_(EntityBase *obj) {
|
||||
auto item = relabel_map_id_.find(obj);
|
||||
return item == relabel_map_id_.end() ? obj->get_object_id() : item->second;
|
||||
if (item != relabel_map_id_.end()) {
|
||||
return item->second;
|
||||
}
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
return obj->get_object_id_to(object_id_buf).str();
|
||||
}
|
||||
|
||||
std::string PrometheusHandler::relabel_name_(EntityBase *obj) {
|
||||
|
||||
@@ -154,7 +154,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
upper=True,
|
||||
key=CONF_MODEL,
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
cv.only_on_esp32,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#if defined(USE_ESP_IDF) && defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "qspi_dbi.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#if defined(USE_ESP_IDF) && defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#if defined(USE_ESP32) && defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
|
||||
@@ -108,9 +108,6 @@ def register_trigger(name, type, data_type):
|
||||
validator = automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type),
|
||||
cv.Optional(CONF_RECEIVER_ID): cv.invalid(
|
||||
"This has been removed in ESPHome 2022.3.0 and the trigger attaches directly to the parent receiver."
|
||||
),
|
||||
}
|
||||
)
|
||||
registerer = TRIGGER_REGISTRY.register(f"on_{name}", validator)
|
||||
@@ -207,13 +204,7 @@ validate_binary_sensor = cv.validate_registry_entry(
|
||||
"remote receiver", BINARY_SENSOR_REGISTRY
|
||||
)
|
||||
TRIGGER_REGISTRY = SimpleRegistry()
|
||||
DUMPER_REGISTRY = Registry(
|
||||
{
|
||||
cv.Optional(CONF_RECEIVER_ID): cv.invalid(
|
||||
"This has been removed in ESPHome 1.20.0 and the dumper attaches directly to the parent receiver."
|
||||
),
|
||||
}
|
||||
)
|
||||
DUMPER_REGISTRY = Registry()
|
||||
|
||||
|
||||
def validate_dumpers(value):
|
||||
@@ -480,10 +471,6 @@ COOLIX_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_FIRST): cv.hex_int_range(0, 16777215),
|
||||
cv.Optional(CONF_SECOND, default=0): cv.hex_int_range(0, 16777215),
|
||||
cv.Optional(CONF_DATA): cv.invalid(
|
||||
"'data' option has been removed in ESPHome 2023.8. "
|
||||
"Use the 'first' and 'second' options instead."
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -122,7 +122,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
),
|
||||
only_on_variant(supported=[VARIANT_ESP32S3]),
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -141,7 +141,14 @@ uint32_t SafeModeComponent::read_rtc_() {
|
||||
return val;
|
||||
}
|
||||
|
||||
void SafeModeComponent::clean_rtc() { this->write_rtc_(0); }
|
||||
void SafeModeComponent::clean_rtc() {
|
||||
// Save without sync - preferences will be written at shutdown or by IntervalSyncer.
|
||||
// This avoids blocking the loop for 50+ ms on flash write. If the device crashes
|
||||
// before sync, the boot wasn't really successful anyway and the counter should
|
||||
// remain incremented.
|
||||
uint32_t val = 0;
|
||||
this->rtc_.save(&val);
|
||||
}
|
||||
|
||||
void SafeModeComponent::on_safe_shutdown() {
|
||||
if (this->read_rtc_() != SafeModeComponent::ENTER_SAFE_MODE_MAGIC)
|
||||
|
||||
@@ -304,9 +304,6 @@ _SENSOR_SCHEMA = (
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_STATE_CLASS): validate_state_class,
|
||||
cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category,
|
||||
cv.Optional("last_reset_type"): cv.invalid(
|
||||
"last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values."
|
||||
),
|
||||
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_EXPIRE_AFTER): cv.All(
|
||||
cv.requires_component("mqtt"),
|
||||
|
||||
@@ -315,7 +315,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ON_VOLUME): automation.validate_automation(single=True),
|
||||
}
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
cv.only_on_esp32,
|
||||
_validate_repeated_speaker,
|
||||
_request_high_performance_networking,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "audio_pipeline.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/audio/audio.h"
|
||||
#include "esphome/components/audio/audio_reader.h"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "speaker_media_player.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/audio/audio.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "speaker_media_player.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "audio_pipeline.h"
|
||||
|
||||
|
||||
@@ -311,7 +311,7 @@ def spi_mode_schema(mode):
|
||||
if mode == TYPE_SINGLE:
|
||||
return SPI_SINGLE_SCHEMA
|
||||
pin_count = 4 if mode == TYPE_QUAD else 8
|
||||
onlys = [cv.only_on([PLATFORM_ESP32]), cv.only_with_esp_idf]
|
||||
onlys = [cv.only_on([PLATFORM_ESP32])]
|
||||
if pin_count == 8:
|
||||
onlys.append(
|
||||
only_on_variant(
|
||||
@@ -399,7 +399,7 @@ def spi_device_schema(
|
||||
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
|
||||
SPI_MODE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_with_esp_idf),
|
||||
cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32),
|
||||
}
|
||||
if cs_pin_required:
|
||||
schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema
|
||||
|
||||
@@ -19,6 +19,7 @@ from esphome.const import (
|
||||
UNIT_MINUTE,
|
||||
UNIT_SECOND,
|
||||
)
|
||||
from esphome.helpers import docs_url
|
||||
|
||||
AUTO_LOAD = ["number", "switch"]
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
@@ -162,55 +163,9 @@ def validate_sprinkler(config):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_RUN_DURATION} must be greater than {CONF_VALVE_OPEN_DELAY}"
|
||||
)
|
||||
if (
|
||||
CONF_PUMP_OFF_SWITCH_ID in valve and CONF_PUMP_ON_SWITCH_ID not in valve
|
||||
) or (
|
||||
CONF_PUMP_ON_SWITCH_ID in valve and CONF_PUMP_OFF_SWITCH_ID not in valve
|
||||
):
|
||||
if CONF_VALVE_SWITCH_ID not in valve:
|
||||
raise cv.Invalid(
|
||||
f"Both {CONF_PUMP_OFF_SWITCH_ID} and {CONF_PUMP_ON_SWITCH_ID} must be specified for latching pump configuration"
|
||||
)
|
||||
if CONF_PUMP_SWITCH_ID in valve and (
|
||||
CONF_PUMP_OFF_SWITCH_ID in valve or CONF_PUMP_ON_SWITCH_ID in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Do not specify {CONF_PUMP_OFF_SWITCH_ID} or {CONF_PUMP_ON_SWITCH_ID} when using {CONF_PUMP_SWITCH_ID}"
|
||||
)
|
||||
if CONF_PUMP_PULSE_DURATION not in sprinkler_controller and (
|
||||
CONF_PUMP_OFF_SWITCH_ID in valve or CONF_PUMP_ON_SWITCH_ID in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_PUMP_PULSE_DURATION} must be specified when using {CONF_PUMP_OFF_SWITCH_ID} and {CONF_PUMP_ON_SWITCH_ID}"
|
||||
)
|
||||
if (
|
||||
CONF_VALVE_OFF_SWITCH_ID in valve
|
||||
and CONF_VALVE_ON_SWITCH_ID not in valve
|
||||
) or (
|
||||
CONF_VALVE_ON_SWITCH_ID in valve
|
||||
and CONF_VALVE_OFF_SWITCH_ID not in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Both {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified for latching valve configuration"
|
||||
)
|
||||
if CONF_VALVE_SWITCH_ID in valve and (
|
||||
CONF_VALVE_OFF_SWITCH_ID in valve or CONF_VALVE_ON_SWITCH_ID in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Do not specify {CONF_VALVE_OFF_SWITCH_ID} or {CONF_VALVE_ON_SWITCH_ID} when using {CONF_VALVE_SWITCH_ID}"
|
||||
)
|
||||
if CONF_VALVE_PULSE_DURATION not in sprinkler_controller and (
|
||||
CONF_VALVE_OFF_SWITCH_ID in valve or CONF_VALVE_ON_SWITCH_ID in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_VALVE_PULSE_DURATION} must be specified when using {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID}"
|
||||
)
|
||||
if (
|
||||
CONF_VALVE_SWITCH_ID not in valve
|
||||
and CONF_VALVE_OFF_SWITCH_ID not in valve
|
||||
and CONF_VALVE_ON_SWITCH_ID not in valve
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Either {CONF_VALVE_SWITCH_ID} or {CONF_VALVE_OFF_SWITCH_ID} and {CONF_VALVE_ON_SWITCH_ID} must be specified in valve configuration"
|
||||
f"{CONF_VALVE_SWITCH_ID} must be specified in valve configuration"
|
||||
)
|
||||
if CONF_RUN_DURATION not in valve and CONF_RUN_DURATION_NUMBER not in valve:
|
||||
raise cv.Invalid(
|
||||
@@ -290,8 +245,15 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_PUMP_ON_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
# Removed latching pump keys - accepted for validation error reporting
|
||||
cv.Optional(CONF_PUMP_OFF_SWITCH_ID): cv.invalid(
|
||||
f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. "
|
||||
f"See {docs_url('components/switch/h_bridge')} for more information"
|
||||
),
|
||||
cv.Optional(CONF_PUMP_ON_SWITCH_ID): cv.invalid(
|
||||
f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. "
|
||||
f"See {docs_url('components/switch/h_bridge')} for more information"
|
||||
),
|
||||
cv.Optional(CONF_PUMP_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_RUN_DURATION_NUMBER): cv.maybe_simple_value(
|
||||
@@ -321,8 +283,15 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
||||
switch.switch_schema(SprinklerControllerSwitch),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_VALVE_OFF_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
cv.Optional(CONF_VALVE_ON_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
# Removed latching valve keys - accepted for validation error reporting
|
||||
cv.Optional(CONF_VALVE_OFF_SWITCH_ID): cv.invalid(
|
||||
f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. "
|
||||
f"See {docs_url('components/switch/h_bridge')} for more information"
|
||||
),
|
||||
cv.Optional(CONF_VALVE_ON_SWITCH_ID): cv.invalid(
|
||||
f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. "
|
||||
f"See {docs_url('components/switch/h_bridge')} for more information"
|
||||
),
|
||||
cv.Optional(CONF_VALVE_SWITCH_ID): cv.use_id(switch.Switch),
|
||||
}
|
||||
)
|
||||
@@ -410,8 +379,15 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||
validate_min_max,
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_PULSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_VALVE_PULSE_DURATION): cv.positive_time_period_milliseconds,
|
||||
# Removed latching valve keys - accepted for validation error reporting
|
||||
cv.Optional(CONF_PUMP_PULSE_DURATION): cv.invalid(
|
||||
f"This option was removed in 2026.1.0; for latching pumps, use {CONF_PUMP_SWITCH_ID} with an H-Bridge switch. "
|
||||
f"See {docs_url('components/switch/h_bridge')} for more information"
|
||||
),
|
||||
cv.Optional(CONF_VALVE_PULSE_DURATION): cv.invalid(
|
||||
f"This option was removed in 2026.1.0; for latching valves, use {CONF_VALVE_SWITCH_ID} with an H-Bridge switch. "
|
||||
f"See {docs_url('components/switch/h_bridge')} for more information"
|
||||
),
|
||||
cv.Exclusive(
|
||||
CONF_PUMP_START_PUMP_DELAY, "pump_start_xxxx_delay"
|
||||
): cv.positive_time_period_seconds,
|
||||
@@ -765,35 +741,10 @@ async def to_code(config):
|
||||
valve_index, valve_switch, valve[CONF_RUN_DURATION]
|
||||
)
|
||||
)
|
||||
elif CONF_VALVE_OFF_SWITCH_ID in valve and CONF_VALVE_ON_SWITCH_ID in valve:
|
||||
valve_switch_off = await cg.get_variable(
|
||||
valve[CONF_VALVE_OFF_SWITCH_ID]
|
||||
)
|
||||
valve_switch_on = await cg.get_variable(valve[CONF_VALVE_ON_SWITCH_ID])
|
||||
cg.add(
|
||||
var.configure_valve_switch_pulsed(
|
||||
valve_index,
|
||||
valve_switch_off,
|
||||
valve_switch_on,
|
||||
sprinkler_controller[CONF_VALVE_PULSE_DURATION],
|
||||
valve[CONF_RUN_DURATION],
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_PUMP_SWITCH_ID in valve:
|
||||
pump = await cg.get_variable(valve[CONF_PUMP_SWITCH_ID])
|
||||
cg.add(var.configure_valve_pump_switch(valve_index, pump))
|
||||
elif CONF_PUMP_OFF_SWITCH_ID in valve and CONF_PUMP_ON_SWITCH_ID in valve:
|
||||
pump_off = await cg.get_variable(valve[CONF_PUMP_OFF_SWITCH_ID])
|
||||
pump_on = await cg.get_variable(valve[CONF_PUMP_ON_SWITCH_ID])
|
||||
cg.add(
|
||||
var.configure_valve_pump_switch_pulsed(
|
||||
valve_index,
|
||||
pump_off,
|
||||
pump_on,
|
||||
sprinkler_controller[CONF_PUMP_PULSE_DURATION],
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_RUN_DURATION_NUMBER in valve:
|
||||
num_rd_var = await number.new_number(
|
||||
|
||||
@@ -11,70 +11,6 @@ namespace esphome::sprinkler {
|
||||
|
||||
static const char *const TAG = "sprinkler";
|
||||
|
||||
SprinklerSwitch::SprinklerSwitch() {}
|
||||
SprinklerSwitch::SprinklerSwitch(switch_::Switch *sprinkler_switch) : on_switch_(sprinkler_switch) {}
|
||||
SprinklerSwitch::SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration)
|
||||
: pulse_duration_(pulse_duration), off_switch_(off_switch), on_switch_(on_switch) {}
|
||||
|
||||
bool SprinklerSwitch::is_latching_valve() { return (this->off_switch_ != nullptr) && (this->on_switch_ != nullptr); }
|
||||
|
||||
void SprinklerSwitch::loop() {
|
||||
if ((this->pinned_millis_) && (App.get_loop_component_start_time() > this->pinned_millis_ + this->pulse_duration_)) {
|
||||
this->pinned_millis_ = 0; // reset tracker
|
||||
if (this->off_switch_->state) {
|
||||
this->off_switch_->turn_off();
|
||||
}
|
||||
if (this->on_switch_->state) {
|
||||
this->on_switch_->turn_off();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerSwitch::turn_off() {
|
||||
if (!this->state()) { // do nothing if we're already in the requested state
|
||||
return;
|
||||
}
|
||||
if (this->off_switch_ != nullptr) { // latching valve, start a pulse
|
||||
if (!this->off_switch_->state) {
|
||||
this->off_switch_->turn_on();
|
||||
}
|
||||
this->pinned_millis_ = millis();
|
||||
} else if (this->on_switch_ != nullptr) { // non-latching valve
|
||||
this->on_switch_->turn_off();
|
||||
}
|
||||
this->state_ = false;
|
||||
}
|
||||
|
||||
void SprinklerSwitch::turn_on() {
|
||||
if (this->state()) { // do nothing if we're already in the requested state
|
||||
return;
|
||||
}
|
||||
if (this->off_switch_ != nullptr) { // latching valve, start a pulse
|
||||
if (!this->on_switch_->state) {
|
||||
this->on_switch_->turn_on();
|
||||
}
|
||||
this->pinned_millis_ = millis();
|
||||
} else if (this->on_switch_ != nullptr) { // non-latching valve
|
||||
this->on_switch_->turn_on();
|
||||
}
|
||||
this->state_ = true;
|
||||
}
|
||||
|
||||
bool SprinklerSwitch::state() {
|
||||
if ((this->off_switch_ == nullptr) && (this->on_switch_ != nullptr)) { // latching valve is not configured...
|
||||
return this->on_switch_->state; // ...so just return the pump switch state
|
||||
}
|
||||
return this->state_;
|
||||
}
|
||||
|
||||
void SprinklerSwitch::sync_valve_state(bool latch_state) {
|
||||
if (this->is_latching_valve()) {
|
||||
this->state_ = latch_state;
|
||||
} else if (this->on_switch_ != nullptr) {
|
||||
this->state_ = this->on_switch_->state;
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerControllerNumber::setup() {
|
||||
float value;
|
||||
if (!this->restore_value_) {
|
||||
@@ -219,8 +155,8 @@ void SprinklerValveOperator::start() {
|
||||
this->state_ = STARTING; // STARTING state requires both a pump and a start_delay_
|
||||
if (this->start_delay_is_valve_delay_) {
|
||||
this->pump_on_();
|
||||
} else if (!this->pump_switch()->state()) { // if the pump is already on, wait to switch on the valve
|
||||
this->valve_on_(); // to ensure consistent run time
|
||||
} else if (!this->pump_switch()->state) { // if the pump is already on, wait to switch on the valve
|
||||
this->valve_on_(); // to ensure consistent run time
|
||||
}
|
||||
} else {
|
||||
this->run_(); // there is no start_delay_, so just start the pump and valve
|
||||
@@ -240,8 +176,8 @@ void SprinklerValveOperator::stop() {
|
||||
} else {
|
||||
this->valve_off_();
|
||||
}
|
||||
if (this->pump_switch()->state()) { // if the pump is still on at this point, it may be in use...
|
||||
this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time
|
||||
if (this->pump_switch()->state) { // if the pump is still on at this point, it may be in use...
|
||||
this->valve_off_(); // ...so just switch the valve off now to ensure consistent run time
|
||||
}
|
||||
} else {
|
||||
this->kill_(); // there is no stop_delay_, so just stop the pump and valve
|
||||
@@ -274,7 +210,7 @@ uint32_t SprinklerValveOperator::time_remaining() {
|
||||
|
||||
SprinklerState SprinklerValveOperator::state() { return this->state_; }
|
||||
|
||||
SprinklerSwitch *SprinklerValveOperator::pump_switch() {
|
||||
switch_::Switch *SprinklerValveOperator::pump_switch() {
|
||||
if ((this->controller_ == nullptr) || (this->valve_ == nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -285,48 +221,50 @@ SprinklerSwitch *SprinklerValveOperator::pump_switch() {
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::pump_off_() {
|
||||
if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first!
|
||||
auto *pump = this->pump_switch();
|
||||
if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
|
||||
return;
|
||||
}
|
||||
if (this->controller_ == nullptr) { // safety first!
|
||||
this->pump_switch()->turn_off(); // if no controller was set, just switch off the pump
|
||||
pump->turn_off(); // if no controller was set, just switch off the pump
|
||||
} else { // ...otherwise, do it "safely"
|
||||
auto state = this->state_; // this is silly, but...
|
||||
this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
|
||||
this->controller_->set_pump_state(this->pump_switch(), false);
|
||||
this->controller_->set_pump_state(pump, false);
|
||||
this->state_ = state;
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::pump_on_() {
|
||||
if ((this->valve_ == nullptr) || (this->pump_switch() == nullptr)) { // safety first!
|
||||
auto *pump = this->pump_switch();
|
||||
if ((this->valve_ == nullptr) || (pump == nullptr)) { // safety first!
|
||||
return;
|
||||
}
|
||||
if (this->controller_ == nullptr) { // safety first!
|
||||
this->pump_switch()->turn_on(); // if no controller was set, just switch on the pump
|
||||
pump->turn_on(); // if no controller was set, just switch on the pump
|
||||
} else { // ...otherwise, do it "safely"
|
||||
auto state = this->state_; // this is silly, but...
|
||||
this->state_ = BYPASS; // ...exclude me from the pump-in-use check that set_pump_state() does
|
||||
this->controller_->set_pump_state(this->pump_switch(), true);
|
||||
this->controller_->set_pump_state(pump, true);
|
||||
this->state_ = state;
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::valve_off_() {
|
||||
if (this->valve_ == nullptr) { // safety first!
|
||||
if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
|
||||
return;
|
||||
}
|
||||
if (this->valve_->valve_switch.state()) {
|
||||
this->valve_->valve_switch.turn_off();
|
||||
if (this->valve_->valve_switch->state) {
|
||||
this->valve_->valve_switch->turn_off();
|
||||
}
|
||||
}
|
||||
|
||||
void SprinklerValveOperator::valve_on_() {
|
||||
if (this->valve_ == nullptr) { // safety first!
|
||||
if ((this->valve_ == nullptr) || (this->valve_->valve_switch == nullptr)) { // safety first!
|
||||
return;
|
||||
}
|
||||
if (!this->valve_->valve_switch.state()) {
|
||||
this->valve_->valve_switch.turn_on();
|
||||
if (!this->valve_->valve_switch->state) {
|
||||
this->valve_->valve_switch->turn_on();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,12 +339,6 @@ Sprinkler::Sprinkler(const std::string &name) {
|
||||
void Sprinkler::setup() { this->all_valves_off_(true); }
|
||||
|
||||
void Sprinkler::loop() {
|
||||
for (auto &p : this->pump_) {
|
||||
p.loop();
|
||||
}
|
||||
for (auto &v : this->valve_) {
|
||||
v.valve_switch.loop();
|
||||
}
|
||||
for (auto &vo : this->valve_op_) {
|
||||
vo.loop();
|
||||
}
|
||||
@@ -423,10 +355,15 @@ void Sprinkler::add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControll
|
||||
|
||||
new_valve->controller_switch = valve_sw;
|
||||
new_valve->controller_switch->set_state_lambda([this, new_valve_number]() -> optional<bool> {
|
||||
if (this->valve_pump_switch(new_valve_number) != nullptr) {
|
||||
return this->valve_switch(new_valve_number)->state() && this->valve_pump_switch(new_valve_number)->state();
|
||||
auto *valve = this->valve_switch(new_valve_number);
|
||||
auto *pump = this->valve_pump_switch(new_valve_number);
|
||||
if (valve == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return this->valve_switch(new_valve_number)->state();
|
||||
if (pump != nullptr) {
|
||||
return valve->state && pump->state;
|
||||
}
|
||||
return valve->state;
|
||||
});
|
||||
|
||||
new_valve->valve_turn_off_automation =
|
||||
@@ -496,18 +433,7 @@ void Sprinkler::set_controller_repeat_number(SprinklerControllerNumber *repeat_n
|
||||
|
||||
void Sprinkler::configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
this->valve_[valve_number].valve_switch.set_on_switch(valve_switch);
|
||||
this->valve_[valve_number].run_duration = run_duration;
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off,
|
||||
switch_::Switch *valve_switch_on, uint32_t pulse_duration,
|
||||
uint32_t run_duration) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
this->valve_[valve_number].valve_switch.set_off_switch(valve_switch_off);
|
||||
this->valve_[valve_number].valve_switch.set_on_switch(valve_switch_on);
|
||||
this->valve_[valve_number].valve_switch.set_pulse_duration(pulse_duration);
|
||||
this->valve_[valve_number].valve_switch = valve_switch;
|
||||
this->valve_[valve_number].run_duration = run_duration;
|
||||
}
|
||||
}
|
||||
@@ -515,31 +441,12 @@ void Sprinkler::configure_valve_switch_pulsed(size_t valve_number, switch_::Swit
|
||||
void Sprinkler::configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
|
||||
if (this->pump_[i].on_switch() == pump_switch) { // if the "new" pump matches one we already have...
|
||||
this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_...
|
||||
if (this->pump_[i] == pump_switch) { // if the "new" pump matches one we already have...
|
||||
this->valve_[valve_number].pump_switch_index = i; // ...save its index in the pump vector...
|
||||
return; // ...and we are done
|
||||
}
|
||||
} // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it
|
||||
this->pump_.resize(this->pump_.size() + 1);
|
||||
this->pump_.back().set_on_switch(pump_switch);
|
||||
this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
|
||||
}
|
||||
}
|
||||
|
||||
void Sprinkler::configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off,
|
||||
switch_::Switch *pump_switch_on, uint32_t pulse_duration) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
for (size_t i = 0; i < this->pump_.size(); i++) { // check each existing registered pump
|
||||
if ((this->pump_[i].off_switch() == pump_switch_off) &&
|
||||
(this->pump_[i].on_switch() == pump_switch_on)) { // if the "new" pump matches one we already have...
|
||||
this->valve_[valve_number].pump_switch_index = i; // ...save its index in the SprinklerSwitch vector pump_...
|
||||
return; // ...and we are done
|
||||
}
|
||||
} // if we end up here, no pumps matched, so add a new one and set the valve's SprinklerSwitch at it
|
||||
this->pump_.resize(this->pump_.size() + 1);
|
||||
this->pump_.back().set_off_switch(pump_switch_off);
|
||||
this->pump_.back().set_on_switch(pump_switch_on);
|
||||
this->pump_.back().set_pulse_duration(pulse_duration);
|
||||
} // if we end up here, no pumps matched, so add a new one
|
||||
this->pump_.push_back(pump_switch);
|
||||
this->valve_[valve_number].pump_switch_index = this->pump_.size() - 1; // save the index to the new pump
|
||||
}
|
||||
}
|
||||
@@ -1041,7 +948,7 @@ size_t Sprinkler::number_of_valves() { return this->valve_.size(); }
|
||||
|
||||
bool Sprinkler::is_a_valid_valve(const size_t valve_number) { return (valve_number < this->number_of_valves()); }
|
||||
|
||||
bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) {
|
||||
bool Sprinkler::pump_in_use(switch_::Switch *pump_switch) {
|
||||
if (pump_switch == nullptr) {
|
||||
return false; // we can't do anything if there's nothing to check
|
||||
}
|
||||
@@ -1054,8 +961,7 @@ bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) {
|
||||
for (auto &vo : this->valve_op_) { // first, check if any SprinklerValveOperator has a valve dependent on this pump
|
||||
if ((vo.state() != BYPASS) && (vo.pump_switch() != nullptr)) {
|
||||
// the SprinklerValveOperator is configured with a pump; now check if it is the pump of interest
|
||||
if ((vo.pump_switch()->off_switch() == pump_switch->off_switch()) &&
|
||||
(vo.pump_switch()->on_switch() == pump_switch->on_switch())) {
|
||||
if (vo.pump_switch() == pump_switch) {
|
||||
// now if the SprinklerValveOperator has a pump and it is either ACTIVE, is STARTING with a valve delay or
|
||||
// is STOPPING with a valve delay, its pump can be considered "in use", so just return indicating this now
|
||||
if ((vo.state() == ACTIVE) ||
|
||||
@@ -1074,13 +980,12 @@ bool Sprinkler::pump_in_use(SprinklerSwitch *pump_switch) {
|
||||
if (valve_pump == nullptr) {
|
||||
return false; // valve has no pump, so this pump isn't in use by it
|
||||
}
|
||||
return (pump_switch->off_switch() == valve_pump->off_switch()) &&
|
||||
(pump_switch->on_switch() == valve_pump->on_switch());
|
||||
return pump_switch == valve_pump;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) {
|
||||
void Sprinkler::set_pump_state(switch_::Switch *pump_switch, bool state) {
|
||||
if (pump_switch == nullptr) {
|
||||
return; // we can't do anything if there's nothing to check
|
||||
}
|
||||
@@ -1091,15 +996,10 @@ void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) {
|
||||
if (controller != this) { // dummy check
|
||||
if (controller->pump_in_use(pump_switch)) {
|
||||
hold_pump_on = true; // if another controller says it's using this pump, keep it on
|
||||
// at this point we know if there exists another SprinklerSwitch that is "on" with its
|
||||
// off_switch_ and on_switch_ pointers pointing to the same pair of switch objects
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hold_pump_on) {
|
||||
// at this point we know if there exists another SprinklerSwitch that is "on" with its
|
||||
// off_switch_ and on_switch_ pointers pointing to the same pair of switch objects...
|
||||
pump_switch->sync_valve_state(true); // ...so ensure our state is consistent
|
||||
ESP_LOGD(TAG, "Leaving pump on because another controller instance is using it");
|
||||
}
|
||||
|
||||
@@ -1107,8 +1007,6 @@ void Sprinkler::set_pump_state(SprinklerSwitch *pump_switch, bool state) {
|
||||
pump_switch->turn_on();
|
||||
} else if (!hold_pump_on && !this->pump_in_use(pump_switch)) {
|
||||
pump_switch->turn_off();
|
||||
} else if (hold_pump_on) { // we must assume the other controller will switch off the pump when done...
|
||||
pump_switch->sync_valve_state(false); // ...this only impacts latching valves
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,23 +1172,23 @@ SprinklerControllerSwitch *Sprinkler::enable_switch(size_t valve_number) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SprinklerSwitch *Sprinkler::valve_switch(const size_t valve_number) {
|
||||
switch_::Switch *Sprinkler::valve_switch(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return &this->valve_[valve_number].valve_switch;
|
||||
return this->valve_[valve_number].valve_switch;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SprinklerSwitch *Sprinkler::valve_pump_switch(const size_t valve_number) {
|
||||
switch_::Switch *Sprinkler::valve_pump_switch(const size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number) && this->valve_[valve_number].pump_switch_index.has_value()) {
|
||||
return &this->pump_[this->valve_[valve_number].pump_switch_index.value()];
|
||||
return this->pump_[this->valve_[valve_number].pump_switch_index.value()];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SprinklerSwitch *Sprinkler::valve_pump_switch_by_pump_index(size_t pump_index) {
|
||||
switch_::Switch *Sprinkler::valve_pump_switch_by_pump_index(size_t pump_index) {
|
||||
if (pump_index < this->pump_.size()) {
|
||||
return &this->pump_[pump_index];
|
||||
return this->pump_[pump_index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1454,8 +1352,9 @@ void Sprinkler::start_valve_(SprinklerValveRunRequest *req) {
|
||||
|
||||
void Sprinkler::all_valves_off_(const bool include_pump) {
|
||||
for (size_t valve_index = 0; valve_index < this->number_of_valves(); valve_index++) {
|
||||
if (this->valve_[valve_index].valve_switch.state()) {
|
||||
this->valve_[valve_index].valve_switch.turn_off();
|
||||
auto *valve_sw = this->valve_[valve_index].valve_switch;
|
||||
if ((valve_sw != nullptr) && valve_sw->state) {
|
||||
valve_sw->turn_off();
|
||||
}
|
||||
if (include_pump) {
|
||||
this->set_pump_state(this->valve_pump_switch(valve_index), false);
|
||||
@@ -1754,10 +1653,6 @@ void Sprinkler::dump_config() {
|
||||
" Name: %s\n"
|
||||
" Run Duration: %" PRIu32 " seconds",
|
||||
valve_number, this->valve_name(valve_number), this->valve_run_duration(valve_number));
|
||||
if (this->valve_[valve_number].valve_switch.pulse_duration()) {
|
||||
ESP_LOGCONFIG(TAG, " Pulse Duration: %" PRIu32 " milliseconds",
|
||||
this->valve_[valve_number].valve_switch.pulse_duration());
|
||||
}
|
||||
}
|
||||
if (!this->pump_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Total number of pumps: %zu", this->pump_.size());
|
||||
|
||||
@@ -35,7 +35,6 @@ enum SprinklerValveRunRequestOrigin : uint8_t {
|
||||
class Sprinkler; // this component
|
||||
class SprinklerControllerNumber; // number components that appear in the front end; based on number core
|
||||
class SprinklerControllerSwitch; // switches that appear in the front end; based on switch core
|
||||
class SprinklerSwitch; // switches representing any valve or pump; provides abstraction for latching valves
|
||||
class SprinklerValveOperator; // manages all switching on/off of valves and associated pumps
|
||||
class SprinklerValveRunRequest; // tells the sprinkler controller what valve to run and for how long as well as what
|
||||
// SprinklerValveOperator is handling it
|
||||
@@ -43,34 +42,6 @@ template<typename... Ts> class StartSingleValveAction;
|
||||
template<typename... Ts> class ShutdownAction;
|
||||
template<typename... Ts> class ResumeOrStartAction;
|
||||
|
||||
class SprinklerSwitch {
|
||||
public:
|
||||
SprinklerSwitch();
|
||||
SprinklerSwitch(switch_::Switch *sprinkler_switch);
|
||||
SprinklerSwitch(switch_::Switch *off_switch, switch_::Switch *on_switch, uint32_t pulse_duration);
|
||||
|
||||
bool is_latching_valve(); // returns true if configured as a latching valve
|
||||
void loop(); // called as a part of loop(), used for latching valve pulses
|
||||
uint32_t pulse_duration() { return this->pulse_duration_; }
|
||||
bool state(); // returns the switch's current state
|
||||
void set_off_switch(switch_::Switch *off_switch) { this->off_switch_ = off_switch; }
|
||||
void set_on_switch(switch_::Switch *on_switch) { this->on_switch_ = on_switch; }
|
||||
void set_pulse_duration(uint32_t pulse_duration) { this->pulse_duration_ = pulse_duration; }
|
||||
void sync_valve_state(
|
||||
bool latch_state); // syncs internal state to switch; if latching valve, sets state to latch_state
|
||||
void turn_off(); // sets internal flag and actuates the switch
|
||||
void turn_on(); // sets internal flag and actuates the switch
|
||||
switch_::Switch *off_switch() { return this->off_switch_; }
|
||||
switch_::Switch *on_switch() { return this->on_switch_; }
|
||||
|
||||
protected:
|
||||
bool state_{false};
|
||||
uint32_t pulse_duration_{0};
|
||||
uint64_t pinned_millis_{0};
|
||||
switch_::Switch *off_switch_{nullptr}; // only used for latching valves
|
||||
switch_::Switch *on_switch_{nullptr}; // used for both latching and non-latching valves
|
||||
};
|
||||
|
||||
struct SprinklerQueueItem {
|
||||
size_t valve_number;
|
||||
uint32_t run_duration;
|
||||
@@ -88,7 +59,7 @@ struct SprinklerValve {
|
||||
SprinklerControllerNumber *run_duration_number;
|
||||
SprinklerControllerSwitch *controller_switch;
|
||||
SprinklerControllerSwitch *enable_switch;
|
||||
SprinklerSwitch valve_switch;
|
||||
switch_::Switch *valve_switch;
|
||||
uint32_t run_duration;
|
||||
optional<size_t> pump_switch_index;
|
||||
bool valve_cycle_complete;
|
||||
@@ -155,7 +126,7 @@ class SprinklerValveOperator {
|
||||
uint32_t run_duration(); // returns the desired run duration in seconds
|
||||
uint32_t time_remaining(); // returns seconds remaining (does not include stop_delay_)
|
||||
SprinklerState state(); // returns the valve's state/status
|
||||
SprinklerSwitch *pump_switch(); // returns this SprinklerValveOperator's pump's SprinklerSwitch
|
||||
switch_::Switch *pump_switch(); // returns this SprinklerValveOperator's pump switch
|
||||
|
||||
protected:
|
||||
void pump_off_();
|
||||
@@ -228,13 +199,9 @@ class Sprinkler : public Component {
|
||||
|
||||
/// configure a valve's switch object and run duration. run_duration is time in seconds.
|
||||
void configure_valve_switch(size_t valve_number, switch_::Switch *valve_switch, uint32_t run_duration);
|
||||
void configure_valve_switch_pulsed(size_t valve_number, switch_::Switch *valve_switch_off,
|
||||
switch_::Switch *valve_switch_on, uint32_t pulse_duration, uint32_t run_duration);
|
||||
|
||||
/// configure a valve's associated pump switch object
|
||||
void configure_valve_pump_switch(size_t valve_number, switch_::Switch *pump_switch);
|
||||
void configure_valve_pump_switch_pulsed(size_t valve_number, switch_::Switch *pump_switch_off,
|
||||
switch_::Switch *pump_switch_on, uint32_t pulse_duration);
|
||||
|
||||
/// configure a valve's run duration number component
|
||||
void configure_valve_run_duration_number(size_t valve_number, SprinklerControllerNumber *run_duration_number);
|
||||
@@ -383,10 +350,10 @@ class Sprinkler : public Component {
|
||||
bool is_a_valid_valve(size_t valve_number);
|
||||
|
||||
/// returns true if the pump the pointer points to is in use
|
||||
bool pump_in_use(SprinklerSwitch *pump_switch);
|
||||
bool pump_in_use(switch_::Switch *pump_switch);
|
||||
|
||||
/// switches on/off a pump "safely" by checking that the new state will not conflict with another controller
|
||||
void set_pump_state(SprinklerSwitch *pump_switch, bool state);
|
||||
void set_pump_state(switch_::Switch *pump_switch, bool state);
|
||||
|
||||
/// returns the amount of time in seconds required for all valves
|
||||
uint32_t total_cycle_time_all_valves();
|
||||
@@ -419,13 +386,13 @@ class Sprinkler : public Component {
|
||||
SprinklerControllerSwitch *enable_switch(size_t valve_number);
|
||||
|
||||
/// returns a pointer to a valve's switch object
|
||||
SprinklerSwitch *valve_switch(size_t valve_number);
|
||||
switch_::Switch *valve_switch(size_t valve_number);
|
||||
|
||||
/// returns a pointer to a valve's pump switch object
|
||||
SprinklerSwitch *valve_pump_switch(size_t valve_number);
|
||||
switch_::Switch *valve_pump_switch(size_t valve_number);
|
||||
|
||||
/// returns a pointer to a valve's pump switch object
|
||||
SprinklerSwitch *valve_pump_switch_by_pump_index(size_t pump_index);
|
||||
switch_::Switch *valve_pump_switch_by_pump_index(size_t pump_index);
|
||||
|
||||
protected:
|
||||
/// returns true if valve number is enabled
|
||||
@@ -577,8 +544,8 @@ class Sprinkler : public Component {
|
||||
/// Queue of valves to activate next, regardless of auto-advance
|
||||
std::vector<SprinklerQueueItem> queued_valves_;
|
||||
|
||||
/// Sprinkler valve pump objects
|
||||
std::vector<SprinklerSwitch> pump_;
|
||||
/// Sprinkler valve pump switches
|
||||
std::vector<switch_::Switch *> pump_;
|
||||
|
||||
/// Sprinkler valve objects
|
||||
std::vector<SprinklerValve> valve_;
|
||||
|
||||
@@ -161,8 +161,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
).extend(spi.spi_device_schema(cs_pin_required=False, default_data_rate=1e6))
|
||||
),
|
||||
cv.only_on_esp32,
|
||||
only_on_variant(supported=[VARIANT_ESP32S3]),
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema(
|
||||
|
||||
@@ -527,7 +527,9 @@ void SX126x::dump_config() {
|
||||
this->spreading_factor_, cr, this->preamble_size_);
|
||||
}
|
||||
if (!this->sync_value_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str());
|
||||
char hex_buf[17]; // 8 bytes max = 16 hex chars + null
|
||||
ESP_LOGCONFIG(TAG, " Sync Value: 0x%s",
|
||||
format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size()));
|
||||
}
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Configuring SX126x failed");
|
||||
|
||||
@@ -476,7 +476,9 @@ void SX127x::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Payload Length: %" PRIu32, this->payload_length_);
|
||||
}
|
||||
if (!this->sync_value_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Sync Value: 0x%s", format_hex(this->sync_value_).c_str());
|
||||
char hex_buf[17]; // 8 bytes max = 16 hex chars + null
|
||||
ESP_LOGCONFIG(TAG, " Sync Value: 0x%s",
|
||||
format_hex_to(hex_buf, this->sync_value_.data(), this->sync_value_.size()));
|
||||
}
|
||||
if (this->preamble_size_ > 0 || this->preamble_detect_ > 0) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID, CONF_SCAN
|
||||
from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID
|
||||
|
||||
CODEOWNERS = ["@andreashergert1984"]
|
||||
|
||||
@@ -18,7 +18,6 @@ CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TCA9548AComponent),
|
||||
cv.Optional(CONF_SCAN): cv.invalid("This option has been removed"),
|
||||
cv.Optional(CONF_CHANNELS, default=[]): cv.ensure_list(
|
||||
{
|
||||
cv.Required(CONF_BUS_ID): cv.declare_id(TCA9548AChannel),
|
||||
|
||||
@@ -7,7 +7,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_RESTORE_STATE,
|
||||
CONF_STATE,
|
||||
CONF_TURN_OFF_ACTION,
|
||||
CONF_TURN_ON_ACTION,
|
||||
@@ -44,9 +43,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_TURN_ON_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_RESTORE_STATE): cv.invalid(
|
||||
"The restore_state option has been removed in 2023.7.0. Use the restore_mode option instead"
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
|
||||
@@ -78,8 +78,8 @@ void TextSensor::add_on_raw_state_callback(std::function<void(const std::string
|
||||
this->raw_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
std::string TextSensor::get_state() const { return this->state; }
|
||||
std::string TextSensor::get_raw_state() const {
|
||||
const std::string &TextSensor::get_state() const { return this->state; }
|
||||
const std::string &TextSensor::get_raw_state() const {
|
||||
// Suppress deprecation warning - get_raw_state() is the replacement API
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
@@ -37,9 +37,9 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
/// Getter-syntax for .state.
|
||||
std::string get_state() const;
|
||||
const std::string &get_state() const;
|
||||
/// Getter-syntax for .raw_state
|
||||
std::string get_raw_state() const;
|
||||
const std::string &get_raw_state() const;
|
||||
|
||||
void publish_state(const std::string &state);
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace thermostat {
|
||||
namespace esphome::thermostat {
|
||||
|
||||
static const char *const TAG = "thermostat.climate";
|
||||
|
||||
@@ -66,10 +65,12 @@ void ThermostatClimate::setup() {
|
||||
}
|
||||
|
||||
void ThermostatClimate::loop() {
|
||||
for (auto &timer : this->timer_) {
|
||||
if (timer.active && (timer.started + timer.time < App.get_loop_component_start_time())) {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
for (uint8_t i = 0; i < THERMOSTAT_TIMER_COUNT; i++) {
|
||||
auto &timer = this->timer_[i];
|
||||
if (timer.active && (now - timer.started >= timer.time)) {
|
||||
timer.active = false;
|
||||
timer.func();
|
||||
this->call_timer_callback_(static_cast<ThermostatClimateTimerIndex>(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -916,8 +917,42 @@ uint32_t ThermostatClimate::timer_duration_(ThermostatClimateTimerIndex timer_in
|
||||
return this->timer_[timer_index].time;
|
||||
}
|
||||
|
||||
std::function<void()> ThermostatClimate::timer_cbf_(ThermostatClimateTimerIndex timer_index) {
|
||||
return this->timer_[timer_index].func;
|
||||
void ThermostatClimate::call_timer_callback_(ThermostatClimateTimerIndex timer_index) {
|
||||
switch (timer_index) {
|
||||
case THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME:
|
||||
this->cooling_max_run_time_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_COOLING_OFF:
|
||||
this->cooling_off_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_COOLING_ON:
|
||||
this->cooling_on_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_FAN_MODE:
|
||||
this->fan_mode_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_FANNING_OFF:
|
||||
this->fanning_off_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_FANNING_ON:
|
||||
this->fanning_on_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME:
|
||||
this->heating_max_run_time_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_HEATING_OFF:
|
||||
this->heating_off_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_HEATING_ON:
|
||||
this->heating_on_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_IDLE_ON:
|
||||
this->idle_on_timer_callback_();
|
||||
break;
|
||||
case THERMOSTAT_TIMER_COUNT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ThermostatClimate::cooling_max_run_time_timer_callback_() {
|
||||
@@ -1330,45 +1365,64 @@ void ThermostatClimate::set_heat_deadband(float deadband) { this->heating_deadba
|
||||
void ThermostatClimate::set_heat_overrun(float overrun) { this->heating_overrun_ = overrun; }
|
||||
void ThermostatClimate::set_supplemental_cool_delta(float delta) { this->supplemental_cool_delta_ = delta; }
|
||||
void ThermostatClimate::set_supplemental_heat_delta(float delta) { this->supplemental_heat_delta_ = delta; }
|
||||
|
||||
void ThermostatClimate::set_timer_duration_in_sec_(ThermostatClimateTimerIndex timer_index, uint32_t time) {
|
||||
uint32_t new_duration_ms = 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
|
||||
if (this->timer_[timer_index].active) {
|
||||
// Timer is running, calculate elapsed time and adjust if needed
|
||||
uint32_t current_time = App.get_loop_component_start_time();
|
||||
uint32_t elapsed = current_time - this->timer_[timer_index].started;
|
||||
|
||||
if (elapsed >= new_duration_ms) {
|
||||
// Timer should complete immediately (including when new_duration_ms is 0)
|
||||
ESP_LOGVV(TAG, "timer %d completing immediately (elapsed %d >= new %d)", timer_index, elapsed, new_duration_ms);
|
||||
this->timer_[timer_index].active = false;
|
||||
// Trigger the timer callback immediately
|
||||
this->call_timer_callback_(timer_index);
|
||||
return;
|
||||
} else {
|
||||
// Adjust timer to run for remaining time - keep original start time
|
||||
ESP_LOGVV(TAG, "timer %d adjusted: elapsed %d, new total %d, remaining %d", timer_index, elapsed, new_duration_ms,
|
||||
new_duration_ms - elapsed);
|
||||
this->timer_[timer_index].time = new_duration_ms;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Original logic for non-running timers
|
||||
this->timer_[timer_index].time = new_duration_ms;
|
||||
}
|
||||
|
||||
void ThermostatClimate::set_cooling_maximum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME, time);
|
||||
}
|
||||
void ThermostatClimate::set_cooling_minimum_off_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_OFF].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_OFF, time);
|
||||
}
|
||||
void ThermostatClimate::set_cooling_minimum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_ON].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_COOLING_ON, time);
|
||||
}
|
||||
void ThermostatClimate::set_fan_mode_minimum_switching_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_FAN_MODE].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FAN_MODE, time);
|
||||
}
|
||||
void ThermostatClimate::set_fanning_minimum_off_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_FANNING_OFF].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FANNING_OFF, time);
|
||||
}
|
||||
void ThermostatClimate::set_fanning_minimum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_FANNING_ON].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_FANNING_ON, time);
|
||||
}
|
||||
void ThermostatClimate::set_heating_maximum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME, time);
|
||||
}
|
||||
void ThermostatClimate::set_heating_minimum_off_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_OFF].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_OFF, time);
|
||||
}
|
||||
void ThermostatClimate::set_heating_minimum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_ON].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_HEATING_ON, time);
|
||||
}
|
||||
void ThermostatClimate::set_idle_minimum_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
this->set_timer_duration_in_sec_(thermostat::THERMOSTAT_TIMER_IDLE_ON, time);
|
||||
}
|
||||
void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
void ThermostatClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) {
|
||||
@@ -1653,5 +1707,4 @@ ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig(float defau
|
||||
float default_temperature_high)
|
||||
: default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
|
||||
|
||||
} // namespace thermostat
|
||||
} // namespace esphome
|
||||
} // namespace esphome::thermostat
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace thermostat {
|
||||
namespace esphome::thermostat {
|
||||
|
||||
enum HumidificationAction : uint8_t {
|
||||
THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF = 0,
|
||||
@@ -41,13 +40,11 @@ enum OnBootRestoreFrom : uint8_t {
|
||||
|
||||
struct ThermostatClimateTimer {
|
||||
ThermostatClimateTimer() = default;
|
||||
ThermostatClimateTimer(bool active, uint32_t time, uint32_t started, std::function<void()> func)
|
||||
: active(active), time(time), started(started), func(std::move(func)) {}
|
||||
ThermostatClimateTimer(bool active, uint32_t time, uint32_t started) : active(active), time(time), started(started) {}
|
||||
|
||||
bool active;
|
||||
uint32_t time;
|
||||
uint32_t started;
|
||||
std::function<void()> func;
|
||||
};
|
||||
|
||||
struct ThermostatClimateTargetTempConfig {
|
||||
@@ -266,7 +263,10 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
bool cancel_timer_(ThermostatClimateTimerIndex timer_index);
|
||||
bool timer_active_(ThermostatClimateTimerIndex timer_index);
|
||||
uint32_t timer_duration_(ThermostatClimateTimerIndex timer_index);
|
||||
std::function<void()> timer_cbf_(ThermostatClimateTimerIndex timer_index);
|
||||
/// Call the appropriate timer callback based on timer index
|
||||
void call_timer_callback_(ThermostatClimateTimerIndex timer_index);
|
||||
/// Enhanced timer duration setter with running timer adjustment
|
||||
void set_timer_duration_in_sec_(ThermostatClimateTimerIndex timer_index, uint32_t time);
|
||||
|
||||
/// set_timeout() callbacks for various actions (see above)
|
||||
void cooling_max_run_time_timer_callback_();
|
||||
@@ -532,27 +532,16 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *prev_humidity_control_trigger_{nullptr};
|
||||
|
||||
/// Climate action timers
|
||||
std::array<ThermostatClimateTimer, THERMOSTAT_TIMER_COUNT> timer_{
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)),
|
||||
};
|
||||
std::array<ThermostatClimateTimer, THERMOSTAT_TIMER_COUNT> timer_{};
|
||||
|
||||
/// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc)
|
||||
FixedVector<PresetEntry> preset_config_{};
|
||||
/// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset")
|
||||
FixedVector<CustomPresetEntry> custom_preset_config_{};
|
||||
/// Default custom preset to use on start up (pointer to entry in custom_preset_config_)
|
||||
|
||||
private:
|
||||
/// Default custom preset to use on start up (pointer to entry in custom_preset_config_)
|
||||
const char *default_custom_preset_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace thermostat
|
||||
} // namespace esphome
|
||||
} // namespace esphome::thermostat
|
||||
|
||||
@@ -37,10 +37,6 @@ COLOR_TYPES = {
|
||||
|
||||
TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component)
|
||||
|
||||
COLOR_CONFIG_ERROR = (
|
||||
"This option has been removed, use color_datapoint and color_type instead."
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
@@ -49,8 +45,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_RGB_DATAPOINT): cv.invalid(COLOR_CONFIG_ERROR),
|
||||
cv.Optional(CONF_HSV_DATAPOINT): cv.invalid(COLOR_CONFIG_ERROR),
|
||||
cv.Inclusive(CONF_COLOR_DATAPOINT, "color"): cv.uint8_t,
|
||||
cv.Inclusive(CONF_COLOR_TYPE, "color"): cv.enum(COLOR_TYPES, upper=True),
|
||||
cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user