mirror of
https://github.com/esphome/esphome.git
synced 2026-02-20 00:15:36 -07:00
Merge branch 'dev' into no_alloc_object_id
This commit is contained in:
@@ -132,13 +132,13 @@ class AlarmControlPanel : public EntityBase {
|
||||
// the call control function
|
||||
virtual void control(const AlarmControlPanelCall &call) = 0;
|
||||
// state callback - triggers check get_state() for specific state
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
// clear callback - fires when leaving TRIGGERED state
|
||||
CallbackManager<void()> cleared_callback_{};
|
||||
LazyCallbackManager<void()> cleared_callback_{};
|
||||
// chime callback
|
||||
CallbackManager<void()> chime_callback_{};
|
||||
LazyCallbackManager<void()> chime_callback_{};
|
||||
// ready callback
|
||||
CallbackManager<void()> ready_callback_{};
|
||||
LazyCallbackManager<void()> ready_callback_{};
|
||||
};
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
|
||||
@@ -747,7 +747,7 @@ message NoiseEncryptionSetKeyRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_API_NOISE";
|
||||
|
||||
bytes key = 1;
|
||||
bytes key = 1 [(pointer_to_buffer) = true];
|
||||
}
|
||||
|
||||
message NoiseEncryptionSetKeyResponse {
|
||||
|
||||
@@ -1666,13 +1666,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
|
||||
resp.success = false;
|
||||
|
||||
psk_t psk{};
|
||||
if (msg.key.empty()) {
|
||||
if (msg.key_len == 0) {
|
||||
if (this->parent_->clear_noise_psk(true)) {
|
||||
resp.success = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to clear encryption key");
|
||||
}
|
||||
} else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) {
|
||||
} else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
|
||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||
|
||||
@@ -858,9 +858,12 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const {
|
||||
#ifdef USE_API_NOISE
|
||||
bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->key = value.as_string();
|
||||
case 1: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->key = value.data();
|
||||
this->key_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1054,11 +1054,12 @@ class SubscribeLogsResponse final : public ProtoMessage {
|
||||
class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 124;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 9;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 19;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "noise_encryption_set_key_request"; }
|
||||
#endif
|
||||
std::string key{};
|
||||
const uint8_t *key{nullptr};
|
||||
uint16_t key_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
@@ -1115,7 +1115,7 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
|
||||
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest");
|
||||
out.append(" key: ");
|
||||
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->key.data()), this->key.size()));
|
||||
out.append(format_hex_pretty(this->key, this->key_len));
|
||||
out.append("\n");
|
||||
}
|
||||
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
|
||||
|
||||
@@ -41,7 +41,7 @@ class Button : public EntityBase, public EntityBase_DeviceClass {
|
||||
*/
|
||||
virtual void press_action() = 0;
|
||||
|
||||
CallbackManager<void()> press_callback_{};
|
||||
LazyCallbackManager<void()> press_callback_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::button
|
||||
|
||||
@@ -326,8 +326,8 @@ class Climate : public EntityBase {
|
||||
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
CallbackManager<void(Climate &)> state_callback_{};
|
||||
CallbackManager<void(ClimateCall &)> control_callback_{};
|
||||
LazyCallbackManager<void(Climate &)> state_callback_{};
|
||||
LazyCallbackManager<void(ClimateCall &)> control_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
|
||||
float visual_min_temperature_override_{NAN};
|
||||
|
||||
@@ -152,7 +152,7 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
|
||||
|
||||
optional<CoverRestoreState> restore_state_();
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ class DateTimeBase : public EntityBase {
|
||||
#endif
|
||||
|
||||
protected:
|
||||
CallbackManager<void()> state_callback_;
|
||||
LazyCallbackManager<void()> state_callback_;
|
||||
|
||||
#ifdef USE_TIME
|
||||
time::RealTimeClock *rtc_;
|
||||
|
||||
@@ -54,6 +54,7 @@ bool MenuItemSelect::select_next() {
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_next(true).perform();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -65,6 +66,7 @@ bool MenuItemSelect::select_prev() {
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_previous(true).perform();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
|
||||
protected:
|
||||
CallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
FixedVector<const char *> types_;
|
||||
|
||||
private:
|
||||
|
||||
@@ -155,7 +155,7 @@ class Fan : public EntityBase {
|
||||
const char *find_preset_mode_(const char *preset_mode);
|
||||
const char *find_preset_mode_(const char *preset_mode, size_t len);
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
FanRestoreMode restore_mode_;
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ class Lock : public EntityBase {
|
||||
*/
|
||||
virtual void control(const LockCall &call) = 0;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
Deduplicator<LockState> publish_dedup_;
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
@@ -157,7 +157,7 @@ class MediaPlayer : public EntityBase {
|
||||
|
||||
virtual void control(const MediaPlayerCall &call) = 0;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
};
|
||||
|
||||
} // namespace media_player
|
||||
|
||||
@@ -49,7 +49,7 @@ class Number : public EntityBase {
|
||||
*/
|
||||
virtual void control(float value) = 0;
|
||||
|
||||
CallbackManager<void(float)> state_callback_;
|
||||
LazyCallbackManager<void(float)> state_callback_;
|
||||
};
|
||||
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -111,7 +111,7 @@ class Select : public EntityBase {
|
||||
}
|
||||
}
|
||||
|
||||
CallbackManager<void(size_t)> state_callback_;
|
||||
LazyCallbackManager<void(size_t)> state_callback_;
|
||||
};
|
||||
|
||||
} // namespace esphome::select
|
||||
|
||||
@@ -4,17 +4,28 @@ import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensirion_common, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ALGORITHM_TUNING,
|
||||
CONF_GAIN_FACTOR,
|
||||
CONF_GATING_MAX_DURATION_MINUTES,
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_INDEX_OFFSET,
|
||||
CONF_LEARNING_TIME_GAIN_HOURS,
|
||||
CONF_LEARNING_TIME_OFFSET_HOURS,
|
||||
CONF_NORMALIZED_OFFSET_SLOPE,
|
||||
CONF_NOX,
|
||||
CONF_OFFSET,
|
||||
CONF_PM_1_0,
|
||||
CONF_PM_2_5,
|
||||
CONF_PM_4_0,
|
||||
CONF_PM_10_0,
|
||||
CONF_STD_INITIAL,
|
||||
CONF_STORE_BASELINE,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TEMPERATURE_COMPENSATION,
|
||||
CONF_TIME_CONSTANT,
|
||||
CONF_VOC,
|
||||
CONF_VOC_BASELINE,
|
||||
DEVICE_CLASS_AQI,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PM1,
|
||||
@@ -42,18 +53,7 @@ SEN5XComponent = sen5x_ns.class_(
|
||||
RhtAccelerationMode = sen5x_ns.enum("RhtAccelerationMode")
|
||||
|
||||
CONF_ACCELERATION_MODE = "acceleration_mode"
|
||||
CONF_ALGORITHM_TUNING = "algorithm_tuning"
|
||||
CONF_AUTO_CLEANING_INTERVAL = "auto_cleaning_interval"
|
||||
CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes"
|
||||
CONF_INDEX_OFFSET = "index_offset"
|
||||
CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours"
|
||||
CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours"
|
||||
CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope"
|
||||
CONF_NOX = "nox"
|
||||
CONF_STD_INITIAL = "std_initial"
|
||||
CONF_TIME_CONSTANT = "time_constant"
|
||||
CONF_VOC = "voc"
|
||||
CONF_VOC_BASELINE = "voc_baseline"
|
||||
|
||||
|
||||
# Actions
|
||||
|
||||
@@ -76,9 +76,7 @@ StateClass Sensor::get_state_class() {
|
||||
|
||||
void Sensor::publish_state(float state) {
|
||||
this->raw_state = state;
|
||||
if (this->raw_callback_) {
|
||||
this->raw_callback_->call(state);
|
||||
}
|
||||
this->raw_callback_.call(state);
|
||||
|
||||
ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state);
|
||||
|
||||
@@ -91,10 +89,7 @@ void Sensor::publish_state(float state) {
|
||||
|
||||
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) { this->callback_.add(std::move(callback)); }
|
||||
void Sensor::add_on_raw_state_callback(std::function<void(float)> &&callback) {
|
||||
if (!this->raw_callback_) {
|
||||
this->raw_callback_ = make_unique<CallbackManager<void(float)>>();
|
||||
}
|
||||
this->raw_callback_->add(std::move(callback));
|
||||
this->raw_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void Sensor::add_filter(Filter *filter) {
|
||||
|
||||
@@ -125,8 +125,8 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
|
||||
void internal_send_state_to_frontend(float state);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CallbackManager<void(float)>> raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
|
||||
CallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
|
||||
LazyCallbackManager<void(float)> raw_callback_; ///< Storage for raw state callbacks.
|
||||
LazyCallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
|
||||
|
||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||
|
||||
|
||||
@@ -2,11 +2,20 @@ import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensirion_common, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ALGORITHM_TUNING,
|
||||
CONF_COMPENSATION,
|
||||
CONF_GAIN_FACTOR,
|
||||
CONF_GATING_MAX_DURATION_MINUTES,
|
||||
CONF_ID,
|
||||
CONF_INDEX_OFFSET,
|
||||
CONF_LEARNING_TIME_GAIN_HOURS,
|
||||
CONF_LEARNING_TIME_OFFSET_HOURS,
|
||||
CONF_NOX,
|
||||
CONF_STD_INITIAL,
|
||||
CONF_STORE_BASELINE,
|
||||
CONF_TEMPERATURE_SOURCE,
|
||||
CONF_VOC,
|
||||
CONF_VOC_BASELINE,
|
||||
DEVICE_CLASS_AQI,
|
||||
ICON_RADIATOR,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
@@ -24,16 +33,7 @@ SGP4xComponent = sgp4x_ns.class_(
|
||||
sensirion_common.SensirionI2CDevice,
|
||||
)
|
||||
|
||||
CONF_ALGORITHM_TUNING = "algorithm_tuning"
|
||||
CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes"
|
||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||
CONF_INDEX_OFFSET = "index_offset"
|
||||
CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours"
|
||||
CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours"
|
||||
CONF_NOX = "nox"
|
||||
CONF_STD_INITIAL = "std_initial"
|
||||
CONF_VOC = "voc"
|
||||
CONF_VOC_BASELINE = "voc_baseline"
|
||||
|
||||
|
||||
def validate_sensors(config):
|
||||
|
||||
@@ -134,8 +134,8 @@ class Switch : public EntityBase, public EntityBase_DeviceClass {
|
||||
// Pointer first (4 bytes)
|
||||
ESPPreferenceObject rtc_;
|
||||
|
||||
// CallbackManager (12 bytes on 32-bit - contains vector)
|
||||
CallbackManager<void(bool)> state_callback_{};
|
||||
// LazyCallbackManager (4 bytes on 32-bit - nullptr when empty)
|
||||
LazyCallbackManager<void(bool)> state_callback_{};
|
||||
|
||||
// Small types grouped together
|
||||
Deduplicator<bool> publish_dedup_; // 2 bytes (bool has_value_ + bool last_value_)
|
||||
|
||||
@@ -44,7 +44,7 @@ class Text : public EntityBase {
|
||||
*/
|
||||
virtual void control(const std::string &value) = 0;
|
||||
|
||||
CallbackManager<void(const std::string &)> state_callback_;
|
||||
LazyCallbackManager<void(const std::string &)> state_callback_;
|
||||
};
|
||||
|
||||
} // namespace text
|
||||
|
||||
@@ -30,9 +30,7 @@ void TextSensor::publish_state(const std::string &state) {
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
this->raw_state = state;
|
||||
#pragma GCC diagnostic pop
|
||||
if (this->raw_callback_) {
|
||||
this->raw_callback_->call(state);
|
||||
}
|
||||
this->raw_callback_.call(state);
|
||||
|
||||
ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str());
|
||||
|
||||
@@ -73,14 +71,11 @@ void TextSensor::clear_filters() {
|
||||
this->filter_list_ = nullptr;
|
||||
}
|
||||
|
||||
void TextSensor::add_on_state_callback(std::function<void(std::string)> callback) {
|
||||
void TextSensor::add_on_state_callback(std::function<void(const std::string &)> callback) {
|
||||
this->callback_.add(std::move(callback));
|
||||
}
|
||||
void TextSensor::add_on_raw_state_callback(std::function<void(std::string)> callback) {
|
||||
if (!this->raw_callback_) {
|
||||
this->raw_callback_ = make_unique<CallbackManager<void(std::string)>>();
|
||||
}
|
||||
this->raw_callback_->add(std::move(callback));
|
||||
void TextSensor::add_on_raw_state_callback(std::function<void(const std::string &)> callback) {
|
||||
this->raw_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
std::string TextSensor::get_state() const { return this->state; }
|
||||
|
||||
@@ -55,9 +55,9 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
/// Clear the entire filter chain.
|
||||
void clear_filters();
|
||||
|
||||
void add_on_state_callback(std::function<void(std::string)> callback);
|
||||
void add_on_state_callback(std::function<void(const std::string &)> callback);
|
||||
/// Add a callback that will be called every time the sensor sends a raw value.
|
||||
void add_on_raw_state_callback(std::function<void(std::string)> callback);
|
||||
void add_on_raw_state_callback(std::function<void(const std::string &)> callback);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
@@ -65,9 +65,8 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
void internal_send_state_to_frontend(const std::string &state);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CallbackManager<void(std::string)>>
|
||||
raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
|
||||
CallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
|
||||
LazyCallbackManager<void(const std::string &)> raw_callback_; ///< Storage for raw state callbacks.
|
||||
LazyCallbackManager<void(const std::string &)> callback_; ///< Storage for filtered state callbacks.
|
||||
|
||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
|
||||
UpdateState state_{UPDATE_STATE_UNKNOWN};
|
||||
UpdateInfo update_info_;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
std::unique_ptr<Trigger<const UpdateInfo &>> update_available_trigger_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ class Valve : public EntityBase, public EntityBase_DeviceClass {
|
||||
|
||||
optional<ValveRestoreState> restore_state_();
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
@@ -528,6 +528,16 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
for (auto *listener : global_wifi_component->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = global_wifi_component->get_selected_sta_();
|
||||
config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : global_wifi_component->ip_state_listeners_) {
|
||||
listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(),
|
||||
global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -739,6 +739,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
|
||||
@@ -305,6 +305,14 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -259,6 +259,15 @@ void WiFiComponent::wifi_loop_() {
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, notify IP listeners immediately as the IP is already configured
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
s_sta_had_ip = true;
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
} else if (!is_connected && s_sta_was_connected) {
|
||||
// Just disconnected
|
||||
|
||||
@@ -123,6 +123,7 @@ CONF_ADDRESS = "address"
|
||||
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
|
||||
CONF_ADVANCED = "advanced"
|
||||
CONF_AFTER = "after"
|
||||
CONF_ALGORITHM_TUNING = "algorithm_tuning"
|
||||
CONF_ALL = "all"
|
||||
CONF_ALLOW_OTHER_USES = "allow_other_uses"
|
||||
CONF_ALPHA = "alpha"
|
||||
@@ -435,6 +436,7 @@ CONF_GAIN_FACTOR = "gain_factor"
|
||||
CONF_GAMMA_CORRECT = "gamma_correct"
|
||||
CONF_GAS_RESISTANCE = "gas_resistance"
|
||||
CONF_GATEWAY = "gateway"
|
||||
CONF_GATING_MAX_DURATION_MINUTES = "gating_max_duration_minutes"
|
||||
CONF_GLASS_ATTENUATION_FACTOR = "glass_attenuation_factor"
|
||||
CONF_GLYPHS = "glyphs"
|
||||
CONF_GPIO = "gpio"
|
||||
@@ -497,6 +499,7 @@ CONF_INCLUDE_INTERNAL = "include_internal"
|
||||
CONF_INCLUDES = "includes"
|
||||
CONF_INCLUDES_C = "includes_c"
|
||||
CONF_INDEX = "index"
|
||||
CONF_INDEX_OFFSET = "index_offset"
|
||||
CONF_INDOOR = "indoor"
|
||||
CONF_INFRARED = "infrared"
|
||||
CONF_INIT_SEQUENCE = "init_sequence"
|
||||
@@ -534,6 +537,8 @@ CONF_LAMBDA = "lambda"
|
||||
CONF_LAST_CONFIDENCE = "last_confidence"
|
||||
CONF_LAST_FINGER_ID = "last_finger_id"
|
||||
CONF_LATITUDE = "latitude"
|
||||
CONF_LEARNING_TIME_GAIN_HOURS = "learning_time_gain_hours"
|
||||
CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours"
|
||||
CONF_LED = "led"
|
||||
CONF_LEGEND = "legend"
|
||||
CONF_LENGTH = "length"
|
||||
@@ -645,7 +650,9 @@ CONF_NEVER = "never"
|
||||
CONF_NEW_PASSWORD = "new_password"
|
||||
CONF_NITROGEN_DIOXIDE = "nitrogen_dioxide"
|
||||
CONF_NOISE_LEVEL = "noise_level"
|
||||
CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope"
|
||||
CONF_NOTIFY = "notify"
|
||||
CONF_NOX = "nox"
|
||||
CONF_NUM_ATTEMPTS = "num_attempts"
|
||||
CONF_NUM_CHANNELS = "num_channels"
|
||||
CONF_NUM_CHIPS = "num_chips"
|
||||
@@ -939,6 +946,7 @@ CONF_STATE_TOPIC = "state_topic"
|
||||
CONF_STATIC_IP = "static_ip"
|
||||
CONF_STATUS = "status"
|
||||
CONF_STB_PIN = "stb_pin"
|
||||
CONF_STD_INITIAL = "std_initial"
|
||||
CONF_STEP = "step"
|
||||
CONF_STEP_DELAY = "step_delay"
|
||||
CONF_STEP_MODE = "step_mode"
|
||||
@@ -1006,6 +1014,7 @@ CONF_TILT_COMMAND_TOPIC = "tilt_command_topic"
|
||||
CONF_TILT_LAMBDA = "tilt_lambda"
|
||||
CONF_TILT_STATE_TOPIC = "tilt_state_topic"
|
||||
CONF_TIME = "time"
|
||||
CONF_TIME_CONSTANT = "time_constant"
|
||||
CONF_TIME_ID = "time_id"
|
||||
CONF_TIMEOUT = "timeout"
|
||||
CONF_TIMES = "times"
|
||||
@@ -1060,6 +1069,8 @@ CONF_VERSION = "version"
|
||||
CONF_VIBRATIONS = "vibrations"
|
||||
CONF_VISIBLE = "visible"
|
||||
CONF_VISUAL = "visual"
|
||||
CONF_VOC = "voc"
|
||||
CONF_VOC_BASELINE = "voc_baseline"
|
||||
CONF_VOLTAGE = "voltage"
|
||||
CONF_VOLTAGE_ATTENUATION = "voltage_attenuation"
|
||||
CONF_VOLTAGE_DIVIDER = "voltage_divider"
|
||||
|
||||
@@ -471,10 +471,14 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) {
|
||||
}
|
||||
|
||||
size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) {
|
||||
int in_len = encoded_string.size();
|
||||
return base64_decode(reinterpret_cast<const uint8_t *>(encoded_string.data()), encoded_string.size(), buf, buf_len);
|
||||
}
|
||||
|
||||
size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len) {
|
||||
size_t in_len = encoded_len;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int in = 0;
|
||||
size_t in = 0;
|
||||
size_t out = 0;
|
||||
uint8_t char_array_4[4], char_array_3[3];
|
||||
bool truncated = false;
|
||||
@@ -482,8 +486,8 @@ size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf
|
||||
// SAFETY: The loop condition checks is_base64() before processing each character.
|
||||
// This ensures base64_find_char() is only called on valid base64 characters,
|
||||
// preventing the edge case where invalid chars would return 0 (same as 'A').
|
||||
while (in_len-- && (encoded_string[in] != '=') && is_base64(encoded_string[in])) {
|
||||
char_array_4[i++] = encoded_string[in];
|
||||
while (in_len-- && (encoded_data[in] != '=') && is_base64(encoded_data[in])) {
|
||||
char_array_4[i++] = encoded_data[in];
|
||||
in++;
|
||||
if (i == 4) {
|
||||
for (i = 0; i < 4; i++)
|
||||
|
||||
@@ -885,6 +885,7 @@ std::string base64_encode(const std::vector<uint8_t> &buf);
|
||||
|
||||
std::vector<uint8_t> base64_decode(const std::string &encoded_string);
|
||||
size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len);
|
||||
size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len);
|
||||
|
||||
///@}
|
||||
|
||||
@@ -941,6 +942,50 @@ template<typename... Ts> class CallbackManager<void(Ts...)> {
|
||||
std::vector<std::function<void(Ts...)>> callbacks_;
|
||||
};
|
||||
|
||||
template<typename... X> class LazyCallbackManager;
|
||||
|
||||
/** Lazy-allocating callback manager that only allocates memory when callbacks are registered.
|
||||
*
|
||||
* This is a drop-in replacement for CallbackManager that saves memory when no callbacks
|
||||
* are registered (common case after the Controller Registry eliminated per-entity callbacks
|
||||
* from API and web_server components).
|
||||
*
|
||||
* Memory overhead comparison (32-bit systems):
|
||||
* - CallbackManager: 12 bytes (empty std::vector)
|
||||
* - LazyCallbackManager: 4 bytes (nullptr unique_ptr)
|
||||
*
|
||||
* @tparam Ts The arguments for the callbacks, wrapped in void().
|
||||
*/
|
||||
template<typename... Ts> class LazyCallbackManager<void(Ts...)> {
|
||||
public:
|
||||
/// Add a callback to the list. Allocates the underlying CallbackManager on first use.
|
||||
void add(std::function<void(Ts...)> &&callback) {
|
||||
if (!this->callbacks_) {
|
||||
this->callbacks_ = make_unique<CallbackManager<void(Ts...)>>();
|
||||
}
|
||||
this->callbacks_->add(std::move(callback));
|
||||
}
|
||||
|
||||
/// Call all callbacks in this manager. No-op if no callbacks registered.
|
||||
void call(Ts... args) {
|
||||
if (this->callbacks_) {
|
||||
this->callbacks_->call(args...);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of registered callbacks.
|
||||
size_t size() const { return this->callbacks_ ? this->callbacks_->size() : 0; }
|
||||
|
||||
/// Check if any callbacks are registered.
|
||||
bool empty() const { return !this->callbacks_ || this->callbacks_->size() == 0; }
|
||||
|
||||
/// Call all callbacks in this manager.
|
||||
void operator()(Ts... args) { this->call(args...); }
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CallbackManager<void(Ts...)>> callbacks_;
|
||||
};
|
||||
|
||||
/// Helper class to deduplicate items in a series of values.
|
||||
template<typename T> class Deduplicator {
|
||||
public:
|
||||
|
||||
@@ -279,14 +279,30 @@ async def test_alarm_control_panel_state_transitions(
|
||||
except TimeoutError:
|
||||
pytest.fail(f"on_chime callback not fired. Log lines: {log_lines[-20:]}")
|
||||
|
||||
# Close the chime sensor
|
||||
# Close the chime sensor and wait for alarm to become ready again
|
||||
# We need to wait for this transition before testing door sensor,
|
||||
# otherwise there's a race where the door sensor state change could
|
||||
# arrive before the chime sensor state change, leaving the alarm in
|
||||
# a continuous "not ready" state with no on_ready callback fired.
|
||||
ready_after_chime_close: asyncio.Future[bool] = loop.create_future()
|
||||
ready_futures.append(ready_after_chime_close)
|
||||
|
||||
client.switch_command(chime_switch_info.key, False)
|
||||
|
||||
# ===== Test ready state changes =====
|
||||
# Opening/closing sensors while disarmed affects ready state
|
||||
# The on_ready callback fires when sensors_ready changes
|
||||
# Wait for alarm to become ready again (chime sensor closed)
|
||||
try:
|
||||
await asyncio.wait_for(ready_after_chime_close, timeout=2.0)
|
||||
except TimeoutError:
|
||||
pytest.fail(
|
||||
f"on_ready callback not fired when chime sensor closed. "
|
||||
f"Log lines: {log_lines[-20:]}"
|
||||
)
|
||||
|
||||
# Set up futures for ready state changes
|
||||
# ===== Test ready state changes =====
|
||||
# Now the alarm is confirmed ready. Opening/closing door sensor
|
||||
# should trigger on_ready callbacks.
|
||||
|
||||
# Set up futures for door sensor state changes
|
||||
ready_future_1: asyncio.Future[bool] = loop.create_future()
|
||||
ready_future_2: asyncio.Future[bool] = loop.create_future()
|
||||
ready_futures.extend([ready_future_1, ready_future_2])
|
||||
|
||||
Reference in New Issue
Block a user