Merge pull request #14025 from esphome/bump-2026.2.0b3

2026.2.0b3
This commit is contained in:
Jesse Hills
2026-02-17 16:22:12 +13:00
committed by GitHub
25 changed files with 250 additions and 115 deletions

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2026.2.0b2
PROJECT_NUMBER = 2026.2.0b3
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a

View File

@@ -126,7 +126,7 @@ void LinearCombinationComponent::setup() {
}
void LinearCombinationComponent::handle_new_value(float value) {
// Multiplies each sensor state by a configured coeffecient and then sums
// Multiplies each sensor state by a configured coefficient and then sums
if (!std::isfinite(value))
return;

View File

@@ -1,3 +1,5 @@
import logging
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
@@ -15,6 +17,8 @@ from esphome.const import (
)
from esphome.core.entity_helpers import inherit_property_from
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@Cat-Ion", "@kahrendt"]
combination_ns = cg.esphome_ns.namespace("combination")
@@ -47,7 +51,8 @@ SumCombinationComponent = combination_ns.class_(
"SumCombinationComponent", cg.Component, sensor.Sensor
)
CONF_COEFFECIENT = "coeffecient"
CONF_COEFFICIENT = "coefficient"
CONF_COEFFECIENT = "coeffecient" # Deprecated, remove before 2026.12.0
CONF_ERROR = "error"
CONF_KALMAN = "kalman"
CONF_LINEAR = "linear"
@@ -68,11 +73,34 @@ KALMAN_SOURCE_SCHEMA = cv.Schema(
}
)
LINEAR_SOURCE_SCHEMA = cv.Schema(
{
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
cv.Required(CONF_COEFFECIENT): cv.templatable(cv.float_),
}
def _migrate_coeffecient(config):
"""Migrate deprecated 'coeffecient' spelling to 'coefficient'."""
if CONF_COEFFECIENT in config:
if CONF_COEFFICIENT in config:
raise cv.Invalid(
f"Cannot specify both '{CONF_COEFFICIENT}' and '{CONF_COEFFECIENT}'"
)
_LOGGER.warning(
"'%s' is deprecated, use '%s' instead. Will be removed in 2026.12.0",
CONF_COEFFECIENT,
CONF_COEFFICIENT,
)
config[CONF_COEFFICIENT] = config.pop(CONF_COEFFECIENT)
elif CONF_COEFFICIENT not in config:
raise cv.Invalid(f"'{CONF_COEFFICIENT}' is a required option")
return config
LINEAR_SOURCE_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
cv.Optional(CONF_COEFFICIENT): cv.templatable(cv.float_),
cv.Optional(CONF_COEFFECIENT): cv.templatable(cv.float_),
}
),
_migrate_coeffecient,
)
SENSOR_ONLY_SOURCE_SCHEMA = cv.Schema(
@@ -162,12 +190,12 @@ async def to_code(config):
)
cg.add(var.add_source(source, error))
elif config[CONF_TYPE] == CONF_LINEAR:
coeffecient = await cg.templatable(
source_conf[CONF_COEFFECIENT],
coefficient = await cg.templatable(
source_conf[CONF_COEFFICIENT],
[(float, "x")],
cg.float_,
)
cg.add(var.add_source(source, coeffecient))
cg.add(var.add_source(source, coefficient))
else:
cg.add(var.add_source(source))

View File

@@ -1,8 +1,30 @@
from esphome.components import esp32
import esphome.config_validation as cv
from esphome.core import CORE
CODEOWNERS = ["@jesserockz"]
VARIANTS_NO_RMT = {esp32.VARIANT_ESP32C2, esp32.VARIANT_ESP32C61}
def validate_rmt_not_supported(rmt_only_keys):
"""Validate that RMT-only config keys are not used on variants without RMT hardware."""
rmt_only_keys = set(rmt_only_keys)
def _validator(config):
if CORE.is_esp32:
variant = esp32.get_esp32_variant()
if variant in VARIANTS_NO_RMT:
unsupported = rmt_only_keys.intersection(config)
if unsupported:
keys = ", ".join(sorted(f"'{k}'" for k in unsupported))
raise cv.Invalid(
f"{keys} not available on {variant} (no RMT hardware)"
)
return config
return _validator
def validate_clock_resolution():
def _validator(value):

View File

@@ -3,7 +3,7 @@ import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32, light
from esphome.components import esp32, esp32_rmt, light
from esphome.components.const import CONF_USE_PSRAM
from esphome.components.esp32 import include_builtin_idf_component
import esphome.config_validation as cv
@@ -71,6 +71,10 @@ CONF_RESET_LOW = "reset_low"
CONFIG_SCHEMA = cv.All(
esp32.only_on_variant(
unsupported=list(esp32_rmt.VARIANTS_NO_RMT),
msg_prefix="ESP32 RMT LED strip",
),
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput),

View File

@@ -221,12 +221,17 @@ void Fan::publish_state() {
}
// Random 32-bit value, change this every time the layout of the FanRestoreState struct changes.
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABB;
optional<FanRestoreState> Fan::restore_state_() {
FanRestoreState recovered{};
this->rtc_ = this->make_entity_preference<FanRestoreState>(RESTORE_STATE_VERSION);
bool restored = this->rtc_.load(&recovered);
if (!restored) {
// No valid saved data; ensure preset_mode sentinel is set
recovered.preset_mode = FanRestoreState::NO_PRESET;
}
switch (this->restore_mode_) {
case FanRestoreMode::NO_RESTORE:
return {};
@@ -264,6 +269,7 @@ void Fan::save_state_() {
state.oscillating = this->oscillating;
state.speed = this->speed;
state.direction = this->direction;
state.preset_mode = FanRestoreState::NO_PRESET;
if (this->has_preset_mode()) {
const auto &preset_modes = traits.supported_preset_modes();

View File

@@ -91,11 +91,13 @@ class FanCall {
};
struct FanRestoreState {
static constexpr uint8_t NO_PRESET = UINT8_MAX;
bool state;
int speed;
bool oscillating;
FanDirection direction;
uint8_t preset_mode;
uint8_t preset_mode{NO_PRESET};
/// Convert this struct to a fan call that can be performed.
FanCall to_call(Fan &fan);

View File

@@ -28,15 +28,15 @@ fan::FanCall HBridgeFan::brake() {
}
void HBridgeFan::setup() {
// Construct traits before restore so preset modes can be looked up by index
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
this->traits_.set_supported_preset_modes(this->preset_modes_);
auto restore = this->restore_state_();
if (restore.has_value()) {
restore->apply(*this);
this->write_state_();
}
// Construct traits
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
this->traits_.set_supported_preset_modes(this->preset_modes_);
}
void HBridgeFan::dump_config() {

View File

@@ -119,6 +119,8 @@ class RemoteComponentBase {
};
#ifdef USE_ESP32
#include <soc/soc_caps.h>
#if SOC_RMT_SUPPORTED
class RemoteRMTChannel {
public:
void set_clock_resolution(uint32_t clock_resolution) { this->clock_resolution_ = clock_resolution; }
@@ -137,7 +139,8 @@ class RemoteRMTChannel {
uint32_t clock_resolution_{1000000};
uint32_t rmt_symbols_;
};
#endif
#endif // SOC_RMT_SUPPORTED
#endif // USE_ESP32
class RemoteTransmitterBase : public RemoteComponentBase {
public:

View File

@@ -65,6 +65,8 @@ RemoteReceiverComponent = remote_receiver_ns.class_(
def validate_config(config):
if CORE.is_esp32:
variant = esp32.get_esp32_variant()
if variant in esp32_rmt.VARIANTS_NO_RMT:
return config
if variant in (esp32.VARIANT_ESP32, esp32.VARIANT_ESP32S2):
max_idle = 65535
else:
@@ -110,6 +112,8 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
cv.SplitDefault(
CONF_BUFFER_SIZE,
esp32="10000b",
esp32_c2="1000b",
esp32_c61="1000b",
esp8266="1000b",
bk72xx="1000b",
ln882x="1000b",
@@ -131,9 +135,11 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
cv.SplitDefault(
CONF_RMT_SYMBOLS,
esp32=192,
esp32_c2=cv.UNDEFINED,
esp32_c3=96,
esp32_c5=96,
esp32_c6=96,
esp32_c61=cv.UNDEFINED,
esp32_h2=96,
esp32_p4=192,
esp32_s2=192,
@@ -145,6 +151,8 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
cv.SplitDefault(
CONF_RECEIVE_SYMBOLS,
esp32=192,
esp32_c2=cv.UNDEFINED,
esp32_c61=cv.UNDEFINED,
): cv.All(cv.only_on_esp32, cv.int_range(min=2)),
cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant(
@@ -152,24 +160,45 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
),
cv.boolean,
),
cv.SplitDefault(CONF_CARRIER_DUTY_PERCENT, esp32=100): cv.All(
cv.SplitDefault(
CONF_CARRIER_DUTY_PERCENT,
esp32=100,
esp32_c2=cv.UNDEFINED,
esp32_c61=cv.UNDEFINED,
): cv.All(
cv.only_on_esp32,
cv.percentage_int,
cv.Range(min=1, max=100),
),
cv.SplitDefault(CONF_CARRIER_FREQUENCY, esp32="0Hz"): cv.All(
cv.only_on_esp32, cv.frequency, cv.int_
),
cv.SplitDefault(
CONF_CARRIER_FREQUENCY,
esp32="0Hz",
esp32_c2=cv.UNDEFINED,
esp32_c61=cv.UNDEFINED,
): cv.All(cv.only_on_esp32, cv.frequency, cv.int_),
}
)
.extend(cv.COMPONENT_SCHEMA)
.add_extra(
esp32_rmt.validate_rmt_not_supported(
[
CONF_CLOCK_RESOLUTION,
CONF_USE_DMA,
CONF_RMT_SYMBOLS,
CONF_FILTER_SYMBOLS,
CONF_RECEIVE_SYMBOLS,
CONF_CARRIER_DUTY_PERCENT,
CONF_CARRIER_FREQUENCY,
]
)
)
.add_extra(validate_config)
)
async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN])
if CORE.is_esp32:
if CORE.is_esp32 and esp32.get_esp32_variant() not in esp32_rmt.VARIANTS_NO_RMT:
# Re-enable ESP-IDF's RMT driver (excluded by default to save compile time)
esp32.include_builtin_idf_component("esp_driver_rmt")
@@ -213,6 +242,8 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_IDF,
},
"remote_receiver.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,

View File

@@ -3,7 +3,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040)
#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
namespace esphome::remote_receiver {

View File

@@ -6,12 +6,15 @@
#include <cinttypes>
#if defined(USE_ESP32)
#include <soc/soc_caps.h>
#if SOC_RMT_SUPPORTED
#include <driver/rmt_rx.h>
#endif
#endif // SOC_RMT_SUPPORTED
#endif // USE_ESP32
namespace esphome::remote_receiver {
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040)
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
struct RemoteReceiverComponentStore {
static void gpio_intr(RemoteReceiverComponentStore *arg);
@@ -35,7 +38,7 @@ struct RemoteReceiverComponentStore {
volatile bool prev_level{false};
volatile bool overflow{false};
};
#elif defined(USE_ESP32)
#elif defined(USE_ESP32) && SOC_RMT_SUPPORTED
struct RemoteReceiverComponentStore {
/// Stores RMT symbols and rx done event data
volatile uint8_t *buffer{nullptr};
@@ -54,7 +57,7 @@ struct RemoteReceiverComponentStore {
class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
public Component
#ifdef USE_ESP32
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
,
public remote_base::RemoteRMTChannel
#endif
@@ -66,7 +69,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
void dump_config() override;
void loop() override;
#ifdef USE_ESP32
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
void set_filter_symbols(uint32_t filter_symbols) { this->filter_symbols_ = filter_symbols; }
void set_receive_symbols(uint32_t receive_symbols) { this->receive_symbols_ = receive_symbols; }
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
@@ -78,7 +81,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; }
protected:
#ifdef USE_ESP32
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
void decode_rmt_(rmt_symbol_word_t *item, size_t item_count);
rmt_channel_handle_t channel_{NULL};
uint32_t filter_symbols_{0};
@@ -94,7 +97,7 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
RemoteReceiverComponentStore store_;
#endif
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040)
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
HighFrequencyLoopRequester high_freq_;
#endif

View File

@@ -2,6 +2,8 @@
#include "esphome/core/log.h"
#ifdef USE_ESP32
#include <soc/soc_caps.h>
#if SOC_RMT_SUPPORTED
#include <driver/gpio.h>
#include <esp_clk_tree.h>
@@ -248,4 +250,5 @@ void RemoteReceiverComponent::decode_rmt_(rmt_symbol_word_t *item, size_t item_c
} // namespace esphome::remote_receiver
#endif
#endif // SOC_RMT_SUPPORTED
#endif // USE_ESP32

View File

@@ -40,45 +40,66 @@ DigitalWriteAction = remote_transmitter_ns.class_(
cg.Parented.template(RemoteTransmitterComponent),
)
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(RemoteTransmitterComponent),
cv.Required(CONF_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All(
cv.percentage_int, cv.Range(min=1, max=100)
),
cv.Optional(CONF_CLOCK_RESOLUTION): cv.All(
cv.only_on_esp32,
esp32_rmt.validate_clock_resolution(),
),
cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_on_esp32, cv.boolean),
cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant(
supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3]
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(RemoteTransmitterComponent),
cv.Required(CONF_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All(
cv.percentage_int, cv.Range(min=1, max=100)
),
cv.boolean,
),
cv.SplitDefault(
CONF_RMT_SYMBOLS,
esp32=64,
esp32_c3=48,
esp32_c5=48,
esp32_c6=48,
esp32_h2=48,
esp32_p4=48,
esp32_s2=64,
esp32_s3=48,
): cv.All(cv.only_on_esp32, cv.int_range(min=2)),
cv.Optional(CONF_NON_BLOCKING): cv.All(cv.only_on_esp32, cv.boolean),
cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True),
cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True),
}
).extend(cv.COMPONENT_SCHEMA)
cv.Optional(CONF_CLOCK_RESOLUTION): cv.All(
cv.only_on_esp32,
esp32_rmt.validate_clock_resolution(),
),
cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_on_esp32, cv.boolean),
cv.Optional(CONF_USE_DMA): cv.All(
esp32.only_on_variant(
supported=[esp32.VARIANT_ESP32P4, esp32.VARIANT_ESP32S3]
),
cv.boolean,
),
cv.SplitDefault(
CONF_RMT_SYMBOLS,
esp32=64,
esp32_c2=cv.UNDEFINED,
esp32_c3=48,
esp32_c5=48,
esp32_c6=48,
esp32_c61=cv.UNDEFINED,
esp32_h2=48,
esp32_p4=48,
esp32_s2=64,
esp32_s3=48,
): cv.All(cv.only_on_esp32, cv.int_range(min=2)),
cv.Optional(CONF_NON_BLOCKING): cv.All(cv.only_on_esp32, cv.boolean),
cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True),
cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True),
}
)
.extend(cv.COMPONENT_SCHEMA)
.add_extra(
esp32_rmt.validate_rmt_not_supported(
[
CONF_CLOCK_RESOLUTION,
CONF_EOT_LEVEL,
CONF_USE_DMA,
CONF_RMT_SYMBOLS,
CONF_NON_BLOCKING,
]
)
)
)
def _validate_non_blocking(config):
if CORE.is_esp32 and CONF_NON_BLOCKING not in config:
if (
CORE.is_esp32
and esp32.get_esp32_variant() not in esp32_rmt.VARIANTS_NO_RMT
and CONF_NON_BLOCKING not in config
):
_LOGGER.warning(
"'non_blocking' is not set for 'remote_transmitter' and will default to 'true'.\n"
"The default behavior changed in 2025.11.0; previously blocking mode was used.\n"
@@ -111,7 +132,7 @@ async def digital_write_action_to_code(config, action_id, template_arg, args):
async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN])
if CORE.is_esp32:
if CORE.is_esp32 and esp32.get_esp32_variant() not in esp32_rmt.VARIANTS_NO_RMT:
# Re-enable ESP-IDF's RMT driver (excluded by default to save compile time)
esp32.include_builtin_idf_component("esp_driver_rmt")
@@ -155,6 +176,8 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_IDF,
},
"remote_transmitter.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,

View File

@@ -5,8 +5,7 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace remote_transmitter {
namespace esphome::remote_transmitter {
template<typename... Ts> class DigitalWriteAction : public Action<Ts...>, public Parented<RemoteTransmitterComponent> {
public:
@@ -14,5 +13,4 @@ template<typename... Ts> class DigitalWriteAction : public Action<Ts...>, public
void play(const Ts &...x) override { this->parent_->digital_write(this->value_.value(x...)); }
};
} // namespace remote_transmitter
} // namespace esphome
} // namespace esphome::remote_transmitter

View File

@@ -2,10 +2,9 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040)
#if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
namespace esphome {
namespace remote_transmitter {
namespace esphome::remote_transmitter {
static const char *const TAG = "remote_transmitter";
@@ -105,7 +104,6 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
this->complete_trigger_.trigger();
}
} // namespace remote_transmitter
} // namespace esphome
} // namespace esphome::remote_transmitter
#endif

View File

@@ -6,13 +6,15 @@
#include <vector>
#if defined(USE_ESP32)
#include <soc/soc_caps.h>
#if SOC_RMT_SUPPORTED
#include <driver/rmt_tx.h>
#endif
#endif // SOC_RMT_SUPPORTED
#endif // USE_ESP32
namespace esphome {
namespace remote_transmitter {
namespace esphome::remote_transmitter {
#ifdef USE_ESP32
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
// IDF version 5.5.1 and above is required because of a bug in
// the RMT encoder: https://github.com/espressif/esp-idf/issues/17244
@@ -33,7 +35,7 @@ struct RemoteTransmitterComponentStore {
class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
public Component
#ifdef USE_ESP32
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
,
public remote_base::RemoteRMTChannel
#endif
@@ -51,7 +53,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
void digital_write(bool value);
#if defined(USE_ESP32)
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; }
void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; }
@@ -62,7 +64,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
protected:
void send_internal(uint32_t send_times, uint32_t send_wait) override;
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040)
#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED)
void calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period, uint32_t *off_time_period);
void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec);
@@ -73,7 +75,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
uint32_t target_time_;
#endif
#ifdef USE_ESP32
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
void configure_rmt_();
void wait_for_rmt_();
@@ -100,5 +102,4 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
Trigger<> complete_trigger_;
};
} // namespace remote_transmitter
} // namespace esphome
} // namespace esphome::remote_transmitter

View File

@@ -3,10 +3,11 @@
#include "esphome/core/application.h"
#ifdef USE_ESP32
#include <soc/soc_caps.h>
#if SOC_RMT_SUPPORTED
#include <driver/gpio.h>
namespace esphome {
namespace remote_transmitter {
namespace esphome::remote_transmitter {
static const char *const TAG = "remote_transmitter";
@@ -358,7 +359,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
}
#endif
} // namespace remote_transmitter
} // namespace esphome
} // namespace esphome::remote_transmitter
#endif
#endif // SOC_RMT_SUPPORTED
#endif // USE_ESP32

View File

@@ -7,15 +7,15 @@ namespace speed {
static const char *const TAG = "speed.fan";
void SpeedFan::setup() {
// Construct traits before restore so preset modes can be looked up by index
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_);
this->traits_.set_supported_preset_modes(this->preset_modes_);
auto restore = this->restore_state_();
if (restore.has_value()) {
restore->apply(*this);
this->write_state_();
}
// Construct traits
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_);
this->traits_.set_supported_preset_modes(this->preset_modes_);
}
void SpeedFan::dump_config() { LOG_FAN("", "Speed Fan", this); }

View File

@@ -6,15 +6,15 @@ namespace esphome::template_ {
static const char *const TAG = "template.fan";
void TemplateFan::setup() {
// Construct traits before restore so preset modes can be looked up by index
this->traits_ =
fan::FanTraits(this->has_oscillating_, this->speed_count_ > 0, this->has_direction_, this->speed_count_);
this->traits_.set_supported_preset_modes(this->preset_modes_);
auto restore = this->restore_state_();
if (restore.has_value()) {
restore->apply(*this);
}
// Construct traits
this->traits_ =
fan::FanTraits(this->has_oscillating_, this->speed_count_ > 0, this->has_direction_, this->speed_count_);
this->traits_.set_supported_preset_modes(this->preset_modes_);
}
void TemplateFan::dump_config() { LOG_FAN("", "Template Fan", this); }

View File

@@ -216,23 +216,16 @@ bool WiFiComponent::wifi_apply_hostname_() {
ESP_LOGV(TAG, "Set hostname failed");
}
// inform dhcp server of hostname change using dhcp_renew()
// Update hostname on all lwIP interfaces so DHCP packets include it.
// lwIP includes the hostname in DHCP DISCOVER/REQUEST automatically
// via LWIP_NETIF_HOSTNAME — no dhcp_renew() needed. The hostname is
// fixed at compile time and never changes at runtime.
for (netif *intf = netif_list; intf; intf = intf->next) {
// unconditionally update all known interfaces
#if LWIP_VERSION_MAJOR == 1
intf->hostname = (char *) wifi_station_get_hostname();
#else
intf->hostname = wifi_station_get_hostname();
#endif
if (netif_dhcp_data(intf) != nullptr) {
// renew already started DHCP leases
err_t lwipret = dhcp_renew(intf);
if (lwipret != ERR_OK) {
ESP_LOGW(TAG, "wifi_apply_hostname_(%s): lwIP error %d on interface %c%c (index %d)", intf->hostname,
(int) lwipret, intf->name[0], intf->name[1], intf->num);
ret = false;
}
}
}
return ret;

View File

@@ -4,7 +4,7 @@ from enum import Enum
from esphome.enum import StrEnum
__version__ = "2026.2.0b2"
__version__ = "2026.2.0b3"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (

View File

@@ -27,9 +27,9 @@ sensor:
name: Linearly combined temperatures
sources:
- source: template_temperature1
coeffecient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;"
coefficient: !lambda "return 0.4 + std::abs(x - 25) * 0.023;"
- source: template_temperature2
coeffecient: 1.5
coefficient: 1.5
- platform: combination
type: max
name: Max of combined temperatures

View File

@@ -0,0 +1,12 @@
remote_receiver:
id: rcvr
pin: GPIO2
dump: all
<<: !include common-actions.yaml
binary_sensor:
- platform: remote_receiver
name: Panasonic Remote Input
panasonic:
address: 0x4004
command: 0x100BCBD

View File

@@ -0,0 +1,7 @@
remote_transmitter:
id: xmitr
pin: GPIO2
carrier_duty_percent: 50%
packages:
buttons: !include common-buttons.yaml