1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-21 11:13:46 +01:00

Merge branch 'integration' into memory_api

This commit is contained in:
J. Nick Koston
2025-10-05 17:10:30 -05:00
14 changed files with 181 additions and 150 deletions

View File

@@ -68,6 +68,10 @@ void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
} }
void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) { void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
this->advertising_set_manufacturer_data(std::span<const uint8_t>(data));
}
void ESP32BLE::advertising_set_manufacturer_data(std::span<const uint8_t> data) {
this->advertising_init_(); this->advertising_init_();
this->advertising_->set_manufacturer_data(data); this->advertising_->set_manufacturer_data(data);
this->advertising_start(); this->advertising_start();

View File

@@ -118,6 +118,7 @@ class ESP32BLE : public Component {
void advertising_start(); void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data); void advertising_set_service_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data); void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(std::span<const uint8_t> data);
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; } void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name); void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name);
void advertising_add_service_uuid(ESPBTUUID uuid); void advertising_add_service_uuid(ESPBTUUID uuid);

View File

@@ -59,6 +59,10 @@ void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) {
} }
void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) { void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
this->set_manufacturer_data(std::span<const uint8_t>(data));
}
void BLEAdvertising::set_manufacturer_data(std::span<const uint8_t> data) {
delete[] this->advertising_data_.p_manufacturer_data; delete[] this->advertising_data_.p_manufacturer_data;
this->advertising_data_.p_manufacturer_data = nullptr; this->advertising_data_.p_manufacturer_data = nullptr;
this->advertising_data_.manufacturer_len = data.size(); this->advertising_data_.manufacturer_len = data.size();

View File

@@ -35,6 +35,7 @@ class BLEAdvertising {
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; } void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data); void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_manufacturer_data(std::span<const uint8_t> data);
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; } void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
void set_service_data(const std::vector<uint8_t> &data); void set_service_data(const std::vector<uint8_t> &data);
void set_service_data(std::span<const uint8_t> data); void set_service_data(std::span<const uint8_t> data);

View File

@@ -99,7 +99,8 @@ bool BLEServer::can_proceed() { return this->is_running() || !this->parent_->is_
void BLEServer::restart_advertising_() { void BLEServer::restart_advertising_() {
if (this->is_running()) { if (this->is_running()) {
this->parent_->advertising_set_manufacturer_data(this->manufacturer_data_); this->parent_->advertising_set_manufacturer_data(
std::span<const uint8_t>(this->manufacturer_data_.get(), this->manufacturer_data_length_));
} }
} }

View File

@@ -35,7 +35,11 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
bool is_running(); bool is_running();
void set_manufacturer_data(const std::vector<uint8_t> &data) { void set_manufacturer_data(const std::vector<uint8_t> &data) {
this->manufacturer_data_ = data; this->manufacturer_data_length_ = data.size();
this->manufacturer_data_.reset(data.empty() ? nullptr : new uint8_t[data.size()]);
if (!data.empty()) {
memcpy(this->manufacturer_data_.get(), data.data(), data.size());
}
this->restart_advertising_(); this->restart_advertising_();
} }
@@ -87,24 +91,27 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
void remove_client_(uint16_t conn_id); void remove_client_(uint16_t conn_id);
void dispatch_callbacks_(CallbackType type, uint16_t conn_id); void dispatch_callbacks_(CallbackType type, uint16_t conn_id);
// 4-byte aligned (pointers and vectors on 32-bit)
std::vector<CallbackEntry> callbacks_; std::vector<CallbackEntry> callbacks_;
std::vector<uint8_t> manufacturer_data_{};
esp_gatt_if_t gatts_if_{0};
bool registered_{false};
uint16_t clients_[USE_ESP32_BLE_MAX_CONNECTIONS]{};
uint8_t client_count_{0};
std::vector<ServiceEntry> services_{}; std::vector<ServiceEntry> services_{};
std::vector<BLEService *> services_to_start_{}; std::vector<BLEService *> services_to_start_{};
std::unique_ptr<uint8_t[]> manufacturer_data_{};
BLEService *device_information_service_{}; BLEService *device_information_service_{};
// 2-byte aligned
uint16_t clients_[USE_ESP32_BLE_MAX_CONNECTIONS]{};
// 1-byte aligned
uint8_t manufacturer_data_length_{0};
uint8_t client_count_{0};
esp_gatt_if_t gatts_if_{0};
enum State : uint8_t { enum State : uint8_t {
INIT = 0x00, INIT = 0x00,
REGISTERING, REGISTERING,
STARTING_SERVICE, STARTING_SERVICE,
RUNNING, RUNNING,
} state_{INIT}; } state_{INIT};
bool registered_{false};
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -10,11 +10,15 @@ namespace light {
static const char *const TAG = "light"; static const char *const TAG = "light";
// Helper functions to reduce code size for logging // Helper functions to reduce code size for logging
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN static void clamp_and_log_if_invalid(const char *name, float &value, const LogString *param_name, float min = 0.0f,
static void log_validation_warning(const char *name, const LogString *param_name, float val, float min, float max) { float max = 1.0f) {
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_ARG(param_name), val, min, max); if (value < min || value > max) {
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_ARG(param_name), value, min, max);
value = clamp(value, min, max);
}
} }
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
static void log_feature_not_supported(const char *name, const LogString *feature) { static void log_feature_not_supported(const char *name, const LogString *feature) {
ESP_LOGW(TAG, "'%s': %s not supported", name, LOG_STR_ARG(feature)); ESP_LOGW(TAG, "'%s': %s not supported", name, LOG_STR_ARG(feature));
} }
@@ -27,7 +31,6 @@ static void log_invalid_parameter(const char *name, const LogString *message) {
ESP_LOGW(TAG, "'%s': %s", name, LOG_STR_ARG(message)); ESP_LOGW(TAG, "'%s': %s", name, LOG_STR_ARG(message));
} }
#else #else
#define log_validation_warning(name, param_name, val, min, max)
#define log_feature_not_supported(name, feature) #define log_feature_not_supported(name, feature)
#define log_color_mode_not_supported(name, feature) #define log_color_mode_not_supported(name, feature)
#define log_invalid_parameter(name, message) #define log_invalid_parameter(name, message)
@@ -44,7 +47,7 @@ static void log_invalid_parameter(const char *name, const LogString *message) {
} \ } \
LightCall &LightCall::set_##name(type name) { \ LightCall &LightCall::set_##name(type name) { \
this->name##_ = name; \ this->name##_ = name; \
this->set_flag_(flag, true); \ this->set_flag_(flag); \
return *this; \ return *this; \
} }
@@ -181,6 +184,16 @@ void LightCall::perform() {
} }
} }
void LightCall::log_and_clear_unsupported_(FieldFlags flag, const LogString *feature, bool use_color_mode_log) {
auto *name = this->parent_->get_name().c_str();
if (use_color_mode_log) {
log_color_mode_not_supported(name, feature);
} else {
log_feature_not_supported(name, feature);
}
this->clear_flag_(flag);
}
LightColorValues LightCall::validate_() { LightColorValues LightCall::validate_() {
auto *name = this->parent_->get_name().c_str(); auto *name = this->parent_->get_name().c_str();
auto traits = this->parent_->get_traits(); auto traits = this->parent_->get_traits();
@@ -188,141 +201,108 @@ LightColorValues LightCall::validate_() {
// Color mode check // Color mode check
if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) { if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) {
ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_))); ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_)));
this->set_flag_(FLAG_HAS_COLOR_MODE, false); this->clear_flag_(FLAG_HAS_COLOR_MODE);
} }
// Ensure there is always a color mode set // Ensure there is always a color mode set
if (!this->has_color_mode()) { if (!this->has_color_mode()) {
this->color_mode_ = this->compute_color_mode_(); this->color_mode_ = this->compute_color_mode_();
this->set_flag_(FLAG_HAS_COLOR_MODE, true); this->set_flag_(FLAG_HAS_COLOR_MODE);
} }
auto color_mode = this->color_mode_; auto color_mode = this->color_mode_;
// Transform calls that use non-native parameters for the current mode. // Transform calls that use non-native parameters for the current mode.
this->transform_parameters_(); this->transform_parameters_();
// Brightness exists check // Business logic adjustments before validation
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
log_feature_not_supported(name, LOG_STR("brightness"));
this->set_flag_(FLAG_HAS_BRIGHTNESS, false);
}
// Transition length possible check
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) {
log_feature_not_supported(name, LOG_STR("transitions"));
this->set_flag_(FLAG_HAS_TRANSITION, false);
}
// Color brightness exists check
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
log_color_mode_not_supported(name, LOG_STR("RGB brightness"));
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false);
}
// RGB exists check
if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) ||
(this->has_blue() && this->blue_ > 0.0f)) {
if (!(color_mode & ColorCapability::RGB)) {
log_color_mode_not_supported(name, LOG_STR("RGB color"));
this->set_flag_(FLAG_HAS_RED, false);
this->set_flag_(FLAG_HAS_GREEN, false);
this->set_flag_(FLAG_HAS_BLUE, false);
}
}
// White value exists check
if (this->has_white() && this->white_ > 0.0f &&
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
log_color_mode_not_supported(name, LOG_STR("white value"));
this->set_flag_(FLAG_HAS_WHITE, false);
}
// Color temperature exists check
if (this->has_color_temperature() &&
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
log_color_mode_not_supported(name, LOG_STR("color temperature"));
this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false);
}
// Cold/warm white value exists check
if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) {
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
log_color_mode_not_supported(name, LOG_STR("cold/warm white value"));
this->set_flag_(FLAG_HAS_COLD_WHITE, false);
this->set_flag_(FLAG_HAS_WARM_WHITE, false);
}
}
#define VALIDATE_RANGE_(name_, upper_name, min, max) \
if (this->has_##name_()) { \
auto val = this->name_##_; \
if (val < (min) || val > (max)) { \
log_validation_warning(name, LOG_STR(upper_name), val, (min), (max)); \
this->name_##_ = clamp(val, (min), (max)); \
} \
}
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f)
// Range checks
VALIDATE_RANGE(brightness, "Brightness")
VALIDATE_RANGE(color_brightness, "Color brightness")
VALIDATE_RANGE(red, "Red")
VALIDATE_RANGE(green, "Green")
VALIDATE_RANGE(blue, "Blue")
VALIDATE_RANGE(white, "White")
VALIDATE_RANGE(cold_white, "Cold white")
VALIDATE_RANGE(warm_white, "Warm white")
VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds())
// Flag whether an explicit turn off was requested, in which case we'll also stop the effect. // Flag whether an explicit turn off was requested, in which case we'll also stop the effect.
bool explicit_turn_off_request = this->has_state() && !this->state_; bool explicit_turn_off_request = this->has_state() && !this->state_;
// Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on). // Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on).
if (this->has_brightness() && this->brightness_ == 0.0f) { if (this->has_brightness() && this->brightness_ == 0.0f) {
this->state_ = false; this->state_ = false;
this->set_flag_(FLAG_HAS_STATE, true); this->set_flag_(FLAG_HAS_STATE);
this->brightness_ = 1.0f; this->brightness_ = 1.0f;
} }
// Set color brightness to 100% if currently zero and a color is set. // Set color brightness to 100% if currently zero and a color is set.
if (this->has_red() || this->has_green() || this->has_blue()) { if ((this->has_red() || this->has_green() || this->has_blue()) && !this->has_color_brightness() &&
if (!this->has_color_brightness() && this->parent_->remote_values.get_color_brightness() == 0.0f) { this->parent_->remote_values.get_color_brightness() == 0.0f) {
this->color_brightness_ = 1.0f; this->color_brightness_ = 1.0f;
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, true); this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS);
}
} }
// Create color values for the light with this call applied. // Capability validation
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS))
this->log_and_clear_unsupported_(FLAG_HAS_BRIGHTNESS, LOG_STR("brightness"), false);
// Transition length possible check
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS))
this->log_and_clear_unsupported_(FLAG_HAS_TRANSITION, LOG_STR("transitions"), false);
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB))
this->log_and_clear_unsupported_(FLAG_HAS_COLOR_BRIGHTNESS, LOG_STR("RGB brightness"), true);
// RGB exists check
if (((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) ||
(this->has_blue() && this->blue_ > 0.0f)) &&
!(color_mode & ColorCapability::RGB)) {
log_color_mode_not_supported(name, LOG_STR("RGB color"));
this->clear_flag_(FLAG_HAS_RED);
this->clear_flag_(FLAG_HAS_GREEN);
this->clear_flag_(FLAG_HAS_BLUE);
}
// White value exists check
if (this->has_white() && this->white_ > 0.0f &&
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE))
this->log_and_clear_unsupported_(FLAG_HAS_WHITE, LOG_STR("white value"), true);
// Color temperature exists check
if (this->has_color_temperature() &&
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE))
this->log_and_clear_unsupported_(FLAG_HAS_COLOR_TEMPERATURE, LOG_STR("color temperature"), true);
// Cold/warm white value exists check
if (((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) &&
!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
log_color_mode_not_supported(name, LOG_STR("cold/warm white value"));
this->clear_flag_(FLAG_HAS_COLD_WHITE);
this->clear_flag_(FLAG_HAS_WARM_WHITE);
}
// Create color values and validate+apply ranges in one step to eliminate duplicate checks
auto v = this->parent_->remote_values; auto v = this->parent_->remote_values;
if (this->has_color_mode()) if (this->has_color_mode())
v.set_color_mode(this->color_mode_); v.set_color_mode(this->color_mode_);
if (this->has_state()) if (this->has_state())
v.set_state(this->state_); v.set_state(this->state_);
if (this->has_brightness())
v.set_brightness(this->brightness_); #define VALIDATE_AND_APPLY(field, setter, name_str, ...) \
if (this->has_color_brightness()) if (this->has_##field()) { \
v.set_color_brightness(this->color_brightness_); clamp_and_log_if_invalid(name, this->field##_, LOG_STR(name_str), ##__VA_ARGS__); \
if (this->has_red()) v.setter(this->field##_); \
v.set_red(this->red_); }
if (this->has_green())
v.set_green(this->green_); VALIDATE_AND_APPLY(brightness, set_brightness, "Brightness")
if (this->has_blue()) VALIDATE_AND_APPLY(color_brightness, set_color_brightness, "Color brightness")
v.set_blue(this->blue_); VALIDATE_AND_APPLY(red, set_red, "Red")
if (this->has_white()) VALIDATE_AND_APPLY(green, set_green, "Green")
v.set_white(this->white_); VALIDATE_AND_APPLY(blue, set_blue, "Blue")
if (this->has_color_temperature()) VALIDATE_AND_APPLY(white, set_white, "White")
v.set_color_temperature(this->color_temperature_); VALIDATE_AND_APPLY(cold_white, set_cold_white, "Cold white")
if (this->has_cold_white()) VALIDATE_AND_APPLY(warm_white, set_warm_white, "Warm white")
v.set_cold_white(this->cold_white_); VALIDATE_AND_APPLY(color_temperature, set_color_temperature, "Color temperature", traits.get_min_mireds(),
if (this->has_warm_white()) traits.get_max_mireds())
v.set_warm_white(this->warm_white_);
#undef VALIDATE_AND_APPLY
v.normalize_color(); v.normalize_color();
// Flash length check // Flash length check
if (this->has_flash_() && this->flash_length_ == 0) { if (this->has_flash_() && this->flash_length_ == 0) {
log_invalid_parameter(name, LOG_STR("flash length must be greater than zero")); log_invalid_parameter(name, LOG_STR("flash length must be >= zero"));
this->set_flag_(FLAG_HAS_FLASH, false); this->clear_flag_(FLAG_HAS_FLASH);
} }
// validate transition length/flash length/effect not used at the same time // validate transition length/flash length/effect not used at the same time
@@ -330,42 +310,40 @@ LightColorValues LightCall::validate_() {
// If effect is already active, remove effect start // If effect is already active, remove effect start
if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) { if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) {
this->set_flag_(FLAG_HAS_EFFECT, false); this->clear_flag_(FLAG_HAS_EFFECT);
} }
// validate effect index // validate effect index
if (this->has_effect_() && this->effect_ > this->parent_->effects_.size()) { if (this->has_effect_() && this->effect_ > this->parent_->effects_.size()) {
ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, this->effect_); ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, this->effect_);
this->set_flag_(FLAG_HAS_EFFECT, false); this->clear_flag_(FLAG_HAS_EFFECT);
} }
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
log_invalid_parameter(name, LOG_STR("effect cannot be used with transition/flash")); log_invalid_parameter(name, LOG_STR("effect cannot be used with transition/flash"));
this->set_flag_(FLAG_HAS_TRANSITION, false); this->clear_flag_(FLAG_HAS_TRANSITION);
this->set_flag_(FLAG_HAS_FLASH, false); this->clear_flag_(FLAG_HAS_FLASH);
} }
if (this->has_flash_() && this->has_transition_()) { if (this->has_flash_() && this->has_transition_()) {
log_invalid_parameter(name, LOG_STR("flash cannot be used with transition")); log_invalid_parameter(name, LOG_STR("flash cannot be used with transition"));
this->set_flag_(FLAG_HAS_TRANSITION, false); this->clear_flag_(FLAG_HAS_TRANSITION);
} }
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || this->effect_ == 0) && if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || this->effect_ == 0) &&
supports_transition) { supports_transition) {
// nothing specified and light supports transitions, set default transition length // nothing specified and light supports transitions, set default transition length
this->transition_length_ = this->parent_->default_transition_length_; this->transition_length_ = this->parent_->default_transition_length_;
this->set_flag_(FLAG_HAS_TRANSITION, true); this->set_flag_(FLAG_HAS_TRANSITION);
} }
if (this->has_transition_() && this->transition_length_ == 0) { if (this->has_transition_() && this->transition_length_ == 0) {
// 0 transition is interpreted as no transition (instant change) // 0 transition is interpreted as no transition (instant change)
this->set_flag_(FLAG_HAS_TRANSITION, false); this->clear_flag_(FLAG_HAS_TRANSITION);
} }
if (this->has_transition_() && !supports_transition) { if (this->has_transition_() && !supports_transition)
log_feature_not_supported(name, LOG_STR("transitions")); this->log_and_clear_unsupported_(FLAG_HAS_TRANSITION, LOG_STR("transitions"), false);
this->set_flag_(FLAG_HAS_TRANSITION, false);
}
// If not a flash and turning the light off, then disable the light // If not a flash and turning the light off, then disable the light
// Do not use light color values directly, so that effects can set 0% brightness // Do not use light color values directly, so that effects can set 0% brightness
@@ -374,17 +352,17 @@ LightColorValues LightCall::validate_() {
if (!this->has_flash_() && !target_state) { if (!this->has_flash_() && !target_state) {
if (this->has_effect_()) { if (this->has_effect_()) {
log_invalid_parameter(name, LOG_STR("cannot start effect when turning off")); log_invalid_parameter(name, LOG_STR("cannot start effect when turning off"));
this->set_flag_(FLAG_HAS_EFFECT, false); this->clear_flag_(FLAG_HAS_EFFECT);
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
// Auto turn off effect // Auto turn off effect
this->effect_ = 0; this->effect_ = 0;
this->set_flag_(FLAG_HAS_EFFECT, true); this->set_flag_(FLAG_HAS_EFFECT);
} }
} }
// Disable saving for flashes // Disable saving for flashes
if (this->has_flash_()) if (this->has_flash_())
this->set_flag_(FLAG_SAVE, false); this->clear_flag_(FLAG_SAVE);
return v; return v;
} }
@@ -418,12 +396,12 @@ void LightCall::transform_parameters_() {
const float gamma = this->parent_->get_gamma_correct(); const float gamma = this->parent_->get_gamma_correct();
this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, gamma); this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, gamma);
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, gamma); this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, gamma);
this->set_flag_(FLAG_HAS_COLD_WHITE, true); this->set_flag_(FLAG_HAS_COLD_WHITE);
this->set_flag_(FLAG_HAS_WARM_WHITE, true); this->set_flag_(FLAG_HAS_WARM_WHITE);
} }
if (this->has_white()) { if (this->has_white()) {
this->brightness_ = this->white_; this->brightness_ = this->white_;
this->set_flag_(FLAG_HAS_BRIGHTNESS, true); this->set_flag_(FLAG_HAS_BRIGHTNESS);
} }
} }
} }
@@ -630,7 +608,7 @@ LightCall &LightCall::set_effect(optional<std::string> effect) {
} }
LightCall &LightCall::set_effect(uint32_t effect_number) { LightCall &LightCall::set_effect(uint32_t effect_number) {
this->effect_ = effect_number; this->effect_ = effect_number;
this->set_flag_(FLAG_HAS_EFFECT, true); this->set_flag_(FLAG_HAS_EFFECT);
return *this; return *this;
} }
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) { LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {

View File

@@ -4,6 +4,10 @@
#include <set> #include <set>
namespace esphome { namespace esphome {
// Forward declaration
struct LogString;
namespace light { namespace light {
class LightState; class LightState;
@@ -207,14 +211,14 @@ class LightCall {
FLAG_SAVE = 1 << 15, FLAG_SAVE = 1 << 15,
}; };
bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; } inline bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; }
bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; } inline bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; }
bool has_effect_() { return (this->flags_ & FLAG_HAS_EFFECT) != 0; } inline bool has_effect_() { return (this->flags_ & FLAG_HAS_EFFECT) != 0; }
bool get_publish_() { return (this->flags_ & FLAG_PUBLISH) != 0; } inline bool get_publish_() { return (this->flags_ & FLAG_PUBLISH) != 0; }
bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; } inline bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; }
// Helper to set flag // Helper to set flag - defaults to true for common case
void set_flag_(FieldFlags flag, bool value) { void set_flag_(FieldFlags flag, bool value = true) {
if (value) { if (value) {
this->flags_ |= flag; this->flags_ |= flag;
} else { } else {
@@ -222,6 +226,12 @@ class LightCall {
} }
} }
// Helper to clear flag - reduces code size for common case
void clear_flag_(FieldFlags flag) { this->flags_ &= ~flag; }
// Helper to log unsupported feature and clear flag - reduces code duplication
void log_and_clear_unsupported_(FieldFlags flag, const LogString *feature, bool use_color_mode_log);
LightState *parent_; LightState *parent_;
// Light state values - use flags_ to check if a value has been set. // Light state values - use flags_ to check if a value has been set.

View File

@@ -79,7 +79,7 @@ void MDNSComponent::compile_records_() {
#ifdef USE_API #ifdef USE_API
if (api::global_api_server != nullptr) { if (api::global_api_server != nullptr) {
auto &service = this->services_[this->services_.count()++]; auto &service = this->services_.emplace_next();
service.service_type = MDNS_STR(SERVICE_ESPHOMELIB); service.service_type = MDNS_STR(SERVICE_ESPHOMELIB);
service.proto = MDNS_STR(SERVICE_TCP); service.proto = MDNS_STR(SERVICE_TCP);
service.port = api::global_api_server->get_port(); service.port = api::global_api_server->get_port();
@@ -158,14 +158,14 @@ void MDNSComponent::compile_records_() {
#endif // USE_API #endif // USE_API
#ifdef USE_PROMETHEUS #ifdef USE_PROMETHEUS
auto &prom_service = this->services_[this->services_.count()++]; auto &prom_service = this->services_.emplace_next();
prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS); prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS);
prom_service.proto = MDNS_STR(SERVICE_TCP); prom_service.proto = MDNS_STR(SERVICE_TCP);
prom_service.port = USE_WEBSERVER_PORT; prom_service.port = USE_WEBSERVER_PORT;
#endif #endif
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
auto &web_service = this->services_[this->services_.count()++]; auto &web_service = this->services_.emplace_next();
web_service.service_type = MDNS_STR(SERVICE_HTTP); web_service.service_type = MDNS_STR(SERVICE_HTTP);
web_service.proto = MDNS_STR(SERVICE_TCP); web_service.proto = MDNS_STR(SERVICE_TCP);
web_service.port = USE_WEBSERVER_PORT; web_service.port = USE_WEBSERVER_PORT;
@@ -174,7 +174,7 @@ void MDNSComponent::compile_records_() {
#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES) #if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES)
// Publish "http" service if not using native API or any other services // Publish "http" service if not using native API or any other services
// This is just to have *some* mDNS service so that .local resolution works // This is just to have *some* mDNS service so that .local resolution works
auto &fallback_service = this->services_[this->services_.count()++]; auto &fallback_service = this->services_.emplace_next();
fallback_service.service_type = "_http"; fallback_service.service_type = "_http";
fallback_service.proto = "_tcp"; fallback_service.proto = "_tcp";
fallback_service.port = USE_WEBSERVER_PORT; fallback_service.port = USE_WEBSERVER_PORT;

View File

@@ -39,7 +39,7 @@ class MDNSComponent : public Component {
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
#ifdef USE_MDNS_EXTRA_SERVICES #ifdef USE_MDNS_EXTRA_SERVICES
void add_extra_service(MDNSService service) { this->services_[this->services_.count()++] = std::move(service); } void add_extra_service(MDNSService service) { this->services_.emplace_next() = std::move(service); }
#endif #endif
const StaticVector<MDNSService, MDNS_SERVICE_COUNT> &get_services() const { return this->services_; } const StaticVector<MDNSService, MDNS_SERVICE_COUNT> &get_services() const { return this->services_; }

View File

@@ -52,6 +52,20 @@ DefaultHeaders &DefaultHeaders::Instance() { return default_headers_instance; }
namespace { namespace {
// Non-blocking send function to prevent watchdog timeouts when TCP buffers are full // Non-blocking send function to prevent watchdog timeouts when TCP buffers are full
/**
* Sends data on a socket in non-blocking mode.
*
* @param hd HTTP server handle (unused).
* @param sockfd Socket file descriptor.
* @param buf Buffer to send.
* @param buf_len Length of buffer.
* @param flags Flags for send().
* @return
* - Number of bytes sent on success.
* - HTTPD_SOCK_ERR_INVALID if buf is nullptr.
* - HTTPD_SOCK_ERR_TIMEOUT if the send buffer is full (EAGAIN/EWOULDBLOCK).
* - HTTPD_SOCK_ERR_FAIL for other errors.
*/
int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags) { int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags) {
if (buf == nullptr) { if (buf == nullptr) {
return HTTPD_SOCK_ERR_INVALID; return HTTPD_SOCK_ERR_INVALID;
@@ -319,8 +333,8 @@ AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) {
} }
} }
// Don't cache misses to prevent memory exhaustion from malicious requests // Don't cache misses to avoid wasting memory when handlers check for
// with thousands of non-existent parameter lookups // optional parameters that don't exist in the request
if (!val.has_value()) { if (!val.has_value()) {
return nullptr; return nullptr;
} }

View File

@@ -172,7 +172,8 @@ class AsyncWebServerRequest {
AsyncWebServerResponse *rsp_{}; AsyncWebServerResponse *rsp_{};
// Use vector instead of map/unordered_map: most requests have 0-3 params, so linear search // Use vector instead of map/unordered_map: most requests have 0-3 params, so linear search
// is faster than tree/hash overhead. AsyncWebParameter stores both name and value to avoid // is faster than tree/hash overhead. AsyncWebParameter stores both name and value to avoid
// duplicate storage. Only successful lookups are cached to prevent memory exhaustion attacks. // duplicate storage. Only successful lookups are cached to prevent cache pollution when
// handlers check for optional parameters that don't exist.
std::vector<AsyncWebParameter *> params_; std::vector<AsyncWebParameter *> params_;
std::string post_query_; std::string post_query_;
AsyncWebServerRequest(httpd_req_t *req) : req_(req) {} AsyncWebServerRequest(httpd_req_t *req) : req_(req) {}

View File

@@ -365,7 +365,7 @@ void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
ESP_LOGV(TAG, " Identity: " LOG_SECRET("'%s'"), eap_config.identity.c_str()); ESP_LOGV(TAG, " Identity: " LOG_SECRET("'%s'"), eap_config.identity.c_str());
ESP_LOGV(TAG, " Username: " LOG_SECRET("'%s'"), eap_config.username.c_str()); ESP_LOGV(TAG, " Username: " LOG_SECRET("'%s'"), eap_config.username.c_str());
ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), eap_config.password.c_str()); ESP_LOGV(TAG, " Password: " LOG_SECRET("'%s'"), eap_config.password.c_str());
#if defined(USE_ESP32) && ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE #if defined(USE_ESP32) && defined(USE_WIFI_WPA2_EAP) && ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGV(TAG, " TTLS Phase 2: " LOG_SECRET("'%s'"), eap_phase2_to_str(eap_config.ttls_phase_2)); ESP_LOGV(TAG, " TTLS Phase 2: " LOG_SECRET("'%s'"), eap_phase2_to_str(eap_config.ttls_phase_2));
#endif #endif
bool ca_cert_present = eap_config.ca_cert != nullptr && strlen(eap_config.ca_cert); bool ca_cert_present = eap_config.ca_cert != nullptr && strlen(eap_config.ca_cert);

View File

@@ -130,6 +130,16 @@ template<typename T, size_t N> class StaticVector {
} }
} }
// Return reference to next element and increment count (with bounds checking)
T &emplace_next() {
if (count_ >= N) {
// Should never happen with proper size calculation
// Return reference to last element to avoid crash
return data_[N - 1];
}
return data_[count_++];
}
size_t size() const { return count_; } size_t size() const { return count_; }
bool empty() const { return count_ == 0; } bool empty() const { return count_ == 0; }