1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 08:41:59 +00:00

Merge branch 'dev' into combine_logs

This commit is contained in:
J. Nick Koston
2026-01-03 16:05:53 -10:00
committed by GitHub
13 changed files with 367 additions and 7 deletions

View File

@@ -3,6 +3,7 @@ import esphome.codegen as cg
from esphome.components import output
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER
from esphome.core import CORE
CODEOWNERS = ["@glmnet"]
@@ -36,6 +37,12 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
if CORE.is_esp8266:
# ac_dimmer uses setTimer1Callback which requires the waveform generator
from esphome.components.esp8266.const import require_waveform
require_waveform()
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -644,6 +644,7 @@ CONF_DISABLE_VFS_SUPPORT_SELECT = "disable_vfs_support_select"
CONF_DISABLE_VFS_SUPPORT_DIR = "disable_vfs_support_dir"
CONF_FREERTOS_IN_IRAM = "freertos_in_iram"
CONF_RINGBUF_IN_IRAM = "ringbuf_in_iram"
CONF_HEAP_IN_IRAM = "heap_in_iram"
CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
# VFS requirement tracking
@@ -745,6 +746,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
cv.Optional(CONF_FREERTOS_IN_IRAM, default=False): cv.boolean,
cv.Optional(CONF_RINGBUF_IN_IRAM, default=False): cv.boolean,
cv.Optional(CONF_HEAP_IN_IRAM, default=False): cv.boolean,
cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean,
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
min=8192, max=32768
@@ -1090,6 +1092,12 @@ async def to_code(config):
# Place in flash to save IRAM (default)
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True)
# Place heap functions into flash to save IRAM (~4-6KB savings)
# Safe as long as heap functions are not called from ISRs (which they shouldn't be)
# Users can set heap_in_iram: true as an escape hatch if needed
if not conf[CONF_ADVANCED][CONF_HEAP_IN_IRAM]:
add_idf_sdkconfig_option("CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH", True)
# Setup watchdog
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True)
add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True)

View File

@@ -98,7 +98,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
channel.trans_queue_depth = 1;
channel.flags.io_loop_back = 0;
channel.flags.io_od_mode = 0;
channel.flags.invert_out = 0;
channel.flags.invert_out = this->invert_out_;
channel.flags.with_dma = this->use_dma_;
channel.intr_priority = 0;
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {

View File

@@ -49,6 +49,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
}
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_inverted(bool inverted) { this->invert_out_ = inverted; }
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
@@ -93,6 +94,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
bool is_wrgb_{false};
bool use_dma_{false};
bool use_psram_{false};
bool invert_out_{false};
RGBOrder rgb_order_{ORDER_RGB};

View File

@@ -8,9 +8,11 @@ from esphome.components.const import CONF_USE_PSRAM
import esphome.config_validation as cv
from esphome.const import (
CONF_CHIPSET,
CONF_INVERTED,
CONF_IS_RGBW,
CONF_MAX_REFRESH_RATE,
CONF_NUM_LEDS,
CONF_NUMBER,
CONF_OUTPUT_ID,
CONF_PIN,
CONF_RGB_ORDER,
@@ -71,7 +73,7 @@ CONFIG_SCHEMA = cv.All(
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
cv.SplitDefault(
@@ -132,7 +134,9 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_pin(config[CONF_PIN][CONF_NUMBER]))
if config[CONF_PIN][CONF_INVERTED]:
cg.add(var.set_inverted(True))
if CONF_MAX_REFRESH_RATE in config:
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))

View File

@@ -66,6 +66,8 @@ void Logger::pre_setup() {
void HOT Logger::write_msg_(const char *msg, size_t len) {
// Single write with newline already in buffer (added by caller)
#ifdef CONFIG_PRINTK
// Requires the debug component and an active SWD connection.
// It is used for pyocd rtt -t nrf52840
k_str_out(const_cast<char *>(msg), len);
#endif
if (this->uart_dev_ == nullptr) {

View File

@@ -0,0 +1,123 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import water_heater
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MODE,
CONF_OPTIMISTIC,
CONF_RESTORE_MODE,
CONF_SET_ACTION,
CONF_SUPPORTED_MODES,
CONF_TARGET_TEMPERATURE,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
from esphome.types import ConfigType
from .. import template_ns
CONF_CURRENT_TEMPERATURE = "current_temperature"
TemplateWaterHeater = template_ns.class_(
"TemplateWaterHeater", water_heater.WaterHeater
)
TemplateWaterHeaterPublishAction = template_ns.class_(
"TemplateWaterHeaterPublishAction",
automation.Action,
cg.Parented.template(TemplateWaterHeater),
)
TemplateWaterHeaterRestoreMode = template_ns.enum("TemplateWaterHeaterRestoreMode")
RESTORE_MODES = {
"NO_RESTORE": TemplateWaterHeaterRestoreMode.WATER_HEATER_NO_RESTORE,
"RESTORE": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE,
"RESTORE_AND_CALL": TemplateWaterHeaterRestoreMode.WATER_HEATER_RESTORE_AND_CALL,
}
CONFIG_SCHEMA = water_heater.water_heater_schema(TemplateWaterHeater).extend(
{
cv.Optional(CONF_OPTIMISTIC, default=True): cv.boolean,
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
RESTORE_MODES, upper=True
),
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.returning_lambda,
cv.Optional(CONF_MODE): cv.returning_lambda,
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
water_heater.validate_water_heater_mode
),
}
)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await water_heater.register_water_heater(var, config)
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if CONF_SET_ACTION in config:
await automation.build_automation(
var.get_set_trigger(), [], config[CONF_SET_ACTION]
)
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
if CONF_CURRENT_TEMPERATURE in config:
template_ = await cg.process_lambda(
config[CONF_CURRENT_TEMPERATURE],
[],
return_type=cg.optional.template(cg.float_),
)
cg.add(var.set_current_temperature_lambda(template_))
if CONF_MODE in config:
template_ = await cg.process_lambda(
config[CONF_MODE],
[],
return_type=cg.optional.template(water_heater.WaterHeaterMode),
)
cg.add(var.set_mode_lambda(template_))
if CONF_SUPPORTED_MODES in config:
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
@automation.register_action(
"water_heater.template.publish",
TemplateWaterHeaterPublishAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(TemplateWaterHeater),
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_MODE): cv.templatable(
water_heater.validate_water_heater_mode
),
}
),
)
async def water_heater_template_publish_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
if current_temp := config.get(CONF_CURRENT_TEMPERATURE):
template_ = await cg.templatable(current_temp, args, float)
cg.add(var.set_current_temperature(template_))
if target_temp := config.get(CONF_TARGET_TEMPERATURE):
template_ = await cg.templatable(target_temp, args, float)
cg.add(var.set_target_temperature(template_))
if mode := config.get(CONF_MODE):
template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode)
cg.add(var.set_mode(template_))
return var

View File

@@ -0,0 +1,35 @@
#pragma once
#include "template_water_heater.h"
#include "esphome/core/automation.h"
namespace esphome::template_ {
template<typename... Ts>
class TemplateWaterHeaterPublishAction : public Action<Ts...>, public Parented<TemplateWaterHeater> {
public:
TEMPLATABLE_VALUE(float, current_temperature)
TEMPLATABLE_VALUE(float, target_temperature)
TEMPLATABLE_VALUE(water_heater::WaterHeaterMode, mode)
void play(const Ts &...x) override {
if (this->current_temperature_.has_value()) {
this->parent_->set_current_temperature(this->current_temperature_.value(x...));
}
bool needs_call = this->target_temperature_.has_value() || this->mode_.has_value();
if (needs_call) {
auto call = this->parent_->make_call();
if (this->target_temperature_.has_value()) {
call.set_target_temperature(this->target_temperature_.value(x...));
}
if (this->mode_.has_value()) {
call.set_mode(this->mode_.value(x...));
}
call.perform();
} else {
this->parent_->publish_state();
}
}
};
} // namespace esphome::template_

View File

@@ -0,0 +1,88 @@
#include "template_water_heater.h"
#include "esphome/core/log.h"
namespace esphome::template_ {
static const char *const TAG = "template.water_heater";
TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {}
void TemplateWaterHeater::setup() {
if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE ||
this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE_AND_CALL) {
auto restore = this->restore_state();
if (restore.has_value()) {
restore->perform();
}
}
if (!this->current_temperature_f_.has_value() && !this->mode_f_.has_value())
this->disable_loop();
}
water_heater::WaterHeaterTraits TemplateWaterHeater::traits() {
auto traits = water_heater::WaterHeater::get_traits();
if (!this->supported_modes_.empty()) {
traits.set_supported_modes(this->supported_modes_);
}
traits.set_supports_current_temperature(true);
return traits;
}
void TemplateWaterHeater::loop() {
bool changed = false;
auto curr_temp = this->current_temperature_f_.call();
if (curr_temp.has_value()) {
if (*curr_temp != this->current_temperature_) {
this->current_temperature_ = *curr_temp;
changed = true;
}
}
auto new_mode = this->mode_f_.call();
if (new_mode.has_value()) {
if (*new_mode != this->mode_) {
this->mode_ = *new_mode;
changed = true;
}
}
if (changed) {
this->publish_state();
}
}
void TemplateWaterHeater::dump_config() {
LOG_WATER_HEATER("", "Template Water Heater", this);
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
}
float TemplateWaterHeater::get_setup_priority() const { return setup_priority::HARDWARE; }
water_heater::WaterHeaterCallInternal TemplateWaterHeater::make_call() {
return water_heater::WaterHeaterCallInternal(this);
}
void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) {
if (call.get_mode().has_value()) {
if (this->optimistic_) {
this->mode_ = *call.get_mode();
}
}
if (!std::isnan(call.get_target_temperature())) {
if (this->optimistic_) {
this->target_temperature_ = call.get_target_temperature();
}
}
this->set_trigger_->trigger();
if (this->optimistic_) {
this->publish_state();
}
}
} // namespace esphome::template_

View File

@@ -0,0 +1,53 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h"
#include "esphome/components/water_heater/water_heater.h"
namespace esphome::template_ {
enum TemplateWaterHeaterRestoreMode {
WATER_HEATER_NO_RESTORE,
WATER_HEATER_RESTORE,
WATER_HEATER_RESTORE_AND_CALL,
};
class TemplateWaterHeater : public water_heater::WaterHeater {
public:
TemplateWaterHeater();
template<typename F> void set_current_temperature_lambda(F &&f) {
this->current_temperature_f_.set(std::forward<F>(f));
}
template<typename F> void set_mode_lambda(F &&f) { this->mode_f_.set(std::forward<F>(f)); }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_restore_mode(TemplateWaterHeaterRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
void set_supported_modes(const std::initializer_list<water_heater::WaterHeaterMode> &modes) {
this->supported_modes_ = modes;
}
Trigger<> *get_set_trigger() const { return this->set_trigger_; }
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
water_heater::WaterHeaterCallInternal make_call() override;
protected:
void control(const water_heater::WaterHeaterCall &call) override;
water_heater::WaterHeaterTraits traits() override;
// Ordered to minimize padding on 32-bit: 4-byte members first, then smaller
Trigger<> *set_trigger_;
TemplateLambda<float> current_temperature_f_;
TemplateLambda<water_heater::WaterHeaterMode> mode_f_;
TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE};
water_heater::WaterHeaterModeMask supported_modes_;
bool optimistic_{true};
};
} // namespace esphome::template_

View File

@@ -794,13 +794,15 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) {
#ifdef USE_WIFI_WPA2_EAP
if (ap.get_eap().has_value()) {
EAPAuth eap_config = ap.get_eap().value();
// clang-format off
ESP_LOGV(
TAG,
" WPA2 Enterprise authentication configured:\n"
" Identity: " LOG_SECRET("'%s'") "\n"
" Username: " LOG_SECRET("'%s'") "\n"
" Password: " LOG_SECRET("'%s'"),
" Username: " LOG_SECRET("'%s'") "\n"
" Password: " LOG_SECRET("'%s'"),
eap_config.identity.c_str(), eap_config.username.c_str(), eap_config.password.c_str());
// clang-format on
#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));
#endif

View File

@@ -12,14 +12,20 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==5.1.0
click==8.1.7
esphome-dashboard==20251013.0
aioesphomeapi==43.10.0
aioesphomeapi==43.10.1
zeroconf==0.148.0
puremagic==1.30
ruamel.yaml==0.19.1 # dashboard_import
ruamel.yaml.clib==0.2.15 # dashboard_import
esphome-glyphsets==0.2.0
pillow==11.3.0
cairosvg==2.8.2
# pycairo fork for Windows
cairosvg @ git+https://github.com/clydebarrow/cairosvg.git@release ; sys_platform == 'win32'
# Original for everything else
cairosvg==2.8.2 ; sys_platform != 'win32'
freetype-py==2.5.1
jinja2==3.1.6
bleak==2.1.1

View File

@@ -9,6 +9,18 @@ esphome:
id: template_sens
state: !lambda "return 42.0;"
- water_heater.template.publish:
id: template_water_heater
target_temperature: 50.0
mode: ECO
# Templated
- water_heater.template.publish:
id: template_water_heater
current_temperature: !lambda "return 45.0;"
target_temperature: !lambda "return 55.0;"
mode: !lambda "return water_heater::WATER_HEATER_MODE_GAS;"
# Test C++ API: set_template() with stateless lambda (no captures)
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
- lambda: |-
@@ -299,6 +311,24 @@ alarm_control_panel:
codes:
- "1234"
water_heater:
- platform: template
id: template_water_heater
name: "Template Water Heater"
optimistic: true
current_temperature: !lambda "return 42.0f;"
mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;"
supported_modes:
- "OFF"
- ECO
- GAS
- ELECTRIC
- HEAT_PUMP
- HIGH_DEMAND
- PERFORMANCE
set_action:
- logger.log: "set_action"
datetime:
- platform: template
name: Date