diff --git a/esphome/components/pid/climate.py b/esphome/components/pid/climate.py index 5919d2cac8..5fa3166f9d 100644 --- a/esphome/components/pid/climate.py +++ b/esphome/components/pid/climate.py @@ -50,8 +50,8 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_HEAT_OUTPUT): cv.use_id(output.FloatOutput), cv.Optional(CONF_DEADBAND_PARAMETERS): cv.Schema( { - cv.Required(CONF_THRESHOLD_HIGH): cv.temperature, - cv.Required(CONF_THRESHOLD_LOW): cv.temperature, + cv.Required(CONF_THRESHOLD_HIGH): cv.temperature_delta, + cv.Required(CONF_THRESHOLD_LOW): cv.temperature_delta, cv.Optional(CONF_KP_MULTIPLIER, default=0.1): cv.float_, cv.Optional(CONF_KI_MULTIPLIER, default=0.0): cv.float_, cv.Optional(CONF_KD_MULTIPLIER, default=0.0): cv.float_, diff --git a/esphome/components/runtime_image/runtime_image.cpp b/esphome/components/runtime_image/runtime_image.cpp index 603ea76f01..5a4a2ea7d6 100644 --- a/esphome/components/runtime_image/runtime_image.cpp +++ b/esphome/components/runtime_image/runtime_image.cpp @@ -2,6 +2,7 @@ #include "image_decoder.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include #include #ifdef USE_RUNTIME_IMAGE_BMP @@ -43,6 +44,14 @@ int RuntimeImage::resize(int width, int height) { int target_width = this->fixed_width_ ? this->fixed_width_ : width; int target_height = this->fixed_height_ ? this->fixed_height_ : height; + // When both fixed dimensions are set, scale uniformly to preserve aspect ratio + if (this->fixed_width_ && this->fixed_height_ && width > 0 && height > 0) { + float scale = + std::min(static_cast(this->fixed_width_) / width, static_cast(this->fixed_height_) / height); + target_width = static_cast(width * scale); + target_height = static_cast(height * scale); + } + size_t result = this->resize_buffer_(target_width, target_height); if (result > 0 && this->progressive_display_) { // Update display dimensions for progressive display diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index fc4932d867..09dda8bca4 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -56,15 +56,6 @@ static const LogString *rht_accel_mode_to_string(RhtAccelerationMode mode) { } } -// This function performs an in-place conversion of the provided buffer -// from uint16_t values to big endianness -static inline const char *sensirion_convert_to_string_in_place(uint16_t *array, size_t length) { - for (size_t i = 0; i < length; i++) { - array[i] = convert_big_endian(array[i]); - } - return reinterpret_cast(array); -} - void SEN5XComponent::setup() { // the sensor needs 1000 ms to enter the idle state this->set_timeout(1000, [this]() { diff --git a/esphome/components/sensirion_common/i2c_sensirion.h b/esphome/components/sensirion_common/i2c_sensirion.h index f3eb3761f6..3c2c14ccb8 100644 --- a/esphome/components/sensirion_common/i2c_sensirion.h +++ b/esphome/components/sensirion_common/i2c_sensirion.h @@ -21,6 +21,17 @@ class SensirionI2CDevice : public i2c::I2CDevice { public: enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 }; + /** + * This function performs an in-place conversion of the provided buffer + * from uint16_t values to big endianness. Useful for Sensirion strings in SEN5X and SEN6X + */ + static inline const char *sensirion_convert_to_string_in_place(uint16_t *array, size_t length) { + for (size_t i = 0; i < length; i++) { + array[i] = convert_big_endian(array[i]); + } + return reinterpret_cast(array); + } + /** Read data words from I2C device. * handles CRC check used by Sensirion sensors * @param data pointer to raw result diff --git a/esphome/config_validation.py b/esphome/config_validation.py index ef1c66a20e..3b0e4da298 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1640,7 +1640,10 @@ def dimensions(value): if width <= 0 or height <= 0: raise Invalid("Width and height must at least be 1") return [width, height] - value = string(value) + if not isinstance(value, str): + raise Invalid( + "Dimensions must be a string (WIDTHxHEIGHT). Got a number instead, try quoting the value." + ) match = re.match(r"\s*([0-9]+)\s*[xX]\s*([0-9]+)\s*", value) if not match: raise Invalid( diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 6aafcdde4d..996cf6b263 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -155,7 +155,7 @@ void Application::setup() { this->after_loop_tasks_(); this->app_state_ = new_app_state; yield(); - } while (!component->can_proceed()); + } while (!component->can_proceed() && !component->is_failed()); } ESP_LOGI(TAG, "setup() finished successfully!"); diff --git a/tests/components/light/common.yaml b/tests/components/light/common.yaml index 55525fc67f..e5fab62a79 100644 --- a/tests/components/light/common.yaml +++ b/tests/components/light/common.yaml @@ -71,6 +71,32 @@ esphome: - light.control: id: test_monochromatic_light state: on + # Test static effect name resolution at codegen time + - light.turn_on: + id: test_monochromatic_light + effect: Strobe + - light.turn_on: + id: test_monochromatic_light + effect: none + # Test resolving a different effect on the same light + - light.control: + id: test_monochromatic_light + effect: My Flicker + # Test effect: None (capitalized) + - light.control: + id: test_monochromatic_light + effect: None + # Test effect lambda with no args (on_boot has empty Ts...) + - light.turn_on: + id: test_monochromatic_light + effect: !lambda 'return "Strobe";' + # Test effect lambda with non-empty args (repeat passes uint32_t iteration) + - repeat: + count: 3 + then: + - light.turn_on: + id: test_monochromatic_light + effect: !lambda 'return iteration > 1 ? "Strobe" : "none";' - light.dim_relative: id: test_monochromatic_light relative_brightness: 5%