mirror of
https://github.com/esphome/esphome.git
synced 2026-01-16 06:54:50 -07:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4906f87751 | ||
|
|
5b37d2fb27 | ||
|
|
68affe0b9c | ||
|
|
8263a8273f | ||
|
|
14b7539094 | ||
|
|
b37cb812a7 | ||
|
|
42491569c8 | ||
|
|
b1230ec6bb | ||
|
|
4eda9e965f | ||
|
|
d2528af649 | ||
|
|
2eabc1b96b | ||
|
|
535c3eb2a2 | ||
|
|
20f937692e | ||
|
|
00cc9e44b6 | ||
|
|
0427350101 | ||
|
|
41dceb76ec | ||
|
|
6380458d78 | ||
|
|
0dc5a7c9a4 | ||
|
|
9003844eda | ||
|
|
22a4ec69c2 | ||
|
|
9d42bfd161 | ||
|
|
49c881d067 | ||
|
|
78aee4f498 | ||
|
|
9da2c08f36 | ||
|
|
03f3deff41 | ||
|
|
f1e5d3a39a | ||
|
|
2f6863230d | ||
|
|
f44036310c |
2
Doxyfile
2
Doxyfile
@@ -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.1.0b2
|
||||
PROJECT_NUMBER = 2026.2.0-dev
|
||||
|
||||
# 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
|
||||
|
||||
@@ -222,8 +222,13 @@ def choose_upload_log_host(
|
||||
else:
|
||||
resolved.append(device)
|
||||
if not resolved:
|
||||
if CORE.dashboard:
|
||||
hint = "If you know the IP, set 'use_address' in your network config."
|
||||
else:
|
||||
hint = "If you know the IP, try --device <IP>"
|
||||
raise EsphomeError(
|
||||
f"All specified devices {defaults} could not be resolved. Is the device connected to the network?"
|
||||
f"All specified devices {defaults} could not be resolved. "
|
||||
f"Is the device connected to the network? {hint}"
|
||||
)
|
||||
return resolved
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from .helpers import (
|
||||
map_section_name,
|
||||
parse_symbol_line,
|
||||
)
|
||||
from .toolchain import find_tool, run_tool
|
||||
from .toolchain import find_tool, resolve_tool_path, run_tool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.platformio_api import IDEData
|
||||
@@ -132,6 +132,12 @@ class MemoryAnalyzer:
|
||||
readelf_path = readelf_path or idedata.readelf_path
|
||||
_LOGGER.debug("Using toolchain paths from PlatformIO idedata")
|
||||
|
||||
# Validate paths exist, fall back to find_tool if they don't
|
||||
# This handles cases like Zephyr where cc_path doesn't include full path
|
||||
# and the toolchain prefix may differ (e.g., arm-zephyr-eabi- vs arm-none-eabi-)
|
||||
objdump_path = resolve_tool_path("objdump", objdump_path, objdump_path)
|
||||
readelf_path = resolve_tool_path("readelf", readelf_path, objdump_path)
|
||||
|
||||
self.objdump_path = objdump_path or "objdump"
|
||||
self.readelf_path = readelf_path or "readelf"
|
||||
self.external_components = external_components or set()
|
||||
|
||||
@@ -9,11 +9,61 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::")
|
||||
# Maps standard section names to their various platform-specific variants
|
||||
# Note: Order matters! More specific patterns (.bss) must come before general ones (.dram)
|
||||
# because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise
|
||||
#
|
||||
# Platform-specific sections:
|
||||
# - ESP8266/ESP32: .iram*, .dram*
|
||||
# - LibreTiny RTL87xx: .xip.code_* (flash), .ram.code_* (RAM)
|
||||
# - LibreTiny BK7231: .itcm.code (fast RAM), .vectors (interrupt vectors)
|
||||
# - LibreTiny LN882X: .flash_text, .flash_copy* (flash code)
|
||||
# - Zephyr/nRF52: text, rodata, datas, bss (no leading dots)
|
||||
SECTION_MAPPING = {
|
||||
".text": frozenset([".text", ".iram"]),
|
||||
".rodata": frozenset([".rodata"]),
|
||||
".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss"
|
||||
".data": frozenset([".data", ".dram"]),
|
||||
".text": frozenset(
|
||||
[
|
||||
".text",
|
||||
".iram",
|
||||
# LibreTiny RTL87xx XIP (eXecute In Place) flash code
|
||||
".xip.code",
|
||||
# LibreTiny RTL87xx RAM code
|
||||
".ram.code_text",
|
||||
# LibreTiny BK7231 fast RAM code and vectors
|
||||
".itcm.code",
|
||||
".vectors",
|
||||
# LibreTiny LN882X flash code
|
||||
".flash_text",
|
||||
".flash_copy",
|
||||
# Zephyr/nRF52 sections (no leading dots)
|
||||
"text",
|
||||
"rom_start",
|
||||
]
|
||||
),
|
||||
".rodata": frozenset(
|
||||
[
|
||||
".rodata",
|
||||
# LibreTiny RTL87xx read-only data in RAM
|
||||
".ram.code_rodata",
|
||||
# Zephyr/nRF52 sections (no leading dots)
|
||||
"rodata",
|
||||
]
|
||||
),
|
||||
# .bss patterns - must be before .data to catch ".dram0.bss"
|
||||
".bss": frozenset(
|
||||
[
|
||||
".bss",
|
||||
# LibreTiny LN882X BSS
|
||||
".bss_ram",
|
||||
# Zephyr/nRF52 sections (no leading dots)
|
||||
"bss",
|
||||
"noinit",
|
||||
]
|
||||
),
|
||||
".data": frozenset(
|
||||
[
|
||||
".data",
|
||||
".dram",
|
||||
# Zephyr/nRF52 sections (no leading dots)
|
||||
"datas",
|
||||
]
|
||||
),
|
||||
}
|
||||
|
||||
# Section to ComponentMemory attribute mapping
|
||||
|
||||
@@ -94,13 +94,13 @@ def parse_symbol_line(line: str) -> tuple[str, str, int, str] | None:
|
||||
return None
|
||||
|
||||
# Find section, size, and name
|
||||
# Try each part as a potential section name
|
||||
for i, part in enumerate(parts):
|
||||
if not part.startswith("."):
|
||||
continue
|
||||
|
||||
# Skip parts that are clearly flags, addresses, or other metadata
|
||||
# Sections start with '.' (standard ELF) or are known section names (Zephyr)
|
||||
section = map_section_name(part)
|
||||
if not section:
|
||||
break
|
||||
continue
|
||||
|
||||
# Need at least size field after section
|
||||
if i + 1 >= len(parts):
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -17,10 +18,82 @@ TOOLCHAIN_PREFIXES = [
|
||||
"xtensa-lx106-elf-", # ESP8266
|
||||
"xtensa-esp32-elf-", # ESP32
|
||||
"xtensa-esp-elf-", # ESP32 (newer IDF)
|
||||
"arm-zephyr-eabi-", # nRF52/Zephyr SDK
|
||||
"arm-none-eabi-", # Generic ARM (RP2040, etc.)
|
||||
"", # System default (no prefix)
|
||||
]
|
||||
|
||||
|
||||
def _find_in_platformio_packages(tool_name: str) -> str | None:
|
||||
"""Search for a tool in PlatformIO package directories.
|
||||
|
||||
This handles cases like Zephyr SDK where tools are installed in nested
|
||||
directories that aren't in PATH.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool (e.g., "readelf", "objdump")
|
||||
|
||||
Returns:
|
||||
Full path to the tool or None if not found
|
||||
"""
|
||||
# Get PlatformIO packages directory
|
||||
platformio_home = Path(os.path.expanduser("~/.platformio/packages"))
|
||||
if not platformio_home.exists():
|
||||
return None
|
||||
|
||||
# Search patterns for toolchains that might contain the tool
|
||||
# Order matters - more specific patterns first
|
||||
search_patterns = [
|
||||
# Zephyr SDK deeply nested structure (4 levels)
|
||||
# e.g., toolchain-gccarmnoneeabi/zephyr-sdk-0.17.4/arm-zephyr-eabi/bin/arm-zephyr-eabi-objdump
|
||||
f"toolchain-*/*/*/bin/*-{tool_name}",
|
||||
# Zephyr SDK nested structure (3 levels)
|
||||
f"toolchain-*/*/bin/*-{tool_name}",
|
||||
f"toolchain-*/bin/*-{tool_name}",
|
||||
# Standard PlatformIO toolchain structure
|
||||
f"toolchain-*/bin/*{tool_name}",
|
||||
]
|
||||
|
||||
for pattern in search_patterns:
|
||||
matches = list(platformio_home.glob(pattern))
|
||||
if matches:
|
||||
# Sort to get consistent results, prefer arm-zephyr-eabi over arm-none-eabi
|
||||
matches.sort(key=lambda p: ("zephyr" not in str(p), str(p)))
|
||||
tool_path = str(matches[0])
|
||||
_LOGGER.debug("Found %s in PlatformIO packages: %s", tool_name, tool_path)
|
||||
return tool_path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def resolve_tool_path(
|
||||
tool_name: str,
|
||||
derived_path: str | None,
|
||||
objdump_path: str | None = None,
|
||||
) -> str | None:
|
||||
"""Resolve a tool path, falling back to find_tool if derived path doesn't exist.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool (e.g., "objdump", "readelf")
|
||||
derived_path: Path derived from idedata (may not exist for some platforms)
|
||||
objdump_path: Path to objdump binary to derive other tool paths from
|
||||
|
||||
Returns:
|
||||
Resolved path to the tool, or the original derived_path if it exists
|
||||
"""
|
||||
if derived_path and not Path(derived_path).exists():
|
||||
found = find_tool(tool_name, objdump_path)
|
||||
if found:
|
||||
_LOGGER.debug(
|
||||
"Derived %s path %s not found, using %s",
|
||||
tool_name,
|
||||
derived_path,
|
||||
found,
|
||||
)
|
||||
return found
|
||||
return derived_path
|
||||
|
||||
|
||||
def find_tool(
|
||||
tool_name: str,
|
||||
objdump_path: str | None = None,
|
||||
@@ -28,7 +101,8 @@ def find_tool(
|
||||
"""Find a toolchain tool by name.
|
||||
|
||||
First tries to derive the tool path from objdump_path (if provided),
|
||||
then falls back to searching for platform-specific tools.
|
||||
then searches PlatformIO package directories (for cross-compile toolchains),
|
||||
and finally falls back to searching for platform-specific tools in PATH.
|
||||
|
||||
Args:
|
||||
tool_name: Name of the tool (e.g., "objdump", "nm", "c++filt")
|
||||
@@ -47,7 +121,13 @@ def find_tool(
|
||||
_LOGGER.debug("Found %s at: %s", tool_name, potential_path)
|
||||
return potential_path
|
||||
|
||||
# Try platform-specific tools
|
||||
# Search in PlatformIO packages directory first (handles Zephyr SDK, etc.)
|
||||
# This must come before PATH search because system tools (e.g., /usr/bin/objdump)
|
||||
# are for the host architecture, not the target (ARM, Xtensa, etc.)
|
||||
if found := _find_in_platformio_packages(tool_name):
|
||||
return found
|
||||
|
||||
# Try platform-specific tools in PATH (fallback for when tools are installed globally)
|
||||
for prefix in TOOLCHAIN_PREFIXES:
|
||||
cmd = f"{prefix}{tool_name}"
|
||||
try:
|
||||
|
||||
@@ -558,8 +558,10 @@ bool APIServer::clear_noise_psk(bool make_active) {
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto &client : this->clients_) {
|
||||
if (!client->flags_.remove && client->is_authenticated())
|
||||
if (!client->flags_.remove && client->is_authenticated()) {
|
||||
client->send_time_request();
|
||||
return; // Only request from one client to avoid clock conflicts
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -44,7 +44,7 @@ void DallasTemperatureSensor::update() {
|
||||
|
||||
this->send_command_(DALLAS_COMMAND_START_CONVERSION);
|
||||
|
||||
this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
|
||||
this->set_timeout(this->get_address_name().c_str(), this->millis_to_wait_for_conversion_(), [this] {
|
||||
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
|
||||
@@ -74,8 +74,11 @@ class DebugComponent : public PollingComponent {
|
||||
#ifdef USE_SENSOR
|
||||
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
|
||||
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
#if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
|
||||
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
void set_min_free_sensor(sensor::Sensor *min_free_sensor) { min_free_sensor_ = min_free_sensor; }
|
||||
#endif
|
||||
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
||||
#ifdef USE_ESP32
|
||||
@@ -97,8 +100,11 @@ class DebugComponent : public PollingComponent {
|
||||
|
||||
sensor::Sensor *free_sensor_{nullptr};
|
||||
sensor::Sensor *block_sensor_{nullptr};
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
#if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
|
||||
sensor::Sensor *fragmentation_sensor_{nullptr};
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
sensor::Sensor *min_free_sensor_{nullptr};
|
||||
#endif
|
||||
sensor::Sensor *loop_time_sensor_{nullptr};
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -234,8 +234,19 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
|
||||
void DebugComponent::update_platform_() {
|
||||
#ifdef USE_SENSOR
|
||||
uint32_t max_alloc = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
||||
if (this->block_sensor_ != nullptr) {
|
||||
this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));
|
||||
this->block_sensor_->publish_state(max_alloc);
|
||||
}
|
||||
if (this->min_free_sensor_ != nullptr) {
|
||||
this->min_free_sensor_->publish_state(heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
|
||||
}
|
||||
if (this->fragmentation_sensor_ != nullptr) {
|
||||
uint32_t free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
if (free_heap > 0) {
|
||||
float fragmentation = 100.0f - (100.0f * max_alloc / free_heap);
|
||||
this->fragmentation_sensor_->publish_state(fragmentation);
|
||||
}
|
||||
}
|
||||
if (this->psram_sensor_ != nullptr) {
|
||||
this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
||||
|
||||
@@ -51,6 +51,9 @@ void DebugComponent::update_platform_() {
|
||||
if (this->block_sensor_ != nullptr) {
|
||||
this->block_sensor_->publish_state(lt_heap_get_max_alloc());
|
||||
}
|
||||
if (this->min_free_sensor_ != nullptr) {
|
||||
this->min_free_sensor_->publish_state(lt_heap_get_min_free());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ from esphome.const import (
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_COUNTER,
|
||||
ICON_TIMER,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RTL87XX,
|
||||
UNIT_BYTES,
|
||||
UNIT_HERTZ,
|
||||
UNIT_MILLISECOND,
|
||||
@@ -25,6 +28,7 @@ from . import ( # noqa: F401 pylint: disable=unused-import
|
||||
|
||||
DEPENDENCIES = ["debug"]
|
||||
|
||||
CONF_MIN_FREE = "min_free"
|
||||
CONF_PSRAM = "psram"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
@@ -42,8 +46,14 @@ CONFIG_SCHEMA = {
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_FRAGMENTATION): cv.All(
|
||||
cv.only_on_esp8266,
|
||||
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
|
||||
cv.Any(
|
||||
cv.All(
|
||||
cv.only_on_esp8266,
|
||||
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
|
||||
),
|
||||
cv.only_on_esp32,
|
||||
msg="This feature is only available on ESP8266 (Arduino 2.5.2+) and ESP32",
|
||||
),
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_COUNTER,
|
||||
@@ -51,6 +61,19 @@ CONFIG_SCHEMA = {
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_MIN_FREE): cv.All(
|
||||
cv.Any(
|
||||
cv.only_on_esp32,
|
||||
cv.only_on([PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX]),
|
||||
msg="This feature is only available on ESP32 and LibreTiny (BK72xx, LN882x, RTL87xx)",
|
||||
),
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BYTES,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLISECOND,
|
||||
icon=ICON_TIMER,
|
||||
@@ -93,6 +116,10 @@ async def to_code(config):
|
||||
sens = await sensor.new_sensor(fragmentation_conf)
|
||||
cg.add(debug_component.set_fragmentation_sensor(sens))
|
||||
|
||||
if min_free_conf := config.get(CONF_MIN_FREE):
|
||||
sens = await sensor.new_sensor(min_free_conf)
|
||||
cg.add(debug_component.set_min_free_sensor(sens))
|
||||
|
||||
if loop_time_conf := config.get(CONF_LOOP_TIME):
|
||||
sens = await sensor.new_sensor(loop_time_conf)
|
||||
cg.add(debug_component.set_loop_time_sensor(sens))
|
||||
|
||||
@@ -193,10 +193,18 @@ void BLEClientBase::log_event_(const char *name) {
|
||||
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_, name);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_gattc_event_(const char *name) {
|
||||
void BLEClientBase::log_gattc_lifecycle_event_(const char *name) {
|
||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_gattc_data_event_(const char *name) {
|
||||
// Data transfer events are logged at VERBOSE level because logging to UART creates
|
||||
// delays that cause timing issues during time-sensitive BLE operations. This is
|
||||
// especially problematic during pairing or firmware updates which require rapid
|
||||
// writes to many characteristics - the log spam can cause these operations to fail.
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_, name);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_, operation, status);
|
||||
}
|
||||
@@ -280,7 +288,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (!this->check_addr(param->open.remote_bda))
|
||||
return false;
|
||||
this->log_gattc_event_("OPEN");
|
||||
this->log_gattc_lifecycle_event_("OPEN");
|
||||
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
||||
this->service_count_ = 0;
|
||||
|
||||
@@ -331,7 +339,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
if (!this->check_addr(param->connect.remote_bda))
|
||||
return false;
|
||||
this->log_gattc_event_("CONNECT");
|
||||
this->log_gattc_lifecycle_event_("CONNECT");
|
||||
this->conn_id_ = param->connect.conn_id;
|
||||
// Start MTU negotiation immediately as recommended by ESP-IDF examples
|
||||
// (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
|
||||
@@ -376,7 +384,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_CLOSE_EVT: {
|
||||
if (this->conn_id_ != param->close.conn_id)
|
||||
return false;
|
||||
this->log_gattc_event_("CLOSE");
|
||||
this->log_gattc_lifecycle_event_("CLOSE");
|
||||
this->release_services();
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
this->conn_id_ = UNSET_CONN_ID;
|
||||
@@ -404,7 +412,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
if (this->conn_id_ != param->search_cmpl.conn_id)
|
||||
return false;
|
||||
this->log_gattc_event_("SEARCH_CMPL");
|
||||
this->log_gattc_lifecycle_event_("SEARCH_CMPL");
|
||||
// For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
|
||||
// This balances performance with bandwidth usage after the critical discovery phase
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
@@ -431,35 +439,35 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_READ_DESCR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_gattc_event_("READ_DESCR");
|
||||
this->log_gattc_data_event_("READ_DESCR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_gattc_event_("WRITE_DESCR");
|
||||
this->log_gattc_data_event_("WRITE_DESCR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_gattc_event_("WRITE_CHAR");
|
||||
this->log_gattc_data_event_("WRITE_CHAR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (this->conn_id_ != param->read.conn_id)
|
||||
return false;
|
||||
this->log_gattc_event_("READ_CHAR");
|
||||
this->log_gattc_data_event_("READ_CHAR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (this->conn_id_ != param->notify.conn_id)
|
||||
return false;
|
||||
this->log_gattc_event_("NOTIFY");
|
||||
this->log_gattc_data_event_("NOTIFY");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->log_gattc_event_("REG_FOR_NOTIFY");
|
||||
this->log_gattc_data_event_("REG_FOR_NOTIFY");
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
// Client is responsible for flipping the descriptor value
|
||||
@@ -491,7 +499,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
esp_err_t status =
|
||||
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
|
||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
||||
ESP_LOGV(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
||||
if (status) {
|
||||
this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status);
|
||||
}
|
||||
@@ -499,13 +507,13 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
this->log_gattc_event_("UNREG_FOR_NOTIFY");
|
||||
this->log_gattc_data_event_("UNREG_FOR_NOTIFY");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// ideally would check all other events for matching conn_id
|
||||
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event);
|
||||
// Unknown events logged at VERBOSE to avoid UART delays during time-sensitive operations
|
||||
ESP_LOGV(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_, event);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -127,7 +127,8 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
// 6 bytes used, 2 bytes padding
|
||||
|
||||
void log_event_(const char *name);
|
||||
void log_gattc_event_(const char *name);
|
||||
void log_gattc_lifecycle_event_(const char *name);
|
||||
void log_gattc_data_event_(const char *name);
|
||||
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||
const char *param_type);
|
||||
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||
|
||||
@@ -18,7 +18,15 @@ InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
|
||||
|
||||
InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
|
||||
this->raw_timings_ = &timings;
|
||||
this->packed_data_ = nullptr; // Clear packed if vector is set
|
||||
this->packed_data_ = nullptr;
|
||||
this->base85_ptr_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfraredCall &InfraredCall::set_raw_timings_base85(const std::string &base85) {
|
||||
this->base85_ptr_ = &base85;
|
||||
this->raw_timings_ = nullptr;
|
||||
this->packed_data_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -26,7 +34,8 @@ InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t
|
||||
this->packed_data_ = data;
|
||||
this->packed_length_ = length;
|
||||
this->packed_count_ = count;
|
||||
this->raw_timings_ = nullptr; // Clear vector if packed is set
|
||||
this->raw_timings_ = nullptr;
|
||||
this->base85_ptr_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -92,6 +101,14 @@ void Infrared::control(const InfraredCall &call) {
|
||||
call.get_packed_count());
|
||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
|
||||
call.get_repeat_count());
|
||||
} else if (call.is_base85()) {
|
||||
// Decode base85 directly into transmit buffer (zero heap allocations)
|
||||
if (!transmit_data->set_data_from_base85(call.get_base85_data())) {
|
||||
ESP_LOGE(TAG, "Invalid base85 data");
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Transmitting base85 raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
|
||||
call.get_repeat_count());
|
||||
} else {
|
||||
// From vector (lambdas/automations)
|
||||
transmit_data->set_data(call.get_raw_timings());
|
||||
|
||||
@@ -28,12 +28,29 @@ class InfraredCall {
|
||||
|
||||
/// Set the carrier frequency in Hz
|
||||
InfraredCall &set_carrier_frequency(uint32_t frequency);
|
||||
/// Set the raw timings (positive = mark, negative = space)
|
||||
/// Note: The timings vector must outlive the InfraredCall (zero-copy reference)
|
||||
|
||||
// ===== Raw Timings Methods =====
|
||||
// All set_raw_timings_* methods store pointers/references to external data.
|
||||
// The referenced data must remain valid until perform() completes.
|
||||
// Safe pattern: call.set_raw_timings_xxx(data); call.perform(); // synchronous
|
||||
// Unsafe pattern: call.set_raw_timings_xxx(data); defer([call]() { call.perform(); }); // data may be gone!
|
||||
|
||||
/// Set the raw timings from a vector (positive = mark, negative = space)
|
||||
/// @note Lifetime: Stores a pointer to the vector. The vector must outlive perform().
|
||||
/// @note Usage: Primarily for lambdas/automations where the vector is in scope.
|
||||
InfraredCall &set_raw_timings(const std::vector<int32_t> &timings);
|
||||
/// Set the raw timings from packed protobuf sint32 data (zero-copy from wire)
|
||||
/// Note: The data must outlive the InfraredCall
|
||||
|
||||
/// Set the raw timings from base85-encoded int32 data
|
||||
/// @note Lifetime: Stores a pointer to the string. The string must outlive perform().
|
||||
/// @note Usage: For web_server where the encoded string is on the stack.
|
||||
/// @note Decoding happens at perform() time, directly into the transmit buffer.
|
||||
InfraredCall &set_raw_timings_base85(const std::string &base85);
|
||||
|
||||
/// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded)
|
||||
/// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform().
|
||||
/// @note Usage: For API component where data comes directly from the protobuf message.
|
||||
InfraredCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count);
|
||||
|
||||
/// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.)
|
||||
InfraredCall &set_repeat_count(uint32_t count);
|
||||
|
||||
@@ -42,12 +59,18 @@ class InfraredCall {
|
||||
|
||||
/// Get the carrier frequency
|
||||
const optional<uint32_t> &get_carrier_frequency() const { return this->carrier_frequency_; }
|
||||
/// Get the raw timings (only valid if set via set_raw_timings, not packed)
|
||||
/// Get the raw timings (only valid if set via set_raw_timings, not packed or base85)
|
||||
const std::vector<int32_t> &get_raw_timings() const { return *this->raw_timings_; }
|
||||
/// Check if raw timings have been set (either vector or packed)
|
||||
bool has_raw_timings() const { return this->raw_timings_ != nullptr || this->packed_data_ != nullptr; }
|
||||
/// Check if raw timings have been set (vector, packed, or base85)
|
||||
bool has_raw_timings() const {
|
||||
return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base85_ptr_ != nullptr;
|
||||
}
|
||||
/// Check if using packed data format
|
||||
bool is_packed() const { return this->packed_data_ != nullptr; }
|
||||
/// Check if using base85 data format
|
||||
bool is_base85() const { return this->base85_ptr_ != nullptr; }
|
||||
/// Get the base85 data string
|
||||
const std::string &get_base85_data() const { return *this->base85_ptr_; }
|
||||
/// Get packed data (only valid if set via set_raw_timings_packed)
|
||||
const uint8_t *get_packed_data() const { return this->packed_data_; }
|
||||
uint16_t get_packed_length() const { return this->packed_length_; }
|
||||
@@ -59,9 +82,11 @@ class InfraredCall {
|
||||
uint32_t repeat_count_{1};
|
||||
Infrared *parent_;
|
||||
optional<uint32_t> carrier_frequency_;
|
||||
// Vector-based timings (for lambdas/automations)
|
||||
// Pointer to vector-based timings (caller-owned, must outlive perform())
|
||||
const std::vector<int32_t> *raw_timings_{nullptr};
|
||||
// Packed protobuf timings (for API zero-copy)
|
||||
// Pointer to base85-encoded string (caller-owned, must outlive perform())
|
||||
const std::string *base85_ptr_{nullptr};
|
||||
// Pointer to packed protobuf buffer (caller-owned, must outlive perform())
|
||||
const uint8_t *packed_data_{nullptr};
|
||||
uint16_t packed_length_{0};
|
||||
uint16_t packed_count_{0};
|
||||
|
||||
@@ -413,6 +413,7 @@ class TextValidator(LValidator):
|
||||
str_args = [str(x) for x in value[CONF_ARGS]]
|
||||
arg_expr = cg.RawExpression(",".join(str_args))
|
||||
format_str = cpp_string_escape(format_str)
|
||||
# str_sprintf justified: user-defined format, can't optimize without permanent RAM cost
|
||||
sprintf_str = f"str_sprintf({format_str}, {arg_expr}).c_str()"
|
||||
if nanval := value.get(CONF_IF_NAN):
|
||||
nanval = cpp_string_escape(nanval)
|
||||
|
||||
@@ -65,7 +65,10 @@ std::string lv_event_code_name_for(uint8_t event_code) {
|
||||
if (event_code < sizeof(EVENT_NAMES) / sizeof(EVENT_NAMES[0])) {
|
||||
return EVENT_NAMES[event_code];
|
||||
}
|
||||
return str_sprintf("%2d", event_code);
|
||||
// max 4 bytes: "%u" with uint8_t (max 255, 3 digits) + null
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%u", event_code);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
|
||||
|
||||
@@ -101,4 +101,225 @@ DriverChip(
|
||||
(0xFF, 0x77, 0x01, 0x00, 0x00, 0x00),
|
||||
]
|
||||
)
|
||||
|
||||
# jc8012P4A1 Driver Configuration (jd9365)
|
||||
# Using parameters from esp_lcd_jd9365.h and the working full init sequence
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# * Resolution: 800x1280
|
||||
# * PCLK Frequency: 60 MHz
|
||||
# * DSI Lane Bit Rate: 1 Gbps (using 2-Lane DSI configuration)
|
||||
# * Horizontal Timing (hsync_pulse_width=20, hsync_back_porch=20, hsync_front_porch=40)
|
||||
# * Vertical Timing (vsync_pulse_width=4, vsync_back_porch=8, vsync_front_porch=20)
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
DriverChip(
|
||||
"JC8012P4A1",
|
||||
width=800,
|
||||
height=1280,
|
||||
hsync_back_porch=20,
|
||||
hsync_pulse_width=20,
|
||||
hsync_front_porch=40,
|
||||
vsync_back_porch=8,
|
||||
vsync_pulse_width=4,
|
||||
vsync_front_porch=20,
|
||||
pclk_frequency="60MHz",
|
||||
lane_bit_rate="1Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
reset_pin=27,
|
||||
initsequence=[
|
||||
(0xE0, 0x00),
|
||||
(0xE1, 0x93),
|
||||
(0xE2, 0x65),
|
||||
(0xE3, 0xF8),
|
||||
(0x80, 0x01),
|
||||
(0xE0, 0x01),
|
||||
(0x00, 0x00),
|
||||
(0x01, 0x39),
|
||||
(0x03, 0x10),
|
||||
(0x04, 0x41),
|
||||
(0x0C, 0x74),
|
||||
(0x17, 0x00),
|
||||
(0x18, 0xD7),
|
||||
(0x19, 0x00),
|
||||
(0x1A, 0x00),
|
||||
(0x1B, 0xD7),
|
||||
(0x1C, 0x00),
|
||||
(0x24, 0xFE),
|
||||
(0x35, 0x26),
|
||||
(0x37, 0x69),
|
||||
(0x38, 0x05),
|
||||
(0x39, 0x06),
|
||||
(0x3A, 0x08),
|
||||
(0x3C, 0x78),
|
||||
(0x3D, 0xFF),
|
||||
(0x3E, 0xFF),
|
||||
(0x3F, 0xFF),
|
||||
(0x40, 0x06),
|
||||
(0x41, 0xA0),
|
||||
(0x43, 0x14),
|
||||
(0x44, 0x0B),
|
||||
(0x45, 0x30),
|
||||
(0x4B, 0x04),
|
||||
(0x55, 0x02),
|
||||
(0x57, 0x89),
|
||||
(0x59, 0x0A),
|
||||
(0x5A, 0x28),
|
||||
(0x5B, 0x15),
|
||||
(0x5D, 0x50),
|
||||
(0x5E, 0x37),
|
||||
(0x5F, 0x29),
|
||||
(0x60, 0x1E),
|
||||
(0x61, 0x1D),
|
||||
(0x62, 0x12),
|
||||
(0x63, 0x1A),
|
||||
(0x64, 0x08),
|
||||
(0x65, 0x25),
|
||||
(0x66, 0x26),
|
||||
(0x67, 0x28),
|
||||
(0x68, 0x49),
|
||||
(0x69, 0x3A),
|
||||
(0x6A, 0x43),
|
||||
(0x6B, 0x3A),
|
||||
(0x6C, 0x3B),
|
||||
(0x6D, 0x32),
|
||||
(0x6E, 0x1F),
|
||||
(0x6F, 0x0E),
|
||||
(0x70, 0x50),
|
||||
(0x71, 0x37),
|
||||
(0x72, 0x29),
|
||||
(0x73, 0x1E),
|
||||
(0x74, 0x1D),
|
||||
(0x75, 0x12),
|
||||
(0x76, 0x1A),
|
||||
(0x77, 0x08),
|
||||
(0x78, 0x25),
|
||||
(0x79, 0x26),
|
||||
(0x7A, 0x28),
|
||||
(0x7B, 0x49),
|
||||
(0x7C, 0x3A),
|
||||
(0x7D, 0x43),
|
||||
(0x7E, 0x3A),
|
||||
(0x7F, 0x3B),
|
||||
(0x80, 0x32),
|
||||
(0x81, 0x1F),
|
||||
(0x82, 0x0E),
|
||||
(0xE0, 0x02),
|
||||
(0x00, 0x1F),
|
||||
(0x01, 0x1F),
|
||||
(0x02, 0x52),
|
||||
(0x03, 0x51),
|
||||
(0x04, 0x50),
|
||||
(0x05, 0x4B),
|
||||
(0x06, 0x4A),
|
||||
(0x07, 0x49),
|
||||
(0x08, 0x48),
|
||||
(0x09, 0x47),
|
||||
(0x0A, 0x46),
|
||||
(0x0B, 0x45),
|
||||
(0x0C, 0x44),
|
||||
(0x0D, 0x40),
|
||||
(0x0E, 0x41),
|
||||
(0x0F, 0x1F),
|
||||
(0x10, 0x1F),
|
||||
(0x11, 0x1F),
|
||||
(0x12, 0x1F),
|
||||
(0x13, 0x1F),
|
||||
(0x14, 0x1F),
|
||||
(0x15, 0x1F),
|
||||
(0x16, 0x1F),
|
||||
(0x17, 0x1F),
|
||||
(0x18, 0x52),
|
||||
(0x19, 0x51),
|
||||
(0x1A, 0x50),
|
||||
(0x1B, 0x4B),
|
||||
(0x1C, 0x4A),
|
||||
(0x1D, 0x49),
|
||||
(0x1E, 0x48),
|
||||
(0x1F, 0x47),
|
||||
(0x20, 0x46),
|
||||
(0x21, 0x45),
|
||||
(0x22, 0x44),
|
||||
(0x23, 0x40),
|
||||
(0x24, 0x41),
|
||||
(0x25, 0x1F),
|
||||
(0x26, 0x1F),
|
||||
(0x27, 0x1F),
|
||||
(0x28, 0x1F),
|
||||
(0x29, 0x1F),
|
||||
(0x2A, 0x1F),
|
||||
(0x2B, 0x1F),
|
||||
(0x2C, 0x1F),
|
||||
(0x2D, 0x1F),
|
||||
(0x2E, 0x52),
|
||||
(0x2F, 0x40),
|
||||
(0x30, 0x41),
|
||||
(0x31, 0x48),
|
||||
(0x32, 0x49),
|
||||
(0x33, 0x4A),
|
||||
(0x34, 0x4B),
|
||||
(0x35, 0x44),
|
||||
(0x36, 0x45),
|
||||
(0x37, 0x46),
|
||||
(0x38, 0x47),
|
||||
(0x39, 0x51),
|
||||
(0x3A, 0x50),
|
||||
(0x3B, 0x1F),
|
||||
(0x3C, 0x1F),
|
||||
(0x3D, 0x1F),
|
||||
(0x3E, 0x1F),
|
||||
(0x3F, 0x1F),
|
||||
(0x40, 0x1F),
|
||||
(0x41, 0x1F),
|
||||
(0x42, 0x1F),
|
||||
(0x43, 0x1F),
|
||||
(0x44, 0x52),
|
||||
(0x45, 0x40),
|
||||
(0x46, 0x41),
|
||||
(0x47, 0x48),
|
||||
(0x48, 0x49),
|
||||
(0x49, 0x4A),
|
||||
(0x4A, 0x4B),
|
||||
(0x4B, 0x44),
|
||||
(0x4C, 0x45),
|
||||
(0x4D, 0x46),
|
||||
(0x4E, 0x47),
|
||||
(0x4F, 0x51),
|
||||
(0x50, 0x50),
|
||||
(0x51, 0x1F),
|
||||
(0x52, 0x1F),
|
||||
(0x53, 0x1F),
|
||||
(0x54, 0x1F),
|
||||
(0x55, 0x1F),
|
||||
(0x56, 0x1F),
|
||||
(0x57, 0x1F),
|
||||
(0x58, 0x40),
|
||||
(0x59, 0x00),
|
||||
(0x5A, 0x00),
|
||||
(0x5B, 0x10),
|
||||
(0x5C, 0x05),
|
||||
(0x5D, 0x50),
|
||||
(0x5E, 0x01),
|
||||
(0x5F, 0x02),
|
||||
(0x60, 0x50),
|
||||
(0x61, 0x06),
|
||||
(0x62, 0x04),
|
||||
(0x63, 0x03),
|
||||
(0x64, 0x64),
|
||||
(0x65, 0x65),
|
||||
(0x66, 0x0B),
|
||||
(0x67, 0x73),
|
||||
(0x68, 0x07),
|
||||
(0x69, 0x06),
|
||||
(0x6A, 0x64),
|
||||
(0x6B, 0x08),
|
||||
(0x6C, 0x00),
|
||||
(0x6D, 0x32),
|
||||
(0x6E, 0x08),
|
||||
(0xE0, 0x04),
|
||||
(0x2C, 0x6B),
|
||||
(0x35, 0x08),
|
||||
(0x37, 0x00),
|
||||
(0xE0, 0x00),
|
||||
]
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
@@ -159,6 +159,10 @@ void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t
|
||||
}
|
||||
}
|
||||
|
||||
bool RemoteTransmitData::set_data_from_base85(const std::string &base85) {
|
||||
return base85_decode_int32_vector(base85, this->data_);
|
||||
}
|
||||
|
||||
/* RemoteTransmitterBase */
|
||||
|
||||
void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
|
||||
|
||||
@@ -36,6 +36,11 @@ class RemoteTransmitData {
|
||||
/// @param len Length of the buffer in bytes
|
||||
/// @param count Number of values (for reserve optimization)
|
||||
void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count);
|
||||
/// Set data from base85-encoded int32 values
|
||||
/// Decodes directly into internal buffer (zero heap allocations)
|
||||
/// @param base85 Base85-encoded string (5 chars per int32 value)
|
||||
/// @return true if successful, false if decode failed or invalid size
|
||||
bool set_data_from_base85(const std::string &base85);
|
||||
void reset() {
|
||||
this->data_.clear();
|
||||
this->carrier_frequency_ = 0;
|
||||
|
||||
@@ -332,6 +332,7 @@ Sprinkler::Sprinkler(const std::string &name) {
|
||||
// The `name` is needed to set timers up, hence non-default constructor
|
||||
// replaces `set_name()` method previously existed
|
||||
this->name_ = name;
|
||||
this->timer_.init(2);
|
||||
this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
|
||||
this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
|
||||
}
|
||||
@@ -1574,7 +1575,8 @@ const LogString *Sprinkler::state_as_str_(SprinklerState state) {
|
||||
|
||||
void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
||||
if (this->timer_duration_(timer_index) > 0) {
|
||||
this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
|
||||
// FixedVector ensures timer_ can't be resized, so .c_str() pointers remain valid
|
||||
this->set_timeout(this->timer_[timer_index].name.c_str(), this->timer_duration_(timer_index),
|
||||
this->timer_cbf_(timer_index));
|
||||
this->timer_[timer_index].start_time = millis();
|
||||
this->timer_[timer_index].active = true;
|
||||
@@ -1585,7 +1587,7 @@ void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
||||
|
||||
bool Sprinkler::cancel_timer_(const SprinklerTimerIndex timer_index) {
|
||||
this->timer_[timer_index].active = false;
|
||||
return this->cancel_timeout(this->timer_[timer_index].name);
|
||||
return this->cancel_timeout(this->timer_[timer_index].name.c_str());
|
||||
}
|
||||
|
||||
bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
@@ -553,8 +554,8 @@ class Sprinkler : public Component {
|
||||
/// Sprinkler valve operator objects
|
||||
std::vector<SprinklerValveOperator> valve_op_{2};
|
||||
|
||||
/// Valve control timers
|
||||
std::vector<SprinklerTimer> timer_{};
|
||||
/// Valve control timers - FixedVector enforces that this can never grow beyond init() size
|
||||
FixedVector<SprinklerTimer> timer_;
|
||||
|
||||
/// Other Sprinkler instances we should be aware of (used to check if pumps are in use)
|
||||
std::vector<Sprinkler *> other_controllers_;
|
||||
|
||||
@@ -31,6 +31,18 @@ void RealTimeClock::dump_config() {
|
||||
|
||||
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
||||
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
||||
// Skip if time is already synchronized to avoid unnecessary writes, log spam,
|
||||
// and prevent clock jumping backwards due to network latency
|
||||
constexpr time_t min_valid_epoch = 1546300800; // January 1, 2019
|
||||
time_t current_time = this->timestamp_now();
|
||||
// Check if time is valid (year >= 2019) before comparing
|
||||
if (current_time >= min_valid_epoch) {
|
||||
// Unsigned subtraction handles wraparound correctly, then cast to signed
|
||||
int32_t diff = static_cast<int32_t>(epoch - static_cast<uint32_t>(current_time));
|
||||
if (diff >= -1 && diff <= 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Update UTC epoch time.
|
||||
#ifdef USE_ZEPHYR
|
||||
struct timespec ts;
|
||||
|
||||
@@ -753,9 +753,6 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::button_state_json_generator(WebServer *web_server, void *source) {
|
||||
return web_server->button_json_((button::Button *) (source), DETAIL_STATE);
|
||||
}
|
||||
std::string WebServer::button_all_json_generator(WebServer *web_server, void *source) {
|
||||
return web_server->button_json_((button::Button *) (source), DETAIL_ALL);
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ class WebServer : public Controller,
|
||||
/// Handle a button request under '/button/<id>/press'.
|
||||
void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
static std::string button_state_json_generator(WebServer *web_server, void *source);
|
||||
// Buttons are stateless, so there is no button_state_json_generator
|
||||
static std::string button_all_json_generator(WebServer *web_server, void *source);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2026.1.0b2"
|
||||
__version__ = "2026.2.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -487,19 +487,26 @@ static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
// Helper function to find the index of a base64 character in the lookup table.
|
||||
// Helper function to find the index of a base64/base64url character in the lookup table.
|
||||
// Returns the character's position (0-63) if found, or 0 if not found.
|
||||
// Supports both standard base64 (+/) and base64url (-_) alphabets.
|
||||
// NOTE: This returns 0 for both 'A' (valid base64 char at index 0) and invalid characters.
|
||||
// This is safe because is_base64() is ALWAYS checked before calling this function,
|
||||
// preventing invalid characters from ever reaching here. The base64_decode function
|
||||
// stops processing at the first invalid character due to the is_base64() check in its
|
||||
// while loop condition, making this edge case harmless in practice.
|
||||
static inline uint8_t base64_find_char(char c) {
|
||||
// Handle base64url variants: '-' maps to '+' (index 62), '_' maps to '/' (index 63)
|
||||
if (c == '-')
|
||||
return 62;
|
||||
if (c == '_')
|
||||
return 63;
|
||||
const char *pos = strchr(BASE64_CHARS, c);
|
||||
return pos ? (pos - BASE64_CHARS) : 0;
|
||||
}
|
||||
|
||||
static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); }
|
||||
// Check if character is valid base64 or base64url
|
||||
static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/') || (c == '-') || (c == '_')); }
|
||||
|
||||
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
||||
|
||||
@@ -617,6 +624,55 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Encode int32 to 5 base85 characters + null terminator
|
||||
/// Standard ASCII85 alphabet: '!' (33) = 0 through 'u' (117) = 84
|
||||
inline void base85_encode_int32(int32_t value, std::span<char, BASE85_INT32_ENCODED_SIZE> output) {
|
||||
uint32_t v = static_cast<uint32_t>(value);
|
||||
// Encode least significant digit first, then reverse
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
output[i] = static_cast<char>('!' + (v % 85));
|
||||
v /= 85;
|
||||
}
|
||||
output[5] = '\0';
|
||||
}
|
||||
|
||||
/// Decode 5 base85 characters to int32
|
||||
inline bool base85_decode_int32(const char *input, int32_t &out) {
|
||||
uint8_t c0 = static_cast<uint8_t>(input[0] - '!');
|
||||
uint8_t c1 = static_cast<uint8_t>(input[1] - '!');
|
||||
uint8_t c2 = static_cast<uint8_t>(input[2] - '!');
|
||||
uint8_t c3 = static_cast<uint8_t>(input[3] - '!');
|
||||
uint8_t c4 = static_cast<uint8_t>(input[4] - '!');
|
||||
|
||||
// Each digit must be 0-84. Since uint8_t wraps, chars below '!' become > 84
|
||||
if (c0 > 84 || c1 > 84 || c2 > 84 || c3 > 84 || c4 > 84)
|
||||
return false;
|
||||
|
||||
// 85^4 = 52200625, 85^3 = 614125, 85^2 = 7225, 85^1 = 85
|
||||
out = static_cast<int32_t>(c0 * 52200625u + c1 * 614125u + c2 * 7225u + c3 * 85u + c4);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Decode base85 string directly into vector (no intermediate buffer)
|
||||
bool base85_decode_int32_vector(const std::string &base85, std::vector<int32_t> &out) {
|
||||
size_t len = base85.size();
|
||||
if (len % 5 != 0)
|
||||
return false;
|
||||
|
||||
out.clear();
|
||||
const char *ptr = base85.data();
|
||||
const char *end = ptr + len;
|
||||
|
||||
while (ptr < end) {
|
||||
int32_t value;
|
||||
if (!base85_decode_int32(ptr, value))
|
||||
return false;
|
||||
out.push_back(value);
|
||||
ptr += 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Colors
|
||||
|
||||
float gamma_correct(float value, float gamma) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
@@ -18,6 +21,7 @@
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <Esp.h>
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
@@ -568,6 +572,53 @@ std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt,
|
||||
/// sprintf-like function returning std::string.
|
||||
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
|
||||
// Format strings must be wrapped with PSTR() macro
|
||||
/// Safely append formatted string to buffer, returning new position (capped at size).
|
||||
/// @param buf Output buffer
|
||||
/// @param size Total buffer size
|
||||
/// @param pos Current position in buffer
|
||||
/// @param fmt Format string (must be in PROGMEM on ESP8266)
|
||||
/// @return New position after appending (capped at size on overflow)
|
||||
inline size_t buf_append_printf_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) {
|
||||
if (pos >= size) {
|
||||
return size;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int written = vsnprintf_P(buf + pos, size - pos, fmt, args);
|
||||
va_end(args);
|
||||
if (written < 0) {
|
||||
return pos; // encoding error
|
||||
}
|
||||
return std::min(pos + static_cast<size_t>(written), size);
|
||||
}
|
||||
#define buf_append_printf(buf, size, pos, fmt, ...) buf_append_printf_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__)
|
||||
#else
|
||||
/// Safely append formatted string to buffer, returning new position (capped at size).
|
||||
/// Handles snprintf edge cases: negative returns (encoding errors) and truncation.
|
||||
/// @param buf Output buffer
|
||||
/// @param size Total buffer size
|
||||
/// @param pos Current position in buffer
|
||||
/// @param fmt printf-style format string
|
||||
/// @return New position after appending (capped at size on overflow)
|
||||
__attribute__((format(printf, 4, 5))) inline size_t buf_append_printf(char *buf, size_t size, size_t pos,
|
||||
const char *fmt, ...) {
|
||||
if (pos >= size) {
|
||||
return size;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int written = vsnprintf(buf + pos, size - pos, fmt, args);
|
||||
va_end(args);
|
||||
if (written < 0) {
|
||||
return pos; // encoding error
|
||||
}
|
||||
return std::min(pos + static_cast<size_t>(written), size);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Concatenate a name with a separator and suffix using an efficient stack-based approach.
|
||||
/// This avoids multiple heap allocations during string construction.
|
||||
/// Maximum name length supported is 120 characters for friendly names.
|
||||
@@ -1086,6 +1137,14 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string);
|
||||
size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len);
|
||||
size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len);
|
||||
|
||||
/// Size of buffer needed for base85 encoded int32 (5 chars + null terminator)
|
||||
static constexpr size_t BASE85_INT32_ENCODED_SIZE = 6;
|
||||
|
||||
void base85_encode_int32(int32_t value, std::span<char, BASE85_INT32_ENCODED_SIZE> output);
|
||||
|
||||
bool base85_decode_int32(const char *input, int32_t &out);
|
||||
bool base85_decode_int32_vector(const std::string &base85, std::vector<int32_t> &out);
|
||||
|
||||
///@}
|
||||
|
||||
/// @name Colors
|
||||
|
||||
@@ -400,6 +400,8 @@ def run_ota_impl_(
|
||||
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
remote_host,
|
||||
)
|
||||
if not CORE.dashboard:
|
||||
_LOGGER.error("(If you know the IP, try --device <IP>)")
|
||||
_LOGGER.error(
|
||||
"(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi/#manual-ips)"
|
||||
|
||||
@@ -19,7 +19,7 @@ ruamel.yaml==0.19.1 # dashboard_import
|
||||
ruamel.yaml.clib==0.2.15 # dashboard_import
|
||||
esphome-glyphsets==0.2.0
|
||||
pillow==11.3.0
|
||||
resvg-py==0.2.5
|
||||
resvg-py==0.2.6
|
||||
freetype-py==2.5.1
|
||||
jinja2==3.1.6
|
||||
bleak==2.1.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pylint==4.0.4
|
||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.14.11 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.14.12 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
|
||||
@@ -90,7 +90,10 @@ class Platform(StrEnum):
|
||||
ESP32_S2_IDF = "esp32-s2-idf"
|
||||
ESP32_S3_IDF = "esp32-s3-idf"
|
||||
BK72XX_ARD = "bk72xx-ard" # LibreTiny BK7231N
|
||||
RTL87XX_ARD = "rtl87xx-ard" # LibreTiny RTL8720x
|
||||
LN882X_ARD = "ln882x-ard" # LibreTiny LN882x
|
||||
RP2040_ARD = "rp2040-ard" # Raspberry Pi Pico
|
||||
NRF52_ZEPHYR = "nrf52-adafruit" # Nordic nRF52 (Zephyr)
|
||||
|
||||
|
||||
# Memory impact analysis constants
|
||||
@@ -110,7 +113,7 @@ PLATFORM_SPECIFIC_COMPONENTS = frozenset(
|
||||
"rtl87xx", # Realtek RTL87xx platform implementation (uses LibreTiny)
|
||||
"ln882x", # Winner Micro LN882x platform implementation (uses LibreTiny)
|
||||
"host", # Host platform (for testing on development machine)
|
||||
"nrf52", # Nordic nRF52 platform implementation
|
||||
"nrf52", # Nordic nRF52 platform implementation (uses Zephyr)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -122,8 +125,9 @@ PLATFORM_SPECIFIC_COMPONENTS = frozenset(
|
||||
# fastest build times, most sensitive to code size changes
|
||||
# 3. ESP32 IDF - Primary ESP32 platform, most representative of modern ESPHome
|
||||
# 4-6. Other ESP32 variants - Less commonly used but still supported
|
||||
# 7. BK72XX - LibreTiny platform (good for detecting LibreTiny-specific changes)
|
||||
# 8. RP2040 - Raspberry Pi Pico platform
|
||||
# 7-9. LibreTiny platforms (BK72XX, RTL87XX, LN882X) - good for detecting LibreTiny-specific changes
|
||||
# 10. RP2040 - Raspberry Pi Pico platform
|
||||
# 11. nRF52 - Nordic nRF52 with Zephyr (good for detecting Zephyr-specific changes)
|
||||
MEMORY_IMPACT_PLATFORM_PREFERENCE = [
|
||||
Platform.ESP32_C6_IDF, # ESP32-C6 IDF (newest, supports Thread/Zigbee)
|
||||
Platform.ESP8266_ARD, # ESP8266 Arduino (most memory constrained, fastest builds)
|
||||
@@ -132,7 +136,10 @@ MEMORY_IMPACT_PLATFORM_PREFERENCE = [
|
||||
Platform.ESP32_S2_IDF, # ESP32-S2 IDF
|
||||
Platform.ESP32_S3_IDF, # ESP32-S3 IDF
|
||||
Platform.BK72XX_ARD, # LibreTiny BK7231N
|
||||
Platform.RTL87XX_ARD, # LibreTiny RTL8720x
|
||||
Platform.LN882X_ARD, # LibreTiny LN882x
|
||||
Platform.RP2040_ARD, # Raspberry Pi Pico
|
||||
Platform.NRF52_ZEPHYR, # Nordic nRF52 (Zephyr)
|
||||
]
|
||||
|
||||
|
||||
@@ -411,6 +418,8 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None:
|
||||
- wifi_component_esp8266.cpp, *_esp8266.h -> ESP8266_ARD
|
||||
- *_esp32*.cpp -> ESP32 IDF (generic)
|
||||
- *_libretiny.cpp, *_bk72*.* -> BK72XX (LibreTiny)
|
||||
- *_rtl87*.* -> RTL87XX (LibreTiny Realtek)
|
||||
- *_ln882*.* -> LN882X (LibreTiny Lightning)
|
||||
- *_pico.cpp, *_rp2040.* -> RP2040_ARD
|
||||
|
||||
Args:
|
||||
@@ -444,7 +453,12 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None:
|
||||
if "esp32" in filename_lower:
|
||||
return Platform.ESP32_IDF
|
||||
|
||||
# LibreTiny (via 'libretiny' pattern or BK72xx-specific files)
|
||||
# LibreTiny platforms (check specific variants before generic libretiny)
|
||||
# Check specific variants first to handle paths like libretiny/wifi_rtl87xx.cpp
|
||||
if "rtl87" in filename_lower:
|
||||
return Platform.RTL87XX_ARD
|
||||
if "ln882" in filename_lower:
|
||||
return Platform.LN882X_ARD
|
||||
if "libretiny" in filename_lower or "bk72" in filename_lower:
|
||||
return Platform.BK72XX_ARD
|
||||
|
||||
@@ -452,6 +466,10 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None:
|
||||
if "pico" in filename_lower or "rp2040" in filename_lower:
|
||||
return Platform.RP2040_ARD
|
||||
|
||||
# nRF52 / Zephyr
|
||||
if "nrf52" in filename_lower or "zephyr" in filename_lower:
|
||||
return Platform.NRF52_ZEPHYR
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ sensor:
|
||||
- platform: debug
|
||||
free:
|
||||
name: "Heap Free"
|
||||
block:
|
||||
name: "Heap Block"
|
||||
loop_time:
|
||||
name: "Loop Time"
|
||||
cpu_frequency:
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
|
||||
@@ -2,3 +2,10 @@
|
||||
|
||||
esp32:
|
||||
cpu_frequency: 240MHz
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
fragmentation:
|
||||
name: "Heap Fragmentation"
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
|
||||
@@ -9,5 +9,9 @@ sensor:
|
||||
name: "Heap Free"
|
||||
psram:
|
||||
name: "Free PSRAM"
|
||||
fragmentation:
|
||||
name: "Heap Fragmentation"
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
|
||||
psram:
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
fragmentation:
|
||||
name: "Heap Fragmentation"
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
fragmentation:
|
||||
name: "Heap Fragmentation"
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
|
||||
6
tests/components/debug/test.rtl87xx-ard.yaml
Normal file
6
tests/components/debug/test.rtl87xx-ard.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
sensor:
|
||||
- platform: debug
|
||||
min_free:
|
||||
name: "Heap Min Free"
|
||||
@@ -1472,6 +1472,24 @@ def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) ->
|
||||
determine_jobs.Platform.BK72XX_ARD,
|
||||
),
|
||||
("esphome/components/ble/ble_bk72xx.cpp", determine_jobs.Platform.BK72XX_ARD),
|
||||
# RTL87xx (LibreTiny Realtek) detection
|
||||
(
|
||||
"tests/components/logger/test.rtl87xx-ard.yaml",
|
||||
determine_jobs.Platform.RTL87XX_ARD,
|
||||
),
|
||||
(
|
||||
"esphome/components/libretiny/wifi_rtl87xx.cpp",
|
||||
determine_jobs.Platform.RTL87XX_ARD,
|
||||
),
|
||||
# LN882x (LibreTiny Lightning) detection
|
||||
(
|
||||
"tests/components/logger/test.ln882x-ard.yaml",
|
||||
determine_jobs.Platform.LN882X_ARD,
|
||||
),
|
||||
(
|
||||
"esphome/components/libretiny/wifi_ln882x.cpp",
|
||||
determine_jobs.Platform.LN882X_ARD,
|
||||
),
|
||||
# RP2040 / Raspberry Pi Pico detection
|
||||
("esphome/components/gpio/gpio_rp2040.cpp", determine_jobs.Platform.RP2040_ARD),
|
||||
("esphome/components/wifi/wifi_rp2040.cpp", determine_jobs.Platform.RP2040_ARD),
|
||||
@@ -1481,6 +1499,23 @@ def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) ->
|
||||
"tests/components/rp2040/test.rp2040-ard.yaml",
|
||||
determine_jobs.Platform.RP2040_ARD,
|
||||
),
|
||||
# nRF52 / Zephyr detection
|
||||
(
|
||||
"tests/components/logger/test.nrf52-adafruit.yaml",
|
||||
determine_jobs.Platform.NRF52_ZEPHYR,
|
||||
),
|
||||
(
|
||||
"esphome/components/nrf52/gpio.cpp",
|
||||
determine_jobs.Platform.NRF52_ZEPHYR,
|
||||
),
|
||||
(
|
||||
"esphome/components/zephyr/core.cpp",
|
||||
determine_jobs.Platform.NRF52_ZEPHYR,
|
||||
),
|
||||
(
|
||||
"esphome/components/zephyr_ble_server/ble_server.cpp",
|
||||
determine_jobs.Platform.NRF52_ZEPHYR,
|
||||
),
|
||||
# No platform hint (generic files)
|
||||
("esphome/components/wifi/wifi.cpp", None),
|
||||
("esphome/components/sensor/sensor.h", None),
|
||||
@@ -1501,11 +1536,19 @@ def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) ->
|
||||
"esp32_in_name",
|
||||
"libretiny",
|
||||
"bk72xx",
|
||||
"rtl87xx_test_yaml",
|
||||
"rtl87xx_wifi",
|
||||
"ln882x_test_yaml",
|
||||
"ln882x_wifi",
|
||||
"rp2040_gpio",
|
||||
"rp2040_wifi",
|
||||
"pico_i2c",
|
||||
"pico_spi",
|
||||
"rp2040_test_yaml",
|
||||
"nrf52_test_yaml",
|
||||
"nrf52_gpio",
|
||||
"zephyr_core",
|
||||
"zephyr_ble_server",
|
||||
"generic_wifi_no_hint",
|
||||
"generic_sensor_no_hint",
|
||||
"core_helpers_no_hint",
|
||||
@@ -1532,6 +1575,11 @@ def test_detect_platform_hint_from_filename(
|
||||
("file_ESP8266.cpp", determine_jobs.Platform.ESP8266_ARD),
|
||||
# ESP32 with different cases
|
||||
("file_ESP32.cpp", determine_jobs.Platform.ESP32_IDF),
|
||||
# nRF52/Zephyr with different cases
|
||||
("file_NRF52.cpp", determine_jobs.Platform.NRF52_ZEPHYR),
|
||||
("file_Nrf52.cpp", determine_jobs.Platform.NRF52_ZEPHYR),
|
||||
("file_ZEPHYR.cpp", determine_jobs.Platform.NRF52_ZEPHYR),
|
||||
("file_Zephyr.cpp", determine_jobs.Platform.NRF52_ZEPHYR),
|
||||
],
|
||||
ids=[
|
||||
"rp2040_uppercase",
|
||||
@@ -1540,6 +1588,10 @@ def test_detect_platform_hint_from_filename(
|
||||
"pico_titlecase",
|
||||
"esp8266_uppercase",
|
||||
"esp32_uppercase",
|
||||
"nrf52_uppercase",
|
||||
"nrf52_mixedcase",
|
||||
"zephyr_uppercase",
|
||||
"zephyr_titlecase",
|
||||
],
|
||||
)
|
||||
def test_detect_platform_hint_from_filename_case_insensitive(
|
||||
|
||||
Reference in New Issue
Block a user