Merge pull request #6097 from esphome/bump-2023.12.6

2023.12.6
This commit is contained in:
Jesse Hills 2024-01-15 13:06:58 +13:00 committed by GitHub
commit 78a6509fb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 167 additions and 100 deletions

View file

@ -319,7 +319,7 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void APIServer::request_time() { void APIServer::request_time() {
for (auto &client : this->clients_) { for (auto &client : this->clients_) {
if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED) if (!client->remove_ && client->is_authenticated())
client->send_time_request(); client->send_time_request();
} }
} }

View file

@ -18,6 +18,7 @@ from esphome.const import (
UNIT_KILOWATT_HOURS, UNIT_KILOWATT_HOURS,
UNIT_VOLT, UNIT_VOLT,
UNIT_WATT, UNIT_WATT,
STATE_CLASS_TOTAL_INCREASING,
) )
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
@ -54,6 +55,7 @@ CONFIG_SCHEMA = (
unit_of_measurement=UNIT_KILOWATT_HOURS, unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY, device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
), ),
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,

View file

@ -19,6 +19,7 @@ from esphome.const import (
UNIT_VOLT, UNIT_VOLT,
UNIT_WATT, UNIT_WATT,
UNIT_HERTZ, UNIT_HERTZ,
STATE_CLASS_TOTAL_INCREASING,
) )
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
@ -52,6 +53,7 @@ CONFIG_SCHEMA = (
unit_of_measurement=UNIT_KILOWATT_HOURS, unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY, device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
), ),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ, unit_of_measurement=UNIT_HERTZ,

View file

@ -52,7 +52,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
size_t available; size_t available;
uart_get_buffered_data_len(this->uart_num_, &available); uart_get_buffered_data_len(this->uart_num_, &available);
if (available) { if (available) {
uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_PERIOD_MS); uart_read_bytes(this->uart_num_, &data, 1, 0);
byte = data; byte = data;
} }
} }
@ -71,7 +71,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
case logger::UART_SELECTION_USB_SERIAL_JTAG: { case logger::UART_SELECTION_USB_SERIAL_JTAG: {
if (usb_serial_jtag_read_bytes((char *) &data, 1, 20 / portTICK_PERIOD_MS)) { if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
byte = data; byte = data;
} }
break; break;

View file

@ -309,7 +309,7 @@ async def component_to_code(config):
lt_options["LT_UART_SILENT_ENABLED"] = 0 lt_options["LT_UART_SILENT_ENABLED"] = 0
lt_options["LT_UART_SILENT_ALL"] = 0 lt_options["LT_UART_SILENT_ALL"] = 0
# set default UART port # set default UART port
if uart_port := framework.get(CONF_UART_PORT, None) is not None: if (uart_port := framework.get(CONF_UART_PORT, None)) is not None:
lt_options["LT_UART_DEFAULT_PORT"] = uart_port lt_options["LT_UART_DEFAULT_PORT"] = uart_port
# add custom options # add custom options
lt_options.update(framework[CONF_OPTIONS]) lt_options.update(framework[CONF_OPTIONS])

View file

@ -84,7 +84,7 @@ UART_SELECTION_ESP32 = {
VARIANT_ESP32: [UART0, UART1, UART2], VARIANT_ESP32: [UART0, UART1, UART2],
VARIANT_ESP32S2: [UART0, UART1, USB_CDC], VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C3: [UART0, UART1, USB_SERIAL_JTAG], VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32C2: [UART0, UART1], VARIANT_ESP32C2: [UART0, UART1],
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
@ -172,9 +172,10 @@ CONFIG_SCHEMA = cv.All(
esp8266=UART0, esp8266=UART0,
esp32=UART0, esp32=UART0,
esp32_s2=USB_CDC, esp32_s2=USB_CDC,
esp32_s3_idf=USB_SERIAL_JTAG,
esp32_c3_idf=USB_SERIAL_JTAG,
esp32_s3_arduino=USB_CDC, esp32_s3_arduino=USB_CDC,
esp32_s3_idf=USB_SERIAL_JTAG,
esp32_c3_arduino=USB_CDC,
esp32_c3_idf=USB_SERIAL_JTAG,
rp2040=USB_CDC, rp2040=USB_CDC,
bk72xx=DEFAULT, bk72xx=DEFAULT,
rtl87xx=DEFAULT, rtl87xx=DEFAULT,
@ -265,6 +266,8 @@ async def to_code(config):
if CORE.using_arduino: if CORE.using_arduino:
if config[CONF_HARDWARE_UART] == USB_CDC: if config[CONF_HARDWARE_UART] == USB_CDC:
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1")
if CORE.is_esp32 and get_esp32_variant() == VARIANT_ESP32C3:
cg.add_build_flag("-DARDUINO_USB_MODE=1")
if CORE.using_esp_idf: if CORE.using_esp_idf:
if config[CONF_HARDWARE_UART] == USB_CDC: if config[CONF_HARDWARE_UART] == USB_CDC:

View file

@ -272,17 +272,13 @@ void Logger::pre_setup() {
#endif #endif
#if defined(USE_ESP32) && \ #if defined(USE_ESP32) && \
(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)) (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3))
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
case UART_SELECTION_USB_CDC: case UART_SELECTION_USB_CDC:
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
case UART_SELECTION_USB_SERIAL_JTAG: case UART_SELECTION_USB_SERIAL_JTAG:
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
#ifdef USE_ESP32_VARIANT_ESP32C3 #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
this->hw_serial_ = &Serial;
Serial.begin(this->baud_rate_);
#endif // USE_ESP32_VARIANT_ESP32C3
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if ARDUINO_USB_CDC_ON_BOOT #if ARDUINO_USB_CDC_ON_BOOT
this->hw_serial_ = &Serial; this->hw_serial_ = &Serial;
Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection
@ -291,7 +287,7 @@ void Logger::pre_setup() {
this->hw_serial_ = &Serial; this->hw_serial_ = &Serial;
Serial.begin(this->baud_rate_); Serial.begin(this->baud_rate_);
#endif // ARDUINO_USB_CDC_ON_BOOT #endif // ARDUINO_USB_CDC_ON_BOOT
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
break; break;
#endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3) #endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3)
#ifdef USE_RP2040 #ifdef USE_RP2040

View file

@ -45,9 +45,10 @@ enum UARTSelection {
UART_SELECTION_UART2, UART_SELECTION_UART2,
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 && #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2 // !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
(defined(USE_ESP32_VARIANT_ESP32C3) && defined(USE_ARDUINO))
UART_SELECTION_USB_CDC, UART_SELECTION_USB_CDC,
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32H2) defined(USE_ESP32_VARIANT_ESP32H2)
UART_SELECTION_USB_SERIAL_JTAG, UART_SELECTION_USB_SERIAL_JTAG,

View file

@ -19,7 +19,7 @@ PylontechComponent = pylontech_ns.class_(
) )
PylontechBattery = pylontech_ns.class_("PylontechBattery") PylontechBattery = pylontech_ns.class_("PylontechBattery")
CV_NUM_BATTERIES = cv.int_range(1, 6) CV_NUM_BATTERIES = cv.int_range(1, 16)
PYLONTECH_COMPONENT_SCHEMA = cv.Schema( PYLONTECH_COMPONENT_SCHEMA = cv.Schema(
{ {

View file

@ -1,5 +1,6 @@
#include "pylontech.h" #include "pylontech.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
namespace pylontech { namespace pylontech {
@ -34,26 +35,30 @@ void PylontechComponent::setup() {
void PylontechComponent::update() { this->write_str("pwr\n"); } void PylontechComponent::update() { this->write_str("pwr\n"); }
void PylontechComponent::loop() { void PylontechComponent::loop() {
uint8_t data; if (this->available() > 0) {
// pylontech sends a lot of data very suddenly
// pylontech sends a lot of data very suddenly // we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow
// we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow uint8_t data;
while (this->available() > 0) { int recv = 0;
if (this->read_byte(&data)) { while (this->available() > 0) {
buffer_[buffer_index_write_] += (char) data; if (this->read_byte(&data)) {
if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) || buffer_[buffer_index_write_] += (char) data;
buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) { recv++;
// complete line received if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) ||
buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS; buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
// complete line received
buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS;
}
} }
} }
} ESP_LOGV(TAG, "received %d bytes", recv);
} else {
// only process one line per call of loop() to not block esphome for too long // only process one line per call of loop() to not block esphome for too long
if (buffer_index_read_ != buffer_index_write_) { if (buffer_index_read_ != buffer_index_write_) {
this->process_line_(buffer_[buffer_index_read_]); this->process_line_(buffer_[buffer_index_read_]);
buffer_[buffer_index_read_].clear(); buffer_[buffer_index_read_].clear();
buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS; buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS;
}
} }
} }
@ -66,10 +71,11 @@ void PylontechComponent::process_line_(std::string &buffer) {
// clang-format on // clang-format on
PylontechListener::LineContents l{}; PylontechListener::LineContents l{};
const int parsed = sscanf( // NOLINT char mostempr_s[6];
buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %d %*s", // NOLINT const int parsed = sscanf( // NOLINT
&l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %5s %*s", // NOLINT
l.curr_st, l.temp_st, &l.coulomb, &l.mostempr); // NOLINT &l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT
l.curr_st, l.temp_st, &l.coulomb, mostempr_s); // NOLINT
if (l.bat_num <= 0) { if (l.bat_num <= 0) {
ESP_LOGD(TAG, "invalid bat_num in line %s", buffer.substr(0, buffer.size() - 2).c_str()); ESP_LOGD(TAG, "invalid bat_num in line %s", buffer.substr(0, buffer.size() - 2).c_str());
@ -79,6 +85,13 @@ void PylontechComponent::process_line_(std::string &buffer) {
ESP_LOGW(TAG, "invalid line: found only %d items in %s", parsed, buffer.substr(0, buffer.size() - 2).c_str()); ESP_LOGW(TAG, "invalid line: found only %d items in %s", parsed, buffer.substr(0, buffer.size() - 2).c_str());
return; return;
} }
auto mostempr_parsed = parse_number<int>(mostempr_s);
if (mostempr_parsed.has_value()) {
l.mostempr = mostempr_parsed.value();
} else {
l.mostempr = -300;
ESP_LOGW(TAG, "bat_num %d: received no mostempr", l.bat_num);
}
for (PylontechListener *listener : this->listeners_) { for (PylontechListener *listener : this->listeners_) {
listener->on_line_read(&l); listener->on_line_read(&l);

View file

@ -227,16 +227,17 @@ optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) {
} }
void ProntoProtocol::dump(const ProntoData &data) { void ProntoProtocol::dump(const ProntoData &data) {
std::string first, rest; std::string rest;
if (data.data.size() < 230) {
first = data.data; rest = data.data;
} else { ESP_LOGI(TAG, "Received Pronto: data=");
first = data.data.substr(0, 229); while (true) {
rest = data.data.substr(230); ESP_LOGI(TAG, "%s", rest.substr(0, 230).c_str());
} if (rest.size() > 230) {
ESP_LOGI(TAG, "Received Pronto: data=%s", first.c_str()); rest = rest.substr(230);
if (!rest.empty()) { } else {
ESP_LOGI(TAG, "%s", rest.c_str()); break;
}
} }
} }

View file

@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2023.12.5" __version__ = "2023.12.6"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (

View file

@ -1,36 +1,37 @@
from __future__ import annotations
import fnmatch import fnmatch
import functools import functools
import inspect import inspect
import logging import logging
import math import math
import os import os
import uuid import uuid
from typing import Any
import yaml import yaml
import yaml.constructor import yaml.constructor
from yaml import SafeLoader as PurePythonLoader
try:
from yaml import CSafeLoader as FastestAvailableSafeLoader
except ImportError:
FastestAvailableSafeLoader = PurePythonLoader
from esphome import core from esphome import core
from esphome.config_helpers import read_config_file, Extend, Remove from esphome.config_helpers import Extend, Remove, read_config_file
from esphome.core import ( from esphome.core import (
CORE,
DocumentRange,
EsphomeError, EsphomeError,
IPAddress, IPAddress,
Lambda, Lambda,
MACAddress, MACAddress,
TimePeriod, TimePeriod,
DocumentRange,
CORE,
) )
from esphome.helpers import add_class_to_obj from esphome.helpers import add_class_to_obj
from esphome.util import OrderedDict, filter_yaml_files from esphome.util import OrderedDict, filter_yaml_files
try:
from yaml import CSafeLoader as FastestAvailableSafeLoader
except ImportError:
from yaml import ( # type: ignore[assignment]
SafeLoader as FastestAvailableSafeLoader,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Mostly copied from Home Assistant because that code works fine and # Mostly copied from Home Assistant because that code works fine and
@ -97,7 +98,7 @@ def _add_data_ref(fn):
return wrapped return wrapped
class ESPHomeLoader(FastestAvailableSafeLoader): class ESPHomeLoaderMixin:
"""Loader class that keeps track of line numbers.""" """Loader class that keeps track of line numbers."""
@_add_data_ref @_add_data_ref
@ -282,8 +283,8 @@ class ESPHomeLoader(FastestAvailableSafeLoader):
return file, vars return file, vars
def substitute_vars(config, vars): def substitute_vars(config, vars):
from esphome.const import CONF_SUBSTITUTIONS
from esphome.components import substitutions from esphome.components import substitutions
from esphome.const import CONF_SUBSTITUTIONS
org_subs = None org_subs = None
result = config result = config
@ -367,50 +368,64 @@ class ESPHomeLoader(FastestAvailableSafeLoader):
return Remove(str(node.value)) return Remove(str(node.value))
ESPHomeLoader.add_constructor("tag:yaml.org,2002:int", ESPHomeLoader.construct_yaml_int) class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader):
ESPHomeLoader.add_constructor( """Loader class that keeps track of line numbers."""
"tag:yaml.org,2002:float", ESPHomeLoader.construct_yaml_float
)
ESPHomeLoader.add_constructor(
"tag:yaml.org,2002:binary", ESPHomeLoader.construct_yaml_binary
)
ESPHomeLoader.add_constructor(
"tag:yaml.org,2002:omap", ESPHomeLoader.construct_yaml_omap
)
ESPHomeLoader.add_constructor("tag:yaml.org,2002:str", ESPHomeLoader.construct_yaml_str)
ESPHomeLoader.add_constructor("tag:yaml.org,2002:seq", ESPHomeLoader.construct_yaml_seq)
ESPHomeLoader.add_constructor("tag:yaml.org,2002:map", ESPHomeLoader.construct_yaml_map)
ESPHomeLoader.add_constructor("!env_var", ESPHomeLoader.construct_env_var)
ESPHomeLoader.add_constructor("!secret", ESPHomeLoader.construct_secret)
ESPHomeLoader.add_constructor("!include", ESPHomeLoader.construct_include)
ESPHomeLoader.add_constructor(
"!include_dir_list", ESPHomeLoader.construct_include_dir_list
)
ESPHomeLoader.add_constructor(
"!include_dir_merge_list", ESPHomeLoader.construct_include_dir_merge_list
)
ESPHomeLoader.add_constructor(
"!include_dir_named", ESPHomeLoader.construct_include_dir_named
)
ESPHomeLoader.add_constructor(
"!include_dir_merge_named", ESPHomeLoader.construct_include_dir_merge_named
)
ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda)
ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force)
ESPHomeLoader.add_constructor("!extend", ESPHomeLoader.construct_extend)
ESPHomeLoader.add_constructor("!remove", ESPHomeLoader.construct_remove)
def load_yaml(fname, clear_secrets=True): class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader):
"""Loader class that keeps track of line numbers."""
for _loader in (ESPHomeLoader, ESPHomePurePythonLoader):
_loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int)
_loader.add_constructor("tag:yaml.org,2002:float", _loader.construct_yaml_float)
_loader.add_constructor("tag:yaml.org,2002:binary", _loader.construct_yaml_binary)
_loader.add_constructor("tag:yaml.org,2002:omap", _loader.construct_yaml_omap)
_loader.add_constructor("tag:yaml.org,2002:str", _loader.construct_yaml_str)
_loader.add_constructor("tag:yaml.org,2002:seq", _loader.construct_yaml_seq)
_loader.add_constructor("tag:yaml.org,2002:map", _loader.construct_yaml_map)
_loader.add_constructor("!env_var", _loader.construct_env_var)
_loader.add_constructor("!secret", _loader.construct_secret)
_loader.add_constructor("!include", _loader.construct_include)
_loader.add_constructor("!include_dir_list", _loader.construct_include_dir_list)
_loader.add_constructor(
"!include_dir_merge_list", _loader.construct_include_dir_merge_list
)
_loader.add_constructor("!include_dir_named", _loader.construct_include_dir_named)
_loader.add_constructor(
"!include_dir_merge_named", _loader.construct_include_dir_merge_named
)
_loader.add_constructor("!lambda", _loader.construct_lambda)
_loader.add_constructor("!force", _loader.construct_force)
_loader.add_constructor("!extend", _loader.construct_extend)
_loader.add_constructor("!remove", _loader.construct_remove)
def load_yaml(fname: str, clear_secrets: bool = True) -> Any:
if clear_secrets: if clear_secrets:
_SECRET_VALUES.clear() _SECRET_VALUES.clear()
_SECRET_CACHE.clear() _SECRET_CACHE.clear()
return _load_yaml_internal(fname) return _load_yaml_internal(fname)
def _load_yaml_internal(fname): def _load_yaml_internal(fname: str) -> Any:
"""Load a YAML file."""
content = read_config_file(fname) content = read_config_file(fname)
loader = ESPHomeLoader(content) try:
return _load_yaml_internal_with_type(ESPHomeLoader, fname, content)
except EsphomeError:
# Loading failed, so we now load with the Python loader which has more
# readable exceptions
return _load_yaml_internal_with_type(ESPHomePurePythonLoader, fname, content)
def _load_yaml_internal_with_type(
loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader],
fname: str,
content: str,
) -> Any:
"""Load a YAML file."""
loader = loader_type(content)
loader.name = fname loader.name = fname
try: try:
return loader.get_single_data() or OrderedDict() return loader.get_single_data() or OrderedDict()

View file

@ -0,0 +1,18 @@
---
substitutions:
name: original
wifi: !include
file: includes/broken_included.yaml.txt
vars:
name: my_custom_ssid
esphome:
# should be substituted as 'original',
# not overwritten by vars in the !include above
name: ${name}
name_add_mac_suffix: true
platform: esp8266
board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}}
libraries: !include {file: includes/list.yaml, vars: {var1: Wire}}

View file

@ -0,0 +1,5 @@
---
# yamllint disable-line
ssid: ${name}
# yamllint disable-line
fdf: error

View file

@ -1,5 +1,6 @@
from esphome import yaml_util from esphome import yaml_util
from esphome.components import substitutions from esphome.components import substitutions
from esphome.core import EsphomeError
def test_include_with_vars(fixture_path): def test_include_with_vars(fixture_path):
@ -11,3 +12,13 @@ def test_include_with_vars(fixture_path):
assert actual["esphome"]["libraries"][0] == "Wire" assert actual["esphome"]["libraries"][0] == "Wire"
assert actual["esphome"]["board"] == "nodemcu" assert actual["esphome"]["board"] == "nodemcu"
assert actual["wifi"]["ssid"] == "my_custom_ssid" assert actual["wifi"]["ssid"] == "my_custom_ssid"
def test_loading_a_broken_yaml_file(fixture_path):
"""Ensure we fallback to pure python to give good errors."""
yaml_file = fixture_path / "yaml_util" / "broken_includetest.yaml"
try:
yaml_util.load_yaml(yaml_file)
except EsphomeError as err:
assert "broken_included.yaml" in str(err)