mirror of
https://github.com/esphome/esphome.git
synced 2026-01-19 17:46:23 -07:00
Compare commits
115 Commits
text_fix_h
...
action_cal
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b22a8b00bf | ||
|
|
297f05d600 | ||
|
|
54fc10714d | ||
|
|
889886909b | ||
|
|
655e2b43cb | ||
|
|
81e639a6ba | ||
|
|
f9ffd134df | ||
|
|
c50bf45496 | ||
|
|
9f9341a700 | ||
|
|
71d532a349 | ||
|
|
61a89a97d7 | ||
|
|
0c3433d056 | ||
|
|
7e1cda8f9f | ||
|
|
7f0e4eaa84 | ||
|
|
8cccfa5369 | ||
|
|
7ea6bcef88 | ||
|
|
353daa97d0 | ||
|
|
6c68ebe86e | ||
|
|
29cef3bc5d | ||
|
|
83eebdf15d | ||
|
|
595217786c | ||
|
|
912f94d1e8 | ||
|
|
ea8ae2ae60 | ||
|
|
e1aac7601d | ||
|
|
f1b11b1855 | ||
|
|
23f9f70b71 | ||
|
|
eeeae53f76 | ||
|
|
45c0796e40 | ||
|
|
38e2e4a56d | ||
|
|
52132ea3bc | ||
|
|
ace3ff2170 | ||
|
|
26e90b4ca6 | ||
|
|
684790c2ab | ||
|
|
6a3737bac3 | ||
|
|
723ca57617 | ||
|
|
909bd1074a | ||
|
|
68064dc974 | ||
|
|
742d724e65 | ||
|
|
5ae46a4369 | ||
|
|
a1395af763 | ||
|
|
6222fae907 | ||
|
|
e34532f283 | ||
|
|
f2eb61a767 | ||
|
|
5725a4840e | ||
|
|
de82f96ccb | ||
|
|
6c981d8b71 | ||
|
|
c03faf2d9a | ||
|
|
da7680f7d9 | ||
|
|
cea2878b55 | ||
|
|
e0ff7fdaa1 | ||
|
|
3c9b300c46 | ||
|
|
32f90b2855 | ||
|
|
2fb7c0d453 | ||
|
|
7935fba4b1 | ||
|
|
ab32b93928 | ||
|
|
3d54ccac65 | ||
|
|
c40f44f4bd | ||
|
|
62cb08c3dc | ||
|
|
7576e032f8 | ||
|
|
cd43b4114e | ||
|
|
2c165e4817 | ||
|
|
5afe4b7b12 | ||
|
|
dcb8c994cc | ||
|
|
012a1e2afd | ||
|
|
d4969f581a | ||
|
|
40f108116b | ||
|
|
52459d1bc7 | ||
|
|
325c938074 | ||
|
|
423a617b15 | ||
|
|
eb5c4f34e2 | ||
|
|
c9ab4ca018 | ||
|
|
da0b01f4d0 | ||
|
|
e301b8d0e0 | ||
|
|
738678e87b | ||
|
|
0ce3ac438b | ||
|
|
afa4fe9820 | ||
|
|
a66df9ab0f | ||
|
|
1339f3e77e | ||
|
|
e29523e248 | ||
|
|
44eac36e05 | ||
|
|
050e9b0d4a | ||
|
|
25ac89e9b5 | ||
|
|
d86d1f9f52 | ||
|
|
fd19280df9 | ||
|
|
b7dbda497a | ||
|
|
815543b77e | ||
|
|
0948e0359f | ||
|
|
2830c7dab8 | ||
|
|
a03c13f304 | ||
|
|
ef64226ed0 | ||
|
|
ed39a130a8 | ||
|
|
21687a1f58 | ||
|
|
bf75f77eee | ||
|
|
39526e5360 | ||
|
|
8e40a55d5d | ||
|
|
20927674da | ||
|
|
8464307a43 | ||
|
|
546cdbde0d | ||
|
|
ada4e6d5e9 | ||
|
|
d6554702d8 | ||
|
|
b083c33857 | ||
|
|
f8309b007c | ||
|
|
ac672e4b8f | ||
|
|
c387c03944 | ||
|
|
fb47bfe92a | ||
|
|
5b9be7c169 | ||
|
|
6d1f6a1084 | ||
|
|
f9ed2aa17f | ||
|
|
35118da606 | ||
|
|
498477c5a2 | ||
|
|
3a84e4a0b4 | ||
|
|
4391457a96 | ||
|
|
68b4bc9d9e | ||
|
|
b052c9f562 | ||
|
|
a19597626b |
@@ -293,6 +293,12 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Configuration Design:** Aim for simplicity with sensible defaults, while allowing for advanced customization.
|
||||
* **Embedded Systems Optimization:** ESPHome targets resource-constrained microcontrollers. Be mindful of flash size and RAM usage.
|
||||
|
||||
**Why Heap Allocation Matters:**
|
||||
|
||||
ESP devices run for months with small heaps shared between Wi-Fi, BLE, LWIP, and application code. Over time, repeated allocations of different sizes fragment the heap. Failures happen when the largest contiguous block shrinks, even if total free heap is still large. We have seen field crashes caused by this.
|
||||
|
||||
**Heap allocation after `setup()` should be avoided unless absolutely unavoidable.** Every allocation/deallocation cycle contributes to fragmentation. ESPHome treats runtime heap allocation as a long-term reliability bug, not a performance issue. Helpers that hide allocation (`std::string`, `std::to_string`, string-returning helpers) are being deprecated and replaced with buffer and view based APIs.
|
||||
|
||||
**STL Container Guidelines:**
|
||||
|
||||
ESPHome runs on embedded systems with limited resources. Choose containers carefully:
|
||||
@@ -322,15 +328,15 @@ This document provides essential context for AI models interacting with this pro
|
||||
std::array<uint8_t, 256> buffer;
|
||||
```
|
||||
|
||||
2. **Compile-time-known fixed sizes with vector-like API:** Use `StaticVector` from `esphome/core/helpers.h` for fixed-size stack allocation with `push_back()` interface.
|
||||
2. **Compile-time-known fixed sizes with vector-like API:** Use `StaticVector` from `esphome/core/helpers.h` for compile-time fixed size with `push_back()` interface (no dynamic allocation).
|
||||
```cpp
|
||||
// Bad - generates STL realloc code (_M_realloc_insert)
|
||||
std::vector<ServiceRecord> services;
|
||||
services.reserve(5); // Still includes reallocation machinery
|
||||
|
||||
// Good - compile-time fixed size, stack allocated, no reallocation machinery
|
||||
StaticVector<ServiceRecord, MAX_SERVICES> services; // Allocates all MAX_SERVICES on stack
|
||||
services.push_back(record1); // Tracks count but all slots allocated
|
||||
// Good - compile-time fixed size, no dynamic allocation
|
||||
StaticVector<ServiceRecord, MAX_SERVICES> services;
|
||||
services.push_back(record1);
|
||||
```
|
||||
Use `cg.add_define("MAX_SERVICES", count)` to set the size from Python configuration.
|
||||
Like `std::array` but with vector-like API (`push_back()`, `size()`) and no STL reallocation code.
|
||||
@@ -372,22 +378,21 @@ This document provides essential context for AI models interacting with this pro
|
||||
```
|
||||
Linear search on small datasets (1-16 elements) is often faster than hashing/tree overhead, but this depends on lookup frequency and access patterns. For frequent lookups in hot code paths, the O(1) vs O(n) complexity difference may still matter even for small datasets. `std::vector` with simple structs is usually fine—it's the heavy containers (`map`, `set`, `unordered_map`) that should be avoided for small datasets unless profiling shows otherwise.
|
||||
|
||||
5. **Detection:** Look for these patterns in compiler output:
|
||||
5. **Avoid `std::deque`:** It allocates in 512-byte blocks regardless of element size, guaranteeing at least 512 bytes of RAM usage immediately. This is a major source of crashes on memory-constrained devices.
|
||||
|
||||
6. **Detection:** Look for these patterns in compiler output:
|
||||
- Large code sections with STL symbols (vector, map, set)
|
||||
- `alloc`, `realloc`, `dealloc` in symbol names
|
||||
- `_M_realloc_insert`, `_M_default_append` (vector reallocation)
|
||||
- Red-black tree code (`rb_tree`, `_Rb_tree`)
|
||||
- Hash table infrastructure (`unordered_map`, `hash`)
|
||||
|
||||
**When to optimize:**
|
||||
**Prioritize optimization effort for:**
|
||||
- Core components (API, network, logger)
|
||||
- Widely-used components (mdns, wifi, ble)
|
||||
- Components causing flash size complaints
|
||||
|
||||
**When not to optimize:**
|
||||
- Single-use niche components
|
||||
- Code where readability matters more than bytes
|
||||
- Already using appropriate containers
|
||||
Note: Avoiding heap allocation after `setup()` is always required regardless of component type. The prioritization above is about the effort spent on container optimization (e.g., migrating from `std::vector` to `StaticVector`).
|
||||
|
||||
* **State Management:** Use `CORE.data` for component state that needs to persist during configuration generation. Avoid module-level mutable globals.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
191a0e6ab5842d153dd77a2023bc5742f9d4333c334de8d81b57f2b8d4d4b65e
|
||||
d272a88e8ca28ae9340a9a03295a566432a52cb696501908f57764475bf7ca65
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.10
|
||||
rev: v0.14.11
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -249,6 +249,7 @@ esphome/components/ina260/* @mreditor97
|
||||
esphome/components/ina2xx_base/* @latonita
|
||||
esphome/components/ina2xx_i2c/* @latonita
|
||||
esphome/components/ina2xx_spi/* @latonita
|
||||
esphome/components/infrared/* @kbx81
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate/* @jesserockz @JosipKuci
|
||||
esphome/components/integration/* @OttoWinter
|
||||
|
||||
@@ -62,6 +62,9 @@ from esphome.util import (
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Maximum buffer size for serial log reading to prevent unbounded memory growth
|
||||
SERIAL_BUFFER_MAX_SIZE = 65536
|
||||
|
||||
# Special non-component keys that appear in configs
|
||||
_NON_COMPONENT_KEYS = frozenset(
|
||||
{
|
||||
@@ -440,11 +443,15 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||
if not chunk:
|
||||
continue
|
||||
time_ = datetime.now()
|
||||
nanoseconds = time_.microsecond // 1000
|
||||
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
|
||||
milliseconds = time_.microsecond // 1000
|
||||
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{milliseconds:03}]"
|
||||
|
||||
# Add to buffer and process complete lines
|
||||
# Limit buffer size to prevent unbounded memory growth
|
||||
# if device sends data without newlines
|
||||
buffer += chunk
|
||||
if len(buffer) > SERIAL_BUFFER_MAX_SIZE:
|
||||
buffer = buffer[-SERIAL_BUFFER_MAX_SIZE:]
|
||||
while b"\n" in buffer:
|
||||
raw_line, buffer = buffer.split(b"\n", 1)
|
||||
line = raw_line.replace(b"\r", b"").decode(
|
||||
@@ -788,13 +795,6 @@ def command_vscode(args: ArgsProtocol) -> int | None:
|
||||
|
||||
|
||||
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
# Set memory analysis options in config
|
||||
if args.analyze_memory:
|
||||
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
|
||||
|
||||
if args.memory_report:
|
||||
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
|
||||
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
@@ -1032,6 +1032,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
idedata.objdump_path,
|
||||
idedata.readelf_path,
|
||||
external_components,
|
||||
idedata=idedata,
|
||||
)
|
||||
analyzer.analyze()
|
||||
|
||||
@@ -1278,17 +1279,6 @@ def parse_args(argv):
|
||||
help="Only generate source code, do not compile.",
|
||||
action="store_true",
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"--analyze-memory",
|
||||
help="Analyze and display memory usage by component after compilation.",
|
||||
action="store_true",
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"--memory-report",
|
||||
help="Save memory analysis report to a file (supports .json or .txt).",
|
||||
type=str,
|
||||
metavar="FILE",
|
||||
)
|
||||
|
||||
parser_upload = subparsers.add_parser(
|
||||
"upload",
|
||||
|
||||
@@ -22,6 +22,7 @@ from .helpers import (
|
||||
map_section_name,
|
||||
parse_symbol_line,
|
||||
)
|
||||
from .toolchain import find_tool, run_tool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.platformio_api import IDEData
|
||||
@@ -53,6 +54,9 @@ _NAMESPACE_STD = "std::"
|
||||
# Type alias for symbol information: (symbol_name, size, component)
|
||||
SymbolInfoType = tuple[str, int, str]
|
||||
|
||||
# RAM sections - symbols in these sections consume RAM
|
||||
RAM_SECTIONS = frozenset([".data", ".bss"])
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemorySection:
|
||||
@@ -60,7 +64,20 @@ class MemorySection:
|
||||
|
||||
name: str
|
||||
symbols: list[SymbolInfoType] = field(default_factory=list)
|
||||
total_size: int = 0
|
||||
total_size: int = 0 # Actual section size from ELF headers
|
||||
symbol_size: int = 0 # Sum of symbol sizes (may be less than total_size)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SDKSymbol:
|
||||
"""Represents a symbol from an SDK library that's not in the ELF symbol table."""
|
||||
|
||||
name: str
|
||||
size: int
|
||||
library: str # Name of the .a file (e.g., "libpp.a")
|
||||
section: str # ".bss" or ".data"
|
||||
is_local: bool # True if static/local symbol (lowercase in nm output)
|
||||
demangled: str = "" # Demangled name (populated after analysis)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -118,6 +135,10 @@ class MemoryAnalyzer:
|
||||
self.objdump_path = objdump_path or "objdump"
|
||||
self.readelf_path = readelf_path or "readelf"
|
||||
self.external_components = external_components or set()
|
||||
self._idedata = idedata
|
||||
|
||||
# Derive nm path from objdump path using shared toolchain utility
|
||||
self.nm_path = find_tool("nm", self.objdump_path)
|
||||
|
||||
self.sections: dict[str, MemorySection] = {}
|
||||
self.components: dict[str, ComponentMemory] = defaultdict(
|
||||
@@ -128,15 +149,25 @@ class MemoryAnalyzer:
|
||||
self._esphome_core_symbols: list[
|
||||
tuple[str, str, int]
|
||||
] = [] # Track core symbols
|
||||
self._component_symbols: dict[str, list[tuple[str, str, int]]] = defaultdict(
|
||||
# Track symbols for all components: (symbol_name, demangled, size, section)
|
||||
self._component_symbols: dict[str, list[tuple[str, str, int, str]]] = (
|
||||
defaultdict(list)
|
||||
)
|
||||
# Track RAM symbols separately for detailed analysis: (symbol_name, demangled, size, section)
|
||||
self._ram_symbols: dict[str, list[tuple[str, str, int, str]]] = defaultdict(
|
||||
list
|
||||
) # Track symbols for all components
|
||||
)
|
||||
# Track ELF symbol names for SDK cross-reference
|
||||
self._elf_symbol_names: set[str] = set()
|
||||
# SDK symbols not in ELF (static/local symbols from closed-source libs)
|
||||
self._sdk_symbols: list[SDKSymbol] = []
|
||||
|
||||
def analyze(self) -> dict[str, ComponentMemory]:
|
||||
"""Analyze the ELF file and return component memory usage."""
|
||||
self._parse_sections()
|
||||
self._parse_symbols()
|
||||
self._categorize_symbols()
|
||||
self._analyze_sdk_libraries()
|
||||
return dict(self.components)
|
||||
|
||||
def _parse_sections(self) -> None:
|
||||
@@ -190,6 +221,8 @@ class MemoryAnalyzer:
|
||||
continue
|
||||
|
||||
self.sections[section].symbols.append((name, size, ""))
|
||||
self.sections[section].symbol_size += size
|
||||
self._elf_symbol_names.add(name)
|
||||
seen_addresses.add(address)
|
||||
|
||||
def _categorize_symbols(self) -> None:
|
||||
@@ -233,8 +266,13 @@ class MemoryAnalyzer:
|
||||
if size > 0:
|
||||
demangled = self._demangle_symbol(symbol_name)
|
||||
self._component_symbols[component].append(
|
||||
(symbol_name, demangled, size)
|
||||
(symbol_name, demangled, size, section_name)
|
||||
)
|
||||
# Track RAM symbols separately for detailed RAM analysis
|
||||
if section_name in RAM_SECTIONS:
|
||||
self._ram_symbols[component].append(
|
||||
(symbol_name, demangled, size, section_name)
|
||||
)
|
||||
|
||||
def _identify_component(self, symbol_name: str) -> str:
|
||||
"""Identify which component a symbol belongs to."""
|
||||
@@ -328,6 +366,247 @@ class MemoryAnalyzer:
|
||||
|
||||
return "Other Core"
|
||||
|
||||
def get_unattributed_ram(self) -> tuple[int, int, int]:
|
||||
"""Get unattributed RAM sizes (SDK/framework overhead).
|
||||
|
||||
Returns:
|
||||
Tuple of (unattributed_bss, unattributed_data, total_unattributed)
|
||||
These are bytes in RAM sections that have no corresponding symbols.
|
||||
"""
|
||||
bss_section = self.sections.get(".bss")
|
||||
data_section = self.sections.get(".data")
|
||||
|
||||
unattributed_bss = 0
|
||||
unattributed_data = 0
|
||||
|
||||
if bss_section:
|
||||
unattributed_bss = max(0, bss_section.total_size - bss_section.symbol_size)
|
||||
if data_section:
|
||||
unattributed_data = max(
|
||||
0, data_section.total_size - data_section.symbol_size
|
||||
)
|
||||
|
||||
return unattributed_bss, unattributed_data, unattributed_bss + unattributed_data
|
||||
|
||||
def _find_sdk_library_dirs(self) -> list[Path]:
|
||||
"""Find SDK library directories based on platform.
|
||||
|
||||
Returns:
|
||||
List of paths to SDK library directories containing .a files.
|
||||
"""
|
||||
sdk_dirs: list[Path] = []
|
||||
|
||||
if self._idedata is None:
|
||||
return sdk_dirs
|
||||
|
||||
# Get the CC path to determine the framework location
|
||||
cc_path = getattr(self._idedata, "cc_path", None)
|
||||
if not cc_path:
|
||||
return sdk_dirs
|
||||
|
||||
cc_path = Path(cc_path)
|
||||
|
||||
# For ESP8266 Arduino framework
|
||||
# CC is like: ~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc
|
||||
# SDK libs are in: ~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib/
|
||||
if "xtensa-lx106" in str(cc_path):
|
||||
platformio_dir = cc_path.parent.parent.parent
|
||||
esp8266_sdk = (
|
||||
platformio_dir
|
||||
/ "framework-arduinoespressif8266"
|
||||
/ "tools"
|
||||
/ "sdk"
|
||||
/ "lib"
|
||||
)
|
||||
if esp8266_sdk.exists():
|
||||
sdk_dirs.append(esp8266_sdk)
|
||||
# Also check for NONOSDK subdirectories (closed-source libs)
|
||||
sdk_dirs.extend(
|
||||
subdir
|
||||
for subdir in esp8266_sdk.iterdir()
|
||||
if subdir.is_dir() and subdir.name.startswith("NONOSDK")
|
||||
)
|
||||
|
||||
# For ESP32 IDF framework
|
||||
# CC is like: ~/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32-elf-gcc
|
||||
# or: ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-gcc
|
||||
elif "xtensa-esp" in str(cc_path) or "riscv32-esp" in str(cc_path):
|
||||
# Detect ESP32 variant from CC path or defines
|
||||
variant = self._detect_esp32_variant()
|
||||
if variant:
|
||||
platformio_dir = cc_path.parent.parent.parent
|
||||
espidf_dir = platformio_dir / "framework-espidf" / "components"
|
||||
if espidf_dir.exists():
|
||||
# Find all directories named after the variant that contain .a files
|
||||
# This handles various ESP-IDF library layouts:
|
||||
# - components/*/lib/<variant>/
|
||||
# - components/*/<variant>/
|
||||
# - components/*/lib/lib/<variant>/
|
||||
# - components/*/*/lib_*/<variant>/
|
||||
sdk_dirs.extend(
|
||||
variant_dir
|
||||
for variant_dir in espidf_dir.rglob(variant)
|
||||
if variant_dir.is_dir() and any(variant_dir.glob("*.a"))
|
||||
)
|
||||
|
||||
return sdk_dirs
|
||||
|
||||
def _detect_esp32_variant(self) -> str | None:
|
||||
"""Detect ESP32 variant from idedata defines.
|
||||
|
||||
Returns:
|
||||
Variant string like 'esp32', 'esp32s2', 'esp32c3', etc. or None.
|
||||
"""
|
||||
if self._idedata is None:
|
||||
return None
|
||||
|
||||
defines = getattr(self._idedata, "defines", [])
|
||||
if not defines:
|
||||
return None
|
||||
|
||||
# ESPHome always adds USE_ESP32_VARIANT_xxx defines
|
||||
variant_prefix = "USE_ESP32_VARIANT_"
|
||||
for define in defines:
|
||||
if define.startswith(variant_prefix):
|
||||
# Extract variant name and convert to lowercase
|
||||
# USE_ESP32_VARIANT_ESP32 -> esp32
|
||||
# USE_ESP32_VARIANT_ESP32S3 -> esp32s3
|
||||
return define[len(variant_prefix) :].lower()
|
||||
|
||||
return None
|
||||
|
||||
def _parse_sdk_library(
|
||||
self, lib_path: Path
|
||||
) -> tuple[list[tuple[str, int, str, bool]], set[str]]:
|
||||
"""Parse a single SDK library for symbols.
|
||||
|
||||
Args:
|
||||
lib_path: Path to the .a library file
|
||||
|
||||
Returns:
|
||||
Tuple of:
|
||||
- List of BSS/DATA symbols: (symbol_name, size, section, is_local)
|
||||
- Set of global BSS/DATA symbol names (for checking if RAM is linked)
|
||||
"""
|
||||
ram_symbols: list[tuple[str, int, str, bool]] = []
|
||||
global_ram_symbols: set[str] = set()
|
||||
|
||||
result = run_tool([self.nm_path, "--size-sort", str(lib_path)], timeout=10)
|
||||
if result is None:
|
||||
return ram_symbols, global_ram_symbols
|
||||
|
||||
for line in result.stdout.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
|
||||
try:
|
||||
size = int(parts[0], 16)
|
||||
sym_type = parts[1]
|
||||
name = parts[2]
|
||||
|
||||
# Only collect BSS (b/B) and DATA (d/D) for RAM analysis
|
||||
if sym_type in ("b", "B"):
|
||||
section = ".bss"
|
||||
is_local = sym_type == "b"
|
||||
ram_symbols.append((name, size, section, is_local))
|
||||
# Track global RAM symbols (B/D) for linking check
|
||||
if sym_type == "B":
|
||||
global_ram_symbols.add(name)
|
||||
elif sym_type in ("d", "D"):
|
||||
section = ".data"
|
||||
is_local = sym_type == "d"
|
||||
ram_symbols.append((name, size, section, is_local))
|
||||
if sym_type == "D":
|
||||
global_ram_symbols.add(name)
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
return ram_symbols, global_ram_symbols
|
||||
|
||||
def _analyze_sdk_libraries(self) -> None:
|
||||
"""Analyze SDK libraries to find symbols not in the ELF.
|
||||
|
||||
This finds static/local symbols from closed-source SDK libraries
|
||||
that consume RAM but don't appear in the final ELF symbol table.
|
||||
Only includes symbols from libraries that have RAM actually linked
|
||||
(at least one global BSS/DATA symbol in the ELF).
|
||||
"""
|
||||
sdk_dirs = self._find_sdk_library_dirs()
|
||||
if not sdk_dirs:
|
||||
_LOGGER.debug("No SDK library directories found")
|
||||
return
|
||||
|
||||
_LOGGER.debug("Analyzing SDK libraries in %d directories", len(sdk_dirs))
|
||||
|
||||
# Track seen symbols to avoid duplicates from multiple SDK versions
|
||||
seen_symbols: set[str] = set()
|
||||
|
||||
for sdk_dir in sdk_dirs:
|
||||
for lib_path in sorted(sdk_dir.glob("*.a")):
|
||||
lib_name = lib_path.name
|
||||
ram_symbols, global_ram_symbols = self._parse_sdk_library(lib_path)
|
||||
|
||||
# Check if this library's RAM is actually linked by seeing if any
|
||||
# of its global BSS/DATA symbols appear in the ELF
|
||||
if not global_ram_symbols & self._elf_symbol_names:
|
||||
# No RAM from this library is in the ELF - skip it
|
||||
continue
|
||||
|
||||
for name, size, section, is_local in ram_symbols:
|
||||
# Skip if already in ELF or already seen from another lib
|
||||
if name in self._elf_symbol_names or name in seen_symbols:
|
||||
continue
|
||||
|
||||
# Only track symbols with non-zero size
|
||||
if size > 0:
|
||||
self._sdk_symbols.append(
|
||||
SDKSymbol(
|
||||
name=name,
|
||||
size=size,
|
||||
library=lib_name,
|
||||
section=section,
|
||||
is_local=is_local,
|
||||
)
|
||||
)
|
||||
seen_symbols.add(name)
|
||||
|
||||
# Demangle SDK symbols for better readability
|
||||
if self._sdk_symbols:
|
||||
sdk_names = [sym.name for sym in self._sdk_symbols]
|
||||
demangled_map = batch_demangle(sdk_names, objdump_path=self.objdump_path)
|
||||
for sym in self._sdk_symbols:
|
||||
sym.demangled = demangled_map.get(sym.name, sym.name)
|
||||
|
||||
# Sort by size descending for reporting
|
||||
self._sdk_symbols.sort(key=lambda s: s.size, reverse=True)
|
||||
|
||||
total_sdk_ram = sum(s.size for s in self._sdk_symbols)
|
||||
_LOGGER.debug(
|
||||
"Found %d SDK symbols not in ELF, totaling %d bytes",
|
||||
len(self._sdk_symbols),
|
||||
total_sdk_ram,
|
||||
)
|
||||
|
||||
def get_sdk_ram_symbols(self) -> list[SDKSymbol]:
|
||||
"""Get SDK symbols that consume RAM but aren't in the ELF symbol table.
|
||||
|
||||
Returns:
|
||||
List of SDKSymbol objects sorted by size descending.
|
||||
"""
|
||||
return self._sdk_symbols
|
||||
|
||||
def get_sdk_ram_by_library(self) -> dict[str, list[SDKSymbol]]:
|
||||
"""Get SDK RAM symbols grouped by library.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping library name to list of symbols.
|
||||
"""
|
||||
by_lib: dict[str, list[SDKSymbol]] = defaultdict(list)
|
||||
for sym in self._sdk_symbols:
|
||||
by_lib[sym.library].append(sym)
|
||||
return dict(by_lib)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .cli import main
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
"""CLI interface for memory analysis with report generation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from . import (
|
||||
_COMPONENT_API,
|
||||
_COMPONENT_CORE,
|
||||
_COMPONENT_PREFIX_ESPHOME,
|
||||
_COMPONENT_PREFIX_EXTERNAL,
|
||||
RAM_SECTIONS,
|
||||
MemoryAnalyzer,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ComponentMemory
|
||||
|
||||
|
||||
class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
"""Memory analyzer with CLI-specific report generation."""
|
||||
@@ -20,6 +27,8 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
SYMBOL_SIZE_THRESHOLD: int = (
|
||||
100 # Show symbols larger than this in detailed analysis
|
||||
)
|
||||
# Lower threshold for RAM symbols (RAM is more constrained)
|
||||
RAM_SYMBOL_SIZE_THRESHOLD: int = 24
|
||||
|
||||
# Column width constants
|
||||
COL_COMPONENT: int = 29
|
||||
@@ -84,6 +93,60 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
COL_CORE_PERCENT,
|
||||
)
|
||||
|
||||
def _add_section_header(self, lines: list[str], title: str) -> None:
|
||||
"""Add a section header with title centered between separator lines."""
|
||||
lines.append("")
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append(title.center(self.TABLE_WIDTH))
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append("")
|
||||
|
||||
def _add_top_consumers(
|
||||
self,
|
||||
lines: list[str],
|
||||
title: str,
|
||||
components: list[tuple[str, ComponentMemory]],
|
||||
get_size: Callable[[ComponentMemory], int],
|
||||
total: int,
|
||||
memory_type: str,
|
||||
limit: int = 25,
|
||||
) -> None:
|
||||
"""Add a formatted list of top memory consumers to the report.
|
||||
|
||||
Args:
|
||||
lines: List of report lines to append the output to.
|
||||
title: Section title to print before the list.
|
||||
components: Sequence of (name, ComponentMemory) tuples to analyze.
|
||||
get_size: Callable that takes a ComponentMemory and returns the
|
||||
size in bytes to use for ranking and display.
|
||||
total: Total size in bytes for computing percentage usage.
|
||||
memory_type: Label for the memory region (e.g., "flash" or "RAM").
|
||||
limit: Maximum number of components to include in the list.
|
||||
"""
|
||||
lines.append("")
|
||||
lines.append(f"{title}:")
|
||||
for i, (name, mem) in enumerate(components[:limit]):
|
||||
size = get_size(mem)
|
||||
if size > 0:
|
||||
percentage = (size / total * 100) if total > 0 else 0
|
||||
lines.append(
|
||||
f"{i + 1}. {name} ({size:,} B) - {percentage:.1f}% of analyzed {memory_type}"
|
||||
)
|
||||
|
||||
def _format_symbol_with_section(
|
||||
self, demangled: str, size: int, section: str | None = None
|
||||
) -> str:
|
||||
"""Format a symbol entry, optionally adding a RAM section label.
|
||||
|
||||
If section is one of the RAM sections (.data or .bss), a label like
|
||||
" [data]" or " [bss]" is appended. For non-RAM sections or when
|
||||
section is None, no section label is added.
|
||||
"""
|
||||
section_label = ""
|
||||
if section in RAM_SECTIONS:
|
||||
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
|
||||
return f"{demangled} ({size:,} B){section_label}"
|
||||
|
||||
def generate_report(self, detailed: bool = False) -> str:
|
||||
"""Generate a formatted memory report."""
|
||||
components = sorted(
|
||||
@@ -124,43 +187,70 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
f"{total_flash:>{self.COL_TOTAL_FLASH - 2},} B | {total_ram:>{self.COL_TOTAL_RAM - 2},} B"
|
||||
)
|
||||
|
||||
# Top consumers
|
||||
lines.append("")
|
||||
lines.append("Top Flash Consumers:")
|
||||
for i, (name, mem) in enumerate(components[:25]):
|
||||
if mem.flash_total > 0:
|
||||
percentage = (
|
||||
(mem.flash_total / total_flash * 100) if total_flash > 0 else 0
|
||||
)
|
||||
lines.append(
|
||||
f"{i + 1}. {name} ({mem.flash_total:,} B) - {percentage:.1f}% of analyzed flash"
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append("Top RAM Consumers:")
|
||||
ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True)
|
||||
for i, (name, mem) in enumerate(ram_components[:25]):
|
||||
if mem.ram_total > 0:
|
||||
percentage = (mem.ram_total / total_ram * 100) if total_ram > 0 else 0
|
||||
lines.append(
|
||||
f"{i + 1}. {name} ({mem.ram_total:,} B) - {percentage:.1f}% of analyzed RAM"
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
|
||||
# Show unattributed RAM (SDK/framework overhead)
|
||||
unattributed_bss, unattributed_data, unattributed_total = (
|
||||
self.get_unattributed_ram()
|
||||
)
|
||||
if unattributed_total > 0:
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"Unattributed RAM: {unattributed_total:,} B (SDK/framework overhead)"
|
||||
)
|
||||
if unattributed_bss > 0 and unattributed_data > 0:
|
||||
lines.append(
|
||||
f" .bss: {unattributed_bss:,} B | .data: {unattributed_data:,} B"
|
||||
)
|
||||
|
||||
# Show SDK symbol breakdown if available
|
||||
sdk_by_lib = self.get_sdk_ram_by_library()
|
||||
if sdk_by_lib:
|
||||
lines.append("")
|
||||
lines.append("SDK library breakdown (static symbols not in ELF):")
|
||||
# Sort libraries by total size
|
||||
lib_totals = [
|
||||
(lib, sum(s.size for s in syms), syms)
|
||||
for lib, syms in sdk_by_lib.items()
|
||||
]
|
||||
lib_totals.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
for lib_name, lib_total, syms in lib_totals:
|
||||
if lib_total == 0:
|
||||
continue
|
||||
lines.append(f" {lib_name}: {lib_total:,} B")
|
||||
# Show top symbols from this library
|
||||
for sym in sorted(syms, key=lambda s: s.size, reverse=True)[:3]:
|
||||
section_label = sym.section.lstrip(".")
|
||||
# Use demangled name (falls back to original if not demangled)
|
||||
display_name = sym.demangled or sym.name
|
||||
if len(display_name) > 50:
|
||||
display_name = f"{display_name[:47]}..."
|
||||
lines.append(
|
||||
f" {sym.size:>6,} B [{section_label}] {display_name}"
|
||||
)
|
||||
|
||||
# Top consumers
|
||||
self._add_top_consumers(
|
||||
lines,
|
||||
"Top Flash Consumers",
|
||||
components,
|
||||
lambda m: m.flash_total,
|
||||
total_flash,
|
||||
"flash",
|
||||
)
|
||||
|
||||
ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True)
|
||||
self._add_top_consumers(
|
||||
lines,
|
||||
"Top RAM Consumers",
|
||||
ram_components,
|
||||
lambda m: m.ram_total,
|
||||
total_ram,
|
||||
"RAM",
|
||||
)
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
|
||||
# Add ESPHome core detailed analysis if there are core symbols
|
||||
if self._esphome_core_symbols:
|
||||
lines.append("")
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append(
|
||||
f"{_COMPONENT_CORE} Detailed Analysis".center(self.TABLE_WIDTH)
|
||||
)
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append("")
|
||||
self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis")
|
||||
|
||||
# Group core symbols by subcategory
|
||||
core_subcategories: dict[str, list[tuple[str, str, int]]] = defaultdict(
|
||||
@@ -212,7 +302,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):"
|
||||
)
|
||||
for i, (symbol, demangled, size) in enumerate(large_core_symbols):
|
||||
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
|
||||
# Core symbols only track (symbol, demangled, size) without section info,
|
||||
# so we don't show section labels here
|
||||
lines.append(
|
||||
f"{i + 1}. {self._format_symbol_with_section(demangled, size)}"
|
||||
)
|
||||
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
|
||||
@@ -268,11 +362,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
for comp_name, comp_mem in components_to_analyze:
|
||||
if not (comp_symbols := self._component_symbols.get(comp_name, [])):
|
||||
continue
|
||||
lines.append("")
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append(f"{comp_name} Detailed Analysis".center(self.TABLE_WIDTH))
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append("")
|
||||
self._add_section_header(lines, f"{comp_name} Detailed Analysis")
|
||||
|
||||
# Sort symbols by size
|
||||
sorted_symbols = sorted(comp_symbols, key=lambda x: x[2], reverse=True)
|
||||
@@ -283,42 +373,70 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
|
||||
# Show all symbols above threshold for better visibility
|
||||
large_symbols = [
|
||||
(sym, dem, size)
|
||||
for sym, dem, size in sorted_symbols
|
||||
(sym, dem, size, sec)
|
||||
for sym, dem, size, sec in sorted_symbols
|
||||
if size > self.SYMBOL_SIZE_THRESHOLD
|
||||
]
|
||||
|
||||
lines.append(
|
||||
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):"
|
||||
)
|
||||
for i, (symbol, demangled, size) in enumerate(large_symbols):
|
||||
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
|
||||
for i, (symbol, demangled, size, section) in enumerate(large_symbols):
|
||||
lines.append(
|
||||
f"{i + 1}. {self._format_symbol_with_section(demangled, size, section)}"
|
||||
)
|
||||
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
|
||||
return "\n".join(lines)
|
||||
# Detailed RAM analysis by component (at end, before RAM strings analysis)
|
||||
self._add_section_header(lines, "RAM Symbol Analysis by Component")
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Export analysis results as JSON."""
|
||||
data = {
|
||||
"components": {
|
||||
name: {
|
||||
"text": mem.text_size,
|
||||
"rodata": mem.rodata_size,
|
||||
"data": mem.data_size,
|
||||
"bss": mem.bss_size,
|
||||
"flash_total": mem.flash_total,
|
||||
"ram_total": mem.ram_total,
|
||||
"symbol_count": mem.symbol_count,
|
||||
}
|
||||
for name, mem in self.components.items()
|
||||
},
|
||||
"totals": {
|
||||
"flash": sum(c.flash_total for c in self.components.values()),
|
||||
"ram": sum(c.ram_total for c in self.components.values()),
|
||||
},
|
||||
}
|
||||
return json.dumps(data, indent=2)
|
||||
# Show top 15 RAM consumers with their large symbols
|
||||
for name, mem in ram_components[:15]:
|
||||
if mem.ram_total == 0:
|
||||
continue
|
||||
ram_syms = self._ram_symbols.get(name, [])
|
||||
if not ram_syms:
|
||||
continue
|
||||
|
||||
# Sort by size descending
|
||||
sorted_ram_syms = sorted(ram_syms, key=lambda x: x[2], reverse=True)
|
||||
large_ram_syms = [
|
||||
s for s in sorted_ram_syms if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD
|
||||
]
|
||||
|
||||
lines.append(f"{name} ({mem.ram_total:,} B total RAM):")
|
||||
|
||||
# Show breakdown by section type
|
||||
data_size = sum(s[2] for s in ram_syms if s[3] == ".data")
|
||||
bss_size = sum(s[2] for s in ram_syms if s[3] == ".bss")
|
||||
lines.append(f" .data (initialized): {data_size:,} B")
|
||||
lines.append(f" .bss (uninitialized): {bss_size:,} B")
|
||||
|
||||
if large_ram_syms:
|
||||
lines.append(
|
||||
f" Symbols > {self.RAM_SYMBOL_SIZE_THRESHOLD} B ({len(large_ram_syms)}):"
|
||||
)
|
||||
for symbol, demangled, size, section in large_ram_syms[:10]:
|
||||
# Format section label consistently by stripping leading dot
|
||||
section_label = section.lstrip(".") if section else ""
|
||||
# Add ellipsis if name is truncated
|
||||
demangled_display = (
|
||||
f"{demangled[:70]}..." if len(demangled) > 70 else demangled
|
||||
)
|
||||
lines.append(
|
||||
f" {size:>6,} B [{section_label}] {demangled_display}"
|
||||
)
|
||||
if len(large_ram_syms) > 10:
|
||||
lines.append(f" ... and {len(large_ram_syms) - 10} more")
|
||||
lines.append("")
|
||||
|
||||
lines.append(
|
||||
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
|
||||
)
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def dump_uncategorized_symbols(self, output_file: str | None = None) -> None:
|
||||
"""Dump uncategorized symbols for analysis."""
|
||||
|
||||
@@ -7,11 +7,13 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::")
|
||||
|
||||
# Section mapping for ELF file sections
|
||||
# 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
|
||||
SECTION_MAPPING = {
|
||||
".text": frozenset([".text", ".iram"]),
|
||||
".rodata": frozenset([".rodata"]),
|
||||
".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss"
|
||||
".data": frozenset([".data", ".dram"]),
|
||||
".bss": frozenset([".bss"]),
|
||||
}
|
||||
|
||||
# Section to ComponentMemory attribute mapping
|
||||
|
||||
@@ -5,6 +5,10 @@ from __future__ import annotations
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -55,3 +59,35 @@ def find_tool(
|
||||
|
||||
_LOGGER.warning("Could not find %s tool", tool_name)
|
||||
return None
|
||||
|
||||
|
||||
def run_tool(
|
||||
cmd: Sequence[str],
|
||||
timeout: int = 30,
|
||||
) -> subprocess.CompletedProcess[str] | None:
|
||||
"""Run a toolchain command and return the result.
|
||||
|
||||
Args:
|
||||
cmd: Command and arguments to run
|
||||
timeout: Timeout in seconds
|
||||
|
||||
Returns:
|
||||
CompletedProcess on success, None on failure
|
||||
"""
|
||||
try:
|
||||
return subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
_LOGGER.warning("Command timed out: %s", " ".join(cmd))
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
_LOGGER.warning("Command not found: %s", cmd[0])
|
||||
return None
|
||||
except OSError as e:
|
||||
_LOGGER.warning("Failed to run command %s: %s", cmd[0], e)
|
||||
return None
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "ac_dimmer.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -9,12 +7,12 @@
|
||||
#ifdef USE_ESP8266
|
||||
#include <core_esp8266_waveform.h>
|
||||
#endif
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#include <esp32-hal-timer.h>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "hw_timer_esp_idf.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
static const char *const TAG = "ac_dimmer";
|
||||
|
||||
@@ -27,7 +25,14 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no
|
||||
/// However other factors like gate driver propagation time
|
||||
/// are also considered and a really low value is not important
|
||||
/// See also: https://github.com/esphome/issues/issues/1632
|
||||
static const uint32_t GATE_ENABLE_TIME = 50;
|
||||
static constexpr uint32_t GATE_ENABLE_TIME = 50;
|
||||
|
||||
#ifdef USE_ESP32
|
||||
/// Timer frequency in Hz (1 MHz = 1µs resolution)
|
||||
static constexpr uint32_t TIMER_FREQUENCY_HZ = 1000000;
|
||||
/// Timer interrupt interval in microseconds
|
||||
static constexpr uint64_t TIMER_INTERVAL_US = 50;
|
||||
#endif
|
||||
|
||||
/// Function called from timer interrupt
|
||||
/// Input is current time in microseconds (micros())
|
||||
@@ -154,7 +159,7 @@ void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
#ifdef USE_ESP32
|
||||
// ESP32 implementation, uses basically the same code but needs to wrap
|
||||
// timer_interrupt() function to auto-reschedule
|
||||
static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static HWTimer *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||
#endif
|
||||
|
||||
@@ -194,15 +199,15 @@ void AcDimmer::setup() {
|
||||
setTimer1Callback(&timer_interrupt);
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
// timer frequency of 1mhz
|
||||
dimmer_timer = timerBegin(1000000);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
||||
dimmer_timer = timer_begin(TIMER_FREQUENCY_HZ);
|
||||
timer_attach_interrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
||||
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
||||
// are not callable from ISR (placed in flash storage).
|
||||
// Here we just use an interrupt firing every 50 µs.
|
||||
timerAlarm(dimmer_timer, 50, true, 0);
|
||||
timer_alarm(dimmer_timer, TIMER_INTERVAL_US, true, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AcDimmer::write_state(float state) {
|
||||
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
@@ -210,6 +215,7 @@ void AcDimmer::write_state(float state) {
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
this->store_.value = new_value;
|
||||
}
|
||||
|
||||
void AcDimmer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"AcDimmer:\n"
|
||||
@@ -230,7 +236,4 @@ void AcDimmer::dump_config() {
|
||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||
}
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING };
|
||||
|
||||
@@ -64,7 +61,4 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
DimMethod method_;
|
||||
};
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
152
esphome/components/ac_dimmer/hw_timer_esp_idf.cpp
Normal file
152
esphome/components/ac_dimmer/hw_timer_esp_idf.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "hw_timer_esp_idf.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_clk_tree.h"
|
||||
#include "soc/clk_tree_defs.h"
|
||||
|
||||
static const char *const TAG = "hw_timer_esp_idf";
|
||||
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
// GPTimer divider constraints from ESP-IDF documentation
|
||||
static constexpr uint32_t GPTIMER_DIVIDER_MIN = 2;
|
||||
static constexpr uint32_t GPTIMER_DIVIDER_MAX = 65536;
|
||||
|
||||
using voidFuncPtr = void (*)();
|
||||
using voidFuncPtrArg = void (*)(void *);
|
||||
|
||||
struct InterruptConfigT {
|
||||
voidFuncPtr fn{nullptr};
|
||||
void *arg{nullptr};
|
||||
};
|
||||
|
||||
struct HWTimer {
|
||||
gptimer_handle_t timer_handle{nullptr};
|
||||
InterruptConfigT interrupt_handle{};
|
||||
bool timer_started{false};
|
||||
};
|
||||
|
||||
HWTimer *timer_begin(uint32_t frequency) {
|
||||
esp_err_t err = ESP_OK;
|
||||
uint32_t counter_src_hz = 0;
|
||||
uint32_t divider = 0;
|
||||
soc_module_clk_t clk;
|
||||
for (auto clk_candidate : SOC_GPTIMER_CLKS) {
|
||||
clk = clk_candidate;
|
||||
esp_clk_tree_src_get_freq_hz(clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &counter_src_hz);
|
||||
divider = counter_src_hz / frequency;
|
||||
if ((divider >= GPTIMER_DIVIDER_MIN) && (divider <= GPTIMER_DIVIDER_MAX)) {
|
||||
break;
|
||||
} else {
|
||||
divider = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (divider == 0) {
|
||||
ESP_LOGE(TAG, "Resolution not possible; aborting");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gptimer_config_t config = {
|
||||
.clk_src = static_cast<gptimer_clock_source_t>(clk),
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = frequency,
|
||||
.flags = {.intr_shared = true},
|
||||
};
|
||||
|
||||
HWTimer *timer = new HWTimer();
|
||||
|
||||
err = gptimer_new_timer(&config, &timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer creation failed; error %d", err);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err = gptimer_enable(timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer enable failed; error %d", err);
|
||||
gptimer_del_timer(timer->timer_handle);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err = gptimer_start(timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer start failed; error %d", err);
|
||||
gptimer_disable(timer->timer_handle);
|
||||
gptimer_del_timer(timer->timer_handle);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
timer->timer_started = true;
|
||||
return timer;
|
||||
}
|
||||
|
||||
bool IRAM_ATTR timer_fn_wrapper(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *args) {
|
||||
auto *isr = static_cast<InterruptConfigT *>(args);
|
||||
if (isr->fn) {
|
||||
if (isr->arg) {
|
||||
reinterpret_cast<voidFuncPtrArg>(isr->fn)(isr->arg);
|
||||
} else {
|
||||
isr->fn();
|
||||
}
|
||||
}
|
||||
// Return false to indicate that no higher-priority task was woken and no context switch is requested.
|
||||
return false;
|
||||
}
|
||||
|
||||
static void timer_attach_interrupt_functional_arg(HWTimer *timer, void (*user_func)(void *), void *arg) {
|
||||
if (timer == nullptr) {
|
||||
ESP_LOGE(TAG, "Timer handle is nullptr");
|
||||
return;
|
||||
}
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = timer_fn_wrapper,
|
||||
};
|
||||
|
||||
timer->interrupt_handle.fn = reinterpret_cast<voidFuncPtr>(user_func);
|
||||
timer->interrupt_handle.arg = arg;
|
||||
|
||||
if (timer->timer_started) {
|
||||
gptimer_stop(timer->timer_handle);
|
||||
}
|
||||
gptimer_disable(timer->timer_handle);
|
||||
esp_err_t err = gptimer_register_event_callbacks(timer->timer_handle, &cbs, &timer->interrupt_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Timer Attach Interrupt failed; error %d", err);
|
||||
}
|
||||
gptimer_enable(timer->timer_handle);
|
||||
if (timer->timer_started) {
|
||||
gptimer_start(timer->timer_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void timer_attach_interrupt(HWTimer *timer, voidFuncPtr user_func) {
|
||||
timer_attach_interrupt_functional_arg(timer, reinterpret_cast<voidFuncPtrArg>(user_func), nullptr);
|
||||
}
|
||||
|
||||
void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count) {
|
||||
if (timer == nullptr) {
|
||||
ESP_LOGE(TAG, "Timer handle is nullptr");
|
||||
return;
|
||||
}
|
||||
gptimer_alarm_config_t alarm_cfg = {
|
||||
.alarm_count = alarm_value,
|
||||
.reload_count = reload_count,
|
||||
.flags = {.auto_reload_on_alarm = autoreload},
|
||||
};
|
||||
esp_err_t err = gptimer_set_alarm_action(timer->timer_handle, &alarm_cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Timer Alarm Write failed; error %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::ac_dimmer
|
||||
#endif
|
||||
17
esphome/components/ac_dimmer/hw_timer_esp_idf.h
Normal file
17
esphome/components/ac_dimmer/hw_timer_esp_idf.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "driver/gptimer_types.h"
|
||||
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
struct HWTimer;
|
||||
|
||||
HWTimer *timer_begin(uint32_t frequency);
|
||||
|
||||
void timer_attach_interrupt(HWTimer *timer, void (*user_func)());
|
||||
void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count);
|
||||
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
#endif
|
||||
@@ -32,7 +32,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ service APIConnection {
|
||||
|
||||
rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {}
|
||||
rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {}
|
||||
|
||||
rpc infrared_rf_transmit_raw_timings(InfraredRFTransmitRawTimingsRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -763,7 +765,7 @@ message SubscribeHomeassistantServicesRequest {
|
||||
|
||||
message HomeassistantServiceMap {
|
||||
string key = 1;
|
||||
string value = 2 [(no_zero_copy) = true];
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message HomeassistantActionRequest {
|
||||
@@ -779,7 +781,7 @@ message HomeassistantActionRequest {
|
||||
bool is_event = 5;
|
||||
uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"];
|
||||
bool wants_response = 7 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||
string response_template = 8 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||
string response_template = 8 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||
}
|
||||
|
||||
// Message sent by Home Assistant to ESPHome with service call response data
|
||||
@@ -2437,3 +2439,49 @@ message ZWaveProxyRequest {
|
||||
ZWaveProxyRequestType type = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
// ==================== INFRARED ====================
|
||||
// Note: Feature and capability flag enums are defined in
|
||||
// esphome/components/infrared/infrared.h
|
||||
|
||||
// Listing of infrared instances
|
||||
message ListEntitiesInfraredResponse {
|
||||
option (id) = 135;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_INFRARED";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 5;
|
||||
EntityCategory entity_category = 6;
|
||||
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
|
||||
uint32 capabilities = 8; // Bitfield of InfraredCapabilityFlags
|
||||
}
|
||||
|
||||
// Command to transmit infrared/RF data using raw timings
|
||||
message InfraredRFTransmitRawTimingsRequest {
|
||||
option (id) = 136;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_IR_RF";
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2; // Key identifying the transmitter instance
|
||||
uint32 carrier_frequency = 3; // Carrier frequency in Hz
|
||||
uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.)
|
||||
repeated sint32 timings = 5 [packed = true, (packed_buffer) = true]; // Raw timings in microseconds (zigzag-encoded): positive = mark (LED/TX on), negative = space (LED/TX off)
|
||||
}
|
||||
|
||||
// Event message for received infrared/RF data
|
||||
message InfraredRFReceiveEvent {
|
||||
option (id) = 137;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_IR_RF";
|
||||
option (no_delay) = true;
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2; // Key identifying the receiver instance
|
||||
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector<int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
#ifdef USE_WATER_HEATER
|
||||
#include "esphome/components/water_heater/water_heater.h"
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
#include "esphome/components/infrared/infrared.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -443,7 +446,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
|
||||
if (traits.supports_direction())
|
||||
msg.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
if (traits.supports_preset_modes() && fan->has_preset_mode())
|
||||
msg.preset_mode = StringRef(fan->get_preset_mode());
|
||||
msg.preset_mode = fan->get_preset_mode();
|
||||
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -499,7 +502,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
||||
resp.cold_white = values.get_cold_white();
|
||||
resp.warm_white = values.get_warm_white();
|
||||
if (light->supports_effects()) {
|
||||
resp.effect = light->get_effect_name_ref();
|
||||
resp.effect = light->get_effect_name();
|
||||
}
|
||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -522,7 +525,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
||||
effects_list.init(light_effects.size() + 1);
|
||||
effects_list.push_back("None");
|
||||
for (auto *effect : light_effects) {
|
||||
effects_list.push_back(effect->get_name());
|
||||
// c_str() is safe as effect names are null-terminated strings from codegen
|
||||
effects_list.push_back(effect->get_name().c_str());
|
||||
}
|
||||
}
|
||||
msg.effects = &effects_list;
|
||||
@@ -675,13 +679,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
|
||||
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
|
||||
resp.custom_fan_mode = StringRef(climate->get_custom_fan_mode());
|
||||
resp.custom_fan_mode = climate->get_custom_fan_mode();
|
||||
}
|
||||
if (traits.get_supports_presets() && climate->preset.has_value()) {
|
||||
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
|
||||
resp.custom_preset = StringRef(climate->get_custom_preset());
|
||||
resp.custom_preset = climate->get_custom_preset();
|
||||
}
|
||||
if (traits.get_supports_swing_modes())
|
||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||
@@ -914,7 +918,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
|
||||
bool is_single) {
|
||||
auto *select = static_cast<select::Select *>(entity);
|
||||
SelectStateResponse resp;
|
||||
resp.state = StringRef(select->current_option());
|
||||
resp.state = select->current_option();
|
||||
resp.missing_state = !select->has_state();
|
||||
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -1414,14 +1418,15 @@ void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequ
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, const char *event_type) {
|
||||
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||
void APIConnection::send_event(event::Event *event, StringRef event_type) {
|
||||
// get_last_event_type() returns StringRef pointing to null-terminated string literals from codegen
|
||||
this->send_message_smart_(event, MessageCreator(event_type.c_str()), EventResponse::MESSAGE_TYPE,
|
||||
EventResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single) {
|
||||
EventResponse resp;
|
||||
resp.event_type = StringRef(event_type);
|
||||
resp.event_type = event_type;
|
||||
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -1436,6 +1441,35 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void APIConnection::infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) {
|
||||
// TODO: When RF is implemented, add a field to the message to distinguish IR vs RF
|
||||
// and dispatch to the appropriate entity type based on that field.
|
||||
#ifdef USE_INFRARED
|
||||
ENTITY_COMMAND_MAKE_CALL(infrared::Infrared, infrared, infrared)
|
||||
call.set_carrier_frequency(msg.carrier_frequency);
|
||||
call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
|
||||
call.set_repeat_count(msg.repeat_count);
|
||||
call.perform();
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) {
|
||||
this->send_message(msg, InfraredRFReceiveEvent::MESSAGE_TYPE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_INFRARED
|
||||
uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
auto *infrared = static_cast<infrared::Infrared *>(entity);
|
||||
ListEntitiesInfraredResponse msg;
|
||||
msg.capabilities = infrared->get_capability_flags();
|
||||
return fill_and_encode_entity_info(infrared, msg, ListEntitiesInfraredResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
|
||||
@@ -1828,6 +1862,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
// Toggle Nagle's algorithm based on message type to prevent log messages from
|
||||
// filling the TCP send buffer and crowding out important state updates.
|
||||
//
|
||||
// This honors the `no_delay` proto option - SubscribeLogsResponse is the only
|
||||
// message with `option (no_delay) = false;` in api.proto, indicating it should
|
||||
// allow Nagle coalescing. This option existed since 2019 but was never implemented.
|
||||
//
|
||||
// - Log messages: Enable Nagle (NODELAY=false) so small log packets coalesce
|
||||
// into fewer, larger packets. They flush naturally via TCP delayed ACK timer
|
||||
// (~200ms), buffer filling, or when a state update triggers a flush.
|
||||
@@ -2051,7 +2089,7 @@ uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnec
|
||||
// Special case: EventResponse uses const char * pointer
|
||||
if (message_type == EventResponse::MESSAGE_TYPE) {
|
||||
auto *e = static_cast<event::Event *>(entity);
|
||||
return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single);
|
||||
return APIConnection::try_send_event_response(e, StringRef(data_.const_char_ptr), conn, remaining_size, is_single);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -172,8 +172,13 @@ class APIConnection final : public APIServerConnection {
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) override;
|
||||
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void send_event(event::Event *event, const char *event_type);
|
||||
void send_event(event::Event *event, StringRef event_type);
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
@@ -468,8 +473,12 @@ class APIConnection final : public APIServerConnection {
|
||||
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
static uint16_t try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single);
|
||||
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
|
||||
#endif
|
||||
|
||||
@@ -27,7 +27,6 @@ extend google.protobuf.MessageOptions {
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional string field_ifdef = 1042;
|
||||
optional uint32 fixed_array_size = 50007;
|
||||
optional bool no_zero_copy = 50008 [default=false];
|
||||
optional bool fixed_array_skip_zero = 50009 [default=false];
|
||||
optional string fixed_array_size_define = 50010;
|
||||
optional string fixed_array_with_length_define = 50011;
|
||||
@@ -80,4 +79,15 @@ extend google.protobuf.FieldOptions {
|
||||
// Example: [(container_pointer_no_template) = "light::ColorModeMask"]
|
||||
// generates: const light::ColorModeMask *supported_color_modes{};
|
||||
optional string container_pointer_no_template = 50014;
|
||||
|
||||
// packed_buffer: Expose raw packed buffer instead of decoding into container
|
||||
// When set on a packed repeated field, the generated code stores a pointer
|
||||
// to the raw protobuf buffer instead of decoding values. This enables
|
||||
// zero-copy passthrough when the consumer can decode on-demand.
|
||||
// The field must be a packed repeated field (packed=true).
|
||||
// Generates three fields:
|
||||
// - const uint8_t *<field>_data_{nullptr};
|
||||
// - uint16_t <field>_length_{0};
|
||||
// - uint16_t <field>_count_{0};
|
||||
optional bool packed_buffer = 50015 [default=false];
|
||||
}
|
||||
|
||||
@@ -3347,5 +3347,98 @@ void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->data_len);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(4, this->icon);
|
||||
#endif
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
buffer.encode_uint32(6, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(7, this->device_id);
|
||||
#endif
|
||||
buffer.encode_uint32(8, this->capabilities);
|
||||
}
|
||||
void ListEntitiesInfraredResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->object_id.size());
|
||||
size.add_fixed32(1, this->key);
|
||||
size.add_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size.add_length(1, this->icon.size());
|
||||
#endif
|
||||
size.add_bool(1, this->disabled_by_default);
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
size.add_uint32(1, this->capabilities);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
bool InfraredRFTransmitRawTimingsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
#ifdef USE_DEVICES
|
||||
case 1:
|
||||
this->device_id = value.as_uint32();
|
||||
break;
|
||||
#endif
|
||||
case 3:
|
||||
this->carrier_frequency = value.as_uint32();
|
||||
break;
|
||||
case 4:
|
||||
this->repeat_count = value.as_uint32();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool InfraredRFTransmitRawTimingsRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 5: {
|
||||
this->timings_data_ = value.data();
|
||||
this->timings_length_ = value.size();
|
||||
this->timings_count_ = count_packed_varints(value.data(), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool InfraredRFTransmitRawTimingsRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
this->key = value.as_fixed32();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void InfraredRFReceiveEvent::encode(ProtoWriteBuffer buffer) const {
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(1, this->device_id);
|
||||
#endif
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
for (const auto &it : *this->timings) {
|
||||
buffer.encode_sint32(3, it, true);
|
||||
}
|
||||
}
|
||||
void InfraredRFReceiveEvent::calculate_size(ProtoSize &size) const {
|
||||
#ifdef USE_DEVICES
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
size.add_fixed32(1, this->key);
|
||||
if (!this->timings->empty()) {
|
||||
for (const auto &it : *this->timings) {
|
||||
size.add_sint32_force(1, it);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -1053,7 +1053,7 @@ class SubscribeHomeassistantServicesRequest final : public ProtoMessage {
|
||||
class HomeassistantServiceMap final : public ProtoMessage {
|
||||
public:
|
||||
StringRef key{};
|
||||
std::string value{};
|
||||
StringRef value{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1081,7 +1081,7 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
bool wants_response{false};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
std::string response_template{};
|
||||
StringRef response_template{};
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -3049,5 +3049,70 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 135;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 44;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_infrared_response"; }
|
||||
#endif
|
||||
uint32_t capabilities{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 136;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 220;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "infrared_rf_transmit_raw_timings_request"; }
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
uint32_t device_id{0};
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
uint32_t carrier_frequency{0};
|
||||
uint32_t repeat_count{0};
|
||||
const uint8_t *timings_data_{nullptr};
|
||||
uint16_t timings_length_{0};
|
||||
uint16_t timings_count_{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class InfraredRFReceiveEvent final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 137;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 17;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "infrared_rf_receive_event"; }
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
uint32_t device_id{0};
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
const std::vector<int32_t> *timings{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -100,6 +100,16 @@ template<typename T> static void dump_field(std::string &out, const char *field_
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
// Helper for bytes fields - uses stack buffer to avoid heap allocation
|
||||
// Buffer sized for 160 bytes of data (480 chars with separators) to fit typical log buffer
|
||||
static void dump_bytes_field(std::string &out, const char *field_name, const uint8_t *data, size_t len,
|
||||
int indent = 2) {
|
||||
char hex_buf[format_hex_pretty_size(160)];
|
||||
append_field_prefix(out, field_name, indent);
|
||||
format_hex_pretty_to(hex_buf, data, len);
|
||||
append_with_newline(out, hex_buf);
|
||||
}
|
||||
|
||||
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
|
||||
switch (value) {
|
||||
case enums::ENTITY_CATEGORY_NONE:
|
||||
@@ -1127,16 +1137,12 @@ void SubscribeLogsRequest::dump_to(std::string &out) const {
|
||||
void SubscribeLogsResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "SubscribeLogsResponse");
|
||||
dump_field(out, "level", static_cast<enums::LogLevel>(this->level));
|
||||
out.append(" message: ");
|
||||
out.append(format_hex_pretty(this->message_ptr_, this->message_len_));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "message", this->message_ptr_, this->message_len_);
|
||||
}
|
||||
#ifdef USE_API_NOISE
|
||||
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest");
|
||||
out.append(" key: ");
|
||||
out.append(format_hex_pretty(this->key, this->key_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "key", this->key, this->key_len);
|
||||
}
|
||||
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyResponse");
|
||||
@@ -1189,9 +1195,7 @@ void HomeassistantActionResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "success", this->success);
|
||||
dump_field(out, "error_message", this->error_message);
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
out.append(" response_data: ");
|
||||
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "response_data", this->response_data, this->response_data_len);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -1278,9 +1282,7 @@ void ExecuteServiceResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "success", this->success);
|
||||
dump_field(out, "error_message", this->error_message);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
out.append(" response_data: ");
|
||||
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "response_data", this->response_data, this->response_data_len);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -1302,9 +1304,7 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
void CameraImageResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "CameraImageResponse");
|
||||
dump_field(out, "key", this->key);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
||||
dump_field(out, "done", this->done);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
@@ -1705,9 +1705,7 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "rssi", this->rssi);
|
||||
dump_field(out, "address_type", this->address_type);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse");
|
||||
@@ -1792,18 +1790,14 @@ void BluetoothGATTReadResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTReadResponse");
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
||||
}
|
||||
void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTWriteRequest");
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
dump_field(out, "response", this->response);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTReadDescriptorRequest");
|
||||
@@ -1814,9 +1808,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTWriteDescriptorRequest");
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTNotifyRequest");
|
||||
@@ -1828,9 +1820,7 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTNotifyDataResponse");
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
||||
}
|
||||
void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const {
|
||||
out.append("SubscribeBluetoothConnectionsFreeRequest {}");
|
||||
@@ -1934,9 +1924,7 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
|
||||
}
|
||||
void VoiceAssistantAudio::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantAudio");
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
dump_field(out, "end", this->end);
|
||||
}
|
||||
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
|
||||
@@ -2297,16 +2285,56 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void ZWaveProxyFrame::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ZWaveProxyFrame");
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
void ZWaveProxyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ZWaveProxyRequest");
|
||||
dump_field(out, "type", static_cast<enums::ZWaveProxyRequestType>(this->type));
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
void ListEntitiesInfraredResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesInfraredResponse");
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "capabilities", this->capabilities);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void InfraredRFTransmitRawTimingsRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "InfraredRFTransmitRawTimingsRequest");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "carrier_frequency", this->carrier_frequency);
|
||||
dump_field(out, "repeat_count", this->repeat_count);
|
||||
out.append(" timings: ");
|
||||
out.append("packed buffer [");
|
||||
out.append(std::to_string(this->timings_count_));
|
||||
out.append(" values, ");
|
||||
out.append(std::to_string(this->timings_length_));
|
||||
out.append(" bytes]\n");
|
||||
}
|
||||
void InfraredRFReceiveEvent::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "InfraredRFReceiveEvent");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "key", this->key);
|
||||
for (const auto &it : *this->timings) {
|
||||
dump_field(out, "timings", it, 4);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -621,6 +621,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
this->on_water_heater_command_request(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
case InfraredRFTransmitRawTimingsRequest::MESSAGE_TYPE: {
|
||||
InfraredRFTransmitRawTimingsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_infrared_rf_transmit_raw_timings_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_infrared_rf_transmit_raw_timings_request(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
@@ -819,6 +830,11 @@ void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { th
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void APIServerConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) {
|
||||
this->infrared_rf_transmit_raw_timings(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
// Check authentication/connection requirements for messages
|
||||
|
||||
@@ -217,6 +217,11 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
@@ -347,6 +352,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
virtual void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
@@ -473,6 +481,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override;
|
||||
#endif
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
@@ -186,14 +186,17 @@ void APIServer::loop() {
|
||||
}
|
||||
|
||||
// Rare case: handle disconnection
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
this->client_disconnected_trigger_->trigger(std::string(client->get_name()), std::string(client->get_peername()));
|
||||
#endif
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
this->unregister_active_action_calls_for_connection(client.get());
|
||||
#endif
|
||||
ESP_LOGV(TAG, "Remove connection %s", client->get_name());
|
||||
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
// Save client info before removal for the trigger
|
||||
std::string client_name(client->get_name());
|
||||
std::string client_peername(client->get_peername());
|
||||
#endif
|
||||
|
||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||
if (client_index < this->clients_.size() - 1) {
|
||||
std::swap(this->clients_[client_index], this->clients_.back());
|
||||
@@ -205,6 +208,11 @@ void APIServer::loop() {
|
||||
this->status_set_warning();
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
// Fire trigger after client is removed so api.connected reflects the true state
|
||||
this->client_disconnected_trigger_->trigger(client_name, client_peername);
|
||||
#endif
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
}
|
||||
}
|
||||
@@ -339,6 +347,21 @@ void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_id, uint32_t key,
|
||||
const std::vector<int32_t> *timings) {
|
||||
InfraredRFReceiveEvent resp{};
|
||||
#ifdef USE_DEVICES
|
||||
resp.device_id = device_id;
|
||||
#endif
|
||||
resp.key = key;
|
||||
resp.timings = timings;
|
||||
|
||||
for (auto &c : this->clients_)
|
||||
c->send_infrared_rf_receive_event(resp);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
|
||||
#endif
|
||||
@@ -616,6 +639,14 @@ bool APIServer::teardown() {
|
||||
#define USE_API_ACTION_CALL_TIMEOUT_MS 30000 // NOLINT
|
||||
#endif
|
||||
|
||||
// SSO-friendly action call key - hex format guarantees max 11 chars ("ac_ffffffff")
|
||||
// which fits in any std::string SSO buffer (typically 12-15 bytes)
|
||||
static inline std::string make_action_call_key(uint32_t id) {
|
||||
char buf[12];
|
||||
size_t len = snprintf(buf, sizeof(buf), "ac_%x", id);
|
||||
return std::string(buf, len);
|
||||
}
|
||||
|
||||
uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConnection *conn) {
|
||||
uint32_t action_call_id = this->next_action_call_id_++;
|
||||
// Handle wraparound (skip 0 as it means "no call")
|
||||
@@ -625,18 +656,17 @@ uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConn
|
||||
this->active_action_calls_.push_back({action_call_id, client_call_id, conn});
|
||||
|
||||
// Schedule automatic cleanup after timeout (client will have given up by then)
|
||||
this->set_timeout(str_sprintf("action_call_%u", action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS,
|
||||
[this, action_call_id]() {
|
||||
ESP_LOGD(TAG, "Action call %u timed out", action_call_id);
|
||||
this->unregister_active_action_call(action_call_id);
|
||||
});
|
||||
this->set_timeout(make_action_call_key(action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS, [this, action_call_id]() {
|
||||
ESP_LOGD(TAG, "Action call %u timed out", action_call_id);
|
||||
this->unregister_active_action_call(action_call_id);
|
||||
});
|
||||
|
||||
return action_call_id;
|
||||
}
|
||||
|
||||
void APIServer::unregister_active_action_call(uint32_t action_call_id) {
|
||||
// Cancel the timeout for this action call
|
||||
this->cancel_timeout(str_sprintf("action_call_%u", action_call_id));
|
||||
this->cancel_timeout(make_action_call_key(action_call_id));
|
||||
|
||||
// Swap-and-pop is more efficient than remove_if for unordered vectors
|
||||
for (size_t i = 0; i < this->active_action_calls_.size(); i++) {
|
||||
@@ -653,7 +683,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
|
||||
for (size_t i = 0; i < this->active_action_calls_.size();) {
|
||||
if (this->active_action_calls_[i].connection == conn) {
|
||||
// Cancel the timeout for this action call
|
||||
this->cancel_timeout(str_sprintf("action_call_%u", this->active_action_calls_[i].action_call_id));
|
||||
this->cancel_timeout(make_action_call_key(this->active_action_calls_[i].action_call_id));
|
||||
|
||||
std::swap(this->active_action_calls_[i], this->active_action_calls_.back());
|
||||
this->active_action_calls_.pop_back();
|
||||
|
||||
@@ -185,6 +185,9 @@ class APIServer : public Component,
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
|
||||
#endif
|
||||
|
||||
bool is_connected(bool state_subscription_only = false) const;
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ class CustomAPIDevice {
|
||||
for (auto &it : data) {
|
||||
auto &kv = resp.data.emplace_back();
|
||||
kv.key = StringRef(it.first);
|
||||
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
|
||||
kv.value = StringRef(it.second); // data map lives until send completes
|
||||
}
|
||||
global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
@@ -308,7 +308,7 @@ class CustomAPIDevice {
|
||||
for (auto &it : data) {
|
||||
auto &kv = resp.data.emplace_back();
|
||||
kv.key = StringRef(it.first);
|
||||
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
|
||||
kv.value = StringRef(it.second); // data map lives until send completes
|
||||
}
|
||||
global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
@@ -149,11 +149,21 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
std::string service_value = this->service_.value(x...);
|
||||
resp.service = StringRef(service_value);
|
||||
resp.is_event = this->flags_.is_event;
|
||||
this->populate_service_map(resp.data, this->data_, x...);
|
||||
this->populate_service_map(resp.data_template, this->data_template_, x...);
|
||||
this->populate_service_map(resp.variables, this->variables_, x...);
|
||||
|
||||
// Local storage for lambda-evaluated strings - lives until after send
|
||||
FixedVector<std::string> data_storage;
|
||||
FixedVector<std::string> data_template_storage;
|
||||
FixedVector<std::string> variables_storage;
|
||||
|
||||
this->populate_service_map(resp.data, this->data_, data_storage, x...);
|
||||
this->populate_service_map(resp.data_template, this->data_template_, data_template_storage, x...);
|
||||
this->populate_service_map(resp.variables, this->variables_, variables_storage, x...);
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
// IMPORTANT: Declare at outer scope so it lives until send_homeassistant_action returns.
|
||||
std::string response_template_value;
|
||||
#endif
|
||||
if (this->flags_.wants_status) {
|
||||
// Generate a unique call ID for this service call
|
||||
static uint32_t call_id_counter = 1;
|
||||
@@ -164,8 +174,8 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
resp.wants_response = true;
|
||||
// Set response template if provided
|
||||
if (this->flags_.has_response_template) {
|
||||
std::string response_template_value = this->response_template_.value(x...);
|
||||
resp.response_template = response_template_value;
|
||||
response_template_value = this->response_template_.value(x...);
|
||||
resp.response_template = StringRef(response_template_value);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -205,12 +215,31 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
}
|
||||
|
||||
template<typename VectorType, typename SourceType>
|
||||
static void populate_service_map(VectorType &dest, SourceType &source, Ts... x) {
|
||||
static void populate_service_map(VectorType &dest, SourceType &source, FixedVector<std::string> &value_storage,
|
||||
Ts... x) {
|
||||
dest.init(source.size());
|
||||
|
||||
// Count non-static strings to allocate exact storage needed
|
||||
size_t lambda_count = 0;
|
||||
for (const auto &it : source) {
|
||||
if (!it.value.is_static_string()) {
|
||||
lambda_count++;
|
||||
}
|
||||
}
|
||||
value_storage.init(lambda_count);
|
||||
|
||||
for (auto &it : source) {
|
||||
auto &kv = dest.emplace_back();
|
||||
kv.key = StringRef(it.key);
|
||||
kv.value = it.value.value(x...);
|
||||
|
||||
if (it.value.is_static_string()) {
|
||||
// Static string from YAML - zero allocation
|
||||
kv.value = StringRef(it.value.get_static_string());
|
||||
} else {
|
||||
// Lambda evaluation - store result, reference it
|
||||
value_storage.push_back(it.value.value(x...));
|
||||
kv.value = StringRef(value_storage.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPane
|
||||
#ifdef USE_WATER_HEATER
|
||||
LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWaterHeaterResponse)
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
LIST_ENTITIES_HANDLER(infrared, infrared::Infrared, ListEntitiesInfraredResponse)
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
|
||||
#endif
|
||||
|
||||
@@ -85,6 +85,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool on_water_heater(water_heater::WaterHeater *entity) override;
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
bool on_infrared(infrared::Infrared *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *entity) override;
|
||||
#endif
|
||||
|
||||
@@ -39,6 +39,24 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) {
|
||||
return (value & 1) ? static_cast<int64_t>(~(value >> 1)) : static_cast<int64_t>(value >> 1);
|
||||
}
|
||||
|
||||
/// Count number of varints in a packed buffer
|
||||
inline uint16_t count_packed_varints(const uint8_t *data, size_t len) {
|
||||
uint16_t count = 0;
|
||||
while (len > 0) {
|
||||
// Skip varint bytes until we find one without continuation bit
|
||||
while (len > 0 && (*data & 0x80)) {
|
||||
data++;
|
||||
len--;
|
||||
}
|
||||
if (len > 0) {
|
||||
data++;
|
||||
len--;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* StringRef Ownership Model for API Protocol Messages
|
||||
* ===================================================
|
||||
@@ -180,9 +198,10 @@ class ProtoVarInt {
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
// Forward declaration for decode_to_message and encode_to_writer
|
||||
class ProtoMessage;
|
||||
// Forward declarations for decode_to_message, encode_message and encode_packed_sint32
|
||||
class ProtoDecodableMessage;
|
||||
class ProtoMessage;
|
||||
class ProtoSize;
|
||||
|
||||
class ProtoLengthDelimited {
|
||||
public:
|
||||
@@ -334,6 +353,8 @@ class ProtoWriteBuffer {
|
||||
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
this->encode_uint64(field_id, encode_zigzag64(value), force);
|
||||
}
|
||||
/// Encode a packed repeated sint32 field (zero-copy from vector)
|
||||
void encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values);
|
||||
void encode_message(uint32_t field_id, const ProtoMessage &value);
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
@@ -341,9 +362,6 @@ class ProtoWriteBuffer {
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
class ProtoSize;
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual ~ProtoMessage() = default;
|
||||
@@ -792,8 +810,43 @@ class ProtoSize {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate size of a packed repeated sint32 field
|
||||
*/
|
||||
inline void add_packed_sint32(uint32_t field_id_size, const std::vector<int32_t> &values) {
|
||||
if (values.empty())
|
||||
return;
|
||||
|
||||
size_t packed_size = 0;
|
||||
for (int value : values) {
|
||||
packed_size += varint(encode_zigzag32(value));
|
||||
}
|
||||
|
||||
// field_id + length varint + packed data
|
||||
total_size_ += field_id_size + varint(static_cast<uint32_t>(packed_size)) + static_cast<uint32_t>(packed_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Implementation of encode_packed_sint32 - must be after ProtoSize is defined
|
||||
inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values) {
|
||||
if (values.empty())
|
||||
return;
|
||||
|
||||
// Calculate packed size
|
||||
size_t packed_size = 0;
|
||||
for (int value : values) {
|
||||
packed_size += ProtoSize::varint(encode_zigzag32(value));
|
||||
}
|
||||
|
||||
// Write tag (LENGTH_DELIMITED) + length + all zigzag-encoded values
|
||||
this->encode_field_raw(field_id, WIRE_TYPE_LENGTH_DELIMITED);
|
||||
this->encode_varint_raw(packed_size);
|
||||
for (int value : values) {
|
||||
this->encode_varint_raw(encode_zigzag32(value));
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of encode_message - must be after ProtoMessage is defined
|
||||
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) {
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||
|
||||
@@ -79,6 +79,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool on_water_heater(water_heater::WaterHeater *entity) override;
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
bool on_infrared(infrared::Infrared *infrared) override { return true; };
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override { return true; };
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace esphome::aqi {
|
||||
|
||||
class AbstractAQICalculator {
|
||||
public:
|
||||
virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
|
||||
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0;
|
||||
};
|
||||
|
||||
} // namespace esphome::aqi
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
|
||||
@@ -9,11 +10,11 @@ namespace esphome::aqi {
|
||||
|
||||
class AQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
||||
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
|
||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
|
||||
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
||||
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -21,25 +22,28 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||
|
||||
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}};
|
||||
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {{0.0f, 9.0f}, {9.1f, 35.4f},
|
||||
{35.5f, 55.4f}, {55.5f, 125.4f},
|
||||
{125.5f, 225.4f}, {225.5f, std::numeric_limits<float>::max()}};
|
||||
|
||||
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
||||
{255, 354}, {355, 424}, {425, INT_MAX}};
|
||||
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {{0.0f, 54.0f}, {55.0f, 154.0f},
|
||||
{155.0f, 254.0f}, {255.0f, 354.0f},
|
||||
{355.0f, 424.0f}, {425.0f, std::numeric_limits<float>::max()}};
|
||||
|
||||
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
int grid_index = get_grid_index(value, array);
|
||||
if (grid_index == -1) {
|
||||
return -1;
|
||||
return -1.0f;
|
||||
}
|
||||
int aqi_lo = INDEX_GRID[grid_index][0];
|
||||
int aqi_hi = INDEX_GRID[grid_index][1];
|
||||
int conc_lo = array[grid_index][0];
|
||||
int conc_hi = array[grid_index][1];
|
||||
float aqi_lo = INDEX_GRID[grid_index][0];
|
||||
float aqi_hi = INDEX_GRID[grid_index][1];
|
||||
float conc_lo = array[grid_index][0];
|
||||
float conc_hi = array[grid_index][1];
|
||||
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||
if (value >= array[i][0] && value <= array[i][1]) {
|
||||
return i;
|
||||
|
||||
@@ -44,8 +44,7 @@ void AQISensor::calculate_aqi_() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t aqi =
|
||||
calculator->get_aqi(static_cast<uint16_t>(this->pm_2_5_value_), static_cast<uint16_t>(this->pm_10_0_value_));
|
||||
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_);
|
||||
this->publish_state(aqi);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
namespace esphome::aqi {
|
||||
|
||||
class CAQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
||||
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
|
||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
|
||||
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
||||
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -18,25 +20,27 @@ class CAQICalculator : public AbstractAQICalculator {
|
||||
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
|
||||
|
||||
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}};
|
||||
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
|
||||
{0.0f, 15.0f}, {15.1f, 30.0f}, {30.1f, 55.0f}, {55.1f, 110.0f}, {110.1f, std::numeric_limits<float>::max()}};
|
||||
|
||||
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
|
||||
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
|
||||
{0.0f, 25.0f}, {25.1f, 50.0f}, {50.1f, 90.0f}, {90.1f, 180.0f}, {180.1f, std::numeric_limits<float>::max()}};
|
||||
|
||||
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
int grid_index = get_grid_index(value, array);
|
||||
if (grid_index == -1) {
|
||||
return -1;
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
int aqi_lo = INDEX_GRID[grid_index][0];
|
||||
int aqi_hi = INDEX_GRID[grid_index][1];
|
||||
int conc_lo = array[grid_index][0];
|
||||
int conc_hi = array[grid_index][1];
|
||||
float aqi_lo = INDEX_GRID[grid_index][0];
|
||||
float aqi_hi = INDEX_GRID[grid_index][1];
|
||||
float conc_lo = array[grid_index][0];
|
||||
float conc_hi = array[grid_index][1];
|
||||
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||
if (value >= array[i][0] && value <= array[i][1]) {
|
||||
return i;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "bedjet_hub.h"
|
||||
#include "bedjet_child.h"
|
||||
#include "bedjet_const.h"
|
||||
#include "esphome/components/esp32_ble/ble_uuid.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cinttypes>
|
||||
|
||||
|
||||
@@ -164,21 +164,21 @@ void BedJetClimate::control(const ClimateCall &call) {
|
||||
return;
|
||||
}
|
||||
} else if (call.has_custom_preset()) {
|
||||
const char *preset = call.get_custom_preset();
|
||||
auto preset = call.get_custom_preset();
|
||||
bool result;
|
||||
|
||||
if (strcmp(preset, "M1") == 0) {
|
||||
if (preset == "M1") {
|
||||
result = this->parent_->button_memory1();
|
||||
} else if (strcmp(preset, "M2") == 0) {
|
||||
} else if (preset == "M2") {
|
||||
result = this->parent_->button_memory2();
|
||||
} else if (strcmp(preset, "M3") == 0) {
|
||||
} else if (preset == "M3") {
|
||||
result = this->parent_->button_memory3();
|
||||
} else if (strcmp(preset, "LTD HT") == 0) {
|
||||
} else if (preset == "LTD HT") {
|
||||
result = this->parent_->button_heat();
|
||||
} else if (strcmp(preset, "EXT HT") == 0) {
|
||||
} else if (preset == "EXT HT") {
|
||||
result = this->parent_->button_ext_heat();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported preset: %s", preset);
|
||||
ESP_LOGW(TAG, "Unsupported preset: %.*s", (int) preset.size(), preset.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,10 +208,11 @@ void BedJetClimate::control(const ClimateCall &call) {
|
||||
this->set_fan_mode_(fan_mode);
|
||||
}
|
||||
} else if (call.has_custom_fan_mode()) {
|
||||
const char *fan_mode = call.get_custom_fan_mode();
|
||||
auto fan_index = bedjet_fan_speed_to_step(fan_mode);
|
||||
auto fan_mode = call.get_custom_fan_mode();
|
||||
auto fan_index = bedjet_fan_speed_to_step(fan_mode.c_str());
|
||||
if (fan_index <= 19) {
|
||||
ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode, fan_index);
|
||||
ESP_LOGV(TAG, "[%s] Converted fan mode %.*s to bedjet fan step %d", this->get_name().c_str(),
|
||||
(int) fan_mode.size(), fan_mode.c_str(), fan_index);
|
||||
bool result = this->parent_->set_fan_index(fan_index);
|
||||
if (result) {
|
||||
this->set_custom_fan_mode_(fan_mode);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "bh1750.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome::bh1750 {
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
|
||||
static const char *const TAG = "bh1750.sensor";
|
||||
|
||||
@@ -13,31 +13,6 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
|
||||
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
|
||||
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
|
||||
|
||||
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
|
||||
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
|
||||
|
||||
// Measurement time constants (datasheet values)
|
||||
static constexpr uint16_t MTREG_DEFAULT = 69;
|
||||
static constexpr uint16_t MTREG_MIN = 31;
|
||||
static constexpr uint16_t MTREG_MAX = 254;
|
||||
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
|
||||
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
|
||||
|
||||
// Conversion constants (datasheet formulas)
|
||||
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
|
||||
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
|
||||
|
||||
// MTreg calculation constants
|
||||
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
|
||||
static constexpr int COUNTS_NUMERATOR = 10;
|
||||
static constexpr int COUNTS_DENOMINATOR = 12;
|
||||
|
||||
// MTreg register bit manipulation constants
|
||||
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
|
||||
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
|
||||
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
|
||||
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
|
||||
|
||||
/*
|
||||
bh1750 properties:
|
||||
|
||||
@@ -68,7 +43,74 @@ void BH1750Sensor::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->state_ = IDLE;
|
||||
}
|
||||
|
||||
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
|
||||
// turn on (after one-shot sensor automatically powers down)
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Power on failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_mtreg_ != mtreg) {
|
||||
// set mtreg
|
||||
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
|
||||
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
|
||||
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Set measurement time failed");
|
||||
active_mtreg_ = 0;
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
active_mtreg_ = mtreg;
|
||||
}
|
||||
|
||||
uint8_t cmd;
|
||||
uint16_t meas_time;
|
||||
switch (mode) {
|
||||
case BH1750_MODE_L:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_L;
|
||||
meas_time = 24 * mtreg / 69;
|
||||
break;
|
||||
case BH1750_MODE_H:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H;
|
||||
meas_time = 180 * mtreg / 69;
|
||||
break;
|
||||
case BH1750_MODE_H2:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H2;
|
||||
meas_time = 180 * mtreg / 69;
|
||||
break;
|
||||
default:
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Start measurement failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
// probably not needed, but adjust for rounding
|
||||
meas_time++;
|
||||
|
||||
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read data failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
lx *= 69.0f / mtreg;
|
||||
if (mode == BH1750_MODE_H2)
|
||||
lx /= 2.0f;
|
||||
|
||||
f(lx);
|
||||
});
|
||||
}
|
||||
|
||||
void BH1750Sensor::dump_config() {
|
||||
@@ -82,189 +124,45 @@ void BH1750Sensor::dump_config() {
|
||||
}
|
||||
|
||||
void BH1750Sensor::update() {
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Start coarse measurement to determine optimal mode/mtreg
|
||||
if (this->state_ != IDLE) {
|
||||
// Safety timeout: reset if stuck
|
||||
if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
|
||||
ESP_LOGW(TAG, "Measurement timeout, resetting state");
|
||||
this->state_ = IDLE;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
|
||||
// first do a quick measurement in L-mode with full range
|
||||
// to find right range
|
||||
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
this->state_ = WAITING_COARSE_MEASUREMENT;
|
||||
this->enable_loop(); // Enable loop while measurement in progress
|
||||
}
|
||||
|
||||
void BH1750Sensor::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
switch (this->state_) {
|
||||
case IDLE:
|
||||
// Disable loop when idle to save cycles
|
||||
this->disable_loop();
|
||||
break;
|
||||
|
||||
case WAITING_COARSE_MEASUREMENT:
|
||||
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
|
||||
this->state_ = READING_COARSE_RESULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case READING_COARSE_RESULT: {
|
||||
float lx;
|
||||
if (!this->read_measurement_(lx)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
this->process_coarse_result_(lx);
|
||||
|
||||
// Start fine measurement with optimal settings
|
||||
// fetch millis() again since the read can take a bit
|
||||
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
this->state_ = WAITING_FINE_MEASUREMENT;
|
||||
break;
|
||||
BH1750Mode use_mode;
|
||||
uint8_t use_mtreg;
|
||||
if (val <= 7000) {
|
||||
use_mode = BH1750_MODE_H2;
|
||||
use_mtreg = 254;
|
||||
} else {
|
||||
use_mode = BH1750_MODE_H;
|
||||
// lx = counts / 1.2 * (69 / mtreg)
|
||||
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
|
||||
// -> mtreg = 50000*(10/12)*(69/lx)
|
||||
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
|
||||
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
|
||||
}
|
||||
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
|
||||
|
||||
case WAITING_FINE_MEASUREMENT:
|
||||
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
|
||||
this->state_ = READING_FINE_RESULT;
|
||||
this->read_lx_(use_mode, use_mtreg, [this](float val) {
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case READING_FINE_RESULT: {
|
||||
float lx;
|
||||
if (!this->read_measurement_(lx)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
|
||||
this->status_clear_warning();
|
||||
this->publish_state(lx);
|
||||
this->state_ = IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
|
||||
// Power on
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Power on failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set MTreg if changed
|
||||
if (this->active_mtreg_ != mtreg) {
|
||||
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
|
||||
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
|
||||
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Set measurement time failed");
|
||||
this->active_mtreg_ = 0;
|
||||
return false;
|
||||
}
|
||||
this->active_mtreg_ = mtreg;
|
||||
}
|
||||
|
||||
// Start measurement
|
||||
uint8_t cmd;
|
||||
uint16_t meas_time;
|
||||
switch (mode) {
|
||||
case BH1750_MODE_L:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_L;
|
||||
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
case BH1750_MODE_H:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H;
|
||||
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
case BH1750_MODE_H2:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H2;
|
||||
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Start measurement failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store current measurement parameters
|
||||
this->current_mode_ = mode;
|
||||
this->current_mtreg_ = mtreg;
|
||||
this->measurement_start_time_ = now;
|
||||
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BH1750Sensor::read_measurement_(float &lx_out) {
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read data failed");
|
||||
return false;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / RESOLUTION_DIVISOR;
|
||||
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
|
||||
if (this->current_mode_ == BH1750_MODE_H2) {
|
||||
lx /= MODE_H2_DIVISOR;
|
||||
}
|
||||
|
||||
lx_out = lx;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BH1750Sensor::process_coarse_result_(float lx) {
|
||||
if (std::isnan(lx)) {
|
||||
// Use defaults if coarse measurement failed
|
||||
this->fine_mode_ = BH1750_MODE_H2;
|
||||
this->fine_mtreg_ = MTREG_MAX;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
|
||||
this->fine_mode_ = BH1750_MODE_H2;
|
||||
this->fine_mtreg_ = MTREG_MAX;
|
||||
} else {
|
||||
this->fine_mode_ = BH1750_MODE_H;
|
||||
// lx = counts / 1.2 * (69 / mtreg)
|
||||
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
|
||||
// -> mtreg = 50000*(10/12)*(69/lx)
|
||||
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
|
||||
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
|
||||
}
|
||||
|
||||
void BH1750Sensor::fail_and_reset_() {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
this->state_ = IDLE;
|
||||
this->publish_state(val);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace esphome::bh1750
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome::bh1750 {
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
|
||||
enum BH1750Mode : uint8_t {
|
||||
enum BH1750Mode {
|
||||
BH1750_MODE_L,
|
||||
BH1750_MODE_H,
|
||||
BH1750_MODE_H2,
|
||||
@@ -20,36 +21,13 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
// State machine states
|
||||
enum State : uint8_t {
|
||||
IDLE,
|
||||
WAITING_COARSE_MEASUREMENT,
|
||||
READING_COARSE_RESULT,
|
||||
WAITING_FINE_MEASUREMENT,
|
||||
READING_FINE_RESULT,
|
||||
};
|
||||
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f);
|
||||
|
||||
// 4-byte aligned members
|
||||
uint32_t measurement_start_time_{0};
|
||||
uint32_t measurement_duration_{0};
|
||||
|
||||
// 1-byte members grouped together to minimize padding
|
||||
State state_{IDLE};
|
||||
BH1750Mode current_mode_{BH1750_MODE_L};
|
||||
uint8_t current_mtreg_{31};
|
||||
BH1750Mode fine_mode_{BH1750_MODE_H2};
|
||||
uint8_t fine_mtreg_{254};
|
||||
uint8_t active_mtreg_{0};
|
||||
|
||||
// Helper methods
|
||||
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
|
||||
bool read_measurement_(float &lx_out);
|
||||
void process_coarse_result_(float lx);
|
||||
void fail_and_reset_();
|
||||
};
|
||||
|
||||
} // namespace esphome::bh1750
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
|
||||
@@ -21,7 +21,7 @@ class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSe
|
||||
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
|
||||
param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
|
||||
this->trigger(std::string(reinterpret_cast<const char *>(param->notify.value), param->notify.value_len));
|
||||
}
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -11,8 +11,6 @@ namespace esphome::ble_client {
|
||||
|
||||
static const char *const TAG = "ble_text_sensor";
|
||||
|
||||
static const std::string EMPTY = "";
|
||||
|
||||
void BLETextSensor::loop() {
|
||||
// Parent BLEClientNode has a loop() method, but this component uses
|
||||
// polling via update() and BLE callbacks so loop isn't needed
|
||||
@@ -47,7 +45,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
case ESP_GATTC_CLOSE_EVT: {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
this->publish_state("");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
@@ -55,7 +53,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
this->publish_state("");
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
|
||||
@@ -67,7 +65,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
auto *descr = chr->get_descriptor(this->descr_uuid_);
|
||||
if (descr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
this->publish_state("");
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
char descr_buf[esp32_ble::UUID_STR_LEN];
|
||||
@@ -99,7 +97,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
|
||||
this->publish_state(reinterpret_cast<const char *>(param->read.value), param->read.value_len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -108,7 +106,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
|
||||
this->publish_state(reinterpret_cast<const char *>(param->notify.value), param->notify.value_len);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
@@ -121,11 +119,6 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
}
|
||||
|
||||
std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) {
|
||||
std::string text(value, value + value_len);
|
||||
return text;
|
||||
}
|
||||
|
||||
void BLETextSensor::update() {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
@@ -140,7 +133,7 @@ void BLETextSensor::update() {
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
this->publish_state("");
|
||||
ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p
|
||||
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||
std::string parse_data(uint8_t *value, uint16_t value_len);
|
||||
uint16_t handle;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -31,7 +31,11 @@ void BME68xBSEC2I2CComponent::dump_config() {
|
||||
BME68xBSEC2Component::dump_config();
|
||||
}
|
||||
|
||||
uint32_t BME68xBSEC2I2CComponent::get_hash() { return fnv1_hash("bme68x_bsec_state_" + to_string(this->address_)); }
|
||||
uint32_t BME68xBSEC2I2CComponent::get_hash() {
|
||||
char buf[22]; // "bme68x_bsec_state_" (18) + uint8_t max (3) + null
|
||||
snprintf(buf, sizeof(buf), "bme68x_bsec_state_%u", this->address_);
|
||||
return fnv1_hash(buf);
|
||||
}
|
||||
|
||||
int8_t BME68xBSEC2I2CComponent::read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr) {
|
||||
ESP_LOGVV(TAG, "read_bytes_wrapper: reg = %u", a_register);
|
||||
|
||||
@@ -7,90 +7,87 @@ namespace esphome::captive_portal {
|
||||
|
||||
#ifdef USE_CAPTIVE_PORTAL_GZIP
|
||||
const uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x95, 0x56, 0xeb, 0x6f, 0xd4, 0x38, 0x10, 0xff, 0xce,
|
||||
0x5f, 0xe1, 0x33, 0x8f, 0x26, 0xd0, 0x3c, 0xb7, 0xdb, 0x96, 0x6c, 0x12, 0x04, 0xdc, 0x21, 0x90, 0x28, 0x20, 0xb5,
|
||||
0x70, 0x1f, 0x10, 0x52, 0xbd, 0xc9, 0x64, 0x63, 0x9a, 0x38, 0x39, 0xdb, 0xfb, 0x62, 0xb5, 0xf7, 0xb7, 0xdf, 0x38,
|
||||
0xc9, 0x6e, 0xb7, 0x15, 0x9c, 0xee, 0x5a, 0x35, 0x1d, 0xdb, 0xf3, 0xf8, 0xcd, 0x78, 0x1e, 0x8e, 0x7f, 0xcb, 0x9b,
|
||||
0x4c, 0xaf, 0x5b, 0x20, 0xa5, 0xae, 0xab, 0x34, 0x36, 0x5f, 0x52, 0x31, 0x31, 0x4b, 0x40, 0xe0, 0x0a, 0x58, 0x9e,
|
||||
0xc6, 0x35, 0x68, 0x46, 0xb2, 0x92, 0x49, 0x05, 0x3a, 0xf9, 0x7c, 0xf5, 0xc6, 0x39, 0x4f, 0xe3, 0x8a, 0x8b, 0x1b,
|
||||
0x22, 0xa1, 0x4a, 0x78, 0xd6, 0x08, 0x52, 0x4a, 0x28, 0x92, 0x9c, 0x69, 0x16, 0xf1, 0x9a, 0xcd, 0x60, 0x10, 0x11,
|
||||
0xac, 0x86, 0x64, 0xc1, 0x61, 0xd9, 0x36, 0x52, 0x13, 0xe4, 0xd3, 0x20, 0x74, 0x42, 0x97, 0x3c, 0xd7, 0x65, 0x92,
|
||||
0xc3, 0x82, 0x67, 0xe0, 0x74, 0x8b, 0x63, 0x2e, 0xb8, 0xe6, 0xac, 0x72, 0x54, 0xc6, 0x2a, 0x48, 0x82, 0xe3, 0xb9,
|
||||
0x02, 0xd9, 0x2d, 0xd8, 0x14, 0xd7, 0xa2, 0xa1, 0x69, 0xac, 0x32, 0xc9, 0x5b, 0x4d, 0x0c, 0xd4, 0xa4, 0x6e, 0xf2,
|
||||
0x79, 0x05, 0xa9, 0xe7, 0x31, 0x85, 0x90, 0x94, 0xc7, 0x45, 0x0e, 0x2b, 0x77, 0xea, 0x67, 0x99, 0x0f, 0xe7, 0xe7,
|
||||
0xee, 0x77, 0xf5, 0x00, 0x9d, 0x9a, 0xd7, 0x68, 0xcd, 0xad, 0x9a, 0x8c, 0x69, 0xde, 0x08, 0x57, 0x01, 0x93, 0x59,
|
||||
0x99, 0x24, 0x09, 0x7d, 0xa1, 0xd8, 0x02, 0xe8, 0x93, 0x27, 0xd6, 0x9e, 0x69, 0x06, 0xfa, 0x8f, 0x0a, 0x0c, 0xa9,
|
||||
0x5e, 0xad, 0xaf, 0xd8, 0xec, 0x03, 0x02, 0xb7, 0x28, 0x53, 0x3c, 0x07, 0x6a, 0x7f, 0xf5, 0xbf, 0xb9, 0x4a, 0xaf,
|
||||
0x2b, 0x70, 0x73, 0xae, 0xda, 0x8a, 0xad, 0x13, 0x3a, 0x45, 0xad, 0x37, 0xd4, 0x9e, 0x14, 0x73, 0x91, 0x19, 0xe5,
|
||||
0x44, 0x59, 0x60, 0x6f, 0x2a, 0x40, 0x78, 0xc9, 0x05, 0xd3, 0xa5, 0x5b, 0xb3, 0x95, 0xd5, 0x13, 0x5c, 0x58, 0xe1,
|
||||
0x53, 0x0b, 0x9e, 0x05, 0xbe, 0x6f, 0x1f, 0x77, 0x1f, 0xdf, 0xf6, 0xf0, 0xff, 0x44, 0x82, 0x9e, 0x4b, 0x41, 0x98,
|
||||
0x75, 0x1d, 0xb7, 0xc8, 0x49, 0xf2, 0x84, 0x5e, 0x04, 0x21, 0x09, 0x9e, 0xbb, 0xe1, 0xf8, 0xbd, 0x7b, 0x46, 0x4e,
|
||||
0xf0, 0x7f, 0x76, 0xe6, 0x8c, 0x49, 0x70, 0x82, 0x9f, 0x30, 0x74, 0xc7, 0xc4, 0xff, 0x41, 0x49, 0xc1, 0xab, 0x2a,
|
||||
0xa1, 0xa2, 0x11, 0x40, 0x89, 0xd2, 0xb2, 0xb9, 0x81, 0x84, 0x66, 0x73, 0x29, 0x11, 0xfb, 0xeb, 0xa6, 0x6a, 0x24,
|
||||
0xf5, 0xd2, 0x07, 0xff, 0x4b, 0xa1, 0x96, 0x4c, 0xa8, 0xa2, 0x91, 0x75, 0x42, 0xbb, 0xe8, 0x5b, 0x8f, 0x36, 0x7a,
|
||||
0x4b, 0xcc, 0xc7, 0x3e, 0x38, 0x74, 0x1a, 0xc9, 0x67, 0x5c, 0x24, 0xd4, 0x68, 0x3c, 0x47, 0x23, 0xd7, 0xf6, 0x76,
|
||||
0xef, 0x3d, 0x33, 0xde, 0x0f, 0xfe, 0x34, 0xd6, 0xd7, 0xeb, 0x58, 0x2d, 0x66, 0x64, 0x55, 0x57, 0x42, 0x25, 0xb4,
|
||||
0xd4, 0xba, 0x8d, 0x3c, 0x6f, 0xb9, 0x5c, 0xba, 0xcb, 0x91, 0xdb, 0xc8, 0x99, 0x17, 0xfa, 0xbe, 0xef, 0x21, 0x07,
|
||||
0x25, 0x7d, 0x22, 0xd0, 0xf0, 0x84, 0x92, 0x12, 0xf8, 0xac, 0xd4, 0x1d, 0x9d, 0x3e, 0xda, 0xc0, 0x36, 0x36, 0x1c,
|
||||
0xe9, 0xf5, 0xb7, 0x03, 0x2b, 0xfc, 0xc0, 0x0a, 0xbc, 0x60, 0x16, 0xdd, 0xb9, 0x79, 0xd4, 0xb9, 0x79, 0xc6, 0x42,
|
||||
0x12, 0x12, 0xbf, 0xfb, 0x0d, 0x1d, 0x43, 0x0f, 0x2b, 0xe7, 0xde, 0x8a, 0x1c, 0xac, 0x0c, 0x55, 0x9f, 0x3a, 0xcf,
|
||||
0xf7, 0xb2, 0x81, 0xd9, 0x59, 0x04, 0xfe, 0xed, 0x86, 0x11, 0x78, 0x7b, 0x7a, 0xb8, 0x76, 0xc2, 0x2f, 0x87, 0x0c,
|
||||
0xc6, 0x5a, 0x19, 0x7c, 0x39, 0x65, 0x63, 0x32, 0x1e, 0x76, 0xc6, 0x8e, 0xa1, 0xf7, 0x2b, 0x32, 0x5e, 0x20, 0x47,
|
||||
0xed, 0x9c, 0x3a, 0x63, 0x36, 0x22, 0xa3, 0x01, 0x08, 0x52, 0xb8, 0x7d, 0x8a, 0x82, 0x07, 0x7b, 0xce, 0xe8, 0xc7,
|
||||
0x91, 0x97, 0x52, 0x3b, 0xa2, 0xf4, 0xd6, 0xf3, 0xe6, 0xd0, 0x73, 0xf7, 0x7b, 0x83, 0x39, 0x45, 0x29, 0x46, 0x06,
|
||||
0x74, 0x56, 0x5a, 0xd4, 0xc3, 0xc2, 0x2a, 0xf8, 0x0c, 0xb3, 0xbe, 0x11, 0xd4, 0x76, 0x75, 0x09, 0xc2, 0xda, 0x89,
|
||||
0x1a, 0x41, 0xe8, 0x4e, 0xac, 0xfb, 0x27, 0xda, 0xde, 0xec, 0xf3, 0x5f, 0x73, 0x8d, 0x65, 0xa6, 0x5d, 0x53, 0xb0,
|
||||
0xc7, 0xfb, 0xdd, 0x69, 0x93, 0xaf, 0x7f, 0x51, 0x1a, 0x65, 0xd0, 0xd7, 0x05, 0x17, 0x02, 0xe4, 0x15, 0xac, 0xf0,
|
||||
0xe6, 0x2e, 0x5e, 0xbe, 0x26, 0x2f, 0xf3, 0x5c, 0x82, 0x52, 0x11, 0xa1, 0xcf, 0x34, 0xd6, 0x40, 0xf6, 0xdf, 0x75,
|
||||
0x05, 0x77, 0x74, 0xfd, 0xc9, 0xdf, 0x70, 0xf2, 0x01, 0xf4, 0xb2, 0x91, 0x37, 0x83, 0x36, 0x03, 0x6d, 0x62, 0x2a,
|
||||
0x4c, 0x22, 0x4e, 0xd6, 0x2a, 0x57, 0x55, 0xd8, 0x3e, 0xac, 0xc0, 0x46, 0x3b, 0xed, 0xad, 0x57, 0x62, 0x17, 0xa8,
|
||||
0xeb, 0x38, 0xe7, 0x0b, 0x92, 0x55, 0xd8, 0x21, 0xb0, 0x5c, 0x7a, 0x55, 0x94, 0x3c, 0x20, 0xdd, 0x4f, 0x23, 0x32,
|
||||
0x94, 0xbe, 0x49, 0xe8, 0x4f, 0x3a, 0xc0, 0xab, 0xf5, 0xbb, 0xdc, 0x3a, 0x52, 0x58, 0xfb, 0x47, 0xb6, 0xbb, 0x60,
|
||||
0xd5, 0x1c, 0x48, 0x42, 0x74, 0xc9, 0xd5, 0x2d, 0xc0, 0xc9, 0x2f, 0xc5, 0x5a, 0x75, 0x83, 0x52, 0x05, 0x1e, 0x2b,
|
||||
0xcb, 0xa6, 0xe9, 0x60, 0x2e, 0x66, 0x7d, 0x83, 0xa4, 0x0f, 0xe9, 0x3d, 0x44, 0x4e, 0x05, 0x85, 0xde, 0xf3, 0x11,
|
||||
0x2c, 0x3b, 0x65, 0x09, 0x57, 0xa2, 0x75, 0x7b, 0xbb, 0xdf, 0x8c, 0x55, 0xcb, 0xc4, 0x7d, 0x41, 0x03, 0xd0, 0x94,
|
||||
0x0a, 0x36, 0x36, 0xa4, 0x4c, 0xbd, 0x20, 0xd3, 0xde, 0xa0, 0xc7, 0x76, 0xe4, 0xa3, 0x0d, 0x47, 0x8d, 0xa6, 0x5f,
|
||||
0xed, 0x35, 0xc6, 0x1e, 0x86, 0x26, 0xbd, 0xde, 0xda, 0xb7, 0x7e, 0xfc, 0x35, 0x07, 0xb9, 0xbe, 0x84, 0x0a, 0x32,
|
||||
0xdd, 0x48, 0x8b, 0x3e, 0x44, 0x2b, 0x98, 0x4a, 0x9d, 0xc3, 0x6f, 0xaf, 0x2e, 0xde, 0x27, 0x8d, 0x25, 0xed, 0xe3,
|
||||
0x5f, 0x71, 0x9b, 0x51, 0xf0, 0x15, 0x47, 0xc1, 0xdf, 0xc9, 0x91, 0x19, 0x06, 0x47, 0xdf, 0x50, 0xb4, 0xf3, 0xf7,
|
||||
0xfa, 0x76, 0x22, 0x98, 0x72, 0x7e, 0x86, 0x2d, 0xe1, 0xd8, 0x78, 0xe8, 0x9c, 0x8e, 0xed, 0x2d, 0xda, 0x47, 0x04,
|
||||
0x88, 0xbb, 0xeb, 0xeb, 0xd8, 0xdf, 0x4d, 0x8b, 0x4d, 0x9f, 0x6e, 0xa6, 0xcd, 0xca, 0x51, 0xfc, 0x07, 0x17, 0xb3,
|
||||
0x88, 0x8b, 0x12, 0x24, 0xd7, 0x5b, 0x84, 0x8b, 0x13, 0xa2, 0x9d, 0xeb, 0x4d, 0xcb, 0xf2, 0xdc, 0x9c, 0x8c, 0xdb,
|
||||
0xd5, 0xa4, 0xc0, 0x79, 0x62, 0x38, 0x21, 0x0a, 0xa0, 0xde, 0xf6, 0xe7, 0x5d, 0x47, 0x89, 0x9e, 0x8f, 0x1f, 0x6f,
|
||||
0x4d, 0xc2, 0x6d, 0x34, 0x5e, 0x96, 0xc3, 0x2a, 0x3e, 0x13, 0x51, 0x86, 0xc0, 0x41, 0xf6, 0x42, 0x05, 0xab, 0x79,
|
||||
0xb5, 0x8e, 0x14, 0xf6, 0x36, 0x07, 0x07, 0x0d, 0x2f, 0xb6, 0xd3, 0xb9, 0xd6, 0x8d, 0x40, 0xdb, 0x32, 0x07, 0x19,
|
||||
0xf9, 0x93, 0x9e, 0x70, 0x24, 0xcb, 0xf9, 0x5c, 0x45, 0xee, 0x48, 0x42, 0x3d, 0x99, 0xb2, 0xec, 0x66, 0x26, 0x9b,
|
||||
0xb9, 0xc8, 0x9d, 0xcc, 0x74, 0xda, 0xe8, 0x61, 0x50, 0xb0, 0x11, 0x64, 0x93, 0x61, 0x55, 0x14, 0xc5, 0x04, 0x43,
|
||||
0x01, 0x4e, 0xdf, 0xcb, 0xa2, 0xd0, 0x3d, 0x31, 0x62, 0x07, 0x30, 0xdd, 0xd0, 0x6c, 0xf4, 0x18, 0x71, 0x04, 0x3c,
|
||||
0x9e, 0xec, 0xdc, 0xf1, 0x27, 0xd8, 0xc2, 0x15, 0x2a, 0x69, 0xb1, 0xb6, 0x11, 0xe6, 0xb6, 0x66, 0x5c, 0x1c, 0xa2,
|
||||
0x37, 0x69, 0x32, 0x19, 0xc6, 0x0f, 0x86, 0xa5, 0x33, 0xd3, 0x0d, 0xa1, 0x09, 0x0e, 0x98, 0x7e, 0x86, 0x46, 0xe1,
|
||||
0xa9, 0xdf, 0xae, 0xb6, 0xee, 0x90, 0x20, 0x9b, 0x1d, 0x77, 0x51, 0xc1, 0x6a, 0xf2, 0x7d, 0xae, 0x34, 0x2f, 0xd6,
|
||||
0xce, 0x30, 0x83, 0x23, 0x4c, 0x16, 0x9c, 0xbd, 0x53, 0x64, 0x05, 0x10, 0x93, 0xce, 0x86, 0xc3, 0x35, 0xd4, 0x6a,
|
||||
0x88, 0xd3, 0x5e, 0x4d, 0x97, 0xa0, 0x77, 0x75, 0xfd, 0x1b, 0xb7, 0xc9, 0xc5, 0x4d, 0xcd, 0x24, 0x8e, 0x0a, 0x67,
|
||||
0xda, 0x60, 0x4c, 0xeb, 0xc8, 0x39, 0xc3, 0xbb, 0x1a, 0xb6, 0x8c, 0x32, 0xf4, 0x1c, 0x61, 0x76, 0xb3, 0x75, 0x17,
|
||||
0xef, 0xa0, 0x5d, 0x11, 0xd5, 0x54, 0x3c, 0x1f, 0xf8, 0x3a, 0x16, 0xe2, 0xef, 0xc3, 0x13, 0xe0, 0x75, 0x13, 0xb3,
|
||||
0xb7, 0x0b, 0xf5, 0x49, 0x71, 0xce, 0x02, 0xff, 0x27, 0x37, 0x92, 0x17, 0x45, 0x38, 0x2d, 0xf6, 0x91, 0x32, 0x63,
|
||||
0xd2, 0x94, 0x46, 0x97, 0x5a, 0xb1, 0xd7, 0xbf, 0x66, 0x4c, 0x66, 0xe0, 0x03, 0x05, 0x23, 0x8c, 0xef, 0x9b, 0x80,
|
||||
0xf0, 0x3c, 0xc1, 0x4e, 0x95, 0x1e, 0xb4, 0x2f, 0x64, 0x0c, 0x76, 0x47, 0x48, 0xdd, 0x69, 0x46, 0xfd, 0x59, 0x87,
|
||||
0x3e, 0x7d, 0xdd, 0x60, 0x7d, 0x60, 0xdb, 0x11, 0x33, 0xa2, 0x1b, 0x32, 0x84, 0xc0, 0x75, 0xdd, 0x78, 0x2a, 0xd3,
|
||||
0x4f, 0x15, 0x30, 0x05, 0x64, 0xc9, 0xb8, 0x76, 0xb1, 0x1a, 0x3b, 0xfe, 0xbe, 0x8e, 0x51, 0x29, 0xb2, 0xa6, 0x43,
|
||||
0xc1, 0xc6, 0xe5, 0xa8, 0x37, 0x70, 0x09, 0xda, 0x68, 0x32, 0x06, 0x46, 0x69, 0x6c, 0x46, 0x2e, 0x61, 0x5d, 0x4b,
|
||||
0x4b, 0xbc, 0x25, 0x2f, 0xb8, 0x79, 0xb2, 0xa4, 0x71, 0x97, 0xe4, 0x46, 0x83, 0x89, 0x73, 0xff, 0xbc, 0xea, 0xa8,
|
||||
0x0a, 0xc4, 0x0c, 0x27, 0xe9, 0x28, 0x24, 0xe8, 0x76, 0x06, 0x65, 0x53, 0x61, 0x58, 0x93, 0xcb, 0xcb, 0x77, 0xbf,
|
||||
0xa7, 0x06, 0xcc, 0xad, 0x1c, 0xf6, 0xa7, 0x5e, 0xcc, 0x10, 0x83, 0xd4, 0xe9, 0x49, 0xff, 0xa8, 0x6a, 0xb1, 0xbf,
|
||||
0xa0, 0x07, 0xf9, 0x1d, 0x1d, 0x9f, 0x86, 0xcd, 0x5e, 0x4f, 0xf7, 0xd7, 0x95, 0x4a, 0x7a, 0x89, 0x80, 0x62, 0x6f,
|
||||
0x58, 0xc4, 0x9e, 0x01, 0xdc, 0x9f, 0x97, 0x03, 0x1f, 0xc6, 0xe9, 0xe3, 0xd5, 0x4b, 0xf2, 0xb9, 0xc5, 0x26, 0x00,
|
||||
0x7d, 0xd8, 0x3a, 0xaf, 0xf0, 0x65, 0x58, 0x36, 0x79, 0xf2, 0xe9, 0xe3, 0xe5, 0xd5, 0xde, 0xc3, 0x79, 0xc7, 0x44,
|
||||
0x40, 0x64, 0xfd, 0xf3, 0x6e, 0x5e, 0x69, 0xde, 0x32, 0xa9, 0x3b, 0xb5, 0x8e, 0xe9, 0x22, 0x3b, 0x1f, 0xba, 0x73,
|
||||
0x7c, 0x03, 0x41, 0xef, 0x46, 0x2f, 0x98, 0x92, 0x1d, 0xaa, 0x9d, 0xb5, 0x7b, 0xb8, 0xbc, 0xfe, 0xb6, 0xbd, 0xfe,
|
||||
0xea, 0xbd, 0xee, 0xa5, 0xfb, 0x0f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
||||
|
||||
static constexpr size_t INDEX_SIZE = sizeof(INDEX_GZ);
|
||||
static constexpr const char *INDEX_CONTENT_ENCODING = "gzip";
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e,
|
||||
0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36,
|
||||
0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf,
|
||||
0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a,
|
||||
0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68,
|
||||
0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5,
|
||||
0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22,
|
||||
0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52,
|
||||
0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06,
|
||||
0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a,
|
||||
0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0,
|
||||
0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84,
|
||||
0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7,
|
||||
0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05,
|
||||
0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6,
|
||||
0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0,
|
||||
0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7,
|
||||
0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b,
|
||||
0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e,
|
||||
0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34,
|
||||
0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b,
|
||||
0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1,
|
||||
0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37,
|
||||
0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac,
|
||||
0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3,
|
||||
0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68,
|
||||
0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc,
|
||||
0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c,
|
||||
0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93,
|
||||
0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c,
|
||||
0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18,
|
||||
0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06,
|
||||
0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c,
|
||||
0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef,
|
||||
0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2,
|
||||
0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9,
|
||||
0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8,
|
||||
0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc,
|
||||
0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca,
|
||||
0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f,
|
||||
0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0,
|
||||
0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f,
|
||||
0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c,
|
||||
0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d,
|
||||
0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf,
|
||||
0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d,
|
||||
0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6,
|
||||
0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5,
|
||||
0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b,
|
||||
0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3,
|
||||
0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69,
|
||||
0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95,
|
||||
0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9,
|
||||
0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e,
|
||||
0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62,
|
||||
0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7,
|
||||
0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97,
|
||||
0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee,
|
||||
0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11,
|
||||
0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b,
|
||||
0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9,
|
||||
0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93,
|
||||
0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97,
|
||||
0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19,
|
||||
0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc,
|
||||
0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2,
|
||||
0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc,
|
||||
0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e,
|
||||
0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e,
|
||||
0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9,
|
||||
0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3,
|
||||
0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5,
|
||||
0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37,
|
||||
0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f,
|
||||
0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22,
|
||||
0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68,
|
||||
0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
||||
|
||||
#else // Brotli (default, smaller)
|
||||
const uint8_t INDEX_BR[] PROGMEM = {
|
||||
0x1f, 0xf8, 0x0a, 0x00, 0x64, 0x5a, 0xd3, 0xfa, 0xe7, 0xf3, 0x62, 0xd8, 0x06, 0x1b, 0xe9, 0x6a, 0x8a, 0x81, 0x2b,
|
||||
0x1b, 0xf8, 0x0a, 0x00, 0x64, 0x5a, 0xd3, 0xfa, 0xe7, 0xf3, 0x62, 0xd8, 0x06, 0x1b, 0xe9, 0x6a, 0x8a, 0x81, 0x2b,
|
||||
0xb5, 0x49, 0x14, 0x37, 0xdc, 0x9e, 0x1a, 0xcb, 0x56, 0x87, 0xfb, 0xff, 0xf7, 0x73, 0x75, 0x12, 0x0a, 0xd6, 0x48,
|
||||
0x84, 0xc6, 0x21, 0xa4, 0x6d, 0xb5, 0x71, 0xef, 0x13, 0xbe, 0x4e, 0x54, 0xf1, 0x64, 0x8f, 0x3f, 0xcc, 0x9a, 0x78,
|
||||
0xa5, 0x89, 0x25, 0xb3, 0xda, 0x2c, 0xa2, 0x32, 0x9c, 0x57, 0x07, 0x56, 0xbc, 0x34, 0x13, 0xff, 0x5c, 0x0a, 0xa1,
|
||||
@@ -152,8 +149,6 @@ const uint8_t INDEX_BR[] PROGMEM = {
|
||||
|
||||
// Backwards compatibility alias
|
||||
#define INDEX_GZ INDEX_BR
|
||||
static constexpr size_t INDEX_SIZE = sizeof(INDEX_BR);
|
||||
static constexpr const char *INDEX_CONTENT_ENCODING = "br";
|
||||
|
||||
#endif // USE_CAPTIVE_PORTAL_GZIP
|
||||
|
||||
|
||||
@@ -93,7 +93,9 @@ bool CH422GComponent::read_inputs_() {
|
||||
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
|
||||
auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "write failed for register 0x%X, error %d", reg, err);
|
||||
this->status_set_warning(buf);
|
||||
return false;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
@@ -104,7 +106,9 @@ uint8_t CH422GComponent::read_reg_(uint8_t reg) {
|
||||
uint8_t value;
|
||||
auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "read failed for register 0x%X, error %d", reg, err);
|
||||
this->status_set_warning(buf);
|
||||
return 0;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
||||
@@ -682,19 +682,19 @@ bool Climate::set_fan_mode_(ClimateFanMode mode) {
|
||||
return set_primary_mode(this->fan_mode, this->custom_fan_mode_, mode);
|
||||
}
|
||||
|
||||
bool Climate::set_custom_fan_mode_(const char *mode) {
|
||||
bool Climate::set_custom_fan_mode_(const char *mode, size_t len) {
|
||||
auto traits = this->get_traits();
|
||||
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode, traits.find_custom_fan_mode_(mode),
|
||||
this->has_custom_fan_mode());
|
||||
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode,
|
||||
traits.find_custom_fan_mode_(mode, len), this->has_custom_fan_mode());
|
||||
}
|
||||
|
||||
void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; }
|
||||
|
||||
bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); }
|
||||
|
||||
bool Climate::set_custom_preset_(const char *preset) {
|
||||
bool Climate::set_custom_preset_(const char *preset, size_t len) {
|
||||
auto traits = this->get_traits();
|
||||
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, traits.find_custom_preset_(preset),
|
||||
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, traits.find_custom_preset_(preset, len),
|
||||
this->has_custom_preset());
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "climate_mode.h"
|
||||
#include "climate_traits.h"
|
||||
|
||||
@@ -110,8 +111,8 @@ class ClimateCall {
|
||||
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||
const optional<ClimatePreset> &get_preset() const;
|
||||
const char *get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||
const char *get_custom_preset() const { return this->custom_preset_; }
|
||||
StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); }
|
||||
StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); }
|
||||
bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; }
|
||||
bool has_custom_preset() const { return this->custom_preset_ != nullptr; }
|
||||
|
||||
@@ -266,11 +267,11 @@ class Climate : public EntityBase {
|
||||
/// The active swing mode of the climate device.
|
||||
ClimateSwingMode swing_mode{CLIMATE_SWING_OFF};
|
||||
|
||||
/// Get the active custom fan mode (read-only access).
|
||||
const char *get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||
/// Get the active custom fan mode (read-only access). Returns StringRef.
|
||||
StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); }
|
||||
|
||||
/// Get the active custom preset (read-only access).
|
||||
const char *get_custom_preset() const { return this->custom_preset_; }
|
||||
/// Get the active custom preset (read-only access). Returns StringRef.
|
||||
StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); }
|
||||
|
||||
protected:
|
||||
friend ClimateCall;
|
||||
@@ -280,7 +281,9 @@ class Climate : public EntityBase {
|
||||
bool set_fan_mode_(ClimateFanMode mode);
|
||||
|
||||
/// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed.
|
||||
bool set_custom_fan_mode_(const char *mode);
|
||||
bool set_custom_fan_mode_(const char *mode) { return this->set_custom_fan_mode_(mode, strlen(mode)); }
|
||||
bool set_custom_fan_mode_(const char *mode, size_t len);
|
||||
bool set_custom_fan_mode_(StringRef mode) { return this->set_custom_fan_mode_(mode.c_str(), mode.size()); }
|
||||
/// Clear custom fan mode.
|
||||
void clear_custom_fan_mode_();
|
||||
|
||||
@@ -288,7 +291,9 @@ class Climate : public EntityBase {
|
||||
bool set_preset_(ClimatePreset preset);
|
||||
|
||||
/// Set custom preset. Reset primary preset. Return true if preset has been changed.
|
||||
bool set_custom_preset_(const char *preset);
|
||||
bool set_custom_preset_(const char *preset) { return this->set_custom_preset_(preset, strlen(preset)); }
|
||||
bool set_custom_preset_(const char *preset, size_t len);
|
||||
bool set_custom_preset_(StringRef preset) { return this->set_custom_preset_(preset.c_str(), preset.size()); }
|
||||
/// Clear custom preset.
|
||||
void clear_custom_preset_();
|
||||
|
||||
|
||||
@@ -8,20 +8,24 @@ static const char *const TAG = "copy.fan";
|
||||
|
||||
void CopyFan::setup() {
|
||||
source_->add_on_state_callback([this]() {
|
||||
this->state = source_->state;
|
||||
this->oscillating = source_->oscillating;
|
||||
this->speed = source_->speed;
|
||||
this->direction = source_->direction;
|
||||
this->set_preset_mode_(source_->get_preset_mode());
|
||||
this->copy_state_from_source_();
|
||||
this->publish_state();
|
||||
});
|
||||
|
||||
this->copy_state_from_source_();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void CopyFan::copy_state_from_source_() {
|
||||
this->state = source_->state;
|
||||
this->oscillating = source_->oscillating;
|
||||
this->speed = source_->speed;
|
||||
this->direction = source_->direction;
|
||||
this->set_preset_mode_(source_->get_preset_mode());
|
||||
this->publish_state();
|
||||
if (source_->has_preset_mode()) {
|
||||
this->set_preset_mode_(source_->get_preset_mode());
|
||||
} else {
|
||||
this->clear_preset_mode_();
|
||||
}
|
||||
}
|
||||
|
||||
void CopyFan::dump_config() { LOG_FAN("", "Copy Fan", this); }
|
||||
|
||||
@@ -16,7 +16,7 @@ class CopyFan : public fan::Fan, public Component {
|
||||
|
||||
protected:
|
||||
void control(const fan::FanCall &call) override;
|
||||
;
|
||||
void copy_state_from_source_();
|
||||
|
||||
fan::Fan *source_;
|
||||
};
|
||||
|
||||
@@ -28,24 +28,23 @@ void DebugComponent::dump_config() {
|
||||
#endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
#endif // USE_SENSOR
|
||||
|
||||
std::string device_info;
|
||||
device_info.reserve(256);
|
||||
char device_info_buffer[DEVICE_INFO_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
|
||||
device_info += ESPHOME_VERSION;
|
||||
size_t pos = buf_append(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION);
|
||||
|
||||
this->free_heap_ = get_free_heap_();
|
||||
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
|
||||
|
||||
get_device_info_(device_info);
|
||||
pos = get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>(device_info_buffer), pos);
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->device_info_ != nullptr) {
|
||||
if (device_info.length() > 255)
|
||||
device_info.resize(255);
|
||||
this->device_info_->publish_state(device_info);
|
||||
this->device_info_->publish_state(device_info_buffer, pos);
|
||||
}
|
||||
if (this->reset_reason_ != nullptr) {
|
||||
this->reset_reason_->publish_state(get_reset_reason_());
|
||||
char reset_reason_buffer[RESET_REASON_BUFFER_SIZE];
|
||||
this->reset_reason_->publish_state(
|
||||
get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reset_reason_buffer)));
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include <span>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
@@ -15,6 +22,44 @@
|
||||
namespace esphome {
|
||||
namespace debug {
|
||||
|
||||
static constexpr size_t DEVICE_INFO_BUFFER_SIZE = 256;
|
||||
static constexpr size_t RESET_REASON_BUFFER_SIZE = 128;
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
|
||||
// Format strings must be wrapped with PSTR() macro
|
||||
inline size_t buf_append_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(buf, size, pos, fmt, ...) buf_append_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__)
|
||||
#else
|
||||
/// Safely append formatted string to buffer, returning new position (capped at size)
|
||||
__attribute__((format(printf, 4, 5))) inline size_t buf_append(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
|
||||
|
||||
class DebugComponent : public PollingComponent {
|
||||
public:
|
||||
void loop() override;
|
||||
@@ -81,10 +126,10 @@ class DebugComponent : public PollingComponent {
|
||||
text_sensor::TextSensor *reset_reason_{nullptr};
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
std::string get_reset_reason_();
|
||||
std::string get_wakeup_cause_();
|
||||
const char *get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer);
|
||||
const char *get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer);
|
||||
uint32_t get_free_heap_();
|
||||
void get_device_info_(std::string &device_info);
|
||||
size_t get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos);
|
||||
void update_platform_();
|
||||
};
|
||||
|
||||
|
||||
@@ -58,24 +58,29 @@ void DebugComponent::on_shutdown() {
|
||||
global_preferences->sync();
|
||||
}
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() {
|
||||
std::string reset_reason;
|
||||
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||
char *buf = buffer.data();
|
||||
const size_t size = RESET_REASON_BUFFER_SIZE;
|
||||
|
||||
unsigned reason = esp_reset_reason();
|
||||
if (reason < sizeof(RESET_REASONS) / sizeof(RESET_REASONS[0])) {
|
||||
reset_reason = RESET_REASONS[reason];
|
||||
if (reason == ESP_RST_SW) {
|
||||
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
|
||||
char buffer[REBOOT_MAX_LEN]{};
|
||||
if (pref.load(&buffer)) {
|
||||
buffer[REBOOT_MAX_LEN - 1] = '\0';
|
||||
reset_reason = "Reboot request from " + std::string(buffer);
|
||||
char reboot_source[REBOOT_MAX_LEN]{};
|
||||
if (pref.load(&reboot_source)) {
|
||||
reboot_source[REBOOT_MAX_LEN - 1] = '\0';
|
||||
snprintf(buf, size, "Reboot request from %s", reboot_source);
|
||||
} else {
|
||||
snprintf(buf, size, "%s", RESET_REASONS[reason]);
|
||||
}
|
||||
} else {
|
||||
snprintf(buf, size, "%s", RESET_REASONS[reason]);
|
||||
}
|
||||
} else {
|
||||
reset_reason = "unknown source";
|
||||
snprintf(buf, size, "unknown source");
|
||||
}
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
|
||||
return reset_reason;
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *const WAKEUP_CAUSES[] = {
|
||||
@@ -94,7 +99,7 @@ static const char *const WAKEUP_CAUSES[] = {
|
||||
"BT",
|
||||
};
|
||||
|
||||
std::string DebugComponent::get_wakeup_cause_() {
|
||||
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||
const char *wake_reason;
|
||||
unsigned reason = esp_sleep_get_wakeup_cause();
|
||||
if (reason < sizeof(WAKEUP_CAUSES) / sizeof(WAKEUP_CAUSES[0])) {
|
||||
@@ -103,6 +108,7 @@ std::string DebugComponent::get_wakeup_cause_() {
|
||||
wake_reason = "unknown source";
|
||||
}
|
||||
ESP_LOGD(TAG, "Wakeup Reason: %s", wake_reason);
|
||||
// Return the static string directly - no need to copy to buffer
|
||||
return wake_reason;
|
||||
}
|
||||
|
||||
@@ -136,7 +142,10 @@ static constexpr ChipFeature CHIP_FEATURES[] = {
|
||||
{CHIP_FEATURE_WIFI_BGN, "2.4GHz WiFi"},
|
||||
};
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
|
||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||
char *buf = buffer.data();
|
||||
|
||||
#if defined(USE_ARDUINO)
|
||||
const char *flash_mode;
|
||||
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
|
||||
@@ -161,68 +170,66 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
default:
|
||||
flash_mode = "UNKNOWN";
|
||||
}
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s",
|
||||
ESP.getFlashChipSize() / 1024, // NOLINT
|
||||
ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT
|
||||
device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT
|
||||
"kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT
|
||||
device_info += flash_mode;
|
||||
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
|
||||
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
|
||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
||||
flash_mode);
|
||||
#endif
|
||||
|
||||
esp_chip_info_t info;
|
||||
esp_chip_info(&info);
|
||||
const char *model = ESPHOME_VARIANT;
|
||||
std::string features;
|
||||
|
||||
// Check each known feature bit
|
||||
// Build features string
|
||||
pos = buf_append(buf, size, pos, "|Chip: %s Features:", model);
|
||||
bool first_feature = true;
|
||||
for (const auto &feature : CHIP_FEATURES) {
|
||||
if (info.features & feature.bit) {
|
||||
features += feature.name;
|
||||
features += ", ";
|
||||
pos = buf_append(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name);
|
||||
first_feature = false;
|
||||
info.features &= ~feature.bit;
|
||||
}
|
||||
}
|
||||
if (info.features != 0)
|
||||
features += "Other:" + format_hex(info.features);
|
||||
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
|
||||
info.revision);
|
||||
device_info += "|Chip: ";
|
||||
device_info += model;
|
||||
device_info += " Features:";
|
||||
device_info += features;
|
||||
device_info += " Cores:" + to_string(info.cores);
|
||||
device_info += " Revision:" + to_string(info.revision);
|
||||
device_info += str_sprintf("|CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
|
||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", arch_get_cpu_freq_hz() / 1000000);
|
||||
if (info.features != 0) {
|
||||
pos = buf_append(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features);
|
||||
}
|
||||
ESP_LOGD(TAG, "Chip: Model=%s, Cores=%u, Revision=%u", model, info.cores, info.revision);
|
||||
pos = buf_append(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
|
||||
|
||||
uint32_t cpu_freq_mhz = arch_get_cpu_freq_hz() / 1000000;
|
||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
||||
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32 " MHz", cpu_freq_mhz);
|
||||
|
||||
// Framework detection
|
||||
device_info += "|Framework: ";
|
||||
#ifdef USE_ARDUINO
|
||||
ESP_LOGD(TAG, "Framework: Arduino");
|
||||
device_info += "Arduino";
|
||||
pos = buf_append(buf, size, pos, "|Framework: Arduino");
|
||||
#elif defined(USE_ESP32)
|
||||
ESP_LOGD(TAG, "Framework: ESP-IDF");
|
||||
device_info += "ESP-IDF";
|
||||
pos = buf_append(buf, size, pos, "|Framework: ESP-IDF");
|
||||
#else
|
||||
ESP_LOGW(TAG, "Framework: UNKNOWN");
|
||||
device_info += "UNKNOWN";
|
||||
pos = buf_append(buf, size, pos, "|Framework: UNKNOWN");
|
||||
#endif
|
||||
|
||||
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
|
||||
device_info += "|ESP-IDF: ";
|
||||
device_info += esp_get_idf_version();
|
||||
pos = buf_append(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version());
|
||||
|
||||
std::string mac = get_mac_address_pretty();
|
||||
ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str());
|
||||
device_info += "|EFuse MAC: ";
|
||||
device_info += mac;
|
||||
uint8_t mac[6];
|
||||
get_mac_address_raw(mac);
|
||||
ESP_LOGD(TAG, "EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
pos = buf_append(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4],
|
||||
mac[5]);
|
||||
|
||||
device_info += "|Reset: ";
|
||||
device_info += get_reset_reason_();
|
||||
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
||||
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason);
|
||||
|
||||
std::string wakeup_reason = this->get_wakeup_cause_();
|
||||
device_info += "|Wakeup: ";
|
||||
device_info += wakeup_reason;
|
||||
const char *wakeup_cause = get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||
pos = buf_append(buf, size, pos, "|Wakeup: %s", wakeup_cause);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void DebugComponent::update_platform_() {
|
||||
|
||||
@@ -8,19 +8,31 @@ namespace debug {
|
||||
|
||||
static const char *const TAG = "debug";
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() {
|
||||
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||
char *buf = buffer.data();
|
||||
#if !defined(CLANG_TIDY)
|
||||
return ESP.getResetReason().c_str();
|
||||
String reason = ESP.getResetReason(); // NOLINT
|
||||
snprintf_P(buf, RESET_REASON_BUFFER_SIZE, PSTR("%s"), reason.c_str());
|
||||
return buf;
|
||||
#else
|
||||
return "";
|
||||
buf[0] = '\0';
|
||||
return buf;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||
// ESP8266 doesn't have detailed wakeup cause like ESP32
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() {
|
||||
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
|
||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||
char *buf = buffer.data();
|
||||
|
||||
const char *flash_mode;
|
||||
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
|
||||
case FM_QIO:
|
||||
@@ -38,42 +50,45 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
default:
|
||||
flash_mode = "UNKNOWN";
|
||||
}
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s",
|
||||
ESP.getFlashChipSize() / 1024, // NOLINT
|
||||
ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT
|
||||
device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT
|
||||
"kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT
|
||||
device_info += flash_mode;
|
||||
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT
|
||||
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed, flash_mode);
|
||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 "kB Speed:%" PRIu32 "MHz Mode:%s", flash_size, flash_speed,
|
||||
flash_mode);
|
||||
|
||||
#if !defined(CLANG_TIDY)
|
||||
auto reset_reason = get_reset_reason_();
|
||||
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
||||
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||
uint32_t chip_id = ESP.getChipId();
|
||||
uint8_t boot_version = ESP.getBootVersion();
|
||||
uint8_t boot_mode = ESP.getBootMode();
|
||||
uint8_t cpu_freq = ESP.getCpuFreqMHz();
|
||||
uint32_t flash_chip_id = ESP.getFlashChipId();
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"Chip ID: 0x%08X\n"
|
||||
"Chip ID: 0x%08" PRIX32 "\n"
|
||||
"SDK Version: %s\n"
|
||||
"Core Version: %s\n"
|
||||
"Boot Version=%u Mode=%u\n"
|
||||
"CPU Frequency: %u\n"
|
||||
"Flash Chip ID=0x%08X\n"
|
||||
"Flash Chip ID=0x%08" PRIX32 "\n"
|
||||
"Reset Reason: %s\n"
|
||||
"Reset Info: %s",
|
||||
ESP.getChipId(), ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), ESP.getBootVersion(), ESP.getBootMode(),
|
||||
ESP.getCpuFreqMHz(), ESP.getFlashChipId(), reset_reason.c_str(), ESP.getResetInfo().c_str());
|
||||
chip_id, ESP.getSdkVersion(), ESP.getCoreVersion().c_str(), boot_version, boot_mode, cpu_freq, flash_chip_id,
|
||||
reset_reason, ESP.getResetInfo().c_str());
|
||||
|
||||
device_info += "|Chip: 0x" + format_hex(ESP.getChipId());
|
||||
device_info += "|SDK: ";
|
||||
device_info += ESP.getSdkVersion();
|
||||
device_info += "|Core: ";
|
||||
device_info += ESP.getCoreVersion().c_str();
|
||||
device_info += "|Boot: ";
|
||||
device_info += to_string(ESP.getBootVersion());
|
||||
device_info += "|Mode: " + to_string(ESP.getBootMode());
|
||||
device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz());
|
||||
device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId());
|
||||
device_info += "|Reset: ";
|
||||
device_info += reset_reason;
|
||||
device_info += "|";
|
||||
device_info += ESP.getResetInfo().c_str();
|
||||
pos = buf_append(buf, size, pos, "|Chip: 0x%08" PRIX32, chip_id);
|
||||
pos = buf_append(buf, size, pos, "|SDK: %s", ESP.getSdkVersion());
|
||||
pos = buf_append(buf, size, pos, "|Core: %s", ESP.getCoreVersion().c_str());
|
||||
pos = buf_append(buf, size, pos, "|Boot: %u", boot_version);
|
||||
pos = buf_append(buf, size, pos, "|Mode: %u", boot_mode);
|
||||
pos = buf_append(buf, size, pos, "|CPU: %u", cpu_freq);
|
||||
pos = buf_append(buf, size, pos, "|Flash: 0x%08" PRIX32, flash_chip_id);
|
||||
pos = buf_append(buf, size, pos, "|Reset: %s", reset_reason);
|
||||
pos = buf_append(buf, size, pos, "|%s", ESP.getResetInfo().c_str());
|
||||
#endif
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void DebugComponent::update_platform_() {
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
namespace esphome {
|
||||
namespace debug {
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() { return ""; }
|
||||
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
|
||||
|
||||
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() { return INT_MAX; }
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {}
|
||||
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) { return pos; }
|
||||
|
||||
void DebugComponent::update_platform_() {}
|
||||
|
||||
|
||||
@@ -7,31 +7,43 @@ namespace debug {
|
||||
|
||||
static const char *const TAG = "debug";
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_name(lt_get_reboot_reason()); }
|
||||
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||
// Return the static string directly
|
||||
return lt_get_reboot_reason_name(lt_get_reboot_reason());
|
||||
}
|
||||
|
||||
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
std::string reset_reason = get_reset_reason_();
|
||||
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
|
||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||
char *buf = buffer.data();
|
||||
|
||||
char reason_buffer[RESET_REASON_BUFFER_SIZE];
|
||||
const char *reset_reason = get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||
uint32_t flash_kib = lt_flash_get_size() / 1024;
|
||||
uint32_t ram_kib = lt_ram_get_size() / 1024;
|
||||
uint32_t mac_id = lt_cpu_get_mac_id();
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"LibreTiny Version: %s\n"
|
||||
"Chip: %s (%04x) @ %u MHz\n"
|
||||
"Chip ID: 0x%06X\n"
|
||||
"Chip ID: 0x%06" PRIX32 "\n"
|
||||
"Board: %s\n"
|
||||
"Flash: %u KiB / RAM: %u KiB\n"
|
||||
"Flash: %" PRIu32 " KiB / RAM: %" PRIu32 " KiB\n"
|
||||
"Reset Reason: %s",
|
||||
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), lt_cpu_get_mac_id(),
|
||||
lt_get_board_code(), lt_flash_get_size() / 1024, lt_ram_get_size() / 1024, reset_reason.c_str());
|
||||
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id,
|
||||
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
|
||||
|
||||
device_info += "|Version: ";
|
||||
device_info += LT_BANNER_STR + 10;
|
||||
device_info += "|Reset Reason: ";
|
||||
device_info += reset_reason;
|
||||
device_info += "|Chip Name: ";
|
||||
device_info += lt_cpu_get_model_name();
|
||||
device_info += "|Chip ID: 0x" + format_hex(lt_cpu_get_mac_id());
|
||||
device_info += "|Flash: " + to_string(lt_flash_get_size() / 1024) + " KiB";
|
||||
device_info += "|RAM: " + to_string(lt_ram_get_size() / 1024) + " KiB";
|
||||
pos = buf_append(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10);
|
||||
pos = buf_append(buf, size, pos, "|Reset Reason: %s", reset_reason);
|
||||
pos = buf_append(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name());
|
||||
pos = buf_append(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id);
|
||||
pos = buf_append(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib);
|
||||
pos = buf_append(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void DebugComponent::update_platform_() {
|
||||
|
||||
@@ -7,13 +7,21 @@ namespace debug {
|
||||
|
||||
static const char *const TAG = "debug";
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() { return ""; }
|
||||
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
|
||||
|
||||
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) { return ""; }
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() { return rp2040.getFreeHeap(); }
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu());
|
||||
device_info += "CPU Frequency: " + to_string(rp2040.f_cpu());
|
||||
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
|
||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||
char *buf = buffer.data();
|
||||
|
||||
uint32_t cpu_freq = rp2040.f_cpu();
|
||||
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
|
||||
pos = buf_append(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void DebugComponent::update_platform_() {}
|
||||
|
||||
@@ -15,14 +15,14 @@ static const char *const TAG = "debug";
|
||||
constexpr std::uintptr_t MBR_PARAM_PAGE_ADDR = 0xFFC;
|
||||
constexpr std::uintptr_t MBR_BOOTLOADER_ADDR = 0xFF8;
|
||||
|
||||
static void show_reset_reason(std::string &reset_reason, bool set, const char *reason) {
|
||||
static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set, const char *reason) {
|
||||
if (!set) {
|
||||
return;
|
||||
return pos;
|
||||
}
|
||||
if (!reset_reason.empty()) {
|
||||
reset_reason += ", ";
|
||||
if (pos > 0) {
|
||||
pos = buf_append(buf, size, pos, ", ");
|
||||
}
|
||||
reset_reason += reason;
|
||||
return buf_append(buf, size, pos, "%s", reason);
|
||||
}
|
||||
|
||||
static inline uint32_t read_mem_u32(uintptr_t addr) {
|
||||
@@ -56,33 +56,47 @@ static inline uint32_t sd_version_get() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() {
|
||||
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||
char *buf = buffer.data();
|
||||
const size_t size = RESET_REASON_BUFFER_SIZE;
|
||||
|
||||
uint32_t cause;
|
||||
auto ret = hwinfo_get_reset_cause(&cause);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Unable to get reset cause: %d", ret);
|
||||
return "";
|
||||
buf[0] = '\0';
|
||||
return buf;
|
||||
}
|
||||
std::string reset_reason;
|
||||
size_t pos = 0;
|
||||
|
||||
show_reset_reason(reset_reason, cause & RESET_PIN, "External pin");
|
||||
show_reset_reason(reset_reason, cause & RESET_SOFTWARE, "Software reset");
|
||||
show_reset_reason(reset_reason, cause & RESET_BROWNOUT, "Brownout (drop in voltage)");
|
||||
show_reset_reason(reset_reason, cause & RESET_POR, "Power-on reset (POR)");
|
||||
show_reset_reason(reset_reason, cause & RESET_WATCHDOG, "Watchdog timer expiration");
|
||||
show_reset_reason(reset_reason, cause & RESET_DEBUG, "Debug event");
|
||||
show_reset_reason(reset_reason, cause & RESET_SECURITY, "Security violation");
|
||||
show_reset_reason(reset_reason, cause & RESET_LOW_POWER_WAKE, "Waking up from low power mode");
|
||||
show_reset_reason(reset_reason, cause & RESET_CPU_LOCKUP, "CPU lock-up detected");
|
||||
show_reset_reason(reset_reason, cause & RESET_PARITY, "Parity error");
|
||||
show_reset_reason(reset_reason, cause & RESET_PLL, "PLL error");
|
||||
show_reset_reason(reset_reason, cause & RESET_CLOCK, "Clock error");
|
||||
show_reset_reason(reset_reason, cause & RESET_HARDWARE, "Hardware reset");
|
||||
show_reset_reason(reset_reason, cause & RESET_USER, "User reset");
|
||||
show_reset_reason(reset_reason, cause & RESET_TEMPERATURE, "Temperature reset");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_PIN, "External pin");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_SOFTWARE, "Software reset");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_BROWNOUT, "Brownout (drop in voltage)");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_POR, "Power-on reset (POR)");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_WATCHDOG, "Watchdog timer expiration");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_DEBUG, "Debug event");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_SECURITY, "Security violation");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_LOW_POWER_WAKE, "Waking up from low power mode");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_CPU_LOCKUP, "CPU lock-up detected");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_PARITY, "Parity error");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_PLL, "PLL error");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_CLOCK, "Clock error");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_HARDWARE, "Hardware reset");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_USER, "User reset");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_TEMPERATURE, "Temperature reset");
|
||||
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
|
||||
return reset_reason;
|
||||
// Ensure null termination if nothing was written
|
||||
if (pos == 0) {
|
||||
buf[0] = '\0';
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
const char *DebugComponent::get_wakeup_cause_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||
// Zephyr doesn't have detailed wakeup cause like ESP32
|
||||
return "";
|
||||
}
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() { return INT_MAX; }
|
||||
@@ -118,175 +132,183 @@ void DebugComponent::log_partition_info_() {
|
||||
flash_area_foreach(fa_cb, nullptr);
|
||||
}
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
std::string supply = "Main supply status: ";
|
||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) {
|
||||
supply += "Normal voltage.";
|
||||
} else {
|
||||
supply += "High voltage.";
|
||||
}
|
||||
ESP_LOGD(TAG, "%s", supply.c_str());
|
||||
device_info += "|" + supply;
|
||||
size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE> buffer, size_t pos) {
|
||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||
char *buf = buffer.data();
|
||||
|
||||
std::string reg0 = "Regulator stage 0: ";
|
||||
// Main supply status
|
||||
const char *supply_status =
|
||||
(nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage.";
|
||||
ESP_LOGD(TAG, "Main supply status: %s", supply_status);
|
||||
pos = buf_append(buf, size, pos, "|Main supply status: %s", supply_status);
|
||||
|
||||
// Regulator stage 0
|
||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) {
|
||||
reg0 += nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||
reg0 += ", ";
|
||||
const char *reg0_type = nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||
const char *reg0_voltage;
|
||||
switch (NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) {
|
||||
case (UICR_REGOUT0_VOUT_DEFAULT << UICR_REGOUT0_VOUT_Pos):
|
||||
reg0 += "1.8V (default)";
|
||||
reg0_voltage = "1.8V (default)";
|
||||
break;
|
||||
case (UICR_REGOUT0_VOUT_1V8 << UICR_REGOUT0_VOUT_Pos):
|
||||
reg0 += "1.8V";
|
||||
reg0_voltage = "1.8V";
|
||||
break;
|
||||
case (UICR_REGOUT0_VOUT_2V1 << UICR_REGOUT0_VOUT_Pos):
|
||||
reg0 += "2.1V";
|
||||
reg0_voltage = "2.1V";
|
||||
break;
|
||||
case (UICR_REGOUT0_VOUT_2V4 << UICR_REGOUT0_VOUT_Pos):
|
||||
reg0 += "2.4V";
|
||||
reg0_voltage = "2.4V";
|
||||
break;
|
||||
case (UICR_REGOUT0_VOUT_2V7 << UICR_REGOUT0_VOUT_Pos):
|
||||
reg0 += "2.7V";
|
||||
reg0_voltage = "2.7V";
|
||||
break;
|
||||
case (UICR_REGOUT0_VOUT_3V0 << UICR_REGOUT0_VOUT_Pos):
|
||||
reg0 += "3.0V";
|
||||
reg0_voltage = "3.0V";
|
||||
break;
|
||||
case (UICR_REGOUT0_VOUT_3V3 << UICR_REGOUT0_VOUT_Pos):
|
||||
reg0 += "3.3V";
|
||||
reg0_voltage = "3.3V";
|
||||
break;
|
||||
default:
|
||||
reg0 += "???V";
|
||||
reg0_voltage = "???V";
|
||||
}
|
||||
ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||
pos = buf_append(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||
} else {
|
||||
reg0 += "disabled";
|
||||
ESP_LOGD(TAG, "Regulator stage 0: disabled");
|
||||
pos = buf_append(buf, size, pos, "|Regulator stage 0: disabled");
|
||||
}
|
||||
ESP_LOGD(TAG, "%s", reg0.c_str());
|
||||
device_info += "|" + reg0;
|
||||
|
||||
std::string reg1 = "Regulator stage 1: ";
|
||||
reg1 += nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||
ESP_LOGD(TAG, "%s", reg1.c_str());
|
||||
device_info += "|" + reg1;
|
||||
// Regulator stage 1
|
||||
const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||
ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type);
|
||||
pos = buf_append(buf, size, pos, "|Regulator stage 1: %s", reg1_type);
|
||||
|
||||
std::string usb_power = "USB power state: ";
|
||||
// USB power state
|
||||
const char *usb_state;
|
||||
if (nrf_power_usbregstatus_vbusdet_get(NRF_POWER)) {
|
||||
if (nrf_power_usbregstatus_outrdy_get(NRF_POWER)) {
|
||||
/**< From the power viewpoint, USB is ready for working. */
|
||||
usb_power += "ready";
|
||||
usb_state = "ready";
|
||||
} else {
|
||||
/**< The USB power is detected, but USB power regulator is not ready. */
|
||||
usb_power += "connected (regulator is not ready)";
|
||||
usb_state = "connected (regulator is not ready)";
|
||||
}
|
||||
} else {
|
||||
/**< No power on USB lines detected. */
|
||||
usb_power += "disconected";
|
||||
usb_state = "disconnected";
|
||||
}
|
||||
ESP_LOGD(TAG, "%s", usb_power.c_str());
|
||||
device_info += "|" + usb_power;
|
||||
ESP_LOGD(TAG, "USB power state: %s", usb_state);
|
||||
pos = buf_append(buf, size, pos, "|USB power state: %s", usb_state);
|
||||
|
||||
// Power-fail comparator
|
||||
bool enabled;
|
||||
nrf_power_pof_thr_t pof_thr;
|
||||
|
||||
pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled);
|
||||
std::string pof = "Power-fail comparator: ";
|
||||
nrf_power_pof_thr_t pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled);
|
||||
if (enabled) {
|
||||
const char *pof_voltage;
|
||||
switch (pof_thr) {
|
||||
case POWER_POFCON_THRESHOLD_V17:
|
||||
pof += "1.7V";
|
||||
pof_voltage = "1.7V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V18:
|
||||
pof += "1.8V";
|
||||
pof_voltage = "1.8V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V19:
|
||||
pof += "1.9V";
|
||||
pof_voltage = "1.9V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V20:
|
||||
pof += "2.0V";
|
||||
pof_voltage = "2.0V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V21:
|
||||
pof += "2.1V";
|
||||
pof_voltage = "2.1V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V22:
|
||||
pof += "2.2V";
|
||||
pof_voltage = "2.2V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V23:
|
||||
pof += "2.3V";
|
||||
pof_voltage = "2.3V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V24:
|
||||
pof += "2.4V";
|
||||
pof_voltage = "2.4V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V25:
|
||||
pof += "2.5V";
|
||||
pof_voltage = "2.5V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V26:
|
||||
pof += "2.6V";
|
||||
pof_voltage = "2.6V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V27:
|
||||
pof += "2.7V";
|
||||
pof_voltage = "2.7V";
|
||||
break;
|
||||
case POWER_POFCON_THRESHOLD_V28:
|
||||
pof += "2.8V";
|
||||
pof_voltage = "2.8V";
|
||||
break;
|
||||
default:
|
||||
pof_voltage = "???V";
|
||||
break;
|
||||
}
|
||||
|
||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) {
|
||||
pof += ", VDDH: ";
|
||||
const char *vddh_voltage;
|
||||
switch (nrf_power_pofcon_vddh_get(NRF_POWER)) {
|
||||
case NRF_POWER_POFTHRVDDH_V27:
|
||||
pof += "2.7V";
|
||||
vddh_voltage = "2.7V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V28:
|
||||
pof += "2.8V";
|
||||
vddh_voltage = "2.8V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V29:
|
||||
pof += "2.9V";
|
||||
vddh_voltage = "2.9V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V30:
|
||||
pof += "3.0V";
|
||||
vddh_voltage = "3.0V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V31:
|
||||
pof += "3.1V";
|
||||
vddh_voltage = "3.1V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V32:
|
||||
pof += "3.2V";
|
||||
vddh_voltage = "3.2V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V33:
|
||||
pof += "3.3V";
|
||||
vddh_voltage = "3.3V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V34:
|
||||
pof += "3.4V";
|
||||
vddh_voltage = "3.4V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V35:
|
||||
pof += "3.5V";
|
||||
vddh_voltage = "3.5V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V36:
|
||||
pof += "3.6V";
|
||||
vddh_voltage = "3.6V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V37:
|
||||
pof += "3.7V";
|
||||
vddh_voltage = "3.7V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V38:
|
||||
pof += "3.8V";
|
||||
vddh_voltage = "3.8V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V39:
|
||||
pof += "3.9V";
|
||||
vddh_voltage = "3.9V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V40:
|
||||
pof += "4.0V";
|
||||
vddh_voltage = "4.0V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V41:
|
||||
pof += "4.1V";
|
||||
vddh_voltage = "4.1V";
|
||||
break;
|
||||
case NRF_POWER_POFTHRVDDH_V42:
|
||||
pof += "4.2V";
|
||||
vddh_voltage = "4.2V";
|
||||
break;
|
||||
default:
|
||||
vddh_voltage = "???V";
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage);
|
||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: %s", pof_voltage);
|
||||
}
|
||||
} else {
|
||||
pof += "disabled";
|
||||
ESP_LOGD(TAG, "Power-fail comparator: disabled");
|
||||
pos = buf_append(buf, size, pos, "|Power-fail comparator: disabled");
|
||||
}
|
||||
ESP_LOGD(TAG, "%s", pof.c_str());
|
||||
device_info += "|" + pof;
|
||||
|
||||
auto package = [](uint32_t value) {
|
||||
switch (value) {
|
||||
@@ -300,6 +322,8 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
return "Unspecified";
|
||||
};
|
||||
|
||||
char mac_pretty[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
get_mac_address_pretty_into_buffer(mac_pretty);
|
||||
ESP_LOGD(TAG,
|
||||
"Code page size: %u, code size: %u, device id: 0x%08x%08x\n"
|
||||
"Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x\n"
|
||||
@@ -308,10 +332,10 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
"RAM: %ukB, Flash: %ukB, production test: %sdone",
|
||||
NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0], NRF_FICR->ER[0],
|
||||
NRF_FICR->ER[1], NRF_FICR->ER[2], NRF_FICR->ER[3], NRF_FICR->IR[0], NRF_FICR->IR[1], NRF_FICR->IR[2],
|
||||
NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), get_mac_address_pretty().c_str(),
|
||||
NRF_FICR->INFO.PART, NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF,
|
||||
NRF_FICR->INFO.VARIANT >> 8 & 0xFF, NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE),
|
||||
NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not "));
|
||||
NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), mac_pretty, NRF_FICR->INFO.PART,
|
||||
NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF, NRF_FICR->INFO.VARIANT >> 8 & 0xFF,
|
||||
NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE), NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH,
|
||||
(NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not "));
|
||||
bool n_reset_enabled = NRF_UICR->PSELRESET[0] == NRF_UICR->PSELRESET[1] &&
|
||||
(NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) == UICR_PSELRESET_CONNECT_Connected
|
||||
<< UICR_PSELRESET_CONNECT_Pos;
|
||||
@@ -373,6 +397,8 @@ void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
"NRFFW %s\n"
|
||||
"NRFHW %s",
|
||||
uicr(NRF_UICR->NRFFW, 13).c_str(), uicr(NRF_UICR->NRFHW, 12).c_str());
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void DebugComponent::update_platform_() {}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace deep_sleep {
|
||||
// - ext0: Single pin wakeup using RTC GPIO (esp_sleep_enable_ext0_wakeup)
|
||||
// - ext1: Multiple pin wakeup (esp_sleep_enable_ext1_wakeup)
|
||||
// - Touch: Touch pad wakeup (esp_sleep_enable_touchpad_wakeup)
|
||||
// - GPIO wakeup: GPIO wakeup for non-RTC pins (esp_deep_sleep_enable_gpio_wakeup)
|
||||
// - GPIO wakeup: GPIO wakeup for RTC pins (esp_deep_sleep_enable_gpio_wakeup)
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
@@ -127,22 +127,14 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
defined(USE_ESP32_VARIANT_ESP32C61)
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
||||
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
|
||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
|
||||
} else if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLDOWN) {
|
||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
|
||||
}
|
||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||
gpio_hold_en(gpio_pin);
|
||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
|
||||
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
|
||||
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
|
||||
gpio_deep_sleep_hold_en();
|
||||
#endif
|
||||
// Make sure GPIO is in input mode, not all RTC GPIO pins are input by default
|
||||
gpio_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||
bool level = !this->wakeup_pin_->is_inverted();
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||
level = !level;
|
||||
}
|
||||
// Internal pullup/pulldown resistors are enabled automatically, when
|
||||
// ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS is set (by default it is)
|
||||
esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(),
|
||||
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
|
||||
}
|
||||
|
||||
@@ -63,13 +63,11 @@ def validate_auto_clear(value):
|
||||
return cv.boolean(value)
|
||||
|
||||
|
||||
def basic_display_schema(default_update_interval: str = "1s") -> cv.Schema:
|
||||
"""Create a basic display schema with configurable default update interval."""
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
).extend(cv.polling_component_schema(default_update_interval))
|
||||
BASIC_DISPLAY_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
|
||||
|
||||
def _validate_test_card(config):
|
||||
@@ -83,41 +81,34 @@ def _validate_test_card(config):
|
||||
return config
|
||||
|
||||
|
||||
def full_display_schema(default_update_interval: str = "1s") -> cv.Schema:
|
||||
"""Create a full display schema with configurable default update interval."""
|
||||
schema = basic_display_schema(default_update_interval).extend(
|
||||
{
|
||||
cv.Optional(CONF_ROTATION): validate_rotation,
|
||||
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
|
||||
cv.ensure_list(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DisplayPage),
|
||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
),
|
||||
cv.Length(min=1),
|
||||
),
|
||||
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
|
||||
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ROTATION): validate_rotation,
|
||||
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
|
||||
cv.ensure_list(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayOnPageChangeTrigger
|
||||
),
|
||||
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
|
||||
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
|
||||
cv.GenerateID(): cv.declare_id(DisplayPage),
|
||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
|
||||
): validate_auto_clear,
|
||||
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
||||
}
|
||||
)
|
||||
schema.add_extra(_validate_test_card)
|
||||
return schema
|
||||
|
||||
|
||||
BASIC_DISPLAY_SCHEMA = basic_display_schema("1s")
|
||||
FULL_DISPLAY_SCHEMA = full_display_schema("1s")
|
||||
cv.Length(min=1),
|
||||
),
|
||||
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayOnPageChangeTrigger
|
||||
),
|
||||
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
|
||||
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
|
||||
): validate_auto_clear,
|
||||
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
||||
}
|
||||
)
|
||||
FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card)
|
||||
|
||||
|
||||
async def setup_display_core_(var, config):
|
||||
|
||||
@@ -42,7 +42,8 @@ std::string MenuItemSelect::get_value_text() const {
|
||||
result = this->value_getter_.value()(this);
|
||||
} else {
|
||||
if (this->select_var_ != nullptr) {
|
||||
result = this->select_var_->current_option();
|
||||
auto option = this->select_var_->current_option();
|
||||
result.assign(option.c_str(), option.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ bool Dsmr::parse_telegram() {
|
||||
|
||||
// publish the telegram, after publishing the sensors so it can also trigger action based on latest values
|
||||
if (this->s_telegram_ != nullptr) {
|
||||
this->s_telegram_->publish_state(std::string(this->telegram_, this->bytes_read_));
|
||||
this->s_telegram_->publish_state(this->telegram_, this->bytes_read_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -82,8 +82,9 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
||||
light_effect->get_last_universe());
|
||||
auto effect_name = light_effect->get_name();
|
||||
ESP_LOGD(TAG, "Registering '%.*s' for universes %d-%d.", (int) effect_name.size(), effect_name.c_str(),
|
||||
light_effect->get_first_universe(), light_effect->get_last_universe());
|
||||
|
||||
light_effects_.push_back(light_effect);
|
||||
|
||||
@@ -98,8 +99,9 @@ void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
||||
light_effect->get_last_universe());
|
||||
auto effect_name = light_effect->get_name();
|
||||
ESP_LOGD(TAG, "Unregistering '%.*s' for universes %d-%d.", (int) effect_name.size(), effect_name.c_str(),
|
||||
light_effect->get_first_universe(), light_effect->get_last_universe());
|
||||
|
||||
// Swap with last element and pop for O(1) removal (order doesn't matter)
|
||||
*it = light_effects_.back();
|
||||
|
||||
@@ -58,8 +58,9 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
|
||||
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
|
||||
auto *input_data = packet.values + 1;
|
||||
|
||||
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name(), universe, output_offset,
|
||||
output_end);
|
||||
auto effect_name = get_name();
|
||||
ESP_LOGV(TAG, "Applying data for '%.*s' on %d universe, for %" PRId32 "-%d.", (int) effect_name.size(),
|
||||
effect_name.c_str(), universe, output_offset, output_end);
|
||||
|
||||
switch (channels_) {
|
||||
case E131_MONO:
|
||||
|
||||
@@ -31,7 +31,6 @@ from esphome.const import (
|
||||
CONF_TRANSFORM,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
CONF_WIDTH,
|
||||
SCHEDULER_DONT_RUN,
|
||||
)
|
||||
from esphome.cpp_generator import RawExpression
|
||||
from esphome.final_validate import full_config
|
||||
@@ -73,10 +72,12 @@ TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL]]
|
||||
class_name = epaper_spi_ns.class_(model.class_name, EPaperBase)
|
||||
minimum_update_interval = update_interval(
|
||||
model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s")
|
||||
)
|
||||
cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required
|
||||
return (
|
||||
display.full_display_schema("60s")
|
||||
.extend(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
spi.spi_device_schema(
|
||||
cs_pin_required=False,
|
||||
default_mode="MODE0",
|
||||
@@ -93,6 +94,9 @@ def model_schema(config):
|
||||
{
|
||||
cv.Optional(CONF_ROTATION, default=0): validate_rotation,
|
||||
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
|
||||
cv.Optional(CONF_UPDATE_INTERVAL, default=cv.UNDEFINED): cv.All(
|
||||
update_interval, cv.Range(min=minimum_update_interval)
|
||||
),
|
||||
cv.Optional(CONF_TRANSFORM): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||
@@ -146,22 +150,15 @@ def _final_validate(config):
|
||||
global_config = full_config.get()
|
||||
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
|
||||
|
||||
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||
if (
|
||||
CONF_LAMBDA not in config
|
||||
and CONF_PAGES not in config
|
||||
and LVGL_DOMAIN not in global_config
|
||||
):
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
|
||||
interval = config[CONF_UPDATE_INTERVAL]
|
||||
if interval != SCHEDULER_DONT_RUN:
|
||||
model = MODELS[config[CONF_MODEL]]
|
||||
minimum = update_interval(model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s"))
|
||||
if interval < minimum:
|
||||
raise cv.Invalid(
|
||||
f"update_interval must be at least {minimum} for {model.name}, got {interval}"
|
||||
)
|
||||
if CONF_LAMBDA not in config and CONF_PAGES not in config:
|
||||
if LVGL_DOMAIN in global_config:
|
||||
if CONF_UPDATE_INTERVAL not in config:
|
||||
config[CONF_UPDATE_INTERVAL] = update_interval("never")
|
||||
else:
|
||||
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
elif CONF_UPDATE_INTERVAL not in config:
|
||||
config[CONF_UPDATE_INTERVAL] = update_interval("1min")
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -116,9 +116,8 @@ void ES8388::setup() {
|
||||
if (this->dac_output_select_ != nullptr) {
|
||||
auto dac_power = this->get_dac_power();
|
||||
if (dac_power.has_value()) {
|
||||
auto dac_power_str = this->dac_output_select_->at(dac_power.value());
|
||||
if (dac_power_str.has_value()) {
|
||||
this->dac_output_select_->publish_state(dac_power_str.value());
|
||||
if (this->dac_output_select_->has_index(dac_power.value())) {
|
||||
this->dac_output_select_->publish_state(dac_power.value());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unknown DAC output power value: %d", dac_power.value());
|
||||
}
|
||||
@@ -127,9 +126,8 @@ void ES8388::setup() {
|
||||
if (this->adc_input_mic_select_ != nullptr) {
|
||||
auto mic_input = this->get_mic_input();
|
||||
if (mic_input.has_value()) {
|
||||
auto mic_input_str = this->adc_input_mic_select_->at(mic_input.value());
|
||||
if (mic_input_str.has_value()) {
|
||||
this->adc_input_mic_select_->publish_state(mic_input_str.value());
|
||||
if (this->adc_input_mic_select_->has_index(mic_input.value())) {
|
||||
this->adc_input_mic_select_->publish_state(mic_input.value());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unknown ADC input mic value: %d", mic_input.value());
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ from esphome.const import (
|
||||
CONF_IGNORE_EFUSE_MAC_CRC,
|
||||
CONF_LOG_LEVEL,
|
||||
CONF_NAME,
|
||||
CONF_OTA,
|
||||
CONF_PATH,
|
||||
CONF_PLATFORM_VERSION,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
@@ -620,11 +621,18 @@ def final_validate(config):
|
||||
)
|
||||
)
|
||||
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
|
||||
safe_mode_config = full_config.get(CONF_SAFE_MODE)
|
||||
if safe_mode_config is None or safe_mode_config.get(CONF_DISABLED, False):
|
||||
_LOGGER.warning(
|
||||
"OTA rollback requires safe_mode, disabling rollback support"
|
||||
)
|
||||
# "disabled: false" means safe mode *is* enabled.
|
||||
safe_mode_config = full_config.get(CONF_SAFE_MODE, {CONF_DISABLED: True})
|
||||
safe_mode_enabled = not safe_mode_config[CONF_DISABLED]
|
||||
ota_enabled = CONF_OTA in full_config
|
||||
# Both need to be enabled for rollback to work
|
||||
if not (ota_enabled and safe_mode_enabled):
|
||||
# But only warn if ota is even possible
|
||||
if ota_enabled:
|
||||
_LOGGER.warning(
|
||||
"OTA rollback requires safe_mode, disabling rollback support"
|
||||
)
|
||||
# disable the rollback feature anyway since it can't be used.
|
||||
advanced[CONF_ENABLE_OTA_ROLLBACK] = False
|
||||
if errs:
|
||||
raise cv.MultipleInvalid(errs)
|
||||
@@ -652,9 +660,6 @@ CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
|
||||
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
|
||||
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
|
||||
|
||||
# Ring buffer IRAM requirement tracking
|
||||
KEY_RINGBUF_IN_IRAM = "ringbuf_in_iram"
|
||||
|
||||
|
||||
def require_vfs_select() -> None:
|
||||
"""Mark that VFS select support is required by a component.
|
||||
@@ -674,17 +679,6 @@ def require_vfs_dir() -> None:
|
||||
CORE.data[KEY_VFS_DIR_REQUIRED] = True
|
||||
|
||||
|
||||
def enable_ringbuf_in_iram() -> None:
|
||||
"""Keep ring buffer functions in IRAM instead of moving them to flash.
|
||||
|
||||
Call this from components that use esphome/core/ring_buffer.cpp and need
|
||||
the ring buffer functions to remain in IRAM for performance reasons
|
||||
(e.g., voice assistants, audio components).
|
||||
This prevents CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH from being enabled.
|
||||
"""
|
||||
CORE.data[KEY_RINGBUF_IN_IRAM] = True
|
||||
|
||||
|
||||
def _parse_idf_component(value: str) -> ConfigType:
|
||||
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
|
||||
# Match operator followed by version-like string (digit or *)
|
||||
@@ -1096,18 +1090,14 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True)
|
||||
|
||||
# Place ring buffer functions into flash instead of IRAM by default
|
||||
# This saves IRAM but may impact performance for audio/voice components.
|
||||
# Components that need ring buffer in IRAM call enable_ringbuf_in_iram().
|
||||
# Users can also set ringbuf_in_iram: true to force IRAM placement.
|
||||
# In ESP-IDF 6.0 flash placement becomes the default.
|
||||
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM] or CORE.data.get(
|
||||
KEY_RINGBUF_IN_IRAM, False
|
||||
):
|
||||
# User config or component requires ring buffer in IRAM for performance
|
||||
# This saves IRAM. In ESP-IDF 6.0 flash placement becomes the default.
|
||||
# Users can set ringbuf_in_iram: true as an escape hatch if they encounter issues.
|
||||
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM]:
|
||||
# User requests ring buffer in IRAM
|
||||
# IDF 6.0+: will need CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=n
|
||||
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH", False)
|
||||
else:
|
||||
# No component needs it - place in flash to save IRAM
|
||||
# Place in flash to save IRAM (default)
|
||||
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True)
|
||||
|
||||
# Place heap functions into flash to save IRAM (~4-6KB savings)
|
||||
|
||||
@@ -85,6 +85,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
|
||||
break;
|
||||
}
|
||||
gpio_set_intr_type(this->get_pin_num(), idf_type);
|
||||
gpio_intr_enable(this->get_pin_num());
|
||||
if (!isr_service_installed) {
|
||||
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
|
||||
if (res != ESP_OK) {
|
||||
@@ -94,7 +95,6 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
|
||||
isr_service_installed = true;
|
||||
}
|
||||
gpio_isr_handler_add(this->get_pin_num(), func, arg);
|
||||
gpio_intr_enable(this->get_pin_num());
|
||||
}
|
||||
|
||||
size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const {
|
||||
|
||||
@@ -98,10 +98,6 @@ void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
|
||||
void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
|
||||
this->advertising_set_manufacturer_data(std::span<const uint8_t>(data));
|
||||
}
|
||||
|
||||
void ESP32BLE::advertising_set_manufacturer_data(std::span<const uint8_t> data) {
|
||||
this->advertising_init_();
|
||||
this->advertising_->set_manufacturer_data(data);
|
||||
this->advertising_start();
|
||||
|
||||
@@ -118,7 +118,6 @@ class ESP32BLE : public Component {
|
||||
void advertising_start();
|
||||
void advertising_set_service_data(const std::vector<uint8_t> &data);
|
||||
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
|
||||
void advertising_set_manufacturer_data(std::span<const uint8_t> data);
|
||||
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
|
||||
void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name);
|
||||
void advertising_add_service_uuid(ESPBTUUID uuid);
|
||||
|
||||
@@ -59,10 +59,6 @@ void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
|
||||
void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
|
||||
this->set_manufacturer_data(std::span<const uint8_t>(data));
|
||||
}
|
||||
|
||||
void BLEAdvertising::set_manufacturer_data(std::span<const uint8_t> data) {
|
||||
delete[] this->advertising_data_.p_manufacturer_data;
|
||||
this->advertising_data_.p_manufacturer_data = nullptr;
|
||||
this->advertising_data_.manufacturer_len = data.size();
|
||||
|
||||
@@ -37,7 +37,6 @@ class BLEAdvertising {
|
||||
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
|
||||
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
|
||||
void set_manufacturer_data(const std::vector<uint8_t> &data);
|
||||
void set_manufacturer_data(std::span<const uint8_t> data);
|
||||
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
|
||||
void set_service_data(const std::vector<uint8_t> &data);
|
||||
void set_service_data(std::span<const uint8_t> data);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "esp32_ble_beacon.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
|
||||
@@ -15,10 +15,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
|
||||
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
|
||||
new Trigger<std::vector<uint8_t>, uint16_t>();
|
||||
characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
|
||||
// Convert span to vector for trigger - copy is necessary because:
|
||||
// 1. Trigger stores the data for use in automation actions that execute later
|
||||
// 2. The span is only valid during this callback (points to temporary BLE stack data)
|
||||
// 3. User lambdas in automations need persistent data they can access asynchronously
|
||||
// Convert span to vector for trigger
|
||||
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
|
||||
});
|
||||
return on_write_trigger;
|
||||
@@ -30,10 +27,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write
|
||||
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
|
||||
new Trigger<std::vector<uint8_t>, uint16_t>();
|
||||
descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
|
||||
// Convert span to vector for trigger - copy is necessary because:
|
||||
// 1. Trigger stores the data for use in automation actions that execute later
|
||||
// 2. The span is only valid during this callback (points to temporary BLE stack data)
|
||||
// 3. User lambdas in automations need persistent data they can access asynchronously
|
||||
// Convert span to vector for trigger
|
||||
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
|
||||
});
|
||||
return on_write_trigger;
|
||||
|
||||
@@ -429,9 +429,11 @@ void ESP32Camera::framebuffer_task(void *pv) {
|
||||
camera_fb_t *framebuffer = esp_camera_fb_get();
|
||||
xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
|
||||
// Only wake the main loop if there's a pending request to consume the frame
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||
if (that->has_requested_image_()) {
|
||||
App.wake_loop_threadsafe();
|
||||
}
|
||||
#endif
|
||||
// return is no-op for config with 1 fb
|
||||
xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
|
||||
esp_camera_fb_return(framebuffer);
|
||||
|
||||
@@ -19,6 +19,7 @@ from esphome.components.esp32 import (
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MODE,
|
||||
CONF_RX_PIN,
|
||||
CONF_RX_QUEUE_LEN,
|
||||
CONF_TX_PIN,
|
||||
@@ -33,6 +34,13 @@ CONF_TX_ENQUEUE_TIMEOUT = "tx_enqueue_timeout"
|
||||
esp32_can_ns = cg.esphome_ns.namespace("esp32_can")
|
||||
esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent)
|
||||
|
||||
# Mode options - consistent with MCP2515 component
|
||||
CanMode = esp32_can_ns.enum("CanMode")
|
||||
CAN_MODES = {
|
||||
"NORMAL": CanMode.CAN_MODE_NORMAL,
|
||||
"LISTENONLY": CanMode.CAN_MODE_LISTEN_ONLY,
|
||||
}
|
||||
|
||||
# Currently the driver only supports a subset of the bit rates defined in canbus
|
||||
# The supported bit rates differ between ESP32 variants.
|
||||
# See ESP-IDF Programming Guide --> API Reference --> Two-Wire Automotive Interface (TWAI)
|
||||
@@ -95,6 +103,7 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
|
||||
cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate,
|
||||
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_MODE, default="NORMAL"): cv.enum(CAN_MODES, upper=True),
|
||||
cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t,
|
||||
cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t,
|
||||
cv.Optional(CONF_TX_ENQUEUE_TIMEOUT): cv.positive_time_period_milliseconds,
|
||||
@@ -117,6 +126,7 @@ async def to_code(config):
|
||||
|
||||
cg.add(var.set_rx(config[CONF_RX_PIN]))
|
||||
cg.add(var.set_tx(config[CONF_TX_PIN]))
|
||||
cg.add(var.set_mode(config[CONF_MODE]))
|
||||
if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None:
|
||||
cg.add(var.set_rx_queue_len(rx_queue_len))
|
||||
if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None:
|
||||
|
||||
@@ -75,8 +75,15 @@ bool ESP32Can::setup_internal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select TWAI mode based on configuration
|
||||
twai_mode_t twai_mode = (this->mode_ == CAN_MODE_LISTEN_ONLY) ? TWAI_MODE_LISTEN_ONLY : TWAI_MODE_NORMAL;
|
||||
|
||||
if (this->mode_ == CAN_MODE_LISTEN_ONLY) {
|
||||
ESP_LOGI(TAG, "CAN bus configured in LISTEN_ONLY mode (passive, no ACKs)");
|
||||
}
|
||||
|
||||
twai_general_config_t g_config =
|
||||
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL);
|
||||
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, twai_mode);
|
||||
g_config.controller_id = next_twai_ctrl_num++;
|
||||
if (this->tx_queue_len_.has_value()) {
|
||||
g_config.tx_queue_len = this->tx_queue_len_.value();
|
||||
@@ -111,6 +118,12 @@ bool ESP32Can::setup_internal() {
|
||||
}
|
||||
|
||||
canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
|
||||
// In listen-only mode, we cannot transmit
|
||||
if (this->mode_ == CAN_MODE_LISTEN_ONLY) {
|
||||
ESP_LOGW(TAG, "Cannot send messages in LISTEN_ONLY mode");
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
if (this->twai_handle_ == nullptr) {
|
||||
// not setup yet or setup failed
|
||||
return canbus::ERROR_FAIL;
|
||||
|
||||
@@ -10,10 +10,16 @@
|
||||
namespace esphome {
|
||||
namespace esp32_can {
|
||||
|
||||
enum CanMode : uint8_t {
|
||||
CAN_MODE_NORMAL = 0,
|
||||
CAN_MODE_LISTEN_ONLY = 1,
|
||||
};
|
||||
|
||||
class ESP32Can : public canbus::Canbus {
|
||||
public:
|
||||
void set_rx(int rx) { rx_ = rx; }
|
||||
void set_tx(int tx) { tx_ = tx; }
|
||||
void set_mode(CanMode mode) { mode_ = mode; }
|
||||
void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; }
|
||||
void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; }
|
||||
void set_tx_enqueue_timeout_ms(uint32_t tx_enqueue_timeout_ms) {
|
||||
@@ -28,6 +34,7 @@ class ESP32Can : public canbus::Canbus {
|
||||
|
||||
int rx_{-1};
|
||||
int tx_{-1};
|
||||
CanMode mode_{CAN_MODE_NORMAL};
|
||||
TickType_t tx_enqueue_timeout_ticks_{};
|
||||
optional<uint32_t> tx_queue_len_{};
|
||||
optional<uint32_t> rx_queue_len_{};
|
||||
|
||||
@@ -93,9 +93,9 @@ async def to_code(config):
|
||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}"
|
||||
if framework_ver >= cv.Version(5, 5, 0):
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.2")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.3")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.7.0")
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.4")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.4")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.9.3")
|
||||
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")
|
||||
|
||||
@@ -4,18 +4,24 @@ from typing import Any
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, update
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_PATH, CONF_RAW_DATA_ID
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.const import CONF_ID, CONF_PATH, CONF_SOURCE, CONF_TYPE
|
||||
from esphome.core import CORE, ID, HexInt
|
||||
|
||||
CODEOWNERS = ["@swoboda1337"]
|
||||
AUTO_LOAD = ["sha256", "watchdog"]
|
||||
AUTO_LOAD = ["sha256", "watchdog", "json"]
|
||||
DEPENDENCIES = ["esp32_hosted"]
|
||||
|
||||
CONF_SHA256 = "sha256"
|
||||
CONF_HTTP_REQUEST_ID = "http_request_id"
|
||||
|
||||
TYPE_EMBEDDED = "embedded"
|
||||
TYPE_HTTP = "http"
|
||||
|
||||
esp32_hosted_ns = cg.esphome_ns.namespace("esp32_hosted")
|
||||
http_request_ns = cg.esphome_ns.namespace("http_request")
|
||||
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
|
||||
Esp32HostedUpdate = esp32_hosted_ns.class_(
|
||||
"Esp32HostedUpdate", update.UpdateEntity, cg.Component
|
||||
"Esp32HostedUpdate", update.UpdateEntity, cg.PollingComponent
|
||||
)
|
||||
|
||||
|
||||
@@ -30,12 +36,29 @@ def _validate_sha256(value: Any) -> str:
|
||||
return value
|
||||
|
||||
|
||||
BASE_SCHEMA = update.update_schema(Esp32HostedUpdate, device_class="firmware").extend(
|
||||
cv.polling_component_schema("6h")
|
||||
)
|
||||
|
||||
EMBEDDED_SCHEMA = BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_PATH): cv.file_,
|
||||
cv.Required(CONF_SHA256): _validate_sha256,
|
||||
}
|
||||
)
|
||||
|
||||
HTTP_SCHEMA = BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||
cv.Required(CONF_SOURCE): cv.url,
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
update.update_schema(Esp32HostedUpdate, device_class="firmware").extend(
|
||||
cv.typed_schema(
|
||||
{
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
cv.Required(CONF_PATH): cv.file_,
|
||||
cv.Required(CONF_SHA256): _validate_sha256,
|
||||
TYPE_EMBEDDED: EMBEDDED_SCHEMA,
|
||||
TYPE_HTTP: HTTP_SCHEMA,
|
||||
}
|
||||
),
|
||||
esp32.only_on_variant(
|
||||
@@ -48,6 +71,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
def _validate_firmware(config: dict[str, Any]) -> None:
|
||||
if config[CONF_TYPE] != TYPE_EMBEDDED:
|
||||
return
|
||||
|
||||
path = CORE.relative_config_path(config[CONF_PATH])
|
||||
with open(path, "rb") as f:
|
||||
firmware_data = f.read()
|
||||
@@ -65,14 +91,22 @@ FINAL_VALIDATE_SCHEMA = _validate_firmware
|
||||
async def to_code(config: dict[str, Any]) -> None:
|
||||
var = await update.new_update(config)
|
||||
|
||||
path = config[CONF_PATH]
|
||||
with open(CORE.relative_config_path(path), "rb") as f:
|
||||
firmware_data = f.read()
|
||||
rhs = [HexInt(x) for x in firmware_data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
if config[CONF_TYPE] == TYPE_EMBEDDED:
|
||||
path = config[CONF_PATH]
|
||||
with open(CORE.relative_config_path(path), "rb") as f:
|
||||
firmware_data = f.read()
|
||||
rhs = [HexInt(x) for x in firmware_data]
|
||||
arr_id = ID(f"{config[CONF_ID]}_data", is_declaration=True, type=cg.uint8)
|
||||
prog_arr = cg.progmem_array(arr_id, rhs)
|
||||
|
||||
sha256_bytes = bytes.fromhex(config[CONF_SHA256])
|
||||
cg.add(var.set_firmware_sha256([HexInt(b) for b in sha256_bytes]))
|
||||
cg.add(var.set_firmware_data(prog_arr))
|
||||
cg.add(var.set_firmware_size(len(firmware_data)))
|
||||
else:
|
||||
http_request_var = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
|
||||
cg.add(var.set_http_request_parent(http_request_var))
|
||||
cg.add(var.set_source_url(config[CONF_SOURCE]))
|
||||
cg.add_define("USE_ESP32_HOSTED_HTTP_UPDATE")
|
||||
|
||||
sha256_bytes = bytes.fromhex(config[CONF_SHA256])
|
||||
cg.add(var.set_firmware_sha256([HexInt(b) for b in sha256_bytes]))
|
||||
cg.add(var.set_firmware_data(prog_arr))
|
||||
cg.add(var.set_firmware_size(len(firmware_data)))
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
#include <esp_image_format.h>
|
||||
#include <esp_app_desc.h>
|
||||
#include <esp_hosted.h>
|
||||
#include <esp_hosted_host_fw_ver.h>
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include <esp_hosted_ota.h>
|
||||
@@ -16,18 +22,50 @@ namespace esphome::esp32_hosted {
|
||||
|
||||
static const char *const TAG = "esp32_hosted.update";
|
||||
|
||||
// older coprocessor firmware versions have a 1500-byte limit per RPC call
|
||||
// Older coprocessor firmware versions have a 1500-byte limit per RPC call
|
||||
constexpr size_t CHUNK_SIZE = 1500;
|
||||
|
||||
// Compile-time version string from esp_hosted_host_fw_ver.h macros
|
||||
#define STRINGIFY_(x) #x
|
||||
#define STRINGIFY(x) STRINGIFY_(x)
|
||||
static const char *const ESP_HOSTED_VERSION_STR = STRINGIFY(ESP_HOSTED_VERSION_MAJOR_1) "." STRINGIFY(
|
||||
ESP_HOSTED_VERSION_MINOR_1) "." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1);
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
// Parse version string "major.minor.patch" into components
|
||||
// Returns true if parsing succeeded
|
||||
static bool parse_version(const std::string &version_str, int &major, int &minor, int &patch) {
|
||||
major = minor = patch = 0;
|
||||
if (sscanf(version_str.c_str(), "%d.%d.%d", &major, &minor, &patch) >= 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare two versions, returns:
|
||||
// -1 if v1 < v2
|
||||
// 0 if v1 == v2
|
||||
// 1 if v1 > v2
|
||||
static int compare_versions(int major1, int minor1, int patch1, int major2, int minor2, int patch2) {
|
||||
if (major1 != major2)
|
||||
return major1 < major2 ? -1 : 1;
|
||||
if (minor1 != minor2)
|
||||
return minor1 < minor2 ? -1 : 1;
|
||||
if (patch1 != patch2)
|
||||
return patch1 < patch2 ? -1 : 1;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Esp32HostedUpdate::setup() {
|
||||
this->update_info_.title = "ESP32 Hosted Coprocessor";
|
||||
|
||||
// if wifi is not present, connect to the coprocessor
|
||||
#ifndef USE_WIFI
|
||||
// If WiFi is not present, connect to the coprocessor
|
||||
esp_hosted_connect_to_slave(); // NOLINT
|
||||
#endif
|
||||
|
||||
// get coprocessor version
|
||||
// Get coprocessor version
|
||||
esp_hosted_coprocessor_fwver_t ver_info;
|
||||
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
|
||||
this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
|
||||
@@ -36,7 +74,8 @@ void Esp32HostedUpdate::setup() {
|
||||
}
|
||||
ESP_LOGD(TAG, "Coprocessor version: %s", this->update_info_.current_version.c_str());
|
||||
|
||||
// get image version
|
||||
#ifndef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
// Embedded mode: get image version from embedded firmware
|
||||
const int app_desc_offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t);
|
||||
if (this->firmware_size_ >= app_desc_offset + sizeof(esp_app_desc_t)) {
|
||||
esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->firmware_data_ + app_desc_offset);
|
||||
@@ -64,57 +103,272 @@ void Esp32HostedUpdate::setup() {
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
}
|
||||
|
||||
// publish state
|
||||
// Publish state
|
||||
this->status_clear_error();
|
||||
this->publish_state();
|
||||
#else
|
||||
// HTTP mode: retry initial check every 10s until network is ready (max 6 attempts)
|
||||
// Only if update interval is > 1 minute to avoid redundant checks
|
||||
if (this->get_update_interval() > 60000) {
|
||||
this->set_retry("initial_check", 10000, 6, [this](uint8_t) {
|
||||
if (!network::is_connected()) {
|
||||
return RetryResult::RETRY;
|
||||
}
|
||||
this->check();
|
||||
return RetryResult::DONE;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Esp32HostedUpdate::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"ESP32 Hosted Update:\n"
|
||||
" Current Version: %s\n"
|
||||
" Latest Version: %s\n"
|
||||
" Latest Size: %zu bytes",
|
||||
this->update_info_.current_version.c_str(), this->update_info_.latest_version.c_str(),
|
||||
" Host Library Version: %s\n"
|
||||
" Coprocessor Version: %s\n"
|
||||
" Latest Version: %s",
|
||||
ESP_HOSTED_VERSION_STR, this->update_info_.current_version.c_str(),
|
||||
this->update_info_.latest_version.c_str());
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Mode: HTTP\n"
|
||||
" Source URL: %s",
|
||||
this->source_url_.c_str());
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Mode: Embedded\n"
|
||||
" Firmware Size: %zu bytes",
|
||||
this->firmware_size_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Esp32HostedUpdate::perform(bool force) {
|
||||
if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
|
||||
ESP_LOGW(TAG, "Update not available");
|
||||
void Esp32HostedUpdate::check() {
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
if (!network::is_connected()) {
|
||||
ESP_LOGD(TAG, "Network not connected, skipping update check");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->fetch_manifest_()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare versions
|
||||
if (this->update_info_.latest_version.empty() ||
|
||||
this->update_info_.latest_version == this->update_info_.current_version) {
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
} else {
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
}
|
||||
|
||||
this->update_info_.has_progress = false;
|
||||
this->update_info_.progress = 0.0f;
|
||||
this->status_clear_error();
|
||||
this->publish_state();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
bool Esp32HostedUpdate::fetch_manifest_() {
|
||||
ESP_LOGD(TAG, "Fetching manifest");
|
||||
|
||||
auto container = this->http_request_parent_->get(this->source_url_);
|
||||
if (container == nullptr || container->status_code != 200) {
|
||||
ESP_LOGE(TAG, "Failed to fetch manifest from %s", this->source_url_.c_str());
|
||||
this->status_set_error(LOG_STR("Failed to fetch manifest"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read manifest JSON into string (manifest is small, ~1KB max)
|
||||
std::string json_str;
|
||||
json_str.reserve(container->content_length);
|
||||
uint8_t buf[256];
|
||||
while (container->get_bytes_read() < container->content_length) {
|
||||
int read = container->read(buf, sizeof(buf));
|
||||
if (read > 0) {
|
||||
json_str.append(reinterpret_cast<char *>(buf), read);
|
||||
}
|
||||
yield();
|
||||
}
|
||||
container->end();
|
||||
|
||||
// Parse JSON manifest
|
||||
// Format: {"versions": [{"version": "2.7.0", "url": "...", "sha256": "..."}]}
|
||||
// Only consider versions <= host library version to avoid compatibility issues
|
||||
bool valid = json::parse_json(json_str, [this](JsonObject root) -> bool {
|
||||
if (!root["versions"].is<JsonArray>()) {
|
||||
ESP_LOGE(TAG, "Manifest does not contain 'versions' array");
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonArray versions = root["versions"].as<JsonArray>();
|
||||
if (versions.size() == 0) {
|
||||
ESP_LOGE(TAG, "Manifest 'versions' array is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the highest version that is compatible with the host library
|
||||
// (version <= host version to avoid upgrading coprocessor ahead of host)
|
||||
int best_major = -1, best_minor = -1, best_patch = -1;
|
||||
std::string best_version, best_url, best_sha256;
|
||||
|
||||
for (JsonObject entry : versions) {
|
||||
if (!entry["version"].is<const char *>() || !entry["url"].is<const char *>() ||
|
||||
!entry["sha256"].is<const char *>()) {
|
||||
continue; // Skip malformed entries
|
||||
}
|
||||
|
||||
std::string ver_str = entry["version"].as<std::string>();
|
||||
int major, minor, patch;
|
||||
if (!parse_version(ver_str, major, minor, patch)) {
|
||||
ESP_LOGW(TAG, "Failed to parse version: %s", ver_str.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this version is compatible (not newer than host)
|
||||
if (compare_versions(major, minor, patch, ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1,
|
||||
ESP_HOSTED_VERSION_PATCH_1) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is better than our current best
|
||||
if (best_major < 0 || compare_versions(major, minor, patch, best_major, best_minor, best_patch) > 0) {
|
||||
best_major = major;
|
||||
best_minor = minor;
|
||||
best_patch = patch;
|
||||
best_version = ver_str;
|
||||
best_url = entry["url"].as<std::string>();
|
||||
best_sha256 = entry["sha256"].as<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
if (best_major < 0) {
|
||||
ESP_LOGW(TAG, "No compatible firmware version found (host is %s)", ESP_HOSTED_VERSION_STR);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->update_info_.latest_version = best_version;
|
||||
this->firmware_url_ = best_url;
|
||||
|
||||
// Parse SHA256 hex string to bytes
|
||||
if (!parse_hex(best_sha256, this->firmware_sha256_.data(), 32)) {
|
||||
ESP_LOGE(TAG, "Invalid SHA256: %s", best_sha256.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Best compatible version: %s", this->update_info_.latest_version.c_str());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
ESP_LOGE(TAG, "Failed to parse manifest JSON");
|
||||
this->status_set_error(LOG_STR("Failed to parse manifest"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
|
||||
ESP_LOGI(TAG, "Downloading firmware");
|
||||
|
||||
auto container = this->http_request_parent_->get(this->firmware_url_);
|
||||
if (container == nullptr || container->status_code != 200) {
|
||||
ESP_LOGE(TAG, "Failed to fetch firmware");
|
||||
this->status_set_error(LOG_STR("Failed to fetch firmware"));
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t total_size = container->content_length;
|
||||
ESP_LOGI(TAG, "Firmware size: %zu bytes", total_size);
|
||||
|
||||
// Begin OTA on coprocessor
|
||||
esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err));
|
||||
container->end();
|
||||
this->status_set_error(LOG_STR("Failed to begin OTA"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stream firmware to coprocessor while computing SHA256
|
||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
hasher.init();
|
||||
|
||||
uint8_t buffer[CHUNK_SIZE];
|
||||
while (container->get_bytes_read() < total_size) {
|
||||
int read = container->read(buffer, sizeof(buffer));
|
||||
|
||||
// Feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
|
||||
// Exit loop if no data available (stream closed or end of data)
|
||||
if (read <= 0) {
|
||||
if (read < 0) {
|
||||
ESP_LOGE(TAG, "Stream closed with error");
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
container->end();
|
||||
this->status_set_error(LOG_STR("Download failed"));
|
||||
return false;
|
||||
}
|
||||
// read == 0: no more data available, exit loop
|
||||
break;
|
||||
}
|
||||
|
||||
hasher.add(buffer, read);
|
||||
err = esp_hosted_slave_ota_write(buffer, read); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
container->end();
|
||||
this->status_set_error(LOG_STR("Failed to write OTA data"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
container->end();
|
||||
|
||||
// Verify SHA256
|
||||
hasher.calculate();
|
||||
if (!hasher.equals_bytes(this->firmware_sha256_.data())) {
|
||||
ESP_LOGE(TAG, "SHA256 mismatch");
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
this->status_set_error(LOG_STR("SHA256 verification failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SHA256 verified successfully");
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() {
|
||||
if (this->firmware_data_ == nullptr || this->firmware_size_ == 0) {
|
||||
ESP_LOGE(TAG, "No firmware data available");
|
||||
return;
|
||||
this->status_set_error(LOG_STR("No firmware data available"));
|
||||
return false;
|
||||
}
|
||||
|
||||
sha256::SHA256 hasher;
|
||||
// Verify SHA256 before writing
|
||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
hasher.init();
|
||||
hasher.add(this->firmware_data_, this->firmware_size_);
|
||||
hasher.calculate();
|
||||
if (!hasher.equals_bytes(this->firmware_sha256_.data())) {
|
||||
ESP_LOGE(TAG, "SHA256 mismatch");
|
||||
this->status_set_error(LOG_STR("SHA256 verification failed"));
|
||||
this->publish_state();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting OTA update (%zu bytes)", this->firmware_size_);
|
||||
|
||||
watchdog::WatchdogManager watchdog(20000);
|
||||
update::UpdateState prev_state = this->state_;
|
||||
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||
this->update_info_.has_progress = false;
|
||||
this->publish_state();
|
||||
|
||||
esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error(LOG_STR("Failed to begin OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t chunk[CHUNK_SIZE];
|
||||
@@ -127,42 +381,68 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error(LOG_STR("Failed to write OTA data"));
|
||||
this->publish_state();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
data_ptr += chunk_size;
|
||||
remaining -= chunk_size;
|
||||
App.feed_wdt();
|
||||
}
|
||||
|
||||
err = esp_hosted_slave_ota_end(); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Esp32HostedUpdate::perform(bool force) {
|
||||
if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
|
||||
ESP_LOGW(TAG, "Update not available");
|
||||
return;
|
||||
}
|
||||
|
||||
update::UpdateState prev_state = this->state_;
|
||||
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||
this->update_info_.has_progress = false;
|
||||
this->publish_state();
|
||||
|
||||
watchdog::WatchdogManager watchdog(60000);
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
if (!this->stream_firmware_to_coprocessor_())
|
||||
#else
|
||||
if (!this->write_embedded_firmware_to_coprocessor_())
|
||||
#endif
|
||||
{
|
||||
this->state_ = prev_state;
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
// End OTA and activate new firmware
|
||||
esp_err_t end_err = esp_hosted_slave_ota_end(); // NOLINT
|
||||
if (end_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(end_err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error(LOG_STR("Failed to end OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
// activate new firmware
|
||||
err = esp_hosted_slave_ota_activate(); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err));
|
||||
esp_err_t activate_err = esp_hosted_slave_ota_activate(); // NOLINT
|
||||
if (activate_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(activate_err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error(LOG_STR("Failed to activate OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
// update state
|
||||
// Update state
|
||||
ESP_LOGI(TAG, "OTA update successful");
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
this->status_clear_error();
|
||||
this->publish_state();
|
||||
|
||||
// schedule a restart to ensure everything is in sync
|
||||
// Schedule a restart to ensure everything is in sync
|
||||
ESP_LOGI(TAG, "Restarting in 1 second");
|
||||
this->set_timeout(1000, []() { App.safe_reboot(); });
|
||||
}
|
||||
|
||||
@@ -5,26 +5,55 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/update/update_entity.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
#include "esphome/components/http_request/http_request.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::esp32_hosted {
|
||||
|
||||
class Esp32HostedUpdate : public update::UpdateEntity, public Component {
|
||||
class Esp32HostedUpdate : public update::UpdateEntity, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void update() override { this->check(); } // PollingComponent - delegates to check()
|
||||
void perform(bool force) override;
|
||||
void check() override {}
|
||||
void check() override;
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
// HTTP mode setters
|
||||
void set_source_url(const std::string &url) { this->source_url_ = url; }
|
||||
void set_http_request_parent(http_request::HttpRequestComponent *parent) { this->http_request_parent_ = parent; }
|
||||
#else
|
||||
// Embedded mode setters
|
||||
void set_firmware_data(const uint8_t *data) { this->firmware_data_ = data; }
|
||||
void set_firmware_size(size_t size) { this->firmware_size_ = size; }
|
||||
void set_firmware_sha256(const std::array<uint8_t, 32> &sha256) { this->firmware_sha256_ = sha256; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
// HTTP mode members
|
||||
http_request::HttpRequestComponent *http_request_parent_{nullptr};
|
||||
std::string source_url_;
|
||||
std::string firmware_url_;
|
||||
|
||||
// HTTP mode helpers
|
||||
bool fetch_manifest_();
|
||||
bool stream_firmware_to_coprocessor_();
|
||||
#else
|
||||
// Embedded mode members
|
||||
const uint8_t *firmware_data_{nullptr};
|
||||
size_t firmware_size_{0};
|
||||
std::array<uint8_t, 32> firmware_sha256_;
|
||||
|
||||
// Embedded mode helper
|
||||
bool write_embedded_firmware_to_coprocessor_();
|
||||
#endif
|
||||
|
||||
std::array<uint8_t, 32> firmware_sha256_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::esp32_hosted
|
||||
|
||||
@@ -243,6 +243,16 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
|
||||
|
||||
uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; }
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
/// Ensure benchmark value is read (v2 touch hardware only).
|
||||
/// Called from multiple places - kept as helper to document shared usage.
|
||||
void ensure_benchmark_read() {
|
||||
if (this->benchmark_ == 0) {
|
||||
touch_pad_read_benchmark(this->touch_pad_, &this->benchmark_);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
friend ESP32TouchComponent;
|
||||
|
||||
|
||||
@@ -102,7 +102,16 @@ void ESP32TouchComponent::process_setup_mode_logging_(uint32_t now) {
|
||||
uint32_t value = this->read_touch_value(child->get_touch_pad());
|
||||
// Store the value for get_value() access in lambdas
|
||||
child->value_ = value;
|
||||
ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value);
|
||||
// Read benchmark if not already read
|
||||
child->ensure_benchmark_read();
|
||||
// Calculate difference to help user set threshold
|
||||
// For ESP32-S2/S3 v2: touch detected when value > benchmark + threshold
|
||||
// So threshold should be < (value - benchmark) when touched
|
||||
int32_t difference = static_cast<int32_t>(value) - static_cast<int32_t>(child->benchmark_);
|
||||
ESP_LOGD(TAG,
|
||||
"Touch Pad '%s' (T%d): value=%d, benchmark=%" PRIu32 ", difference=%" PRId32 " (set threshold < %" PRId32
|
||||
" to detect touch)",
|
||||
child->get_name().c_str(), child->get_touch_pad(), value, child->benchmark_, difference, difference);
|
||||
#endif
|
||||
}
|
||||
this->setup_mode_last_log_print_ = now;
|
||||
|
||||
@@ -105,8 +105,10 @@ void ESP32TouchComponent::setup() {
|
||||
touch_pad_set_charge_discharge_times(this->meas_cycle_);
|
||||
touch_pad_set_measurement_interval(this->sleep_cycle_);
|
||||
|
||||
// Configure timeout if needed
|
||||
touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX);
|
||||
// Disable hardware timeout - it causes continuous interrupts with high-capacitance
|
||||
// setups (e.g., pressure sensors under cushions). The periodic release check in
|
||||
// loop() handles state detection reliably without needing hardware timeout.
|
||||
touch_pad_timeout_set(false, TOUCH_PAD_THRESHOLD_MAX);
|
||||
|
||||
// Register ISR handler with interrupt mask
|
||||
esp_err_t err =
|
||||
@@ -314,8 +316,7 @@ void ESP32TouchComponent::loop() {
|
||||
|
||||
size_t pads_off = 0;
|
||||
for (auto *child : this->children_) {
|
||||
if (child->benchmark_ == 0)
|
||||
touch_pad_read_benchmark(child->touch_pad_, &child->benchmark_);
|
||||
child->ensure_benchmark_read();
|
||||
// Handle initial state publication after startup
|
||||
this->publish_initial_state_if_needed_(child, now);
|
||||
|
||||
@@ -354,7 +355,7 @@ void ESP32TouchComponent::loop() {
|
||||
|
||||
void ESP32TouchComponent::on_shutdown() {
|
||||
// Disable interrupts
|
||||
touch_pad_intr_disable(static_cast<touch_pad_intr_mask_t>(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT));
|
||||
touch_pad_intr_disable(TOUCH_PAD_INTR_MASK_ACTIVE);
|
||||
touch_pad_isr_deregister(touch_isr_handler, this);
|
||||
this->cleanup_touch_queue_();
|
||||
|
||||
|
||||
@@ -66,11 +66,17 @@ CONF_WAIT_FOR_SENT = "wait_for_sent"
|
||||
MAX_ESPNOW_PACKET_SIZE = 250 # Maximum size of the payload in bytes
|
||||
|
||||
|
||||
def validate_channel(value):
|
||||
if value is None:
|
||||
raise cv.Invalid("channel is required if wifi is not configured")
|
||||
return wifi.validate_channel(value)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESPNowComponent),
|
||||
cv.OnlyWithout(CONF_CHANNEL, CONF_WIFI): wifi.validate_channel,
|
||||
cv.OnlyWithout(CONF_CHANNEL, CONF_WIFI): validate_channel,
|
||||
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_AUTO_ADD_PEER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PEERS): cv.ensure_list(cv.mac_address),
|
||||
|
||||
@@ -779,8 +779,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
|
||||
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)];
|
||||
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
|
||||
|
||||
/*
|
||||
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
|
||||
@@ -797,10 +797,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
|
||||
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
|
||||
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
|
||||
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s",
|
||||
format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif // USE_ETHERNET_KSZ8081
|
||||
|
||||
@@ -14,12 +14,15 @@ class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextS
|
||||
auto ips = ethernet::global_eth_component->get_ip_addresses();
|
||||
if (ips != this->last_ips_) {
|
||||
this->last_ips_ = ips;
|
||||
this->publish_state(ips[0].str());
|
||||
char buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
ips[0].str_to(buf);
|
||||
this->publish_state(buf);
|
||||
uint8_t sensor = 0;
|
||||
for (auto &ip : ips) {
|
||||
if (ip.is_set()) {
|
||||
if (this->ip_sensors_[sensor] != nullptr) {
|
||||
this->ip_sensors_[sensor]->publish_state(ip.str());
|
||||
ip.str_to(buf);
|
||||
this->ip_sensors_[sensor]->publish_state(buf);
|
||||
}
|
||||
sensor++;
|
||||
}
|
||||
@@ -64,7 +67,10 @@ class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::Text
|
||||
|
||||
class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); }
|
||||
void setup() override {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
float get_setup_priority() const override { return setup_priority::ETHERNET; }
|
||||
void dump_config() override;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace event {
|
||||
@@ -44,8 +45,11 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
/// Return the event types supported by this event.
|
||||
const FixedVector<const char *> &get_event_types() const { return this->types_; }
|
||||
|
||||
/// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet.
|
||||
const char *get_last_event_type() const { return this->last_event_type_; }
|
||||
/// Return the last triggered event type, or empty StringRef if no event triggered yet.
|
||||
StringRef get_last_event_type() const { return StringRef::from_maybe_nullptr(this->last_event_type_); }
|
||||
|
||||
/// Check if an event has been triggered.
|
||||
bool has_event() const { return this->last_event_type_ != nullptr; }
|
||||
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
|
||||
|
||||
@@ -212,19 +212,18 @@ class FanPresetSetTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
const auto *preset_mode = state->get_preset_mode();
|
||||
auto preset_mode = state->get_preset_mode();
|
||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
if (should_trigger) {
|
||||
// Trigger with empty string when nullptr to maintain backward compatibility
|
||||
this->trigger(preset_mode != nullptr ? preset_mode : "");
|
||||
this->trigger(std::string(preset_mode));
|
||||
}
|
||||
});
|
||||
this->last_preset_mode_ = state->get_preset_mode();
|
||||
}
|
||||
|
||||
protected:
|
||||
const char *last_preset_mode_{nullptr};
|
||||
StringRef last_preset_mode_{};
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
||||
@@ -61,7 +61,7 @@ void FanCall::perform() {
|
||||
if (this->direction_.has_value()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
|
||||
}
|
||||
if (this->has_preset_mode()) {
|
||||
if (this->preset_mode_ != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
}
|
||||
this->parent_.control(*this);
|
||||
@@ -71,7 +71,7 @@ void FanCall::validate_() {
|
||||
auto traits = this->parent_.get_traits();
|
||||
|
||||
if (this->speed_.has_value()) {
|
||||
this->speed_ = clamp(*this->speed_, 1, static_cast<int>(traits.supported_speed_count()));
|
||||
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
|
||||
|
||||
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
|
||||
// "Manually setting a speed must disable any set preset mode"
|
||||
@@ -83,7 +83,7 @@ void FanCall::validate_() {
|
||||
*this->binary_state_
|
||||
// ..,and no preset mode will be active...
|
||||
&& !this->has_preset_mode() &&
|
||||
this->parent_.get_preset_mode() == nullptr
|
||||
!this->parent_.has_preset_mode()
|
||||
// ...and neither current nor new speed is available...
|
||||
&& traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) {
|
||||
// ...set speed to 100%
|
||||
@@ -154,16 +154,16 @@ const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) {
|
||||
return this->get_traits().find_preset_mode(preset_mode, len);
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(const char *preset_mode) {
|
||||
if (preset_mode == nullptr) {
|
||||
// Treat nullptr as clearing the preset mode
|
||||
bool Fan::set_preset_mode_(const char *preset_mode, size_t len) {
|
||||
if (preset_mode == nullptr || len == 0) {
|
||||
// Treat nullptr or empty string as clearing the preset mode (no valid preset is "")
|
||||
if (this->preset_mode_ == nullptr) {
|
||||
return false; // No change
|
||||
}
|
||||
this->clear_preset_mode_();
|
||||
return true;
|
||||
}
|
||||
const char *validated = this->find_preset_mode_(preset_mode);
|
||||
const char *validated = this->find_preset_mode_(preset_mode, len);
|
||||
if (validated == nullptr || this->preset_mode_ == validated) {
|
||||
return false; // Preset mode not supported or no change
|
||||
}
|
||||
@@ -171,10 +171,31 @@ bool Fan::set_preset_mode_(const char *preset_mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(const std::string &preset_mode) { return this->set_preset_mode_(preset_mode.c_str()); }
|
||||
bool Fan::set_preset_mode_(const char *preset_mode) {
|
||||
return this->set_preset_mode_(preset_mode, preset_mode ? strlen(preset_mode) : 0);
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(const std::string &preset_mode) {
|
||||
return this->set_preset_mode_(preset_mode.data(), preset_mode.size());
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(StringRef preset_mode) {
|
||||
// Safe: find_preset_mode_ only uses the input for comparison and returns
|
||||
// a pointer from traits, so the input StringRef's lifetime doesn't matter.
|
||||
return this->set_preset_mode_(preset_mode.c_str(), preset_mode.size());
|
||||
}
|
||||
|
||||
void Fan::clear_preset_mode_() { this->preset_mode_ = nullptr; }
|
||||
|
||||
void Fan::apply_preset_mode_(const FanCall &call) {
|
||||
if (call.has_preset_mode()) {
|
||||
this->set_preset_mode_(call.get_preset_mode());
|
||||
} else if (call.get_speed().has_value()) {
|
||||
// Manually setting speed clears preset (per Home Assistant convention)
|
||||
this->clear_preset_mode_();
|
||||
}
|
||||
}
|
||||
|
||||
void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||
void Fan::publish_state() {
|
||||
auto traits = this->get_traits();
|
||||
@@ -192,9 +213,8 @@ void Fan::publish_state() {
|
||||
if (traits.supports_direction()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
|
||||
}
|
||||
const char *preset = this->get_preset_mode();
|
||||
if (preset != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", preset);
|
||||
if (this->preset_mode_ != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
}
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY)
|
||||
@@ -249,12 +269,11 @@ void Fan::save_state_() {
|
||||
state.speed = this->speed;
|
||||
state.direction = this->direction;
|
||||
|
||||
const char *preset = this->get_preset_mode();
|
||||
if (preset != nullptr) {
|
||||
if (this->has_preset_mode()) {
|
||||
const auto &preset_modes = traits.supported_preset_modes();
|
||||
// Find index of current preset mode (pointer comparison is safe since preset is from traits)
|
||||
for (size_t i = 0; i < preset_modes.size(); i++) {
|
||||
if (preset_modes[i] == preset) {
|
||||
if (preset_modes[i] == this->preset_mode_) {
|
||||
state.preset_mode = i;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "fan_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -128,8 +129,11 @@ class Fan : public EntityBase {
|
||||
/// Set the restore mode of this fan.
|
||||
void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||
|
||||
/// Get the current preset mode (returns pointer to string stored in traits, or nullptr if not set)
|
||||
const char *get_preset_mode() const { return this->preset_mode_; }
|
||||
/// Get the current preset mode.
|
||||
/// Returns a StringRef of the string stored in traits, or empty ref if not set.
|
||||
/// The returned ref points to string literals from codegen (static storage).
|
||||
/// Traits are set once at startup and valid for the lifetime of the program.
|
||||
StringRef get_preset_mode() const { return StringRef::from_maybe_nullptr(this->preset_mode_); }
|
||||
|
||||
/// Check if a preset mode is currently active
|
||||
bool has_preset_mode() const { return this->preset_mode_ != nullptr; }
|
||||
@@ -146,11 +150,15 @@ class Fan : public EntityBase {
|
||||
void dump_traits_(const char *tag, const char *prefix);
|
||||
|
||||
/// Set the preset mode (finds and stores pointer from traits). Returns true if changed.
|
||||
/// Passing nullptr or empty string clears the preset mode.
|
||||
bool set_preset_mode_(const char *preset_mode, size_t len);
|
||||
bool set_preset_mode_(const char *preset_mode);
|
||||
/// Set the preset mode (finds and stores pointer from traits). Returns true if changed.
|
||||
bool set_preset_mode_(const std::string &preset_mode);
|
||||
bool set_preset_mode_(StringRef preset_mode);
|
||||
/// Clear the preset mode
|
||||
void clear_preset_mode_();
|
||||
/// Apply preset mode from a FanCall (handles speed-clears-preset convention)
|
||||
void apply_preset_mode_(const FanCall &call);
|
||||
/// Find and return the matching preset mode pointer from traits, or nullptr if not found.
|
||||
const char *find_preset_mode_(const char *preset_mode);
|
||||
const char *find_preset_mode_(const char *preset_mode, size_t len);
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace fan {
|
||||
class FanTraits {
|
||||
public:
|
||||
FanTraits() = default;
|
||||
FanTraits(bool oscillation, bool speed, bool direction, uint8_t speed_count)
|
||||
FanTraits(bool oscillation, bool speed, bool direction, int speed_count)
|
||||
: oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {}
|
||||
|
||||
/// Return if this fan supports oscillation.
|
||||
@@ -23,9 +23,9 @@ class FanTraits {
|
||||
/// Set whether this fan supports speed levels.
|
||||
void set_speed(bool speed) { this->speed_ = speed; }
|
||||
/// Return how many speed levels the fan has
|
||||
uint8_t supported_speed_count() const { return this->speed_count_; }
|
||||
int supported_speed_count() const { return this->speed_count_; }
|
||||
/// Set how many speed levels this fan has.
|
||||
void set_supported_speed_count(uint8_t speed_count) { this->speed_count_ = speed_count; }
|
||||
void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; }
|
||||
/// Return if this fan supports changing direction
|
||||
bool supports_direction() const { return this->direction_; }
|
||||
/// Set whether this fan supports changing direction
|
||||
@@ -64,7 +64,7 @@ class FanTraits {
|
||||
bool oscillation_{false};
|
||||
bool speed_{false};
|
||||
bool direction_{false};
|
||||
uint8_t speed_count_{};
|
||||
int speed_count_{};
|
||||
std::vector<const char *> preset_modes_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -232,17 +232,19 @@ void GraphLegend::init(Graph *g) {
|
||||
ESP_LOGI(TAGL, " %s %d %d", txtstr.c_str(), fw, fh);
|
||||
|
||||
if (this->values_ != VALUE_POSITION_TYPE_NONE) {
|
||||
std::string valstr =
|
||||
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
char valstr[VALUE_ACCURACY_MAX_LEN];
|
||||
if (this->units_) {
|
||||
valstr += trace->sensor_->get_unit_of_measurement_ref();
|
||||
value_accuracy_with_uom_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals(),
|
||||
trace->sensor_->get_unit_of_measurement_ref());
|
||||
} else {
|
||||
value_accuracy_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
}
|
||||
this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
|
||||
this->font_value_->measure(valstr, &fw, &fos, &fbl, &fh);
|
||||
if (fw > valw)
|
||||
valw = fw;
|
||||
if (fh > valh)
|
||||
valh = fh;
|
||||
ESP_LOGI(TAGL, " %s %d %d", valstr.c_str(), fw, fh);
|
||||
ESP_LOGI(TAGL, " %s %d %d", valstr, fw, fh);
|
||||
}
|
||||
}
|
||||
// Add extra margin
|
||||
@@ -368,13 +370,15 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
|
||||
if (legend_->values_ != VALUE_POSITION_TYPE_NONE) {
|
||||
int xv = x + legend_->xv_;
|
||||
int yv = y + legend_->yv_;
|
||||
std::string valstr =
|
||||
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
char valstr[VALUE_ACCURACY_MAX_LEN];
|
||||
if (legend_->units_) {
|
||||
valstr += trace->sensor_->get_unit_of_measurement_ref();
|
||||
value_accuracy_with_uom_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals(),
|
||||
trace->sensor_->get_unit_of_measurement_ref());
|
||||
} else {
|
||||
value_accuracy_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
}
|
||||
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str());
|
||||
ESP_LOGV(TAG, " value: %s", valstr.c_str());
|
||||
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr);
|
||||
ESP_LOGV(TAG, " value: %s", valstr);
|
||||
}
|
||||
x += legend_->xs_;
|
||||
y += legend_->ys_;
|
||||
|
||||
@@ -39,7 +39,7 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
|
||||
DECAY_MODE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
|
||||
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
|
||||
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ void HBridgeFan::control(const fan::FanCall &call) {
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
this->set_preset_mode_(call.get_preset_mode());
|
||||
this->apply_preset_mode_(call);
|
||||
|
||||
this->write_state_();
|
||||
this->publish_state();
|
||||
|
||||
@@ -15,7 +15,7 @@ enum DecayMode {
|
||||
|
||||
class HBridgeFan : public Component, public fan::Fan {
|
||||
public:
|
||||
HBridgeFan(uint8_t speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
|
||||
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
|
||||
|
||||
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
|
||||
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
|
||||
@@ -33,7 +33,7 @@ class HBridgeFan : public Component, public fan::Fan {
|
||||
output::FloatOutput *pin_b_;
|
||||
output::FloatOutput *enable_{nullptr};
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
uint8_t speed_count_{};
|
||||
int speed_count_{};
|
||||
DecayMode decay_mode_{DECAY_MODE_SLOW};
|
||||
fan::FanTraits traits_;
|
||||
std::vector<const char *> preset_modes_{};
|
||||
|
||||
@@ -181,6 +181,11 @@ void HeatpumpIRClimate::transmit_state() {
|
||||
power_mode_cmd = POWER_ON;
|
||||
operating_mode_cmd = MODE_HEAT;
|
||||
break;
|
||||
// Map HEAT_COOL to hardware AUTO mode (automatic heat/cool changeover based on temperature).
|
||||
// In hardware AUTO mode, the device automatically switches between heating and cooling
|
||||
// based on the current temperature versus the target temperature.
|
||||
// See https://github.com/esphome/esphome/issues/11161 for further discussion.
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
power_mode_cmd = POWER_ON;
|
||||
operating_mode_cmd = MODE_AUTO;
|
||||
|
||||
@@ -91,11 +91,14 @@ void HomeassistantNumber::control(float value) {
|
||||
resp.data.init(2);
|
||||
auto &entity_id = resp.data.emplace_back();
|
||||
entity_id.key = ENTITY_ID_KEY;
|
||||
entity_id.value = this->entity_id_;
|
||||
entity_id.value = StringRef(this->entity_id_);
|
||||
|
||||
auto &entity_value = resp.data.emplace_back();
|
||||
entity_value.key = VALUE_KEY;
|
||||
entity_value.value = to_string(value);
|
||||
// Stack buffer - no heap allocation; %g produces shortest representation
|
||||
char value_buf[16];
|
||||
snprintf(value_buf, sizeof(value_buf), "%g", value);
|
||||
entity_value.value = StringRef(value_buf);
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ void HomeassistantSwitch::write_state(bool state) {
|
||||
resp.data.init(1);
|
||||
auto &entity_id_kv = resp.data.emplace_back();
|
||||
entity_id_kv.key = ENTITY_ID_KEY;
|
||||
entity_id_kv.value = this->entity_id_;
|
||||
entity_id_kv.value = StringRef(this->entity_id_);
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ void HomeassistantTextSensor::setup() {
|
||||
} else {
|
||||
ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str());
|
||||
}
|
||||
this->publish_state(state.str());
|
||||
this->publish_state(state.c_str(), state.size());
|
||||
});
|
||||
}
|
||||
void HomeassistantTextSensor::dump_config() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user