mirror of
https://github.com/esphome/esphome.git
synced 2026-05-22 10:25:46 +08:00
Merge pull request #16488 from esphome/bump-2026.5.0b2
CI for docker images / Build docker containers (docker, ubuntu-24.04) (push) Has been cancelled
CI for docker images / Build docker containers (docker, ubuntu-24.04-arm) (push) Has been cancelled
CI for docker images / Build docker containers (ha-addon, ubuntu-24.04) (push) Has been cancelled
CI for docker images / Build docker containers (ha-addon, ubuntu-24.04-arm) (push) Has been cancelled
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Check import esphome.__main__ time (push) Has been cancelled
CI / Test downstream esphome/device-builder (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (${{ matrix.bucket.name }}) (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Has been cancelled
CI / Test components batch (${{ matrix.components }}) (push) Has been cancelled
CI / Test components with native ESP-IDF (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
CI for docker images / Build docker containers (docker, ubuntu-24.04) (push) Has been cancelled
CI for docker images / Build docker containers (docker, ubuntu-24.04-arm) (push) Has been cancelled
CI for docker images / Build docker containers (ha-addon, ubuntu-24.04) (push) Has been cancelled
CI for docker images / Build docker containers (ha-addon, ubuntu-24.04-arm) (push) Has been cancelled
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Check import esphome.__main__ time (push) Has been cancelled
CI / Test downstream esphome/device-builder (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (${{ matrix.bucket.name }}) (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Has been cancelled
CI / Test components batch (${{ matrix.components }}) (push) Has been cancelled
CI / Test components with native ESP-IDF (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
2026.5.0b2
This commit is contained in:
@@ -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.5.0b1
|
||||
PROJECT_NUMBER = 2026.5.0b2
|
||||
|
||||
# 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
|
||||
|
||||
+7
-3
@@ -13,12 +13,16 @@ RUN git config --system --add safe.directory "*" \
|
||||
&& git config --system advice.detachedHead false
|
||||
|
||||
# Install build tools for Python packages that require compilation
|
||||
# (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager)
|
||||
# (e.g., ruamel.yaml.clib used by ESP-IDF's idf-component-manager).
|
||||
# Also install libusb-1.0 at runtime so the ESP-IDF tools installer can
|
||||
# validate openocd-esp32 (it dynamically links libusb-1.0.so.0); without
|
||||
# it idf_tools.py rejects the openocd install with exit 127 and aborts
|
||||
# the whole framework setup.
|
||||
RUN if command -v apk > /dev/null; then \
|
||||
apk add --no-cache build-base; \
|
||||
apk add --no-cache build-base libusb; \
|
||||
else \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends build-essential \
|
||||
&& apt-get install -y --no-install-recommends build-essential libusb-1.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ from esphome.const import (
|
||||
CONF_TOPIC,
|
||||
CONF_USERNAME,
|
||||
CONF_WEB_SERVER,
|
||||
CONF_WIFI,
|
||||
ENV_NOGITIGNORE,
|
||||
KEY_CORE,
|
||||
KEY_TARGET_PLATFORM,
|
||||
@@ -733,6 +734,13 @@ def write_cpp_file() -> int:
|
||||
|
||||
|
||||
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
# Keep this gate here, NOT in config validation: device-builder needs
|
||||
# `esphome config` to keep succeeding with placeholders so onboarding can run.
|
||||
if CONF_WIFI in config:
|
||||
from esphome.components.wifi import check_placeholder_credentials
|
||||
|
||||
check_placeholder_credentials(config)
|
||||
|
||||
# NOTE: "Build path:" format is parsed by script/ci_memory_impact_extract.py
|
||||
# If you change this format, update the regex in that script as well
|
||||
_LOGGER.info("Compiling app... Build path: %s", CORE.build_path)
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32 import get_esp32_variant, idf_version
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||
from esphome.writer import update_storage_json
|
||||
@@ -61,6 +62,11 @@ def get_project_cmakelists(minimal: bool = False) -> str:
|
||||
variant = get_esp32_variant()
|
||||
idf_target = variant.lower().replace("-", "")
|
||||
|
||||
# esp_idf_size 2.x (bundled with IDF >=6.0) made NG the default and
|
||||
# removed the --ng flag; on 1.x (IDF 5.5) --ng is required to get
|
||||
# --format=raw because the legacy mode doesn't support it.
|
||||
size_ng_flag = "--ng" if idf_version() < cv.Version(6, 0, 0) else ""
|
||||
|
||||
# Project-wide compile options: -D defines and -W warning flags (skip
|
||||
# -Wl, linker flags — those go on the src component via
|
||||
# target_link_options below). Emitted via idf_build_set_property so the
|
||||
@@ -146,7 +152,7 @@ project({CORE.name})
|
||||
# Emit raw JSON size data for ESPHome to read post-build.
|
||||
add_custom_command(
|
||||
TARGET ${{CMAKE_PROJECT_NAME}}.elf POST_BUILD
|
||||
COMMAND ${{PYTHON}} -m esp_idf_size --ng --format=raw
|
||||
COMMAND ${{PYTHON}} -m esp_idf_size {size_ng_flag} --format=raw
|
||||
-o ${{CMAKE_BINARY_DIR}}/esp_idf_size.json
|
||||
${{CMAKE_PROJECT_NAME}}.map
|
||||
WORKING_DIRECTORY ${{CMAKE_BINARY_DIR}}
|
||||
|
||||
@@ -395,7 +395,7 @@ async def to_code(config):
|
||||
)
|
||||
if data.mp3_support:
|
||||
cg.add_define("USE_AUDIO_MP3_SUPPORT")
|
||||
add_idf_component(name="esphome/micro-mp3", ref="0.2.0")
|
||||
add_idf_component(name="esphome/micro-mp3", ref="0.2.1")
|
||||
_emit_memory_pair(
|
||||
data.mp3.buffer_memory,
|
||||
"CONFIG_MP3_DECODER_PREFER_PSRAM",
|
||||
|
||||
@@ -161,7 +161,7 @@ void BME680BSECComponent::dump_config() {
|
||||
" IAQ Mode: %s\n"
|
||||
" Supply Voltage: %sV\n"
|
||||
" Sample Rate: %s\n"
|
||||
" State Save Interval: %ims",
|
||||
" State Save Interval: %" PRIu32 "ms",
|
||||
this->temperature_offset_, this->iaq_mode_ == IAQ_MODE_STATIC ? "Static" : "Mobile",
|
||||
this->supply_voltage_ == SUPPLY_VOLTAGE_3V3 ? "3.3" : "1.8",
|
||||
BME680_BSEC_SAMPLE_RATE_LOG(this->sample_rate_), this->state_save_interval_ms_);
|
||||
@@ -461,7 +461,7 @@ int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t devid, uint8_t a_registe
|
||||
}
|
||||
|
||||
void BME680BSECComponent::delay_ms(uint32_t period) {
|
||||
ESP_LOGV(TAG, "Delaying for %ums", period);
|
||||
ESP_LOGV(TAG, "Delaying for %" PRIu32 "ms", period);
|
||||
delay(period);
|
||||
}
|
||||
|
||||
|
||||
@@ -1767,9 +1767,11 @@ async def to_code(config):
|
||||
else:
|
||||
cg.add_build_flag("-Wno-error=format")
|
||||
cg.add_build_flag("-Wno-error=maybe-uninitialized")
|
||||
cg.add_build_flag("-Wno-error=missing-field-initializers")
|
||||
cg.add_build_flag("-Wno-error=overloaded-virtual")
|
||||
cg.add_build_flag("-Wno-error=reorder")
|
||||
cg.add_build_flag("-Wno-error=volatile")
|
||||
# -Wno- (not -Wno-error=): suppress entirely, too noisy on C++ aggregates
|
||||
cg.add_build_flag("-Wno-missing-field-initializers")
|
||||
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_build_flag("-DUSE_ESP32")
|
||||
@@ -2464,8 +2466,14 @@ def _write_sdkconfig():
|
||||
)
|
||||
|
||||
want_opts = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||
# Include the resolved framework version as a Kconfig comment so a
|
||||
# version switch that happens to leave the option set unchanged still
|
||||
# bumps this file's content -- which is what has_outdated_files()
|
||||
# uses to decide whether to reconfigure.
|
||||
framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
contents = (
|
||||
"\n".join(
|
||||
f"# ESPHOME_IDF_VERSION={framework_version}\n"
|
||||
+ "\n".join(
|
||||
f"{name}={_format_sdkconfig_val(value)}"
|
||||
for name, value in sorted(want_opts.items())
|
||||
)
|
||||
@@ -2509,7 +2517,12 @@ def _write_idf_component_yml():
|
||||
|
||||
stubs_dir = CORE.relative_build_path("component_stubs")
|
||||
stubs_dir.mkdir(exist_ok=True)
|
||||
for component_name in components_to_stub:
|
||||
# Sort so the dict insertion order (and thus the generated
|
||||
# src/idf_component.yml) is deterministic across runs; otherwise
|
||||
# the manifest content shuffles every build, write_file_if_changed
|
||||
# always writes, and ninja keeps triggering CMake re-runs on
|
||||
# otherwise-cached rebuilds.
|
||||
for component_name in sorted(components_to_stub):
|
||||
# Create stub directory with minimal CMakeLists.txt
|
||||
stub_path = stubs_dir / _idf_component_stub_name(component_name)
|
||||
stub_path.mkdir(exist_ok=True)
|
||||
|
||||
@@ -249,7 +249,7 @@ async def to_code(config):
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.5.1")
|
||||
esp32.add_idf_component(name="espressif/wifi_remote_over_eppp", ref="0.3.2")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.5")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.12.6")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.12.7")
|
||||
else:
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.13.0")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0")
|
||||
|
||||
@@ -92,7 +92,7 @@ void Esp32HostedUpdate::setup() {
|
||||
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
|
||||
// 16 bytes: "255.255.255" (11 chars) + null + safety margin
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
|
||||
snprintf(buf, sizeof(buf), "%" PRIu32 ".%" PRIu32 ".%" PRIu32, ver_info.major1, ver_info.minor1, ver_info.patch1);
|
||||
this->update_info_.current_version = buf;
|
||||
} else {
|
||||
this->update_info_.current_version = "unknown";
|
||||
@@ -120,8 +120,8 @@ void Esp32HostedUpdate::setup() {
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid app description magic word: 0x%08x (expected 0x%08x)", app_desc->magic_word,
|
||||
ESP_APP_DESC_MAGIC_WORD);
|
||||
ESP_LOGW(TAG, "Invalid app description magic word: 0x%08" PRIx32 " (expected 0x%08" PRIx32 ")",
|
||||
app_desc->magic_word, static_cast<uint32_t>(ESP_APP_DESC_MAGIC_WORD));
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -108,8 +108,8 @@ void ESPHomeOTAComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Partition access allowed\n"
|
||||
" Running app:\n"
|
||||
" Partition address: 0x%X\n"
|
||||
" Used size: %zu bytes (0x%X)",
|
||||
" Partition address: 0x%" PRIX32 "\n"
|
||||
" Used size: %zu bytes (0x%zX)",
|
||||
this->running_app_offset_, this->running_app_size_, this->running_app_size_);
|
||||
|
||||
#ifdef USE_ESP32
|
||||
@@ -378,7 +378,7 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
}
|
||||
ota_size = (static_cast<size_t>(buf[0]) << 24) | (static_cast<size_t>(buf[1]) << 16) |
|
||||
(static_cast<size_t>(buf[2]) << 8) | buf[3];
|
||||
ESP_LOGV(TAG, "Size is %u bytes", ota_size);
|
||||
ESP_LOGV(TAG, "Size is %zu bytes", ota_size);
|
||||
|
||||
#ifndef USE_OTA_PARTITIONS
|
||||
if (ota_type != ota::OTA_TYPE_UPDATE_APP) {
|
||||
@@ -749,7 +749,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
|
||||
this->auth_buf_[0] = this->auth_type_;
|
||||
hasher.get_hex(buf);
|
||||
|
||||
ESP_LOGV(TAG, "Auth: Nonce is %.*s", hex_size, buf);
|
||||
ESP_LOGV(TAG, "Auth: Nonce is %.*s", (int) hex_size, buf);
|
||||
}
|
||||
|
||||
// Try to write auth_type + nonce
|
||||
@@ -809,13 +809,13 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
|
||||
hasher.add(nonce, hex_size * 2); // Add both nonce and cnonce (contiguous in buffer)
|
||||
hasher.calculate();
|
||||
|
||||
ESP_LOGV(TAG, "Auth: CNonce is %.*s", hex_size, cnonce);
|
||||
ESP_LOGV(TAG, "Auth: CNonce is %.*s", (int) hex_size, cnonce);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char computed_hash[SHA256_HEX_SIZE + 1]; // Buffer for hex-encoded hash (max expected length + null terminator)
|
||||
hasher.get_hex(computed_hash);
|
||||
ESP_LOGV(TAG, "Auth: Result is %.*s", hex_size, computed_hash);
|
||||
ESP_LOGV(TAG, "Auth: Result is %.*s", (int) hex_size, computed_hash);
|
||||
#endif
|
||||
ESP_LOGV(TAG, "Auth: Response is %.*s", hex_size, response);
|
||||
ESP_LOGV(TAG, "Auth: Response is %.*s", (int) hex_size, response);
|
||||
|
||||
// Compare response
|
||||
bool matches = hasher.equals_hex(response);
|
||||
|
||||
@@ -19,7 +19,7 @@ void FastLEDLightOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"FastLED light:\n"
|
||||
" Num LEDs: %u\n"
|
||||
" Max refresh rate: %u",
|
||||
" Max refresh rate: %" PRIu32,
|
||||
this->num_leds_, this->max_refresh_rate_.value_or(0));
|
||||
}
|
||||
void FastLEDLightOutput::write_state(light::LightState *state) {
|
||||
|
||||
@@ -206,6 +206,7 @@ uint8_t FingerprintGrowComponent::save_fingerprint_() {
|
||||
break;
|
||||
case ENROLL_MISMATCH:
|
||||
ESP_LOGE(TAG, "Scans do not match");
|
||||
[[fallthrough]];
|
||||
default:
|
||||
return this->data_[0];
|
||||
}
|
||||
|
||||
@@ -15,6 +15,16 @@ void FT5x06Touchscreen::setup() {
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
// reading the chip registers to get max x/y does not seem to work.
|
||||
if (this->display_ != nullptr) {
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
|
||||
// wait 200ms after reset.
|
||||
this->set_timeout(200, [this] { this->continue_setup_(); });
|
||||
}
|
||||
@@ -39,15 +49,6 @@ void FT5x06Touchscreen::continue_setup_() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// reading the chip registers to get max x/y does not seem to work.
|
||||
if (this->display_ != nullptr) {
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FT5x06Touchscreen::update_touches() {
|
||||
@@ -71,7 +72,7 @@ void FT5x06Touchscreen::update_touches() {
|
||||
uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
|
||||
uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
|
||||
|
||||
ESP_LOGD(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
|
||||
ESP_LOGV(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
|
||||
if (status == 0 || status == 2) {
|
||||
this->add_raw_touch_position_(id, x, y);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ static constexpr uint8_t MEAS_CONF_HUM = 0x04; // Bits 2:1 = 10: humidity only
|
||||
void HDC2080Component::setup() {
|
||||
const uint8_t data = 0x00; // automatic measurement mode disabled, heater off
|
||||
if (this->write_register(REG_RESET_DRDY_INT_CONF, &data, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,6 @@ async def to_code(config):
|
||||
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))
|
||||
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||
cg.add_build_flag("-Wno-error=overloaded-virtual")
|
||||
|
||||
cg.add_library("tonia/HeatpumpIR", "1.0.41")
|
||||
if CORE.is_libretiny or CORE.is_esp32:
|
||||
|
||||
@@ -319,7 +319,7 @@ void Inkplate::fill(Color color) {
|
||||
memset(this->partial_buffer_, fill, this->get_buffer_length_());
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
|
||||
ESP_LOGV(TAG, "Fill finished (%" PRIu32 "ms)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate::display() {
|
||||
|
||||
@@ -506,13 +506,13 @@ async def _late_logger_init(config: ConfigType) -> None:
|
||||
def validate_printf(value):
|
||||
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
|
||||
cfmt = r"""
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
(?:[-+0 #]{0,5}) # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:h|l|ll|w|I|I32|I64)? # size
|
||||
[cCdiouxXeEfgGaAnpsSZ] # type
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
(?:[-+0 #]{0,5}) # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:hh|h|ll|l|j|z|t|L|w|I|I32|I64)? # size
|
||||
[cCdiouxXeEfgGaAnpsSZ] # type
|
||||
)
|
||||
""" # noqa
|
||||
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.VERBOSE)
|
||||
|
||||
@@ -55,6 +55,7 @@ from .automation import layers_to_code, lvgl_update
|
||||
from .defines import (
|
||||
CONF_ALIGN_TO_LAMBDA_ID,
|
||||
LOGGER,
|
||||
add_lv_use,
|
||||
get_focused_widgets,
|
||||
get_lv_images_used,
|
||||
get_refreshed_widgets,
|
||||
@@ -71,6 +72,7 @@ from .keypads import KEYPADS_CONFIG, keypads_to_code
|
||||
from .lv_validation import lv_bool
|
||||
from .lvcode import LvContext, LvglComponent, lv_event_t_ptr, lvgl_static
|
||||
from .schemas import (
|
||||
BASE_PROPS,
|
||||
DISP_BG_SCHEMA,
|
||||
FULL_STYLE_SCHEMA,
|
||||
STYLE_REMAP,
|
||||
@@ -100,6 +102,7 @@ from .widgets import (
|
||||
get_screen_active,
|
||||
set_obj_properties,
|
||||
)
|
||||
from .widgets.img import CONF_IMAGE
|
||||
|
||||
# Import only what we actually use directly in this file
|
||||
from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code
|
||||
@@ -433,6 +436,8 @@ async def to_code(configs):
|
||||
|
||||
# This must be done after all widgets are created
|
||||
styles_used = df.get_styles_used()
|
||||
if any(BASE_PROPS.get(x) is lvalid.lv_image for x in styles_used):
|
||||
add_lv_use(CONF_IMAGE)
|
||||
for use in df.get_lv_uses():
|
||||
df.add_define(f"LV_USE_{use.upper()}")
|
||||
cg.add_define(f"USE_LVGL_{use.upper()}")
|
||||
|
||||
@@ -9,13 +9,13 @@ CONF_IF_NAN = "if_nan"
|
||||
# noqa
|
||||
f_regex = re.compile(
|
||||
r"""
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
[-+0 #]{0,5} # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:h|l|ll|w|I|I32|I64)? # size
|
||||
f # type
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
[-+0 #]{0,5} # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:hh|h|ll|l|j|z|t|L|w|I|I32|I64)? # size
|
||||
f # type
|
||||
)
|
||||
""",
|
||||
flags=re.VERBOSE,
|
||||
@@ -23,13 +23,13 @@ f_regex = re.compile(
|
||||
# noqa
|
||||
c_regex = re.compile(
|
||||
r"""
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
[-+0 #]{0,5} # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:h|l|ll|w|I|I32|I64)? # size
|
||||
[cCdiouxXeEfgGaAnpsSZ] # type
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
[-+0 #]{0,5} # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:hh|h|ll|l|j|z|t|L|w|I|I32|I64)? # size
|
||||
[cCdiouxXeEfgGaAnpsSZ] # type
|
||||
)
|
||||
""",
|
||||
flags=re.VERBOSE,
|
||||
|
||||
@@ -74,11 +74,11 @@ inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) {
|
||||
lv_style_set_text_font(style, font->get_lv_font());
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_LVGL_IMAGE) && defined(USE_IMAGE)
|
||||
#if LV_USE_IMAGE
|
||||
|
||||
#ifdef USE_IMAGE
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
// Shortcut / overload, so that the source of an image widget can easily be updated from within a lambda.
|
||||
inline void lv_image_set_src(lv_obj_t *obj, image::Image *image) { ::lv_image_set_src(obj, image->get_lv_image_dsc()); }
|
||||
#endif // LV_USE_IMAGE
|
||||
|
||||
inline void lv_obj_set_style_bitmap_mask_src(lv_obj_t *obj, image::Image *image, lv_style_selector_t selector) {
|
||||
::lv_obj_set_style_bitmap_mask_src(obj, image->get_lv_image_dsc(), selector);
|
||||
@@ -93,7 +93,8 @@ inline void lv_style_set_bg_image_src(lv_style_t *style, image::Image *image) {
|
||||
inline void lv_style_set_bitmap_mask_src(lv_style_t *style, image::Image *image) {
|
||||
::lv_style_set_bitmap_mask_src(style, image->get_lv_image_dsc());
|
||||
}
|
||||
#endif // USE_LVGL_IMAGE
|
||||
#endif
|
||||
|
||||
#ifdef USE_LVGL_ANIMIMG
|
||||
inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) {
|
||||
auto *dsc = static_cast<std::vector<lv_image_dsc_t *> *>(lv_obj_get_user_data(img));
|
||||
@@ -109,6 +110,7 @@ inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images
|
||||
lv_animimg_set_src(img, (const void **) dsc->data(), dsc->size());
|
||||
}
|
||||
#endif // USE_LVGL_ANIMIMG
|
||||
#endif // USE_IMAGE
|
||||
|
||||
#ifdef USE_LVGL_METER
|
||||
int16_t lv_get_needle_angle_for_value(lv_obj_t *obj, int32_t value);
|
||||
|
||||
@@ -9,6 +9,7 @@ from .defines import (
|
||||
CONF_THEME,
|
||||
LValidator,
|
||||
add_lv_use,
|
||||
get_styles_used,
|
||||
get_theme_widget_map,
|
||||
literal,
|
||||
)
|
||||
@@ -25,6 +26,7 @@ def has_style_props(config) -> bool:
|
||||
async def style_set(svar, style):
|
||||
for prop, validator in ALL_STYLES.items():
|
||||
if (value := style.get(prop)) is not None:
|
||||
get_styles_used().add(prop)
|
||||
if isinstance(validator, LValidator):
|
||||
value = await validator.process(value)
|
||||
if isinstance(value, list):
|
||||
|
||||
@@ -130,8 +130,8 @@ ClimateTraits AirConditioner::traits() {
|
||||
void AirConditioner::dump_config() {
|
||||
ESP_LOGCONFIG(Constants::TAG,
|
||||
"MideaDongle:\n"
|
||||
" [x] Period: %dms\n"
|
||||
" [x] Response timeout: %dms\n"
|
||||
" [x] Period: %" PRIu32 "ms\n"
|
||||
" [x] Response timeout: %" PRIu32 "ms\n"
|
||||
" [x] Request attempts: %d",
|
||||
this->base_.getPeriod(), this->base_.getTimeout(), this->base_.getNumAttempts());
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
|
||||
@@ -210,7 +210,7 @@ OTAResponseTypes IDFOTABackend::update_partition_table() {
|
||||
ESP_LOGE(TAG, "Cannot resolve running app partition at address 0x%" PRIX32, running_app_offset);
|
||||
return OTA_RESPONSE_ERROR_PARTITION_TABLE_UPDATE;
|
||||
}
|
||||
ESP_LOGD(TAG, "Copying running app from 0x%X to 0x%X (size: 0x%X)", running_app_part->address,
|
||||
ESP_LOGD(TAG, "Copying running app from 0x%" PRIX32 " to 0x%" PRIX32 " (size: 0x%zX)", running_app_part->address,
|
||||
plan.copy_dest_part->address, running_app_size);
|
||||
err = esp_partition_copy(plan.copy_dest_part, 0, running_app_part, 0, running_app_size);
|
||||
if (err != ESP_OK) {
|
||||
@@ -261,7 +261,7 @@ OTAResponseTypes IDFOTABackend::update_partition_table() {
|
||||
ESP_LOGE(TAG, "Selected app partition not found after partition table update");
|
||||
return OTA_RESPONSE_ERROR_PARTITION_TABLE_UPDATE;
|
||||
}
|
||||
ESP_LOGD(TAG, "Setting next boot partition to 0x%X", new_boot_partition->address);
|
||||
ESP_LOGD(TAG, "Setting next boot partition to 0x%" PRIX32, new_boot_partition->address);
|
||||
err = esp_ota_set_boot_partition(new_boot_partition);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (err=0x%X)", err);
|
||||
|
||||
@@ -150,7 +150,7 @@ void IRAM_ATTR PulseMeterSensor::edge_intr(PulseMeterSensor *sensor) {
|
||||
edge_state.last_sent_edge_us_ = now;
|
||||
state.last_detected_edge_us_ = now;
|
||||
state.last_rising_edge_us_ = now;
|
||||
state.count_++; // NOLINT(clang-diagnostic-deprecated-volatile)
|
||||
state.count_ += 1;
|
||||
}
|
||||
|
||||
// This ISR is bound to rising edges, so the pin is high
|
||||
@@ -173,7 +173,7 @@ void IRAM_ATTR PulseMeterSensor::pulse_intr(PulseMeterSensor *sensor) {
|
||||
} else if (length && !pulse_state.latched_ && sensor->last_pin_val_) { // Long enough high edge
|
||||
pulse_state.latched_ = true;
|
||||
state.last_detected_edge_us_ = pulse_state.last_intr_;
|
||||
state.count_++; // NOLINT(clang-diagnostic-deprecated-volatile)
|
||||
state.count_ += 1;
|
||||
}
|
||||
|
||||
// Due to order of operations this includes
|
||||
|
||||
@@ -78,10 +78,10 @@ void RemoteReceiverComponent::setup() {
|
||||
void RemoteReceiverComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Remote Receiver:\n"
|
||||
" Buffer Size: %u\n"
|
||||
" Tolerance: %u%s\n"
|
||||
" Filter out pulses shorter than: %u us\n"
|
||||
" Signal is done after %u us of no changes",
|
||||
" Buffer Size: %" PRIu32 "\n"
|
||||
" Tolerance: %" PRIu32 "%s\n"
|
||||
" Filter out pulses shorter than: %" PRIu32 " us\n"
|
||||
" Signal is done after %" PRIu32 " us of no changes",
|
||||
this->buffer_size_, this->tolerance_,
|
||||
(this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", this->filter_us_,
|
||||
this->idle_us_);
|
||||
|
||||
@@ -153,7 +153,7 @@ bool SendspinHub::save_last_server_hash(uint32_t hash) {
|
||||
LastPlayedServerPref pref{.server_id_hash = hash};
|
||||
bool ok = this->last_played_server_pref_.save(&pref);
|
||||
if (ok) {
|
||||
ESP_LOGD(TAG, "Persisted last played server hash: 0x%08X", hash);
|
||||
ESP_LOGD(TAG, "Persisted last played server hash: 0x%08" PRIX32, hash);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to persist last played server hash");
|
||||
}
|
||||
@@ -164,7 +164,7 @@ bool SendspinHub::save_last_server_hash(uint32_t hash) {
|
||||
std::optional<uint32_t> SendspinHub::load_last_server_hash() {
|
||||
LastPlayedServerPref pref{};
|
||||
if (this->last_played_server_pref_.load(&pref)) {
|
||||
ESP_LOGI(TAG, "Loaded last played server hash: 0x%08X", pref.server_id_hash);
|
||||
ESP_LOGI(TAG, "Loaded last played server hash: 0x%08" PRIX32, pref.server_id_hash);
|
||||
return pref.server_id_hash;
|
||||
}
|
||||
return std::nullopt;
|
||||
|
||||
@@ -126,7 +126,7 @@ void Sim800LComponent::parse_cmd_(std::string message) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Else fall thru ...
|
||||
[[fallthrough]];
|
||||
}
|
||||
case STATE_CHECK_SMS:
|
||||
send_cmd_("AT+CMGL=\"ALL\"");
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace esphome::sound_level {
|
||||
|
||||
static const char *const TAG = "sound_level";
|
||||
|
||||
static const uint32_t AUDIO_BUFFER_DURATION_MS = 30;
|
||||
static const uint32_t MAX_FILL_DURATION_MS = 30;
|
||||
static const uint32_t RING_BUFFER_DURATION_MS = 120;
|
||||
|
||||
// Square INT16_MIN since INT16_MIN^2 > INT16_MAX^2
|
||||
@@ -30,8 +30,7 @@ void SoundLevelComponent::dump_config() {
|
||||
void SoundLevelComponent::setup() {
|
||||
this->microphone_source_->add_data_callback([this](const std::vector<uint8_t> &data) {
|
||||
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
|
||||
if (this->ring_buffer_.use_count() == 2) {
|
||||
// ``audio_buffer_`` and ``temp_ring_buffer`` share ownership of a ring buffer, so its safe/useful to write
|
||||
if (temp_ring_buffer != nullptr) {
|
||||
temp_ring_buffer->write((void *) data.data(), data.size());
|
||||
}
|
||||
});
|
||||
@@ -81,10 +80,11 @@ void SoundLevelComponent::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy data from ring buffer into the transfer buffer - don't block to avoid slowing the main loop
|
||||
this->audio_buffer_->transfer_data_from_source(0);
|
||||
// Expose a chunk of the ring buffer's internal storage - don't block to avoid slowing the main loop.
|
||||
// pre_shift is ignored by RingBufferAudioSource (no intermediate transfer buffer to compact).
|
||||
this->audio_source_->fill(0, false);
|
||||
|
||||
if (this->audio_buffer_->available() == 0) {
|
||||
if (this->audio_source_->available() == 0) {
|
||||
// No new audio available for processing
|
||||
return;
|
||||
}
|
||||
@@ -92,11 +92,11 @@ void SoundLevelComponent::loop() {
|
||||
const uint32_t samples_in_window =
|
||||
this->microphone_source_->get_audio_stream_info().ms_to_samples(this->measurement_duration_ms_);
|
||||
const uint32_t samples_available_to_process =
|
||||
this->microphone_source_->get_audio_stream_info().bytes_to_samples(this->audio_buffer_->available());
|
||||
this->microphone_source_->get_audio_stream_info().bytes_to_samples(this->audio_source_->available());
|
||||
const uint32_t samples_to_process = std::min(samples_in_window - this->sample_count_, samples_available_to_process);
|
||||
|
||||
// MicrophoneSource always provides int16 samples due to Python codegen settings
|
||||
const int16_t *audio_data = reinterpret_cast<const int16_t *>(this->audio_buffer_->get_buffer_start());
|
||||
const int16_t *audio_data = reinterpret_cast<const int16_t *>(this->audio_source_->data());
|
||||
|
||||
// Process all the new audio samples
|
||||
for (uint32_t i = 0; i < samples_to_process; ++i) {
|
||||
@@ -115,9 +115,8 @@ void SoundLevelComponent::loop() {
|
||||
++this->sample_count_;
|
||||
}
|
||||
|
||||
// Remove the processed samples from ``audio_buffer_``
|
||||
this->audio_buffer_->decrease_buffer_length(
|
||||
this->microphone_source_->get_audio_stream_info().samples_to_bytes(samples_to_process));
|
||||
// Remove the processed samples from ``audio_source_``
|
||||
this->audio_source_->consume(this->microphone_source_->get_audio_stream_info().samples_to_bytes(samples_to_process));
|
||||
|
||||
if (this->sample_count_ == samples_in_window) {
|
||||
// Processed enough samples for the measurement window, compute and publish the sensor values
|
||||
@@ -158,36 +157,39 @@ void SoundLevelComponent::stop() {
|
||||
}
|
||||
|
||||
bool SoundLevelComponent::start_() {
|
||||
if (this->audio_buffer_ != nullptr) {
|
||||
if (this->audio_source_ != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allocate a transfer buffer
|
||||
this->audio_buffer_ = audio::AudioSourceTransferBuffer::create(
|
||||
this->microphone_source_->get_audio_stream_info().ms_to_bytes(AUDIO_BUFFER_DURATION_MS));
|
||||
if (this->audio_buffer_ == nullptr) {
|
||||
this->status_momentary_error("transfer_buffer", 15000);
|
||||
const auto &stream_info = this->microphone_source_->get_audio_stream_info();
|
||||
const size_t bytes_per_frame = stream_info.frames_to_bytes(1);
|
||||
|
||||
// Allocate a ring buffer for the microphone callback to write into. Round the size down to a multiple
|
||||
// of bytes_per_frame so the wrap boundary stays frame-aligned and avoids unnecessary single-frame splices.
|
||||
this->ring_buffer_.reset(); // Reset pointer to any previous ring buffer allocation
|
||||
const size_t ring_buffer_size =
|
||||
(stream_info.ms_to_bytes(RING_BUFFER_DURATION_MS) / bytes_per_frame) * bytes_per_frame;
|
||||
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = ring_buffer::RingBuffer::create(ring_buffer_size);
|
||||
if (temp_ring_buffer == nullptr) {
|
||||
this->status_momentary_error("ring_buffer", 15000);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocates a new ring buffer, adds it as a source for the transfer buffer, and points ring_buffer_ to it
|
||||
this->ring_buffer_.reset(); // Reset pointer to any previous ring buffer allocation
|
||||
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = ring_buffer::RingBuffer::create(
|
||||
this->microphone_source_->get_audio_stream_info().ms_to_bytes(RING_BUFFER_DURATION_MS));
|
||||
if (temp_ring_buffer.use_count() == 0) {
|
||||
this->status_momentary_error("ring_buffer", 15000);
|
||||
this->stop_();
|
||||
// Zero-copy source that reads directly from the ring buffer's internal storage. Frame-aligned reads
|
||||
// ensure multi-channel frames are never split across the ring buffer's wrap boundary.
|
||||
this->audio_source_ = audio::RingBufferAudioSource::create(
|
||||
temp_ring_buffer, stream_info.ms_to_bytes(MAX_FILL_DURATION_MS), static_cast<uint8_t>(bytes_per_frame));
|
||||
if (this->audio_source_ == nullptr) {
|
||||
this->status_momentary_error("audio_source", 15000);
|
||||
return false;
|
||||
} else {
|
||||
this->ring_buffer_ = temp_ring_buffer;
|
||||
this->audio_buffer_->set_source(temp_ring_buffer);
|
||||
}
|
||||
this->ring_buffer_ = temp_ring_buffer;
|
||||
|
||||
this->status_clear_error();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SoundLevelComponent::stop_() { this->audio_buffer_.reset(); }
|
||||
void SoundLevelComponent::stop_() { this->audio_source_.reset(); }
|
||||
|
||||
} // namespace esphome::sound_level
|
||||
|
||||
|
||||
@@ -36,11 +36,12 @@ class SoundLevelComponent : public Component {
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
/// @brief Internal start command that, if necessary, allocates ``audio_buffer_`` and a ring buffer which
|
||||
/// ``audio_buffer_`` owns and ``ring_buffer_`` points to. Returns true if allocations were successful.
|
||||
/// @brief Internal start command that, if necessary, allocates a ring buffer and a zero-copy
|
||||
/// ``RingBufferAudioSource`` that reads directly from it. ``ring_buffer_`` weakly references the
|
||||
/// ring buffer owned by ``audio_source_``. Returns true if allocations were successful.
|
||||
bool start_();
|
||||
|
||||
/// @brief Internal stop command the deallocates ``audio_buffer_`` (which automatically deallocates its ring buffer)
|
||||
/// @brief Internal stop command that deallocates ``audio_source_`` (which releases its ring buffer)
|
||||
void stop_();
|
||||
|
||||
microphone::MicrophoneSource *microphone_source_{nullptr};
|
||||
@@ -48,7 +49,7 @@ class SoundLevelComponent : public Component {
|
||||
sensor::Sensor *peak_sensor_{nullptr};
|
||||
sensor::Sensor *rms_sensor_{nullptr};
|
||||
|
||||
std::unique_ptr<audio::AudioSourceTransferBuffer> audio_buffer_;
|
||||
std::unique_ptr<audio::RingBufferAudioSource> audio_source_;
|
||||
std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
|
||||
|
||||
int32_t squared_peak_{0};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from esphome import final_validate as fv
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32
|
||||
from esphome.components.esp32 import (
|
||||
@@ -8,7 +9,7 @@ from esphome.components.esp32 import (
|
||||
add_idf_sdkconfig_option,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_HARDWARE_UART, CONF_ID
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
CONFLICTS_WITH = ["usb_host"]
|
||||
@@ -20,6 +21,13 @@ CONF_USB_PRODUCT_STR = "usb_product_str"
|
||||
CONF_USB_SERIAL_STR = "usb_serial_str"
|
||||
CONF_USB_VENDOR_ID = "usb_vendor_id"
|
||||
|
||||
# Components that provide a USB device class (CDC, HID, MSC, ...) on top of
|
||||
# tinyusb. Configuring `tinyusb:` without any of these triggers a 5s hang in
|
||||
# esp_tinyusb's driver install (descriptors_set fails with no class and no
|
||||
# user-provided full_speed_config), which trips the task watchdog before
|
||||
# loop() ever runs.
|
||||
_USB_CLASS_COMPONENTS = ("usb_cdc_acm",)
|
||||
|
||||
tinyusb_ns = cg.esphome_ns.namespace("tinyusb")
|
||||
TinyUSB = tinyusb_ns.class_("TinyUSB", cg.Component)
|
||||
|
||||
@@ -41,6 +49,29 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
full_config = fv.full_config.get()
|
||||
if not any(name in full_config for name in _USB_CLASS_COMPONENTS):
|
||||
raise cv.Invalid(
|
||||
"The 'tinyusb' component requires at least one USB class component"
|
||||
)
|
||||
# tinyusb owns the USB OTG peripheral. The logger's USB_CDC backend routes
|
||||
# the ROM console through that same peripheral, so the two cannot coexist.
|
||||
# (USB_SERIAL_JTAG is a separate peripheral and is fine alongside tinyusb.)
|
||||
logger_config = full_config.get("logger")
|
||||
if logger_config and logger_config.get(CONF_HARDWARE_UART) == "USB_CDC":
|
||||
raise cv.Invalid(
|
||||
"'tinyusb' cannot be used with 'logger.hardware_uart: USB_CDC' "
|
||||
"because both share the USB OTG peripheral. Set "
|
||||
"'logger.hardware_uart' to a hardware UART (e.g. UART0), or to "
|
||||
"USB_SERIAL_JTAG on variants that support it (ESP32-S3, ESP32-P4)"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -26,6 +26,21 @@ void TinyUSB::setup() {
|
||||
.string_count = SIZE,
|
||||
};
|
||||
|
||||
// Defense-in-depth: esp_tinyusb's tinyusb_descriptors_set() fails with
|
||||
// ESP_ERR_INVALID_ARG when no configuration descriptor is provided and
|
||||
// no class that has a built-in default (CDC/MSC/NCM) is compiled in. In
|
||||
// that case the internal task exits without notifying us, and
|
||||
// tinyusb_driver_install() blocks 5s on the notify-take -- long enough
|
||||
// to trip the task watchdog. Bail early so the rest of the device can
|
||||
// still boot.
|
||||
#if !(CFG_TUD_CDC > 0 || CFG_TUD_MSC > 0 || CFG_TUD_NCM > 0)
|
||||
if (this->tusb_cfg_.descriptor.full_speed_config == nullptr) {
|
||||
ESP_LOGE(TAG, "No USB class configured");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t result = tinyusb_driver_install(&this->tusb_cfg_);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tinyusb_driver_install failed: %s", esp_err_to_name(result));
|
||||
|
||||
@@ -72,7 +72,7 @@ void TotalDailyEnergy::schedule_midnight_reset_() {
|
||||
timeout_seconds = seconds_until_midnight + 1;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Scheduling midnight check in %us", timeout_seconds);
|
||||
ESP_LOGD(TAG, "Scheduling midnight check in %" PRIu32 "s", timeout_seconds);
|
||||
this->set_timeout(TIMEOUT_ID_MIDNIGHT, timeout_seconds * MILLIS_PER_SECOND,
|
||||
[this]() { this->schedule_midnight_reset_(); });
|
||||
}
|
||||
|
||||
@@ -684,8 +684,10 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType
|
||||
case 4:
|
||||
data.push_back(value >> 24);
|
||||
data.push_back(value >> 16);
|
||||
[[fallthrough]];
|
||||
case 2:
|
||||
data.push_back(value >> 8);
|
||||
[[fallthrough]];
|
||||
case 1:
|
||||
data.push_back(value >> 0);
|
||||
break;
|
||||
|
||||
@@ -135,7 +135,7 @@ void Tx20Component::decode_and_publish_() {
|
||||
}
|
||||
if (tx20_se == tx20_sb) {
|
||||
tx20_wind_direction = tx20_se;
|
||||
if (tx20_wind_direction >= 0 && tx20_wind_direction < 16) {
|
||||
if (tx20_wind_direction < 16) {
|
||||
wind_cardinal_direction_ = DIRECTIONS[tx20_wind_direction];
|
||||
}
|
||||
ESP_LOGV(TAG, "WindDirection %d", tx20_wind_direction);
|
||||
@@ -164,7 +164,7 @@ void IRAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) {
|
||||
}
|
||||
arg->buffer[arg->buffer_index] = 1;
|
||||
arg->start_time = now;
|
||||
arg->buffer_index++; // NOLINT(clang-diagnostic-deprecated-volatile)
|
||||
arg->buffer_index += 1;
|
||||
return;
|
||||
}
|
||||
const uint32_t delay = now - arg->start_time;
|
||||
@@ -195,7 +195,7 @@ void IRAM_ATTR Tx20ComponentStore::gpio_intr(Tx20ComponentStore *arg) {
|
||||
}
|
||||
arg->spent_time += delay;
|
||||
arg->start_time = now;
|
||||
arg->buffer_index++; // NOLINT(clang-diagnostic-deprecated-volatile)
|
||||
arg->buffer_index += 1;
|
||||
}
|
||||
void IRAM_ATTR Tx20ComponentStore::reset() {
|
||||
tx20_available = false;
|
||||
|
||||
@@ -135,7 +135,7 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
|
||||
// Computed as ceil(buffer_size / 64) + 1 in Python codegen; defaults to 5 (256 / 64 + 1).
|
||||
static constexpr uint8_t USB_OUTPUT_CHUNK_COUNT = USB_UART_OUTPUT_CHUNK_COUNT;
|
||||
|
||||
USBUartChannel(uint8_t index, uint16_t buffer_size) : index_(index), input_buffer_(RingBuffer(buffer_size)) {}
|
||||
USBUartChannel(uint8_t index, uint16_t buffer_size) : input_buffer_(RingBuffer(buffer_size)), index_(index) {}
|
||||
void write_array(const uint8_t *data, size_t len) override;
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
|
||||
@@ -611,7 +611,7 @@ static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const c
|
||||
}
|
||||
|
||||
// Helper to get request detail parameter
|
||||
static JsonDetail get_request_detail(AsyncWebServerRequest *request) {
|
||||
[[maybe_unused]] static JsonDetail get_request_detail(AsyncWebServerRequest *request) {
|
||||
return request->arg(ESPHOME_F("detail")) == "all" ? DETAIL_ALL : DETAIL_STATE;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace {
|
||||
* - HTTPD_SOCK_ERR_TIMEOUT if the send buffer is full (EAGAIN/EWOULDBLOCK).
|
||||
* - HTTPD_SOCK_ERR_FAIL for other errors.
|
||||
*/
|
||||
int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags) {
|
||||
[[maybe_unused]] int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags) {
|
||||
if (buf == nullptr) {
|
||||
return HTTPD_SOCK_ERR_INVALID;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ static const char *const KEYS = "0123456789*#";
|
||||
void IRAM_ATTR HOT WiegandStore::d0_gpio_intr(WiegandStore *arg) {
|
||||
if (arg->d0.digital_read())
|
||||
return;
|
||||
arg->count++; // NOLINT(clang-diagnostic-deprecated-volatile)
|
||||
arg->count += 1;
|
||||
arg->value <<= 1;
|
||||
arg->last_bit_time = millis();
|
||||
arg->done = false;
|
||||
@@ -20,7 +20,7 @@ void IRAM_ATTR HOT WiegandStore::d0_gpio_intr(WiegandStore *arg) {
|
||||
void IRAM_ATTR HOT WiegandStore::d1_gpio_intr(WiegandStore *arg) {
|
||||
if (arg->d1.digital_read())
|
||||
return;
|
||||
arg->count++; // NOLINT(clang-diagnostic-deprecated-volatile)
|
||||
arg->count += 1;
|
||||
arg->value = (arg->value << 1) | 1;
|
||||
arg->last_bit_time = millis();
|
||||
arg->done = false;
|
||||
|
||||
@@ -54,10 +54,18 @@ from esphome.const import (
|
||||
CONF_TTLS_PHASE_2,
|
||||
CONF_USE_ADDRESS,
|
||||
CONF_USERNAME,
|
||||
CONF_WIFI,
|
||||
PLACEHOLDER_WIFI_SSID,
|
||||
Platform,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, HexInt, coroutine_with_priority
|
||||
from esphome.core import (
|
||||
CORE,
|
||||
CoroPriority,
|
||||
EsphomeError,
|
||||
HexInt,
|
||||
coroutine_with_priority,
|
||||
)
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -903,3 +911,45 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
"wifi_component_pico_w.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _placeholder_wifi_credentials(config: ConfigType) -> list[str]:
|
||||
"""Return human-readable locations where the dashboard's placeholder wifi
|
||||
values still appear. Empty list means no placeholders were found.
|
||||
"""
|
||||
placeholders: list[str] = []
|
||||
wifi_conf = config.get(CONF_WIFI)
|
||||
if not wifi_conf:
|
||||
return placeholders
|
||||
|
||||
for idx, network in enumerate(wifi_conf.get(CONF_NETWORKS, [])):
|
||||
ssid = network.get(CONF_SSID)
|
||||
if isinstance(ssid, str) and ssid == PLACEHOLDER_WIFI_SSID:
|
||||
placeholders.append(f"wifi.networks[{idx}].ssid")
|
||||
|
||||
ap_conf = wifi_conf.get(CONF_AP)
|
||||
if ap_conf:
|
||||
ap_ssid = ap_conf.get(CONF_SSID)
|
||||
if isinstance(ap_ssid, str) and ap_ssid == PLACEHOLDER_WIFI_SSID:
|
||||
placeholders.append("wifi.ap.ssid")
|
||||
|
||||
return placeholders
|
||||
|
||||
|
||||
def check_placeholder_credentials(config: ConfigType) -> None:
|
||||
"""Raise EsphomeError if any wifi credential is the dashboard placeholder.
|
||||
|
||||
Call only at compile time. NEVER from CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA,
|
||||
or any path reached by `esphome config`; device-builder relies on
|
||||
validation passing with the placeholders still in place.
|
||||
"""
|
||||
locations = _placeholder_wifi_credentials(config)
|
||||
if not locations:
|
||||
return
|
||||
formatted = ", ".join(locations)
|
||||
raise EsphomeError(
|
||||
f"wifi configuration still contains the dashboard placeholder value "
|
||||
f"'{PLACEHOLDER_WIFI_SSID}' at: {formatted}. "
|
||||
f"Open secrets.yaml and replace 'wifi_ssid' (and 'wifi_password') "
|
||||
f"with your real wifi credentials before flashing."
|
||||
)
|
||||
|
||||
+10
-1
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2026.5.0b1"
|
||||
__version__ = "2026.5.0b2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
@@ -1415,3 +1415,12 @@ ENTITY_CATEGORY_DIAGNOSTIC = "diagnostic"
|
||||
# The corresponding constant exists in c++
|
||||
# when update_interval is set to never, it becomes SCHEDULER_DONT_RUN milliseconds
|
||||
SCHEDULER_DONT_RUN = 4294967295
|
||||
|
||||
# Sentinel values written by the esphome-device-builder dashboard into
|
||||
# secrets.yaml on first boot so that !secret wifi_ssid / !secret wifi_password
|
||||
# references resolve cleanly through validation before the user has finished
|
||||
# the onboarding wizard. Compilation refuses if these reach the binary so that
|
||||
# a user who dismisses onboarding can't accidentally flash a device that will
|
||||
# never associate with their wifi.
|
||||
PLACEHOLDER_WIFI_SSID = "REPLACE_WITH_YOUR_WIFI_NETWORK"
|
||||
PLACEHOLDER_WIFI_PASSWORD = "REPLACE_WITH_YOUR_WIFI_PASSWORD" # noqa: S105
|
||||
|
||||
@@ -69,7 +69,7 @@ ESPHOME_IDF_DEFAULT_FEATURES = _str_to_lst_of_str(
|
||||
ESPHOME_IDF_FRAMEWORK_MIRRORS = _str_to_lst_of_str(
|
||||
os.environ.get(
|
||||
"ESPHOME_IDF_FRAMEWORK_MIRRORS",
|
||||
"https://github.com/espressif/esp-idf/releases/download/v{VERSION}/esp-idf-v{VERSION}.zip;https://github.com/espressif/esp-idf/releases/download/v{MAJOR}.{MINOR}/esp-idf-v{MAJOR}.{MINOR}.zip",
|
||||
"https://github.com/esphome-libs/esp-idf/releases/download/v{VERSION}/esp-idf-v{VERSION}.tar.xz;https://github.com/esphome-libs/esp-idf/releases/download/v{MAJOR}.{MINOR}/esp-idf-v{MAJOR}.{MINOR}.tar.xz",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+30
-23
@@ -191,17 +191,38 @@ def run_reconfigure() -> int:
|
||||
def has_outdated_files():
|
||||
"""Check if the build configuration is stale.
|
||||
|
||||
Returns True if required build files are missing or if configuration inputs
|
||||
are newer than the generated CMake/Ninja build artifacts.
|
||||
Returns True if required build files are missing or if ESPHome's
|
||||
resolved build inputs are newer than CMakeCache.txt:
|
||||
|
||||
- ``sdkconfig.<name>.esphomeinternal`` -- the canonical "what state
|
||||
did ESPHome resolve the YAML to" snapshot. Any change in build
|
||||
flags, enabled components, framework version, or target ends up
|
||||
rewriting it (we embed a ``# ESPHOME_IDF_VERSION=`` comment line
|
||||
for the version case where the option set would otherwise be
|
||||
identical).
|
||||
- ``src/idf_component.yml`` -- the project manifest. Managed
|
||||
component additions/removals (e.g. via ``add_idf_component``) can
|
||||
happen without any sdkconfig impact, and ``_write_idf_component_yml``
|
||||
already deletes ``dependencies.lock`` on a change but that signal
|
||||
gets lost as soon as the lock is missing.
|
||||
|
||||
We deliberately don't watch:
|
||||
- The top-level/src ``CMakeLists.txt`` -- ESPHome owns those, and
|
||||
ninja already tracks them as configure-time deps. Including them
|
||||
causes a perpetual reconfigure loop because CMake doesn't restamp
|
||||
``CMakeCache.txt`` when only ``idf_build_set_property`` values
|
||||
change between configures.
|
||||
- ``$IDF_PATH`` and CMake's ``build/config/`` -- both have mtime
|
||||
semantics that fire after the wrong configure (or not at all in
|
||||
common cases like in-place IDF version replacement). The sdkconfig
|
||||
and manifest hashes subsume the meaningful signal.
|
||||
"""
|
||||
cmakecache_txt_path = CORE.relative_build_path("build/CMakeCache.txt")
|
||||
|
||||
cmakelists_txt_build_path = CORE.relative_build_path("CMakeLists.txt")
|
||||
cmakelists_txt_src_path = CORE.relative_src_path("CMakeLists.txt")
|
||||
build_config_path = CORE.relative_build_path("build/config")
|
||||
sdkconfig_internal_path = CORE.relative_build_path(
|
||||
f"sdkconfig.{CORE.name}.esphomeinternal"
|
||||
)
|
||||
idf_component_yml_path = CORE.relative_build_path("src/idf_component.yml")
|
||||
dependency_lock_path = CORE.relative_build_path("dependencies.lock")
|
||||
build_ninja_path = CORE.relative_build_path("build/build.ninja")
|
||||
|
||||
@@ -219,14 +240,8 @@ def has_outdated_files():
|
||||
cmakecache_txt_mtime = os.path.getmtime(cmakecache_txt_path)
|
||||
return any(
|
||||
os.path.getmtime(f) > cmakecache_txt_mtime
|
||||
for f in [
|
||||
_get_idf_path(),
|
||||
cmakelists_txt_build_path,
|
||||
cmakelists_txt_src_path,
|
||||
sdkconfig_internal_path,
|
||||
build_config_path,
|
||||
]
|
||||
if f and os.path.exists(f)
|
||||
for f in [sdkconfig_internal_path, idf_component_yml_path]
|
||||
if f.exists()
|
||||
)
|
||||
|
||||
|
||||
@@ -302,21 +317,13 @@ def run_compile(config, verbose: bool) -> int:
|
||||
return rc
|
||||
_LOGGER.info("Regenerating CMakeLists.txt with discovered components...")
|
||||
write_project(minimal=False)
|
||||
# The post-discovery rewrite leaves CMakeLists newer than
|
||||
# CMakeCache.txt. CMake won't re-touch CMakeCache.txt on a
|
||||
# configure that only changes idf_build_set_property values
|
||||
# (those aren't cache variables), so has_outdated_files() would
|
||||
# return True on every subsequent build, perpetually retriggering
|
||||
# the two-pass. Touch CMakeCache.txt now so its mtime stays past
|
||||
# the rewritten CMakeLists.
|
||||
cmakecache = CORE.relative_build_path("build/CMakeCache.txt")
|
||||
if cmakecache.is_file():
|
||||
os.utime(cmakecache)
|
||||
if CORE.testing_mode:
|
||||
# Reconfigure again so cmake is up to date with the full
|
||||
# component list before the build's idf.py invocation runs --
|
||||
# idf.py build would otherwise re-run cmake and regenerate
|
||||
# memory.ld, wiping the DRAM/IRAM patches applied below.
|
||||
# Outside testing mode ninja's own configure-time dep on
|
||||
# CMakeLists.txt handles the re-run as part of the build step.
|
||||
rc = run_reconfigure()
|
||||
if rc != 0:
|
||||
_LOGGER.error("Reconfigure with discovered components failed")
|
||||
|
||||
@@ -10,7 +10,7 @@ dependencies:
|
||||
esphome/micro-flac:
|
||||
version: 0.2.0
|
||||
esphome/micro-mp3:
|
||||
version: 0.2.0
|
||||
version: 0.2.1
|
||||
esphome/micro-opus:
|
||||
version: 0.4.1
|
||||
esphome/micro-wav:
|
||||
@@ -36,7 +36,7 @@ dependencies:
|
||||
rules:
|
||||
- if: "target in [esp32h2, esp32p4]"
|
||||
espressif/esp_hosted:
|
||||
version: 2.12.6
|
||||
version: 2.12.7
|
||||
rules:
|
||||
- if: "target in [esp32h2, esp32p4]"
|
||||
zorxx/multipart-parser:
|
||||
|
||||
+12
-1
@@ -273,10 +273,21 @@ class StorageJSON:
|
||||
"""
|
||||
CORE.name = self.name
|
||||
CORE.build_path = self.build_path
|
||||
target_platform = self.core_platform or self.target_platform.lower()
|
||||
CORE.data[KEY_CORE] = {
|
||||
KEY_TARGET_PLATFORM: self.core_platform or self.target_platform.lower(),
|
||||
KEY_TARGET_PLATFORM: target_platform,
|
||||
KEY_TARGET_FRAMEWORK: self.framework,
|
||||
}
|
||||
# The compile pipeline populates CORE.data[KEY_ESP32] when esp32's
|
||||
# validator runs; on the cache fast path that validator is skipped,
|
||||
# so populate the variant upload_using_esptool reads via
|
||||
# esp32.get_esp32_variant(). target_platform on disk is the variant
|
||||
# (e.g. "ESP32S3"); core_platform is the family (e.g. "esp32").
|
||||
if target_platform == const.PLATFORM_ESP32:
|
||||
from esphome.components.esp32.const import KEY_ESP32
|
||||
from esphome.const import KEY_VARIANT
|
||||
|
||||
CORE.data[KEY_ESP32] = {KEY_VARIANT: self.target_platform}
|
||||
|
||||
def __eq__(self, o) -> bool:
|
||||
return isinstance(o, StorageJSON) and self.as_dict() == o.as_dict()
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@ platformio==6.1.19
|
||||
esptool==5.2.0
|
||||
click==8.3.3
|
||||
esphome-dashboard==20260425.0
|
||||
aioesphomeapi==45.0.0
|
||||
zeroconf==0.148.0
|
||||
aioesphomeapi==45.0.3
|
||||
zeroconf==0.149.3
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.19.1 # dashboard_import
|
||||
ruamel.yaml.clib==0.2.15 # dashboard_import
|
||||
|
||||
@@ -29,12 +29,12 @@ esp32_ble_tracker:
|
||||
- service_uuid: ABCD
|
||||
then:
|
||||
- lambda: !lambda |-
|
||||
ESP_LOGD("main", "Length of service data is %i", x.size());
|
||||
ESP_LOGD("main", "Length of service data is %zu", x.size());
|
||||
on_ble_manufacturer_data_advertise:
|
||||
- manufacturer_id: ABCD
|
||||
then:
|
||||
- lambda: !lambda |-
|
||||
ESP_LOGD("main", "Length of manufacturer data is %i", x.size());
|
||||
ESP_LOGD("main", "Length of manufacturer data is %zu", x.size());
|
||||
on_scan_end:
|
||||
- then:
|
||||
- lambda: |-
|
||||
|
||||
@@ -123,7 +123,7 @@ select:
|
||||
- lambda: |-
|
||||
id(uart_bus).flush();
|
||||
uint32_t new_baud_rate = stoi(x);
|
||||
ESP_LOGD("change_baud_rate", "Changing baud rate from %i to %i",id(uart_bus).get_baud_rate(), new_baud_rate);
|
||||
ESP_LOGD("change_baud_rate", "Changing baud rate from %" PRIu32 " to %" PRIu32, id(uart_bus).get_baud_rate(), new_baud_rate);
|
||||
if (id(uart_bus).get_baud_rate() != new_baud_rate) {
|
||||
id(uart_bus).set_baud_rate(new_baud_rate);
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
|
||||
@@ -660,13 +660,13 @@ lvgl:
|
||||
on_release:
|
||||
logger.log:
|
||||
format: Button released at %d/%d
|
||||
args: [point.x, point.y]
|
||||
args: ['(int) point.x', '(int) point.y']
|
||||
on_long_press_repeat:
|
||||
logger.log: Button clicked
|
||||
on_pressing:
|
||||
logger.log:
|
||||
format: Button pressing at %d/%d
|
||||
args: [point.x, point.y]
|
||||
args: ['(int) point.x', '(int) point.y']
|
||||
on_press_lost:
|
||||
logger.log: Button press lost
|
||||
on_single_click:
|
||||
@@ -944,7 +944,7 @@ lvgl:
|
||||
on_release:
|
||||
logger.log:
|
||||
format: Slider released at %d/%d with value %.0f
|
||||
args: [point.x, point.y, x]
|
||||
args: ['(int) point.x', '(int) point.y', x]
|
||||
- button:
|
||||
styles: spin_button
|
||||
id: spin_up
|
||||
|
||||
@@ -21,7 +21,7 @@ modbus_server:
|
||||
read_lambda: |-
|
||||
return 31;
|
||||
write_lambda: |-
|
||||
printf("address=%d, value=%d", x);
|
||||
printf("address=%d, value=%" PRId32 "\n", (int) address, x);
|
||||
return true;
|
||||
- id: modbus_server4
|
||||
modbus_id: mod_bus2
|
||||
|
||||
@@ -64,7 +64,7 @@ mqtt:
|
||||
topic: some/topic
|
||||
payload: Good-bye
|
||||
- lambda: |-
|
||||
ESP_LOGD("MQTT", "Disconnect reason %d", reason);
|
||||
ESP_LOGD("MQTT", "Disconnect reason %d", (int) reason);
|
||||
publish_nan_as_none: false
|
||||
|
||||
binary_sensor:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user