Compare commits

..

19 Commits

Author SHA1 Message Date
Jesse Hills
78a6509fb1 Merge pull request #6097 from esphome/bump-2023.12.6
2023.12.6
2024-01-15 13:06:58 +13:00
Jesse Hills
534c14e313 Bump version to 2023.12.6 2024-01-15 08:25:48 +09:00
J. Nick Koston
3fec8f9b53 Fallback to pure-python loader for better error when YAML loading fails (#6081) 2024-01-15 08:25:41 +09:00
tomaszduda23
b8b6462844 add STATE_CLASS_TOTAL_INCREASING to bl0940 and bl0942 (#6090) 2024-01-15 08:23:56 +09:00
Keith Burzinski
59e7c52341 Improv Serial -- don't wait for incoming bytes (#6089) 2024-01-15 08:23:55 +09:00
Keith Burzinski
ff7de4c971 ESP32-C3 USB_CDC fixes (#6069) 2024-01-15 08:23:55 +09:00
Robert Paskowitz
978a676c7c Support full (>460 char) dumps of Pronto IR commands (#6040)
Co-authored-by: Rob Paskowitz <rob@paskowitz.ca>
2024-01-15 08:23:55 +09:00
functionpointer
33051906bd pylontech: Fix parsing error with US2000 (#6061) 2024-01-15 08:23:55 +09:00
tomaszduda23
da56d333dc fix compilation error for libretiny (#6064) 2024-01-15 08:23:55 +09:00
J. Nick Koston
48a4e6bae9 Fix device not requesting Home Assistant time at the update interval (#6022) 2024-01-15 08:23:55 +09:00
Jesse Hills
41dc73d228 Merge pull request #6017 from esphome/bump-2023.12.5
2023.12.5
2023-12-26 02:44:59 +13:00
Jesse Hills
6ceefe08ab Bump version to 2023.12.5 2023-12-25 22:24:13 +09:00
J. Nick Koston
21e5806a73 Fix docker builds (#6012) 2023-12-25 22:24:13 +09:00
Jesse Hills
4fd79fee2c Merge pull request #6016 from esphome/bump-2023.12.4
2023.12.4
2023-12-26 01:45:41 +13:00
Jesse Hills
4c8c4a2579 Bump version to 2023.12.4 2023-12-25 21:14:55 +09:00
NP v/d Spek
b68420b2cc Display: fix class inherence in Python script (#6009) 2023-12-25 21:14:55 +09:00
J. Nick Koston
7bce999bba dashboard: Fix file writes on Windows (#6013) 2023-12-25 21:14:55 +09:00
NP v/d Spek
dc0cc0b431 tt21100: restore init read (#6008) 2023-12-25 21:14:55 +09:00
J. Nick Koston
0990d0812e dashboard: Only ping when polling is active (#6001)
fixes https://github.com/esphome/issues/issues/5257
2023-12-25 21:14:55 +09:00
22 changed files with 184 additions and 105 deletions

View File

@@ -34,7 +34,7 @@ RUN \
python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1 \
git=1:2.39.2-1.1 \
curl=7.88.1-10+deb12u4 \
curl=7.88.1-10+deb12u5 \
openssh-client=1:9.2p1-2+deb12u1 \
python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \

View File

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

View File

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

View File

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

View File

@@ -18,8 +18,8 @@ from esphome.core import coroutine_with_priority
IS_PLATFORM_COMPONENT = True
display_ns = cg.esphome_ns.namespace("display")
Display = display_ns.class_("Display")
DisplayBuffer = display_ns.class_("DisplayBuffer")
Display = display_ns.class_("Display", cg.PollingComponent)
DisplayBuffer = display_ns.class_("DisplayBuffer", Display)
DisplayPage = display_ns.class_("DisplayPage")
DisplayPagePtr = DisplayPage.operator("ptr")
DisplayRef = Display.operator("ref")

View File

@@ -52,7 +52,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
size_t available;
uart_get_buffered_data_len(this->uart_num_, &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;
}
}
@@ -71,7 +71,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
#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)
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;
}
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_ALL"] = 0
# 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
# add custom options
lt_options.update(framework[CONF_OPTIONS])

View File

@@ -84,7 +84,7 @@ UART_SELECTION_ESP32 = {
VARIANT_ESP32: [UART0, UART1, UART2],
VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
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_ESP32C6: [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,
esp32=UART0,
esp32_s2=USB_CDC,
esp32_s3_idf=USB_SERIAL_JTAG,
esp32_c3_idf=USB_SERIAL_JTAG,
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,
bk72xx=DEFAULT,
rtl87xx=DEFAULT,
@@ -265,6 +266,8 @@ async def to_code(config):
if CORE.using_arduino:
if config[CONF_HARDWARE_UART] == USB_CDC:
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 config[CONF_HARDWARE_UART] == USB_CDC:

View File

@@ -272,17 +272,13 @@ void Logger::pre_setup() {
#endif
#if defined(USE_ESP32) && \
(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:
#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)
case UART_SELECTION_USB_SERIAL_JTAG:
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
#ifdef 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 defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
#if ARDUINO_USB_CDC_ON_BOOT
this->hw_serial_ = &Serial;
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;
Serial.begin(this->baud_rate_);
#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;
#endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3)
#ifdef USE_RP2040

View File

@@ -45,9 +45,10 @@ enum UARTSelection {
UART_SELECTION_UART2,
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
// !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,
#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) || \
defined(USE_ESP32_VARIANT_ESP32H2)
UART_SELECTION_USB_SERIAL_JTAG,

View File

@@ -19,7 +19,7 @@ PylontechComponent = pylontech_ns.class_(
)
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(
{

View File

@@ -1,5 +1,6 @@
#include "pylontech.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace pylontech {
@@ -34,26 +35,30 @@ void PylontechComponent::setup() {
void PylontechComponent::update() { this->write_str("pwr\n"); }
void PylontechComponent::loop() {
uint8_t data;
// 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
while (this->available() > 0) {
if (this->read_byte(&data)) {
buffer_[buffer_index_write_] += (char) data;
if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) ||
buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
// complete line received
buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS;
if (this->available() > 0) {
// 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
uint8_t data;
int recv = 0;
while (this->available() > 0) {
if (this->read_byte(&data)) {
buffer_[buffer_index_write_] += (char) data;
recv++;
if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) ||
buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
// complete line received
buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS;
}
}
}
}
// only process one line per call of loop() to not block esphome for too long
if (buffer_index_read_ != buffer_index_write_) {
this->process_line_(buffer_[buffer_index_read_]);
buffer_[buffer_index_read_].clear();
buffer_index_read_ = (buffer_index_read_ + 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
if (buffer_index_read_ != buffer_index_write_) {
this->process_line_(buffer_[buffer_index_read_]);
buffer_[buffer_index_read_].clear();
buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS;
}
}
}
@@ -66,10 +71,11 @@ void PylontechComponent::process_line_(std::string &buffer) {
// clang-format on
PylontechListener::LineContents l{};
const int parsed = sscanf( // 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 %d %*s", // 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, &l.mostempr); // NOLINT
char mostempr_s[6];
const int parsed = sscanf( // 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.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) {
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());
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_) {
listener->on_line_read(&l);

View File

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

View File

@@ -64,6 +64,9 @@ void TT21100Touchscreen::setup() {
// Update display dimensions if they were updated during display setup
this->x_raw_max_ = this->get_width_();
this->y_raw_max_ = this->get_height_();
// Trigger initial read to activate the interrupt
this->store_.touched = true;
}
void TT21100Touchscreen::update_touches() {

View File

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

View File

@@ -31,6 +31,7 @@ class PingStatus:
while not dashboard.stop_event.is_set():
# Only ping if the dashboard is open
await dashboard.ping_request.wait()
dashboard.ping_request.clear()
current_entries = dashboard.entries.async_all()
to_ping: list[DashboardEntry] = [
entry for entry in current_entries if entry.address is not None

View File

@@ -30,6 +30,7 @@ def write_file(
"""
tmp_filename = ""
missing_fchmod = False
try:
# Modern versions of Python tempfile create this file with mode 0o600
with tempfile.NamedTemporaryFile(
@@ -38,8 +39,15 @@ def write_file(
fdesc.write(utf8_data)
tmp_filename = fdesc.name
if not private:
os.fchmod(fdesc.fileno(), 0o644)
try:
os.fchmod(fdesc.fileno(), 0o644)
except AttributeError:
# os.fchmod is not available on Windows
missing_fchmod = True
os.replace(tmp_filename, filename)
if missing_fchmod:
os.chmod(filename, 0o644)
finally:
if os.path.exists(tmp_filename):
try:

View File

@@ -1,36 +1,37 @@
from __future__ import annotations
import fnmatch
import functools
import inspect
import logging
import math
import os
import uuid
from typing import Any
import yaml
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.config_helpers import read_config_file, Extend, Remove
from esphome.config_helpers import Extend, Remove, read_config_file
from esphome.core import (
CORE,
DocumentRange,
EsphomeError,
IPAddress,
Lambda,
MACAddress,
TimePeriod,
DocumentRange,
CORE,
)
from esphome.helpers import add_class_to_obj
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__)
# Mostly copied from Home Assistant because that code works fine and
@@ -97,7 +98,7 @@ def _add_data_ref(fn):
return wrapped
class ESPHomeLoader(FastestAvailableSafeLoader):
class ESPHomeLoaderMixin:
"""Loader class that keeps track of line numbers."""
@_add_data_ref
@@ -282,8 +283,8 @@ class ESPHomeLoader(FastestAvailableSafeLoader):
return file, vars
def substitute_vars(config, vars):
from esphome.const import CONF_SUBSTITUTIONS
from esphome.components import substitutions
from esphome.const import CONF_SUBSTITUTIONS
org_subs = None
result = config
@@ -367,50 +368,64 @@ class ESPHomeLoader(FastestAvailableSafeLoader):
return Remove(str(node.value))
ESPHomeLoader.add_constructor("tag:yaml.org,2002:int", ESPHomeLoader.construct_yaml_int)
ESPHomeLoader.add_constructor(
"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)
class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader):
"""Loader class that keeps track of line numbers."""
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:
_SECRET_VALUES.clear()
_SECRET_CACHE.clear()
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)
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
try:
return loader.get_single_data() or OrderedDict()

View File

@@ -13,7 +13,7 @@ def test_write_utf8_file(tmp_path: Path) -> None:
assert tmp_path.joinpath("foo.txt").read_text() == "foo"
with pytest.raises(OSError):
write_utf8_file(Path("/not-writable"), "bar")
write_utf8_file(Path("/dev/not-writable"), "bar")
def test_write_file(tmp_path: Path) -> None:

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.components import substitutions
from esphome.core import EsphomeError
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"]["board"] == "nodemcu"
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)