mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 17:05:36 +00:00
Merge branch 'dev' into socket-lwip-raw-udp
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
8e48e836c6fc196d3da000d46eb09db243b87fe33518a74e49c8e009d756074a
|
||||
9f5d763f95ff720024f3fdddba2fad3801e2bfe00b7cc2124e6d68c17d3504c6
|
||||
|
||||
@@ -339,7 +339,7 @@ jobs:
|
||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@281164b0f014a4e7badd2c02cecad9b595b70537 # v4
|
||||
uses: CodSpeedHQ/action@1c8ae4843586d3ba879736b7f6b7b0c990757fab # v4
|
||||
with:
|
||||
run: ${{ steps.build.outputs.binary }}
|
||||
mode: simulation
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
|
||||
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
|
||||
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -457,6 +457,8 @@ esphome/components/sn74hc165/* @jesserockz
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/sound_level/* @kahrendt
|
||||
esphome/components/spa06_base/* @danielkent-net
|
||||
esphome/components/spa06_i2c/* @danielkent-net
|
||||
esphome/components/speaker/* @jesserockz @kahrendt
|
||||
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
|
||||
esphome/components/speaker_source/* @kahrendt
|
||||
|
||||
@@ -56,6 +56,10 @@ _COMPONENT_PREFIX_LIB = "[lib]"
|
||||
_COMPONENT_CORE = f"{_COMPONENT_PREFIX_ESPHOME}core"
|
||||
_COMPONENT_API = f"{_COMPONENT_PREFIX_ESPHOME}api"
|
||||
|
||||
# Placement new storage suffix (generated by codegen Pvariable)
|
||||
_PSTORAGE_SUFFIX = "__pstorage"
|
||||
|
||||
|
||||
# C++ namespace prefixes
|
||||
_NAMESPACE_ESPHOME = "esphome::"
|
||||
_NAMESPACE_STD = "std::"
|
||||
@@ -201,6 +205,9 @@ class MemoryAnalyzer:
|
||||
self._cswtch_symbols: list[tuple[str, int, str, str]] = []
|
||||
# Library symbol mapping: symbol_name -> library_name
|
||||
self._lib_symbol_map: dict[str, str] = {}
|
||||
# Source file symbol mapping: symbol_name -> component_name
|
||||
# Used for extern "C" and other symbols without C++ namespace
|
||||
self._source_symbol_map: dict[str, str] = {}
|
||||
# Library dir to name mapping: "lib641" -> "espsoftwareserial",
|
||||
# "espressif__mdns" -> "mdns"
|
||||
self._lib_hash_to_name: dict[str, str] = {}
|
||||
@@ -214,6 +221,7 @@ class MemoryAnalyzer:
|
||||
self._parse_sections()
|
||||
self._parse_symbols()
|
||||
self._scan_libraries()
|
||||
self._scan_source_symbols()
|
||||
self._categorize_symbols()
|
||||
self._analyze_cswtch_symbols()
|
||||
self._analyze_sdk_libraries()
|
||||
@@ -328,6 +336,13 @@ class MemoryAnalyzer:
|
||||
# Demangle C++ names if needed
|
||||
demangled = self._demangle_symbol(symbol_name)
|
||||
|
||||
# Check for placement new storage symbols (generated by codegen)
|
||||
# Format: {component}__{id}__pstorage
|
||||
if demangled.endswith(_PSTORAGE_SUFFIX) and (
|
||||
component := self._match_pstorage_component(demangled)
|
||||
):
|
||||
return component
|
||||
|
||||
# Check for special component classes first (before namespace pattern)
|
||||
# This handles cases like esphome::ESPHomeOTAComponent which should map to ota
|
||||
if _NAMESPACE_ESPHOME in demangled:
|
||||
@@ -363,6 +378,11 @@ class MemoryAnalyzer:
|
||||
if lib_name := self._lib_symbol_map.get(symbol_name):
|
||||
return f"{_COMPONENT_PREFIX_LIB}{lib_name}"
|
||||
|
||||
# Check source file mapping (catches extern "C" functions in ESPHome sources)
|
||||
# Must be before heuristic patterns since source attribution is authoritative
|
||||
if component := self._source_symbol_map.get(symbol_name):
|
||||
return component
|
||||
|
||||
# Check against symbol patterns
|
||||
for component, patterns in SYMBOL_PATTERNS.items():
|
||||
if any(pattern in symbol_name for pattern in patterns):
|
||||
@@ -390,6 +410,24 @@ class MemoryAnalyzer:
|
||||
# Track uncategorized symbols for analysis
|
||||
return "other"
|
||||
|
||||
def _match_pstorage_component(self, symbol_name: str) -> str | None:
|
||||
"""Match a __pstorage symbol to its ESPHome component.
|
||||
|
||||
Symbol format: {component}__{id}__pstorage
|
||||
The component namespace is embedded by codegen before the double underscore.
|
||||
"""
|
||||
prefix = symbol_name[: -len(_PSTORAGE_SUFFIX)]
|
||||
# Extract component namespace before the first double underscore
|
||||
dunder_pos = prefix.find("__")
|
||||
if dunder_pos == -1:
|
||||
return None
|
||||
component_name = prefix[:dunder_pos]
|
||||
if component_name in get_esphome_components():
|
||||
return f"{_COMPONENT_PREFIX_ESPHOME}{component_name}"
|
||||
if component_name in self.external_components:
|
||||
return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}"
|
||||
return None
|
||||
|
||||
def _batch_demangle_symbols(self, symbols: list[str]) -> None:
|
||||
"""Batch demangle C++ symbol names for efficiency."""
|
||||
if not symbols:
|
||||
@@ -653,6 +691,7 @@ class MemoryAnalyzer:
|
||||
return None
|
||||
|
||||
symbol_map: dict[str, str] = {}
|
||||
source_symbol_map: dict[str, str] = {}
|
||||
current_symbol: str | None = None
|
||||
section_prefixes = (".text.", ".rodata.", ".data.", ".bss.", ".literal.")
|
||||
|
||||
@@ -688,9 +727,18 @@ class MemoryAnalyzer:
|
||||
if dir_key in source_path:
|
||||
symbol_map[current_symbol] = lib_name
|
||||
break
|
||||
else:
|
||||
# Map ESPHome source files to components for extern "C"
|
||||
# and other symbols without C++ namespace
|
||||
component = self._source_file_to_component(source_path)
|
||||
if component.startswith(
|
||||
(_COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL)
|
||||
):
|
||||
source_symbol_map[current_symbol] = component
|
||||
|
||||
current_symbol = None
|
||||
|
||||
self._source_symbol_map = source_symbol_map
|
||||
return symbol_map or None
|
||||
|
||||
def _scan_libraries(self) -> None:
|
||||
@@ -741,6 +789,112 @@ class MemoryAnalyzer:
|
||||
len(libraries),
|
||||
)
|
||||
|
||||
def _scan_source_symbols(self) -> None:
|
||||
"""Scan ESPHome source object files to map extern "C" symbols to components.
|
||||
|
||||
When no linker map file is available, this uses ``nm`` to scan ``.o`` files
|
||||
under ``src/esphome/`` and build a symbol-to-component mapping. This catches
|
||||
``extern "C"`` functions and other symbols that lack C++ namespace prefixes.
|
||||
|
||||
Skips scanning if ``_source_symbol_map`` was already populated by
|
||||
``_parse_map_file()``.
|
||||
"""
|
||||
if self._source_symbol_map or not self.nm_path:
|
||||
return
|
||||
|
||||
obj_dir = self._find_object_files_dir()
|
||||
if obj_dir is None:
|
||||
return
|
||||
|
||||
# Find ESPHome source object files
|
||||
esphome_src_dir = obj_dir / "src" / "esphome"
|
||||
if not esphome_src_dir.is_dir():
|
||||
return
|
||||
|
||||
obj_files = sorted(esphome_src_dir.rglob("*.o"))
|
||||
if not obj_files:
|
||||
return
|
||||
|
||||
# Run nm with --print-file-name to get file:symbol mapping
|
||||
result = run_tool(
|
||||
[self.nm_path, "--print-file-name", "-g", "--defined-only"]
|
||||
+ [str(f) for f in obj_files],
|
||||
)
|
||||
if result is None or result.returncode != 0:
|
||||
_LOGGER.debug("nm scan of source objects failed")
|
||||
return
|
||||
|
||||
self._source_symbol_map = self._parse_nm_source_output(result.stdout, obj_dir)
|
||||
if self._source_symbol_map:
|
||||
_LOGGER.info(
|
||||
"Built source symbol map from nm: %d symbols",
|
||||
len(self._source_symbol_map),
|
||||
)
|
||||
|
||||
def _parse_nm_source_output(self, output: str, base_dir: Path) -> dict[str, str]:
|
||||
"""Parse nm output to map non-namespaced symbols to ESPHome components.
|
||||
|
||||
Extracts global defined symbols from ESPHome source object files that
|
||||
don't use C++ namespacing (e.g. ``extern "C"`` functions).
|
||||
|
||||
Args:
|
||||
output: Raw stdout from ``nm --print-file-name -g --defined-only``
|
||||
or ``nm --print-file-name -S``.
|
||||
base_dir: Build directory for computing relative paths.
|
||||
|
||||
Returns:
|
||||
Dict mapping symbol names to component names.
|
||||
"""
|
||||
source_map: dict[str, str] = {}
|
||||
for line in output.splitlines():
|
||||
# Format: /path/to/file.o: addr type name
|
||||
# or: /path/to/file.o: addr size type name (with -S)
|
||||
colon_idx = line.rfind(".o:")
|
||||
if colon_idx == -1:
|
||||
continue
|
||||
|
||||
file_path = line[: colon_idx + 2]
|
||||
fields = line[colon_idx + 3 :].split()
|
||||
if len(fields) < 3:
|
||||
continue
|
||||
|
||||
# With -S flag, format is: addr size type name
|
||||
# Without -S flag: addr type name
|
||||
# type is a single char; size is hex digits
|
||||
# Detect by checking if fields[1] is a single uppercase letter (type)
|
||||
if len(fields[1]) == 1 and fields[1].isalpha():
|
||||
# addr type name
|
||||
sym_type = fields[1]
|
||||
symbol_name = fields[2]
|
||||
elif len(fields) >= 4:
|
||||
# addr size type name
|
||||
sym_type = fields[2]
|
||||
symbol_name = fields[3]
|
||||
else:
|
||||
continue
|
||||
|
||||
# Only global defined symbols (uppercase type)
|
||||
if not sym_type.isupper() or sym_type == "U":
|
||||
continue
|
||||
|
||||
# Skip symbols already in esphome:: namespace
|
||||
if symbol_name.startswith("_ZN7esphome"):
|
||||
continue
|
||||
|
||||
# Make path relative to base_dir for _source_file_to_component
|
||||
try:
|
||||
rel_path = str(Path(file_path).relative_to(base_dir))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
component = self._source_file_to_component(rel_path)
|
||||
if component.startswith(
|
||||
(_COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL)
|
||||
):
|
||||
source_map[symbol_name] = component
|
||||
|
||||
return source_map
|
||||
|
||||
def _find_object_files_dir(self) -> Path | None:
|
||||
"""Find the directory containing object files for this build.
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from . import (
|
||||
_COMPONENT_PREFIX_ESPHOME,
|
||||
_COMPONENT_PREFIX_EXTERNAL,
|
||||
_COMPONENT_PREFIX_LIB,
|
||||
_PSTORAGE_SUFFIX,
|
||||
RAM_SECTIONS,
|
||||
MemoryAnalyzer,
|
||||
)
|
||||
@@ -23,6 +24,17 @@ if TYPE_CHECKING:
|
||||
from . import ComponentMemory
|
||||
|
||||
|
||||
def _format_pstorage_name(name: str) -> str:
|
||||
"""Format a __pstorage symbol as 'storage for {id}'."""
|
||||
if not name.endswith(_PSTORAGE_SUFFIX):
|
||||
return name
|
||||
prefix = name[: -len(_PSTORAGE_SUFFIX)]
|
||||
# Strip component namespace prefix: {component}__{id} -> {id}
|
||||
dunder_pos = prefix.find("__")
|
||||
var_id = prefix[dunder_pos + 2 :] if dunder_pos != -1 else prefix
|
||||
return f"storage for {var_id}"
|
||||
|
||||
|
||||
class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
"""Memory analyzer with CLI-specific report generation."""
|
||||
|
||||
@@ -148,11 +160,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
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.
|
||||
|
||||
Placement new storage symbols are formatted as "storage for {id}".
|
||||
"""
|
||||
display_name = _format_pstorage_name(demangled)
|
||||
section_label = ""
|
||||
if section in RAM_SECTIONS:
|
||||
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
|
||||
return f"{demangled} ({size:,} B){section_label}"
|
||||
return f"{display_name} ({size:,} B){section_label}"
|
||||
|
||||
def _add_top_symbols(self, lines: list[str]) -> None:
|
||||
"""Add a section showing the top largest symbols in the binary."""
|
||||
@@ -175,11 +190,13 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
for i, (_, demangled, size, section, component) in enumerate(top_symbols):
|
||||
# Format section label
|
||||
section_label = f"[{section[1:]}]" if section else ""
|
||||
# Truncate demangled name if too long
|
||||
# Format storage symbols readably
|
||||
display_name = _format_pstorage_name(demangled)
|
||||
# Truncate if too long
|
||||
demangled_display = (
|
||||
f"{demangled[:truncate_limit]}..."
|
||||
if len(demangled) > self.COL_TOP_SYMBOL_NAME
|
||||
else demangled
|
||||
f"{display_name[:truncate_limit]}..."
|
||||
if len(display_name) > self.COL_TOP_SYMBOL_NAME
|
||||
else display_name
|
||||
)
|
||||
lines.append(
|
||||
f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}"
|
||||
@@ -573,15 +590,16 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
lines.append(f"Total size: {comp_mem.flash_total:,} B")
|
||||
lines.append("")
|
||||
|
||||
# Show all symbols above threshold for better visibility
|
||||
# Show symbols above threshold, always include storage symbols
|
||||
large_symbols = [
|
||||
(sym, dem, size, sec)
|
||||
for sym, dem, size, sec in sorted_symbols
|
||||
if size > self.SYMBOL_SIZE_THRESHOLD
|
||||
or dem.endswith(_PSTORAGE_SUFFIX)
|
||||
]
|
||||
|
||||
lines.append(
|
||||
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):"
|
||||
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B & storage ({len(large_symbols)} symbols):"
|
||||
)
|
||||
for i, (symbol, demangled, size, section) in enumerate(large_symbols):
|
||||
lines.append(
|
||||
@@ -604,7 +622,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
# 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
|
||||
s
|
||||
for s in sorted_ram_syms
|
||||
if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD
|
||||
or s[1].endswith(_PSTORAGE_SUFFIX)
|
||||
]
|
||||
|
||||
lines.append(f"{name} ({mem.ram_total:,} B total RAM):")
|
||||
@@ -622,13 +643,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
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 ""
|
||||
display_name = _format_pstorage_name(demangled)
|
||||
# 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}"
|
||||
display_name = (
|
||||
f"{display_name[:70]}..."
|
||||
if len(display_name) > 70
|
||||
else display_name
|
||||
)
|
||||
lines.append(f" {size:>6,} B [{section_label}] {display_name}")
|
||||
if len(large_ram_syms) > 10:
|
||||
lines.append(f" ... and {len(large_ram_syms) - 10} more")
|
||||
lines.append("")
|
||||
|
||||
@@ -408,7 +408,6 @@ SYMBOL_PATTERNS = {
|
||||
],
|
||||
"arduino_core": [
|
||||
"pinMode",
|
||||
"resetPins",
|
||||
"millis",
|
||||
"micros",
|
||||
"delay(", # More specific - Arduino delay function with parenthesis
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "absolute_humidity.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace absolute_humidity {
|
||||
namespace esphome::absolute_humidity {
|
||||
|
||||
static const char *const TAG = "absolute_humidity.sensor";
|
||||
static const char *const TAG{"absolute_humidity.sensor"};
|
||||
|
||||
void AbsoluteHumidityComponent::setup() {
|
||||
this->temperature_sensor_->add_on_state_callback([this](float state) {
|
||||
this->temperature_ = state;
|
||||
this->enable_loop();
|
||||
});
|
||||
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
|
||||
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
|
||||
// Get initial value
|
||||
if (this->temperature_sensor_->has_state()) {
|
||||
this->temperature_callback_(this->temperature_sensor_->get_state());
|
||||
this->temperature_ = this->temperature_sensor_->get_state();
|
||||
}
|
||||
|
||||
this->humidity_sensor_->add_on_state_callback([this](float state) {
|
||||
this->humidity_ = state;
|
||||
this->enable_loop();
|
||||
});
|
||||
ESP_LOGD(TAG, " Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str());
|
||||
this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); });
|
||||
// Get initial value
|
||||
if (this->humidity_sensor_->has_state()) {
|
||||
this->humidity_callback_(this->humidity_sensor_->get_state());
|
||||
this->humidity_ = this->humidity_sensor_->get_state();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,14 +53,12 @@ void AbsoluteHumidityComponent::dump_config() {
|
||||
}
|
||||
|
||||
void AbsoluteHumidityComponent::loop() {
|
||||
if (!this->next_update_) {
|
||||
return;
|
||||
}
|
||||
this->next_update_ = false;
|
||||
// Only run once
|
||||
this->disable_loop();
|
||||
|
||||
// Ensure we have source data
|
||||
const bool no_temperature = std::isnan(this->temperature_);
|
||||
const bool no_humidity = std::isnan(this->humidity_);
|
||||
const bool no_temperature{std::isnan(this->temperature_)};
|
||||
const bool no_humidity{std::isnan(this->humidity_)};
|
||||
if (no_temperature || no_humidity) {
|
||||
if (no_temperature) {
|
||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||
@@ -67,9 +72,9 @@ void AbsoluteHumidityComponent::loop() {
|
||||
}
|
||||
|
||||
// Convert to desired units
|
||||
const float temperature_c = this->temperature_;
|
||||
const float temperature_k = temperature_c + 273.15;
|
||||
const float hr = this->humidity_ / 100;
|
||||
const float temperature_c{this->temperature_};
|
||||
const float temperature_k{temperature_c + 273.15f};
|
||||
const float hr{this->humidity_ / 100.0f};
|
||||
|
||||
// Calculate saturation vapor pressure
|
||||
float es;
|
||||
@@ -90,7 +95,7 @@ void AbsoluteHumidityComponent::loop() {
|
||||
}
|
||||
|
||||
// Calculate absolute humidity
|
||||
const float absolute_humidity = vapor_density(es, hr, temperature_k);
|
||||
const float absolute_humidity{vapor_density(es, hr, temperature_k)};
|
||||
|
||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa, absolute humidity %f g/m³", es, absolute_humidity);
|
||||
|
||||
@@ -103,16 +108,16 @@ void AbsoluteHumidityComponent::loop() {
|
||||
// More accurate than Tetens in normal meteorologic conditions
|
||||
float AbsoluteHumidityComponent::es_buck(float temperature_c) {
|
||||
float a, b, c, d;
|
||||
if (temperature_c >= 0) {
|
||||
a = 0.61121;
|
||||
b = 18.678;
|
||||
c = 234.5;
|
||||
d = 257.14;
|
||||
if (temperature_c >= 0.0f) {
|
||||
a = 0.61121f;
|
||||
b = 18.678f;
|
||||
c = 234.5f;
|
||||
d = 257.14f;
|
||||
} else {
|
||||
a = 0.61115;
|
||||
b = 18.678;
|
||||
c = 233.7;
|
||||
d = 279.82;
|
||||
a = 0.61115f;
|
||||
b = 18.678f;
|
||||
c = 233.7f;
|
||||
d = 279.82f;
|
||||
}
|
||||
return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c)));
|
||||
}
|
||||
@@ -120,14 +125,14 @@ float AbsoluteHumidityComponent::es_buck(float temperature_c) {
|
||||
// Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation)
|
||||
float AbsoluteHumidityComponent::es_tetens(float temperature_c) {
|
||||
float a, b;
|
||||
if (temperature_c >= 0) {
|
||||
a = 17.27;
|
||||
b = 237.3;
|
||||
if (temperature_c >= 0.0f) {
|
||||
a = 17.27f;
|
||||
b = 237.3f;
|
||||
} else {
|
||||
a = 21.875;
|
||||
b = 265.5;
|
||||
a = 21.875f;
|
||||
b = 265.5f;
|
||||
}
|
||||
return 0.61078 * expf((a * temperature_c) / (temperature_c + b));
|
||||
return 0.61078f * expf((a * temperature_c) / (temperature_c + b));
|
||||
}
|
||||
|
||||
// Wobus equation
|
||||
@@ -146,18 +151,18 @@ float AbsoluteHumidityComponent::es_wobus(float t) {
|
||||
//
|
||||
// Baker, Schlatter 17-MAY-1982 Original version.
|
||||
|
||||
const float c0 = +0.99999683e00;
|
||||
const float c1 = -0.90826951e-02;
|
||||
const float c2 = +0.78736169e-04;
|
||||
const float c3 = -0.61117958e-06;
|
||||
const float c4 = +0.43884187e-08;
|
||||
const float c5 = -0.29883885e-10;
|
||||
const float c6 = +0.21874425e-12;
|
||||
const float c7 = -0.17892321e-14;
|
||||
const float c8 = +0.11112018e-16;
|
||||
const float c9 = -0.30994571e-19;
|
||||
const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))));
|
||||
return 0.61078 / pow(p, 8);
|
||||
constexpr float c0{+0.99999683e+00f};
|
||||
constexpr float c1{-0.90826951e-02f};
|
||||
constexpr float c2{+0.78736169e-04f};
|
||||
constexpr float c3{-0.61117958e-06f};
|
||||
constexpr float c4{+0.43884187e-08f};
|
||||
constexpr float c5{-0.29883885e-10f};
|
||||
constexpr float c6{+0.21874425e-12f};
|
||||
constexpr float c7{-0.17892321e-14f};
|
||||
constexpr float c8{+0.11112018e-16f};
|
||||
constexpr float c9{-0.30994571e-19f};
|
||||
const float p{c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))))};
|
||||
return 0.61078f / powf(p, 8.0f);
|
||||
}
|
||||
|
||||
// From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/
|
||||
@@ -168,11 +173,10 @@ float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) {
|
||||
// hr = relative humidity [0-1]
|
||||
// ta = absolute temperature (K)
|
||||
|
||||
const float ea = hr * es * 1000; // vapor pressure of the air (Pa)
|
||||
const float mw = 18.01528; // molar mass of water (g⋅mol⁻¹)
|
||||
const float r = 8.31446261815324; // molar gas constant (J⋅K⁻¹)
|
||||
const float ea{hr * es * 1000.0f}; // vapor pressure of the air (Pa)
|
||||
const float mw{18.01528f}; // molar mass of water (g⋅mol⁻¹)
|
||||
const float r{8.31446261815324f}; // molar gas constant (J⋅K⁻¹)
|
||||
return (ea * mw) / (r * ta);
|
||||
}
|
||||
|
||||
} // namespace absolute_humidity
|
||||
} // namespace esphome
|
||||
} // namespace esphome::absolute_humidity
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace absolute_humidity {
|
||||
namespace esphome::absolute_humidity {
|
||||
|
||||
/// Enum listing all implemented saturation vapor pressure equations.
|
||||
enum SaturationVaporPressureEquation {
|
||||
@@ -16,8 +15,6 @@ enum SaturationVaporPressureEquation {
|
||||
/// This class implements calculation of absolute humidity from temperature and relative humidity.
|
||||
class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
public:
|
||||
AbsoluteHumidityComponent() = default;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; }
|
||||
@@ -27,15 +24,6 @@ class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
void temperature_callback_(float state) {
|
||||
this->next_update_ = true;
|
||||
this->temperature_ = state;
|
||||
}
|
||||
void humidity_callback_(float state) {
|
||||
this->next_update_ = true;
|
||||
this->humidity_ = state;
|
||||
}
|
||||
|
||||
/** Buck equation for saturation vapor pressure in kPa.
|
||||
*
|
||||
* @param temperature_c Air temperature in °C.
|
||||
@@ -57,19 +45,15 @@ class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
* @param es Saturation vapor pressure in kPa.
|
||||
* @param hr Relative humidity 0 to 1.
|
||||
* @param ta Absolute temperature in K.
|
||||
* @param heater_duration The duration in ms that the heater should turn on for when measuring.
|
||||
*/
|
||||
static float vapor_density(float es, float hr, float ta);
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
|
||||
bool next_update_{false};
|
||||
|
||||
float temperature_{NAN};
|
||||
float humidity_{NAN};
|
||||
SaturationVaporPressureEquation equation_;
|
||||
};
|
||||
|
||||
} // namespace absolute_humidity
|
||||
} // namespace esphome
|
||||
} // namespace esphome::absolute_humidity
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
@@ -455,6 +455,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.11")
|
||||
# Enable optimized memzero/memcmp in libsodium instead of volatile byte loops
|
||||
cg.add_build_flag("-DHAVE_WEAK_SYMBOLS=1")
|
||||
cg.add_build_flag("-DHAVE_INLINE_ASM=1")
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ message ListEntitiesBinarySensorResponse {
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -334,7 +334,7 @@ message BinarySensorStateResponse {
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
// If the binary sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -350,7 +350,7 @@ message ListEntitiesCoverResponse {
|
||||
option (ifdef) = "USE_COVER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -383,7 +383,7 @@ message CoverStateResponse {
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// legacy: state has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
// Deprecated in API version 1.1
|
||||
@@ -409,7 +409,7 @@ message CoverCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
|
||||
// legacy: command has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
@@ -434,7 +434,7 @@ message ListEntitiesFanResponse {
|
||||
option (ifdef) = "USE_FAN";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -466,7 +466,7 @@ message FanStateResponse {
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
// Deprecated in API version 1.6
|
||||
@@ -483,7 +483,7 @@ message FanCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
// Deprecated in API version 1.6
|
||||
@@ -522,7 +522,7 @@ message ListEntitiesLightResponse {
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -551,7 +551,7 @@ message LightStateResponse {
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
ColorMode color_mode = 11;
|
||||
@@ -573,7 +573,7 @@ message LightCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
@@ -627,7 +627,7 @@ message ListEntitiesSensorResponse {
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -651,7 +651,7 @@ message SensorStateResponse {
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float state = 2;
|
||||
// If the sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -667,7 +667,7 @@ message ListEntitiesSwitchResponse {
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -685,7 +685,7 @@ message SwitchStateResponse {
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -696,7 +696,7 @@ message SwitchCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -709,7 +709,7 @@ message ListEntitiesTextSensorResponse {
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -726,7 +726,7 @@ message TextSensorStateResponse {
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
// If the text sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -922,7 +922,7 @@ message ListEntitiesServicesResponse {
|
||||
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
|
||||
|
||||
string name = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
repeated ListEntitiesServicesArgument args = 3 [(fixed_vector) = true];
|
||||
SupportsResponseType supports_response = 4;
|
||||
}
|
||||
@@ -945,7 +945,7 @@ message ExecuteServiceRequest {
|
||||
option (no_delay) = true;
|
||||
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
repeated ExecuteServiceArgument args = 2 [(fixed_vector) = true];
|
||||
uint32 call_id = 3 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"];
|
||||
bool return_response = 4 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"];
|
||||
@@ -972,7 +972,7 @@ message ListEntitiesCameraResponse {
|
||||
option (ifdef) = "USE_CAMERA";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
bool disabled_by_default = 5;
|
||||
@@ -987,7 +987,7 @@ message CameraImageResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CAMERA";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bytes data = 2;
|
||||
bool done = 3;
|
||||
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
|
||||
@@ -1057,7 +1057,7 @@ message ListEntitiesClimateResponse {
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1095,7 +1095,7 @@ message ClimateStateResponse {
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
ClimateMode mode = 2;
|
||||
float current_temperature = 3;
|
||||
float target_temperature = 4;
|
||||
@@ -1121,7 +1121,7 @@ message ClimateCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_mode = 2;
|
||||
ClimateMode mode = 3;
|
||||
bool has_target_temperature = 4;
|
||||
@@ -1168,7 +1168,7 @@ message ListEntitiesWaterHeaterResponse {
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 5;
|
||||
@@ -1189,7 +1189,7 @@ message WaterHeaterStateResponse {
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float current_temperature = 2;
|
||||
float target_temperature = 3;
|
||||
WaterHeaterMode mode = 4;
|
||||
@@ -1219,7 +1219,7 @@ message WaterHeaterCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// Bitmask of which fields are set (see WaterHeaterCommandHasField)
|
||||
uint32 has_fields = 2;
|
||||
WaterHeaterMode mode = 3;
|
||||
@@ -1244,7 +1244,7 @@ message ListEntitiesNumberResponse {
|
||||
option (ifdef) = "USE_NUMBER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1266,7 +1266,7 @@ message NumberStateResponse {
|
||||
option (ifdef) = "USE_NUMBER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float state = 2;
|
||||
// If the number does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -1280,7 +1280,7 @@ message NumberCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -1293,7 +1293,7 @@ message ListEntitiesSelectResponse {
|
||||
option (ifdef) = "USE_SELECT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1310,7 +1310,7 @@ message SelectStateResponse {
|
||||
option (ifdef) = "USE_SELECT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
// If the select does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -1324,7 +1324,7 @@ message SelectCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -1337,7 +1337,7 @@ message ListEntitiesSirenResponse {
|
||||
option (ifdef) = "USE_SIREN";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1356,7 +1356,7 @@ message SirenStateResponse {
|
||||
option (ifdef) = "USE_SIREN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -1367,7 +1367,7 @@ message SirenCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_tone = 4;
|
||||
@@ -1400,7 +1400,7 @@ message ListEntitiesLockResponse {
|
||||
option (ifdef) = "USE_LOCK";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1422,7 +1422,7 @@ message LockStateResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
LockState state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -1432,7 +1432,7 @@ message LockCommandRequest {
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
LockCommand command = 2;
|
||||
|
||||
// Not yet implemented:
|
||||
@@ -1449,7 +1449,7 @@ message ListEntitiesButtonResponse {
|
||||
option (ifdef) = "USE_BUTTON";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1466,7 +1466,7 @@ message ButtonCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
uint32 device_id = 2 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
@@ -1516,7 +1516,7 @@ message ListEntitiesMediaPlayerResponse {
|
||||
option (ifdef) = "USE_MEDIA_PLAYER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1538,7 +1538,7 @@ message MediaPlayerStateResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_MEDIA_PLAYER";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
MediaPlayerState state = 2;
|
||||
float volume = 3;
|
||||
bool muted = 4;
|
||||
@@ -1551,7 +1551,7 @@ message MediaPlayerCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
|
||||
bool has_command = 2;
|
||||
MediaPlayerCommand command = 3;
|
||||
@@ -2104,7 +2104,7 @@ message ListEntitiesAlarmControlPanelResponse {
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
@@ -2122,7 +2122,7 @@ message AlarmControlPanelStateResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
AlarmControlPanelState state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2133,7 +2133,7 @@ message AlarmControlPanelCommandRequest {
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
AlarmControlPanelStateCommand command = 2;
|
||||
string code = 3;
|
||||
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
|
||||
@@ -2151,7 +2151,7 @@ message ListEntitiesTextResponse {
|
||||
option (ifdef) = "USE_TEXT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
@@ -2171,7 +2171,7 @@ message TextStateResponse {
|
||||
option (ifdef) = "USE_TEXT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
// If the Text does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -2185,7 +2185,7 @@ message TextCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2199,7 +2199,7 @@ message ListEntitiesDateResponse {
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2215,7 +2215,7 @@ message DateStateResponse {
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// If the date does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 2;
|
||||
@@ -2231,7 +2231,7 @@ message DateCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
uint32 year = 2;
|
||||
uint32 month = 3;
|
||||
uint32 day = 4;
|
||||
@@ -2246,7 +2246,7 @@ message ListEntitiesTimeResponse {
|
||||
option (ifdef) = "USE_DATETIME_TIME";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2262,7 +2262,7 @@ message TimeStateResponse {
|
||||
option (ifdef) = "USE_DATETIME_TIME";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// If the time does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 2;
|
||||
@@ -2278,7 +2278,7 @@ message TimeCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
uint32 hour = 2;
|
||||
uint32 minute = 3;
|
||||
uint32 second = 4;
|
||||
@@ -2293,7 +2293,7 @@ message ListEntitiesEventResponse {
|
||||
option (ifdef) = "USE_EVENT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2311,7 +2311,7 @@ message EventResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_EVENT";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string event_type = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2324,7 +2324,7 @@ message ListEntitiesValveResponse {
|
||||
option (ifdef) = "USE_VALVE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2351,7 +2351,7 @@ message ValveStateResponse {
|
||||
option (ifdef) = "USE_VALVE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float position = 2;
|
||||
ValveOperation current_operation = 3;
|
||||
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
|
||||
@@ -2364,7 +2364,7 @@ message ValveCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_position = 2;
|
||||
float position = 3;
|
||||
bool stop = 4;
|
||||
@@ -2379,7 +2379,7 @@ message ListEntitiesDateTimeResponse {
|
||||
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2395,7 +2395,7 @@ message DateTimeStateResponse {
|
||||
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// If the datetime does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 2;
|
||||
@@ -2409,7 +2409,7 @@ message DateTimeCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
fixed32 epoch_seconds = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2422,7 +2422,7 @@ message ListEntitiesUpdateResponse {
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2439,7 +2439,7 @@ message UpdateStateResponse {
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool missing_state = 2;
|
||||
bool in_progress = 3;
|
||||
bool has_progress = 4;
|
||||
@@ -2463,7 +2463,7 @@ message UpdateCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
UpdateCommand command = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2505,7 +2505,7 @@ message ListEntitiesInfraredResponse {
|
||||
option (ifdef) = "USE_INFRARED";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 5;
|
||||
@@ -2521,7 +2521,7 @@ message InfraredRFTransmitRawTimingsRequest {
|
||||
option (ifdef) = "USE_IR_RF";
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2; // Key identifying the transmitter instance
|
||||
fixed32 key = 2 [(force) = true]; // 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)
|
||||
@@ -2535,7 +2535,7 @@ message InfraredRFReceiveEvent {
|
||||
option (no_delay) = true;
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2; // Key identifying the receiver instance
|
||||
fixed32 key = 2 [(force) = true]; // 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
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,11 @@ static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS *
|
||||
// A stalled handshake from a buggy client or network glitch holds a connection
|
||||
// slot, which can prevent legitimate clients from reconnecting. Also hardens
|
||||
// against the less likely case of intentional connection slot exhaustion.
|
||||
static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 15000;
|
||||
//
|
||||
// 60s is intentionally high: on ESP8266 with power_save_mode: LIGHT and weak
|
||||
// WiFi (-70 dBm+), TCP retransmissions push real-world handshake times to
|
||||
// 28-30s. See https://github.com/esphome/esphome/issues/14999
|
||||
static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 60000;
|
||||
|
||||
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||
|
||||
@@ -230,7 +234,7 @@ void APIConnection::loop() {
|
||||
this->last_traffic_ = now;
|
||||
}
|
||||
// read a packet
|
||||
this->read_message(buffer.data_len, buffer.type, buffer.data);
|
||||
this->read_message_(buffer.data_len, buffer.type, buffer.data);
|
||||
if (this->flags_.remove)
|
||||
return;
|
||||
}
|
||||
@@ -1515,16 +1519,16 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
|
||||
resp.instance = msg.instance;
|
||||
resp.type = enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH;
|
||||
switch (proxies[msg.instance]->flush_port()) {
|
||||
case uart::FlushResult::SUCCESS:
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_OK;
|
||||
break;
|
||||
case uart::FlushResult::ASSUMED_SUCCESS:
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS;
|
||||
break;
|
||||
case uart::FlushResult::TIMEOUT:
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_TIMEOUT;
|
||||
break;
|
||||
case uart::FlushResult::FAILED:
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -49,11 +49,29 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
friend class APIServer;
|
||||
friend class ListEntitiesIterator;
|
||||
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
|
||||
virtual ~APIConnection();
|
||||
~APIConnection();
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
|
||||
protected:
|
||||
// read_message_ is defined here (instead of in APIServerConnectionBase) so the
|
||||
// compiler can devirtualize and inline on_* handler calls within this final class.
|
||||
void read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data);
|
||||
|
||||
// Auth helpers defined here (not in ProtoService) so the compiler can
|
||||
// devirtualize is_connection_setup()/on_no_setup_connection() calls
|
||||
// within this final class.
|
||||
inline bool check_connection_setup_() {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
inline bool check_authenticated_() { return this->check_connection_setup_(); }
|
||||
|
||||
public:
|
||||
bool send_list_info_done() {
|
||||
return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE,
|
||||
ListEntitiesDoneResponse::ESTIMATED_SIZE);
|
||||
@@ -63,72 +81,72 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state(cover::Cover *cover);
|
||||
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
||||
void on_cover_command_request(const CoverCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state(fan::Fan *fan);
|
||||
void on_fan_command_request(const FanCommandRequest &msg) override;
|
||||
void on_fan_command_request(const FanCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state(light::LightState *light);
|
||||
void on_light_command_request(const LightCommandRequest &msg) override;
|
||||
void on_light_command_request(const LightCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state(sensor::Sensor *sensor);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state(switch_::Switch *a_switch);
|
||||
void on_switch_command_request(const SwitchCommandRequest &msg) override;
|
||||
void on_switch_command_request(const SwitchCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor);
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
void set_camera_state(std::shared_ptr<camera::CameraImage> image);
|
||||
void on_camera_image_request(const CameraImageRequest &msg) override;
|
||||
void on_camera_image_request(const CameraImageRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg) override;
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool send_number_state(number::Number *number);
|
||||
void on_number_command_request(const NumberCommandRequest &msg) override;
|
||||
void on_number_command_request(const NumberCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool send_date_state(datetime::DateEntity *date);
|
||||
void on_date_command_request(const DateCommandRequest &msg) override;
|
||||
void on_date_command_request(const DateCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool send_time_state(datetime::TimeEntity *time);
|
||||
void on_time_command_request(const TimeCommandRequest &msg) override;
|
||||
void on_time_command_request(const TimeCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool send_datetime_state(datetime::DateTimeEntity *datetime);
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_text_state(text::Text *text);
|
||||
void on_text_command_request(const TextCommandRequest &msg) override;
|
||||
void on_text_command_request(const TextCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_select_state(select::Select *select);
|
||||
void on_select_command_request(const SelectCommandRequest &msg) override;
|
||||
void on_select_command_request(const SelectCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
void on_button_command_request(const ButtonCommandRequest &msg) override;
|
||||
void on_button_command_request(const ButtonCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state(lock::Lock *a_lock);
|
||||
void on_lock_command_request(const LockCommandRequest &msg) override;
|
||||
void on_lock_command_request(const LockCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool send_valve_state(valve::Valve *valve);
|
||||
void on_valve_command_request(const ValveCommandRequest &msg) override;
|
||||
void on_valve_command_request(const ValveCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool send_media_player_state(media_player::MediaPlayer *media_player);
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &msg);
|
||||
#endif
|
||||
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
@@ -138,23 +156,23 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
this->send_message(call);
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &msg);
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request() override;
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg);
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request();
|
||||
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
|
||||
void on_subscribe_bluetooth_connections_free_request() override;
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
|
||||
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg) override;
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg);
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg);
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg);
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg);
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg);
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg);
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg);
|
||||
void on_subscribe_bluetooth_connections_free_request();
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg);
|
||||
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg);
|
||||
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
@@ -165,42 +183,42 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
|
||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
|
||||
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override;
|
||||
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg);
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &msg);
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg);
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &msg);
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg);
|
||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg);
|
||||
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg);
|
||||
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override;
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
|
||||
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg);
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
|
||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
|
||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool send_water_heater_state(water_heater::WaterHeater *water_heater);
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override;
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg);
|
||||
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) override;
|
||||
void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) override;
|
||||
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) override;
|
||||
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) override;
|
||||
void on_serial_proxy_request(const SerialProxyRequest &msg) override;
|
||||
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg);
|
||||
void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg);
|
||||
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg);
|
||||
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg);
|
||||
void on_serial_proxy_request(const SerialProxyRequest &msg);
|
||||
void send_serial_proxy_data(const SerialProxyDataReceived &msg);
|
||||
#endif
|
||||
|
||||
@@ -210,26 +228,26 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool send_update_state(update::UpdateEntity *update);
|
||||
void on_update_command_request(const UpdateCommandRequest &msg) override;
|
||||
void on_update_command_request(const UpdateCommandRequest &msg);
|
||||
#endif
|
||||
|
||||
void on_disconnect_response() override;
|
||||
void on_ping_response() override {
|
||||
void on_disconnect_response();
|
||||
void on_ping_response() {
|
||||
// we initiated ping
|
||||
this->flags_.sent_ping = false;
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void on_get_time_response(const GetTimeResponse &value) override;
|
||||
void on_get_time_response(const GetTimeResponse &value);
|
||||
#endif
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
void on_disconnect_request() override;
|
||||
void on_ping_request() override;
|
||||
void on_device_info_request() override;
|
||||
void on_list_entities_request() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
|
||||
void on_subscribe_states_request() override {
|
||||
void on_hello_request(const HelloRequest &msg);
|
||||
void on_disconnect_request();
|
||||
void on_ping_request();
|
||||
void on_device_info_request();
|
||||
void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
|
||||
void on_subscribe_states_request() {
|
||||
this->flags_.state_subscription = true;
|
||||
// Start initial state iterator only if no iterator is active
|
||||
// If list_entities is running, we'll start initial_state when it completes
|
||||
@@ -237,7 +255,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||
}
|
||||
}
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override {
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
|
||||
this->flags_.log_subscription = msg.level;
|
||||
if (msg.dump_config)
|
||||
App.schedule_dump_config();
|
||||
@@ -249,13 +267,13 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; }
|
||||
void on_subscribe_homeassistant_services_request() { this->flags_.service_call_subscription = true; }
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void on_subscribe_home_assistant_states_request() override;
|
||||
void on_subscribe_home_assistant_states_request();
|
||||
#endif
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
@@ -265,13 +283,13 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg);
|
||||
#endif
|
||||
|
||||
bool is_authenticated() override {
|
||||
bool is_authenticated() {
|
||||
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED;
|
||||
}
|
||||
bool is_connection_setup() override {
|
||||
bool is_connection_setup() {
|
||||
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED ||
|
||||
this->is_authenticated();
|
||||
}
|
||||
@@ -284,8 +302,8 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
(this->client_api_version_major_ == major && this->client_api_version_minor_ >= minor);
|
||||
}
|
||||
|
||||
void on_fatal_error() override;
|
||||
void on_no_setup_connection() override;
|
||||
void on_fatal_error();
|
||||
void on_no_setup_connection();
|
||||
|
||||
// Function pointer type for type-erased message encoding
|
||||
using MessageEncodeFn = void (*)(const void *, ProtoWriteBuffer &);
|
||||
@@ -324,7 +342,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
return true;
|
||||
return this->try_to_clear_buffer_slow_(log_out_of_space);
|
||||
}
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type);
|
||||
|
||||
const char *get_name() const { return this->helper_->get_client_name(); }
|
||||
/// Get peer name (IP address) into caller-provided buffer, returns buf for convenience
|
||||
|
||||
+100
-100
@@ -208,7 +208,7 @@ uint32_t DeviceInfoResponse::calculate_size() const {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(5, this->device_class);
|
||||
buffer.encode_bool(6, this->is_status_binary_sensor);
|
||||
@@ -224,7 +224,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesBinarySensorResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_length(1, this->device_class.size());
|
||||
size += ProtoSize::calc_bool(1, this->is_status_binary_sensor);
|
||||
@@ -239,7 +239,7 @@ uint32_t ListEntitiesBinarySensorResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -248,7 +248,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t BinarySensorStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -260,7 +260,7 @@ uint32_t BinarySensorStateResponse::calculate_size() const {
|
||||
#ifdef USE_COVER
|
||||
void ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_bool(5, this->assumed_state);
|
||||
buffer.encode_bool(6, this->supports_position);
|
||||
@@ -279,7 +279,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesCoverResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_bool(1, this->assumed_state);
|
||||
size += ProtoSize::calc_bool(1, this->supports_position);
|
||||
@@ -297,7 +297,7 @@ uint32_t ListEntitiesCoverResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void CoverStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(3, this->position);
|
||||
buffer.encode_float(4, this->tilt);
|
||||
buffer.encode_uint32(5, static_cast<uint32_t>(this->current_operation));
|
||||
@@ -307,7 +307,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t CoverStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->position);
|
||||
size += ProtoSize::calc_float(1, this->tilt);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->current_operation));
|
||||
@@ -357,7 +357,7 @@ bool CoverCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_FAN
|
||||
void ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_bool(5, this->supports_oscillation);
|
||||
buffer.encode_bool(6, this->supports_speed);
|
||||
@@ -378,7 +378,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesFanResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_bool(1, this->supports_oscillation);
|
||||
size += ProtoSize::calc_bool(1, this->supports_speed);
|
||||
@@ -400,7 +400,7 @@ uint32_t ListEntitiesFanResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void FanStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_bool(3, this->oscillating);
|
||||
buffer.encode_uint32(5, static_cast<uint32_t>(this->direction));
|
||||
@@ -412,7 +412,7 @@ void FanStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t FanStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->oscillating);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->direction));
|
||||
@@ -487,7 +487,7 @@ bool FanCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_LIGHT
|
||||
void ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
for (const auto &it : *this->supported_color_modes) {
|
||||
buffer.encode_uint32(12, static_cast<uint32_t>(it), true);
|
||||
@@ -509,7 +509,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesLightResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
if (!this->supported_color_modes->empty()) {
|
||||
for (const auto &it : *this->supported_color_modes) {
|
||||
@@ -534,7 +534,7 @@ uint32_t ListEntitiesLightResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void LightStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_float(3, this->brightness);
|
||||
buffer.encode_uint32(11, static_cast<uint32_t>(this->color_mode));
|
||||
@@ -553,7 +553,7 @@ void LightStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t LightStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
size += ProtoSize::calc_float(1, this->brightness);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->color_mode));
|
||||
@@ -683,7 +683,7 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_SENSOR
|
||||
void ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -702,7 +702,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesSensorResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -720,7 +720,7 @@ uint32_t ListEntitiesSensorResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void SensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -729,7 +729,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t SensorStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -741,7 +741,7 @@ uint32_t SensorStateResponse::calculate_size() const {
|
||||
#ifdef USE_SWITCH
|
||||
void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -757,7 +757,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesSwitchResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -772,7 +772,7 @@ uint32_t ListEntitiesSwitchResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void SwitchStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -780,7 +780,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t SwitchStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -816,7 +816,7 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -831,7 +831,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesTextSensorResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -845,7 +845,7 @@ uint32_t ListEntitiesTextSensorResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void TextSensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -854,7 +854,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t TextSensorStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->state.size());
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1124,7 +1124,7 @@ uint32_t ListEntitiesServicesArgument::calculate_size() const {
|
||||
}
|
||||
void ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->name);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
for (auto &it : this->args) {
|
||||
buffer.encode_sub_message(3, it);
|
||||
}
|
||||
@@ -1133,7 +1133,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesServicesResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
if (!this->args.empty()) {
|
||||
for (const auto &it : this->args) {
|
||||
size += ProtoSize::calc_message_force(1, it.calculate_size());
|
||||
@@ -1269,7 +1269,7 @@ uint32_t ExecuteServiceResponse::calculate_size() const {
|
||||
#ifdef USE_CAMERA
|
||||
void ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
@@ -1283,7 +1283,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesCameraResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_bool(1, this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
@@ -1296,7 +1296,7 @@ uint32_t ListEntitiesCameraResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void CameraImageResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bytes(2, this->data_ptr_, this->data_len_);
|
||||
buffer.encode_bool(3, this->done);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1305,7 +1305,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t CameraImageResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->data_len_);
|
||||
size += ProtoSize::calc_bool(1, this->done);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1330,7 +1330,7 @@ bool CameraImageRequest::decode_varint(uint32_t field_id, proto_varint_value_t v
|
||||
#ifdef USE_CLIMATE
|
||||
void ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_bool(5, this->supports_current_temperature);
|
||||
buffer.encode_bool(6, this->supports_two_point_target_temperature);
|
||||
@@ -1374,7 +1374,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesClimateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_bool(1, this->supports_current_temperature);
|
||||
size += ProtoSize::calc_bool(1, this->supports_two_point_target_temperature);
|
||||
@@ -1429,7 +1429,7 @@ uint32_t ListEntitiesClimateResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void ClimateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
||||
buffer.encode_float(3, this->current_temperature);
|
||||
buffer.encode_float(4, this->target_temperature);
|
||||
@@ -1449,7 +1449,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t ClimateStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->mode));
|
||||
size += ProtoSize::calc_float(1, this->current_temperature);
|
||||
size += ProtoSize::calc_float(1, this->target_temperature);
|
||||
@@ -1563,7 +1563,7 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_WATER_HEATER
|
||||
void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(4, this->icon);
|
||||
@@ -1584,7 +1584,7 @@ void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1606,7 +1606,7 @@ uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(2, this->current_temperature);
|
||||
buffer.encode_float(3, this->target_temperature);
|
||||
buffer.encode_uint32(4, static_cast<uint32_t>(this->mode));
|
||||
@@ -1619,7 +1619,7 @@ void WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t WaterHeaterStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->current_temperature);
|
||||
size += ProtoSize::calc_float(1, this->target_temperature);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->mode));
|
||||
@@ -1675,7 +1675,7 @@ bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value
|
||||
#ifdef USE_NUMBER
|
||||
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -1695,7 +1695,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesNumberResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1714,7 +1714,7 @@ uint32_t ListEntitiesNumberResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void NumberStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1723,7 +1723,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t NumberStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1760,7 +1760,7 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_SELECT
|
||||
void ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -1777,7 +1777,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesSelectResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1795,7 +1795,7 @@ uint32_t ListEntitiesSelectResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void SelectStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1804,7 +1804,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t SelectStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->state.size());
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1849,7 +1849,7 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_SIREN
|
||||
void ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -1868,7 +1868,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesSirenResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1888,7 +1888,7 @@ uint32_t ListEntitiesSirenResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void SirenStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -1896,7 +1896,7 @@ void SirenStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t SirenStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -1961,7 +1961,7 @@ bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_LOCK
|
||||
void ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -1979,7 +1979,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesLockResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1996,7 +1996,7 @@ uint32_t ListEntitiesLockResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void LockStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -2004,7 +2004,7 @@ void LockStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t LockStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -2054,7 +2054,7 @@ bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_BUTTON
|
||||
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -2069,7 +2069,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesButtonResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -2124,7 +2124,7 @@ uint32_t MediaPlayerSupportedFormat::calculate_size() const {
|
||||
}
|
||||
void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -2143,7 +2143,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesMediaPlayerResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -2163,7 +2163,7 @@ uint32_t ListEntitiesMediaPlayerResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
buffer.encode_float(3, this->volume);
|
||||
buffer.encode_bool(4, this->muted);
|
||||
@@ -2173,7 +2173,7 @@ void MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t MediaPlayerStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
|
||||
size += ProtoSize::calc_float(1, this->volume);
|
||||
size += ProtoSize::calc_bool(1, this->muted);
|
||||
@@ -2942,7 +2942,7 @@ bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengt
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -2959,7 +2959,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer) con
|
||||
uint32_t ListEntitiesAlarmControlPanelResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -2975,7 +2975,7 @@ uint32_t ListEntitiesAlarmControlPanelResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -2983,7 +2983,7 @@ void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t AlarmControlPanelStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -3030,7 +3030,7 @@ bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit
|
||||
#ifdef USE_TEXT
|
||||
void ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3048,7 +3048,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesTextResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3065,7 +3065,7 @@ uint32_t ListEntitiesTextResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void TextStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3074,7 +3074,7 @@ void TextStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t TextStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->state.size());
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3119,7 +3119,7 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3133,7 +3133,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesDateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3146,7 +3146,7 @@ uint32_t ListEntitiesDateResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void DateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_uint32(3, this->year);
|
||||
buffer.encode_uint32(4, this->month);
|
||||
@@ -3157,7 +3157,7 @@ void DateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t DateStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
size += ProtoSize::calc_uint32(1, this->year);
|
||||
size += ProtoSize::calc_uint32(1, this->month);
|
||||
@@ -3202,7 +3202,7 @@ bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3216,7 +3216,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesTimeResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3229,7 +3229,7 @@ uint32_t ListEntitiesTimeResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void TimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_uint32(3, this->hour);
|
||||
buffer.encode_uint32(4, this->minute);
|
||||
@@ -3240,7 +3240,7 @@ void TimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t TimeStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
size += ProtoSize::calc_uint32(1, this->hour);
|
||||
size += ProtoSize::calc_uint32(1, this->minute);
|
||||
@@ -3285,7 +3285,7 @@ bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_EVENT
|
||||
void ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3303,7 +3303,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesEventResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3322,7 +3322,7 @@ uint32_t ListEntitiesEventResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void EventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_string(2, this->event_type);
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -3330,7 +3330,7 @@ void EventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t EventResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->event_type.size());
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -3341,7 +3341,7 @@ uint32_t EventResponse::calculate_size() const {
|
||||
#ifdef USE_VALVE
|
||||
void ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3359,7 +3359,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesValveResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3376,7 +3376,7 @@ uint32_t ListEntitiesValveResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void ValveStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(2, this->position);
|
||||
buffer.encode_uint32(3, static_cast<uint32_t>(this->current_operation));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3385,7 +3385,7 @@ void ValveStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t ValveStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->position);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->current_operation));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3428,7 +3428,7 @@ bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3442,7 +3442,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesDateTimeResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3455,7 +3455,7 @@ uint32_t ListEntitiesDateTimeResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void DateTimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_fixed32(3, this->epoch_seconds);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3464,7 +3464,7 @@ void DateTimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t DateTimeStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
size += ProtoSize::calc_fixed32(1, this->epoch_seconds);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3501,7 +3501,7 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_UPDATE
|
||||
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3516,7 +3516,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesUpdateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3530,7 +3530,7 @@ uint32_t ListEntitiesUpdateResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void UpdateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_bool(3, this->in_progress);
|
||||
buffer.encode_bool(4, this->has_progress);
|
||||
@@ -3546,7 +3546,7 @@ void UpdateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t UpdateStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
size += ProtoSize::calc_bool(1, this->in_progress);
|
||||
size += ProtoSize::calc_bool(1, this->has_progress);
|
||||
@@ -3642,7 +3642,7 @@ uint32_t ZWaveProxyRequest::calculate_size() const {
|
||||
#ifdef USE_INFRARED
|
||||
void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(4, this->icon);
|
||||
@@ -3657,7 +3657,7 @@ void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesInfraredResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3717,7 +3717,7 @@ void InfraredRFReceiveEvent::encode(ProtoWriteBuffer &buffer) const {
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(1, this->device_id);
|
||||
#endif
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
for (const auto &it : *this->timings) {
|
||||
buffer.encode_sint32(3, it, true);
|
||||
}
|
||||
@@ -3727,7 +3727,7 @@ uint32_t InfraredRFReceiveEvent::calculate_size() const {
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
#endif
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
if (!this->timings->empty()) {
|
||||
for (const auto &it : *this->timings) {
|
||||
size += ProtoSize::calc_sint32_force(1, it);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See script/api_protobuf/api_protobuf.py
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::api {
|
||||
@@ -20,7 +21,7 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
// Check authentication/connection requirements
|
||||
switch (msg_type) {
|
||||
case HelloRequest::MESSAGE_TYPE: // No setup required
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
class APIServerConnectionBase : public ProtoService {
|
||||
class APIServerConnectionBase {
|
||||
public:
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
protected:
|
||||
@@ -19,227 +19,223 @@ class APIServerConnectionBase : public ProtoService {
|
||||
public:
|
||||
#endif
|
||||
|
||||
virtual void on_hello_request(const HelloRequest &value){};
|
||||
void on_hello_request(const HelloRequest &value){};
|
||||
|
||||
virtual void on_disconnect_request(){};
|
||||
virtual void on_disconnect_response(){};
|
||||
virtual void on_ping_request(){};
|
||||
virtual void on_ping_response(){};
|
||||
virtual void on_device_info_request(){};
|
||||
void on_disconnect_request(){};
|
||||
void on_disconnect_response(){};
|
||||
void on_ping_request(){};
|
||||
void on_ping_response(){};
|
||||
void on_device_info_request(){};
|
||||
|
||||
virtual void on_list_entities_request(){};
|
||||
void on_list_entities_request(){};
|
||||
|
||||
virtual void on_subscribe_states_request(){};
|
||||
void on_subscribe_states_request(){};
|
||||
|
||||
#ifdef USE_COVER
|
||||
virtual void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
virtual void on_fan_command_request(const FanCommandRequest &value){};
|
||||
void on_fan_command_request(const FanCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
virtual void on_light_command_request(const LightCommandRequest &value){};
|
||||
void on_light_command_request(const LightCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
virtual void on_subscribe_homeassistant_services_request(){};
|
||||
void on_subscribe_homeassistant_services_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
virtual void on_subscribe_home_assistant_states_request(){};
|
||||
void on_subscribe_home_assistant_states_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
#endif
|
||||
|
||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||
void on_get_time_response(const GetTimeResponse &value){};
|
||||
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||
void on_camera_image_request(const CameraImageRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
virtual void on_number_command_request(const NumberCommandRequest &value){};
|
||||
void on_number_command_request(const NumberCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
virtual void on_select_command_request(const SelectCommandRequest &value){};
|
||||
void on_select_command_request(const SelectCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SIREN
|
||||
virtual void on_siren_command_request(const SirenCommandRequest &value){};
|
||||
void on_siren_command_request(const SirenCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
virtual void on_lock_command_request(const LockCommandRequest &value){};
|
||||
void on_lock_command_request(const LockCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
virtual void on_button_command_request(const ButtonCommandRequest &value){};
|
||||
void on_button_command_request(const ButtonCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_subscribe_bluetooth_connections_free_request(){};
|
||||
void on_subscribe_bluetooth_connections_free_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_unsubscribe_bluetooth_le_advertisements_request(){};
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){};
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
|
||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
|
||||
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
|
||||
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
|
||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
virtual void on_text_command_request(const TextCommandRequest &value){};
|
||||
void on_text_command_request(const TextCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
virtual void on_date_command_request(const DateCommandRequest &value){};
|
||||
void on_date_command_request(const DateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
virtual void on_time_command_request(const TimeCommandRequest &value){};
|
||||
void on_time_command_request(const TimeCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VALVE
|
||||
virtual void on_valve_command_request(const ValveCommandRequest &value){};
|
||||
void on_valve_command_request(const ValveCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
virtual void on_update_command_request(const UpdateCommandRequest &value){};
|
||||
void on_update_command_request(const UpdateCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
|
||||
void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
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){};
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){};
|
||||
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){};
|
||||
void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){};
|
||||
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){};
|
||||
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_request(const SerialProxyRequest &value){};
|
||||
void on_serial_proxy_request(const SerialProxyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
|
||||
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -136,8 +136,9 @@ class CustomAPIDevice {
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
|
||||
auto *obj = static_cast<T *>(this);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
|
||||
[obj, callback](StringRef state) { (obj->*callback)(state); });
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
|
||||
@@ -148,10 +149,12 @@ class CustomAPIDevice {
|
||||
ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
auto *obj = static_cast<T *>(this);
|
||||
// Explicit type to disambiguate overload resolution
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(f));
|
||||
global_api_server->subscribe_home_assistant_state(
|
||||
entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(
|
||||
[obj, callback](const std::string &state) { (obj->*callback)(state); }));
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||
@@ -176,8 +179,10 @@ class CustomAPIDevice {
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
|
||||
auto *obj = static_cast<T *>(this);
|
||||
global_api_server->subscribe_home_assistant_state(
|
||||
entity_id, optional<std::string>(attribute),
|
||||
[obj, callback, entity_id](StringRef state) { (obj->*callback)(entity_id, state); });
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
|
||||
@@ -188,10 +193,12 @@ class CustomAPIDevice {
|
||||
ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
auto *obj = static_cast<T *>(this);
|
||||
// Explicit type to disambiguate overload resolution
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(f));
|
||||
global_api_server->subscribe_home_assistant_state(
|
||||
entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(
|
||||
[obj, callback, entity_id](const std::string &state) { (obj->*callback)(entity_id, state); }));
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
|
||||
@@ -236,6 +236,21 @@ class ProtoWriteBuffer {
|
||||
* Following https://protobuf.dev/programming-guides/encoding/#structure
|
||||
*/
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
|
||||
/// Write a precomputed tag byte + 32-bit value in one operation.
|
||||
/// Tag must be a single-byte varint (< 128). No zero check.
|
||||
inline void write_tag_and_fixed32(uint8_t tag, uint32_t value) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(5);
|
||||
this->pos_[0] = tag;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
std::memcpy(this->pos_ + 1, &value, 4);
|
||||
#else
|
||||
this->pos_[1] = static_cast<uint8_t>(value & 0xFF);
|
||||
this->pos_[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
|
||||
this->pos_[3] = static_cast<uint8_t>((value >> 16) & 0xFF);
|
||||
this->pos_[4] = static_cast<uint8_t>((value >> 24) & 0xFF);
|
||||
#endif
|
||||
this->pos_ += 5;
|
||||
}
|
||||
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
@@ -276,8 +291,7 @@ class ProtoWriteBuffer {
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = value ? 0x01 : 0x00;
|
||||
}
|
||||
// noinline: 51 call sites; inlining causes net code growth vs a single out-of-line copy
|
||||
__attribute__((noinline)) void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
@@ -697,26 +711,7 @@ inline void ProtoLengthDelimited::decode_to_message(ProtoDecodableMessage &msg)
|
||||
|
||||
template<typename T> const char *proto_enum_to_string(T value);
|
||||
|
||||
class ProtoService {
|
||||
public:
|
||||
protected:
|
||||
virtual bool is_authenticated() = 0;
|
||||
virtual bool is_connection_setup() = 0;
|
||||
virtual void on_fatal_error() = 0;
|
||||
virtual void on_no_setup_connection() = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
|
||||
virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0;
|
||||
|
||||
// Authentication helper methods
|
||||
inline bool check_connection_setup_() {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool check_authenticated_() { return this->check_connection_setup_(); }
|
||||
};
|
||||
// ProtoService removed — its methods were inlined into APIConnection.
|
||||
// APIConnection is the concrete server-side implementation; the extra virtual layer was unnecessary.
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -96,8 +96,7 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
||||
|
||||
void setup() override {
|
||||
this->last_state_ = this->parent_->get_state_default(false);
|
||||
auto f = std::bind(&MultiClickTrigger::on_state_, this, std::placeholders::_1);
|
||||
this->parent_->add_on_state_callback(f);
|
||||
this->parent_->add_on_state_callback([this](bool state) { this->on_state_(state); });
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
@@ -103,17 +103,17 @@ size_t BLENUS::available() {
|
||||
#endif
|
||||
}
|
||||
|
||||
uart::FlushResult BLENUS::flush() {
|
||||
uart::UARTFlushResult BLENUS::flush() {
|
||||
constexpr uint32_t timeout_500ms = 500;
|
||||
uint32_t start = millis();
|
||||
while (atomic_get(&this->tx_status_) != TX_DISABLED && !ring_buf_is_empty(&global_ble_tx_ring_buf)) {
|
||||
if (millis() - start > timeout_500ms) {
|
||||
ESP_LOGW(TAG, "Flush timeout");
|
||||
return uart::FlushResult::TIMEOUT;
|
||||
return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
return uart::FlushResult::SUCCESS;
|
||||
return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void BLENUS::connected(bt_conn *conn, uint8_t err) {
|
||||
|
||||
@@ -26,7 +26,7 @@ class BLENUS : public uart::UARTComponent, public Component {
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
size_t available() override;
|
||||
uart::FlushResult flush() override;
|
||||
uart::UARTFlushResult flush() override;
|
||||
void check_logger_conflict() override {}
|
||||
void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; }
|
||||
#ifdef USE_LOGGER
|
||||
|
||||
@@ -279,7 +279,8 @@ void BME68xBSEC2Component::run_() {
|
||||
uint32_t meas_dur = 0;
|
||||
meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
|
||||
ESP_LOGV(TAG, "Queueing read in %uus", meas_dur);
|
||||
this->set_timeout("read", meas_dur / 1000, [this, curr_time_ns]() { this->read_(curr_time_ns); });
|
||||
this->trigger_time_ns_ = curr_time_ns;
|
||||
this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); });
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Measurement not required");
|
||||
this->read_(curr_time_ns);
|
||||
|
||||
@@ -116,6 +116,8 @@ class BME68xBSEC2Component : public Component {
|
||||
int8_t bme68x_status_{BME68X_OK};
|
||||
|
||||
int64_t last_time_ms_{0};
|
||||
int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit
|
||||
// toolchains with small std::function SBO
|
||||
uint32_t millis_overflow_counter_{0};
|
||||
|
||||
std::queue<std::function<void()>> queue_;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace combination {
|
||||
@@ -20,12 +18,12 @@ void CombinationComponent::log_config_(const LogString *combo_type) {
|
||||
|
||||
void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); }
|
||||
|
||||
void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
|
||||
this->sensor_pairs_.emplace_back(sensor, stddev);
|
||||
void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &compute) {
|
||||
this->sensor_sources_.push_back({sensor, compute, this});
|
||||
}
|
||||
|
||||
void CombinationOneParameterComponent::add_source(Sensor *sensor, float stddev) {
|
||||
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
|
||||
void CombinationOneParameterComponent::add_source(Sensor *sensor, float value) {
|
||||
this->add_source(sensor, std::function<float(float)>{[value](float x) -> float { return value; }});
|
||||
}
|
||||
|
||||
void CombinationNoParameterComponent::log_source_sensors() {
|
||||
@@ -37,9 +35,8 @@ void CombinationNoParameterComponent::log_source_sensors() {
|
||||
|
||||
void CombinationOneParameterComponent::log_source_sensors() {
|
||||
ESP_LOGCONFIG(TAG, " Source Sensors:");
|
||||
for (const auto &sensor : this->sensor_pairs_) {
|
||||
auto &entity = *sensor.first;
|
||||
ESP_LOGCONFIG(TAG, " - %s", entity.get_name().c_str());
|
||||
for (const auto &source : this->sensor_sources_) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", source.sensor->get_name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,9 +59,12 @@ void KalmanCombinationComponent::dump_config() {
|
||||
}
|
||||
|
||||
void KalmanCombinationComponent::setup() {
|
||||
for (const auto &sensor : this->sensor_pairs_) {
|
||||
const auto stddev = sensor.second;
|
||||
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
|
||||
for (auto &source : this->sensor_sources_) {
|
||||
// [&source] is safe: source refers to a FixedVector element that never reallocates,
|
||||
// so the reference remains valid for the component's lifetime.
|
||||
source.sensor->add_on_state_callback([&source](float x) -> void {
|
||||
static_cast<KalmanCombinationComponent *>(source.parent)->correct_(x, source.compute(x));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,10 +117,10 @@ void KalmanCombinationComponent::correct_(float value, float stddev) {
|
||||
}
|
||||
|
||||
void LinearCombinationComponent::setup() {
|
||||
for (const auto &sensor : this->sensor_pairs_) {
|
||||
for (auto &source : this->sensor_sources_) {
|
||||
// All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
|
||||
// repeatedly in the same loop if multiple source senors update.
|
||||
sensor.first->add_on_state_callback(
|
||||
source.sensor->add_on_state_callback(
|
||||
[this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
|
||||
}
|
||||
}
|
||||
@@ -133,10 +133,10 @@ void LinearCombinationComponent::handle_new_value(float value) {
|
||||
|
||||
float sum = 0.0;
|
||||
|
||||
for (const auto &sensor : this->sensor_pairs_) {
|
||||
const float sensor_state = sensor.first->state;
|
||||
for (const auto &source : this->sensor_sources_) {
|
||||
const float sensor_state = source.sensor->state;
|
||||
if (std::isfinite(sensor_state)) {
|
||||
sum += sensor_state * sensor.second(sensor_state);
|
||||
sum += sensor_state * source.compute(sensor_state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace esphome {
|
||||
namespace combination {
|
||||
@@ -41,14 +42,21 @@ class CombinationNoParameterComponent : public CombinationComponent {
|
||||
// Base class for opertions that require one parameter to compute the combination
|
||||
class CombinationOneParameterComponent : public CombinationComponent {
|
||||
public:
|
||||
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
|
||||
void add_source(Sensor *sensor, float stddev);
|
||||
void set_source_count(size_t count) { this->sensor_sources_.init(count); }
|
||||
void add_source(Sensor *sensor, std::function<float(float)> const &compute);
|
||||
void add_source(Sensor *sensor, float value);
|
||||
|
||||
/// @brief Logs all source sensor's names in sensor_pairs_
|
||||
/// @brief Logs all source sensors' names in sensor_sources_
|
||||
void log_source_sensors() override;
|
||||
|
||||
protected:
|
||||
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensor_pairs_;
|
||||
struct SensorSource {
|
||||
sensor::Sensor *sensor;
|
||||
std::function<float(float)> compute;
|
||||
CombinationOneParameterComponent *parent;
|
||||
};
|
||||
|
||||
FixedVector<SensorSource> sensor_sources_;
|
||||
};
|
||||
|
||||
class KalmanCombinationComponent : public CombinationOneParameterComponent {
|
||||
|
||||
@@ -180,6 +180,9 @@ async def to_code(config):
|
||||
if proces_std_dev := config.get(CONF_PROCESS_STD_DEV):
|
||||
cg.add(var.set_process_std_dev(proces_std_dev))
|
||||
|
||||
if config[CONF_TYPE] in (CONF_KALMAN, CONF_LINEAR):
|
||||
cg.add(var.set_source_count(len(config[CONF_SOURCES])))
|
||||
|
||||
for source_conf in config[CONF_SOURCES]:
|
||||
source = await cg.get_variable(source_conf[CONF_SOURCE])
|
||||
if config[CONF_TYPE] == CONF_KALMAN:
|
||||
|
||||
@@ -105,17 +105,18 @@ template<typename... Ts> using CoverIsClosedCondition = CoverPositionCondition<f
|
||||
|
||||
template<bool OPEN> class CoverPositionTrigger : public Trigger<> {
|
||||
public:
|
||||
CoverPositionTrigger(Cover *a_cover) {
|
||||
a_cover->add_on_state_callback([this, a_cover]() {
|
||||
if (a_cover->position != this->last_position_) {
|
||||
this->last_position_ = a_cover->position;
|
||||
if (a_cover->position == (OPEN ? COVER_OPEN : COVER_CLOSED))
|
||||
CoverPositionTrigger(Cover *a_cover) : cover_(a_cover) {
|
||||
a_cover->add_on_state_callback([this]() {
|
||||
if (this->cover_->position != this->last_position_) {
|
||||
this->last_position_ = this->cover_->position;
|
||||
if (this->cover_->position == (OPEN ? COVER_OPEN : COVER_CLOSED))
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
float last_position_{NAN};
|
||||
};
|
||||
|
||||
@@ -124,9 +125,9 @@ using CoverClosedTrigger = CoverPositionTrigger<false>;
|
||||
|
||||
template<CoverOperation OP> class CoverTrigger : public Trigger<> {
|
||||
public:
|
||||
CoverTrigger(Cover *a_cover) {
|
||||
a_cover->add_on_state_callback([this, a_cover]() {
|
||||
auto current_op = a_cover->current_operation;
|
||||
CoverTrigger(Cover *a_cover) : cover_(a_cover) {
|
||||
a_cover->add_on_state_callback([this]() {
|
||||
auto current_op = this->cover_->current_operation;
|
||||
if (current_op == OP) {
|
||||
if (!this->last_operation_.has_value() || this->last_operation_.value() != OP) {
|
||||
this->trigger();
|
||||
@@ -137,6 +138,7 @@ template<CoverOperation OP> class CoverTrigger : public Trigger<> {
|
||||
}
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
optional<CoverOperation> last_operation_{};
|
||||
};
|
||||
} // namespace esphome::cover
|
||||
|
||||
@@ -33,9 +33,12 @@ class DateTimeBase : public EntityBase {
|
||||
|
||||
class DateTimeStateTrigger : public Trigger<ESPTime> {
|
||||
public:
|
||||
explicit DateTimeStateTrigger(DateTimeBase *parent) {
|
||||
parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); });
|
||||
explicit DateTimeStateTrigger(DateTimeBase *parent) : parent_(parent) {
|
||||
parent->add_on_state_callback([this]() { this->trigger(this->parent_->state_as_esptime()); });
|
||||
}
|
||||
|
||||
protected:
|
||||
DateTimeBase *parent_;
|
||||
};
|
||||
|
||||
} // namespace esphome::datetime
|
||||
|
||||
@@ -96,37 +96,52 @@ template<typename... Ts> class IsActiveCondition : public Condition<Ts...> {
|
||||
|
||||
class DisplayMenuOnEnterTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnEnterTrigger(MenuItem *parent) {
|
||||
parent->add_on_enter_callback([this, parent]() { this->trigger(parent); });
|
||||
explicit DisplayMenuOnEnterTrigger(MenuItem *parent) : parent_(parent) {
|
||||
parent->add_on_enter_callback([this]() { this->trigger(this->parent_); });
|
||||
}
|
||||
|
||||
protected:
|
||||
MenuItem *parent_;
|
||||
};
|
||||
|
||||
class DisplayMenuOnLeaveTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) {
|
||||
parent->add_on_leave_callback([this, parent]() { this->trigger(parent); });
|
||||
explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) : parent_(parent) {
|
||||
parent->add_on_leave_callback([this]() { this->trigger(this->parent_); });
|
||||
}
|
||||
|
||||
protected:
|
||||
MenuItem *parent_;
|
||||
};
|
||||
|
||||
class DisplayMenuOnValueTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnValueTrigger(MenuItem *parent) {
|
||||
parent->add_on_value_callback([this, parent]() { this->trigger(parent); });
|
||||
explicit DisplayMenuOnValueTrigger(MenuItem *parent) : parent_(parent) {
|
||||
parent->add_on_value_callback([this]() { this->trigger(this->parent_); });
|
||||
}
|
||||
|
||||
protected:
|
||||
MenuItem *parent_;
|
||||
};
|
||||
|
||||
class DisplayMenuOnNextTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) {
|
||||
parent->add_on_next_callback([this, parent]() { this->trigger(parent); });
|
||||
explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) : parent_(parent) {
|
||||
parent->add_on_next_callback([this]() { this->trigger(this->parent_); });
|
||||
}
|
||||
|
||||
protected:
|
||||
MenuItemCustom *parent_;
|
||||
};
|
||||
|
||||
class DisplayMenuOnPrevTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) {
|
||||
parent->add_on_prev_callback([this, parent]() { this->trigger(parent); });
|
||||
explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) : parent_(parent) {
|
||||
parent->add_on_prev_callback([this]() { this->trigger(this->parent_); });
|
||||
}
|
||||
|
||||
protected:
|
||||
MenuItemCustom *parent_;
|
||||
};
|
||||
|
||||
} // namespace display_menu_base
|
||||
|
||||
@@ -127,8 +127,7 @@ void DPS310Component::read_() {
|
||||
this->update_in_progress_ = false;
|
||||
this->status_clear_warning();
|
||||
} else {
|
||||
auto f = std::bind(&DPS310Component::read_, this);
|
||||
this->set_timeout("dps310", 10, f);
|
||||
this->set_timeout("dps310", 10, [this]() { this->read_(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ from esphome.const import (
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
CONF_REF,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_SIZE,
|
||||
CONF_SOURCE,
|
||||
CONF_TYPE,
|
||||
CONF_VARIANT,
|
||||
@@ -96,6 +97,7 @@ CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
||||
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
||||
CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision"
|
||||
CONF_RELEASE = "release"
|
||||
CONF_SUBTYPE = "subtype"
|
||||
|
||||
ARDUINO_FRAMEWORK_NAME = "framework-arduinoespressif32"
|
||||
ARDUINO_FRAMEWORK_PKG = f"pioarduino/{ARDUINO_FRAMEWORK_NAME}"
|
||||
@@ -1258,6 +1260,43 @@ def _set_default_framework(config):
|
||||
return config
|
||||
|
||||
|
||||
RESERVED_PARTITION_NAMES = {
|
||||
"nvs",
|
||||
"app0",
|
||||
"app1",
|
||||
"otadata",
|
||||
"eeprom",
|
||||
"spiffs",
|
||||
"phy_init",
|
||||
}
|
||||
|
||||
VALID_APP_SUBTYPES = {"factory", "test"}
|
||||
VALID_DATA_SUBTYPES = {
|
||||
"nvs",
|
||||
"nvs_keys",
|
||||
"spiffs",
|
||||
"coredump",
|
||||
"efuse",
|
||||
"fat",
|
||||
"undefined",
|
||||
"littlefs",
|
||||
}
|
||||
|
||||
|
||||
def _validate_custom_partition(config: ConfigType) -> ConfigType:
|
||||
"""Voluptuous validator for custom partition schema."""
|
||||
try:
|
||||
_validate_partition(
|
||||
config[CONF_NAME],
|
||||
config[CONF_TYPE],
|
||||
config[CONF_SUBTYPE],
|
||||
config[CONF_SIZE],
|
||||
)
|
||||
except ValueError as e:
|
||||
raise cv.Invalid(str(e)) from e
|
||||
return config
|
||||
|
||||
|
||||
FLASH_SIZES = [
|
||||
"2MB",
|
||||
"4MB",
|
||||
@@ -1280,7 +1319,28 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_FLASH_SIZE, default="4MB"): cv.one_of(
|
||||
*FLASH_SIZES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_PARTITIONS): cv.file_,
|
||||
cv.Optional(CONF_PARTITIONS): cv.Any(
|
||||
cv.file_,
|
||||
cv.ensure_list(
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Required(CONF_TYPE): cv.All(
|
||||
cv.Any(cv.string_strict, cv.int_range(0x40, 0xFE)),
|
||||
cv.int_to_hex_string,
|
||||
),
|
||||
cv.Required(CONF_SUBTYPE): cv.All(
|
||||
cv.Any(cv.string_strict, cv.int_range(0, 0xFE)),
|
||||
cv.int_to_hex_string,
|
||||
),
|
||||
cv.Required(CONF_SIZE): cv.int_range(min=0x1000),
|
||||
}
|
||||
),
|
||||
_validate_custom_partition,
|
||||
),
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True),
|
||||
cv.Optional(CONF_FRAMEWORK): FRAMEWORK_SCHEMA,
|
||||
}
|
||||
@@ -1749,9 +1809,18 @@ async def to_code(config):
|
||||
if use_platformio:
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
if CONF_PARTITIONS in config:
|
||||
add_extra_build_file(
|
||||
"partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS])
|
||||
)
|
||||
if isinstance(config[CONF_PARTITIONS], list):
|
||||
for partition in config[CONF_PARTITIONS]:
|
||||
add_partition(
|
||||
partition[CONF_NAME],
|
||||
partition[CONF_TYPE],
|
||||
partition[CONF_SUBTYPE],
|
||||
partition[CONF_SIZE],
|
||||
)
|
||||
else:
|
||||
add_extra_build_file(
|
||||
"partitions.csv", CORE.relative_config_path(config[CONF_PARTITIONS])
|
||||
)
|
||||
|
||||
if assertion_level := advanced.get(CONF_ASSERTION_LEVEL):
|
||||
for key, flag in ASSERTION_LEVELS.items():
|
||||
@@ -1851,6 +1920,18 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA384_C", False)
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA512_C", False)
|
||||
|
||||
# Disable PicolibC Newlib compatibility shim on IDF 6.0+
|
||||
# IDF 6.0 switched from Newlib to PicolibC. The shim provides thread-local
|
||||
# stdin/stdout/stderr and getreent() for code compiled against Newlib.
|
||||
# ESPHome doesn't link against Newlib-built libraries that use stdio.
|
||||
# If a component needs it (e.g. precompiled Newlib binaries), re-enable via:
|
||||
# esp32:
|
||||
# framework:
|
||||
# sdkconfig_options:
|
||||
# CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY: "y"
|
||||
if idf_version() >= cv.Version(6, 0, 0):
|
||||
add_idf_sdkconfig_option("CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY", False)
|
||||
|
||||
# Disable regi2c control functions in IRAM
|
||||
# Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled
|
||||
if advanced[CONF_DISABLE_REGI2C_IN_IRAM]:
|
||||
@@ -1885,45 +1966,175 @@ async def to_code(config):
|
||||
CORE.add_job(_write_arduino_libraries_sdkconfig)
|
||||
|
||||
|
||||
APP_PARTITION_SIZES = {
|
||||
"2MB": 0x0C0000, # 768 KB
|
||||
"4MB": 0x1C0000, # 1792 KB
|
||||
"8MB": 0x3C0000, # 3840 KB
|
||||
"16MB": 0x7C0000, # 7936 KB
|
||||
"32MB": 0xFC0000, # 16128 KB
|
||||
KEY_CUSTOM_PARTITIONS = "custom_partitions"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PartitionEntry:
|
||||
name: str
|
||||
type: str
|
||||
subtype: str
|
||||
size: int
|
||||
|
||||
|
||||
# Partition sizes (offsets auto-placed by gen_esp32part.py).
|
||||
# These constants are the single source of truth — used in both
|
||||
# the CSV generation and the overhead calculation.
|
||||
BOOTLOADER_SIZE = 0x8000
|
||||
PARTITION_TABLE_SIZE = 0x1000
|
||||
FIRST_PARTITION_OFFSET = BOOTLOADER_SIZE + PARTITION_TABLE_SIZE
|
||||
OTADATA_SIZE = 0x2000
|
||||
PHY_INIT_SIZE = 0x1000
|
||||
EEPROM_SIZE = 0x1000 # Arduino only
|
||||
SPIFFS_SIZE = 0xF000 # Arduino only
|
||||
ARDUINO_NVS_SIZE = 0x60000
|
||||
IDF_NVS_SIZE = 0x70000
|
||||
|
||||
|
||||
def _get_partition_overhead() -> int:
|
||||
"""Total non-app partition budget (system partitions + nvs + padding).
|
||||
|
||||
Custom partitions are appended at the end and steal from app.
|
||||
"""
|
||||
# otadata + phy_init are followed by app0 which requires 64KB alignment,
|
||||
# so pad up to the next 64KB boundary.
|
||||
overhead = (
|
||||
FIRST_PARTITION_OFFSET + OTADATA_SIZE + PHY_INIT_SIZE + 0xFFFF
|
||||
) & ~0xFFFF
|
||||
if CORE.using_arduino:
|
||||
overhead += EEPROM_SIZE + SPIFFS_SIZE + ARDUINO_NVS_SIZE
|
||||
else:
|
||||
overhead += IDF_NVS_SIZE
|
||||
return overhead
|
||||
|
||||
|
||||
VALID_SUBTYPES: dict[str, set[str]] = {
|
||||
"app": VALID_APP_SUBTYPES,
|
||||
"data": VALID_DATA_SUBTYPES,
|
||||
}
|
||||
|
||||
|
||||
def get_arduino_partition_csv(flash_size: str):
|
||||
app_partition_size = APP_PARTITION_SIZES[flash_size]
|
||||
eeprom_partition_size = 0x1000 # 4 KB
|
||||
spiffs_partition_size = 0xF000 # 60 KB
|
||||
|
||||
app0_partition_start = 0x010000 # 64 KB
|
||||
app1_partition_start = app0_partition_start + app_partition_size
|
||||
eeprom_partition_start = app1_partition_start + app_partition_size
|
||||
spiffs_partition_start = eeprom_partition_start + eeprom_partition_size
|
||||
|
||||
return f"""\
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xE000, 0x2000,
|
||||
app0, app, ota_0, 0x{app0_partition_start:X}, 0x{app_partition_size:X},
|
||||
app1, app, ota_1, 0x{app1_partition_start:X}, 0x{app_partition_size:X},
|
||||
eeprom, data, 0x99, 0x{eeprom_partition_start:X}, 0x{eeprom_partition_size:X},
|
||||
spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size:X}
|
||||
"""
|
||||
def _validate_partition(
|
||||
name: str, p_type: str | int, subtype: str | int, size: int
|
||||
) -> None:
|
||||
"""Validate partition parameters. Raises ValueError on invalid input."""
|
||||
if name in RESERVED_PARTITION_NAMES:
|
||||
raise ValueError(f"Partition name '{name}' is reserved.")
|
||||
if size % 0x1000 != 0:
|
||||
raise ValueError("Partition size must be 4KB (0x1000) aligned.")
|
||||
# Numeric or already-normalized hex types/subtypes skip string validation
|
||||
if not isinstance(p_type, str) or p_type.startswith("0x"):
|
||||
return
|
||||
if p_type not in VALID_SUBTYPES:
|
||||
raise ValueError(
|
||||
f"Type '{p_type}' is invalid. Only 'app' and 'data' are allowed."
|
||||
" Use numbers for custom types."
|
||||
)
|
||||
if not isinstance(subtype, str) or subtype.startswith("0x"):
|
||||
return
|
||||
valid = VALID_SUBTYPES[p_type]
|
||||
if subtype not in valid:
|
||||
raise ValueError(
|
||||
f"Subtype '{subtype}' is invalid for {p_type} type."
|
||||
f" Only {', '.join(sorted(valid))} are allowed."
|
||||
" Use numbers for custom subtypes."
|
||||
)
|
||||
|
||||
|
||||
def get_idf_partition_csv(flash_size: str):
|
||||
app_partition_size = APP_PARTITION_SIZES[flash_size]
|
||||
def add_partition(name: str, p_type: str | int, subtype: str | int, size: int) -> None:
|
||||
"""Register a custom partition to be appended to the partition table.
|
||||
|
||||
return f"""\
|
||||
otadata, data, ota, , 0x2000,
|
||||
phy_init, data, phy, , 0x1000,
|
||||
app0, app, ota_0, , 0x{app_partition_size:X},
|
||||
app1, app, ota_1, , 0x{app_partition_size:X},
|
||||
nvs, data, nvs, , 0x6D000,
|
||||
"""
|
||||
Called from component to_code() to request additional flash partitions.
|
||||
Size must be 4KB aligned. Integer types/subtypes are converted to hex strings.
|
||||
"""
|
||||
if name in CORE.data[KEY_ESP32].get(KEY_CUSTOM_PARTITIONS, {}):
|
||||
raise ValueError(f"Partition name '{name}' is already defined.")
|
||||
_validate_partition(name, p_type, subtype, size)
|
||||
p_type_str = f"0x{p_type:X}" if isinstance(p_type, int) else p_type
|
||||
subtype_str = f"0x{subtype:X}" if isinstance(subtype, int) else subtype
|
||||
custom_partitions = CORE.data[KEY_ESP32].setdefault(KEY_CUSTOM_PARTITIONS, {})
|
||||
custom_partitions[name] = PartitionEntry(
|
||||
name=name, type=p_type_str, subtype=subtype_str, size=size
|
||||
)
|
||||
|
||||
|
||||
def _flash_size_to_bytes(flash_size_mb: str) -> int:
|
||||
"""Convert flash size string (e.g. '4MB') to bytes."""
|
||||
return int(flash_size_mb.removesuffix("MB")) * 1024 * 1024
|
||||
|
||||
|
||||
def _get_custom_partitions_total_size() -> int:
|
||||
"""Total size of custom partitions including alignment padding."""
|
||||
size = 0
|
||||
for partition in CORE.data[KEY_ESP32].get(KEY_CUSTOM_PARTITIONS, {}).values():
|
||||
if partition.type == "app":
|
||||
size = (size + 0xFFFF) & ~0xFFFF # align to 64KB
|
||||
size += partition.size
|
||||
return size
|
||||
|
||||
|
||||
def _get_app_partition_size(flash_size_mb: str) -> int:
|
||||
flash_bytes = _flash_size_to_bytes(flash_size_mb)
|
||||
custom_total = _get_custom_partitions_total_size()
|
||||
# Align down to 64KB — app partitions require 64KB-aligned offsets,
|
||||
# so the size must also be aligned to avoid unbudgeted padding.
|
||||
raw_size = (flash_bytes - _get_partition_overhead() - custom_total) // 2
|
||||
app_size = raw_size & ~0xFFFF
|
||||
wasted = (raw_size - app_size) * 2
|
||||
if wasted:
|
||||
_LOGGER.info(
|
||||
"Custom partitions cause %dKB of wasted flash due to 64KB app partition alignment.",
|
||||
wasted // 1024,
|
||||
)
|
||||
if app_size <= 0x10000: # 64 KB
|
||||
raise ValueError(
|
||||
"Custom partitions are too large to fit in the available flash size. "
|
||||
"Reduce custom partition sizes."
|
||||
)
|
||||
if app_size <= 0x80000: # 512 KB
|
||||
_LOGGER.warning(
|
||||
"App partition size is only %dKB. This may be too small for firmware with "
|
||||
"many components. Consider reducing custom partition sizes or using a "
|
||||
"larger flash chip.",
|
||||
app_size // 1024,
|
||||
)
|
||||
return app_size
|
||||
|
||||
|
||||
def get_partition_csv(flash_size_mb: str) -> str:
|
||||
app_size = _get_app_partition_size(flash_size_mb)
|
||||
|
||||
partitions: list[PartitionEntry] = [
|
||||
PartitionEntry(name="otadata", type="data", subtype="ota", size=OTADATA_SIZE),
|
||||
PartitionEntry(name="phy_init", type="data", subtype="phy", size=PHY_INIT_SIZE),
|
||||
PartitionEntry(name="app0", type="app", subtype="ota_0", size=app_size),
|
||||
PartitionEntry(name="app1", type="app", subtype="ota_1", size=app_size),
|
||||
]
|
||||
if CORE.using_arduino:
|
||||
partitions.append(
|
||||
PartitionEntry(name="eeprom", type="data", subtype="0x99", size=EEPROM_SIZE)
|
||||
)
|
||||
partitions.append(
|
||||
PartitionEntry(
|
||||
name="spiffs", type="data", subtype="spiffs", size=SPIFFS_SIZE
|
||||
)
|
||||
)
|
||||
partitions.append(
|
||||
PartitionEntry(
|
||||
name="nvs", type="data", subtype="nvs", size=ARDUINO_NVS_SIZE
|
||||
)
|
||||
)
|
||||
else:
|
||||
partitions.append(
|
||||
PartitionEntry(name="nvs", type="data", subtype="nvs", size=IDF_NVS_SIZE)
|
||||
)
|
||||
partitions.extend(CORE.data[KEY_ESP32].get(KEY_CUSTOM_PARTITIONS, {}).values())
|
||||
|
||||
csv = "".join(
|
||||
f"{p.name}, {p.type}, {p.subtype}, , 0x{p.size:X},\n" for p in partitions
|
||||
)
|
||||
_LOGGER.debug("Partition table:\n%s", csv)
|
||||
return csv
|
||||
|
||||
|
||||
def _format_sdkconfig_val(value: SdkconfigValueType) -> str:
|
||||
@@ -2030,16 +2241,10 @@ def copy_files():
|
||||
|
||||
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
||||
flash_size = CORE.data[KEY_ESP32][KEY_FLASH_SIZE]
|
||||
if CORE.using_arduino:
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
get_arduino_partition_csv(flash_size),
|
||||
)
|
||||
else:
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
get_idf_partition_csv(flash_size),
|
||||
)
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
get_partition_csv(flash_size),
|
||||
)
|
||||
# IDF build scripts look for version string to put in the build.
|
||||
# However, if the build path does not have an initialized git repo,
|
||||
# and no version.txt file exists, the CMake script fails for some setups.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "crash_handler.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
@@ -15,7 +16,6 @@
|
||||
#include <freertos/task.h>
|
||||
|
||||
void setup(); // NOLINT(readability-redundant-declaration)
|
||||
void loop(); // NOLINT(readability-redundant-declaration)
|
||||
|
||||
// Weak stub for initArduino - overridden when the Arduino component is present
|
||||
extern "C" __attribute__((weak)) void initArduino() {}
|
||||
@@ -65,7 +65,7 @@ TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-
|
||||
void loop_task(void *pv_params) {
|
||||
setup();
|
||||
while (true) {
|
||||
loop();
|
||||
App.loop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,11 @@ def esp32_validate_gpio_pin(value: int) -> int:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
|
||||
if value in _ESP_SDIO_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
|
||||
f"This pin cannot be used on ESP32s and is already used by the flash interface"
|
||||
f" (function: {_ESP_SDIO_PINS[value]})."
|
||||
f" If you are using an ESP32 module that uses a different flash pin"
|
||||
f" configuration (e.g. ESP32-PICO-V3-02), you can set"
|
||||
f" 'ignore_pin_validation_error: true' to bypass this check."
|
||||
)
|
||||
if 9 <= value <= 10:
|
||||
_LOGGER.warning(
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/portmacro.h>
|
||||
#include "esphome/core/log.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
uint32_t random_uint32() { return esp_random(); }
|
||||
static const char *const TAG = "esp32";
|
||||
|
||||
bool random_bytes(uint8_t *data, size_t len) {
|
||||
esp_fill_random(data, len);
|
||||
return true;
|
||||
@@ -64,22 +66,43 @@ LwIPLock::~LwIPLock() {
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Read MAC and validate both the return code and content.
|
||||
static bool read_valid_mac(uint8_t *mac, esp_err_t err) { return err == ESP_OK && mac_address_is_valid(mac); }
|
||||
|
||||
static constexpr size_t MAC_ADDRESS_SIZE_BITS = MAC_ADDRESS_SIZE * 8; // 48 bits
|
||||
|
||||
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
|
||||
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
|
||||
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
|
||||
// returns the 802.15.4 EUI-64 address, so we read directly from eFuse instead.
|
||||
if (has_custom_mac_address()) {
|
||||
esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48);
|
||||
} else {
|
||||
esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48);
|
||||
// Both paths already read raw eFuse bytes, so there is no CRC-bypass fallback
|
||||
// (unlike the non-IEEE802154 path where esp_efuse_mac_get_default does CRC checks).
|
||||
if (has_custom_mac_address() &&
|
||||
read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS))) {
|
||||
return;
|
||||
}
|
||||
if (read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, MAC_ADDRESS_SIZE_BITS))) {
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (has_custom_mac_address()) {
|
||||
esp_efuse_mac_get_custom(mac);
|
||||
} else {
|
||||
esp_efuse_mac_get_default(mac);
|
||||
if (has_custom_mac_address() && read_valid_mac(mac, esp_efuse_mac_get_custom(mac))) {
|
||||
return;
|
||||
}
|
||||
if (read_valid_mac(mac, esp_efuse_mac_get_default(mac))) {
|
||||
return;
|
||||
}
|
||||
// Default MAC read failed (e.g., eFuse CRC error) - try reading raw eFuse bytes
|
||||
// directly, bypassing CRC validation. A MAC that passes mac_address_is_valid()
|
||||
// (non-zero, non-broadcast, unicast) is almost certainly the real factory MAC
|
||||
// with a corrupted CRC byte, which is far better than returning garbage or zeros.
|
||||
if (read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, MAC_ADDRESS_SIZE_BITS))) {
|
||||
ESP_LOGW(TAG, "eFuse MAC CRC failed but raw bytes appear valid - using raw eFuse MAC");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// All methods failed - zero the MAC rather than returning garbage
|
||||
ESP_LOGE(TAG, "Failed to read a valid MAC address from eFuse");
|
||||
memset(mac, 0, MAC_ADDRESS_SIZE);
|
||||
}
|
||||
|
||||
void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
|
||||
@@ -89,9 +112,11 @@ bool has_custom_mac_address() {
|
||||
uint8_t mac[6];
|
||||
// do not use 'esp_efuse_mac_get_custom(mac)' because it drops an error in the logs whenever it fails
|
||||
#ifndef USE_ESP32_VARIANT_ESP32
|
||||
return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
|
||||
return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS) == ESP_OK) &&
|
||||
mac_address_is_valid(mac);
|
||||
#else
|
||||
return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
|
||||
return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS) == ESP_OK) &&
|
||||
mac_address_is_valid(mac);
|
||||
#endif
|
||||
#else
|
||||
return false;
|
||||
|
||||
@@ -4,12 +4,40 @@ import re
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
# IRAM size for testing mode (2MB - large enough to accommodate grouped tests)
|
||||
TESTING_IRAM_SIZE = 0x200000
|
||||
# Memory sizes for testing mode (large enough to accommodate grouped tests)
|
||||
TESTING_IRAM_SIZE = 0x200000 # 2MB
|
||||
TESTING_DRAM_SIZE = 0x200000 # 2MB
|
||||
|
||||
|
||||
def patch_segment(content, segment_name, new_size):
|
||||
"""Patch a memory segment's length in linker script content.
|
||||
|
||||
Handles both single-line and multi-line segment definitions, e.g.:
|
||||
iram0_0_seg (RX) : org = 0x40080000, len = 0x20000 + 0x0
|
||||
or split across lines:
|
||||
dram0_0_seg (RW) : org = 0x3FFB0000 + 0xdb5c,
|
||||
len = 0x2c200 - 0xdb5c
|
||||
|
||||
Args:
|
||||
content: Full linker script content as string
|
||||
segment_name: Name of the segment (e.g., 'iram0_0_seg')
|
||||
new_size: New size as integer
|
||||
|
||||
Returns:
|
||||
Tuple of (new_content, was_patched)
|
||||
"""
|
||||
# Match segment name through to "len = <value>" allowing newlines between org and len
|
||||
pattern = rf'({re.escape(segment_name)}\s*\([^)]*\)\s*:\s*org\s*=\s*.+?,\s*len\s*=\s*)(\S+[^\n]*)'
|
||||
if match := re.search(pattern, content, re.DOTALL):
|
||||
replacement = f"{match.group(1)}{new_size:#x}"
|
||||
new_content = content[:match.start()] + replacement + content[match.end():]
|
||||
if new_content != content:
|
||||
return new_content, True
|
||||
return content, False
|
||||
|
||||
|
||||
def patch_idf_linker_script(source, target, env):
|
||||
"""Patch ESP-IDF linker script to increase IRAM size for testing mode."""
|
||||
"""Patch ESP-IDF linker script to increase IRAM and DRAM size for testing mode."""
|
||||
# Check if we're in testing mode by looking for the define
|
||||
build_flags = env.get("BUILD_FLAGS", [])
|
||||
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
|
||||
@@ -34,36 +62,22 @@ def patch_idf_linker_script(source, target, env):
|
||||
print(f"ESPHome: Error reading linker script: {e}")
|
||||
return
|
||||
|
||||
# Check if this file contains iram0_0_seg
|
||||
if 'iram0_0_seg' not in content:
|
||||
print(f"ESPHome: Warning - iram0_0_seg not found in {memory_ld}")
|
||||
return
|
||||
patches = []
|
||||
|
||||
# Look for iram0_0_seg definition and increase its length
|
||||
# ESP-IDF format can be:
|
||||
# iram0_0_seg (RX) : org = 0x40080000, len = 0x20000 + 0x0
|
||||
# or more complex with nested parentheses:
|
||||
# iram0_0_seg (RX) : org = (0x40370000 + 0x4000), len = (((0x403CB700 - (0x40378000 - 0x3FC88000)) - 0x3FC88000) + 0x8000 - 0x4000)
|
||||
# We want to change len to TESTING_IRAM_SIZE for testing
|
||||
content, patched = patch_segment(content, 'iram0_0_seg', TESTING_IRAM_SIZE)
|
||||
if patched:
|
||||
patches.append(f"IRAM={TESTING_IRAM_SIZE:#x}")
|
||||
|
||||
# Use a more robust approach: find the line and manually parse it
|
||||
lines = content.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if 'iram0_0_seg' in line and 'len' in line:
|
||||
# Find the position of "len = " and replace everything after it until the end of the statement
|
||||
match = re.search(r'(iram0_0_seg\s*\([^)]*\)\s*:\s*org\s*=\s*(?:\([^)]+\)|0x[0-9a-fA-F]+)\s*,\s*len\s*=\s*)(.+?)(\s*)$', line)
|
||||
if match:
|
||||
lines[i] = f"{match.group(1)}{TESTING_IRAM_SIZE:#x}{match.group(3)}"
|
||||
break
|
||||
content, patched = patch_segment(content, 'dram0_0_seg', TESTING_DRAM_SIZE)
|
||||
if patched:
|
||||
patches.append(f"DRAM={TESTING_DRAM_SIZE:#x}")
|
||||
|
||||
updated = '\n'.join(lines)
|
||||
|
||||
if updated != content:
|
||||
if patches:
|
||||
with open(memory_ld, "w") as f:
|
||||
f.write(updated)
|
||||
print(f"ESPHome: Patched IRAM size to {TESTING_IRAM_SIZE:#x} in {memory_ld} for testing mode")
|
||||
f.write(content)
|
||||
print(f"ESPHome: Patched {', '.join(patches)} in {memory_ld} for testing mode")
|
||||
else:
|
||||
print(f"ESPHome: Warning - could not patch iram0_0_seg in {memory_ld}")
|
||||
print(f"ESPHome: Warning - could not patch memory segments in {memory_ld}")
|
||||
|
||||
|
||||
# Hook into the build process before linking
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace esphome::esp32 {
|
||||
|
||||
static const char *const TAG = "esp32.preferences";
|
||||
static const char *const TAG = "preferences";
|
||||
|
||||
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
|
||||
static constexpr size_t KEY_BUFFER_SIZE = 12;
|
||||
|
||||
@@ -12,58 +12,73 @@ namespace esp32_improv {
|
||||
|
||||
class ESP32ImprovProvisionedTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ESP32ImprovProvisionedTrigger(ESP32ImprovComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||
if (state == improv::STATE_PROVISIONED && !parent->is_failed()) {
|
||||
trigger();
|
||||
explicit ESP32ImprovProvisionedTrigger(ESP32ImprovComponent *parent) : parent_(parent) {
|
||||
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
|
||||
if (state == improv::STATE_PROVISIONED && !this->parent_->is_failed()) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
ESP32ImprovComponent *parent_;
|
||||
};
|
||||
|
||||
class ESP32ImprovProvisioningTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ESP32ImprovProvisioningTrigger(ESP32ImprovComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||
if (state == improv::STATE_PROVISIONING && !parent->is_failed()) {
|
||||
trigger();
|
||||
explicit ESP32ImprovProvisioningTrigger(ESP32ImprovComponent *parent) : parent_(parent) {
|
||||
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
|
||||
if (state == improv::STATE_PROVISIONING && !this->parent_->is_failed()) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
ESP32ImprovComponent *parent_;
|
||||
};
|
||||
|
||||
class ESP32ImprovStartTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ESP32ImprovStartTrigger(ESP32ImprovComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||
explicit ESP32ImprovStartTrigger(ESP32ImprovComponent *parent) : parent_(parent) {
|
||||
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
|
||||
if ((state == improv::STATE_AUTHORIZED || state == improv::STATE_AWAITING_AUTHORIZATION) &&
|
||||
!parent->is_failed()) {
|
||||
trigger();
|
||||
!this->parent_->is_failed()) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
ESP32ImprovComponent *parent_;
|
||||
};
|
||||
|
||||
class ESP32ImprovStateTrigger : public Trigger<improv::State, improv::Error> {
|
||||
public:
|
||||
explicit ESP32ImprovStateTrigger(ESP32ImprovComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||
if (!parent->is_failed()) {
|
||||
trigger(state, error);
|
||||
explicit ESP32ImprovStateTrigger(ESP32ImprovComponent *parent) : parent_(parent) {
|
||||
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
|
||||
if (!this->parent_->is_failed()) {
|
||||
this->trigger(state, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
ESP32ImprovComponent *parent_;
|
||||
};
|
||||
|
||||
class ESP32ImprovStoppedTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ESP32ImprovStoppedTrigger(ESP32ImprovComponent *parent) {
|
||||
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||
if (state == improv::STATE_STOPPED && !parent->is_failed()) {
|
||||
trigger();
|
||||
explicit ESP32ImprovStoppedTrigger(ESP32ImprovComponent *parent) : parent_(parent) {
|
||||
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
|
||||
if (state == improv::STATE_STOPPED && !this->parent_->is_failed()) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
ESP32ImprovComponent *parent_;
|
||||
};
|
||||
|
||||
} // namespace esp32_improv
|
||||
|
||||
@@ -350,8 +350,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
|
||||
ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
|
||||
this->set_timeout("wifi-connect-timeout", 30000, f);
|
||||
this->set_timeout("wifi-connect-timeout", 30000, [this]() { this->on_wifi_connect_timeout_(); });
|
||||
this->incoming_data_.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -360,11 +360,16 @@ void ESP32TouchComponent::loop() {
|
||||
}
|
||||
|
||||
// Publish initial OFF state for sensors that haven't received events yet
|
||||
bool all_initial_published = true;
|
||||
for (auto *child : this->children_) {
|
||||
this->publish_initial_state_if_needed_(child, now);
|
||||
if (!child->initial_state_published_) {
|
||||
all_initial_published = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->setup_mode_) {
|
||||
// Only disable loop once all initial states are published
|
||||
if (!this->setup_mode_ && all_initial_published) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ extern "C" {
|
||||
|
||||
namespace esphome::esp8266 {
|
||||
|
||||
static const char *const TAG = "esp8266.preferences";
|
||||
static const char *const TAG = "preferences";
|
||||
|
||||
static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
|
||||
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
|
||||
|
||||
@@ -18,12 +18,6 @@ void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
|
||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||
const char *EthernetComponent::get_use_address() const { return this->use_address_; }
|
||||
|
||||
void EthernetComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
void EthernetComponent::notify_ip_state_listeners_() {
|
||||
auto ips = this->get_ip_addresses();
|
||||
|
||||
@@ -103,8 +103,8 @@ class EthernetComponent final : public Component {
|
||||
|
||||
network::IPAddresses get_ip_addresses();
|
||||
network::IPAddress get_dns_address(uint8_t num);
|
||||
const char *get_use_address() const;
|
||||
void set_use_address(const char *use_address);
|
||||
const char *get_use_address() const { return this->use_address_; }
|
||||
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||
void get_eth_mac_address_raw(uint8_t *mac);
|
||||
// Remove before 2026.9.0
|
||||
ESPDEPRECATED("Use get_eth_mac_address_pretty_into_buffer() instead. Removed in 2026.9.0", "2026.3.0")
|
||||
|
||||
@@ -113,16 +113,19 @@ template<typename... Ts> class FanIsOffCondition : public Condition<Ts...> {
|
||||
|
||||
class FanStateTrigger : public Trigger<Fan *> {
|
||||
public:
|
||||
FanStateTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() { this->trigger(state); });
|
||||
FanStateTrigger(Fan *state) : fan_(state) {
|
||||
state->add_on_state_callback([this]() { this->trigger(this->fan_); });
|
||||
}
|
||||
|
||||
protected:
|
||||
Fan *fan_;
|
||||
};
|
||||
|
||||
class FanTurnOnTrigger : public Trigger<> {
|
||||
public:
|
||||
FanTurnOnTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto is_on = state->state;
|
||||
FanTurnOnTrigger(Fan *state) : fan_(state) {
|
||||
state->add_on_state_callback([this]() {
|
||||
auto is_on = this->fan_->state;
|
||||
auto should_trigger = is_on && !this->last_on_;
|
||||
this->last_on_ = is_on;
|
||||
if (should_trigger) {
|
||||
@@ -133,14 +136,15 @@ class FanTurnOnTrigger : public Trigger<> {
|
||||
}
|
||||
|
||||
protected:
|
||||
Fan *fan_;
|
||||
bool last_on_;
|
||||
};
|
||||
|
||||
class FanTurnOffTrigger : public Trigger<> {
|
||||
public:
|
||||
FanTurnOffTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto is_on = state->state;
|
||||
FanTurnOffTrigger(Fan *state) : fan_(state) {
|
||||
state->add_on_state_callback([this]() {
|
||||
auto is_on = this->fan_->state;
|
||||
auto should_trigger = !is_on && this->last_on_;
|
||||
this->last_on_ = is_on;
|
||||
if (should_trigger) {
|
||||
@@ -151,14 +155,15 @@ class FanTurnOffTrigger : public Trigger<> {
|
||||
}
|
||||
|
||||
protected:
|
||||
Fan *fan_;
|
||||
bool last_on_;
|
||||
};
|
||||
|
||||
class FanDirectionSetTrigger : public Trigger<FanDirection> {
|
||||
public:
|
||||
FanDirectionSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto direction = state->direction;
|
||||
FanDirectionSetTrigger(Fan *state) : fan_(state) {
|
||||
state->add_on_state_callback([this]() {
|
||||
auto direction = this->fan_->direction;
|
||||
auto should_trigger = direction != this->last_direction_;
|
||||
this->last_direction_ = direction;
|
||||
if (should_trigger) {
|
||||
@@ -169,14 +174,15 @@ class FanDirectionSetTrigger : public Trigger<FanDirection> {
|
||||
}
|
||||
|
||||
protected:
|
||||
Fan *fan_;
|
||||
FanDirection last_direction_;
|
||||
};
|
||||
|
||||
class FanOscillatingSetTrigger : public Trigger<bool> {
|
||||
public:
|
||||
FanOscillatingSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto oscillating = state->oscillating;
|
||||
FanOscillatingSetTrigger(Fan *state) : fan_(state) {
|
||||
state->add_on_state_callback([this]() {
|
||||
auto oscillating = this->fan_->oscillating;
|
||||
auto should_trigger = oscillating != this->last_oscillating_;
|
||||
this->last_oscillating_ = oscillating;
|
||||
if (should_trigger) {
|
||||
@@ -187,14 +193,15 @@ class FanOscillatingSetTrigger : public Trigger<bool> {
|
||||
}
|
||||
|
||||
protected:
|
||||
Fan *fan_;
|
||||
bool last_oscillating_;
|
||||
};
|
||||
|
||||
class FanSpeedSetTrigger : public Trigger<int> {
|
||||
public:
|
||||
FanSpeedSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto speed = state->speed;
|
||||
FanSpeedSetTrigger(Fan *state) : fan_(state) {
|
||||
state->add_on_state_callback([this]() {
|
||||
auto speed = this->fan_->speed;
|
||||
auto should_trigger = speed != this->last_speed_;
|
||||
this->last_speed_ = speed;
|
||||
if (should_trigger) {
|
||||
@@ -205,14 +212,15 @@ class FanSpeedSetTrigger : public Trigger<int> {
|
||||
}
|
||||
|
||||
protected:
|
||||
Fan *fan_;
|
||||
int last_speed_;
|
||||
};
|
||||
|
||||
class FanPresetSetTrigger : public Trigger<StringRef> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto preset_mode = state->get_preset_mode();
|
||||
FanPresetSetTrigger(Fan *state) : fan_(state) {
|
||||
state->add_on_state_callback([this]() {
|
||||
auto preset_mode = this->fan_->get_preset_mode();
|
||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
if (should_trigger) {
|
||||
@@ -223,6 +231,7 @@ class FanPresetSetTrigger : public Trigger<StringRef> {
|
||||
}
|
||||
|
||||
protected:
|
||||
Fan *fan_;
|
||||
StringRef last_preset_mode_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -552,6 +552,7 @@ async def to_code(config):
|
||||
"""
|
||||
|
||||
# get the codepoints from glyphsets and flatten to a set of chrs.
|
||||
cg.add_define("USE_FONT")
|
||||
point_set: set[str] = {
|
||||
chr(x)
|
||||
for x in flatten(
|
||||
|
||||
@@ -9,13 +9,87 @@ namespace font {
|
||||
static const char *const TAG = "font";
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
const uint8_t *Font::get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
|
||||
auto *fe = (Font *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data_(unicode_letter);
|
||||
static const uint8_t OPA4_TABLE[16] = {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255};
|
||||
|
||||
static const uint8_t OPA2_TABLE[4] = {0, 85, 170, 255};
|
||||
|
||||
const void *Font::get_glyph_bitmap(lv_font_glyph_dsc_t *dsc, lv_draw_buf_t *draw_buf) {
|
||||
const auto *font = dsc->resolved_font;
|
||||
auto *const fe = (Font *) font->dsc;
|
||||
|
||||
const auto *gd = fe->get_glyph_data_(dsc->gid.index);
|
||||
if (gd == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return gd->data;
|
||||
|
||||
const uint8_t *bitmap_in = gd->data;
|
||||
uint8_t *bitmap_out_tmp = draw_buf->data;
|
||||
int32_t i = 0;
|
||||
int32_t x, y;
|
||||
uint32_t stride = lv_draw_buf_width_to_stride(gd->width, LV_COLOR_FORMAT_A8);
|
||||
|
||||
switch (fe->get_bpp()) {
|
||||
case 1: {
|
||||
uint8_t mask = 0;
|
||||
uint8_t byte = 0;
|
||||
for (y = 0; y != gd->height; y++) {
|
||||
for (x = 0; x != gd->width; x++) {
|
||||
if (mask == 0) {
|
||||
mask = 0x80;
|
||||
byte = *bitmap_in++;
|
||||
}
|
||||
bitmap_out_tmp[x] = byte & mask ? 255 : 0;
|
||||
mask >>= 1;
|
||||
}
|
||||
bitmap_out_tmp += stride;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 2:
|
||||
for (y = 0; y != gd->height; y++) {
|
||||
for (x = 0; x != gd->width; x++, i++) {
|
||||
switch (i & 0x3) {
|
||||
default:
|
||||
bitmap_out_tmp[x] = OPA2_TABLE[(*bitmap_in) >> 6];
|
||||
break;
|
||||
case 1:
|
||||
bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 4) & 0x3];
|
||||
break;
|
||||
case 2:
|
||||
bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 2) & 0x3];
|
||||
break;
|
||||
case 3:
|
||||
bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 0) & 0x3];
|
||||
bitmap_in++;
|
||||
}
|
||||
}
|
||||
bitmap_out_tmp += stride;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
for (y = 0; y != gd->height; y++) {
|
||||
for (x = 0; x != gd->width; x++, i++) {
|
||||
i = i & 0x1;
|
||||
if (i == 0) {
|
||||
bitmap_out_tmp[x] = OPA4_TABLE[(*bitmap_in) >> 4];
|
||||
} else if (i == 1) {
|
||||
bitmap_out_tmp[x] = OPA4_TABLE[(*bitmap_in) & 0xF];
|
||||
bitmap_in++;
|
||||
}
|
||||
}
|
||||
bitmap_out_tmp += stride;
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
memcpy(bitmap_out_tmp, bitmap_in, gd->width * gd->height);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Unknown bpp: %d", fe->get_bpp());
|
||||
break;
|
||||
}
|
||||
return draw_buf;
|
||||
}
|
||||
|
||||
bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
|
||||
@@ -30,7 +104,8 @@ bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uin
|
||||
dsc->box_w = gd->width;
|
||||
dsc->box_h = gd->height;
|
||||
dsc->is_placeholder = 0;
|
||||
dsc->bpp = fe->get_bpp();
|
||||
dsc->format = (lv_font_glyph_format_t) fe->get_bpp();
|
||||
dsc->gid.index = unicode_letter;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ class Font
|
||||
uint8_t bpp_; // bits per pixel
|
||||
#ifdef USE_LVGL_FONT
|
||||
lv_font_t lv_font_{};
|
||||
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter);
|
||||
static const void *get_glyph_bitmap(lv_font_glyph_dsc_t *dsc, lv_draw_buf_t *draw_buf);
|
||||
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next);
|
||||
const Glyph *get_glyph_data_(uint32_t unicode_letter);
|
||||
uint32_t last_letter_{};
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace esphome {
|
||||
namespace gpio {
|
||||
|
||||
static const char *const TAG = "switch.gpio";
|
||||
static constexpr uint32_t INTERLOCK_TIMEOUT_ID = 0;
|
||||
|
||||
float GPIOSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
void GPIOSwitch::setup() {
|
||||
@@ -51,7 +52,7 @@ void GPIOSwitch::write_state(bool state) {
|
||||
}
|
||||
}
|
||||
if (found && this->interlock_wait_time_ != 0) {
|
||||
this->set_timeout("interlock", this->interlock_wait_time_, [this, state] {
|
||||
this->set_timeout(INTERLOCK_TIMEOUT_ID, this->interlock_wait_time_, [this, state] {
|
||||
// Don't write directly, call the function again
|
||||
// (some other switch may have changed state while we were waiting)
|
||||
this->write_state(state);
|
||||
@@ -61,7 +62,7 @@ void GPIOSwitch::write_state(bool state) {
|
||||
} else if (this->interlock_wait_time_ != 0) {
|
||||
// If we are switched off during the interlock wait time, cancel any pending
|
||||
// re-activations
|
||||
this->cancel_timeout("interlock");
|
||||
this->cancel_timeout(INTERLOCK_TIMEOUT_ID);
|
||||
}
|
||||
|
||||
this->pin_->digital_write(state);
|
||||
|
||||
@@ -75,9 +75,12 @@ class GraphicalDisplayMenu : public display_menu_base::DisplayMenuComponent {
|
||||
|
||||
class GraphicalDisplayMenuOnRedrawTrigger : public Trigger<const GraphicalDisplayMenu *> {
|
||||
public:
|
||||
explicit GraphicalDisplayMenuOnRedrawTrigger(GraphicalDisplayMenu *parent) {
|
||||
parent->add_on_redraw_callback([this, parent]() { this->trigger(parent); });
|
||||
explicit GraphicalDisplayMenuOnRedrawTrigger(GraphicalDisplayMenu *parent) : parent_(parent) {
|
||||
parent->add_on_redraw_callback([this]() { this->trigger(this->parent_); });
|
||||
}
|
||||
|
||||
protected:
|
||||
GraphicalDisplayMenu *parent_;
|
||||
};
|
||||
|
||||
} // namespace graphical_display_menu
|
||||
|
||||
@@ -242,7 +242,7 @@ void HaierClimateBase::setup() {
|
||||
this->last_request_timestamp_ = std::chrono::steady_clock::now();
|
||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||
this->haier_protocol_.set_default_timeout_handler(
|
||||
std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1));
|
||||
[this](haier_protocol::FrameType type) { return this->timeout_default_handler_(type); });
|
||||
this->set_handlers();
|
||||
this->initialization();
|
||||
}
|
||||
|
||||
@@ -301,32 +301,38 @@ void HonClimate::set_handlers() {
|
||||
// Set handlers
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::GET_DEVICE_VERSION,
|
||||
std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->get_device_version_answer_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::GET_DEVICE_ID,
|
||||
std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->get_device_id_answer_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::CONTROL,
|
||||
std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
|
||||
std::placeholders::_4));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->status_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION,
|
||||
std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->get_management_information_answer_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::GET_ALARM_STATUS,
|
||||
std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->get_alarm_status_answer_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::REPORT_NETWORK_STATUS,
|
||||
std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
this->haier_protocol_.set_message_handler(
|
||||
haier_protocol::FrameType::ALARM_STATUS,
|
||||
std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->report_network_status_answer_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_message_handler(haier_protocol::FrameType::ALARM_STATUS,
|
||||
[this](haier_protocol::FrameType type, const uint8_t *data, size_t size) {
|
||||
return this->alarm_status_message_handler_(type, data, size);
|
||||
});
|
||||
}
|
||||
|
||||
void HonClimate::dump_config() {
|
||||
|
||||
@@ -106,18 +106,21 @@ void Smartair2Climate::set_handlers() {
|
||||
// Set handlers
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::GET_DEVICE_VERSION,
|
||||
std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->get_device_version_answer_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::CONTROL,
|
||||
std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->status_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_answer_handler(
|
||||
haier_protocol::FrameType::REPORT_NETWORK_STATUS,
|
||||
std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
[this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
|
||||
return this->report_network_status_answer_handler_(req, msg, data, size);
|
||||
});
|
||||
this->haier_protocol_.set_default_timeout_handler(
|
||||
std::bind(&Smartair2Climate::messages_timeout_handler_with_cycle_for_init_, this, std::placeholders::_1));
|
||||
[this](haier_protocol::FrameType type) { return this->messages_timeout_handler_with_cycle_for_init_(type); });
|
||||
}
|
||||
|
||||
void Smartair2Climate::dump_config() {
|
||||
|
||||
@@ -55,15 +55,15 @@ void HomeassistantNumber::step_retrieved_(StringRef step) {
|
||||
}
|
||||
|
||||
void HomeassistantNumber::setup() {
|
||||
api::global_api_server->subscribe_home_assistant_state(
|
||||
this->entity_id_, nullptr, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1));
|
||||
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr,
|
||||
[this](StringRef state) { this->state_changed_(state); });
|
||||
|
||||
api::global_api_server->get_home_assistant_state(
|
||||
this->entity_id_, "min", std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1));
|
||||
api::global_api_server->get_home_assistant_state(
|
||||
this->entity_id_, "max", std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1));
|
||||
api::global_api_server->get_home_assistant_state(
|
||||
this->entity_id_, "step", std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1));
|
||||
api::global_api_server->get_home_assistant_state(this->entity_id_, "min",
|
||||
[this](StringRef min) { this->min_retrieved_(min); });
|
||||
api::global_api_server->get_home_assistant_state(this->entity_id_, "max",
|
||||
[this](StringRef max) { this->max_retrieved_(max); });
|
||||
api::global_api_server->get_home_assistant_state(this->entity_id_, "step",
|
||||
[this](StringRef step) { this->step_retrieved_(step); });
|
||||
}
|
||||
|
||||
void HomeassistantNumber::dump_config() {
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -18,13 +16,6 @@ namespace esphome {
|
||||
|
||||
static const char *const TAG = "helpers.host";
|
||||
|
||||
uint32_t random_uint32() {
|
||||
std::random_device dev;
|
||||
std::mt19937 rng(dev());
|
||||
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
|
||||
return dist(rng);
|
||||
}
|
||||
|
||||
bool random_bytes(uint8_t *data, size_t len) {
|
||||
FILE *fp = fopen("/dev/urandom", "r");
|
||||
if (fp == nullptr) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
namespace esphome::host {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static const char *const TAG = "host.preferences";
|
||||
static const char *const TAG = "preferences";
|
||||
|
||||
void HostPreferences::setup_() {
|
||||
if (this->setup_complete_)
|
||||
|
||||
@@ -487,12 +487,10 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
body = this->body_.value(x...);
|
||||
}
|
||||
if (!this->json_.empty()) {
|
||||
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_, this, x..., std::placeholders::_1);
|
||||
body = json::build_json(f);
|
||||
body = json::build_json([this, x...](JsonObject root) { this->encode_json_(x..., root); });
|
||||
}
|
||||
if (this->json_func_ != nullptr) {
|
||||
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
|
||||
body = json::build_json(f);
|
||||
body = json::build_json([this, x...](JsonObject root) { this->json_func_(x..., root); });
|
||||
}
|
||||
std::vector<Header> request_headers;
|
||||
request_headers.reserve(this->request_headers_.size());
|
||||
@@ -561,7 +559,6 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
root[item.first] = val.value(x...);
|
||||
}
|
||||
}
|
||||
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
|
||||
HttpRequestComponent *parent_;
|
||||
FixedVector<std::pair<const char *, TemplatableValue<const char *, Ts...>>> request_headers_{};
|
||||
std::vector<std::string> lower_case_collect_headers_{"content-type", "content-length"};
|
||||
|
||||
@@ -28,6 +28,7 @@ from esphome.const import (
|
||||
CONF_URL,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.final_validate import full_config
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -84,7 +85,7 @@ class ImageEncoder:
|
||||
|
||||
def __init__(self, width, height, transparency, dither, invert_alpha):
|
||||
"""
|
||||
:param width: The image width in pixels
|
||||
:param width: The image width in pixels (or bytes)
|
||||
:param height: The image height in pixels
|
||||
:param transparency: Transparency type
|
||||
:param dither: Dither method
|
||||
@@ -93,11 +94,12 @@ class ImageEncoder:
|
||||
self.transparency = transparency
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.data = [0 for _ in range(width * height)]
|
||||
self.data = [0] * width * height
|
||||
self.dither = dither
|
||||
self.index = 0
|
||||
self.invert_alpha = invert_alpha
|
||||
self.path = ""
|
||||
self.big_endian = False
|
||||
|
||||
def convert(self, image, path):
|
||||
"""
|
||||
@@ -119,12 +121,21 @@ class ImageEncoder:
|
||||
:return:
|
||||
"""
|
||||
|
||||
def end_image(self):
|
||||
"""
|
||||
Called at the end of the image.
|
||||
:return:
|
||||
"""
|
||||
|
||||
def set_big_endian(self, big_endian: bool) -> None:
|
||||
self.big_endian = big_endian
|
||||
|
||||
@classmethod
|
||||
def is_endian(cls) -> bool:
|
||||
"""
|
||||
Check if the image encoder supports endianness configuration
|
||||
"""
|
||||
return getattr(cls, "set_big_endian", None) is not None
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_options(cls) -> list[str]:
|
||||
@@ -212,18 +223,21 @@ class ImageGrayscale(ImageEncoder):
|
||||
|
||||
class ImageRGB565(ImageEncoder):
|
||||
def __init__(self, width, height, transparency, dither, invert_alpha):
|
||||
stride = 3 if transparency == CONF_ALPHA_CHANNEL else 2
|
||||
super().__init__(
|
||||
width * stride,
|
||||
width * 2,
|
||||
height,
|
||||
transparency,
|
||||
dither,
|
||||
invert_alpha,
|
||||
)
|
||||
self.big_endian = True
|
||||
self.alpha = [0] * width * height
|
||||
|
||||
def set_big_endian(self, big_endian: bool) -> None:
|
||||
self.big_endian = big_endian
|
||||
@classmethod
|
||||
def is_endian(cls) -> bool:
|
||||
"""
|
||||
Check if the image encoder supports endianness configuration
|
||||
"""
|
||||
return True
|
||||
|
||||
def convert(self, image, path):
|
||||
return image.convert("RGBA")
|
||||
@@ -233,6 +247,9 @@ class ImageRGB565(ImageEncoder):
|
||||
r = r >> 3
|
||||
g = g >> 2
|
||||
b = b >> 3
|
||||
if self.invert_alpha:
|
||||
a ^= 0xFF
|
||||
self.alpha[self.index // 2] = a
|
||||
if self.transparency == CONF_CHROMA_KEY:
|
||||
if r == 0 and g == 1 and b == 0:
|
||||
g = 0
|
||||
@@ -251,11 +268,10 @@ class ImageRGB565(ImageEncoder):
|
||||
self.index += 1
|
||||
self.data[self.index] = rgb >> 8
|
||||
self.index += 1
|
||||
|
||||
def end_image(self):
|
||||
if self.transparency == CONF_ALPHA_CHANNEL:
|
||||
if self.invert_alpha:
|
||||
a ^= 0xFF
|
||||
self.data[self.index] = a
|
||||
self.index += 1
|
||||
self.data.extend(self.alpha)
|
||||
|
||||
|
||||
class ImageRGB(ImageEncoder):
|
||||
@@ -281,11 +297,11 @@ class ImageRGB(ImageEncoder):
|
||||
r = 0
|
||||
g = 1
|
||||
b = 0
|
||||
self.data[self.index] = r
|
||||
self.data[self.index] = b
|
||||
self.index += 1
|
||||
self.data[self.index] = g
|
||||
self.index += 1
|
||||
self.data[self.index] = b
|
||||
self.data[self.index] = r
|
||||
self.index += 1
|
||||
if self.transparency == CONF_ALPHA_CHANNEL:
|
||||
if self.invert_alpha:
|
||||
@@ -655,6 +671,24 @@ def _config_schema(value):
|
||||
CONFIG_SCHEMA = _config_schema
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
"""
|
||||
For LVGL 9 the default byte order for RGB565 images is little-endian
|
||||
:param config:
|
||||
:return:
|
||||
"""
|
||||
fv = full_config.get()
|
||||
if "lvgl" in fv and not all(CONF_BYTE_ORDER in x for x in config):
|
||||
config = config.copy()
|
||||
for c in config:
|
||||
if not c.get(CONF_BYTE_ORDER):
|
||||
c[CONF_BYTE_ORDER] = "LITTLE_ENDIAN"
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def write_image(config, all_frames=False):
|
||||
path = Path(config[CONF_FILE])
|
||||
if not path.is_file():
|
||||
@@ -720,6 +754,7 @@ async def write_image(config, all_frames=False):
|
||||
for col in range(width):
|
||||
encoder.encode(pixels[row * width + col])
|
||||
encoder.end_row()
|
||||
encoder.end_image()
|
||||
|
||||
rhs = [HexInt(x) for x in encoder.data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
@@ -729,31 +764,24 @@ async def write_image(config, all_frames=False):
|
||||
return prog_arr, width, height, image_type, trans_value, frame_count
|
||||
|
||||
|
||||
async def _image_to_code(entry):
|
||||
"""
|
||||
Convert a single image entry to code and return its metadata.
|
||||
:param entry: The config entry for the image.
|
||||
:return: An ImageMetaData object
|
||||
"""
|
||||
prog_arr, width, height, image_type, trans_value, _ = await write_image(entry)
|
||||
cg.new_Pvariable(entry[CONF_ID], prog_arr, width, height, image_type, trans_value)
|
||||
return ImageMetaData(
|
||||
width,
|
||||
height,
|
||||
entry[CONF_TYPE],
|
||||
entry[CONF_TRANSPARENCY],
|
||||
def add_metadata(id: str, width: int, height: int, image_type: str, transparency):
|
||||
all_metadata = CORE.data.setdefault(DOMAIN, {}).setdefault(KEY_METADATA, {})
|
||||
all_metadata[str(id)] = ImageMetaData(
|
||||
width=width, height=height, image_type=image_type, transparency=transparency
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_IMAGE")
|
||||
# By now the config will be a simple list.
|
||||
# Use a subkey to allow for other data in the future
|
||||
CORE.data[DOMAIN] = {
|
||||
KEY_METADATA: {
|
||||
entry[CONF_ID].id: await _image_to_code(entry) for entry in config
|
||||
}
|
||||
}
|
||||
for entry in config:
|
||||
prog_arr, width, height, image_type, trans_value, _ = await write_image(entry)
|
||||
cg.new_Pvariable(
|
||||
entry[CONF_ID], prog_arr, width, height, image_type, trans_value
|
||||
)
|
||||
add_metadata(
|
||||
entry[CONF_ID], width, height, entry[CONF_TYPE], entry[CONF_TRANSPARENCY]
|
||||
)
|
||||
|
||||
|
||||
def get_all_image_metadata() -> dict[str, ImageMetaData]:
|
||||
|
||||
@@ -105,22 +105,22 @@ Color Image::get_pixel(int x, int y, const Color color_on, const Color color_off
|
||||
}
|
||||
}
|
||||
#ifdef USE_LVGL
|
||||
lv_img_dsc_t *Image::get_lv_img_dsc() {
|
||||
lv_image_dsc_t *Image::get_lv_image_dsc() {
|
||||
// lazily construct lvgl image_dsc.
|
||||
if (this->dsc_.data != this->data_start_) {
|
||||
this->dsc_.data = this->data_start_;
|
||||
this->dsc_.header.always_zero = 0;
|
||||
this->dsc_.header.reserved = 0;
|
||||
this->dsc_.header.reserved_2 = 0;
|
||||
this->dsc_.header.stride = this->get_width_stride();
|
||||
this->dsc_.header.w = this->width_;
|
||||
this->dsc_.header.h = this->height_;
|
||||
this->dsc_.data_size = this->get_width_stride() * this->get_height();
|
||||
switch (this->get_type()) {
|
||||
case IMAGE_TYPE_BINARY:
|
||||
this->dsc_.header.cf = LV_IMG_CF_ALPHA_1BIT;
|
||||
this->dsc_.header.cf = LV_COLOR_FORMAT_A1;
|
||||
break;
|
||||
|
||||
case IMAGE_TYPE_GRAYSCALE:
|
||||
this->dsc_.header.cf = LV_IMG_CF_ALPHA_8BIT;
|
||||
this->dsc_.header.cf = LV_COLOR_FORMAT_A8;
|
||||
break;
|
||||
|
||||
case IMAGE_TYPE_RGB:
|
||||
@@ -138,7 +138,7 @@ lv_img_dsc_t *Image::get_lv_img_dsc() {
|
||||
}
|
||||
#else
|
||||
this->dsc_.header.cf =
|
||||
this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_IMG_CF_RGBA8888 : LV_IMG_CF_RGB888;
|
||||
this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? LV_COLOR_FORMAT_ARGB8888 : LV_COLOR_FORMAT_RGB888;
|
||||
#endif
|
||||
break;
|
||||
|
||||
@@ -146,14 +146,10 @@ lv_img_dsc_t *Image::get_lv_img_dsc() {
|
||||
#if LV_COLOR_DEPTH == 16
|
||||
switch (this->transparency_) {
|
||||
case TRANSPARENCY_ALPHA_CHANNEL:
|
||||
this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA;
|
||||
break;
|
||||
case TRANSPARENCY_CHROMA_KEY:
|
||||
this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED;
|
||||
this->dsc_.header.cf = LV_COLOR_FORMAT_RGB565A8;
|
||||
break;
|
||||
default:
|
||||
this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||||
break;
|
||||
this->dsc_.header.cf = LV_COLOR_FORMAT_RGB565;
|
||||
}
|
||||
#else
|
||||
this->dsc_.header.cf =
|
||||
@@ -173,8 +169,8 @@ bool Image::get_binary_pixel_(int x, int y) const {
|
||||
}
|
||||
Color Image::get_rgb_pixel_(int x, int y) const {
|
||||
const uint32_t pos = (x + y * this->width_) * this->bpp_ / 8;
|
||||
Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
|
||||
progmem_read_byte(this->data_start_ + pos + 2), 0xFF);
|
||||
Color color = Color(progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 1),
|
||||
progmem_read_byte(this->data_start_ + pos + 0), 0xFF);
|
||||
|
||||
switch (this->transparency_) {
|
||||
case TRANSPARENCY_CHROMA_KEY:
|
||||
@@ -200,7 +196,7 @@ Color Image::get_rgb565_pixel_(int x, int y) const {
|
||||
auto a = 0xFF;
|
||||
switch (this->transparency_) {
|
||||
case TRANSPARENCY_ALPHA_CHANNEL:
|
||||
a = progmem_read_byte(pos + 2);
|
||||
a = progmem_read_byte(this->data_start_ + this->width_ * this->height_ * 2 + (x + y * this->width_));
|
||||
break;
|
||||
case TRANSPARENCY_CHROMA_KEY:
|
||||
if (rgb565 == 0x0020)
|
||||
@@ -239,7 +235,7 @@ Image::Image(const uint8_t *data_start, int width, int height, ImageType type, T
|
||||
this->bpp_ = 8;
|
||||
break;
|
||||
case IMAGE_TYPE_RGB565:
|
||||
this->bpp_ = transparency == TRANSPARENCY_ALPHA_CHANNEL ? 24 : 16;
|
||||
this->bpp_ = 16;
|
||||
break;
|
||||
case IMAGE_TYPE_RGB:
|
||||
this->bpp_ = this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? 32 : 24;
|
||||
|
||||
@@ -41,7 +41,7 @@ class Image : public display::BaseImage {
|
||||
bool has_transparency() const { return this->transparency_ != TRANSPARENCY_OPAQUE; }
|
||||
|
||||
#ifdef USE_LVGL
|
||||
lv_img_dsc_t *get_lv_img_dsc();
|
||||
lv_image_dsc_t *get_lv_image_dsc();
|
||||
#endif
|
||||
protected:
|
||||
bool get_binary_pixel_(int x, int y) const;
|
||||
|
||||
@@ -245,8 +245,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
|
||||
this->set_timeout("wifi-connect-timeout", 30000, f);
|
||||
this->set_timeout("wifi-connect-timeout", 30000, [this]() { this->on_wifi_connect_timeout_(); });
|
||||
return true;
|
||||
}
|
||||
case improv::GET_CURRENT_STATE:
|
||||
|
||||
@@ -530,10 +530,11 @@ void LD2450Component::handle_periodic_data_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Store target info for zone target count
|
||||
this->target_info_[index].x = tx;
|
||||
this->target_info_[index].y = ty;
|
||||
this->target_info_[index].is_moving = is_moving;
|
||||
// Store target info for zone target count. Zero out untracked targets (td==0)
|
||||
// so stale coordinates don't produce ghost counts in count_targets_in_zone_().
|
||||
this->target_info_[index].x = (td > 0) ? tx : 0;
|
||||
this->target_info_[index].y = (td > 0) ? ty : 0;
|
||||
this->target_info_[index].is_moving = (td > 0) && is_moving;
|
||||
|
||||
} // End loop thru targets
|
||||
|
||||
|
||||
@@ -193,7 +193,9 @@ void LEDCOutput::setup() {
|
||||
chan_conf.gpio_num = static_cast<gpio_num_t>(this->pin_->get_pin());
|
||||
chan_conf.speed_mode = speed_mode;
|
||||
chan_conf.channel = chan_num;
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
chan_conf.intr_type = LEDC_INTR_DISABLE;
|
||||
#endif
|
||||
chan_conf.timer_sel = timer_num;
|
||||
chan_conf.duty = this->inverted_ == this->pin_->is_inverted() ? 0 : (1U << this->bit_depth_);
|
||||
chan_conf.hpoint = hpoint;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
uint32_t random_uint32() { return rand(); }
|
||||
|
||||
bool random_bytes(uint8_t *data, size_t len) {
|
||||
lt_rand_bytes(data, len);
|
||||
return true;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace esphome::libretiny {
|
||||
|
||||
static const char *const TAG = "lt.preferences";
|
||||
static const char *const TAG = "preferences";
|
||||
|
||||
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
|
||||
static constexpr size_t KEY_BUFFER_SIZE = 12;
|
||||
|
||||
@@ -81,18 +81,32 @@ def _get_data() -> LightData:
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
def generate_gamma_table(gamma_correct: float) -> list[HexInt]:
|
||||
"""Generate a 256-entry uint16 gamma lookup table.
|
||||
|
||||
For gamma > 0, non-zero indices are clamped to a minimum of 1 to preserve
|
||||
the invariant that non-zero input always produces non-zero output. Without
|
||||
this, small brightness values (e.g. 1%) get quantized to exactly 0.0,
|
||||
which breaks zero_means_zero logic in FloatOutput.
|
||||
"""
|
||||
if gamma_correct > 0:
|
||||
return [
|
||||
HexInt(
|
||||
max(1, min(65535, int(round((i / 255.0) ** gamma_correct * 65535))))
|
||||
if i > 0
|
||||
else HexInt(0)
|
||||
)
|
||||
for i in range(256)
|
||||
]
|
||||
return [HexInt(int(round(i / 255.0 * 65535))) for i in range(256)]
|
||||
|
||||
|
||||
def _get_or_create_gamma_table(gamma_correct):
|
||||
data = _get_data()
|
||||
if gamma_correct in data.gamma_tables:
|
||||
return data.gamma_tables[gamma_correct]
|
||||
|
||||
if gamma_correct > 0:
|
||||
forward = [
|
||||
HexInt(min(65535, int(round((i / 255.0) ** gamma_correct * 65535))))
|
||||
for i in range(256)
|
||||
]
|
||||
else:
|
||||
forward = [HexInt(int(round(i / 255.0 * 65535))) for i in range(256)]
|
||||
forward = generate_gamma_table(gamma_correct)
|
||||
|
||||
gamma_str = f"{gamma_correct}".replace(".", "_")
|
||||
fwd_id = ID(f"gamma_{gamma_str}_fwd", is_declaration=True, type=cg.uint16)
|
||||
|
||||
@@ -154,6 +154,16 @@ class LightColorValues {
|
||||
}
|
||||
|
||||
/// Convert these light color values to an CWWW representation with the given parameters.
|
||||
///
|
||||
/// Note on gamma and constant_brightness: This method operates on the raw/internal channel
|
||||
/// values stored in this object. For cold_white_ and warm_white_ specifically, these
|
||||
/// may already be gamma-uncorrected when derived from a color_temperature value.
|
||||
/// For constant_brightness=false, additional gamma for the output can be applied after
|
||||
/// this method since gamma commutes with simple multiplication. For constant_brightness=true,
|
||||
/// the caller (LightState::current_values_as_cwww) must apply gamma to the individual
|
||||
/// channel values BEFORE the balancing formula, because the nonlinear max/sum ratio does
|
||||
/// not commute with gamma. See LightState::current_values_as_cwww() for the correct
|
||||
/// implementation.
|
||||
void as_cwww(float *cold_white, float *warm_white, bool constant_brightness = false) const {
|
||||
if (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) {
|
||||
const float cw_level = this->cold_white_;
|
||||
|
||||
@@ -223,12 +223,11 @@ void LightState::current_values_as_rgbw(float *red, float *green, float *blue, f
|
||||
}
|
||||
void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white,
|
||||
bool constant_brightness) {
|
||||
this->current_values.as_rgbww(red, green, blue, cold_white, warm_white, constant_brightness);
|
||||
this->current_values.as_rgb(red, green, blue);
|
||||
*red = this->gamma_correct_lut(*red);
|
||||
*green = this->gamma_correct_lut(*green);
|
||||
*blue = this->gamma_correct_lut(*blue);
|
||||
*cold_white = this->gamma_correct_lut(*cold_white);
|
||||
*warm_white = this->gamma_correct_lut(*warm_white);
|
||||
this->current_values_as_cwww(cold_white, warm_white, constant_brightness);
|
||||
}
|
||||
void LightState::current_values_as_rgbct(float *red, float *green, float *blue, float *color_temperature,
|
||||
float *white_brightness) {
|
||||
@@ -241,9 +240,45 @@ void LightState::current_values_as_rgbct(float *red, float *green, float *blue,
|
||||
*white_brightness = this->gamma_correct_lut(*white_brightness);
|
||||
}
|
||||
void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) {
|
||||
this->current_values.as_cwww(cold_white, warm_white, constant_brightness);
|
||||
*cold_white = this->gamma_correct_lut(*cold_white);
|
||||
*warm_white = this->gamma_correct_lut(*warm_white);
|
||||
if (!constant_brightness) {
|
||||
// Without constant_brightness, gamma commutes with simple multiplication:
|
||||
// gamma(white_level * cw) = gamma(white_level) * gamma(cw)
|
||||
// (since gamma(a*b) = (a*b)^g = a^g * b^g = gamma(a) * gamma(b))
|
||||
// so applying gamma after is mathematically equivalent and simpler.
|
||||
this->current_values.as_cwww(cold_white, warm_white, false);
|
||||
*cold_white = this->gamma_correct_lut(*cold_white);
|
||||
*warm_white = this->gamma_correct_lut(*warm_white);
|
||||
return;
|
||||
}
|
||||
|
||||
// For constant_brightness mode, gamma MUST be applied to the individual
|
||||
// channel values BEFORE the balancing formula (max/sum ratio), not after.
|
||||
//
|
||||
// Why: The cold_white_ and warm_white_ values stored in LightColorValues
|
||||
// are gamma-uncorrected (see transform_parameters_() which applies
|
||||
// gamma_uncorrect to the linear CW/WW fractions derived from color
|
||||
// temperature). Applying gamma_correct here recovers the original linear
|
||||
// fractions, which the constant_brightness formula then uses to distribute
|
||||
// power evenly. The max/sum formula ensures cold+warm PWM output sums to
|
||||
// a constant, keeping total power (and perceived brightness) the same
|
||||
// across all color temperatures.
|
||||
//
|
||||
// Applying gamma AFTER the formula would be incorrect because gamma is
|
||||
// nonlinear: gamma(a/b) != gamma(a)/gamma(b), so the carefully balanced
|
||||
// ratio would be distorted, causing a severe brightness dip at mid-range
|
||||
// color temperatures.
|
||||
const auto &v = this->current_values;
|
||||
if (!(v.get_color_mode() & ColorCapability::COLD_WARM_WHITE)) {
|
||||
*cold_white = *warm_white = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const float cw_level = this->gamma_correct_lut(v.get_cold_white());
|
||||
const float ww_level = this->gamma_correct_lut(v.get_warm_white());
|
||||
const float white_level = this->gamma_correct_lut(v.get_state() * v.get_brightness());
|
||||
const float sum = cw_level > 0 || ww_level > 0 ? cw_level + ww_level : 1; // Don't divide by zero.
|
||||
*cold_white = white_level * std::max(cw_level, ww_level) * cw_level / sum;
|
||||
*warm_white = white_level * std::max(cw_level, ww_level) * ww_level / sum;
|
||||
}
|
||||
void LightState::current_values_as_ct(float *color_temperature, float *white_brightness) {
|
||||
auto traits = this->get_traits();
|
||||
|
||||
@@ -51,13 +51,16 @@ template<typename... Ts> class LockCondition : public Condition<Ts...> {
|
||||
|
||||
template<LockState State> class LockStateTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit LockStateTrigger(Lock *a_lock) {
|
||||
a_lock->add_on_state_callback([this, a_lock]() {
|
||||
if (a_lock->state == State) {
|
||||
explicit LockStateTrigger(Lock *a_lock) : lock_(a_lock) {
|
||||
a_lock->add_on_state_callback([this]() {
|
||||
if (this->lock_->state == State) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
using LockLockTrigger = LockStateTrigger<LockState::LOCK_STATE_LOCKED>;
|
||||
|
||||
@@ -56,6 +56,7 @@ from esphome.const import (
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, Lambda, coroutine_with_priority
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
logger_ns = cg.esphome_ns.namespace("logger")
|
||||
@@ -323,19 +324,34 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
|
||||
async def to_code(config):
|
||||
baud_rate = config[CONF_BAUD_RATE]
|
||||
@coroutine_with_priority(CoroPriority.EARLY_INIT)
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
baud_rate: int = config[CONF_BAUD_RATE]
|
||||
level = config[CONF_LEVEL]
|
||||
CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level
|
||||
initial_level = LOG_LEVELS[config.get(CONF_INITIAL_LEVEL, level)]
|
||||
tx_buffer_size = config[CONF_TX_BUFFER_SIZE]
|
||||
cg.add_define("ESPHOME_LOGGER_TX_BUFFER_SIZE", tx_buffer_size)
|
||||
log = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
baud_rate,
|
||||
)
|
||||
if CORE.is_esp32:
|
||||
# Determine task log buffer size and define USE_ESPHOME_TASK_LOG_BUFFER early
|
||||
# so the constructor can allocate the buffer immediately, preventing a race
|
||||
# where another task logs before the buffer is initialized.
|
||||
task_log_buffer_size = 0
|
||||
if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52:
|
||||
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
|
||||
elif CORE.is_host:
|
||||
task_log_buffer_size = 64 # Fixed 64 slots for host
|
||||
if task_log_buffer_size > 0:
|
||||
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
||||
log = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
baud_rate,
|
||||
task_log_buffer_size,
|
||||
)
|
||||
else:
|
||||
log = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
baud_rate,
|
||||
)
|
||||
if CORE.is_esp32 or CORE.is_host:
|
||||
cg.add(log.create_pthread_key())
|
||||
# set_uart_selection() must be called before pre_setup() because
|
||||
# pre_setup() switches on uart_ to decide which hardware to initialize
|
||||
@@ -347,24 +363,28 @@ async def to_code(config):
|
||||
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]]
|
||||
)
|
||||
)
|
||||
# pre_setup() must be called before init_log_buffer() because
|
||||
# init_log_buffer() calls disable_loop() which may log at VV level,
|
||||
# and global_logger must be set before any logging occurs.
|
||||
# pre_setup() sets global_logger and must run before any other code
|
||||
# that may call ESP_LOG* (e.g. setup_preferences contains ESP_LOGVV).
|
||||
cg.add(log.pre_setup())
|
||||
if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52:
|
||||
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
|
||||
if task_log_buffer_size > 0:
|
||||
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
||||
cg.add(log.init_log_buffer(task_log_buffer_size))
|
||||
if CORE.using_zephyr:
|
||||
zephyr_add_prj_conf("MPSC_PBUF", True)
|
||||
elif CORE.is_host:
|
||||
cg.add(log.create_pthread_key())
|
||||
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
||||
cg.add(log.init_log_buffer(64)) # Fixed 64 slots for host
|
||||
|
||||
initial_level = LOG_LEVELS[config.get(CONF_INITIAL_LEVEL, level)]
|
||||
cg.add(log.set_log_level(initial_level))
|
||||
|
||||
# Schedule the rest of logger setup at DIAGNOSTICS priority, after
|
||||
# Application is constructed (CORE priority) but before most components.
|
||||
CORE.add_job(_late_logger_init, config)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
|
||||
async def _late_logger_init(config: ConfigType) -> None:
|
||||
"""Finish logger setup after Application is constructed."""
|
||||
log = await cg.get_variable(config[CONF_ID])
|
||||
level = config[CONF_LEVEL]
|
||||
baud_rate: int = config[CONF_BAUD_RATE]
|
||||
if CORE.using_zephyr:
|
||||
task_log_buffer_size = config.get(CONF_TASK_LOG_BUFFER_SIZE, 0)
|
||||
if task_log_buffer_size > 0:
|
||||
zephyr_add_prj_conf("MPSC_PBUF", True)
|
||||
|
||||
# Enable runtime tag levels if logs are configured or explicitly enabled
|
||||
logs_config = config[CONF_LOGS]
|
||||
if logs_config or config[CONF_RUNTIME_TAG_LEVELS]:
|
||||
@@ -594,6 +614,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
},
|
||||
"task_log_buffer_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -8,8 +9,8 @@ namespace esphome::logger {
|
||||
// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin)
|
||||
static constexpr uint16_t MAX_HEADER_SIZE = 128;
|
||||
|
||||
// ANSI color code last digit (30-38 range, store only last digit to save RAM)
|
||||
static constexpr char LOG_LEVEL_COLOR_DIGIT[] = {
|
||||
// ANSI color code last digit (30-38 range, store only last digit to save RAM on ESP8266)
|
||||
static const char LOG_LEVEL_COLOR_DIGIT[] PROGMEM = {
|
||||
'\0', // NONE
|
||||
'1', // ERROR (31 = red)
|
||||
'3', // WARNING (33 = yellow)
|
||||
@@ -20,7 +21,7 @@ static constexpr char LOG_LEVEL_COLOR_DIGIT[] = {
|
||||
'8', // VERY_VERBOSE (38 = white)
|
||||
};
|
||||
|
||||
static constexpr char LOG_LEVEL_LETTER_CHARS[] = {
|
||||
static const char LOG_LEVEL_LETTER_CHARS[] PROGMEM = {
|
||||
'\0', // NONE
|
||||
'E', // ERROR
|
||||
'W', // WARNING
|
||||
@@ -64,7 +65,7 @@ struct LogBuffer {
|
||||
*p++ = 'V'; // VERY_VERBOSE = "VV"
|
||||
*p++ = 'V';
|
||||
} else {
|
||||
*p++ = LOG_LEVEL_LETTER_CHARS[level];
|
||||
*p++ = static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(&LOG_LEVEL_LETTER_CHARS[level])));
|
||||
}
|
||||
}
|
||||
*p++ = ']';
|
||||
@@ -184,7 +185,7 @@ struct LogBuffer {
|
||||
*p++ = (level == 1) ? '1' : '0'; // Only ERROR is bold
|
||||
*p++ = ';';
|
||||
*p++ = '3';
|
||||
*p++ = LOG_LEVEL_COLOR_DIGIT[level];
|
||||
*p++ = static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(&LOG_LEVEL_COLOR_DIGIT[level])));
|
||||
*p++ = 'm';
|
||||
}
|
||||
// Copy string without null terminator, updates pointer in place
|
||||
|
||||
@@ -152,29 +152,25 @@ inline uint8_t Logger::level_for(const char *tag) {
|
||||
return this->current_level_;
|
||||
}
|
||||
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
Logger::Logger(uint32_t baud_rate, size_t task_log_buffer_size) : baud_rate_(baud_rate) {
|
||||
#else
|
||||
Logger::Logger(uint32_t baud_rate) : baud_rate_(baud_rate) {
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
this->main_task_ = xTaskGetCurrentTaskHandle();
|
||||
#elif defined(USE_ZEPHYR)
|
||||
this->main_task_ = k_current_get();
|
||||
#elif defined(USE_HOST)
|
||||
this->main_thread_ = pthread_self();
|
||||
this->main_thread_ = pthread_self();
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
void Logger::init_log_buffer(size_t total_buffer_size) {
|
||||
// Host uses slot count instead of byte size
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed
|
||||
this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size);
|
||||
|
||||
#if !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
// Start with loop disabled when using task buffer
|
||||
// The loop will be enabled automatically when messages arrive
|
||||
// Zephyr with USB CDC needs loop active to poll port readiness via cdc_loop_()
|
||||
this->disable_loop_when_buffer_empty_();
|
||||
this->log_buffer_ = new logger::TaskLogBuffer(task_log_buffer_size);
|
||||
// Note: we don't disable loop here because the component isn't registered with App yet.
|
||||
// The loop self-disables on its first iteration when it finds no messages to process.
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
void Logger::loop() {
|
||||
|
||||
@@ -143,9 +143,10 @@ enum UARTSelection : uint8_t {
|
||||
*/
|
||||
class Logger final : public Component {
|
||||
public:
|
||||
explicit Logger(uint32_t baud_rate);
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
void init_log_buffer(size_t total_buffer_size);
|
||||
explicit Logger(uint32_t baud_rate, size_t task_log_buffer_size);
|
||||
#else
|
||||
explicit Logger(uint32_t baud_rate);
|
||||
#endif
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
void loop() override;
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
import importlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import pkgutil
|
||||
|
||||
from esphome.automation import build_automation, validate_automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_COLOR_DEPTH, CONF_DRAW_ROUNDING
|
||||
from esphome.components.const import (
|
||||
CONF_BYTE_ORDER,
|
||||
CONF_COLOR_DEPTH,
|
||||
CONF_DRAW_ROUNDING,
|
||||
)
|
||||
from esphome.components.display import Display
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32P4,
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
get_esp32_variant,
|
||||
)
|
||||
from esphome.components.image import (
|
||||
CONF_OPAQUE,
|
||||
IMAGE_TYPE,
|
||||
ImageBinary,
|
||||
ImageGrayscale,
|
||||
ImageRGB,
|
||||
ImageRGB565,
|
||||
get_image_metadata,
|
||||
)
|
||||
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -21,7 +39,6 @@ from esphome.const import (
|
||||
CONF_PAGES,
|
||||
CONF_TIMEOUT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from esphome.core import CORE, ID, Lambda
|
||||
from esphome.cpp_generator import MockObj
|
||||
@@ -30,8 +47,7 @@ from esphome.helpers import write_file_if_changed
|
||||
from esphome.yaml_util import load_yaml
|
||||
|
||||
from . import defines as df, helpers, lv_validation as lvalid, widgets
|
||||
from .automation import disp_update, focused_widgets, refreshed_widgets
|
||||
from .defines import add_define
|
||||
from .automation import focused_widgets, layers_to_code, lvgl_update, refreshed_widgets
|
||||
from .encoders import (
|
||||
ENCODERS_CONFIG,
|
||||
encoders_to_code,
|
||||
@@ -45,12 +61,13 @@ from .lvcode import LvContext, LvglComponent, lvgl_static
|
||||
from .schemas import (
|
||||
DISP_BG_SCHEMA,
|
||||
FULL_STYLE_SCHEMA,
|
||||
STYLE_REMAP,
|
||||
WIDGET_TYPES,
|
||||
any_widget_schema,
|
||||
container_schema,
|
||||
obj_schema,
|
||||
)
|
||||
from .styles import add_top_layer, styles_to_code, theme_to_code
|
||||
from .styles import styles_to_code, theme_to_code
|
||||
from .touchscreens import touchscreen_schema, touchscreens_to_code
|
||||
from .trigger import add_on_boot_triggers, generate_triggers
|
||||
from .types import IdleTrigger, PlainTrigger, lv_font_t, lv_group_t, lv_style_t, lvgl_ns
|
||||
@@ -58,7 +75,7 @@ from .widgets import (
|
||||
LvScrActType,
|
||||
Widget,
|
||||
add_widgets,
|
||||
get_scr_act,
|
||||
get_screen_active,
|
||||
set_obj_properties,
|
||||
styles_used,
|
||||
)
|
||||
@@ -84,7 +101,6 @@ DOMAIN = "lvgl"
|
||||
DEPENDENCIES = ["display"]
|
||||
AUTO_LOAD = ["key_provider"]
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
HELLO_WORLD_FILE = "hello_world.yaml"
|
||||
|
||||
|
||||
@@ -102,6 +118,7 @@ def as_macro(macro, value):
|
||||
return f"#define {macro} {value}"
|
||||
|
||||
|
||||
LVGL_VERSION = "9.5.0"
|
||||
LV_CONF_FILENAME = "lv_conf.h"
|
||||
LV_CONF_H_FORMAT = """\
|
||||
#pragma once
|
||||
@@ -110,7 +127,17 @@ LV_CONF_H_FORMAT = """\
|
||||
|
||||
|
||||
def generate_lv_conf_h():
|
||||
definitions = [as_macro(m, v) for m, v in df.get_data(df.KEY_LV_DEFINES).items()]
|
||||
# Get all possible LV_ config defines based on the widgets used in the config, and the standard LVGL options
|
||||
all_defines = set(
|
||||
df.LV_DEFINES + tuple(f"LV_USE_{w.upper()}" for w in WIDGET_TYPES)
|
||||
)
|
||||
# Get the defines that are actually used based on the config
|
||||
lv_defines = df.get_data(df.KEY_LV_DEFINES)
|
||||
unused_defines = all_defines - set(lv_defines)
|
||||
# Create the content of lv_conf.h with the used defines set to their value, and the unused defines disabled
|
||||
definitions = [as_macro(m, v) for m, v in lv_defines.items()] + [
|
||||
as_macro(m, "0") for m in unused_defines
|
||||
]
|
||||
definitions.sort()
|
||||
return LV_CONF_H_FORMAT.format("\n".join(definitions))
|
||||
|
||||
@@ -133,7 +160,7 @@ def multi_conf_validate(configs: list[dict]):
|
||||
for item in (
|
||||
CONF_LOG_LEVEL,
|
||||
CONF_COLOR_DEPTH,
|
||||
df.CONF_BYTE_ORDER,
|
||||
CONF_BYTE_ORDER,
|
||||
df.CONF_TRANSPARENCY_KEY,
|
||||
):
|
||||
if base_config[item] != config[item]:
|
||||
@@ -166,14 +193,7 @@ def final_validation(config_list):
|
||||
)
|
||||
buffer_frac = config[CONF_BUFFER_SIZE]
|
||||
if CORE.is_esp32 and buffer_frac > 0.5 and PSRAM_DOMAIN not in global_config:
|
||||
LOGGER.warning("buffer_size: may need to be reduced without PSRAM")
|
||||
for image_id in lv_images_used:
|
||||
path = global_config.get_path_for_id(image_id)[:-1]
|
||||
image_conf = global_config.get_config_for_path(path)
|
||||
if image_conf[CONF_TYPE] in ("RGBA", "RGB24"):
|
||||
raise cv.Invalid(
|
||||
"Using RGBA or RGB24 in image config not compatible with LVGL", path
|
||||
)
|
||||
df.LOGGER.warning("buffer_size: may need to be reduced without PSRAM")
|
||||
for w in focused_widgets:
|
||||
path = global_config.get_path_for_id(w)
|
||||
widget_conf = global_config.get_config_for_path(path[:-1])
|
||||
@@ -205,39 +225,48 @@ def final_validation(config_list):
|
||||
async def to_code(configs):
|
||||
config_0 = configs[0]
|
||||
# Global configuration
|
||||
cg.add_library("lvgl/lvgl", "8.4.0")
|
||||
if CORE.is_esp32:
|
||||
if get_esp32_variant() == VARIANT_ESP32P4:
|
||||
add_idf_sdkconfig_option("CONFIG_LV_DRAW_BUF_ALIGN", 64)
|
||||
# disable use of PPA for fills until upstream bugs fixed
|
||||
df.add_define("LV_USE_PPA", "0")
|
||||
df.add_define("LV_DRAW_BUF_ALIGN", "64")
|
||||
else:
|
||||
df.add_define("LV_DRAW_BUF_ALIGN", "32")
|
||||
add_idf_component(name="lvgl/lvgl", ref=LVGL_VERSION)
|
||||
else:
|
||||
df.add_define("LV_DRAW_BUF_ALIGN", "1")
|
||||
cg.add_library("lvgl/lvgl", LVGL_VERSION)
|
||||
df.add_define("LV_DRAW_BUF_STRIDE_ALIGN", "1")
|
||||
df.add_define("LV_USE_DRAW_SW", "1")
|
||||
df.add_define("LV_USE_STDLIB_SPRINTF", "LV_STDLIB_CLIB")
|
||||
df.add_define("LV_USE_STDLIB_STRING", "LV_STDLIB_CLIB")
|
||||
df.add_define("LV_USE_STDLIB_MALLOC", "LV_STDLIB_CUSTOM")
|
||||
cg.add_define("USE_LVGL")
|
||||
# suppress default enabling of extra widgets
|
||||
add_define("_LV_KCONFIG_PRESENT")
|
||||
# cg.add_define("LV_KCONFIG_PRESENT")
|
||||
# Always enable - lots of things use it.
|
||||
add_define("LV_DRAW_COMPLEX", "1")
|
||||
add_define("LV_TICK_CUSTOM", "1")
|
||||
add_define("LV_TICK_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"')
|
||||
add_define("LV_TICK_CUSTOM_SYS_TIME_EXPR", "(lv_millis())")
|
||||
add_define("LV_MEM_CUSTOM", "1")
|
||||
add_define("LV_MEM_CUSTOM_ALLOC", "lv_custom_mem_alloc")
|
||||
add_define("LV_MEM_CUSTOM_FREE", "lv_custom_mem_free")
|
||||
add_define("LV_MEM_CUSTOM_REALLOC", "lv_custom_mem_realloc")
|
||||
add_define("LV_MEM_CUSTOM_INCLUDE", '"esphome/components/lvgl/lvgl_hal.h"')
|
||||
df.add_define("LV_DRAW_SW_COMPLEX", "1")
|
||||
|
||||
add_define(
|
||||
df.add_define(
|
||||
"LV_LOG_LEVEL",
|
||||
f"LV_LOG_LEVEL_{df.LV_LOG_LEVELS[config_0[CONF_LOG_LEVEL]]}",
|
||||
)
|
||||
df.add_define("LV_USE_LOG", "1")
|
||||
cg.add_define(
|
||||
"LVGL_LOG_LEVEL",
|
||||
cg.RawExpression(f"ESPHOME_LOG_LEVEL_{config_0[CONF_LOG_LEVEL]}"),
|
||||
)
|
||||
add_define("LV_COLOR_DEPTH", config_0[CONF_COLOR_DEPTH])
|
||||
df.add_define("LV_COLOR_DEPTH", config_0[CONF_COLOR_DEPTH])
|
||||
for font in helpers.lv_fonts_used:
|
||||
add_define(f"LV_FONT_{font.upper()}")
|
||||
df.add_define(f"LV_FONT_{font.upper()}")
|
||||
|
||||
if config_0[CONF_COLOR_DEPTH] == 16:
|
||||
add_define(
|
||||
df.add_define(
|
||||
"LV_COLOR_16_SWAP",
|
||||
"1" if config_0[df.CONF_BYTE_ORDER] == "big_endian" else "0",
|
||||
"1" if config_0[CONF_BYTE_ORDER] == "big_endian" else "0",
|
||||
)
|
||||
add_define(
|
||||
df.add_define(
|
||||
"LV_COLOR_CHROMA_KEY",
|
||||
await lvalid.lv_color.process(config_0[df.CONF_TRANSPARENCY_KEY]),
|
||||
)
|
||||
@@ -248,7 +277,7 @@ async def to_code(configs):
|
||||
await cg.get_variable(font)
|
||||
default_font = config_0[df.CONF_DEFAULT_FONT]
|
||||
if not lvalid.is_lv_font(default_font):
|
||||
add_define(
|
||||
df.add_define(
|
||||
"LV_FONT_CUSTOM_DECLARE", f"LV_FONT_DECLARE(*{df.DEFAULT_ESPHOME_FONT})"
|
||||
)
|
||||
globfont_id = ID(
|
||||
@@ -262,9 +291,9 @@ async def to_code(configs):
|
||||
MockObj(await lvalid.lv_font.process(default_font), "->").get_lv_font(),
|
||||
static=False,
|
||||
)
|
||||
add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT)
|
||||
df.add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT)
|
||||
else:
|
||||
add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font))
|
||||
df.add_define("LV_FONT_DEFAULT", await lvalid.lv_font.process(default_font))
|
||||
cg.add(lvgl_static.esphome_lvgl_init())
|
||||
default_group = get_default_group(config_0)
|
||||
|
||||
@@ -293,8 +322,9 @@ async def to_code(configs):
|
||||
await cg.register_component(lv_component, config)
|
||||
Widget.create(config[CONF_ID], lv_component, LvScrActType(), config)
|
||||
|
||||
lv_scr_act = get_scr_act(lv_component)
|
||||
lv_scr_act = get_screen_active(lv_component)
|
||||
async with LvContext():
|
||||
cg.add(lv_component.set_big_endian(config[CONF_BYTE_ORDER] == "big_endian"))
|
||||
await touchscreens_to_code(lv_component, config)
|
||||
await encoders_to_code(lv_component, config, default_group)
|
||||
await keypads_to_code(lv_component, config, default_group)
|
||||
@@ -304,9 +334,10 @@ async def to_code(configs):
|
||||
await set_obj_properties(lv_scr_act, config)
|
||||
await add_widgets(lv_scr_act, config)
|
||||
await add_pages(lv_component, config)
|
||||
await add_top_layer(lv_component, config)
|
||||
await layers_to_code(lv_component, config)
|
||||
await lvgl_update(lv_component, config)
|
||||
await msgboxes_to_code(lv_component, config)
|
||||
await disp_update(lv_component.get_disp(), config)
|
||||
# await disp_update(lv_component.get_disp(), config)
|
||||
# Set this directly since we are limited in how many methods can be added to the Widget class.
|
||||
Widget.widgets_completed = True
|
||||
async with LvContext():
|
||||
@@ -336,15 +367,53 @@ async def to_code(configs):
|
||||
# This must be done after all widgets are created
|
||||
for comp in helpers.lvgl_components_required:
|
||||
cg.add_define(f"USE_LVGL_{comp.upper()}")
|
||||
if {"transform_angle", "transform_zoom"} & styles_used:
|
||||
add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
||||
lv_image_formats = df.get_color_formats().copy()
|
||||
if {
|
||||
"transform_rotation",
|
||||
"transform_scale",
|
||||
"transform_scale_x",
|
||||
"transform_scale_y",
|
||||
} & styles_used:
|
||||
df.add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
||||
lv_image_formats.add("ARGB8888")
|
||||
lv_image_formats.add(
|
||||
"RGB565"
|
||||
) # Currently always need RGB565 for the display buffer
|
||||
for use in helpers.lv_uses:
|
||||
add_define(f"LV_USE_{use.upper()}")
|
||||
df.add_define(f"LV_USE_{use.upper()}")
|
||||
cg.add_define(f"USE_LVGL_{use.upper()}")
|
||||
|
||||
for image_id in lv_images_used:
|
||||
await cg.get_variable(image_id)
|
||||
metadata = get_image_metadata(image_id.id)
|
||||
image_type = IMAGE_TYPE[metadata.image_type]
|
||||
transparent = metadata.transparency != CONF_OPAQUE
|
||||
if transparent:
|
||||
# Internal draw layer will use ARGB8888
|
||||
lv_image_formats.add("ARGB8888")
|
||||
if image_type == ImageBinary:
|
||||
lv_image_formats.add("I1")
|
||||
if image_type == ImageGrayscale:
|
||||
lv_image_formats.add("A8")
|
||||
if image_type == ImageRGB565:
|
||||
lv_image_formats.add("RGB565A8" if transparent else "RGB565")
|
||||
if image_type == ImageRGB:
|
||||
lv_image_formats.add("ARGB8888" if transparent else "RGB8888")
|
||||
if df.is_defined("LV_GRADIENT_MAX_STOPS"):
|
||||
lv_image_formats.add("RGB888")
|
||||
for fmt in lv_image_formats:
|
||||
df.add_define(f"LV_DRAW_SW_SUPPORT_{fmt}", "1")
|
||||
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
|
||||
write_file_if_changed(lv_conf_h_file, generate_lv_conf_h())
|
||||
cg.add_build_flag("-DLV_CONF_H=1")
|
||||
cg.add_build_flag(f'-DLV_CONF_PATH="{LV_CONF_FILENAME}"')
|
||||
cg.add_build_flag(f'-DLV_CONF_PATH=\\"{LV_CONF_FILENAME}\\"')
|
||||
|
||||
for prop in df.get_remapped_uses():
|
||||
df.LOGGER.warning(
|
||||
"Property '%s' is deprecated, use '%s' instead", prop, STYLE_REMAP[prop]
|
||||
)
|
||||
for warning in df.get_warnings():
|
||||
df.LOGGER.warning(warning)
|
||||
|
||||
|
||||
def display_schema(config):
|
||||
@@ -357,7 +426,9 @@ def display_schema(config):
|
||||
|
||||
def add_hello_world(config):
|
||||
if df.CONF_WIDGETS not in config and CONF_PAGES not in config:
|
||||
LOGGER.info("No pages or widgets configured, creating default hello_world page")
|
||||
df.LOGGER.info(
|
||||
"No pages or widgets configured, creating default hello_world page"
|
||||
)
|
||||
hello_world_path = Path(__file__).parent / HELLO_WORLD_FILE
|
||||
config[df.CONF_WIDGETS] = any_widget_schema()(load_yaml(hello_world_path))
|
||||
return config
|
||||
@@ -395,8 +466,8 @@ LVGL_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_LOG_LEVEL, default="WARN"): cv.one_of(
|
||||
*df.LV_LOG_LEVELS, upper=True
|
||||
),
|
||||
cv.Optional(df.CONF_BYTE_ORDER, default="big_endian"): cv.one_of(
|
||||
"big_endian", "little_endian"
|
||||
cv.Optional(CONF_BYTE_ORDER, default="big_endian"): cv.one_of(
|
||||
"big_endian", "little_endian", lower=True
|
||||
),
|
||||
cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list(
|
||||
cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)}).extend(
|
||||
@@ -424,6 +495,7 @@ LVGL_SCHEMA = cv.All(
|
||||
cv.Optional(df.CONF_MSGBOXES): cv.ensure_list(MSGBOX_SCHEMA),
|
||||
cv.Optional(df.CONF_PAGE_WRAP, default=True): lv_bool,
|
||||
cv.Optional(df.CONF_TOP_LAYER): container_schema(obj_spec),
|
||||
cv.Optional(df.CONF_BOTTOM_LAYER): container_schema(obj_spec),
|
||||
cv.Optional(
|
||||
df.CONF_TRANSPARENCY_KEY, default=0x000400
|
||||
): lvalid.lv_color,
|
||||
|
||||
@@ -10,18 +10,21 @@ from esphome.cpp_generator import TemplateArguments, get_variable
|
||||
from esphome.cpp_types import nullptr
|
||||
|
||||
from .defines import (
|
||||
CONF_DISP_BG_COLOR,
|
||||
CONF_DISP_BG_IMAGE,
|
||||
CONF_DISP_BG_OPA,
|
||||
CONF_BG_OPA,
|
||||
CONF_BOTTOM_LAYER,
|
||||
CONF_EDITING,
|
||||
CONF_FREEZE,
|
||||
CONF_LVGL_ID,
|
||||
CONF_MAIN,
|
||||
CONF_OBJ,
|
||||
CONF_SCROLLBAR,
|
||||
CONF_SHOW_SNOW,
|
||||
CONF_TOP_LAYER,
|
||||
PARTS,
|
||||
literal,
|
||||
static_cast,
|
||||
StaticCastExpression,
|
||||
add_warning,
|
||||
)
|
||||
from .lv_validation import lv_bool, lv_color, lv_image, lv_milliseconds, opacity
|
||||
from .lv_validation import lv_bool, lv_milliseconds
|
||||
from .lvcode import (
|
||||
LVGL_COMP_ARG,
|
||||
UPDATE_EVENT,
|
||||
@@ -42,13 +45,13 @@ from .schemas import (
|
||||
LIST_ACTION_SCHEMA,
|
||||
LVGL_SCHEMA,
|
||||
base_update_schema,
|
||||
part_schema,
|
||||
)
|
||||
from .types import (
|
||||
LV_STATE,
|
||||
LvglAction,
|
||||
LvglCondition,
|
||||
ObjUpdateAction,
|
||||
lv_disp_t,
|
||||
lv_group_t,
|
||||
lv_obj_base_t,
|
||||
lv_obj_t,
|
||||
@@ -56,7 +59,9 @@ from .types import (
|
||||
)
|
||||
from .widgets import (
|
||||
Widget,
|
||||
get_scr_act,
|
||||
WidgetType,
|
||||
add_widgets,
|
||||
get_screen_active,
|
||||
get_widgets,
|
||||
set_obj_properties,
|
||||
wait_for_widgets,
|
||||
@@ -67,6 +72,41 @@ focused_widgets = set()
|
||||
refreshed_widgets = set()
|
||||
|
||||
|
||||
async def layers_to_code(lv_component, config):
|
||||
if top_conf := config.get(CONF_TOP_LAYER):
|
||||
top_layer = lv_expr.display_get_layer_top(lv_component.get_disp())
|
||||
with LocalVariable("top_layer", lv_obj_t, top_layer) as top_layer_obj:
|
||||
top_w = Widget(top_layer_obj, layer_spec, top_conf)
|
||||
await set_obj_properties(top_w, top_conf)
|
||||
await add_widgets(top_w, top_conf)
|
||||
if bottom_conf := config.get(CONF_BOTTOM_LAYER):
|
||||
bottom_layer = lv_expr.display_get_layer_bottom(lv_component.get_disp())
|
||||
with LocalVariable("bottom_layer", lv_obj_t, bottom_layer) as bottom_layer_obj:
|
||||
bottom_w = Widget(bottom_layer_obj, layer_spec, bottom_conf)
|
||||
await set_obj_properties(bottom_w, bottom_conf)
|
||||
await add_widgets(bottom_w, bottom_conf)
|
||||
|
||||
|
||||
async def lvgl_update(lv_component, config):
|
||||
bottom = {k.removeprefix("disp_"): v for k, v in config.items() if k in DISP_PROPS}
|
||||
if not bottom:
|
||||
return
|
||||
plural = len(bottom) != 1
|
||||
add_warning(
|
||||
"The propert"
|
||||
+ ("ies " if plural else "y ")
|
||||
+ "'"
|
||||
+ "','".join(k for k in config if k in DISP_PROPS)
|
||||
+ "'"
|
||||
+ (" are " if plural else " is ")
|
||||
+ "deprecated, use 'bottom_layer' instead."
|
||||
)
|
||||
# Preserve default opacity from 8.x
|
||||
if CONF_BG_OPA not in bottom:
|
||||
bottom[CONF_BG_OPA] = 1.0
|
||||
await layers_to_code(lv_component, {CONF_BOTTOM_LAYER: bottom})
|
||||
|
||||
|
||||
async def action_to_code(
|
||||
widgets: list[Widget],
|
||||
action: Callable[[Widget], Any],
|
||||
@@ -151,25 +191,6 @@ async def lvgl_is_idle(config, condition_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
async def disp_update(disp, config: dict):
|
||||
if (
|
||||
CONF_DISP_BG_COLOR not in config
|
||||
and CONF_DISP_BG_IMAGE not in config
|
||||
and CONF_DISP_BG_OPA not in config
|
||||
):
|
||||
return
|
||||
with LocalVariable("lv_disp_tmp", lv_disp_t, disp) as disp_temp:
|
||||
if (bg_color := config.get(CONF_DISP_BG_COLOR)) is not None:
|
||||
lv.disp_set_bg_color(disp_temp, await lv_color.process(bg_color))
|
||||
if bg_image := config.get(CONF_DISP_BG_IMAGE):
|
||||
if bg_image == "none":
|
||||
lv.disp_set_bg_image(disp_temp, static_cast("void *", "nullptr"))
|
||||
else:
|
||||
lv.disp_set_bg_image(disp_temp, await lv_image.process(bg_image))
|
||||
if (bg_opa := config.get(CONF_DISP_BG_OPA)) is not None:
|
||||
lv.disp_set_bg_opa(disp_temp, await opacity.process(bg_opa))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.widget.redraw",
|
||||
ObjUpdateAction,
|
||||
@@ -187,7 +208,7 @@ async def disp_update(disp, config: dict):
|
||||
async def obj_invalidate_to_code(config, action_id, template_arg, args):
|
||||
if CONF_LVGL_ID in config:
|
||||
lv_comp = await cg.get_variable(config[CONF_LVGL_ID])
|
||||
widgets = [get_scr_act(lv_comp)]
|
||||
widgets = [get_screen_active(lv_comp)]
|
||||
else:
|
||||
widgets = await get_widgets(config)
|
||||
|
||||
@@ -197,20 +218,30 @@ async def obj_invalidate_to_code(config, action_id, template_arg, args):
|
||||
return await action_to_code(widgets, do_invalidate, action_id, template_arg, args)
|
||||
|
||||
|
||||
layer_spec = WidgetType(CONF_OBJ, lv_obj_t, (CONF_MAIN, CONF_SCROLLBAR), is_mock=True)
|
||||
|
||||
DISP_PROPS = {str(x) for x in DISP_BG_SCHEMA.schema}
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.update",
|
||||
LvglAction,
|
||||
DISP_BG_SCHEMA.extend(LVGL_SCHEMA).add_extra(
|
||||
cv.has_at_least_one_key(CONF_DISP_BG_COLOR, CONF_DISP_BG_IMAGE)
|
||||
part_schema(layer_spec.parts)
|
||||
.extend(LVGL_SCHEMA)
|
||||
.extend(DISP_BG_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_TOP_LAYER): part_schema(layer_spec.parts),
|
||||
cv.Optional(CONF_BOTTOM_LAYER): part_schema(layer_spec.parts),
|
||||
}
|
||||
),
|
||||
synchronous=True,
|
||||
)
|
||||
async def lvgl_update_to_code(config, action_id, template_arg, args):
|
||||
widgets = await get_widgets(config, CONF_LVGL_ID)
|
||||
w = widgets[0]
|
||||
disp = literal(f"{w.obj}->get_disp()")
|
||||
async with LambdaContext(LVGL_COMP_ARG, where=action_id) as context:
|
||||
await disp_update(disp, config)
|
||||
await lvgl_update(w.var, config)
|
||||
var = cg.new_Pvariable(action_id, template_arg, await context.get_lambda())
|
||||
await cg.register_parented(var, w.var)
|
||||
return var
|
||||
@@ -336,7 +367,7 @@ async def widget_focus(config, action_id, template_arg, args):
|
||||
widget = await get_widgets(config)
|
||||
if widget:
|
||||
widget = widget[0]
|
||||
group = static_cast(
|
||||
group = StaticCastExpression(
|
||||
lv_group_t.operator("ptr"), lv_expr.obj_get_group(widget.obj)
|
||||
)
|
||||
elif group := config.get(CONF_GROUP):
|
||||
|
||||
@@ -10,8 +10,12 @@ from typing import Any
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.const import CONF_ITEMS
|
||||
from esphome.core import CORE, ID, Lambda
|
||||
from esphome.cpp_generator import LambdaExpression, MockObj
|
||||
from esphome.cpp_types import uint32
|
||||
from esphome.cpp_generator import (
|
||||
CallExpression,
|
||||
LambdaExpression,
|
||||
MockObj,
|
||||
MockObjClass,
|
||||
)
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import Expression, SafeExpType
|
||||
|
||||
@@ -21,8 +25,11 @@ LOGGER = logging.getLogger(__name__)
|
||||
lvgl_ns = cg.esphome_ns.namespace("lvgl")
|
||||
|
||||
DOMAIN = "lvgl"
|
||||
KEY_COLOR_FORMATS = "color_formats"
|
||||
KEY_LV_DEFINES = "lv_defines"
|
||||
KEY_REMAPPED_USES = "remapped_uses"
|
||||
KEY_UPDATED_WIDGETS = "updated_widgets"
|
||||
KEY_WARNINGS = "warnings"
|
||||
|
||||
|
||||
def get_data(key, default=None):
|
||||
@@ -33,10 +40,37 @@ def get_data(key, default=None):
|
||||
:return:
|
||||
"""
|
||||
return CORE.data.setdefault(DOMAIN, {}).setdefault(
|
||||
key, default if default is not None else {}
|
||||
key, {} if default is None else default
|
||||
)
|
||||
|
||||
|
||||
def get_warnings():
|
||||
return get_data(KEY_WARNINGS, set())
|
||||
|
||||
|
||||
def get_remapped_uses():
|
||||
return get_data(KEY_REMAPPED_USES, set())
|
||||
|
||||
|
||||
def get_color_formats():
|
||||
return get_data(KEY_COLOR_FORMATS, set())
|
||||
|
||||
|
||||
def add_warning(msg: str):
|
||||
get_warnings().add(msg)
|
||||
|
||||
|
||||
class StaticCastExpression(Expression):
|
||||
__slots__ = ("type", "exp")
|
||||
|
||||
def __init__(self, type: Any, exp: SafeExpType):
|
||||
self.type = str(type)
|
||||
self.exp = cg.safe_exp(exp)
|
||||
|
||||
def __str__(self):
|
||||
return f"static_cast<{self.type}>({self.exp})"
|
||||
|
||||
|
||||
def add_define(macro, value="1"):
|
||||
lv_defines = get_data(KEY_LV_DEFINES)
|
||||
value = str(value)
|
||||
@@ -47,27 +81,43 @@ def add_define(macro, value="1"):
|
||||
lv_defines[macro] = value
|
||||
|
||||
|
||||
def is_defined(macro):
|
||||
return macro in get_data(KEY_LV_DEFINES)
|
||||
|
||||
|
||||
def literal(arg) -> MockObj:
|
||||
if isinstance(arg, str):
|
||||
return MockObj(arg)
|
||||
return arg
|
||||
|
||||
|
||||
def static_cast(type, value):
|
||||
return literal(f"static_cast<{type}>({value})")
|
||||
def addr(arg) -> MockObj:
|
||||
return MockObj(f"&{arg}")
|
||||
|
||||
|
||||
def call_lambda(lamb: LambdaExpression):
|
||||
"""
|
||||
Given a lambda, either reduce to a simple expression or call it, possibly with parameters
|
||||
from the surrounding context
|
||||
:param lamb:
|
||||
:return:
|
||||
"""
|
||||
expr = lamb.content.strip()
|
||||
if expr.startswith("return") and expr.endswith(";"):
|
||||
return expr[6:-1].strip()
|
||||
# If lambda has parameters, call it with those parameter names
|
||||
# Convert a lambda returning a simple expression to just that expression
|
||||
expr = cg.RawExpression(expr[6:-1].strip())
|
||||
# Don't cast if the return type is a class
|
||||
if isinstance(lamb.return_type, MockObjClass):
|
||||
return expr
|
||||
return StaticCastExpression(lamb.return_type, expr)
|
||||
# If lambda has parameters, call it with their names
|
||||
# Parameter names come from hardcoded component code (like "x", "it", "event")
|
||||
# not from user input, so they're safe to use directly
|
||||
if lamb.parameters and lamb.parameters.parameters:
|
||||
param_names = ", ".join(str(param.id) for param in lamb.parameters.parameters)
|
||||
return f"{lamb}({param_names})"
|
||||
return f"{lamb}()"
|
||||
return CallExpression(
|
||||
lamb, *[MockObj(x.id) for x in lamb.parameters.parameters]
|
||||
)
|
||||
return CallExpression(lamb)
|
||||
|
||||
|
||||
class LValidator:
|
||||
@@ -76,7 +126,7 @@ class LValidator:
|
||||
has `process()` to convert a value during code generation
|
||||
"""
|
||||
|
||||
def __init__(self, validator, rtype, retmapper=None, requires=None):
|
||||
def __init__(self, validator, rtype: MockObj, retmapper=None, requires=None):
|
||||
self.validator = validator
|
||||
self.rtype = rtype
|
||||
self.retmapper = retmapper
|
||||
@@ -99,10 +149,9 @@ class LValidator:
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
args = args or get_lambda_context_args()
|
||||
return cg.RawExpression(
|
||||
call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
)
|
||||
|
||||
return call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
)
|
||||
if self.retmapper is not None:
|
||||
return self.retmapper(value)
|
||||
@@ -112,6 +161,8 @@ class LValidator:
|
||||
value = [
|
||||
await cg.get_variable(x) if isinstance(x, ID) else x for x in value
|
||||
]
|
||||
if self.rtype is cg.int_:
|
||||
value = int(value)
|
||||
return cg.safe_exp(value)
|
||||
|
||||
|
||||
@@ -122,10 +173,11 @@ class LvConstant(LValidator):
|
||||
The property `one_of` has the single case validator, and `several_of` allows a list of constants.
|
||||
"""
|
||||
|
||||
def __init__(self, prefix: str, *choices):
|
||||
def __init__(self, prefix: str, *choices, typename=None):
|
||||
self.prefix = prefix
|
||||
self.choices = choices
|
||||
prefixed_choices = [prefix + v for v in choices]
|
||||
self.choices = tuple(x.upper() for x in choices)
|
||||
self.typename = typename or prefix.lower() + "t"
|
||||
prefixed_choices = [prefix + v.upper() for v in choices]
|
||||
prefixed_validator = cv.one_of(*prefixed_choices, upper=True)
|
||||
|
||||
@schema_extractor("one_of")
|
||||
@@ -136,24 +188,30 @@ class LvConstant(LValidator):
|
||||
return prefixed_validator(value)
|
||||
return self.prefix + cv.one_of(*choices, upper=True)(value)
|
||||
|
||||
super().__init__(validator, rtype=uint32)
|
||||
super().__init__(validator, rtype=cg.uint32)
|
||||
self.retmapper = self.mapper
|
||||
self.one_of = LValidator(validator, uint32, retmapper=self.mapper)
|
||||
self.one_of = LValidator(validator, cg.uint32, retmapper=self.mapper)
|
||||
self.several_of = LValidator(
|
||||
cv.ensure_list(self.one_of), uint32, retmapper=self.mapper
|
||||
cv.ensure_list(self.one_of), cg.uint32, retmapper=self.mapper
|
||||
)
|
||||
|
||||
def mapper(self, value):
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
return literal(
|
||||
"|".join(
|
||||
[
|
||||
str(v) if str(v).startswith(self.prefix) else self.prefix + str(v)
|
||||
for v in value
|
||||
]
|
||||
).upper()
|
||||
)
|
||||
value = [
|
||||
(
|
||||
str(v).upper()
|
||||
if str(v).startswith(self.prefix)
|
||||
else self.prefix + str(v).upper()
|
||||
)
|
||||
for v in value
|
||||
]
|
||||
if len(value) == 1:
|
||||
return literal(value[0])
|
||||
value = literal("|".join(value))
|
||||
if self.typename is None:
|
||||
return value
|
||||
return StaticCastExpression(self.typename, value)
|
||||
|
||||
def extend(self, *choices):
|
||||
"""
|
||||
@@ -161,7 +219,14 @@ class LvConstant(LValidator):
|
||||
:param choices: The extra choices
|
||||
:return: A new LVConstant instance
|
||||
"""
|
||||
return LvConstant(self.prefix, *(self.choices + choices))
|
||||
return LvConstant(
|
||||
self.prefix, *(self.choices + choices), typename=self.typename
|
||||
)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.upper() not in self.choices:
|
||||
raise AttributeError(f"{item} not one of {self.choices}")
|
||||
return self.mapper(item)
|
||||
|
||||
|
||||
# Parts
|
||||
@@ -215,7 +280,7 @@ SWIPE_TRIGGERS = tuple(
|
||||
|
||||
|
||||
LV_ANIM = LvConstant(
|
||||
"LV_SCR_LOAD_ANIM_",
|
||||
"LV_SCREEN_LOAD_ANIM_",
|
||||
"NONE",
|
||||
"OVER_LEFT",
|
||||
"OVER_RIGHT",
|
||||
@@ -277,11 +342,13 @@ PARTS = (
|
||||
CONF_KNOB,
|
||||
CONF_SELECTED,
|
||||
CONF_ITEMS,
|
||||
CONF_TICKS,
|
||||
# CONF_TICKS,
|
||||
CONF_CURSOR,
|
||||
CONF_TEXTAREA_PLACEHOLDER,
|
||||
)
|
||||
|
||||
LV_PART = LvConstant("LV_PART_", *(p.upper() for p in PARTS))
|
||||
|
||||
KEYBOARD_MODES = LvConstant(
|
||||
"LV_KEYBOARD_MODE_",
|
||||
"TEXT_LOWER",
|
||||
@@ -359,6 +426,7 @@ OBJ_FLAGS = (
|
||||
"overflow_visible",
|
||||
"layout_1",
|
||||
"layout_2",
|
||||
"send_draw_task_events",
|
||||
"widget_1",
|
||||
"widget_2",
|
||||
"user_1",
|
||||
@@ -366,12 +434,14 @@ OBJ_FLAGS = (
|
||||
"user_3",
|
||||
"user_4",
|
||||
)
|
||||
LV_OBJ_FLAG = LvConstant("LV_OBJ_FLAG_", *OBJ_FLAGS)
|
||||
|
||||
ARC_MODES = LvConstant("LV_ARC_MODE_", "NORMAL", "REVERSE", "SYMMETRICAL")
|
||||
BAR_MODES = LvConstant("LV_BAR_MODE_", "NORMAL", "SYMMETRICAL", "RANGE")
|
||||
SLIDER_MODES = LvConstant("LV_SLIDER_MODE_", "NORMAL", "SYMMETRICAL", "RANGE")
|
||||
|
||||
BUTTONMATRIX_CTRLS = LvConstant(
|
||||
"LV_BTNMATRIX_CTRL_",
|
||||
"LV_BUTTONMATRIX_CTRL_",
|
||||
"HIDDEN",
|
||||
"NO_REPEAT",
|
||||
"DISABLED",
|
||||
@@ -434,12 +504,16 @@ CONF_ACCEPTED_CHARS = "accepted_chars"
|
||||
CONF_ADJUSTABLE = "adjustable"
|
||||
CONF_ALIGN = "align"
|
||||
CONF_ALIGN_TO = "align_to"
|
||||
CONF_ANGLE_RANGE = "angle_range"
|
||||
CONF_ANIMATED = "animated"
|
||||
CONF_ANIMATION = "animation"
|
||||
CONF_ANIMATIONS = "animations"
|
||||
CONF_ANTIALIAS = "antialias"
|
||||
CONF_ARC_LENGTH = "arc_length"
|
||||
CONF_AUTO_START = "auto_start"
|
||||
CONF_BACKGROUND_STYLE = "background_style"
|
||||
CONF_BG_OPA = "bg_opa"
|
||||
CONF_BOTTOM_LAYER = "bottom_layer"
|
||||
CONF_BUTTON_STYLE = "button_style"
|
||||
CONF_DECIMAL_PLACES = "decimal_places"
|
||||
CONF_COLUMN = "column"
|
||||
@@ -449,9 +523,11 @@ CONF_DISP_BG_IMAGE = "disp_bg_image"
|
||||
CONF_DISP_BG_OPA = "disp_bg_opa"
|
||||
CONF_BODY = "body"
|
||||
CONF_BUTTONS = "buttons"
|
||||
CONF_BYTE_ORDER = "byte_order"
|
||||
CONF_CHANGE_RATE = "change_rate"
|
||||
CONF_CLOSE_BUTTON = "close_button"
|
||||
CONF_COLOR_DEPTH = "color_depth"
|
||||
CONF_COLOR_END = "color_end"
|
||||
CONF_COLOR_START = "color_start"
|
||||
CONF_CONTAINER = "container"
|
||||
CONF_CONTROL = "control"
|
||||
CONF_DEFAULT_FONT = "default_font"
|
||||
@@ -483,8 +559,10 @@ CONF_GRID_COLUMN_ALIGN = "grid_column_align"
|
||||
CONF_GRID_COLUMNS = "grid_columns"
|
||||
CONF_GRID_ROW_ALIGN = "grid_row_align"
|
||||
CONF_GRID_ROWS = "grid_rows"
|
||||
CONF_HEADER_BUTTONS = "header_buttons"
|
||||
CONF_HEADER_MODE = "header_mode"
|
||||
CONF_HOME = "home"
|
||||
CONF_INDICATORS = "indicators"
|
||||
CONF_INITIAL_FOCUS = "initial_focus"
|
||||
CONF_SELECTED_DIGIT = "selected_digit"
|
||||
CONF_KEY_CODE = "key_code"
|
||||
@@ -496,6 +574,7 @@ CONF_LONG_PRESS_TIME = "long_press_time"
|
||||
CONF_LONG_PRESS_REPEAT_TIME = "long_press_repeat_time"
|
||||
CONF_LVGL_ID = "lvgl_id"
|
||||
CONF_LONG_MODE = "long_mode"
|
||||
CONF_MAJOR_TICKS_STYLE = "major_ticks_style"
|
||||
CONF_MSGBOXES = "msgboxes"
|
||||
CONF_OBJ = "obj"
|
||||
CONF_ONE_CHECKED = "one_checked"
|
||||
@@ -517,12 +596,15 @@ CONF_PIVOT_Y = "pivot_y"
|
||||
CONF_PLACEHOLDER_TEXT = "placeholder_text"
|
||||
CONF_POINTS = "points"
|
||||
CONF_PREVIOUS = "previous"
|
||||
CONF_RADIUS = "radius"
|
||||
CONF_REPEAT_COUNT = "repeat_count"
|
||||
CONF_RECOLOR = "recolor"
|
||||
CONF_RESUME_ON_INPUT = "resume_on_input"
|
||||
CONF_RIGHT_BUTTON = "right_button"
|
||||
CONF_ROLLOVER = "rollover"
|
||||
CONF_ROOT_BACK_BTN = "root_back_btn"
|
||||
CONF_ROWS = "rows"
|
||||
CONF_SCALE = "scale"
|
||||
CONF_SCALE_LINES = "scale_lines"
|
||||
CONF_SCROLLBAR_MODE = "scrollbar_mode"
|
||||
CONF_SCROLL_DIR = "scroll_dir"
|
||||
@@ -536,6 +618,7 @@ CONF_SRC = "src"
|
||||
CONF_START_ANGLE = "start_angle"
|
||||
CONF_START_VALUE = "start_value"
|
||||
CONF_STATES = "states"
|
||||
CONF_STRIDE = "stride"
|
||||
CONF_STYLE = "style"
|
||||
CONF_STYLES = "styles"
|
||||
CONF_STYLE_DEFINITIONS = "style_definitions"
|
||||
@@ -544,6 +627,7 @@ CONF_SKIP = "skip"
|
||||
CONF_SYMBOL = "symbol"
|
||||
CONF_TAB_ID = "tab_id"
|
||||
CONF_TABS = "tabs"
|
||||
CONF_TICK_STYLE = "tick_style"
|
||||
CONF_TIME_FORMAT = "time_format"
|
||||
CONF_TILE = "tile"
|
||||
CONF_TILE_ID = "tile_id"
|
||||
@@ -551,6 +635,8 @@ CONF_TILES = "tiles"
|
||||
CONF_TITLE = "title"
|
||||
CONF_TOP_LAYER = "top_layer"
|
||||
CONF_TOUCHSCREENS = "touchscreens"
|
||||
CONF_TRANSFORM_ROTATION = "transform_rotation"
|
||||
CONF_TRANSFORM_SCALE = "transform_scale"
|
||||
CONF_TRANSPARENCY_KEY = "transparency_key"
|
||||
CONF_THEME = "theme"
|
||||
CONF_UPDATE_ON_RELEASE = "update_on_release"
|
||||
@@ -578,6 +664,16 @@ LV_KEYS = LvConstant(
|
||||
"END",
|
||||
)
|
||||
|
||||
LV_SCALE_MODE = LvConstant(
|
||||
"LV_SCALE_MODE_",
|
||||
"HORIZONTAL_TOP",
|
||||
"HORIZONTAL_BOTTOM",
|
||||
"VERTICAL_LEFT",
|
||||
"VERTICAL_RIGHT",
|
||||
"ROUND_INNER",
|
||||
"ROUND_OUTER",
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_ESPHOME_FONT = "esphome_lv_default_font"
|
||||
|
||||
@@ -590,3 +686,29 @@ def join_enums(enums, prefix=""):
|
||||
if prefix:
|
||||
return literal("|".join(f"{prefix}{e.upper()}" for e in enums))
|
||||
return literal("|".join(f"(int){e.upper()}" for e in enums))
|
||||
|
||||
|
||||
# fmt: off
|
||||
LV_COLOR_FORMATS = (
|
||||
"RGB565", "SWAPPED", "RGB565A8", "RGB888", "XRGB8888", "ARGB8888", "PREMULTIPLIED", "L8", "AL88", "A8", "I1",
|
||||
)
|
||||
|
||||
LV_DEFINES = (
|
||||
"LV_USE_FREERTOS_TASK_NOTIFY", "LV_DRAW_BUF_STRIDE_ALIGN", "LV_USE_DRAW_SW", "LV_DRAW_SW_DRAW_UNIT_CNT",
|
||||
"LV_DRAW_SW_COMPLEX", "LV_USE_DRAW_PXP", "LV_USE_PXP_DRAW_THREAD", "LV_USE_DRAW_G2D",
|
||||
"LV_USE_G2D_DRAW_THREAD", "LV_VG_LITE_USE_BOX_SHADOW", "LV_VG_LITE_THORVG_16PIXELS_ALIGN", "LV_LOG_USE_TIMESTAMP",
|
||||
"LV_LOG_USE_FILE_LINE", "LV_USE_OBJ_ID_BUILTIN", "LV_USE_OBJ_PROPERTY_NAME", "LV_ATTRIBUTE_MEM_ALIGN_SIZE",
|
||||
"LV_FONT_MONTSERRAT_14", "LV_USE_FONT_PLACEHOLDER", "LV_WIDGETS_HAS_DEFAULT_VALUE", "LV_USE_ARCLABEL",
|
||||
"LV_USE_CALENDAR", "LV_USE_CALENDAR_HEADER_ARROW", "LV_USE_CALENDAR_HEADER_DROPDOWN", "LV_USE_CHART",
|
||||
"LV_USE_LIST", "LV_USE_MENU", "LV_USE_MSGBOX", "LV_USE_SCALE",
|
||||
"LV_USE_TABLE", "LV_USE_SPAN", "LV_USE_WIN", "LV_USE_THEME_DEFAULT",
|
||||
"LV_THEME_DEFAULT_GROW", "LV_USE_THEME_SIMPLE", "LV_USE_THEME_MONO", "LV_USE_FLEX",
|
||||
"LV_USE_GRID", "LV_USE_PROFILER_BUILTIN", "LV_PROFILER_BUILTIN_DEFAULT_ENABLE", "LV_PROFILER_LAYOUT",
|
||||
"LV_PROFILER_REFR", "LV_PROFILER_DRAW", "LV_PROFILER_INDEV", "LV_PROFILER_DECODER",
|
||||
"LV_PROFILER_FONT", "LV_PROFILER_FS", "LV_PROFILER_TIMER", "LV_PROFILER_CACHE",
|
||||
"LV_PROFILER_EVENT", "LV_USE_OBSERVER", "LV_IME_PINYIN_USE_DEFAULT_DICT", "LV_IME_PINYIN_USE_K9_MODE",
|
||||
"LV_FILE_EXPLORER_QUICK_ACCESS", "LV_TEST_SCREENSHOT_CREATE_REFERENCE_IMAGE", "LV_LINUX_FBDEV_MMAP",
|
||||
"LV_USE_NUTTX_MOUSE_MOVE_STEP", "LV_USE_GENERIC_MIPI", "LV_BUILD_EXAMPLES", "LV_BUILD_DEMOS",
|
||||
"LV_WAYLAND_USE_EGL", "LV_WAYLAND_USE_G2D", "LV_WAYLAND_USE_SHM", "LV_LINUX_DRM_USE_EGL",
|
||||
"LV_USE_LZ4", "LV_USE_THORVG", "LV_SDL_USE_EGL", "LV_USE_EGL", "LV_LABEL_LONG_TXT_HINT", "LV_LABEL_TEXT_SELECTION",
|
||||
) + tuple(f"LV_DRAW_SW_SUPPORT_{f}" for f in LV_COLOR_FORMATS)
|
||||
|
||||
@@ -71,7 +71,7 @@ async def encoders_to_code(var, config, default_group):
|
||||
lv_assign(group, lv_expr.group_create())
|
||||
else:
|
||||
group = default_group
|
||||
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)
|
||||
lv.indev_set_group(listener.get_drv(), group)
|
||||
|
||||
|
||||
async def initial_focus_to_code(config):
|
||||
|
||||
@@ -7,12 +7,13 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_POSITION,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from .defines import CONF_GRADIENTS, LV_DITHER, LV_GRAD_DIR, add_define
|
||||
from .lv_validation import lv_color, lv_fraction
|
||||
from .lvcode import lv_assign
|
||||
from .types import lv_gradient_t
|
||||
from .defines import CONF_GRADIENTS, CONF_OPA, LV_DITHER, add_define, add_warning
|
||||
from .lv_validation import lv_color, lv_percentage, opacity
|
||||
from .lvcode import lv
|
||||
from .types import lv_color_t, lv_gradient_t, lv_opa_t
|
||||
|
||||
CONF_STOPS = "stops"
|
||||
|
||||
@@ -27,14 +28,17 @@ GRADIENT_SCHEMA = cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(lv_gradient_t),
|
||||
cv.Optional(CONF_DIRECTION, default="NONE"): LV_GRAD_DIR.one_of,
|
||||
cv.Required(CONF_DIRECTION): cv.one_of(
|
||||
"HOR", "HORIZONTAL", "VER", "VERTICAL", upper=True
|
||||
),
|
||||
cv.Optional(CONF_DITHER, default="NONE"): LV_DITHER.one_of,
|
||||
cv.Required(CONF_STOPS): cv.All(
|
||||
[
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_COLOR): lv_color,
|
||||
cv.Required(CONF_POSITION): lv_fraction,
|
||||
cv.Optional(CONF_OPA, default=1.0): opacity,
|
||||
cv.Required(CONF_POSITION): lv_percentage,
|
||||
}
|
||||
)
|
||||
],
|
||||
@@ -47,15 +51,31 @@ GRADIENT_SCHEMA = cv.ensure_list(
|
||||
|
||||
async def gradients_to_code(config):
|
||||
max_stops = 2
|
||||
if any(CONF_DITHER in x for x in config.get(CONF_GRADIENTS, ())):
|
||||
add_warning(
|
||||
"The 'dither' option for gradients is not supported by LVGL 9.x and will be ignored"
|
||||
)
|
||||
for gradient in config.get(CONF_GRADIENTS, ()):
|
||||
var = MockObj(cg.new_Pvariable(gradient[CONF_ID]), "->")
|
||||
max_stops = max(max_stops, len(gradient[CONF_STOPS]))
|
||||
lv_assign(var.dir, await LV_GRAD_DIR.process(gradient[CONF_DIRECTION]))
|
||||
lv_assign(var.dither, await LV_DITHER.process(gradient[CONF_DITHER]))
|
||||
lv_assign(var.stops_count, len(gradient[CONF_STOPS]))
|
||||
for index, stop in enumerate(gradient[CONF_STOPS]):
|
||||
lv_assign(var.stops[index].color, await lv_color.process(stop[CONF_COLOR]))
|
||||
lv_assign(
|
||||
var.stops[index].frac, await lv_fraction.process(stop[CONF_POSITION])
|
||||
)
|
||||
idbase = gradient[CONF_ID].id
|
||||
stops = gradient[CONF_STOPS]
|
||||
max_stops = max(max_stops, len(stops))
|
||||
if gradient[CONF_DIRECTION].startswith("VER"):
|
||||
lv.grad_vertical_init(var)
|
||||
else:
|
||||
lv.grad_horizontal_init(var)
|
||||
stop_colors = cg.static_const_array(
|
||||
ID(idbase + "_colors_", type=lv_color_t),
|
||||
[await lv_color.process(x[CONF_COLOR]) for x in stops],
|
||||
)
|
||||
stop_opacities = cg.static_const_array(
|
||||
ID(idbase + "_opacities_", type=lv_opa_t),
|
||||
[await opacity.process(x[CONF_OPA]) for x in stops],
|
||||
)
|
||||
stop_positions = cg.static_const_array(
|
||||
ID(idbase + "_positions_", type=cg.uint8),
|
||||
[await lv_percentage.process(x[CONF_POSITION]) for x in stops],
|
||||
)
|
||||
lv.grad_init_stops(var, stop_colors, stop_opacities, stop_positions, len(stops))
|
||||
|
||||
add_define("LV_GRADIENT_MAX_STOPS", max_stops)
|
||||
|
||||
@@ -67,7 +67,7 @@ async def keypads_to_code(var, config, default_group):
|
||||
lv_assign(group, lv_expr.group_create())
|
||||
else:
|
||||
group = default_group
|
||||
lv.indev_set_group(lv_expr.indev_drv_register(listener.get_drv()), group)
|
||||
lv.indev_set_group(listener.get_drv(), group)
|
||||
|
||||
|
||||
async def initial_focus_to_code(config):
|
||||
|
||||
@@ -88,8 +88,8 @@ grid_spec = cv.Any(size, LvConstant("LV_GRID_", "CONTENT").one_of, grid_free_spa
|
||||
GRID_CELL_SCHEMA = {
|
||||
cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_ROW_SPAN): cv.int_range(min=1),
|
||||
cv.Optional(CONF_GRID_CELL_COLUMN_SPAN): cv.int_range(min=1),
|
||||
cv.Optional(CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
||||
cv.Optional(CONF_GRID_CELL_Y_ALIGN): grid_alignments,
|
||||
}
|
||||
@@ -198,12 +198,8 @@ class GridLayout(Layout):
|
||||
{
|
||||
cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
||||
cv.Optional(
|
||||
CONF_GRID_CELL_ROW_SPAN, default=1
|
||||
): cv.positive_int,
|
||||
cv.Optional(
|
||||
CONF_GRID_CELL_COLUMN_SPAN, default=1
|
||||
): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_ROW_SPAN): cv.int_range(min=1),
|
||||
cv.Optional(CONF_GRID_CELL_COLUMN_SPAN): cv.int_range(min=1),
|
||||
cv.Optional(
|
||||
CONF_GRID_CELL_X_ALIGN, default="center"
|
||||
): grid_alignments,
|
||||
@@ -231,8 +227,8 @@ class GridLayout(Layout):
|
||||
{
|
||||
cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int,
|
||||
cv.Optional(CONF_GRID_CELL_ROW_SPAN): cv.int_range(min=1),
|
||||
cv.Optional(CONF_GRID_CELL_COLUMN_SPAN): cv.int_range(min=1),
|
||||
cv.Optional(CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
||||
cv.Optional(CONF_GRID_CELL_Y_ALIGN): grid_alignments,
|
||||
},
|
||||
@@ -299,11 +295,13 @@ class GridLayout(Layout):
|
||||
w[CONF_GRID_CELL_ROW_POS] = row
|
||||
w[CONF_GRID_CELL_COLUMN_POS] = column
|
||||
|
||||
for i in range(w[CONF_GRID_CELL_ROW_SPAN]):
|
||||
for j in range(w[CONF_GRID_CELL_COLUMN_SPAN]):
|
||||
row_span = w.get(CONF_GRID_CELL_ROW_SPAN, 1)
|
||||
column_span = w.get(CONF_GRID_CELL_COLUMN_SPAN, 1)
|
||||
for i in range(row_span):
|
||||
for j in range(column_span):
|
||||
if row + i >= rows or column + j >= columns:
|
||||
raise cv.Invalid(
|
||||
f"Cell at {row}/{column} span {w[CONF_GRID_CELL_ROW_SPAN]}x{w[CONF_GRID_CELL_COLUMN_SPAN]} "
|
||||
f"Cell at {row}/{column} span {row_span}x{column_span} "
|
||||
f"exceeds grid size {rows}x{columns}",
|
||||
[CONF_WIDGETS, index],
|
||||
)
|
||||
|
||||
@@ -38,7 +38,7 @@ class LVLight : public light::LightOutput {
|
||||
void set_value_(lv_color_t value) {
|
||||
lv_led_set_color(this->obj_, value);
|
||||
lv_led_on(this->obj_);
|
||||
lv_event_send(this->obj_, lv_api_event, nullptr);
|
||||
lv_obj_send_event(this->obj_, lv_api_event, nullptr);
|
||||
}
|
||||
lv_obj_t *obj_{};
|
||||
optional<lv_color_t> initial_value_{};
|
||||
|
||||
@@ -30,6 +30,7 @@ from .defines import (
|
||||
LV_FONTS,
|
||||
LValidator,
|
||||
LvConstant,
|
||||
StaticCastExpression,
|
||||
call_lambda,
|
||||
literal,
|
||||
)
|
||||
@@ -40,22 +41,28 @@ from .helpers import (
|
||||
lv_fonts_used,
|
||||
requires_component,
|
||||
)
|
||||
from .types import lv_gradient_t
|
||||
from .types import lv_gradient_t, lv_opa_t
|
||||
|
||||
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
||||
LV_OPA = LvConstant("LV_OPA_", "TRANSP", "COVER")
|
||||
|
||||
|
||||
@schema_extractor("one_of")
|
||||
def opacity_validator(value):
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return opacity_consts.choices
|
||||
value = cv.Any(cv.percentage, opacity_consts.one_of)(value)
|
||||
if isinstance(value, float):
|
||||
return int(value * 255)
|
||||
return value
|
||||
return LV_OPA.choices
|
||||
value = cv.Any(cv.percentage, LV_OPA.one_of)(value)
|
||||
if value == str(LV_OPA.COVER):
|
||||
value = 1.0
|
||||
if value == str(LV_OPA.TRANSP):
|
||||
value = 0.0
|
||||
return cv.float_range(0.0, 1.0)(value)
|
||||
|
||||
|
||||
opacity = LValidator(opacity_validator, uint32, retmapper=literal)
|
||||
opacity = LValidator(
|
||||
opacity_validator,
|
||||
lv_opa_t,
|
||||
retmapper=lambda opa: StaticCastExpression(cg.uint8, opa * 255.0),
|
||||
)
|
||||
|
||||
COLOR_NAMES = {
|
||||
"aliceblue": 0xF0F8FF,
|
||||
@@ -244,7 +251,17 @@ def option_string(value):
|
||||
return value
|
||||
|
||||
|
||||
lv_color = LValidator(color, ty.lv_color_t, retmapper=color_retmapper)
|
||||
class LvColor(LValidator):
|
||||
def __init__(self):
|
||||
super().__init__(color, ty.lv_color_t, retmapper=color_retmapper)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item in COLOR_NAMES:
|
||||
return color_retmapper(COLOR_NAMES[item])
|
||||
raise AttributeError(item)
|
||||
|
||||
|
||||
lv_color = LvColor()
|
||||
|
||||
|
||||
def pixels_or_percent_validator(value):
|
||||
@@ -252,16 +269,17 @@ def pixels_or_percent_validator(value):
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return ["pixels", "..%"]
|
||||
if isinstance(value, str) and value.lower().endswith("px"):
|
||||
value = cv.int_(value[:-2])
|
||||
return cv.int_(value[:-2])
|
||||
if isinstance(value, str) and re.match(r"^lv_pct\((\d+)\)$", value):
|
||||
return value
|
||||
value = cv.Any(cv.int_, cv.percentage)(value)
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
return f"lv_pct({int(value * 100)})"
|
||||
return int(value[6:-1]) / 100.0
|
||||
return cv.Any(cv.int_, cv.possibly_negative_percentage)(value)
|
||||
|
||||
|
||||
pixels_or_percent = LValidator(pixels_or_percent_validator, uint32, retmapper=literal)
|
||||
pixels_or_percent = LValidator(
|
||||
pixels_or_percent_validator,
|
||||
uint32,
|
||||
retmapper=lambda x: x if isinstance(x, int) else literal(f"lv_pct({int(x * 100)})"),
|
||||
)
|
||||
|
||||
|
||||
def pixels_validator(value):
|
||||
@@ -282,15 +300,11 @@ def padding_validator(value):
|
||||
padding = LValidator(padding_validator, int32, retmapper=literal)
|
||||
|
||||
|
||||
def zoom_validator(value):
|
||||
def scale_validator(value):
|
||||
return cv.float_range(0.1, 10.0)(value)
|
||||
|
||||
|
||||
def zoom_retmapper(value):
|
||||
return int(value * 256)
|
||||
|
||||
|
||||
zoom = LValidator(zoom_validator, uint32, retmapper=zoom_retmapper)
|
||||
scale = LValidator(scale_validator, uint32, retmapper=lambda x: int(x * 256))
|
||||
|
||||
|
||||
def angle(value):
|
||||
@@ -321,17 +335,23 @@ def size_validator(value):
|
||||
return pixels_or_percent_validator(value)
|
||||
|
||||
|
||||
size = LValidator(size_validator, uint32, retmapper=literal)
|
||||
size = LValidator(
|
||||
size_validator,
|
||||
uint32,
|
||||
retmapper=lambda x: (
|
||||
literal(x) if isinstance(x, str) else pixels_or_percent.retmapper(x)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
radius_consts = LvConstant("LV_RADIUS_", "CIRCLE")
|
||||
LV_RADIUS = LvConstant("LV_RADIUS_", "CIRCLE")
|
||||
|
||||
|
||||
@schema_extractor("one_of")
|
||||
def fraction_validator(value):
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return radius_consts.choices
|
||||
value = cv.Any(size, cv.percentage, radius_consts.one_of)(value)
|
||||
return LV_RADIUS.choices
|
||||
value = cv.Any(size, cv.percentage, LV_RADIUS.one_of)(value)
|
||||
if isinstance(value, float):
|
||||
return int(value * 255)
|
||||
return value
|
||||
@@ -374,12 +394,6 @@ lv_image_list = LValidator(
|
||||
lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal)
|
||||
|
||||
|
||||
def lv_pct(value: int | float):
|
||||
if isinstance(value, float):
|
||||
value = int(value * 100)
|
||||
return literal(f"lv_pct({value})")
|
||||
|
||||
|
||||
def lvms_validator_(value):
|
||||
if value == "never":
|
||||
value = "2147483647ms"
|
||||
@@ -424,30 +438,28 @@ class TextValidator(LValidator):
|
||||
if time_format := value.get(CONF_TIME_FORMAT):
|
||||
source = value[CONF_TIME]
|
||||
if isinstance(source, Lambda):
|
||||
time_format = cpp_string_escape(time_format)
|
||||
return cg.RawExpression(
|
||||
source = MockObj(
|
||||
call_lambda(
|
||||
await cg.process_lambda(source, args, return_type=ESPTime)
|
||||
)
|
||||
+ f".strftime({time_format}).c_str()"
|
||||
)
|
||||
# must be an ID
|
||||
source = await cg.get_variable(source)
|
||||
return source.now().strftime(time_format).c_str()
|
||||
else:
|
||||
source = (await cg.get_variable(source)).now()
|
||||
return source.strftime(time_format).c_str()
|
||||
if isinstance(value, Lambda):
|
||||
value = call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
)
|
||||
textvalue = str(value)
|
||||
|
||||
# Was the lambda call reduced to a string?
|
||||
if value.endswith("c_str()") or (
|
||||
value.endswith('"') and value.startswith('"')
|
||||
if textvalue.endswith("c_str()") or (
|
||||
textvalue.endswith('"') and textvalue.startswith('"')
|
||||
):
|
||||
pass
|
||||
else:
|
||||
# Either a std::string or a lambda call returning that. We need const char*
|
||||
value = f"({value}).c_str()"
|
||||
return cg.RawExpression(value)
|
||||
return value
|
||||
# Either a std::string or a lambda call returning that. We need const char*
|
||||
return MockObj(f"({value}).c_str()")
|
||||
return await super().process(value, args)
|
||||
|
||||
|
||||
@@ -455,21 +467,24 @@ lv_text = TextValidator()
|
||||
lv_float = LValidator(cv.float_, cg.float_)
|
||||
lv_int = LValidator(cv.int_, cg.int_)
|
||||
lv_positive_int = LValidator(cv.positive_int, cg.int_)
|
||||
lv_brightness = LValidator(cv.percentage, cg.float_, retmapper=lambda x: int(x * 255))
|
||||
|
||||
|
||||
def gradient_mapper(value):
|
||||
return MockObj(value)
|
||||
def _percentage_validator(value):
|
||||
value = cv.Any(cv.percentage, cv.float_range(0.0, 1.0), cv.int_range(0, 255))(value)
|
||||
if isinstance(value, int):
|
||||
return value / 255.0
|
||||
return value
|
||||
|
||||
|
||||
def gradient_validator(value):
|
||||
return cv.use_id(lv_gradient_t)(value)
|
||||
lv_percentage = LValidator(
|
||||
_percentage_validator, cg.float_, retmapper=lambda x: int(x * 255)
|
||||
)
|
||||
|
||||
|
||||
lv_gradient = LValidator(
|
||||
validator=gradient_validator,
|
||||
validator=cv.use_id(lv_gradient_t),
|
||||
rtype=lv_gradient_t,
|
||||
retmapper=gradient_mapper,
|
||||
retmapper=MockObj,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -168,6 +168,9 @@ class LambdaContext(CodeContext):
|
||||
def get_automation_parameters(self) -> list[tuple[SafeExpType, str]]:
|
||||
return self.parameters
|
||||
|
||||
def get_parameter(self, index: int):
|
||||
return literal(self.parameters[index][1])
|
||||
|
||||
async def __aenter__(self):
|
||||
await super().__aenter__()
|
||||
add_line_marks(self.where)
|
||||
@@ -250,10 +253,14 @@ class MockLv:
|
||||
A mock object that can be used to generate LVGL calls.
|
||||
"""
|
||||
|
||||
# Mapping for LVGL 9
|
||||
ATTR_MAP = {"event_send": "obj_send_event", "dither": "bg_dither_mode"}
|
||||
|
||||
def __init__(self, base):
|
||||
self.base = base
|
||||
|
||||
def __getattr__(self, attr: str) -> "MockLv":
|
||||
attr = MockLv.ATTR_MAP.get(attr, attr)
|
||||
return MockLv(f"{self.base}{attr}")
|
||||
|
||||
def append(self, expression):
|
||||
@@ -307,6 +314,7 @@ class ReturnStatement(ExpressionStatement):
|
||||
|
||||
class LvExpr(MockLv):
|
||||
def __getattr__(self, attr: str) -> "MockLv":
|
||||
attr = MockLv.ATTR_MAP.get(attr, attr)
|
||||
return LvExpr(f"{self.base}{attr}")
|
||||
|
||||
def append(self, expression):
|
||||
|
||||
@@ -2,16 +2,21 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "lvgl_hal.h"
|
||||
#include "lvgl_esphome.h"
|
||||
|
||||
#include "core/lv_global.h"
|
||||
#include "core/lv_obj_class_private.h"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
namespace esphome {
|
||||
namespace lvgl {
|
||||
static void *lv_alloc_draw_buf(size_t size, bool internal);
|
||||
static void *draw_buf_alloc_cb(size_t size, lv_color_format_t color_format) { return lv_alloc_draw_buf(size, false); };
|
||||
|
||||
namespace esphome::lvgl {
|
||||
static const char *const TAG = "lvgl";
|
||||
|
||||
static const size_t MIN_BUFFER_FRAC = 8;
|
||||
static const size_t MIN_BUFFER_FRAC = 8; // buffer must be at least 1/8 of the display size
|
||||
static const size_t MIN_BUFFER_SIZE = 2048; // Sensible minimum buffer size
|
||||
|
||||
static const char *const EVENT_NAMES[] = {
|
||||
"NONE",
|
||||
@@ -61,7 +66,14 @@ static const char *const EVENT_NAMES[] = {
|
||||
"GET_SELF_SIZE",
|
||||
};
|
||||
|
||||
std::string lv_event_code_name_for(uint8_t event_code) {
|
||||
static const unsigned LOG_LEVEL_MAP[] = {
|
||||
ESPHOME_LOG_LEVEL_DEBUG, ESPHOME_LOG_LEVEL_INFO, ESPHOME_LOG_LEVEL_WARN,
|
||||
ESPHOME_LOG_LEVEL_ERROR, ESPHOME_LOG_LEVEL_ERROR, ESPHOME_LOG_LEVEL_NONE,
|
||||
|
||||
};
|
||||
|
||||
std::string lv_event_code_name_for(lv_event_t *event) {
|
||||
auto event_code = lv_event_get_code(event);
|
||||
if (event_code < sizeof(EVENT_NAMES) / sizeof(EVENT_NAMES[0])) {
|
||||
return EVENT_NAMES[event_code];
|
||||
}
|
||||
@@ -71,11 +83,12 @@ std::string lv_event_code_name_for(uint8_t event_code) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
|
||||
static void rounder_cb(lv_event_t *event) {
|
||||
auto *comp = static_cast<LvglComponent *>(lv_event_get_user_data(event));
|
||||
auto *area = static_cast<lv_area_t *>(lv_event_get_param(event));
|
||||
// cater for display driver chips with special requirements for bounds of partial
|
||||
// draw areas. Extend the draw area to satisfy:
|
||||
// * Coordinates must be a multiple of draw_rounding
|
||||
auto *comp = static_cast<LvglComponent *>(disp_drv->user_data);
|
||||
auto draw_rounding = comp->draw_rounding;
|
||||
// round down the start coordinates
|
||||
area->x1 = area->x1 / draw_rounding * draw_rounding;
|
||||
@@ -85,15 +98,14 @@ static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {
|
||||
area->y2 = (area->y2 + draw_rounding) / draw_rounding * draw_rounding - 1;
|
||||
}
|
||||
|
||||
void LvglComponent::monitor_cb(lv_disp_drv_t *disp_drv, uint32_t time, uint32_t px) {
|
||||
ESP_LOGVV(TAG, "Draw end: %" PRIu32 " pixels in %" PRIu32 " ms", px, time);
|
||||
auto *comp = static_cast<LvglComponent *>(disp_drv->user_data);
|
||||
void LvglComponent::render_end_cb(lv_event_t *event) {
|
||||
auto *comp = static_cast<LvglComponent *>(lv_event_get_user_data(event));
|
||||
comp->draw_end_();
|
||||
}
|
||||
|
||||
void LvglComponent::render_start_cb(lv_disp_drv_t *disp_drv) {
|
||||
void LvglComponent::render_start_cb(lv_event_t *event) {
|
||||
ESP_LOGVV(TAG, "Draw start");
|
||||
auto *comp = static_cast<LvglComponent *>(disp_drv->user_data);
|
||||
auto *comp = static_cast<LvglComponent *>(lv_event_get_user_data(event));
|
||||
comp->draw_start_();
|
||||
}
|
||||
|
||||
@@ -106,16 +118,15 @@ void LvglComponent::dump_config() {
|
||||
" Buffer size: %zu%%\n"
|
||||
" Rotation: %d\n"
|
||||
" Draw rounding: %d",
|
||||
this->disp_drv_.hor_res, this->disp_drv_.ver_res, 100 / this->buffer_frac_, this->rotation,
|
||||
(int) this->draw_rounding);
|
||||
this->width_, this->height_, 100 / this->buffer_frac_, this->rotation, (int) this->draw_rounding);
|
||||
}
|
||||
|
||||
void LvglComponent::set_paused(bool paused, bool show_snow) {
|
||||
this->paused_ = paused;
|
||||
this->show_snow_ = show_snow;
|
||||
if (!paused && lv_scr_act() != nullptr) {
|
||||
lv_disp_trig_activity(this->disp_); // resets the inactivity time
|
||||
lv_obj_invalidate(lv_scr_act());
|
||||
if (!paused && lv_screen_active() != nullptr) {
|
||||
lv_display_trigger_activity(this->disp_); // resets the inactivity time
|
||||
lv_obj_invalidate(lv_screen_active());
|
||||
}
|
||||
if (paused && this->pause_callback_ != nullptr)
|
||||
this->pause_callback_->trigger();
|
||||
@@ -125,6 +136,14 @@ void LvglComponent::set_paused(bool paused, bool show_snow) {
|
||||
|
||||
void LvglComponent::esphome_lvgl_init() {
|
||||
lv_init();
|
||||
// override draw buf alloc to ensure proper alignment for PPA
|
||||
LV_GLOBAL_DEFAULT()->draw_buf_handlers.buf_malloc_cb = draw_buf_alloc_cb;
|
||||
LV_GLOBAL_DEFAULT()->draw_buf_handlers.buf_free_cb = lv_free_core;
|
||||
LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers.buf_malloc_cb = draw_buf_alloc_cb;
|
||||
LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers.buf_free_cb = lv_free_core;
|
||||
LV_GLOBAL_DEFAULT()->font_draw_buf_handlers.buf_malloc_cb = draw_buf_alloc_cb;
|
||||
LV_GLOBAL_DEFAULT()->font_draw_buf_handlers.buf_free_cb = lv_free_core;
|
||||
lv_tick_set_cb([] { return millis(); });
|
||||
lv_update_event = static_cast<lv_event_code_t>(lv_event_register_id());
|
||||
lv_api_event = static_cast<lv_event_code_t>(lv_event_register_id());
|
||||
}
|
||||
@@ -149,7 +168,7 @@ void LvglComponent::add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_ev
|
||||
void LvglComponent::add_page(LvPageType *page) {
|
||||
this->pages_.push_back(page);
|
||||
page->set_parent(this);
|
||||
lv_disp_set_default(this->disp_);
|
||||
lv_display_set_default(this->disp_);
|
||||
page->setup(this->pages_.size() - 1);
|
||||
}
|
||||
|
||||
@@ -157,7 +176,11 @@ void LvglComponent::show_page(size_t index, lv_scr_load_anim_t anim, uint32_t ti
|
||||
if (index >= this->pages_.size())
|
||||
return;
|
||||
this->current_page_ = index;
|
||||
lv_scr_load_anim(this->pages_[this->current_page_]->obj, anim, time, 0, false);
|
||||
if (anim == LV_SCREEN_LOAD_ANIM_NONE) {
|
||||
lv_scr_load(this->pages_[this->current_page_]->obj);
|
||||
} else {
|
||||
lv_scr_load_anim(this->pages_[this->current_page_]->obj, anim, time, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
void LvglComponent::show_next_page(lv_scr_load_anim_t anim, uint32_t time) {
|
||||
@@ -187,13 +210,13 @@ void LvglComponent::show_prev_page(lv_scr_load_anim_t anim, uint32_t time) {
|
||||
size_t LvglComponent::get_current_page() const { return this->current_page_; }
|
||||
bool LvPageType::is_showing() const { return this->parent_->get_current_page() == this->index; }
|
||||
|
||||
void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
||||
void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_data *ptr) {
|
||||
auto width = lv_area_get_width(area);
|
||||
auto height = lv_area_get_height(area);
|
||||
auto height_rounded = (height + this->draw_rounding - 1) / this->draw_rounding * this->draw_rounding;
|
||||
auto x1 = area->x1;
|
||||
auto y1 = area->y1;
|
||||
lv_color_t *dst = this->rotate_buf_;
|
||||
lv_color_data *dst = reinterpret_cast<lv_color_data *>(this->rotate_buf_);
|
||||
switch (this->rotation) {
|
||||
case display::DISPLAY_ROTATION_90_DEGREES:
|
||||
for (lv_coord_t x = height; x-- != 0;) {
|
||||
@@ -202,7 +225,7 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
||||
}
|
||||
}
|
||||
y1 = x1;
|
||||
x1 = this->disp_drv_.ver_res - area->y1 - height;
|
||||
x1 = this->height_ - area->y1 - height;
|
||||
height = width;
|
||||
width = height_rounded;
|
||||
break;
|
||||
@@ -213,8 +236,8 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
||||
dst[y * width + x] = *ptr++;
|
||||
}
|
||||
}
|
||||
x1 = this->disp_drv_.hor_res - x1 - width;
|
||||
y1 = this->disp_drv_.ver_res - y1 - height;
|
||||
x1 = this->width_ - x1 - width;
|
||||
y1 = this->height_ - y1 - height;
|
||||
break;
|
||||
|
||||
case display::DISPLAY_ROTATION_270_DEGREES:
|
||||
@@ -224,7 +247,7 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
||||
}
|
||||
}
|
||||
x1 = y1;
|
||||
y1 = this->disp_drv_.hor_res - area->x1 - width;
|
||||
y1 = this->width_ - area->x1 - width;
|
||||
height = width;
|
||||
width = height_rounded;
|
||||
break;
|
||||
@@ -234,20 +257,19 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_t *ptr) {
|
||||
break;
|
||||
}
|
||||
for (auto *display : this->displays_) {
|
||||
ESP_LOGV(TAG, "draw buffer x1=%d, y1=%d, width=%d, height=%d", x1, y1, width, height);
|
||||
display->draw_pixels_at(x1, y1, width, height, (const uint8_t *) dst, display::COLOR_ORDER_RGB, LV_BITNESS,
|
||||
LV_COLOR_16_SWAP);
|
||||
this->big_endian_);
|
||||
}
|
||||
}
|
||||
|
||||
void LvglComponent::flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
|
||||
void LvglComponent::flush_cb_(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *color_p) {
|
||||
if (!this->is_paused()) {
|
||||
auto now = millis();
|
||||
this->draw_buffer_(area, color_p);
|
||||
ESP_LOGVV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area),
|
||||
lv_area_get_height(area), (int) (millis() - now));
|
||||
this->draw_buffer_(area, reinterpret_cast<lv_color_data *>(color_p));
|
||||
ESP_LOGV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", (int) area->x1, (int) area->y1,
|
||||
(int) lv_area_get_width(area), (int) lv_area_get_height(area), (int) (millis() - now));
|
||||
}
|
||||
lv_disp_flush_ready(disp_drv);
|
||||
lv_display_flush_ready(disp_drv);
|
||||
}
|
||||
|
||||
IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeout) : timeout_(std::move(timeout)) {
|
||||
@@ -264,14 +286,14 @@ IdleTrigger::IdleTrigger(LvglComponent *parent, TemplatableValue<uint32_t> timeo
|
||||
#ifdef USE_LVGL_TOUCHSCREEN
|
||||
LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_repeat_time, LvglComponent *parent) {
|
||||
this->set_parent(parent);
|
||||
lv_indev_drv_init(&this->drv_);
|
||||
this->drv_.disp = parent->get_disp();
|
||||
this->drv_.long_press_repeat_time = long_press_repeat_time;
|
||||
this->drv_.long_press_time = long_press_time;
|
||||
this->drv_.type = LV_INDEV_TYPE_POINTER;
|
||||
this->drv_.user_data = this;
|
||||
this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) {
|
||||
auto *l = static_cast<LVTouchListener *>(d->user_data);
|
||||
this->drv_ = lv_indev_create();
|
||||
lv_indev_set_type(this->drv_, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_disp(this->drv_, parent->get_disp());
|
||||
lv_indev_set_long_press_time(this->drv_, long_press_time);
|
||||
// long press repeat time TBD
|
||||
lv_indev_set_user_data(this->drv_, this);
|
||||
lv_indev_set_read_cb(this->drv_, [](lv_indev_t *d, lv_indev_data_t *data) {
|
||||
auto *l = static_cast<LVTouchListener *>(lv_indev_get_user_data(d));
|
||||
if (l->touch_pressed_) {
|
||||
data->point.x = l->touch_point_.x;
|
||||
data->point.y = l->touch_point_.y;
|
||||
@@ -279,7 +301,7 @@ LVTouchListener::LVTouchListener(uint16_t long_press_time, uint16_t long_press_r
|
||||
} else {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
void LVTouchListener::update(const touchscreen::TouchPoints_t &tpoints) {
|
||||
@@ -289,21 +311,78 @@ void LVTouchListener::update(const touchscreen::TouchPoints_t &tpoints) {
|
||||
}
|
||||
#endif // USE_LVGL_TOUCHSCREEN
|
||||
|
||||
#ifdef USE_LVGL_METER
|
||||
|
||||
int16_t lv_get_needle_angle_for_value(lv_obj_t *obj, int value) {
|
||||
auto *scale = lv_obj_get_parent(obj);
|
||||
auto min_value = lv_scale_get_range_min_value(scale);
|
||||
return ((value - min_value) * lv_scale_get_angle_range(scale) / (lv_scale_get_range_max_value(scale) - min_value) +
|
||||
lv_scale_get_rotation((scale))) %
|
||||
360;
|
||||
}
|
||||
|
||||
void IndicatorLine::set_obj(lv_obj_t *lv_obj) {
|
||||
LvCompound::set_obj(lv_obj);
|
||||
lv_line_set_points(lv_obj, this->points_, 2);
|
||||
lv_obj_add_event_cb(
|
||||
lv_obj_get_parent(obj),
|
||||
[](lv_event_t *e) {
|
||||
auto *indicator = static_cast<IndicatorLine *>(lv_event_get_user_data(e));
|
||||
indicator->update_length_();
|
||||
ESP_LOGV(TAG, "Updated length, value = %d", indicator->angle_);
|
||||
},
|
||||
LV_EVENT_SIZE_CHANGED, this);
|
||||
}
|
||||
|
||||
void IndicatorLine::set_value(int value) {
|
||||
auto angle = lv_get_needle_angle_for_value(this->obj, value);
|
||||
if (angle != this->angle_) {
|
||||
this->angle_ = angle;
|
||||
this->update_length_();
|
||||
}
|
||||
}
|
||||
|
||||
void IndicatorLine::update_length_() {
|
||||
uint32_t actual_needle_length;
|
||||
auto radius = lv_obj_get_width(lv_obj_get_parent(this->obj)) / 2;
|
||||
auto length = lv_obj_get_style_length(this->obj, LV_PART_MAIN);
|
||||
auto radial_offset = lv_obj_get_style_radial_offset(this->obj, LV_PART_MAIN);
|
||||
if (LV_COORD_IS_PCT(radial_offset)) {
|
||||
radial_offset = radius * LV_COORD_GET_PCT(radial_offset) / 100;
|
||||
}
|
||||
if (LV_COORD_IS_PCT(length)) {
|
||||
actual_needle_length = radius * LV_COORD_GET_PCT(length) / 100;
|
||||
} else if (length < 0) {
|
||||
actual_needle_length = radius + length;
|
||||
} else {
|
||||
actual_needle_length = length;
|
||||
}
|
||||
auto x = lv_trigo_cos(this->angle_) / 32768.0f;
|
||||
auto y = lv_trigo_sin(this->angle_) / 32768.0f;
|
||||
this->points_[0].x = radius + radial_offset * x;
|
||||
this->points_[0].y = radius + radial_offset * y;
|
||||
this->points_[1].x = x * actual_needle_length + radius;
|
||||
this->points_[1].y = y * actual_needle_length + radius;
|
||||
lv_obj_refresh_self_size(this->obj);
|
||||
lv_obj_invalidate(this->obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LVGL_KEY_LISTENER
|
||||
LVEncoderListener::LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt) {
|
||||
lv_indev_drv_init(&this->drv_);
|
||||
this->drv_.type = type;
|
||||
this->drv_.user_data = this;
|
||||
this->drv_.long_press_time = lpt;
|
||||
this->drv_.long_press_repeat_time = lprt;
|
||||
this->drv_.read_cb = [](lv_indev_drv_t *d, lv_indev_data_t *data) {
|
||||
auto *l = static_cast<LVEncoderListener *>(d->user_data);
|
||||
LVEncoderListener::LVEncoderListener(lv_indev_type_t type, uint16_t long_press_time, uint16_t long_press_repeat_time) {
|
||||
this->drv_ = lv_indev_create();
|
||||
lv_indev_set_type(this->drv_, type);
|
||||
lv_indev_set_long_press_time(this->drv_, long_press_time);
|
||||
lv_indev_set_long_press_repeat_time(this->drv_, long_press_repeat_time);
|
||||
lv_indev_set_user_data(this->drv_, this);
|
||||
lv_indev_set_read_cb(this->drv_, [](lv_indev_t *d, lv_indev_data_t *data) {
|
||||
auto *l = static_cast<LVEncoderListener *>(lv_indev_get_user_data(d));
|
||||
data->state = l->pressed_ ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
data->key = l->key_;
|
||||
data->enc_diff = (int16_t) (l->count_ - l->last_count_);
|
||||
l->last_count_ = l->count_;
|
||||
data->continue_reading = false;
|
||||
};
|
||||
});
|
||||
}
|
||||
#endif // USE_LVGL_KEY_LISTENER
|
||||
|
||||
@@ -325,7 +404,7 @@ void LvSelectable::set_selected_text(const std::string &text, lv_anim_enable_t a
|
||||
auto index = std::find(this->options_.begin(), this->options_.end(), text);
|
||||
if (index != this->options_.end()) {
|
||||
this->set_selected_index(index - this->options_.begin(), anim);
|
||||
lv_event_send(this->obj, lv_api_event, nullptr);
|
||||
lv_obj_send_event(this->obj, lv_api_event, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +414,7 @@ void LvSelectable::set_options(std::vector<std::string> options) {
|
||||
index = options.size() - 1;
|
||||
this->options_ = std::move(options);
|
||||
this->set_option_string(join_string(this->options_).c_str());
|
||||
lv_event_send(this->obj, LV_EVENT_REFRESH, nullptr);
|
||||
lv_obj_send_event(this->obj, LV_EVENT_REFRESH, nullptr);
|
||||
this->set_selected_index(index, LV_ANIM_OFF);
|
||||
}
|
||||
#endif // USE_LVGL_DROPDOWN || LV_USE_ROLLER
|
||||
@@ -346,17 +425,17 @@ void LvButtonMatrixType::set_obj(lv_obj_t *lv_obj) {
|
||||
lv_obj_add_event_cb(
|
||||
lv_obj,
|
||||
[](lv_event_t *event) {
|
||||
auto *self = static_cast<LvButtonMatrixType *>(event->user_data);
|
||||
auto *self = static_cast<LvButtonMatrixType *>(lv_event_get_user_data(event));
|
||||
if (self->key_callback_.size() == 0)
|
||||
return;
|
||||
auto key_idx = lv_btnmatrix_get_selected_btn(self->obj);
|
||||
if (key_idx == LV_BTNMATRIX_BTN_NONE)
|
||||
auto key_idx = lv_buttonmatrix_get_selected_button(self->obj);
|
||||
if (key_idx == LV_BUTTONMATRIX_BUTTON_NONE)
|
||||
return;
|
||||
if (self->key_map_.count(key_idx) != 0) {
|
||||
self->send_key_(self->key_map_[key_idx]);
|
||||
return;
|
||||
}
|
||||
const auto *str = lv_btnmatrix_get_btn_text(self->obj, key_idx);
|
||||
const auto *str = lv_buttonmatrix_get_button_text(self->obj, key_idx);
|
||||
auto len = strlen(str);
|
||||
while (len--)
|
||||
self->send_key_(*str++);
|
||||
@@ -376,14 +455,14 @@ void LvKeyboardType::set_obj(lv_obj_t *lv_obj) {
|
||||
lv_obj_add_event_cb(
|
||||
lv_obj,
|
||||
[](lv_event_t *event) {
|
||||
auto *self = static_cast<LvKeyboardType *>(event->user_data);
|
||||
auto *self = static_cast<LvKeyboardType *>(lv_event_get_user_data(event));
|
||||
if (self->key_callback_.size() == 0)
|
||||
return;
|
||||
|
||||
auto key_idx = lv_btnmatrix_get_selected_btn(self->obj);
|
||||
if (key_idx == LV_BTNMATRIX_BTN_NONE)
|
||||
auto key_idx = lv_buttonmatrix_get_selected_button(self->obj);
|
||||
if (key_idx == LV_BUTTONMATRIX_BUTTON_NONE)
|
||||
return;
|
||||
const char *txt = lv_btnmatrix_get_btn_text(self->obj, key_idx);
|
||||
const char *txt = lv_buttonmatrix_get_button_text(self->obj, key_idx);
|
||||
if (txt == nullptr)
|
||||
return;
|
||||
for (const auto *kb_special_key : KB_SPECIAL_KEYS) {
|
||||
@@ -419,33 +498,29 @@ bool LvglComponent::is_paused() const {
|
||||
}
|
||||
|
||||
void LvglComponent::write_random_() {
|
||||
int iterations = 6 - lv_disp_get_inactive_time(this->disp_) / 60000;
|
||||
int iterations = 6 - lv_display_get_inactive_time(this->disp_) / 60000;
|
||||
if (iterations <= 0)
|
||||
iterations = 1;
|
||||
while (iterations-- != 0) {
|
||||
auto col = random_uint32() % this->disp_drv_.hor_res;
|
||||
int32_t col = random_uint32() % this->width_;
|
||||
col = col / this->draw_rounding * this->draw_rounding;
|
||||
auto row = random_uint32() % this->disp_drv_.ver_res;
|
||||
int32_t row = random_uint32() % this->height_;
|
||||
row = row / this->draw_rounding * this->draw_rounding;
|
||||
auto size = ((random_uint32() % 32) / this->draw_rounding + 2) * this->draw_rounding - 1;
|
||||
// clamp size so the square fits within the draw buffer
|
||||
if ((size + 1) * (size + 1) > this->draw_buf_.size)
|
||||
size = static_cast<decltype(size)>(sqrtf(this->draw_buf_.size)) - 1;
|
||||
lv_area_t area;
|
||||
area.x1 = col;
|
||||
area.y1 = row;
|
||||
area.x2 = col + size;
|
||||
area.y2 = row + size;
|
||||
if (area.x2 >= this->disp_drv_.hor_res)
|
||||
area.x2 = this->disp_drv_.hor_res - 1;
|
||||
if (area.y2 >= this->disp_drv_.ver_res)
|
||||
area.y2 = this->disp_drv_.ver_res - 1;
|
||||
// size will be between 8 and 32, and a multiple of draw_rounding
|
||||
int32_t size = (random_uint32() % 25 + 8) / this->draw_rounding * this->draw_rounding;
|
||||
lv_area_t area{col, row, col + size - 1, row + size - 1};
|
||||
// clip to display bounds just in case
|
||||
if (area.x2 >= this->width_)
|
||||
area.x2 = this->width_ - 1;
|
||||
if (area.y2 >= this->height_)
|
||||
area.y2 = this->height_ - 1;
|
||||
|
||||
// line_len can't exceed 1024, and minimum buffer size is 2048, so this won't overflow the buffer
|
||||
size_t line_len = lv_area_get_width(&area) * lv_area_get_height(&area) / 2;
|
||||
for (size_t i = 0; i != line_len; i++) {
|
||||
((uint32_t *) (this->draw_buf_.buf1))[i] = random_uint32();
|
||||
((uint32_t *) (this->draw_buf_))[i] = random_uint32();
|
||||
}
|
||||
this->draw_buffer_(&area, (lv_color_t *) this->draw_buf_.buf1);
|
||||
this->draw_buffer_(&area, (lv_color_data *) this->draw_buf_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,38 +552,32 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf
|
||||
full_refresh_(full_refresh),
|
||||
resume_on_input_(resume_on_input),
|
||||
update_when_display_idle_(update_when_display_idle) {
|
||||
lv_disp_draw_buf_init(&this->draw_buf_, nullptr, nullptr, 0);
|
||||
lv_disp_drv_init(&this->disp_drv_);
|
||||
this->disp_drv_.draw_buf = &this->draw_buf_;
|
||||
this->disp_drv_.user_data = this;
|
||||
this->disp_drv_.full_refresh = this->full_refresh_;
|
||||
this->disp_drv_.flush_cb = static_flush_cb;
|
||||
this->disp_drv_.rounder_cb = rounder_cb;
|
||||
this->disp_ = lv_disp_drv_register(&this->disp_drv_);
|
||||
this->disp_ = lv_display_create(240, 240);
|
||||
}
|
||||
|
||||
void LvglComponent::setup() {
|
||||
auto *display = this->displays_[0];
|
||||
auto rounding = this->draw_rounding;
|
||||
// cater for displays with dimensions that don't divide by the required rounding
|
||||
this->width_ = display->get_width();
|
||||
this->height_ = display->get_height();
|
||||
auto width = (display->get_width() + rounding - 1) / rounding * rounding;
|
||||
auto height = (display->get_height() + rounding - 1) / rounding * rounding;
|
||||
auto frac = this->buffer_frac_;
|
||||
if (frac == 0)
|
||||
frac = 1;
|
||||
size_t buffer_pixels = width * height / frac;
|
||||
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
|
||||
auto buf_bytes = clamp_at_least(width * height / frac * LV_COLOR_DEPTH / 8, MIN_BUFFER_SIZE);
|
||||
void *buffer = nullptr;
|
||||
// for small buffers, try to allocate in internal memory first to improve performance
|
||||
if (this->buffer_frac_ >= MIN_BUFFER_FRAC / 2)
|
||||
buffer = malloc(buf_bytes); // NOLINT
|
||||
buffer = lv_alloc_draw_buf(buf_bytes, true); // NOLINT
|
||||
if (buffer == nullptr)
|
||||
buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
buffer = lv_alloc_draw_buf(buf_bytes, false); // NOLINT
|
||||
// if specific buffer size not set and can't get 100%, try for a smaller one
|
||||
if (buffer == nullptr && this->buffer_frac_ == 0) {
|
||||
frac = MIN_BUFFER_FRAC;
|
||||
buffer_pixels /= MIN_BUFFER_FRAC;
|
||||
buf_bytes /= MIN_BUFFER_FRAC;
|
||||
buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
buffer = lv_alloc_draw_buf(buf_bytes, false); // NOLINT
|
||||
}
|
||||
this->buffer_frac_ = frac;
|
||||
if (buffer == nullptr) {
|
||||
@@ -516,13 +585,17 @@ void LvglComponent::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels);
|
||||
this->disp_drv_.hor_res = display->get_width();
|
||||
this->disp_drv_.ver_res = display->get_height();
|
||||
lv_disp_drv_update(this->disp_, &this->disp_drv_);
|
||||
this->draw_buf_ = static_cast<uint8_t *>(buffer);
|
||||
lv_display_set_resolution(this->disp_, this->width_, this->height_);
|
||||
lv_display_set_color_format(this->disp_, LV_COLOR_FORMAT_RGB565);
|
||||
lv_display_set_flush_cb(this->disp_, static_flush_cb);
|
||||
lv_display_set_user_data(this->disp_, this);
|
||||
lv_display_add_event_cb(this->disp_, rounder_cb, LV_EVENT_INVALIDATE_AREA, this);
|
||||
lv_display_set_buffers(this->disp_, this->draw_buf_, nullptr, buf_bytes,
|
||||
this->full_refresh_ ? LV_DISPLAY_RENDER_MODE_FULL : LV_DISPLAY_RENDER_MODE_PARTIAL);
|
||||
this->rotation = display->get_rotation();
|
||||
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
||||
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
|
||||
this->rotate_buf_ = static_cast<lv_color_t *>(lv_alloc_draw_buf(buf_bytes, false)); // NOLINT
|
||||
if (this->rotate_buf_ == nullptr) {
|
||||
this->status_set_error(LOG_STR("Memory allocation failure"));
|
||||
this->mark_failed();
|
||||
@@ -530,26 +603,28 @@ void LvglComponent::setup() {
|
||||
}
|
||||
}
|
||||
if (this->draw_start_callback_ != nullptr) {
|
||||
this->disp_drv_.render_start_cb = render_start_cb;
|
||||
lv_display_add_event_cb(this->disp_, render_start_cb, LV_EVENT_RENDER_START, this);
|
||||
}
|
||||
if (this->draw_end_callback_ != nullptr || this->update_when_display_idle_) {
|
||||
this->disp_drv_.monitor_cb = monitor_cb;
|
||||
lv_display_add_event_cb(this->disp_, render_end_cb, LV_EVENT_REFR_READY, this);
|
||||
}
|
||||
#if LV_USE_LOG
|
||||
lv_log_register_print_cb([](const char *buf) {
|
||||
lv_log_register_print_cb([](lv_log_level_t level, const char *buf) {
|
||||
auto next = strchr(buf, ')');
|
||||
if (next != nullptr)
|
||||
buf = next + 1;
|
||||
while (isspace(*buf))
|
||||
buf++;
|
||||
esp_log_printf_(LVGL_LOG_LEVEL, TAG, 0, "%.*s", (int) strlen(buf) - 1, buf);
|
||||
if (level >= sizeof(LOG_LEVEL_MAP) / sizeof(LOG_LEVEL_MAP[0]))
|
||||
level = sizeof(LOG_LEVEL_MAP) / sizeof(LOG_LEVEL_MAP[0]) - 1;
|
||||
esp_log_printf_(LOG_LEVEL_MAP[level], TAG, 0, "%.*s", (int) strlen(buf) - 1, buf);
|
||||
});
|
||||
#endif
|
||||
// Rotation will be handled by our drawing function, so reset the display rotation.
|
||||
for (auto *disp : this->displays_)
|
||||
disp->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);
|
||||
this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0);
|
||||
lv_disp_trig_activity(this->disp_);
|
||||
this->show_page(0, LV_SCREEN_LOAD_ANIM_NONE, 0);
|
||||
lv_display_trigger_activity(this->disp_);
|
||||
}
|
||||
|
||||
void LvglComponent::update() {
|
||||
@@ -557,7 +632,7 @@ void LvglComponent::update() {
|
||||
if (this->is_paused()) {
|
||||
return;
|
||||
}
|
||||
this->idle_callbacks_.call(lv_disp_get_inactive_time(this->disp_));
|
||||
this->idle_callbacks_.call(lv_display_get_inactive_time(this->disp_));
|
||||
}
|
||||
|
||||
void LvglComponent::loop() {
|
||||
@@ -565,41 +640,131 @@ void LvglComponent::loop() {
|
||||
if (this->paused_ && this->show_snow_)
|
||||
this->write_random_();
|
||||
} else {
|
||||
lv_timer_handler_run_in_period(5);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
auto now = millis();
|
||||
lv_timer_handler();
|
||||
auto elapsed = millis() - now;
|
||||
if (elapsed > 15) {
|
||||
ESP_LOGV(TAG, "lv_timer_handler took %dms", (int) (millis() - now));
|
||||
}
|
||||
#else
|
||||
lv_timer_handler();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_LVGL_ANIMIMG
|
||||
void lv_animimg_stop(lv_obj_t *obj) {
|
||||
auto *animg = (lv_animimg_t *) obj;
|
||||
int32_t duration = animg->anim.time;
|
||||
int32_t duration = lv_animimg_get_duration(obj);
|
||||
lv_animimg_set_duration(obj, 0);
|
||||
lv_animimg_start(obj);
|
||||
lv_animimg_set_duration(obj, duration);
|
||||
}
|
||||
#endif
|
||||
void LvglComponent::static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
|
||||
reinterpret_cast<LvglComponent *>(disp_drv->user_data)->flush_cb_(disp_drv, area, color_p);
|
||||
void LvglComponent::static_flush_cb(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *color_p) {
|
||||
reinterpret_cast<LvglComponent *>(lv_display_get_user_data(disp_drv))->flush_cb_(disp_drv, area, color_p);
|
||||
}
|
||||
} // namespace lvgl
|
||||
} // namespace esphome
|
||||
|
||||
size_t lv_millis(void) { return esphome::millis(); }
|
||||
#ifdef USE_LVGL_SCALE
|
||||
/**
|
||||
* Function to apply colors to ticks based on position
|
||||
* @param e The event data
|
||||
* @param color_start The color to apply to the first tick
|
||||
* @param color_end The color to apply to the last tick
|
||||
* @param width
|
||||
*/
|
||||
void lv_scale_draw_event_cb(lv_event_t *e, uint16_t range_start, uint16_t range_end, lv_color_t color_start,
|
||||
lv_color_t color_end, int width, bool local) {
|
||||
auto *scale = static_cast<lv_obj_t *>(lv_event_get_target(e));
|
||||
lv_draw_task_t *task = lv_event_get_draw_task(e);
|
||||
|
||||
if (lv_draw_task_get_type(task) == LV_DRAW_TASK_TYPE_LINE) {
|
||||
auto *line_dsc = static_cast<lv_draw_line_dsc_t *>(lv_draw_task_get_draw_dsc(task));
|
||||
auto tick = line_dsc->base.id1;
|
||||
if (tick >= range_start && tick <= range_end) {
|
||||
unsigned range = range_end - range_start;
|
||||
if (local) {
|
||||
tick -= range_start;
|
||||
} else {
|
||||
range = lv_scale_get_total_tick_count(scale) - 1;
|
||||
}
|
||||
if (range == 0)
|
||||
range = 1;
|
||||
auto ratio = (tick * 255) / range;
|
||||
line_dsc->color = lv_color_mix(color_end, color_start, ratio);
|
||||
line_dsc->width += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // USE_LVGL_SCALE
|
||||
|
||||
static void lv_container_constructor(const lv_obj_class_t *class_p, lv_obj_t *obj) {
|
||||
LV_TRACE_OBJ_CREATE("begin");
|
||||
LV_UNUSED(class_p);
|
||||
}
|
||||
|
||||
// Container class. Name is based on LVGL naming convention but upper case to keep ESPHome clang-tidy happy
|
||||
const lv_obj_class_t LV_CONTAINER_CLASS = {
|
||||
.base_class = &lv_obj_class,
|
||||
.constructor_cb = lv_container_constructor,
|
||||
.name = "lv_container",
|
||||
};
|
||||
|
||||
lv_obj_t *lv_container_create(lv_obj_t *parent) {
|
||||
lv_obj_t *obj = lv_obj_class_create_obj(&LV_CONTAINER_CLASS, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
return obj;
|
||||
}
|
||||
} // namespace esphome::lvgl
|
||||
|
||||
lv_result_t lv_mem_test_core() { return LV_RESULT_OK; }
|
||||
|
||||
void lv_mem_init() {}
|
||||
|
||||
void lv_mem_deinit() {}
|
||||
|
||||
#if defined(USE_HOST) || defined(USE_RP2040) || defined(USE_ESP8266)
|
||||
void *lv_custom_mem_alloc(size_t size) {
|
||||
void *lv_malloc_core(size_t size) {
|
||||
auto *ptr = malloc(size); // NOLINT
|
||||
if (ptr == nullptr) {
|
||||
ESP_LOGE(esphome::lvgl::TAG, "Failed to allocate %zu bytes", size);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
void lv_custom_mem_free(void *ptr) { return free(ptr); } // NOLINT
|
||||
void *lv_custom_mem_realloc(void *ptr, size_t size) { return realloc(ptr, size); } // NOLINT
|
||||
#else
|
||||
void lv_free_core(void *ptr) { return free(ptr); } // NOLINT
|
||||
void *lv_realloc_core(void *ptr, size_t size) { return realloc(ptr, size); } // NOLINT
|
||||
|
||||
void lv_mem_monitor_core(lv_mem_monitor_t *mon_p) { memset(mon_p, 0, sizeof(lv_mem_monitor_t)); }
|
||||
static void *lv_alloc_draw_buf(size_t size, bool internal) {
|
||||
return malloc(size); // NOLINT
|
||||
}
|
||||
|
||||
#elif defined(USE_ESP32)
|
||||
static unsigned cap_bits = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; // NOLINT
|
||||
|
||||
void *lv_custom_mem_alloc(size_t size) {
|
||||
static void *lv_alloc_draw_buf(size_t size, bool internal) {
|
||||
void *buffer;
|
||||
size = ((size + LV_DRAW_BUF_ALIGN - 1) / LV_DRAW_BUF_ALIGN) * LV_DRAW_BUF_ALIGN;
|
||||
buffer = heap_caps_aligned_alloc(LV_DRAW_BUF_ALIGN, size, internal ? MALLOC_CAP_8BIT : cap_bits); // NOLINT
|
||||
if (buffer == nullptr)
|
||||
ESP_LOGW(esphome::lvgl::TAG, "Failed to allocate %zu bytes for %sdraw buffer", size, internal ? "internal " : "");
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void lv_mem_monitor_core(lv_mem_monitor_t *mon_p) {
|
||||
multi_heap_info_t heap_info;
|
||||
heap_caps_get_info(&heap_info, cap_bits);
|
||||
mon_p->total_size = heap_info.total_allocated_bytes + heap_info.total_free_bytes;
|
||||
mon_p->free_size = heap_info.total_free_bytes;
|
||||
mon_p->max_used = heap_info.total_allocated_bytes;
|
||||
mon_p->free_biggest_size = heap_info.largest_free_block;
|
||||
mon_p->used_cnt = heap_info.allocated_blocks;
|
||||
mon_p->free_cnt = heap_info.free_blocks;
|
||||
mon_p->used_pct = heap_info.allocated_blocks * 100 / (heap_info.allocated_blocks + heap_info.free_blocks);
|
||||
mon_p->frag_pct = 0;
|
||||
}
|
||||
|
||||
void *lv_malloc_core(size_t size) {
|
||||
void *ptr;
|
||||
ptr = heap_caps_malloc(size, cap_bits);
|
||||
if (ptr == nullptr) {
|
||||
@@ -614,14 +779,14 @@ void *lv_custom_mem_alloc(size_t size) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void lv_custom_mem_free(void *ptr) {
|
||||
void lv_free_core(void *ptr) {
|
||||
ESP_LOGV(esphome::lvgl::TAG, "free %p", ptr);
|
||||
if (ptr == nullptr)
|
||||
return;
|
||||
heap_caps_free(ptr);
|
||||
}
|
||||
|
||||
void *lv_custom_mem_realloc(void *ptr, size_t size) {
|
||||
void *lv_realloc_core(void *ptr, size_t size) {
|
||||
ESP_LOGV(esphome::lvgl::TAG, "realloc %p: %zu", ptr, size);
|
||||
return heap_caps_realloc(ptr, size, cap_bits);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif // USE_BINARY_SENSOR
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
#ifdef USE_IMAGE
|
||||
#include "esphome/components/image/image.h"
|
||||
#endif // USE_LVGL_IMAGE
|
||||
#ifdef USE_LVGL_ROTARY_ENCODER
|
||||
@@ -19,16 +19,17 @@
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esphome/components/display/display_color_utils.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <list>
|
||||
#include <lvgl.h>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
#ifdef USE_FONT
|
||||
#include "esphome/components/font/font.h"
|
||||
#endif // USE_LVGL_FONT
|
||||
#ifdef USE_LVGL_TOUCHSCREEN
|
||||
#ifdef USE_TOUCHSCREEN
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#endif // USE_LVGL_TOUCHSCREEN
|
||||
|
||||
@@ -36,12 +37,24 @@
|
||||
#include "esphome/components/key_provider/key_provider.h"
|
||||
#endif // USE_LVGL_BUTTONMATRIX
|
||||
|
||||
namespace esphome {
|
||||
namespace lvgl {
|
||||
namespace esphome::lvgl {
|
||||
|
||||
#if LV_COLOR_DEPTH == 16
|
||||
using lv_color_data = uint16_t;
|
||||
#endif
|
||||
#if LV_COLOR_DEPTH == 32
|
||||
using lv_color_data = uint32_t;
|
||||
#endif
|
||||
|
||||
extern lv_event_code_t lv_api_event; // NOLINT
|
||||
extern lv_event_code_t lv_update_event; // NOLINT
|
||||
extern std::string lv_event_code_name_for(uint8_t event_code);
|
||||
extern std::string lv_event_code_name_for(lv_event_t *event);
|
||||
|
||||
lv_obj_t *lv_container_create(lv_obj_t *parent);
|
||||
#ifdef USE_LVGL_SCALE
|
||||
void lv_scale_draw_event_cb(lv_event_t *e, uint16_t range_start, uint16_t range_end, lv_color_t color_start,
|
||||
lv_color_t color_end, int width, bool local);
|
||||
#endif
|
||||
#if LV_COLOR_DEPTH == 16
|
||||
static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565;
|
||||
#elif LV_COLOR_DEPTH == 32
|
||||
@@ -50,7 +63,7 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
|
||||
static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_332;
|
||||
#endif // LV_COLOR_DEPTH
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
#if defined(USE_FONT) && defined(USE_LVGL_FONT)
|
||||
inline void lv_obj_set_style_text_font(lv_obj_t *obj, const font::Font *font, lv_style_selector_t part) {
|
||||
lv_obj_set_style_text_font(obj, font->get_lv_font(), part);
|
||||
}
|
||||
@@ -58,50 +71,37 @@ inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) {
|
||||
lv_style_set_text_font(style, font->get_lv_font());
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
#ifdef USE_IMAGE
|
||||
// Shortcut / overload, so that the source of an image can easily be updated
|
||||
// from within a lambda.
|
||||
inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) {
|
||||
lv_img_set_src(obj, image->get_lv_img_dsc());
|
||||
}
|
||||
inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) {
|
||||
lv_disp_set_bg_image(disp, image->get_lv_img_dsc());
|
||||
inline void lv_image_set_src(lv_obj_t *obj, esphome::image::Image *image) {
|
||||
lv_image_set_src(obj, image->get_lv_image_dsc());
|
||||
}
|
||||
|
||||
inline void lv_obj_set_style_bg_img_src(lv_obj_t *obj, esphome::image::Image *image, lv_style_selector_t selector) {
|
||||
lv_obj_set_style_bg_img_src(obj, image->get_lv_img_dsc(), selector);
|
||||
inline void lv_obj_set_style_bg_image_src(lv_obj_t *obj, esphome::image::Image *image, lv_style_selector_t selector) {
|
||||
lv_obj_set_style_bg_image_src(obj, image->get_lv_image_dsc(), selector);
|
||||
}
|
||||
#ifdef USE_LVGL_CANVAS
|
||||
inline void lv_canvas_draw_img(lv_obj_t *canvas, lv_coord_t x, lv_coord_t y, image::Image *image,
|
||||
lv_draw_img_dsc_t *dsc) {
|
||||
lv_canvas_draw_img(canvas, x, y, image->get_lv_img_dsc(), dsc);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LVGL_METER
|
||||
inline lv_meter_indicator_t *lv_meter_add_needle_img(lv_obj_t *obj, lv_meter_scale_t *scale, esphome::image::Image *src,
|
||||
lv_coord_t pivot_x, lv_coord_t pivot_y) {
|
||||
return lv_meter_add_needle_img(obj, scale, src->get_lv_img_dsc(), pivot_x, pivot_y);
|
||||
}
|
||||
#endif // USE_LVGL_METER
|
||||
#endif // USE_LVGL_IMAGE
|
||||
#ifdef USE_LVGL_ANIMIMG
|
||||
inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) {
|
||||
auto *dsc = static_cast<std::vector<lv_img_dsc_t *> *>(lv_obj_get_user_data(img));
|
||||
auto *dsc = static_cast<std::vector<lv_image_dsc_t *> *>(lv_obj_get_user_data(img));
|
||||
if (dsc == nullptr) {
|
||||
// object will be lazily allocated but never freed.
|
||||
dsc = new std::vector<lv_img_dsc_t *>(images.size()); // NOLINT
|
||||
dsc = new std::vector<lv_image_dsc_t *>(images.size()); // NOLINT
|
||||
lv_obj_set_user_data(img, dsc);
|
||||
}
|
||||
dsc->clear();
|
||||
for (auto &image : images) {
|
||||
dsc->push_back(image->get_lv_img_dsc());
|
||||
dsc->push_back(image->get_lv_image_dsc());
|
||||
}
|
||||
lv_animimg_set_src(img, (const void **) dsc->data(), dsc->size());
|
||||
}
|
||||
|
||||
#endif // USE_LVGL_ANIMIMG
|
||||
|
||||
#ifdef USE_LVGL_METER
|
||||
int16_t lv_get_needle_angle_for_value(lv_obj_t *obj, int value);
|
||||
#endif
|
||||
|
||||
// Parent class for things that wrap an LVGL object
|
||||
class LvCompound {
|
||||
public:
|
||||
@@ -130,7 +130,7 @@ class LvPageType : public Parented<LvglComponent> {
|
||||
|
||||
using LvLambdaType = std::function<void(lv_obj_t *)>;
|
||||
using set_value_lambda_t = std::function<void(float)>;
|
||||
using event_callback_t = void(_lv_event_t *);
|
||||
using event_callback_t = void(lv_event_t *);
|
||||
using text_lambda_t = std::function<const char *()>;
|
||||
|
||||
template<typename... Ts> class ObjUpdateAction : public Action<Ts...> {
|
||||
@@ -152,7 +152,7 @@ class LvglComponent : public PollingComponent {
|
||||
public:
|
||||
LvglComponent(std::vector<display::Display *> displays, float buffer_frac, bool full_refresh, int draw_rounding,
|
||||
bool resume_on_input, bool update_when_display_idle);
|
||||
static void static_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
|
||||
static void static_flush_cb(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *color_p);
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
void setup() override;
|
||||
@@ -160,11 +160,11 @@ class LvglComponent : public PollingComponent {
|
||||
void loop() override;
|
||||
template<typename F> void add_on_idle_callback(F &&callback) { this->idle_callbacks_.add(std::forward<F>(callback)); }
|
||||
|
||||
static void monitor_cb(lv_disp_drv_t *disp_drv, uint32_t time, uint32_t px);
|
||||
static void render_start_cb(lv_disp_drv_t *disp_drv);
|
||||
static void render_end_cb(lv_event_t *event);
|
||||
static void render_start_cb(lv_event_t *event);
|
||||
void dump_config() override;
|
||||
lv_disp_t *get_disp() { return this->disp_; }
|
||||
lv_obj_t *get_scr_act() { return lv_disp_get_scr_act(this->disp_); }
|
||||
lv_obj_t *get_screen_active() { return lv_display_get_screen_active(this->disp_); }
|
||||
// Pause or resume the display.
|
||||
// @param paused If true, pause the display. If false, resume the display.
|
||||
// @param show_snow If true, show the snow effect when paused.
|
||||
@@ -187,11 +187,13 @@ class LvglComponent : public PollingComponent {
|
||||
static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2);
|
||||
static void add_event_cb(lv_obj_t *obj, event_callback_t callback, lv_event_code_t event1, lv_event_code_t event2,
|
||||
lv_event_code_t event3);
|
||||
|
||||
void add_page(LvPageType *page);
|
||||
void show_page(size_t index, lv_scr_load_anim_t anim, uint32_t time);
|
||||
void show_next_page(lv_scr_load_anim_t anim, uint32_t time);
|
||||
void show_prev_page(lv_scr_load_anim_t anim, uint32_t time);
|
||||
void set_page_wrap(bool wrap) { this->page_wrap_ = wrap; }
|
||||
void set_big_endian(bool big_endian) { this->big_endian_ = big_endian; }
|
||||
size_t get_current_page() const;
|
||||
void set_focus_mark(lv_group_t *group) { this->focus_marks_[group] = lv_group_get_focused(group); }
|
||||
void restore_focus_mark(lv_group_t *group) {
|
||||
@@ -216,22 +218,25 @@ class LvglComponent : public PollingComponent {
|
||||
void draw_start_() const { this->draw_start_callback_->trigger(); }
|
||||
|
||||
void write_random_();
|
||||
void draw_buffer_(const lv_area_t *area, lv_color_t *ptr);
|
||||
void flush_cb_(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
|
||||
void draw_buffer_(const lv_area_t *area, lv_color_data *ptr);
|
||||
void flush_cb_(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *color_p);
|
||||
|
||||
std::vector<display::Display *> displays_{};
|
||||
size_t buffer_frac_{1};
|
||||
bool full_refresh_{};
|
||||
bool resume_on_input_{};
|
||||
bool update_when_display_idle_{};
|
||||
|
||||
lv_disp_draw_buf_t draw_buf_{};
|
||||
lv_disp_drv_t disp_drv_{};
|
||||
lv_disp_t *disp_{};
|
||||
uint8_t *draw_buf_{};
|
||||
lv_display_t *disp_{};
|
||||
uint16_t width_{};
|
||||
uint16_t height_{};
|
||||
bool paused_{};
|
||||
std::vector<LvPageType *> pages_{};
|
||||
size_t current_page_{0};
|
||||
bool show_snow_{};
|
||||
bool page_wrap_{true};
|
||||
bool big_endian_{};
|
||||
std::map<lv_group_t *, lv_obj_t *> focus_marks_{};
|
||||
|
||||
CallbackManager<void(uint32_t)> idle_callbacks_{};
|
||||
@@ -239,7 +244,7 @@ class LvglComponent : public PollingComponent {
|
||||
Trigger<> *resume_callback_{};
|
||||
Trigger<> *draw_start_callback_{};
|
||||
Trigger<> *draw_end_callback_{};
|
||||
lv_color_t *rotate_buf_{};
|
||||
void *rotate_buf_{};
|
||||
};
|
||||
|
||||
class IdleTrigger : public Trigger<> {
|
||||
@@ -278,19 +283,37 @@ class LVTouchListener : public touchscreen::TouchListener, public Parented<LvglC
|
||||
touch_pressed_ = false;
|
||||
this->parent_->maybe_wakeup();
|
||||
}
|
||||
lv_indev_drv_t *get_drv() { return &this->drv_; }
|
||||
lv_indev_t *get_drv() { return this->drv_; }
|
||||
|
||||
protected:
|
||||
lv_indev_drv_t drv_{};
|
||||
lv_indev_t *drv_{};
|
||||
touchscreen::TouchPoint touch_point_{};
|
||||
bool touch_pressed_{};
|
||||
};
|
||||
#endif // USE_LVGL_TOUCHSCREEN
|
||||
|
||||
#ifdef USE_LVGL_METER
|
||||
|
||||
class IndicatorLine : public LvCompound {
|
||||
public:
|
||||
IndicatorLine() = default;
|
||||
|
||||
void set_obj(lv_obj_t *lv_obj) override;
|
||||
|
||||
void set_value(int value);
|
||||
|
||||
private:
|
||||
void update_length_();
|
||||
|
||||
int16_t angle_{};
|
||||
lv_point_precise_t points_[2]{};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LVGL_KEY_LISTENER
|
||||
class LVEncoderListener : public Parented<LvglComponent> {
|
||||
public:
|
||||
LVEncoderListener(lv_indev_type_t type, uint16_t lpt, uint16_t lprt);
|
||||
LVEncoderListener(lv_indev_type_t type, uint16_t long_press_time, uint16_t long_press_repeat_time);
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void add_button(binary_sensor::BinarySensor *button, lv_key_t key) {
|
||||
@@ -322,10 +345,10 @@ class LVEncoderListener : public Parented<LvglComponent> {
|
||||
}
|
||||
}
|
||||
|
||||
lv_indev_drv_t *get_drv() { return &this->drv_; }
|
||||
lv_indev_t *get_drv() { return this->drv_; }
|
||||
|
||||
protected:
|
||||
lv_indev_drv_t drv_{};
|
||||
lv_indev_t *drv_{};
|
||||
bool pressed_{};
|
||||
int32_t count_{};
|
||||
int32_t last_count_{};
|
||||
@@ -336,14 +359,13 @@ class LVEncoderListener : public Parented<LvglComponent> {
|
||||
#ifdef USE_LVGL_LINE
|
||||
class LvLineType : public LvCompound {
|
||||
public:
|
||||
std::vector<lv_point_t> get_points() { return this->points_; }
|
||||
void set_points(std::vector<lv_point_t> points) {
|
||||
void set_points(FixedVector<lv_point_precise_t> points) {
|
||||
this->points_ = std::move(points);
|
||||
lv_line_set_points(this->obj, this->points_.data(), this->points_.size());
|
||||
lv_line_set_points(this->obj, this->points_.begin(), this->points_.size());
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<lv_point_t> points_{};
|
||||
FixedVector<lv_point_precise_t> points_{};
|
||||
};
|
||||
#endif
|
||||
#if defined(USE_LVGL_DROPDOWN) || defined(LV_USE_ROLLER)
|
||||
@@ -392,7 +414,7 @@ class LvRollerType : public LvSelectable {
|
||||
class LvButtonMatrixType : public key_provider::KeyProvider, public LvCompound {
|
||||
public:
|
||||
void set_obj(lv_obj_t *lv_obj) override;
|
||||
uint16_t get_selected() { return lv_btnmatrix_get_selected_btn(this->obj); }
|
||||
uint16_t get_selected() { return lv_buttonmatrix_get_selected_button(this->obj); }
|
||||
void set_key(size_t idx, uint8_t key) { this->key_map_[idx] = key; }
|
||||
|
||||
protected:
|
||||
@@ -406,5 +428,4 @@ class LvKeyboardType : public key_provider::KeyProvider, public LvCompound {
|
||||
void set_obj(lv_obj_t *lv_obj) override;
|
||||
};
|
||||
#endif // USE_LVGL_KEYBOARD
|
||||
} // namespace lvgl
|
||||
} // namespace esphome
|
||||
} // namespace esphome::lvgl
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// Created by Clyde Stubbs on 20/9/2023.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define EXTERNC extern "C"
|
||||
#include <cstddef>
|
||||
namespace esphome {
|
||||
namespace lvgl {}
|
||||
} // namespace esphome
|
||||
#else
|
||||
#define EXTERNC extern
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
|
||||
EXTERNC size_t lv_millis(void);
|
||||
EXTERNC void *lv_custom_mem_alloc(size_t size);
|
||||
EXTERNC void lv_custom_mem_free(void *ptr);
|
||||
EXTERNC void *lv_custom_mem_realloc(void *ptr, size_t size);
|
||||
@@ -52,9 +52,9 @@ async def to_code(config):
|
||||
await value.get_lambda(),
|
||||
event_code,
|
||||
config[CONF_RESTORE_VALUE],
|
||||
max_value=widget.get_max(),
|
||||
min_value=widget.get_min(),
|
||||
step=widget.get_step(),
|
||||
max_value=widget.type.get_max(widget.config),
|
||||
min_value=widget.type.get_min(widget.config),
|
||||
step=widget.type.get_step(widget.config),
|
||||
)
|
||||
async with LambdaContext(EVENT_ARG) as event:
|
||||
event.add(var.on_value())
|
||||
|
||||
@@ -29,6 +29,7 @@ from .defines import (
|
||||
CONF_SCROLLBAR_MODE,
|
||||
CONF_TIME_FORMAT,
|
||||
LV_GRAD_DIR,
|
||||
get_remapped_uses,
|
||||
)
|
||||
from .helpers import CONF_IF_NAN, requires_component, validate_printf
|
||||
from .layout import (
|
||||
@@ -42,16 +43,17 @@ from .lvcode import LvglComponent, lv_event_t_ptr
|
||||
from .types import (
|
||||
LVEncoderListener,
|
||||
LvType,
|
||||
WidgetType,
|
||||
lv_group_t,
|
||||
lv_obj_t,
|
||||
lv_pseudo_button_t,
|
||||
lv_style_t,
|
||||
)
|
||||
from .widgets import WidgetType
|
||||
|
||||
# this will be populated later, in __init__.py to avoid circular imports.
|
||||
WIDGET_TYPES: dict = {}
|
||||
|
||||
|
||||
TIME_TEXT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TIME_FORMAT): cv.string,
|
||||
@@ -176,23 +178,28 @@ STYLE_PROPS = {
|
||||
"height": lvalid.size,
|
||||
"image_recolor": lvalid.lv_color,
|
||||
"image_recolor_opa": lvalid.opacity,
|
||||
"line_width": lvalid.lv_positive_int,
|
||||
"line_dash_width": lvalid.lv_positive_int,
|
||||
"line_dash_gap": lvalid.lv_positive_int,
|
||||
"line_rounded": lvalid.lv_bool,
|
||||
"line_color": lvalid.lv_color,
|
||||
"line_dash_gap": lvalid.lv_positive_int,
|
||||
"line_dash_width": lvalid.lv_positive_int,
|
||||
"line_opa": lvalid.opacity,
|
||||
"line_rounded": lvalid.lv_bool,
|
||||
"line_width": lvalid.lv_positive_int,
|
||||
"opa": lvalid.opacity,
|
||||
"opa_layered": lvalid.opacity,
|
||||
"outline_color": lvalid.lv_color,
|
||||
"outline_opa": lvalid.opacity,
|
||||
"outline_pad": lvalid.padding,
|
||||
"outline_width": lvalid.pixels,
|
||||
"length": lvalid.pixels_or_percent,
|
||||
"pad_all": lvalid.padding,
|
||||
"pad_bottom": lvalid.padding,
|
||||
"pad_left": lvalid.padding,
|
||||
"pad_right": lvalid.padding,
|
||||
"pad_top": lvalid.padding,
|
||||
"radial_offset": lvalid.size,
|
||||
"shadow_color": lvalid.lv_color,
|
||||
"shadow_offset_x": lvalid.lv_int,
|
||||
"shadow_offset_y": lvalid.lv_int,
|
||||
"shadow_ofs_x": lvalid.lv_int,
|
||||
"shadow_ofs_y": lvalid.lv_int,
|
||||
"shadow_opa": lvalid.opacity,
|
||||
@@ -213,7 +220,13 @@ STYLE_PROPS = {
|
||||
"transform_height": lvalid.pixels_or_percent,
|
||||
"transform_pivot_x": lvalid.pixels_or_percent,
|
||||
"transform_pivot_y": lvalid.pixels_or_percent,
|
||||
"transform_zoom": lvalid.zoom,
|
||||
"transform_rotation": lvalid.lv_angle,
|
||||
"transform_scale": lvalid.scale,
|
||||
"transform_scale_x": lvalid.scale,
|
||||
"transform_scale_y": lvalid.scale,
|
||||
"transform_skew_x": lvalid.lv_angle,
|
||||
"transform_skew_y": lvalid.lv_angle,
|
||||
"transform_zoom": lvalid.scale,
|
||||
"translate_x": lvalid.pixels_or_percent,
|
||||
"translate_y": lvalid.pixels_or_percent,
|
||||
"max_height": lvalid.pixels_or_percent,
|
||||
@@ -227,15 +240,23 @@ STYLE_PROPS = {
|
||||
}
|
||||
|
||||
STYLE_REMAP = {
|
||||
"bg_image_opa": "bg_img_opa",
|
||||
"bg_image_recolor": "bg_img_recolor",
|
||||
"bg_image_recolor_opa": "bg_img_recolor_opa",
|
||||
"bg_image_src": "bg_img_src",
|
||||
"bg_image_tiled": "bg_img_tiled",
|
||||
"image_recolor": "img_recolor",
|
||||
"image_recolor_opa": "img_recolor_opa",
|
||||
"transform_angle": "transform_rotation",
|
||||
"transform_zoom": "transform_scale",
|
||||
"zoom": "scale",
|
||||
"angle": "rotation",
|
||||
"shadow_ofs_x": "shadow_offset_x",
|
||||
"shadow_ofs_y": "shadow_offset_y",
|
||||
"r_mod": "length",
|
||||
}
|
||||
|
||||
|
||||
def remap_property(prop):
|
||||
if prop in STYLE_REMAP:
|
||||
get_remapped_uses().add(prop)
|
||||
return STYLE_REMAP[prop]
|
||||
return prop
|
||||
|
||||
|
||||
# Complete object style schema
|
||||
STYLE_SCHEMA = cv.Schema({cv.Optional(k): v for k, v in STYLE_PROPS.items()}).extend(
|
||||
{
|
||||
@@ -276,7 +297,7 @@ SET_STATE_SCHEMA = cv.Schema(
|
||||
)
|
||||
# Setting object flags
|
||||
FLAG_SCHEMA = cv.Schema({cv.Optional(flag): lvalid.lv_bool for flag in df.OBJ_FLAGS})
|
||||
FLAG_LIST = cv.ensure_list(df.LvConstant("LV_OBJ_FLAG_", *df.OBJ_FLAGS).one_of)
|
||||
FLAG_LIST = cv.ensure_list(df.LV_OBJ_FLAG.one_of)
|
||||
|
||||
|
||||
def part_schema(parts):
|
||||
@@ -418,6 +439,17 @@ ALL_STYLES = {
|
||||
}
|
||||
|
||||
|
||||
def strip_defaults(schema: cv.Schema):
|
||||
"""
|
||||
Take a schema and remove any default values, also convert Required to Optional.
|
||||
Useful for converting an object schema to a modify schema
|
||||
:param schema: The original Schema
|
||||
:return: A new schema with no defaults and all items optional
|
||||
"""
|
||||
|
||||
return cv.Schema({cv.Optional(k): v for k, v in schema.schema.items()})
|
||||
|
||||
|
||||
def container_schema(widget_type: WidgetType, extras=None):
|
||||
"""
|
||||
Create a schema for a container widget of a given type. All obj properties are available, plus
|
||||
@@ -481,9 +513,9 @@ def any_widget_schema(extras=None):
|
||||
container_validator, requires_component(required)
|
||||
)
|
||||
# Apply custom validation
|
||||
value = widget_type.validate(value or {})
|
||||
path = [key] if is_dict else [index, key]
|
||||
with prepend_path(path):
|
||||
value = widget_type.validate(value or {})
|
||||
result.append({key: container_validator(value)})
|
||||
return result
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "../lvgl.h"
|
||||
#include "esphome/components/lvgl/lvgl_esphome.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lvgl {
|
||||
@@ -28,12 +28,12 @@ class LVGLSelect : public select::Select, public Component {
|
||||
lv_obj_add_event_cb(
|
||||
this->widget_->obj,
|
||||
[](lv_event_t *e) {
|
||||
auto *it = static_cast<LVGLSelect *>(e->user_data);
|
||||
auto *it = static_cast<LVGLSelect *>(lv_event_get_user_data(e));
|
||||
it->set_options_();
|
||||
},
|
||||
LV_EVENT_REFRESH, this);
|
||||
auto lamb = [](lv_event_t *e) {
|
||||
auto *self = static_cast<LVGLSelect *>(e->user_data);
|
||||
auto *self = static_cast<LVGLSelect *>(lv_event_get_user_data(e));
|
||||
self->publish();
|
||||
};
|
||||
lv_obj_add_event_cb(this->widget_->obj, lamb, LV_EVENT_VALUE_CHANGED, this);
|
||||
|
||||
@@ -13,7 +13,7 @@ from .defines import (
|
||||
)
|
||||
from .helpers import add_lv_use
|
||||
from .lvcode import LambdaContext, LocalVariable, lv
|
||||
from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, STYLE_REMAP
|
||||
from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, remap_property
|
||||
from .types import ObjUpdateAction, lv_obj_t, lv_style_t
|
||||
from .widgets import (
|
||||
Widget,
|
||||
@@ -26,6 +26,10 @@ from .widgets import (
|
||||
from .widgets.obj import obj_spec
|
||||
|
||||
|
||||
def has_style_props(config) -> bool:
|
||||
return any(prop in config for prop in ALL_STYLES)
|
||||
|
||||
|
||||
async def style_set(svar, style):
|
||||
for prop, validator in ALL_STYLES.items():
|
||||
if (value := style.get(prop)) is not None:
|
||||
@@ -33,22 +37,44 @@ async def style_set(svar, style):
|
||||
value = await validator.process(value)
|
||||
if isinstance(value, list):
|
||||
value = "|".join(value)
|
||||
remapped_prop = STYLE_REMAP.get(prop, prop)
|
||||
lv.call(f"style_set_{remapped_prop}", svar, literal(value))
|
||||
lv.call(f"style_set_{remap_property(prop)}", svar, literal(value))
|
||||
|
||||
|
||||
async def create_style(style, id_name):
|
||||
async def create_style(id_name, style=None):
|
||||
style_id = ID(id_name, True, lv_style_t)
|
||||
svar = cg.new_Pvariable(style_id)
|
||||
lv.style_init(svar)
|
||||
await style_set(svar, style)
|
||||
if style:
|
||||
await style_set(svar, style)
|
||||
return svar
|
||||
|
||||
|
||||
class LVStyle:
|
||||
"""
|
||||
A class to lazily create a named style
|
||||
"""
|
||||
|
||||
named_styles = {}
|
||||
|
||||
def __init__(self, id_name, style=None):
|
||||
self.id_name = id_name
|
||||
self.style = style
|
||||
self._style_var = None
|
||||
|
||||
async def get_var(self):
|
||||
if self._style_var is None:
|
||||
self._style_var = await create_style(self.id_name + "_style", self.style)
|
||||
return self._style_var
|
||||
|
||||
@classmethod
|
||||
def get_style(cls, id_name):
|
||||
return cls.named_styles.setdefault(id_name, LVStyle(id_name))
|
||||
|
||||
|
||||
async def styles_to_code(config):
|
||||
"""Convert styles to C__ code."""
|
||||
for style in config.get(CONF_STYLE_DEFINITIONS, ()):
|
||||
await create_style(style, style[CONF_ID].id)
|
||||
await create_style(style[CONF_ID].id, style)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
@@ -81,8 +107,7 @@ async def theme_to_code(config):
|
||||
for part, states in collect_parts(style).items():
|
||||
styles[part] = {
|
||||
state: await create_style(
|
||||
props,
|
||||
"_lv_theme_style_" + w_name + "_" + part + "_" + state,
|
||||
"_lv_theme_style_" + w_name + "_" + part + "_" + state, props
|
||||
)
|
||||
for state, props in states.items()
|
||||
}
|
||||
@@ -90,7 +115,7 @@ async def theme_to_code(config):
|
||||
|
||||
|
||||
async def add_top_layer(lv_component, config):
|
||||
top_layer = lv.disp_get_layer_top(lv_component.get_disp())
|
||||
top_layer = lv.disp_get_layer_top(lv_component.var.get_disp())
|
||||
if top_conf := config.get(CONF_TOP_LAYER):
|
||||
with LocalVariable("top_layer", lv_obj_t, top_layer) as top_layer_obj:
|
||||
top_w = Widget(top_layer_obj, obj_spec, top_conf)
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/components/text/text.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lvgl {
|
||||
|
||||
class LVGLText : public text::Text {
|
||||
public:
|
||||
void set_control_lambda(std::function<void(const std::string)> control_lambda) {
|
||||
this->control_lambda_ = std::move(control_lambda);
|
||||
void set_control_lambda(const std::function<void(std::string)> &control_lambda) {
|
||||
this->control_lambda_ = control_lambda;
|
||||
if (this->initial_state_.has_value()) {
|
||||
this->control_lambda_(this->initial_state_.value());
|
||||
this->initial_state_.reset();
|
||||
@@ -28,7 +25,7 @@ class LVGLText : public text::Text {
|
||||
this->initial_state_ = value;
|
||||
}
|
||||
}
|
||||
std::function<void(const std::string)> control_lambda_{};
|
||||
std::function<void(std::string)> control_lambda_{};
|
||||
optional<std::string> initial_state_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ from .defines import (
|
||||
CONF_TOUCHSCREENS,
|
||||
)
|
||||
from .helpers import lvgl_components_required
|
||||
from .lvcode import lv
|
||||
from .schemas import PRESS_TIME
|
||||
from .types import LVTouchListener
|
||||
|
||||
@@ -40,5 +39,4 @@ async def touchscreens_to_code(lv_component, config):
|
||||
lpt = tconf[CONF_LONG_PRESS_TIME].total_milliseconds
|
||||
lprt = tconf[CONF_LONG_PRESS_REPEAT_TIME].total_milliseconds
|
||||
listener = cg.new_Pvariable(tconf[CONF_ID], lpt, lprt, lv_component)
|
||||
lv.indev_drv_register(listener.get_drv())
|
||||
cg.add(touchscreen.register_listener(listener))
|
||||
|
||||
@@ -30,7 +30,7 @@ from .lvcode import (
|
||||
lvgl_static,
|
||||
)
|
||||
from .types import LV_EVENT
|
||||
from .widgets import LvScrActType, get_scr_act, widget_map
|
||||
from .widgets import LvScrActType, get_screen_active, widget_map
|
||||
|
||||
|
||||
async def add_on_boot_triggers(triggers):
|
||||
@@ -48,7 +48,7 @@ async def generate_triggers():
|
||||
|
||||
for w in widget_map.values():
|
||||
if isinstance(w.type, LvScrActType):
|
||||
w = get_scr_act(w.var)
|
||||
w = get_screen_active(w.var)
|
||||
|
||||
if w.config:
|
||||
for event, conf in {
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import sys
|
||||
|
||||
from esphome import automation, codegen as cg
|
||||
from esphome.automation import register_action
|
||||
from esphome.config_validation import Schema
|
||||
from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.cpp_generator import MockObj, MockObjClass
|
||||
from esphome.const import CONF_TEXT, CONF_VALUE
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.cpp_types import esphome_ns
|
||||
|
||||
from .defines import lvgl_ns
|
||||
from .lvcode import lv_expr
|
||||
|
||||
|
||||
class LvType(cg.MockObjClass):
|
||||
@@ -26,6 +20,10 @@ class LvType(cg.MockObjClass):
|
||||
return None
|
||||
return [arg[0] for arg in self.args]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.base.removeprefix("lv_").removesuffix("_t")
|
||||
|
||||
|
||||
class LvNumber(LvType):
|
||||
def __init__(self, *args):
|
||||
@@ -63,6 +61,7 @@ lv_obj_base_t = cg.global_ns.class_("lv_obj_t", lv_pseudo_button_t)
|
||||
lv_obj_t_ptr = lv_obj_base_t.operator("ptr")
|
||||
lv_disp_t = cg.global_ns.struct("lv_disp_t")
|
||||
lv_color_t = cg.global_ns.struct("lv_color_t")
|
||||
lv_opa_t = cg.global_ns.struct("lv_opa_t")
|
||||
lv_group_t = cg.global_ns.struct("lv_group_t")
|
||||
LVTouchListener = lvgl_ns.class_("LVTouchListener")
|
||||
LVEncoderListener = lvgl_ns.class_("LVEncoderListener")
|
||||
@@ -70,10 +69,11 @@ lv_obj_t = LvType("lv_obj_t")
|
||||
lv_page_t = LvType("LvPageType", parents=(LvCompound,))
|
||||
lv_img_t = LvType("lv_img_t")
|
||||
lv_gradient_t = LvType("lv_grad_dsc_t")
|
||||
lv_event_t = LvType("lv_event_t")
|
||||
|
||||
LV_EVENT = MockObj(base="LV_EVENT_", op="")
|
||||
LV_STATE = MockObj(base="LV_STATE_", op="")
|
||||
LV_BTNMATRIX_CTRL = MockObj(base="LV_BTNMATRIX_CTRL_", op="")
|
||||
LV_BTNMATRIX_CTRL = MockObj(base="LV_BUTTONMATRIX_CTRL_", op="")
|
||||
|
||||
|
||||
class LvText(LvType):
|
||||
@@ -93,7 +93,7 @@ class LvBoolean(LvType):
|
||||
super().__init__(
|
||||
*args,
|
||||
largs=[(cg.bool_, "x")],
|
||||
lvalue=lambda w: w.is_checked(),
|
||||
lvalue=lambda w: w.has_state(LV_STATE.CHECKED),
|
||||
has_on_value=True,
|
||||
**kwargs,
|
||||
)
|
||||
@@ -110,137 +110,3 @@ class LvSelect(LvType):
|
||||
parents=parens,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class WidgetType:
|
||||
"""
|
||||
Describes a type of Widget, e.g. "bar" or "line"
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
w_type: LvType,
|
||||
parts: tuple,
|
||||
schema=None,
|
||||
modify_schema=None,
|
||||
lv_name=None,
|
||||
is_mock: bool = False,
|
||||
):
|
||||
"""
|
||||
:param name: The widget name, e.g. "bar"
|
||||
:param w_type: The C type of the widget
|
||||
:param parts: What parts this widget supports
|
||||
:param schema: The config schema for defining a widget
|
||||
:param modify_schema: A schema to update the widget, defaults to the same as the schema
|
||||
:param lv_name: The name of the LVGL widget in the LVGL library, if different from the name
|
||||
:param is_mock: Whether this widget is a mock widget, i.e. not a real LVGL widget
|
||||
"""
|
||||
self.name = name
|
||||
self.lv_name = lv_name or name
|
||||
self.w_type = w_type
|
||||
self.parts = parts
|
||||
if not isinstance(schema, Schema):
|
||||
schema = Schema(schema or {})
|
||||
self.schema = schema
|
||||
if modify_schema is None:
|
||||
modify_schema = schema
|
||||
if not isinstance(modify_schema, Schema):
|
||||
modify_schema = Schema(modify_schema)
|
||||
self.modify_schema = modify_schema
|
||||
self.mock_obj = MockObj(f"lv_{self.lv_name}", "_")
|
||||
|
||||
# Local import to avoid circular import
|
||||
from .automation import update_to_code
|
||||
from .schemas import WIDGET_TYPES, base_update_schema
|
||||
|
||||
if not is_mock:
|
||||
if self.name in WIDGET_TYPES:
|
||||
raise EsphomeError(f"Duplicate definition of widget type '{self.name}'")
|
||||
WIDGET_TYPES[self.name] = self
|
||||
|
||||
# Register the update action automatically, adding widget-specific properties
|
||||
register_action(
|
||||
f"lvgl.{self.name}.update",
|
||||
ObjUpdateAction,
|
||||
base_update_schema(self, self.parts).extend(self.modify_schema),
|
||||
synchronous=True,
|
||||
)(update_to_code)
|
||||
|
||||
@property
|
||||
def animated(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def required_component(self):
|
||||
return None
|
||||
|
||||
def is_compound(self):
|
||||
return self.w_type.inherits_from(LvCompound)
|
||||
|
||||
async def to_code(self, w, config: dict):
|
||||
"""
|
||||
Generate code for a given widget
|
||||
:param w: The widget
|
||||
:param config: Its configuration
|
||||
"""
|
||||
|
||||
async def obj_creator(self, parent: MockObjClass, config: dict):
|
||||
"""
|
||||
Create an instance of the widget type
|
||||
:param parent: The parent to which it should be attached
|
||||
:param config: Its configuration
|
||||
:return: Generated code as a single text line
|
||||
"""
|
||||
return lv_expr.call(f"{self.lv_name}_create", parent)
|
||||
|
||||
def on_create(self, var: MockObj, config: dict):
|
||||
"""
|
||||
Called from to_code when the widget is created, to set up any initial properties
|
||||
:param var: The variable representing the widget
|
||||
:param config: Its configuration
|
||||
"""
|
||||
|
||||
def get_uses(self):
|
||||
"""
|
||||
Get a list of other widgets used by this one
|
||||
:return:
|
||||
"""
|
||||
return ()
|
||||
|
||||
def get_max(self, config: dict):
|
||||
return sys.maxsize
|
||||
|
||||
def get_min(self, config: dict):
|
||||
return -sys.maxsize
|
||||
|
||||
def get_step(self, config: dict):
|
||||
return 1
|
||||
|
||||
def get_scale(self, config: dict):
|
||||
return 1.0
|
||||
|
||||
def validate(self, value):
|
||||
"""
|
||||
Provides an opportunity for custom validation for a given widget type
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
return value
|
||||
|
||||
def final_validate(self, widget, update_config, widget_config, path):
|
||||
"""
|
||||
Allow final validation for a given widget type update action
|
||||
:param widget: A widget
|
||||
:param update_config: The configuration for the update action
|
||||
:param widget_config: The configuration for the widget itself
|
||||
:param path: The path to the widget, for error reporting
|
||||
"""
|
||||
|
||||
|
||||
class NumberType(WidgetType):
|
||||
def get_max(self, config: dict):
|
||||
return int(config.get(CONF_MAX_VALUE, 100))
|
||||
|
||||
def get_min(self, config: dict):
|
||||
return int(config.get(CONF_MIN_VALUE, 0))
|
||||
|
||||
@@ -2,9 +2,18 @@ import sys
|
||||
from typing import Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.const import CONF_DEFAULT, CONF_GROUP, CONF_ID, CONF_STATE, CONF_TYPE
|
||||
from esphome.core import ID, TimePeriod
|
||||
from esphome.automation import register_action
|
||||
from esphome.config_validation import Invalid, Schema
|
||||
from esphome.const import (
|
||||
CONF_DEFAULT,
|
||||
CONF_GROUP,
|
||||
CONF_ID,
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_STATE,
|
||||
CONF_TYPE,
|
||||
)
|
||||
from esphome.core import ID, EsphomeError, TimePeriod
|
||||
from esphome.coroutine import FakeAwaitable
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
@@ -21,6 +30,7 @@ from ..defines import (
|
||||
CONF_MAIN,
|
||||
CONF_PAD_COLUMN,
|
||||
CONF_PAD_ROW,
|
||||
CONF_SCALE,
|
||||
CONF_STYLES,
|
||||
CONF_WIDGETS,
|
||||
OBJ_FLAGS,
|
||||
@@ -44,8 +54,15 @@ from ..lvcode import (
|
||||
lv_obj,
|
||||
lv_Pvariable,
|
||||
)
|
||||
from ..schemas import ALL_STYLES, OBJ_PROPERTIES, STYLE_REMAP, WIDGET_TYPES
|
||||
from ..types import LV_STATE, LvType, WidgetType, lv_coord_t, lv_obj_t, lv_obj_t_ptr
|
||||
from ..types import (
|
||||
LV_STATE,
|
||||
LvCompound,
|
||||
LvType,
|
||||
ObjUpdateAction,
|
||||
lv_coord_t,
|
||||
lv_obj_t,
|
||||
lv_obj_t_ptr,
|
||||
)
|
||||
|
||||
EVENT_LAMB = "event_lamb__"
|
||||
|
||||
@@ -53,6 +70,171 @@ theme_widget_map = {}
|
||||
styles_used = set()
|
||||
|
||||
|
||||
class WidgetType:
|
||||
"""
|
||||
Describes a type of Widget, e.g. "bar" or "line"
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
w_type: LvType,
|
||||
parts: tuple,
|
||||
schema=None,
|
||||
modify_schema=None,
|
||||
lv_name=None,
|
||||
is_mock: bool = False,
|
||||
):
|
||||
"""
|
||||
:param name: The widget name, e.g. "bar"
|
||||
:param w_type: The C type of the widget
|
||||
:param parts: What parts this widget supports
|
||||
:param schema: The config schema for defining a widget
|
||||
:param modify_schema: A schema to update the widget, defaults to the same as the schema
|
||||
:param lv_name: The name of the LVGL widget in the LVGL library, if different from the name
|
||||
:param is_mock: Whether this widget is a mock widget, i.e. not a real LVGL widget
|
||||
"""
|
||||
self.name = name
|
||||
self.lv_name = lv_name or name
|
||||
self.w_type = w_type
|
||||
self.parts = parts
|
||||
if not isinstance(schema, Schema):
|
||||
schema = Schema(schema or {})
|
||||
self.schema = schema
|
||||
if modify_schema is None:
|
||||
modify_schema = schema
|
||||
if not isinstance(modify_schema, Schema):
|
||||
modify_schema = Schema(modify_schema)
|
||||
self.modify_schema = modify_schema
|
||||
self.mock_obj = MockObj(f"lv_{self.lv_name}", "_")
|
||||
|
||||
# Local import to avoid circular import
|
||||
from ..automation import update_to_code
|
||||
from ..schemas import WIDGET_TYPES, base_update_schema
|
||||
|
||||
if not is_mock:
|
||||
if self.name in WIDGET_TYPES:
|
||||
raise EsphomeError(f"Duplicate definition of widget type '{self.name}'")
|
||||
WIDGET_TYPES[self.name] = self
|
||||
|
||||
# Register the update action automatically, adding widget-specific properties
|
||||
register_action(
|
||||
f"lvgl.{self.name}.update",
|
||||
ObjUpdateAction,
|
||||
base_update_schema(self, self.parts).extend(self.modify_schema),
|
||||
synchronous=True,
|
||||
)(update_to_code)
|
||||
|
||||
@property
|
||||
def animated(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def required_component(self):
|
||||
return None
|
||||
|
||||
def is_compound(self):
|
||||
return self.w_type.inherits_from(LvCompound)
|
||||
|
||||
async def create_to_code(self, config: dict, parent: MockObj) -> "Widget":
|
||||
"""
|
||||
Generate code for a widget creation.
|
||||
:param config: The configuration for the widget
|
||||
:param parent: The parent to which it should be attached
|
||||
"""
|
||||
|
||||
creator = await self.obj_creator(parent, config)
|
||||
add_lv_use(self.name)
|
||||
add_lv_use(*self.get_uses())
|
||||
wid = config[CONF_ID]
|
||||
add_line_marks(wid)
|
||||
if self.is_compound():
|
||||
var = cg.new_Pvariable(wid)
|
||||
lv_add(var.set_obj(creator))
|
||||
await self.on_create(var.obj, config)
|
||||
else:
|
||||
var = lv_Pvariable(lv_obj_t, wid)
|
||||
lv_assign(var, creator)
|
||||
await self.on_create(var, config)
|
||||
|
||||
w = Widget.create(wid, var, self, config)
|
||||
if theme := theme_widget_map.get(self.w_type.name):
|
||||
for part, states in theme.items():
|
||||
part = "LV_PART_" + part.upper()
|
||||
for state, style in states.items():
|
||||
state = "LV_STATE_" + state.upper()
|
||||
if state == "LV_STATE_DEFAULT":
|
||||
lv_state = literal(part)
|
||||
elif part == "LV_PART_MAIN":
|
||||
lv_state = literal(state)
|
||||
else:
|
||||
lv_state = join_enums((state, part))
|
||||
w.add_style(style, lv_state)
|
||||
await set_obj_properties(w, config)
|
||||
await add_widgets(w, config)
|
||||
await self.to_code(w, config)
|
||||
return w
|
||||
|
||||
async def to_code(self, w: "Widget", config: dict):
|
||||
"""
|
||||
Update a widget, also called when creating
|
||||
:param config:
|
||||
:return:
|
||||
"""
|
||||
|
||||
async def obj_creator(self, parent: MockObj, config: dict):
|
||||
"""
|
||||
Create an instance of the widget type
|
||||
:param parent: The parent to which it should be attached
|
||||
:param config: Its configuration
|
||||
:return: Generated code as a single text line
|
||||
"""
|
||||
return lv_expr.call(f"{self.lv_name}_create", parent)
|
||||
|
||||
async def on_create(self, var: MockObj, config: dict):
|
||||
"""
|
||||
Called from to_code when the widget is created, to set up any initial properties
|
||||
:param var: The variable representing the widget
|
||||
:param config: Its configuration
|
||||
"""
|
||||
|
||||
def get_uses(self):
|
||||
"""
|
||||
Get a list of other widgets used by this one
|
||||
:return:
|
||||
"""
|
||||
return ()
|
||||
|
||||
def get_max(self, config: dict):
|
||||
return sys.maxsize
|
||||
|
||||
def get_min(self, config: dict):
|
||||
return -sys.maxsize
|
||||
|
||||
def get_step(self, config: dict):
|
||||
return 1
|
||||
|
||||
def get_scale(self, config: dict):
|
||||
return 1.0
|
||||
|
||||
def validate(self, value):
|
||||
"""
|
||||
Provides an opportunity for custom validation for a given widget type
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
return value
|
||||
|
||||
def final_validate(self, widget, update_config, widget_config, path):
|
||||
"""
|
||||
Allow final validation for a given widget type update action
|
||||
:param widget: A widget
|
||||
:param update_config: The configuration for the update action
|
||||
:param widget_config: The configuration for the widget itself
|
||||
:param path: The path to the widget, for error reporting
|
||||
"""
|
||||
|
||||
|
||||
class Widget:
|
||||
"""
|
||||
Represents a Widget.
|
||||
@@ -74,19 +256,25 @@ class Widget:
|
||||
self.obj = var
|
||||
self.outer = None
|
||||
self.move_to_foreground = False
|
||||
# Properties for linear equations
|
||||
self.slope = None
|
||||
self.y_int = None
|
||||
|
||||
@staticmethod
|
||||
def create(name, var, wtype: WidgetType, config: dict = None):
|
||||
w = Widget(var, wtype, config)
|
||||
if name is not None:
|
||||
widget_map[name] = w
|
||||
widget_map[name] = w
|
||||
return w
|
||||
|
||||
def add_state(self, state):
|
||||
if "|" in state:
|
||||
state = f"(lv_state_t)({state})"
|
||||
return lv_obj.add_state(self.obj, literal(state))
|
||||
|
||||
def clear_state(self, state):
|
||||
return lv_obj.clear_state(self.obj, literal(state))
|
||||
if "|" in state:
|
||||
state = f"(lv_state_t)({state})"
|
||||
return lv_obj.remove_state(self.obj, literal(state))
|
||||
|
||||
def has_state(self, state):
|
||||
return (lv_expr.obj_get_state(self.obj) & literal(state)) != 0
|
||||
@@ -98,12 +286,23 @@ class Widget:
|
||||
return self.has_state(LV_STATE.CHECKED)
|
||||
|
||||
def add_flag(self, flag):
|
||||
if "|" in flag:
|
||||
flag = f"(lv_obj_flag_t)({flag})"
|
||||
return lv_obj.add_flag(self.obj, literal(flag))
|
||||
|
||||
def clear_flag(self, flag):
|
||||
return lv_obj.clear_flag(self.obj, literal(flag))
|
||||
if "|" in flag:
|
||||
flag = f"(lv_obj_flag_t)({flag})"
|
||||
return lv_obj.remove_flag(self.obj, literal(flag))
|
||||
|
||||
async def set_property(self, prop, value, animated: bool = None, lv_name=None):
|
||||
def add_style(self, style_id, state=LV_STATE.DEFAULT):
|
||||
if "|" in state:
|
||||
state = f"(lv_state_t)({state})"
|
||||
lv_obj.add_style(self.obj, MockObj(style_id), literal(state))
|
||||
|
||||
async def set_property(
|
||||
self, prop, value, animated: bool = None, lv_name=None, processor=None
|
||||
):
|
||||
"""
|
||||
Set a property of the widget.
|
||||
:param prop: The property name
|
||||
@@ -111,18 +310,28 @@ class Widget:
|
||||
:param animated: If the change should be animated
|
||||
:param lv_name: The base type of the widget e.g. "obj"
|
||||
"""
|
||||
|
||||
from ..schemas import ALL_STYLES, remap_property
|
||||
|
||||
if isinstance(value, dict):
|
||||
value = value.get(prop)
|
||||
if isinstance(ALL_STYLES.get(prop), LValidator):
|
||||
value = await ALL_STYLES[prop].process(value)
|
||||
else:
|
||||
value = literal(value)
|
||||
if value is None:
|
||||
if value is None:
|
||||
return
|
||||
if not processor and isinstance(ALL_STYLES.get(prop), LValidator):
|
||||
processor = ALL_STYLES[prop]
|
||||
if isinstance(processor, LValidator):
|
||||
processor = processor.process
|
||||
if processor:
|
||||
value = await processor(value)
|
||||
elif value is None:
|
||||
return
|
||||
prop = remap_property(prop)
|
||||
if isinstance(value, TimePeriod):
|
||||
value = value.total_milliseconds
|
||||
if isinstance(value, str):
|
||||
elif isinstance(value, str):
|
||||
value = literal(value)
|
||||
elif isinstance(value, ID):
|
||||
value = MockObj(value)
|
||||
lv_name = lv_name or self.type.lv_name
|
||||
if animated is None or self.type.animated is not True:
|
||||
lv.call(f"{lv_name}_set_{prop}", self.obj, value)
|
||||
@@ -138,10 +347,12 @@ class Widget:
|
||||
ltype = ltype or self.__type_base()
|
||||
return cg.RawExpression(f"lv_{ltype}_get_{prop}({self.obj})")
|
||||
|
||||
def set_style(self, prop, value, state):
|
||||
def set_style(self, prop, value, state=LV_STATE.DEFAULT):
|
||||
if value is None:
|
||||
return
|
||||
styles_used.add(prop)
|
||||
if isinstance(value, str):
|
||||
value = literal(value)
|
||||
lv.call(f"obj_set_style_{prop}", self.obj, value, state)
|
||||
|
||||
def __type_base(self):
|
||||
@@ -189,15 +400,6 @@ class Widget:
|
||||
"""
|
||||
return
|
||||
|
||||
def get_max(self):
|
||||
return self.type.get_max(self.config)
|
||||
|
||||
def get_min(self):
|
||||
return self.type.get_min(self.config)
|
||||
|
||||
def get_step(self):
|
||||
return self.type.get_step(self.config)
|
||||
|
||||
def get_scale(self):
|
||||
return self.type.get_scale(self.config)
|
||||
|
||||
@@ -212,14 +414,14 @@ class LvScrActType(WidgetType):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("lv_scr_act()", lv_obj_t, (), is_mock=True)
|
||||
super().__init__("lv_screen_active()", lv_obj_t, (), is_mock=True)
|
||||
|
||||
async def to_code(self, w, config: dict):
|
||||
return []
|
||||
pass
|
||||
|
||||
|
||||
def get_scr_act(lv_comp: MockObj) -> Widget:
|
||||
return Widget.create(None, lv_comp.get_scr_act(), LvScrActType(), {})
|
||||
def get_screen_active(lv_comp: MockObj) -> Widget:
|
||||
return Widget(lv_comp.get_screen_active(), LvScrActType(), {})
|
||||
|
||||
|
||||
def get_widget_generator(wid):
|
||||
@@ -271,10 +473,17 @@ def collect_props(config):
|
||||
:param config:
|
||||
:return:
|
||||
"""
|
||||
|
||||
from ..schemas import ALL_STYLES
|
||||
|
||||
props = {}
|
||||
for prop in [*ALL_STYLES, *OBJ_FLAGS, CONF_STYLES, CONF_GROUP]:
|
||||
if prop in config:
|
||||
props[prop] = config[prop]
|
||||
if prop == CONF_SCALE:
|
||||
props[CONF_SCALE + "_x"] = config[prop]
|
||||
props[CONF_SCALE + "_y"] = config[prop]
|
||||
else:
|
||||
props[prop] = config[prop]
|
||||
return props
|
||||
|
||||
|
||||
@@ -304,34 +513,41 @@ def collect_parts(config):
|
||||
return parts
|
||||
|
||||
|
||||
def _size_to_str(value):
|
||||
if isinstance(value, float):
|
||||
return f"lv_pct({int(value * 100)})"
|
||||
return str(value)
|
||||
|
||||
|
||||
async def set_obj_properties(w: Widget, config):
|
||||
"""Generate a list of C++ statements to apply properties to an lv_obj_t"""
|
||||
|
||||
from ..schemas import ALL_STYLES, OBJ_PROPERTIES, remap_property
|
||||
|
||||
if layout := config.get(CONF_LAYOUT):
|
||||
layout_type: str = layout[CONF_TYPE]
|
||||
add_lv_use(layout_type)
|
||||
lv_obj.set_layout(w.obj, literal(f"LV_LAYOUT_{layout_type.upper()}"))
|
||||
if (pad_row := layout.get(CONF_PAD_ROW)) is not None:
|
||||
w.set_style(CONF_PAD_ROW, pad_row, 0)
|
||||
w.set_style(CONF_PAD_ROW, pad_row)
|
||||
if (pad_column := layout.get(CONF_PAD_COLUMN)) is not None:
|
||||
w.set_style(CONF_PAD_COLUMN, pad_column, 0)
|
||||
w.set_style(CONF_PAD_COLUMN, pad_column)
|
||||
if layout_type == TYPE_GRID:
|
||||
wid = config[CONF_ID]
|
||||
rows = [str(x) for x in layout[CONF_GRID_ROWS]]
|
||||
rows = [_size_to_str(x) for x in layout[CONF_GRID_ROWS]]
|
||||
rows = "{" + ",".join(rows) + ", LV_GRID_TEMPLATE_LAST}"
|
||||
row_id = ID(f"{wid}_row_dsc", is_declaration=True, type=lv_coord_t)
|
||||
row_array = cg.static_const_array(row_id, cg.RawExpression(rows))
|
||||
w.set_style("grid_row_dsc_array", row_array, 0)
|
||||
columns = [str(x) for x in layout[CONF_GRID_COLUMNS]]
|
||||
w.set_style("grid_row_dsc_array", row_array)
|
||||
columns = [_size_to_str(x) for x in layout[CONF_GRID_COLUMNS]]
|
||||
columns = "{" + ",".join(columns) + ", LV_GRID_TEMPLATE_LAST}"
|
||||
column_id = ID(f"{wid}_column_dsc", is_declaration=True, type=lv_coord_t)
|
||||
column_array = cg.static_const_array(column_id, cg.RawExpression(columns))
|
||||
w.set_style("grid_column_dsc_array", column_array, 0)
|
||||
w.set_style("grid_column_dsc_array", column_array)
|
||||
w.set_style(
|
||||
CONF_GRID_COLUMN_ALIGN, literal(layout.get(CONF_GRID_COLUMN_ALIGN)), 0
|
||||
)
|
||||
w.set_style(
|
||||
CONF_GRID_ROW_ALIGN, literal(layout.get(CONF_GRID_ROW_ALIGN)), 0
|
||||
CONF_GRID_COLUMN_ALIGN, literal(layout.get(CONF_GRID_COLUMN_ALIGN))
|
||||
)
|
||||
w.set_style(CONF_GRID_ROW_ALIGN, literal(layout.get(CONF_GRID_ROW_ALIGN)))
|
||||
if layout_type == TYPE_FLEX:
|
||||
lv_obj.set_flex_flow(w.obj, literal(layout[CONF_FLEX_FLOW]))
|
||||
main = literal(layout[CONF_FLEX_ALIGN_MAIN])
|
||||
@@ -353,13 +569,13 @@ async def set_obj_properties(w: Widget, config):
|
||||
else:
|
||||
lv_state = join_enums((state, part))
|
||||
for style_id in props.get(CONF_STYLES, ()):
|
||||
lv_obj.add_style(w.obj, MockObj(style_id), lv_state)
|
||||
w.add_style(style_id, lv_state)
|
||||
for prop, value in {
|
||||
k: v for k, v in props.items() if k in ALL_STYLES
|
||||
}.items():
|
||||
if isinstance(ALL_STYLES[prop], LValidator):
|
||||
value = await ALL_STYLES[prop].process(value)
|
||||
prop_r = STYLE_REMAP.get(prop, prop)
|
||||
prop_r = remap_property(prop)
|
||||
w.set_style(prop_r, value, lv_state)
|
||||
if group := config.get(CONF_GROUP):
|
||||
group = await cg.get_variable(group)
|
||||
@@ -429,7 +645,7 @@ async def add_widgets(parent: Widget, config: dict):
|
||||
await widget_to_code(w_cnfig, w_type, parent.obj)
|
||||
|
||||
|
||||
async def widget_to_code(w_cnfig, w_type: WidgetType, parent):
|
||||
async def widget_to_code(w_cnfig, w_type: WidgetType | str, parent) -> Widget:
|
||||
"""
|
||||
Converts a Widget definition to C code.
|
||||
:param w_cnfig: The widget configuration
|
||||
@@ -437,34 +653,18 @@ async def widget_to_code(w_cnfig, w_type: WidgetType, parent):
|
||||
:param parent: The parent to which the widget should be added
|
||||
:return:
|
||||
"""
|
||||
spec: WidgetType = WIDGET_TYPES[w_type]
|
||||
creator = await spec.obj_creator(parent, w_cnfig)
|
||||
add_lv_use(spec.name)
|
||||
add_lv_use(*spec.get_uses())
|
||||
wid = w_cnfig[CONF_ID]
|
||||
add_line_marks(wid)
|
||||
if spec.is_compound():
|
||||
var = cg.new_Pvariable(wid)
|
||||
lv_add(var.set_obj(creator))
|
||||
spec.on_create(var.obj, w_cnfig)
|
||||
else:
|
||||
var = lv_Pvariable(lv_obj_t, wid)
|
||||
lv_assign(var, creator)
|
||||
spec.on_create(var, w_cnfig)
|
||||
|
||||
w = Widget.create(wid, var, spec, w_cnfig)
|
||||
if theme := theme_widget_map.get(w_type):
|
||||
for part, states in theme.items():
|
||||
part = "LV_PART_" + part.upper()
|
||||
for state, style in states.items():
|
||||
state = "LV_STATE_" + state.upper()
|
||||
if state == "LV_STATE_DEFAULT":
|
||||
lv_state = literal(part)
|
||||
elif part == "LV_PART_MAIN":
|
||||
lv_state = literal(state)
|
||||
else:
|
||||
lv_state = join_enums((state, part))
|
||||
lv.obj_add_style(w.obj, style, lv_state)
|
||||
await set_obj_properties(w, w_cnfig)
|
||||
await add_widgets(w, w_cnfig)
|
||||
await spec.to_code(w, w_cnfig)
|
||||
from ..schemas import WIDGET_TYPES
|
||||
|
||||
spec: WidgetType = (
|
||||
w_type if isinstance(w_type, WidgetType) else WIDGET_TYPES[w_type]
|
||||
)
|
||||
return await spec.create_to_code(w_cnfig, parent)
|
||||
|
||||
|
||||
class NumberType(WidgetType):
|
||||
def get_max(self, config: dict):
|
||||
return int(config.get(CONF_MAX_VALUE, 100))
|
||||
|
||||
def get_min(self, config: dict):
|
||||
return int(config.get(CONF_MIN_VALUE, 0))
|
||||
|
||||
@@ -18,7 +18,8 @@ from ..defines import (
|
||||
CONF_KNOB,
|
||||
CONF_MAIN,
|
||||
CONF_START_ANGLE,
|
||||
literal,
|
||||
LV_OBJ_FLAG,
|
||||
LV_PART,
|
||||
)
|
||||
from ..lv_validation import (
|
||||
get_start_value,
|
||||
@@ -28,8 +29,8 @@ from ..lv_validation import (
|
||||
lv_positive_int,
|
||||
)
|
||||
from ..lvcode import lv, lv_expr, lv_obj
|
||||
from ..types import LvNumber, NumberType
|
||||
from . import Widget
|
||||
from ..types import LvNumber
|
||||
from . import NumberType, Widget
|
||||
|
||||
CONF_ARC = "arc"
|
||||
ARC_SCHEMA = cv.Schema(
|
||||
@@ -71,39 +72,17 @@ class ArcType(NumberType):
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
if CONF_MIN_VALUE in config and CONF_MAX_VALUE in config:
|
||||
max_value = await lv_int.process(config[CONF_MAX_VALUE])
|
||||
min_value = await lv_int.process(config[CONF_MIN_VALUE])
|
||||
lv.arc_set_range(w.obj, min_value, max_value)
|
||||
elif CONF_MIN_VALUE in config:
|
||||
max_value = w.get_property(CONF_MAX_VALUE)
|
||||
min_value = await lv_int.process(config[CONF_MIN_VALUE])
|
||||
lv.arc_set_range(w.obj, min_value, max_value)
|
||||
elif CONF_MAX_VALUE in config:
|
||||
max_value = await lv_int.process(config[CONF_MAX_VALUE])
|
||||
min_value = w.get_property(CONF_MIN_VALUE)
|
||||
lv.arc_set_range(w.obj, min_value, max_value)
|
||||
|
||||
await w.set_property(
|
||||
"bg_start_angle",
|
||||
await lv_angle_degrees.process(config.get(CONF_START_ANGLE)),
|
||||
)
|
||||
await w.set_property(
|
||||
"bg_end_angle", await lv_angle_degrees.process(config.get(CONF_END_ANGLE))
|
||||
)
|
||||
await w.set_property(
|
||||
CONF_ROTATION, await lv_angle_degrees.process(config.get(CONF_ROTATION))
|
||||
)
|
||||
await w.set_property(CONF_MODE, config)
|
||||
await w.set_property(
|
||||
CONF_CHANGE_RATE,
|
||||
await lv_positive_int.process(config.get(CONF_CHANGE_RATE)),
|
||||
)
|
||||
|
||||
for prop, validator in ARC_MODIFY_SCHEMA.schema.items():
|
||||
if prop != CONF_VALUE:
|
||||
# start_angle and end_angle are mapped to bg_start_angle and bg_end_angle
|
||||
prop = str(prop)
|
||||
if prop.endswith("_angle"):
|
||||
prop = "bg_" + prop
|
||||
await w.set_property(prop, config, processor=validator)
|
||||
if CONF_ADJUSTABLE in config:
|
||||
if not config[CONF_ADJUSTABLE]:
|
||||
lv_obj.remove_style(w.obj, nullptr, literal("LV_PART_KNOB"))
|
||||
w.clear_flag("LV_OBJ_FLAG_CLICKABLE")
|
||||
lv_obj.remove_style(w.obj, nullptr, LV_PART.KNOB)
|
||||
w.clear_flag(LV_OBJ_FLAG.CLICKABLE)
|
||||
elif CONF_GROUP not in config:
|
||||
# For some reason arc does not get automatically added to the default group
|
||||
lv.group_add_obj(lv_expr.group_get_default(), w.obj)
|
||||
|
||||
@@ -7,11 +7,11 @@ from ..helpers import add_lv_use
|
||||
from ..lv_validation import lv_text
|
||||
from ..lvcode import lv, lv_expr
|
||||
from ..schemas import TEXT_SCHEMA
|
||||
from ..types import LvBoolean, WidgetType
|
||||
from . import Widget
|
||||
from ..types import LvBoolean
|
||||
from . import Widget, WidgetType
|
||||
from .label import label_spec
|
||||
|
||||
lv_button_t = LvBoolean("lv_btn_t")
|
||||
lv_button_t = LvBoolean("lv_button_t")
|
||||
|
||||
|
||||
class ButtonType(WidgetType):
|
||||
@@ -30,7 +30,7 @@ class ButtonType(WidgetType):
|
||||
def get_uses(self):
|
||||
return ("btn",)
|
||||
|
||||
def on_create(self, var: MockObj, config: dict):
|
||||
async def on_create(self, var: MockObj, config: dict):
|
||||
if CONF_TEXT in config:
|
||||
lv.label_create(var)
|
||||
return var
|
||||
|
||||
@@ -118,15 +118,15 @@ class MatrixButton(Widget):
|
||||
|
||||
def has_state(self, state):
|
||||
state = self.map_ctrls(state)
|
||||
return lv_expr.btnmatrix_has_btn_ctrl(self.obj, self.index, state)
|
||||
return lv_expr.buttonmatrix_has_button_ctrl(self.obj, self.index, state)
|
||||
|
||||
def add_state(self, state):
|
||||
state = self.map_ctrls(state)
|
||||
return lv.btnmatrix_set_btn_ctrl(self.obj, self.index, state)
|
||||
return lv.buttonmatrix_set_button_ctrl(self.obj, self.index, state)
|
||||
|
||||
def clear_state(self, state):
|
||||
state = self.map_ctrls(state)
|
||||
return lv.btnmatrix_clear_btn_ctrl(self.obj, self.index, state)
|
||||
return lv.buttonmatrix_clear_button_ctrl(self.obj, self.index, state)
|
||||
|
||||
def is_pressed(self):
|
||||
return self.is_selected() & self.parent.has_state(LV_STATE.PRESSED)
|
||||
@@ -161,7 +161,7 @@ async def get_button_data(config, buttonmatrix: Widget):
|
||||
text_list.append(button_conf.get(CONF_TEXT) or "")
|
||||
key_list.append(button_conf.get(CONF_KEY_CODE) or 0)
|
||||
width_list.append(button_conf[CONF_WIDTH])
|
||||
ctrl = ["LV_BTNMATRIX_CTRL_CLICK_TRIG"]
|
||||
ctrl = ["CLICK_TRIG"]
|
||||
for item in button_conf.get(CONF_CONTROL, ()):
|
||||
ctrl.extend([k for k, v in item.items() if v])
|
||||
ctrl_list.append(await BUTTONMATRIX_CTRLS.process(ctrl))
|
||||
@@ -187,7 +187,7 @@ class ButtonMatrixType(WidgetType):
|
||||
(CONF_MAIN, CONF_ITEMS),
|
||||
BUTTONMATRIX_SCHEMA,
|
||||
{},
|
||||
lv_name="btnmatrix",
|
||||
lv_name="buttonmatrix",
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
@@ -199,22 +199,22 @@ class ButtonMatrixType(WidgetType):
|
||||
)
|
||||
text_id = config[CONF_BUTTON_TEXT_LIST_ID]
|
||||
text_id = cg.static_const_array(text_id, text_list)
|
||||
lv.btnmatrix_set_map(w.obj, text_id)
|
||||
lv.buttonmatrix_set_map(w.obj, text_id)
|
||||
set_btn_data(w.obj, ctrl_list, width_list)
|
||||
lv.btnmatrix_set_one_checked(w.obj, config[CONF_ONE_CHECKED])
|
||||
lv.buttonmatrix_set_one_checked(w.obj, config[CONF_ONE_CHECKED])
|
||||
for index, key in enumerate(key_list):
|
||||
if key != 0:
|
||||
lv_add(w.var.set_key(index, key))
|
||||
|
||||
def get_uses(self):
|
||||
return ("btnmatrix",)
|
||||
return ("buttonmatrix",)
|
||||
|
||||
|
||||
def set_btn_data(obj, ctrl_list, width_list):
|
||||
for index, ctrl in enumerate(ctrl_list):
|
||||
lv.btnmatrix_set_btn_ctrl(obj, index, ctrl)
|
||||
lv.buttonmatrix_set_button_ctrl(obj, index, ctrl)
|
||||
for index, width in enumerate(width_list):
|
||||
lv.btnmatrix_set_btn_width(obj, index, width)
|
||||
lv.buttonmatrix_set_button_width(obj, index, width)
|
||||
|
||||
|
||||
buttonmatrix_spec = ButtonMatrixType()
|
||||
@@ -253,25 +253,21 @@ async def button_update_to_code(config, action_id, template_arg, args):
|
||||
|
||||
async def do_button_update(w):
|
||||
if (width := config.get(CONF_WIDTH)) is not None:
|
||||
lv.btnmatrix_set_btn_width(w.obj, w.index, width)
|
||||
lv.buttonmatrix_set_button_width(w.obj, w.index, width)
|
||||
if config.get(CONF_SELECTED):
|
||||
lv.btnmatrix_set_selected_btn(w.obj, w.index)
|
||||
lv.buttonmatrix_set_selected_button(w.obj, w.index)
|
||||
if controls := config.get(CONF_CONTROL):
|
||||
adds = []
|
||||
clrs = []
|
||||
for item in controls:
|
||||
adds.extend(
|
||||
[f"LV_BTNMATRIX_CTRL_{k.upper()}" for k, v in item.items() if v]
|
||||
)
|
||||
clrs.extend(
|
||||
[f"LV_BTNMATRIX_CTRL_{k.upper()}" for k, v in item.items() if not v]
|
||||
)
|
||||
adds.extend([f"{k.upper()}" for k, v in item.items() if v])
|
||||
clrs.extend([f"{k.upper()}" for k, v in item.items() if not v])
|
||||
if adds:
|
||||
lv.btnmatrix_set_btn_ctrl(
|
||||
lv.buttonmatrix_set_button_ctrl(
|
||||
w.obj, w.index, await BUTTONMATRIX_CTRLS.process(adds)
|
||||
)
|
||||
if clrs:
|
||||
lv.btnmatrix_clear_btn_ctrl(
|
||||
lv.buttonmatrix_clear_button_ctrl(
|
||||
w.obj, w.index, await BUTTONMATRIX_CTRLS.process(clrs)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
"""
|
||||
LVGL 9.4 Canvas Widget Implementation
|
||||
|
||||
This module implements the canvas widget for LVGL 9.4. Key changes from LVGL 8.4:
|
||||
|
||||
1. Buffer allocation:
|
||||
- LV_IMG_CF_TRUE_COLOR → LV_COLOR_FORMAT_RGB565
|
||||
- LV_IMG_CF_TRUE_COLOR_ALPHA → LV_COLOR_FORMAT_ARGB8888
|
||||
- LV_CANVAS_BUF_SIZE_TRUE_COLOR → LV_CANVAS_BUF_SIZE(w, h, bpp, stride)
|
||||
|
||||
2. Drawing API:
|
||||
- All lv_canvas_draw_* functions removed
|
||||
- Use layer-based drawing: lv_canvas_init_layer() / lv_canvas_finish_layer()
|
||||
- Draw using low-level lv_draw_* functions (rect, line, arc, image, label)
|
||||
|
||||
3. Pixel operations:
|
||||
- lv_canvas_set_px_color + lv_canvas_set_px_opa → lv_canvas_set_px(color, opa)
|
||||
"""
|
||||
|
||||
from esphome import automation, codegen as cg, config_validation as cv
|
||||
from esphome.components.display_menu_base import CONF_LABEL
|
||||
from esphome.const import (
|
||||
@@ -9,7 +28,7 @@ from esphome.const import (
|
||||
CONF_X,
|
||||
CONF_Y,
|
||||
)
|
||||
from esphome.cpp_generator import Literal, MockObj
|
||||
from esphome.cpp_types import FixedVector
|
||||
|
||||
from ..automation import action_to_code
|
||||
from ..defines import (
|
||||
@@ -19,8 +38,11 @@ from ..defines import (
|
||||
CONF_PIVOT_X,
|
||||
CONF_PIVOT_Y,
|
||||
CONF_POINTS,
|
||||
CONF_RADIUS,
|
||||
CONF_SRC,
|
||||
CONF_START_ANGLE,
|
||||
addr,
|
||||
get_color_formats,
|
||||
literal,
|
||||
)
|
||||
from ..lv_validation import (
|
||||
@@ -34,18 +56,20 @@ from ..lv_validation import (
|
||||
size,
|
||||
)
|
||||
from ..lvcode import LocalVariable, lv, lv_assign, lv_expr
|
||||
from ..schemas import STYLE_PROPS, STYLE_REMAP, TEXT_SCHEMA, point_schema
|
||||
from ..types import LvType, ObjUpdateAction, WidgetType
|
||||
from . import Widget, get_widgets
|
||||
from .line import lv_point_t, process_coord
|
||||
from ..schemas import STYLE_PROPS, TEXT_SCHEMA, point_schema, remap_property
|
||||
from ..types import LvType, ObjUpdateAction
|
||||
from . import Widget, WidgetType, get_widgets
|
||||
from .img import CONF_IMAGE
|
||||
from .line import lv_point_precise_t, process_coord
|
||||
|
||||
CONF_CANVAS = "canvas"
|
||||
CONF_BUFFER_ID = "buffer_id"
|
||||
CONF_MAX_WIDTH = "max_width"
|
||||
CONF_TRANSPARENT = "transparent"
|
||||
CONF_RADIUS = "radius"
|
||||
CONF_DRAW_BUF_ID = "draw_buf_id"
|
||||
|
||||
lv_canvas_t = LvType("lv_canvas_t")
|
||||
lv_draw_buf_t = LvType("lv_draw_buf_t")
|
||||
|
||||
|
||||
class CanvasType(WidgetType):
|
||||
@@ -59,32 +83,44 @@ class CanvasType(WidgetType):
|
||||
cv.Required(CONF_WIDTH): size,
|
||||
cv.Required(CONF_HEIGHT): size,
|
||||
cv.Optional(CONF_TRANSPARENT, default=False): cv.boolean,
|
||||
cv.GenerateID(CONF_DRAW_BUF_ID): cv.declare_id(lv_draw_buf_t),
|
||||
}
|
||||
),
|
||||
modify_schema={},
|
||||
)
|
||||
|
||||
def get_uses(self):
|
||||
return "img", CONF_LABEL
|
||||
return CONF_IMAGE, CONF_LABEL
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
width = config[CONF_WIDTH]
|
||||
height = config[CONF_HEIGHT]
|
||||
use_alpha = "_ALPHA" if config[CONF_TRANSPARENT] else ""
|
||||
buf_size = literal(
|
||||
f"LV_CANVAS_BUF_SIZE_TRUE_COLOR{use_alpha}({width}, {height})"
|
||||
# LVGL 9.4: Use LV_COLOR_FORMAT instead of LV_IMG_CF
|
||||
# RGB565 is 16-bit (2 bytes per pixel), ARGB8888 is 32-bit (4 bytes per pixel)
|
||||
if config[CONF_TRANSPARENT]:
|
||||
color_format = "LV_COLOR_FORMAT_ARGB8888"
|
||||
get_color_formats().add("ARGB8888")
|
||||
else:
|
||||
color_format = "LV_COLOR_FORMAT_NATIVE"
|
||||
|
||||
# LVGL 9.4: LV_CANVAS_BUF_SIZE(width, height, bits_per_pixel, stride)
|
||||
# stride is 0 for default (width * bytes_per_pixel)
|
||||
draw_buf = cg.new_Pvariable(config[CONF_DRAW_BUF_ID])
|
||||
buf_size = literal(f"LV_DRAW_BUF_SIZE({width}, {height}, {color_format})")
|
||||
lv.draw_buf_init(
|
||||
draw_buf,
|
||||
width,
|
||||
height,
|
||||
literal(color_format),
|
||||
0,
|
||||
lv_expr.malloc_core(buf_size),
|
||||
literal(buf_size),
|
||||
)
|
||||
with LocalVariable("buf", cg.void, lv_expr.custom_mem_alloc(buf_size)) as buf:
|
||||
cg.add(cg.RawExpression(f"memset({buf}, 0, {buf_size});"))
|
||||
lv.canvas_set_buffer(
|
||||
w.obj,
|
||||
buf,
|
||||
width,
|
||||
height,
|
||||
literal(f"LV_IMG_CF_TRUE_COLOR{use_alpha}"),
|
||||
)
|
||||
lv.draw_buf_set_flag(draw_buf, literal("LV_IMAGE_FLAGS_MODIFIABLE"))
|
||||
lv.canvas_set_draw_buf(w.obj, draw_buf)
|
||||
|
||||
|
||||
canvas_spec = CanvasType()
|
||||
CanvasType()
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
@@ -117,7 +153,7 @@ async def canvas_fill(config, action_id, template_arg, args):
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(lv_canvas_t),
|
||||
cv.Required(CONF_COLOR): lv_color,
|
||||
cv.Optional(CONF_OPA): opacity,
|
||||
cv.Optional(CONF_OPA, default="COVER"): opacity,
|
||||
cv.Required(CONF_POINTS): cv.ensure_list(point_schema),
|
||||
},
|
||||
),
|
||||
@@ -126,7 +162,7 @@ async def canvas_fill(config, action_id, template_arg, args):
|
||||
async def canvas_set_pixel(config, action_id, template_arg, args):
|
||||
widget = await get_widgets(config)
|
||||
color = await lv_color.process(config[CONF_COLOR])
|
||||
opa = await opacity.process(config.get(CONF_OPA))
|
||||
opa = await opacity.process(config.get(CONF_OPA), "COVER")
|
||||
points = [
|
||||
(
|
||||
await pixels.process(p[CONF_X]),
|
||||
@@ -136,25 +172,11 @@ async def canvas_set_pixel(config, action_id, template_arg, args):
|
||||
]
|
||||
|
||||
async def do_set_pixels(w: Widget):
|
||||
if isinstance(color, MockObj):
|
||||
for point in points:
|
||||
x, y = point
|
||||
lv.canvas_set_px_color(w.obj, x, y, color)
|
||||
else:
|
||||
with LocalVariable("color", "lv_color_t", color, modifier="") as color_var:
|
||||
for point in points:
|
||||
x, y = point
|
||||
lv.canvas_set_px_color(w.obj, x, y, color_var)
|
||||
if opa:
|
||||
if isinstance(opa, Literal):
|
||||
for point in points:
|
||||
x, y = point
|
||||
lv.canvas_set_px_opa(w.obj, x, y, opa)
|
||||
else:
|
||||
with LocalVariable("opa", "lv_opa_t", opa, modifier="") as opa_var:
|
||||
for point in points:
|
||||
x, y = point
|
||||
lv.canvas_set_px_opa(w.obj, x, y, opa_var)
|
||||
# LVGL 9.4: lv_canvas_set_px combines color and opacity
|
||||
# Could optimize this for lambda values
|
||||
for point in points:
|
||||
x, y = point
|
||||
lv.canvas_set_px(w.obj, x, y, color, opa)
|
||||
|
||||
return await action_to_code(
|
||||
widget, do_set_pixels, action_id, template_arg, args, config
|
||||
@@ -178,18 +200,21 @@ async def draw_to_code(config, dsc_type, props, do_draw, action_id, template_arg
|
||||
y = await pixels.process(config.get(CONF_Y))
|
||||
|
||||
async def action_func(w: Widget):
|
||||
with LocalVariable("dsc", f"lv_draw_{dsc_type}_dsc_t", modifier="") as dsc:
|
||||
dsc_addr = literal(f"&{dsc}")
|
||||
lv.call(f"draw_{dsc_type}_dsc_init", dsc_addr)
|
||||
if CONF_OPA in config:
|
||||
opa = await opacity.process(config[CONF_OPA])
|
||||
lv_assign(dsc.opa, opa)
|
||||
for prop, validator in props.items():
|
||||
if prop in config:
|
||||
value = await validator.process(config[prop])
|
||||
mapped_prop = STYLE_REMAP.get(prop, prop)
|
||||
lv_assign(getattr(dsc, mapped_prop), value)
|
||||
await do_draw(w, x, y, dsc_addr)
|
||||
# LVGL 9.4: Create a layer for drawing on canvas
|
||||
with LocalVariable("layer", "lv_layer_t", modifier="") as layer:
|
||||
lv.canvas_init_layer(w.obj, addr(layer))
|
||||
with LocalVariable("dsc", f"lv_draw_{dsc_type}_dsc_t", modifier="") as dsc:
|
||||
lv.call(f"draw_{dsc_type}_dsc_init", addr(dsc))
|
||||
if CONF_OPA in config:
|
||||
opa = await opacity.process(config[CONF_OPA])
|
||||
lv_assign(dsc.opa, opa)
|
||||
for prop, validator in props.items():
|
||||
if prop in config:
|
||||
value = await validator.process(config[prop])
|
||||
mapped_prop = remap_property(prop)
|
||||
lv_assign(getattr(dsc, mapped_prop), value)
|
||||
await do_draw(addr(layer), x, y, dsc)
|
||||
lv.canvas_finish_layer(w.obj, addr(layer))
|
||||
|
||||
return await action_to_code(
|
||||
widget, action_func, action_id, template_arg, args, config
|
||||
@@ -212,6 +237,8 @@ RECT_PROPS = {
|
||||
"outline_opa",
|
||||
"shadow_color",
|
||||
"shadow_width",
|
||||
"shadow_offset_x",
|
||||
"shadow_offset_y",
|
||||
"shadow_ofs_x",
|
||||
"shadow_ofs_y",
|
||||
"shadow_spread",
|
||||
@@ -220,6 +247,24 @@ RECT_PROPS = {
|
||||
}
|
||||
|
||||
|
||||
def _draw_line(layer, dsc, points):
|
||||
# LVGL 9.4: Use lv_draw_line for each line segment
|
||||
with (
|
||||
LocalVariable(
|
||||
"points", FixedVector.template(lv_point_precise_t), points, modifier=""
|
||||
) as points_var,
|
||||
LocalVariable("i", "uint32_t", literal("0"), modifier="") as i,
|
||||
):
|
||||
# Draw lines between consecutive points
|
||||
lv.append(
|
||||
cg.RawStatement(f"for ({i} = 0; {i} != {points_var}.size() - 1; {i}++) {{")
|
||||
)
|
||||
lv_assign(dsc.p1, points_var[i])
|
||||
lv_assign(dsc.p2, points_var[i + 1])
|
||||
lv.draw_line(layer, addr(dsc))
|
||||
lv.append(cg.RawStatement("}"))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lvgl.canvas.draw_rectangle",
|
||||
ObjUpdateAction,
|
||||
@@ -237,8 +282,14 @@ async def canvas_draw_rect(config, action_id, template_arg, args):
|
||||
width = await pixels.process(config[CONF_WIDTH])
|
||||
height = await pixels.process(config[CONF_HEIGHT])
|
||||
|
||||
async def do_draw_rect(w: Widget, x, y, dsc_addr):
|
||||
lv.canvas_draw_rect(w.obj, x, y, width, height, dsc_addr)
|
||||
async def do_draw_rect(layer, x, y, dsc):
|
||||
# LVGL 9.4: Use lv_draw_rect with area
|
||||
with LocalVariable("area", "lv_area_t", modifier="") as area:
|
||||
lv_assign(area.x1, x)
|
||||
lv_assign(area.y1, y)
|
||||
lv_assign(area.x2, literal(f"{x} + {width} - 1"))
|
||||
lv_assign(area.y2, literal(f"{y} + {height} - 1"))
|
||||
lv.draw_rect(layer, addr(dsc), addr(area))
|
||||
|
||||
return await draw_to_code(
|
||||
config, "rect", RECT_PROPS, do_draw_rect, action_id, template_arg, args
|
||||
@@ -277,21 +328,55 @@ async def canvas_draw_text(config, action_id, template_arg, args):
|
||||
text = await lv_text.process(config[CONF_TEXT])
|
||||
max_width = await pixels.process(config[CONF_MAX_WIDTH])
|
||||
|
||||
async def do_draw_text(w: Widget, x, y, dsc_addr):
|
||||
lv.canvas_draw_text(w.obj, x, y, max_width, dsc_addr, text)
|
||||
async def do_draw_text(layer, x, y, dsc):
|
||||
# LVGL 9.4: Use lv_draw_label with area and hint
|
||||
with LocalVariable("area", "lv_area_t", modifier="") as area:
|
||||
lv_assign(area.x1, x)
|
||||
lv_assign(area.y1, y)
|
||||
lv_assign(area.x2, literal(f"{x} + {max_width} - 1"))
|
||||
lv_assign(area.y2, literal(f"{y} + LV_COORD_MAX"))
|
||||
lv_assign(dsc.text, text)
|
||||
lv.draw_label(layer, addr(dsc), addr(area))
|
||||
|
||||
return await draw_to_code(
|
||||
config, "label", TEXT_PROPS, do_draw_text, action_id, template_arg, args
|
||||
)
|
||||
|
||||
|
||||
IMG_PROPS = {
|
||||
"angle": STYLE_PROPS["transform_angle"],
|
||||
"zoom": STYLE_PROPS["transform_zoom"],
|
||||
"recolor": STYLE_PROPS["image_recolor"],
|
||||
"recolor_opa": STYLE_PROPS["image_recolor_opa"],
|
||||
"opa": STYLE_PROPS["opa"],
|
||||
}
|
||||
IMG_PROPS = (
|
||||
"angle",
|
||||
"rotation",
|
||||
"scale_x",
|
||||
"scale_y",
|
||||
"skew_x",
|
||||
"skew_y",
|
||||
"scale",
|
||||
"zoom",
|
||||
"recolor",
|
||||
"recolor_opa",
|
||||
"opa",
|
||||
)
|
||||
|
||||
|
||||
def _scale_map(config):
|
||||
config = {remap_property(p): v for p, v in config.items()}
|
||||
if "scale" in config and {"scale_x", "scale_y"} & config.keys():
|
||||
raise cv.Invalid("Cannot specify both scale and scale_x/scale_y")
|
||||
if "scale" in config:
|
||||
config.update({"scale_x": config["scale"], "scale_y": config["scale"]})
|
||||
del config["scale"]
|
||||
return config
|
||||
|
||||
|
||||
def _get_prop_validator(prop):
|
||||
return STYLE_PROPS.get(f"transform_{remap_property(prop)}") or STYLE_PROPS.get(prop)
|
||||
|
||||
|
||||
def _prop_validator(prop):
|
||||
def validator(value):
|
||||
return _get_prop_validator(prop)(value)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
@@ -303,9 +388,9 @@ IMG_PROPS = {
|
||||
cv.Required(CONF_SRC): lv_image,
|
||||
cv.Optional(CONF_PIVOT_X, default=0): pixels,
|
||||
cv.Optional(CONF_PIVOT_Y, default=0): pixels,
|
||||
**{cv.Optional(prop): validator for prop, validator in IMG_PROPS.items()},
|
||||
**{cv.Optional(prop): _prop_validator(prop) for prop in IMG_PROPS},
|
||||
}
|
||||
),
|
||||
).add_extra(_scale_map),
|
||||
synchronous=True,
|
||||
)
|
||||
async def canvas_draw_image(config, action_id, template_arg, args):
|
||||
@@ -313,15 +398,29 @@ async def canvas_draw_image(config, action_id, template_arg, args):
|
||||
pivot_x = await pixels.process(config[CONF_PIVOT_X])
|
||||
pivot_y = await pixels.process(config[CONF_PIVOT_Y])
|
||||
|
||||
async def do_draw_image(w: Widget, x, y, dsc_addr):
|
||||
dsc = MockObj(f"(*{dsc_addr})")
|
||||
async def do_draw_image(layer, x, y, dsc):
|
||||
# LVGL 9.4: Use lv_draw_image with area
|
||||
lv_assign(dsc.src, src.get_lv_image_dsc())
|
||||
if pivot_x or pivot_y:
|
||||
# pylint :disable=no-member
|
||||
lv_assign(dsc.pivot, literal(f"{{{pivot_x}, {pivot_y}}}"))
|
||||
lv.canvas_draw_img(w.obj, x, y, src, dsc_addr)
|
||||
lv_assign(dsc.pivot.x, pivot_x)
|
||||
lv_assign(dsc.pivot.y, pivot_y)
|
||||
with LocalVariable("area", "lv_area_t", modifier="") as area:
|
||||
lv_assign(area.x1, x)
|
||||
lv_assign(area.y1, y)
|
||||
# Image size will be determined from the image descriptor
|
||||
lv_assign(area.x2, x)
|
||||
lv_assign(area.y2, y)
|
||||
lv.draw_image(layer, addr(dsc), addr(area))
|
||||
|
||||
return await draw_to_code(
|
||||
config, "img", IMG_PROPS, do_draw_image, action_id, template_arg, args
|
||||
config,
|
||||
"image",
|
||||
{prop: _get_prop_validator(prop) for prop in IMG_PROPS},
|
||||
do_draw_image,
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
)
|
||||
|
||||
|
||||
@@ -354,11 +453,8 @@ async def canvas_draw_line(config, action_id, template_arg, args):
|
||||
for p in config[CONF_POINTS]
|
||||
]
|
||||
|
||||
async def do_draw_line(w: Widget, x, y, dsc_addr):
|
||||
with LocalVariable(
|
||||
"points", cg.std_vector.template(lv_point_t), points, modifier=""
|
||||
) as points_var:
|
||||
lv.canvas_draw_line(w.obj, points_var.data(), points_var.size(), dsc_addr)
|
||||
async def do_draw_line(layer, _x, _y, dsc):
|
||||
_draw_line(layer, dsc, points)
|
||||
|
||||
return await draw_to_code(
|
||||
config, "line", LINE_PROPS, do_draw_line, action_id, template_arg, args
|
||||
@@ -382,14 +478,20 @@ async def canvas_draw_polygon(config, action_id, template_arg, args):
|
||||
[await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])]
|
||||
for p in config[CONF_POINTS]
|
||||
]
|
||||
# Close the polygon
|
||||
points.append(points[0])
|
||||
|
||||
async def do_draw_polygon(w: Widget, x, y, dsc_addr):
|
||||
with LocalVariable(
|
||||
"points", cg.std_vector.template(lv_point_t), points, modifier=""
|
||||
) as points_var:
|
||||
lv.canvas_draw_polygon(
|
||||
w.obj, points_var.data(), points_var.size(), dsc_addr
|
||||
)
|
||||
async def do_draw_polygon(layer, x, y, dsc):
|
||||
# LVGL 9.4: Draw polygon using line drawing in a closed path
|
||||
# Note: This draws outline only. For filled polygons, would need different approach
|
||||
# Convert rect descriptor to line descriptor for polygon outline
|
||||
with LocalVariable("line_dsc", "lv_draw_line_dsc_t", modifier="") as line_dsc:
|
||||
lv.draw_line_dsc_init(addr(line_dsc))
|
||||
# Copy border properties from rect descriptor to line descriptor
|
||||
lv_assign(line_dsc.color, dsc.border_color)
|
||||
lv_assign(line_dsc.width, dsc.border_width)
|
||||
lv_assign(line_dsc.opa, dsc.border_opa)
|
||||
_draw_line(layer, line_dsc, points)
|
||||
|
||||
return await draw_to_code(
|
||||
config, "rect", RECT_PROPS, do_draw_polygon, action_id, template_arg, args
|
||||
@@ -422,8 +524,14 @@ async def canvas_draw_arc(config, action_id, template_arg, args):
|
||||
start_angle = await lv_angle_degrees.process(config[CONF_START_ANGLE])
|
||||
end_angle = await lv_angle_degrees.process(config[CONF_END_ANGLE])
|
||||
|
||||
async def do_draw_arc(w: Widget, x, y, dsc_addr):
|
||||
lv.canvas_draw_arc(w.obj, x, y, radius, start_angle, end_angle, dsc_addr)
|
||||
async def do_draw_arc(layer, x, y, dsc):
|
||||
# LVGL 9.4: Use lv_draw_arc with center point
|
||||
lv_assign(dsc.center.x, x)
|
||||
lv_assign(dsc.center.y, y)
|
||||
lv_assign(dsc.start_angle, start_angle)
|
||||
lv_assign(dsc.end_angle, end_angle)
|
||||
lv_assign(dsc.radius, radius)
|
||||
lv.draw_arc(layer, addr(dsc))
|
||||
|
||||
return await draw_to_code(
|
||||
config, "arc", ARC_PROPS, do_draw_arc, action_id, template_arg, args
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_HEIGHT, CONF_WIDTH
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from ..defines import CONF_CONTAINER, CONF_MAIN, CONF_OBJ, CONF_SCROLLBAR
|
||||
from ..defines import CONF_CONTAINER, CONF_MAIN, CONF_SCROLLBAR
|
||||
from ..lv_validation import size
|
||||
from ..lvcode import lv
|
||||
from ..types import WidgetType, lv_obj_t
|
||||
from ..types import lv_obj_t
|
||||
from . import WidgetType
|
||||
|
||||
CONTAINER_SCHEMA = cv.Schema(
|
||||
{
|
||||
@@ -28,12 +27,9 @@ class ContainerType(WidgetType):
|
||||
(CONF_MAIN, CONF_SCROLLBAR),
|
||||
schema=CONTAINER_SCHEMA,
|
||||
modify_schema={},
|
||||
lv_name=CONF_OBJ,
|
||||
lv_name=CONF_CONTAINER,
|
||||
)
|
||||
self.styles = {}
|
||||
|
||||
def on_create(self, var: MockObj, config: dict):
|
||||
lv.obj_remove_style_all(var)
|
||||
|
||||
|
||||
container_spec = ContainerType()
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ANGLE, CONF_MODE, CONF_OFFSET_X, CONF_OFFSET_Y
|
||||
from esphome.const import (
|
||||
CONF_ANGLE,
|
||||
CONF_MODE,
|
||||
CONF_OFFSET_X,
|
||||
CONF_OFFSET_Y,
|
||||
CONF_ROTATION,
|
||||
)
|
||||
|
||||
from ..defines import (
|
||||
CONF_ANTIALIAS,
|
||||
CONF_MAIN,
|
||||
CONF_PIVOT_X,
|
||||
CONF_PIVOT_Y,
|
||||
CONF_SCALE,
|
||||
CONF_SRC,
|
||||
CONF_ZOOM,
|
||||
LvConstant,
|
||||
)
|
||||
from ..lv_validation import lv_angle, lv_bool, lv_image, size, zoom
|
||||
from ..lvcode import lv
|
||||
from ..lv_validation import lv_angle, lv_bool, lv_image, scale, size
|
||||
from ..types import lv_img_t
|
||||
from . import Widget, WidgetType
|
||||
from .label import CONF_LABEL
|
||||
@@ -22,14 +27,14 @@ BASE_IMG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_PIVOT_X): size,
|
||||
cv.Optional(CONF_PIVOT_Y): size,
|
||||
cv.Optional(CONF_ANGLE): lv_angle,
|
||||
cv.Optional(CONF_ZOOM): zoom,
|
||||
cv.Exclusive(CONF_ANGLE, CONF_ROTATION): lv_angle,
|
||||
cv.Exclusive(CONF_ROTATION, CONF_ROTATION): lv_angle,
|
||||
cv.Exclusive(CONF_ZOOM, CONF_SCALE): scale,
|
||||
cv.Exclusive(CONF_SCALE, CONF_SCALE): scale,
|
||||
cv.Optional(CONF_OFFSET_X): size,
|
||||
cv.Optional(CONF_OFFSET_Y): size,
|
||||
cv.Optional(CONF_ANTIALIAS): lv_bool,
|
||||
cv.Optional(CONF_MODE): LvConstant(
|
||||
"LV_IMG_SIZE_MODE_", "VIRTUAL", "REAL"
|
||||
).one_of,
|
||||
cv.Optional(CONF_MODE): cv.invalid(f"{CONF_MODE} is not supported in LVGL 9.x"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -54,33 +59,15 @@ class ImgType(WidgetType):
|
||||
(CONF_MAIN,),
|
||||
IMG_SCHEMA,
|
||||
IMG_MODIFY_SCHEMA,
|
||||
lv_name="img",
|
||||
)
|
||||
|
||||
def get_uses(self):
|
||||
return "img", CONF_LABEL
|
||||
return CONF_IMAGE, CONF_LABEL
|
||||
|
||||
async def to_code(self, w: Widget, config):
|
||||
if src := config.get(CONF_SRC):
|
||||
lv.img_set_src(w.obj, await lv_image.process(src))
|
||||
if (pivot_x := config.get(CONF_PIVOT_X)) and (
|
||||
pivot_y := config.get(CONF_PIVOT_Y)
|
||||
):
|
||||
lv.img_set_pivot(
|
||||
w.obj, await size.process(pivot_x), await size.process(pivot_y)
|
||||
)
|
||||
if (cf_angle := config.get(CONF_ANGLE)) is not None:
|
||||
lv.img_set_angle(w.obj, await lv_angle.process(cf_angle))
|
||||
if (img_zoom := config.get(CONF_ZOOM)) is not None:
|
||||
lv.img_set_zoom(w.obj, await zoom.process(img_zoom))
|
||||
if (offset := config.get(CONF_OFFSET_X)) is not None:
|
||||
lv.img_set_offset_x(w.obj, await size.process(offset))
|
||||
if (offset := config.get(CONF_OFFSET_Y)) is not None:
|
||||
lv.img_set_offset_y(w.obj, await size.process(offset))
|
||||
if CONF_ANTIALIAS in config:
|
||||
lv.img_set_antialias(w.obj, await lv_bool.process(config[CONF_ANTIALIAS]))
|
||||
if mode := config.get(CONF_MODE):
|
||||
await w.set_property("size_mode", mode)
|
||||
await w.set_property(CONF_SRC, await lv_image.process(config.get(CONF_SRC)))
|
||||
for prop, validator in BASE_IMG_SCHEMA.schema.items():
|
||||
await w.set_property(prop, config, processor=validator)
|
||||
|
||||
|
||||
img_spec = ImgType()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user