1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-09 01:01:56 +00:00

Compare commits

..

16 Commits

Author SHA1 Message Date
J. Nick Koston
840ad30880 Merge branch 'dev' into json_web_server_stack 2026-01-29 14:48:48 -10:00
J. Nick Koston
cfe121b38b private implementation details 2026-01-29 18:45:18 -06:00
J. Nick Koston
5fbd9d5b14 avoid misuse 2026-01-29 18:23:25 -06:00
J. Nick Koston
2b1783ce61 tweak 2026-01-29 18:19:29 -06:00
J. Nick Koston
904072ce79 make sure NRVO works 2026-01-29 18:17:34 -06:00
J. Nick Koston
0a4b98d74a fix double templates 2026-01-29 17:43:26 -06:00
J. Nick Koston
b8017de724 avoid regressing performance of mqtt 2026-01-29 16:55:20 -06:00
J. Nick Koston
ca96604582 change all the cases 2026-01-29 16:49:28 -06:00
J. Nick Koston
d18d378f06 missed a few 2026-01-29 16:27:28 -06:00
J. Nick Koston
83e3752544 missed a few 2026-01-29 16:26:50 -06:00
J. Nick Koston
0490b2d450 no dummy 2026-01-29 16:24:30 -06:00
J. Nick Koston
55ff740e4e no dummy 2026-01-29 16:23:41 -06:00
J. Nick Koston
aba8a83cba ard as well 2026-01-29 16:02:32 -06:00
J. Nick Koston
a23809d5db fixes 2026-01-29 15:41:29 -06:00
J. Nick Koston
32fc3ea6f5 config stack 2026-01-29 15:33:24 -06:00
J. Nick Koston
deb8ffd348 json webserver stack 2026-01-29 15:26:37 -06:00
535 changed files with 3681 additions and 7389 deletions

View File

@@ -1 +1 @@
37ec8d5a343c8d0a485fd2118cbdabcbccd7b9bca197e4a392be75087974dced
cf3d341206b4184ec8b7fe85141aef4fe4696aa720c3f8a06d4e57930574bdab

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
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@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
with:
category: "/language:${{matrix.language}}"

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.15.0
rev: v0.14.14
hooks:
# Run the linter.
- id: ruff

View File

@@ -134,7 +134,6 @@ esphome/components/dfplayer/* @glmnet
esphome/components/dfrobot_sen0395/* @niklasweber
esphome/components/dht/* @OttoWinter
esphome/components/display_menu_base/* @numo68
esphome/components/dlms_meter/* @SimonFischer04
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
@@ -532,7 +531,7 @@ esphome/components/uart/packet_transport/* @clydebarrow
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @ssieb @swoboda1337
esphome/components/ultrasonic/* @OttoWinter
esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon
esphome/components/usb_cdc_acm/* @kbx81

View File

@@ -294,13 +294,8 @@ def has_api() -> bool:
def has_ota() -> bool:
"""Check if OTA upload is available (requires platform: esphome)."""
if CONF_OTA not in CORE.config:
return False
return any(
ota_item.get(CONF_PLATFORM) == CONF_ESPHOME
for ota_item in CORE.config[CONF_OTA]
)
"""Check if OTA is available."""
return CONF_OTA in CORE.config
def has_mqtt_ip_lookup() -> bool:

View File

@@ -12,6 +12,7 @@ from .const import (
CORE_SUBCATEGORY_PATTERNS,
DEMANGLED_PATTERNS,
ESPHOME_COMPONENT_PATTERN,
SECTION_TO_ATTR,
SYMBOL_PATTERNS,
)
from .demangle import batch_demangle
@@ -90,17 +91,6 @@ class ComponentMemory:
bss_size: int = 0 # Uninitialized data (ram only)
symbol_count: int = 0
def add_section_size(self, section_name: str, size: int) -> None:
"""Add size to the appropriate attribute for a section."""
if section_name == ".text":
self.text_size += size
elif section_name == ".rodata":
self.rodata_size += size
elif section_name == ".data":
self.data_size += size
elif section_name == ".bss":
self.bss_size += size
@property
def flash_total(self) -> int:
"""Total flash usage (text + rodata + data)."""
@@ -177,15 +167,12 @@ class MemoryAnalyzer:
self._elf_symbol_names: set[str] = set()
# SDK symbols not in ELF (static/local symbols from closed-source libs)
self._sdk_symbols: list[SDKSymbol] = []
# CSWTCH symbols: list of (name, size, source_file, component)
self._cswtch_symbols: list[tuple[str, int, str, str]] = []
def analyze(self) -> dict[str, ComponentMemory]:
"""Analyze the ELF file and return component memory usage."""
self._parse_sections()
self._parse_symbols()
self._categorize_symbols()
self._analyze_cswtch_symbols()
self._analyze_sdk_libraries()
return dict(self.components)
@@ -268,7 +255,8 @@ class MemoryAnalyzer:
comp_mem.symbol_count += 1
# Update the appropriate size attribute based on section
comp_mem.add_section_size(section_name, size)
if attr_name := SECTION_TO_ATTR.get(section_name):
setattr(comp_mem, attr_name, getattr(comp_mem, attr_name) + size)
# Track uncategorized symbols
if component == "other" and size > 0:
@@ -384,205 +372,6 @@ class MemoryAnalyzer:
return "Other Core"
def _find_object_files_dir(self) -> Path | None:
"""Find the directory containing object files for this build.
Returns:
Path to the directory containing .o files, or None if not found.
"""
# The ELF is typically at .pioenvs/<env>/firmware.elf
# Object files are in .pioenvs/<env>/src/ and .pioenvs/<env>/lib*/
pioenvs_dir = self.elf_path.parent
if pioenvs_dir.exists() and any(pioenvs_dir.glob("src/*.o")):
return pioenvs_dir
return None
def _scan_cswtch_in_objects(
self, obj_dir: Path
) -> dict[str, list[tuple[str, int]]]:
"""Scan object files for CSWTCH symbols using a single nm invocation.
Uses ``nm --print-file-name -S`` on all ``.o`` files at once.
Output format: ``/path/to/file.o:address size type name``
Args:
obj_dir: Directory containing object files (.pioenvs/<env>/)
Returns:
Dict mapping "CSWTCH$NNN:size" to list of (source_file, size) tuples.
"""
cswtch_map: dict[str, list[tuple[str, int]]] = defaultdict(list)
if not self.nm_path:
return cswtch_map
# Find all .o files recursively, sorted for deterministic output
obj_files = sorted(obj_dir.rglob("*.o"))
if not obj_files:
return cswtch_map
_LOGGER.debug("Scanning %d object files for CSWTCH symbols", len(obj_files))
# Single nm call with --print-file-name for all object files
result = run_tool(
[self.nm_path, "--print-file-name", "-S"] + [str(f) for f in obj_files],
timeout=30,
)
if result is None or result.returncode != 0:
return cswtch_map
for line in result.stdout.splitlines():
if "CSWTCH$" not in line:
continue
# Split on last ":" that precedes a hex address.
# nm --print-file-name format: filepath:hex_addr hex_size type name
# We split from the right: find the last colon followed by hex digits.
parts_after_colon = line.rsplit(":", 1)
if len(parts_after_colon) != 2:
continue
file_path = parts_after_colon[0]
fields = parts_after_colon[1].split()
# fields: [address, size, type, name]
if len(fields) < 4:
continue
sym_name = fields[3]
if not sym_name.startswith("CSWTCH$"):
continue
try:
size = int(fields[1], 16)
except ValueError:
continue
# Get relative path from obj_dir for readability
try:
rel_path = str(Path(file_path).relative_to(obj_dir))
except ValueError:
rel_path = file_path
key = f"{sym_name}:{size}"
cswtch_map[key].append((rel_path, size))
return cswtch_map
def _source_file_to_component(self, source_file: str) -> str:
"""Map a source object file path to its component name.
Args:
source_file: Relative path like 'src/esphome/components/wifi/wifi_component.cpp.o'
Returns:
Component name like '[esphome]wifi' or the source file if unknown.
"""
parts = Path(source_file).parts
# ESPHome component: src/esphome/components/<name>/...
if "components" in parts:
idx = parts.index("components")
if idx + 1 < len(parts):
component_name = parts[idx + 1]
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}"
# ESPHome core: src/esphome/core/... or src/esphome/...
if "core" in parts and "esphome" in parts:
return _COMPONENT_CORE
if "esphome" in parts and "components" not in parts:
return _COMPONENT_CORE
# Framework/library files - return the first path component
# e.g., lib65b/ESPAsyncTCP/... -> lib65b
# FrameworkArduino/... -> FrameworkArduino
return parts[0] if parts else source_file
def _analyze_cswtch_symbols(self) -> None:
"""Analyze CSWTCH (GCC switch table) symbols by tracing to source objects.
CSWTCH symbols are compiler-generated lookup tables for switch statements.
They are local symbols, so the same name can appear in different object files.
This method scans .o files to attribute them to their source components.
"""
obj_dir = self._find_object_files_dir()
if obj_dir is None:
_LOGGER.debug("No object files directory found, skipping CSWTCH analysis")
return
# Scan object files for CSWTCH symbols
cswtch_map = self._scan_cswtch_in_objects(obj_dir)
if not cswtch_map:
_LOGGER.debug("No CSWTCH symbols found in object files")
return
# Collect CSWTCH symbols from the ELF (already parsed in sections)
# Include section_name for re-attribution of component totals
elf_cswtch = [
(symbol_name, size, section_name)
for section_name, section in self.sections.items()
for symbol_name, size, _ in section.symbols
if symbol_name.startswith("CSWTCH$")
]
_LOGGER.debug(
"Found %d CSWTCH symbols in ELF, %d unique in object files",
len(elf_cswtch),
len(cswtch_map),
)
# Match ELF CSWTCH symbols to source files and re-attribute component totals.
# _categorize_symbols() already ran and put these into "other" since CSWTCH$
# names don't match any component pattern. We move the bytes to the correct
# component based on the object file mapping.
other_mem = self.components.get("other")
for sym_name, size, section_name in elf_cswtch:
key = f"{sym_name}:{size}"
sources = cswtch_map.get(key, [])
if len(sources) == 1:
source_file = sources[0][0]
component = self._source_file_to_component(source_file)
elif len(sources) > 1:
# Ambiguous - multiple object files have same CSWTCH name+size
source_file = "ambiguous"
component = "ambiguous"
_LOGGER.debug(
"Ambiguous CSWTCH %s (%d B) found in %d files: %s",
sym_name,
size,
len(sources),
", ".join(src for src, _ in sources),
)
else:
source_file = "unknown"
component = "unknown"
self._cswtch_symbols.append((sym_name, size, source_file, component))
# Re-attribute from "other" to the correct component
if (
component not in ("other", "unknown", "ambiguous")
and other_mem is not None
):
other_mem.add_section_size(section_name, -size)
if component not in self.components:
self.components[component] = ComponentMemory(component)
self.components[component].add_section_size(section_name, size)
# Sort by size descending
self._cswtch_symbols.sort(key=lambda x: x[1], reverse=True)
total_size = sum(size for _, size, _, _ in self._cswtch_symbols)
_LOGGER.debug(
"CSWTCH analysis: %d symbols, %d bytes total",
len(self._cswtch_symbols),
total_size,
)
def get_unattributed_ram(self) -> tuple[int, int, int]:
"""Get unattributed RAM sizes (SDK/framework overhead).

View File

@@ -4,8 +4,6 @@ from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable
import heapq
from operator import itemgetter
import sys
from typing import TYPE_CHECKING
@@ -31,10 +29,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
)
# Lower threshold for RAM symbols (RAM is more constrained)
RAM_SYMBOL_SIZE_THRESHOLD: int = 24
# Number of top symbols to show in the largest symbols report
TOP_SYMBOLS_LIMIT: int = 30
# Width for symbol name display in top symbols report
COL_TOP_SYMBOL_NAME: int = 55
# Column width constants
COL_COMPONENT: int = 29
@@ -153,83 +147,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
return f"{demangled} ({size:,} B){section_label}"
def _add_top_symbols(self, lines: list[str]) -> None:
"""Add a section showing the top largest symbols in the binary."""
# Collect all symbols from all components: (symbol, demangled, size, section, component)
all_symbols = [
(symbol, demangled, size, section, component)
for component, symbols in self._component_symbols.items()
for symbol, demangled, size, section in symbols
]
# Get top N symbols by size using heapq for efficiency
top_symbols = heapq.nlargest(
self.TOP_SYMBOLS_LIMIT, all_symbols, key=itemgetter(2)
)
lines.append("")
lines.append(f"Top {self.TOP_SYMBOLS_LIMIT} Largest Symbols:")
# Calculate truncation limit from column width (leaving room for "...")
truncate_limit = self.COL_TOP_SYMBOL_NAME - 3
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
demangled_display = (
f"{demangled[:truncate_limit]}..."
if len(demangled) > self.COL_TOP_SYMBOL_NAME
else demangled
)
lines.append(
f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}"
)
def _add_cswtch_analysis(self, lines: list[str]) -> None:
"""Add CSWTCH (GCC switch table lookup) analysis section."""
self._add_section_header(lines, "CSWTCH Analysis (GCC Switch Table Lookups)")
total_size = sum(size for _, size, _, _ in self._cswtch_symbols)
lines.append(
f"Total: {len(self._cswtch_symbols)} switch table(s), {total_size:,} B"
)
lines.append("")
# Group by component
by_component: dict[str, list[tuple[str, int, str]]] = defaultdict(list)
for sym_name, size, source_file, component in self._cswtch_symbols:
by_component[component].append((sym_name, size, source_file))
# Sort components by total size descending
sorted_components = sorted(
by_component.items(),
key=lambda x: sum(s[1] for s in x[1]),
reverse=True,
)
for component, symbols in sorted_components:
comp_total = sum(s[1] for s in symbols)
lines.append(f"{component} ({comp_total:,} B, {len(symbols)} tables):")
# Group by source file within component
by_file: dict[str, list[tuple[str, int]]] = defaultdict(list)
for sym_name, size, source_file in symbols:
by_file[source_file].append((sym_name, size))
for source_file, file_symbols in sorted(
by_file.items(),
key=lambda x: sum(s[1] for s in x[1]),
reverse=True,
):
file_total = sum(s[1] for s in file_symbols)
lines.append(
f" {source_file} ({file_total:,} B, {len(file_symbols)} tables)"
)
for sym_name, size in sorted(
file_symbols, key=lambda x: x[1], reverse=True
):
lines.append(f" {size:>6,} B {sym_name}")
lines.append("")
def generate_report(self, detailed: bool = False) -> str:
"""Generate a formatted memory report."""
components = sorted(
@@ -331,9 +248,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
"RAM",
)
# Top largest symbols in the binary
self._add_top_symbols(lines)
# Add ESPHome core detailed analysis if there are core symbols
if self._esphome_core_symbols:
self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis")
@@ -517,10 +431,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
lines.append(f" ... and {len(large_ram_syms) - 10} more")
lines.append("")
# CSWTCH (GCC switch table) analysis
if self._cswtch_symbols:
self._add_cswtch_analysis(lines)
lines.append(
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
)

View File

@@ -66,6 +66,15 @@ SECTION_MAPPING = {
),
}
# Section to ComponentMemory attribute mapping
# Maps section names to the attribute name in ComponentMemory dataclass
SECTION_TO_ATTR = {
".text": "text_size",
".rodata": "rodata_size",
".data": "data_size",
".bss": "bss_size",
}
# Component identification rules
# Symbol patterns: patterns found in raw symbol names
SYMBOL_PATTERNS = {
@@ -504,9 +513,7 @@ SYMBOL_PATTERNS = {
"__FUNCTION__$",
"DAYS_IN_MONTH",
"_DAYS_BEFORE_MONTH",
# Note: CSWTCH$ symbols are GCC switch table lookup tables.
# They are attributed to their source object files via _analyze_cswtch_symbols()
# rather than being lumped into libc.
"CSWTCH$",
"dst$",
"sulp",
"_strtol_l", # String to long with locale

View File

@@ -45,6 +45,8 @@ void AbsoluteHumidityComponent::dump_config() {
this->temperature_sensor_->get_name().c_str(), this->humidity_sensor_->get_name().c_str());
}
float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; }
void AbsoluteHumidityComponent::loop() {
if (!this->next_update_) {
return;

View File

@@ -24,6 +24,7 @@ class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
protected:

View File

@@ -68,6 +68,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
/// This method is called during the ESPHome setup process to log the configuration.
void dump_config() override;
/// Return the setup priority for this component.
/// Components with higher priority are initialized earlier during setup.
/// @return A float representing the setup priority.
float get_setup_priority() const override;
#ifdef USE_ZEPHYR
/// Set the ADC channel to be used by the ADC sensor.
/// @param channel Pointer to an adc_dt_spec structure representing the ADC channel.

View File

@@ -79,5 +79,7 @@ void ADCSensor::set_sample_count(uint8_t sample_count) {
void ADCSensor::set_sampling_mode(SamplingMode sampling_mode) { this->sampling_mode_ = sampling_mode; }
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace adc
} // namespace esphome

View File

@@ -42,11 +42,11 @@ void ADCSensor::setup() {
adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize
init_config.unit_id = this->adc_unit_;
init_config.ulp_mode = ADC_ULP_MODE_DISABLE;
#if USE_ESP32_VARIANT_ESP32C2 || USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || \
USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
init_config.clk_src = ADC_DIGI_CLK_SRC_DEFAULT;
#endif // USE_ESP32_VARIANT_ESP32C2 || USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 ||
// USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 ||
// USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2
esp_err_t err = adc_oneshot_new_unit(&init_config, &ADCSensor::shared_adc_handles[this->adc_unit_]);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error initializing %s: %d", LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)), err);
@@ -76,7 +76,7 @@ void ADCSensor::setup() {
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32C61 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3
// RISC-V variants (except C2) and S3 use curve fitting calibration
// RISC-V variants and S3 use curve fitting calibration
adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
cali_config.chan = this->channel_;
@@ -94,14 +94,14 @@ void ADCSensor::setup() {
ESP_LOGW(TAG, "Curve fitting calibration failed with error %d, will use uncalibrated readings", err);
this->setup_flags_.calibration_complete = false;
}
#else // ESP32, ESP32-S2, and ESP32-C2 use line fitting calibration
#else // Other ESP32 variants use line fitting calibration
adc_cali_line_fitting_config_t cali_config = {
.unit_id = this->adc_unit_,
.atten = this->attenuation_,
.bitwidth = ADC_BITWIDTH_DEFAULT,
#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
#if !defined(USE_ESP32_VARIANT_ESP32S2)
.default_vref = 1100, // Default reference voltage in mV
#endif // !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
#endif // !defined(USE_ESP32_VARIANT_ESP32S2)
};
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
if (err == ESP_OK) {
@@ -112,7 +112,7 @@ void ADCSensor::setup() {
ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err);
this->setup_flags_.calibration_complete = false;
}
#endif // ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
}
this->setup_flags_.init_complete = true;
@@ -189,7 +189,7 @@ float ADCSensor::sample_fixed_attenuation_() {
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
#else // Other ESP32 variants use line fitting calibration
adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
#endif // ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32C61 || ESP32H2 || ESP32P4 || ESP32S3
this->calibration_handle_ = nullptr;
}
}
@@ -247,7 +247,7 @@ float ADCSensor::sample_autorange_() {
.unit_id = this->adc_unit_,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
#if !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32C2)
#if !defined(USE_ESP32_VARIANT_ESP32S2)
.default_vref = 1100,
#endif
};

View File

@@ -9,6 +9,8 @@ static const char *const TAG = "adc128s102.sensor";
ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {}
float ADC128S102Sensor::get_setup_priority() const { return setup_priority::DATA; }
void ADC128S102Sensor::dump_config() {
LOG_SENSOR("", "ADC128S102 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->channel_);

View File

@@ -19,6 +19,7 @@ class ADC128S102Sensor : public PollingComponent,
void update() override;
void dump_config() override;
float get_setup_priority() const override;
float sample() override;
protected:

View File

@@ -150,6 +150,8 @@ void AHT10Component::update() {
this->restart_read_();
}
float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }
void AHT10Component::dump_config() {
ESP_LOGCONFIG(TAG, "AHT10:");
LOG_I2C_DEVICE(this);

View File

@@ -16,6 +16,7 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
void setup() override;
void update() override;
void dump_config() override;
float get_setup_priority() const override;
void set_variant(AHT10Variant variant) { this->variant_ = variant; }
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

View File

@@ -1,15 +1,32 @@
#include "alarm_control_panel_state.h"
#include "esphome/core/progmem.h"
namespace esphome::alarm_control_panel {
// Alarm control panel state strings indexed by AlarmControlPanelState enum (0-9)
PROGMEM_STRING_TABLE(AlarmControlPanelStateStrings, "DISARMED", "ARMED_HOME", "ARMED_AWAY", "ARMED_NIGHT",
"ARMED_VACATION", "ARMED_CUSTOM_BYPASS", "PENDING", "ARMING", "DISARMING", "TRIGGERED", "UNKNOWN");
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
return AlarmControlPanelStateStrings::get_log_str(static_cast<uint8_t>(state),
AlarmControlPanelStateStrings::LAST_INDEX);
switch (state) {
case ACP_STATE_DISARMED:
return LOG_STR("DISARMED");
case ACP_STATE_ARMED_HOME:
return LOG_STR("ARMED_HOME");
case ACP_STATE_ARMED_AWAY:
return LOG_STR("ARMED_AWAY");
case ACP_STATE_ARMED_NIGHT:
return LOG_STR("ARMED_NIGHT");
case ACP_STATE_ARMED_VACATION:
return LOG_STR("ARMED_VACATION");
case ACP_STATE_ARMED_CUSTOM_BYPASS:
return LOG_STR("ARMED_CUSTOM_BYPASS");
case ACP_STATE_PENDING:
return LOG_STR("PENDING");
case ACP_STATE_ARMING:
return LOG_STR("ARMING");
case ACP_STATE_DISARMING:
return LOG_STR("DISARMING");
case ACP_STATE_TRIGGERED:
return LOG_STR("TRIGGERED");
default:
return LOG_STR("UNKNOWN");
}
}
} // namespace esphome::alarm_control_panel

View File

@@ -176,5 +176,7 @@ void AM2315C::dump_config() {
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
float AM2315C::get_setup_priority() const { return setup_priority::DATA; }
} // namespace am2315c
} // namespace esphome

View File

@@ -33,6 +33,7 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
void dump_config() override;
void update() override;
void setup() override;
float get_setup_priority() const override;
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; }

View File

@@ -51,6 +51,7 @@ void AM2320Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
float AM2320Component::get_setup_priority() const { return setup_priority::DATA; }
bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) {
if (!this->write_bytes(a_register, data, 2)) {

View File

@@ -11,6 +11,7 @@ class AM2320Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

View File

@@ -384,6 +384,7 @@ void APDS9960::process_dataset_(int up, int down, int left, int right) {
}
}
}
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
bool APDS9960::is_proximity_enabled_() const {
return
#ifdef USE_SENSOR

View File

@@ -32,6 +32,7 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void loop() override;

View File

@@ -45,7 +45,6 @@ service APIConnection {
rpc time_command (TimeCommandRequest) returns (void) {}
rpc update_command (UpdateCommandRequest) returns (void) {}
rpc valve_command (ValveCommandRequest) returns (void) {}
rpc water_heater_command (WaterHeaterCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}

View File

@@ -300,7 +300,7 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
// Encodes a message to the buffer and returns the total number of bytes used,
// including header and footer overhead. Returns 0 if the message doesn't fit.
uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size) {
uint32_t remaining_size, bool is_single) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// If in log-only mode, just log and return
if (conn->flags_.log_only_mode) {
@@ -330,9 +330,12 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
// Get buffer size after allocation (which includes header padding)
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
if (conn->flags_.batch_first_message) {
// First message - buffer already prepared by caller, just clear flag
conn->flags_.batch_first_message = false;
if (is_single || conn->flags_.batch_first_message) {
// Single message or first batch message
conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size);
if (conn->flags_.batch_first_message) {
conn->flags_.batch_first_message = false;
}
} else {
// Batch message second or later
// Add padding for previous message footer + this message header
@@ -362,22 +365,24 @@ bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary
BinarySensorStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
BinarySensorStateResponse resp;
resp.state = binary_sensor->state;
resp.missing_state = !binary_sensor->has_state();
return fill_and_encode_entity_state(binary_sensor, resp, BinarySensorStateResponse::MESSAGE_TYPE, conn,
remaining_size);
remaining_size, is_single);
}
uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
ListEntitiesBinarySensorResponse msg;
msg.device_class = binary_sensor->get_device_class_ref();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
remaining_size);
remaining_size, is_single);
}
#endif
@@ -385,7 +390,8 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
bool APIConnection::send_cover_state(cover::Cover *cover) {
return this->send_message_smart_(cover, CoverStateResponse::MESSAGE_TYPE, CoverStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *cover = static_cast<cover::Cover *>(entity);
CoverStateResponse msg;
auto traits = cover->get_traits();
@@ -393,9 +399,10 @@ uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *
if (traits.get_supports_tilt())
msg.tilt = cover->tilt;
msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
return fill_and_encode_entity_state(cover, msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(cover, msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *cover = static_cast<cover::Cover *>(entity);
ListEntitiesCoverResponse msg;
auto traits = cover->get_traits();
@@ -404,7 +411,8 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
msg.supports_tilt = traits.get_supports_tilt();
msg.supports_stop = traits.get_supports_stop();
msg.device_class = cover->get_device_class_ref();
return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::cover_command(const CoverCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
@@ -422,7 +430,8 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
bool APIConnection::send_fan_state(fan::Fan *fan) {
return this->send_message_smart_(fan, FanStateResponse::MESSAGE_TYPE, FanStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *fan = static_cast<fan::Fan *>(entity);
FanStateResponse msg;
auto traits = fan->get_traits();
@@ -436,9 +445,10 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
msg.direction = static_cast<enums::FanDirection>(fan->direction);
if (traits.supports_preset_modes() && fan->has_preset_mode())
msg.preset_mode = fan->get_preset_mode();
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *fan = static_cast<fan::Fan *>(entity);
ListEntitiesFanResponse msg;
auto traits = fan->get_traits();
@@ -447,7 +457,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
msg.supports_direction = traits.supports_direction();
msg.supported_speed_count = traits.supported_speed_count();
msg.supported_preset_modes = &traits.supported_preset_modes();
return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan)
@@ -471,7 +481,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
bool APIConnection::send_light_state(light::LightState *light) {
return this->send_message_smart_(light, LightStateResponse::MESSAGE_TYPE, LightStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *light = static_cast<light::LightState *>(entity);
LightStateResponse resp;
auto values = light->remote_values;
@@ -490,9 +501,10 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
if (light->supports_effects()) {
resp.effect = light->get_effect_name();
}
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *light = static_cast<light::LightState *>(entity);
ListEntitiesLightResponse msg;
auto traits = light->get_traits();
@@ -515,7 +527,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
}
}
msg.effects = &effects_list;
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(light, msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::light_command(const LightCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light)
@@ -555,15 +568,17 @@ bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
return this->send_message_smart_(sensor, SensorStateResponse::MESSAGE_TYPE, SensorStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *sensor = static_cast<sensor::Sensor *>(entity);
SensorStateResponse resp;
resp.state = sensor->state;
resp.missing_state = !sensor->has_state();
return fill_and_encode_entity_state(sensor, resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(sensor, resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *sensor = static_cast<sensor::Sensor *>(entity);
ListEntitiesSensorResponse msg;
msg.unit_of_measurement = sensor->get_unit_of_measurement_ref();
@@ -571,7 +586,8 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
msg.force_update = sensor->get_force_update();
msg.device_class = sensor->get_device_class_ref();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
#endif
@@ -580,19 +596,23 @@ bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
return this->send_message_smart_(a_switch, SwitchStateResponse::MESSAGE_TYPE, SwitchStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *a_switch = static_cast<switch_::Switch *>(entity);
SwitchStateResponse resp;
resp.state = a_switch->state;
return fill_and_encode_entity_state(a_switch, resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(a_switch, resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *a_switch = static_cast<switch_::Switch *>(entity);
ListEntitiesSwitchResponse msg;
msg.assumed_state = a_switch->assumed_state();
msg.device_class = a_switch->get_device_class_ref();
return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch)
@@ -611,19 +631,22 @@ bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor)
TextSensorStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
TextSensorStateResponse resp;
resp.state = StringRef(text_sensor->state);
resp.missing_state = !text_sensor->has_state();
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
ListEntitiesTextSensorResponse msg;
msg.device_class = text_sensor->get_device_class_ref();
return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
remaining_size);
remaining_size, is_single);
}
#endif
@@ -631,7 +654,8 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
bool APIConnection::send_climate_state(climate::Climate *climate) {
return this->send_message_smart_(climate, ClimateStateResponse::MESSAGE_TYPE, ClimateStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *climate = static_cast<climate::Climate *>(entity);
ClimateStateResponse resp;
auto traits = climate->get_traits();
@@ -663,9 +687,11 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
resp.current_humidity = climate->current_humidity;
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY))
resp.target_humidity = climate->target_humidity;
return fill_and_encode_entity_state(climate, resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(climate, resp, ClimateStateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *climate = static_cast<climate::Climate *>(entity);
ListEntitiesClimateResponse msg;
auto traits = climate->get_traits();
@@ -690,7 +716,8 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supported_presets = &traits.get_supported_presets();
msg.supported_custom_presets = &traits.get_supported_custom_presets();
msg.supported_swing_modes = &traits.get_supported_swing_modes();
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate)
@@ -723,15 +750,17 @@ bool APIConnection::send_number_state(number::Number *number) {
return this->send_message_smart_(number, NumberStateResponse::MESSAGE_TYPE, NumberStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *number = static_cast<number::Number *>(entity);
NumberStateResponse resp;
resp.state = number->state;
resp.missing_state = !number->has_state();
return fill_and_encode_entity_state(number, resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(number, resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *number = static_cast<number::Number *>(entity);
ListEntitiesNumberResponse msg;
msg.unit_of_measurement = number->traits.get_unit_of_measurement_ref();
@@ -740,7 +769,8 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
msg.step = number->traits.get_step();
return fill_and_encode_entity_info(number, msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(number, msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::number_command(const NumberCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(number::Number, number, number)
@@ -753,19 +783,22 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
bool APIConnection::send_date_state(datetime::DateEntity *date) {
return this->send_message_smart_(date, DateStateResponse::MESSAGE_TYPE, DateStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *date = static_cast<datetime::DateEntity *>(entity);
DateStateResponse resp;
resp.missing_state = !date->has_state();
resp.year = date->year;
resp.month = date->month;
resp.day = date->day;
return fill_and_encode_entity_state(date, resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(date, resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *date = static_cast<datetime::DateEntity *>(entity);
ListEntitiesDateResponse msg;
return fill_and_encode_entity_info(date, msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(date, msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::date_command(const DateCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date)
@@ -778,19 +811,22 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
bool APIConnection::send_time_state(datetime::TimeEntity *time) {
return this->send_message_smart_(time, TimeStateResponse::MESSAGE_TYPE, TimeStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *time = static_cast<datetime::TimeEntity *>(entity);
TimeStateResponse resp;
resp.missing_state = !time->has_state();
resp.hour = time->hour;
resp.minute = time->minute;
resp.second = time->second;
return fill_and_encode_entity_state(time, resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(time, resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *time = static_cast<datetime::TimeEntity *>(entity);
ListEntitiesTimeResponse msg;
return fill_and_encode_entity_info(time, msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(time, msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::time_command(const TimeCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time)
@@ -804,7 +840,8 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
return this->send_message_smart_(datetime, DateTimeStateResponse::MESSAGE_TYPE,
DateTimeStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
DateTimeStateResponse resp;
resp.missing_state = !datetime->has_state();
@@ -812,12 +849,15 @@ uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnectio
ESPTime state = datetime->state_as_esptime();
resp.epoch_seconds = state.timestamp;
}
return fill_and_encode_entity_state(datetime, resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(datetime, resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
ListEntitiesDateTimeResponse msg;
return fill_and_encode_entity_info(datetime, msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(datetime, msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime)
@@ -831,22 +871,25 @@ bool APIConnection::send_text_state(text::Text *text) {
return this->send_message_smart_(text, TextStateResponse::MESSAGE_TYPE, TextStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *text = static_cast<text::Text *>(entity);
TextStateResponse resp;
resp.state = StringRef(text->state);
resp.missing_state = !text->has_state();
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *text = static_cast<text::Text *>(entity);
ListEntitiesTextResponse msg;
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
msg.min_length = text->traits.get_min_length();
msg.max_length = text->traits.get_max_length();
msg.pattern = text->traits.get_pattern_ref();
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::text_command(const TextCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(text::Text, text, text)
@@ -860,19 +903,22 @@ bool APIConnection::send_select_state(select::Select *select) {
return this->send_message_smart_(select, SelectStateResponse::MESSAGE_TYPE, SelectStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *select = static_cast<select::Select *>(entity);
SelectStateResponse resp;
resp.state = select->current_option();
resp.missing_state = !select->has_state();
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *select = static_cast<select::Select *>(entity);
ListEntitiesSelectResponse msg;
msg.options = &select->traits.get_options();
return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::select_command(const SelectCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
@@ -882,11 +928,13 @@ void APIConnection::select_command(const SelectCommandRequest &msg) {
#endif
#ifdef USE_BUTTON
uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *button = static_cast<button::Button *>(entity);
ListEntitiesButtonResponse msg;
msg.device_class = button->get_device_class_ref();
return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) {
ENTITY_COMMAND_GET(button::Button, button, button)
@@ -899,20 +947,23 @@ bool APIConnection::send_lock_state(lock::Lock *a_lock) {
return this->send_message_smart_(a_lock, LockStateResponse::MESSAGE_TYPE, LockStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *a_lock = static_cast<lock::Lock *>(entity);
LockStateResponse resp;
resp.state = static_cast<enums::LockState>(a_lock->state);
return fill_and_encode_entity_state(a_lock, resp, LockStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(a_lock, resp, LockStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *a_lock = static_cast<lock::Lock *>(entity);
ListEntitiesLockResponse msg;
msg.assumed_state = a_lock->traits.get_assumed_state();
msg.supports_open = a_lock->traits.get_supports_open();
msg.requires_code = a_lock->traits.get_requires_code();
return fill_and_encode_entity_info(a_lock, msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(a_lock, msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::lock_command(const LockCommandRequest &msg) {
ENTITY_COMMAND_GET(lock::Lock, a_lock, lock)
@@ -935,14 +986,16 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
bool APIConnection::send_valve_state(valve::Valve *valve) {
return this->send_message_smart_(valve, ValveStateResponse::MESSAGE_TYPE, ValveStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *valve = static_cast<valve::Valve *>(entity);
ValveStateResponse resp;
resp.position = valve->position;
resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
return fill_and_encode_entity_state(valve, resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(valve, resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *valve = static_cast<valve::Valve *>(entity);
ListEntitiesValveResponse msg;
auto traits = valve->get_traits();
@@ -950,7 +1003,8 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position();
msg.supports_stop = traits.get_supports_stop();
return fill_and_encode_entity_info(valve, msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(valve, msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::valve_command(const ValveCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve)
@@ -967,7 +1021,8 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla
return this->send_message_smart_(media_player, MediaPlayerStateResponse::MESSAGE_TYPE,
MediaPlayerStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
MediaPlayerStateResponse resp;
media_player::MediaPlayerState report_state = media_player->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING
@@ -976,9 +1031,11 @@ uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConne
resp.state = static_cast<enums::MediaPlayerState>(report_state);
resp.volume = media_player->volume;
resp.muted = media_player->is_muted();
return fill_and_encode_entity_state(media_player, resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(media_player, resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *media_player = static_cast<media_player::MediaPlayer *>(entity);
ListEntitiesMediaPlayerResponse msg;
auto traits = media_player->get_traits();
@@ -994,7 +1051,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
media_format.sample_bytes = supported_format.sample_bytes;
}
return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn,
remaining_size);
remaining_size, is_single);
}
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player)
@@ -1035,7 +1092,7 @@ void APIConnection::try_send_camera_image_() {
msg.device_id = camera::Camera::instance()->get_device_id();
#endif
if (!this->send_message_impl(msg, CameraImageResponse::MESSAGE_TYPE)) {
if (!this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
return; // Send failed, try again later
}
this->image_reader_->consume_data(to_send);
@@ -1058,10 +1115,12 @@ void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image)
this->try_send_camera_image_();
}
}
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *camera = static_cast<camera::Camera *>(entity);
ListEntitiesCameraResponse msg;
return fill_and_encode_entity_info(camera, msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(camera, msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::camera_image(const CameraImageRequest &msg) {
if (camera::Camera::instance() == nullptr)
@@ -1246,22 +1305,22 @@ bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmCon
AlarmControlPanelStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
uint32_t remaining_size) {
uint32_t remaining_size, bool is_single) {
auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
AlarmControlPanelStateResponse resp;
resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
return fill_and_encode_entity_state(a_alarm_control_panel, resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn,
remaining_size);
remaining_size, is_single);
}
uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn,
uint32_t remaining_size) {
uint32_t remaining_size, bool is_single) {
auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
ListEntitiesAlarmControlPanelResponse msg;
msg.supported_features = a_alarm_control_panel->get_supported_features();
msg.requires_code = a_alarm_control_panel->get_requires_code();
msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
return fill_and_encode_entity_info(a_alarm_control_panel, msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE,
conn, remaining_size);
conn, remaining_size, is_single);
}
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel)
@@ -1298,7 +1357,8 @@ bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_hea
return this->send_message_smart_(water_heater, WaterHeaterStateResponse::MESSAGE_TYPE,
WaterHeaterStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
WaterHeaterStateResponse resp;
resp.mode = static_cast<enums::WaterHeaterMode>(wh->get_mode());
@@ -1309,9 +1369,10 @@ uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConne
resp.state = wh->get_state();
resp.key = wh->get_object_id_hash();
return encode_message_to_buffer(resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size);
return encode_message_to_buffer(resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
ListEntitiesWaterHeaterResponse msg;
auto traits = wh->get_traits();
@@ -1320,10 +1381,11 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec
msg.target_temperature_step = traits.get_target_temperature_step();
msg.supported_modes = &traits.get_supported_modes();
msg.supported_features = traits.get_feature_flags();
return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::water_heater_command(const WaterHeaterCommandRequest &msg) {
void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));
@@ -1349,18 +1411,20 @@ void APIConnection::send_event(event::Event *event) {
event->get_last_event_type_index());
}
uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
uint32_t remaining_size) {
uint32_t remaining_size, bool is_single) {
EventResponse resp;
resp.event_type = event_type;
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *event = static_cast<event::Event *>(entity);
ListEntitiesEventResponse msg;
msg.device_class = event->get_device_class_ref();
msg.event_types = &event->get_event_types();
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
#endif
@@ -1383,11 +1447,13 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent
#endif
#ifdef USE_INFRARED
uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *infrared = static_cast<infrared::Infrared *>(entity);
ListEntitiesInfraredResponse msg;
msg.capabilities = infrared->get_capability_flags();
return fill_and_encode_entity_info(infrared, msg, ListEntitiesInfraredResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(infrared, msg, ListEntitiesInfraredResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
#endif
@@ -1395,7 +1461,8 @@ uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection
bool APIConnection::send_update_state(update::UpdateEntity *update) {
return this->send_message_smart_(update, UpdateStateResponse::MESSAGE_TYPE, UpdateStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *update = static_cast<update::UpdateEntity *>(entity);
UpdateStateResponse resp;
resp.missing_state = !update->has_state();
@@ -1411,13 +1478,15 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
resp.release_summary = StringRef(update->update_info.summary);
resp.release_url = StringRef(update->update_info.release_url);
}
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *update = static_cast<update::UpdateEntity *>(entity);
ListEntitiesUpdateResponse msg;
msg.device_class = update->get_device_class_ref();
return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size);
return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
void APIConnection::update_command(const UpdateCommandRequest &msg) {
ENTITY_COMMAND_GET(update::UpdateEntity, update, update)
@@ -1443,7 +1512,7 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
SubscribeLogsResponse msg;
msg.level = static_cast<enums::LogLevel>(level);
msg.set_message(reinterpret_cast<const uint8_t *>(line), message_len);
return this->send_message_impl(msg, SubscribeLogsResponse::MESSAGE_TYPE);
return this->send_message_(msg, SubscribeLogsResponse::MESSAGE_TYPE);
}
void APIConnection::complete_authentication_() {
@@ -1768,14 +1837,6 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
}
return false;
}
bool APIConnection::send_message_impl(const ProtoMessage &msg, uint8_t message_type) {
ProtoSize size;
msg.calculate_size(size);
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
this->prepare_first_message_buffer(shared_buf, size.get_size());
msg.encode({&shared_buf});
return this->send_buffer({&shared_buf}, message_type);
}
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
@@ -1836,23 +1897,6 @@ void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t me
}
}
bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index) {
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
auto &shared_buf = this->parent_->get_shared_buffer_ref();
this->prepare_first_message_buffer(shared_buf, estimated_size);
DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index};
if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) &&
this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_batch_item_(item);
#endif
return true;
}
}
return this->schedule_message_(entity, message_type, estimated_size, aux_data_index);
}
bool APIConnection::schedule_batch_() {
if (!this->flags_.batch_scheduled) {
this->flags_.batch_scheduled = true;
@@ -1881,21 +1925,10 @@ void APIConnection::process_batch_() {
auto &shared_buf = this->parent_->get_shared_buffer_ref();
size_t num_items = this->deferred_batch_.size();
// Cache these values to avoid repeated virtual calls
const uint8_t header_padding = this->helper_->frame_header_padding();
const uint8_t footer_size = this->helper_->frame_footer_size();
// Pre-calculate exact buffer size needed based on message types
uint32_t total_estimated_size = num_items * (header_padding + footer_size);
for (size_t i = 0; i < num_items; i++) {
total_estimated_size += this->deferred_batch_[i].estimated_size;
}
this->prepare_first_message_buffer(shared_buf, header_padding, total_estimated_size);
// Fast path for single message - buffer already allocated above
// Fast path for single message - allocate exact size needed
if (num_items == 1) {
const auto &item = this->deferred_batch_[0];
// Let dispatch_message_ calculate size and encode if it fits
uint16_t payload_size = this->dispatch_message_(item, std::numeric_limits<uint16_t>::max(), true);
@@ -1918,8 +1951,30 @@ void APIConnection::process_batch_() {
// Stack-allocated array for message info
alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)];
MessageInfo *message_info = reinterpret_cast<MessageInfo *>(message_info_storage);
size_t message_count = 0;
// Cache these values to avoid repeated virtual calls
const uint8_t header_padding = this->helper_->frame_header_padding();
const uint8_t footer_size = this->helper_->frame_footer_size();
// Initialize buffer and tracking variables
shared_buf.clear();
// Pre-calculate exact buffer size needed based on message types
uint32_t total_estimated_size = num_items * (header_padding + footer_size);
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
const auto &item = this->deferred_batch_[i];
total_estimated_size += item.estimated_size;
}
// Calculate total overhead for all messages
// Reserve based on estimated size (much more accurate than 24-byte worst-case)
shared_buf.reserve(total_estimated_size);
this->flags_.batch_first_message = true;
size_t items_processed = 0;
uint16_t remaining_size = std::numeric_limits<uint16_t>::max();
// Track where each message's header padding begins in the buffer
// For plaintext: this is where the 6-byte header padding starts
// For noise: this is where the 7-byte header padding starts
@@ -1931,7 +1986,7 @@ void APIConnection::process_batch_() {
const auto &item = this->deferred_batch_[i];
// Try to encode message via dispatch
// The dispatch function calculates overhead to determine if the message fits
uint16_t payload_size = this->dispatch_message_(item, remaining_size, i == 0);
uint16_t payload_size = this->dispatch_message_(item, remaining_size, false);
if (payload_size == 0) {
// Message won't fit, stop processing
@@ -1945,7 +2000,10 @@ void APIConnection::process_batch_() {
// This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements
// Explicit destruction is not needed because MessageInfo is trivially destructible,
// as ensured by the static_assert in its definition.
new (&message_info[items_processed++]) MessageInfo(item.message_type, current_offset, proto_payload_size);
new (&message_info[message_count++]) MessageInfo(item.message_type, current_offset, proto_payload_size);
// Update tracking variables
items_processed++;
// After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation
if (items_processed == 1) {
remaining_size = MAX_BATCH_PACKET_SIZE;
@@ -1968,7 +2026,7 @@ void APIConnection::process_batch_() {
// Send all collected messages
APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf},
std::span<const MessageInfo>(message_info, items_processed));
std::span<const MessageInfo>(message_info, message_count));
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
}
@@ -1997,8 +2055,7 @@ void APIConnection::process_batch_() {
// Dispatch message encoding based on message_type
// Switch assigns function pointer, single call site for smaller code size
uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size,
bool batch_first) {
this->flags_.batch_first_message = batch_first;
bool is_single) {
#ifdef USE_EVENT
// Events need aux_data_index to look up event type from entity
if (item.message_type == EventResponse::MESSAGE_TYPE) {
@@ -2007,7 +2064,7 @@ uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item,
return 0;
auto *event = static_cast<event::Event *>(item.entity);
return try_send_event_response(event, StringRef::from_maybe_nullptr(event->get_event_type(item.aux_data_index)),
this, remaining_size);
this, remaining_size, is_single);
}
#endif
@@ -2117,22 +2174,25 @@ uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item,
#undef CASE_STATE_INFO
#undef CASE_INFO_ONLY
return func(item.entity, this, remaining_size);
return func(item.entity, this, remaining_size, is_single);
}
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
ListEntitiesDoneResponse resp;
return encode_message_to_buffer(resp, ListEntitiesDoneResponse::MESSAGE_TYPE, conn, remaining_size);
return encode_message_to_buffer(resp, ListEntitiesDoneResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
DisconnectRequest req;
return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size);
return encode_message_to_buffer(req, DisconnectRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) {
uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
PingRequest req;
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size);
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
}
#ifdef USE_API_HOMEASSISTANT_STATES

View File

@@ -170,7 +170,7 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_WATER_HEATER
bool send_water_heater_state(water_heater::WaterHeater *water_heater);
void water_heater_command(const WaterHeaterCommandRequest &msg) override;
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
#endif
#ifdef USE_IR_RF
@@ -255,7 +255,17 @@ class APIConnection final : public APIServerConnection {
void on_fatal_error() override;
void on_no_setup_connection() override;
bool send_message_impl(const ProtoMessage &msg, uint8_t message_type) override;
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
// FIXME: ensure no recursive writes can happen
// Get header padding size - used for both reserve and insert
uint8_t header_padding = this->helper_->frame_header_padding();
// Get shared buffer from parent server
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
this->prepare_first_message_buffer(shared_buf, header_padding,
reserve_size + header_padding + this->helper_->frame_footer_size());
return {&shared_buf};
}
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
shared_buf.clear();
@@ -267,13 +277,6 @@ class APIConnection final : public APIServerConnection {
shared_buf.resize(header_padding);
}
// Convenience overload - computes frame overhead internally
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t payload_size) {
const uint8_t header_padding = this->helper_->frame_header_padding();
const uint8_t footer_size = this->helper_->frame_footer_size();
this->prepare_first_message_buffer(shared_buf, header_padding, payload_size + header_padding + footer_size);
}
bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
@@ -295,21 +298,21 @@ class APIConnection final : public APIServerConnection {
// Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size);
uint32_t remaining_size, bool is_single);
// Helper to fill entity state base and encode message
static uint16_t fill_and_encode_entity_state(EntityBase *entity, StateResponseProtoMessage &msg, uint8_t message_type,
APIConnection *conn, uint32_t remaining_size) {
APIConnection *conn, uint32_t remaining_size, bool is_single) {
msg.key = entity->get_object_id_hash();
#ifdef USE_DEVICES
msg.device_id = entity->get_device_id();
#endif
return encode_message_to_buffer(msg, message_type, conn, remaining_size);
return encode_message_to_buffer(msg, message_type, conn, remaining_size, is_single);
}
// Helper to fill entity info base and encode message
static uint16_t fill_and_encode_entity_info(EntityBase *entity, InfoResponseProtoMessage &msg, uint8_t message_type,
APIConnection *conn, uint32_t remaining_size) {
APIConnection *conn, uint32_t remaining_size, bool is_single) {
// Set common fields that are shared by all entity types
msg.key = entity->get_object_id_hash();
@@ -336,7 +339,7 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_DEVICES
msg.device_id = entity->get_device_id();
#endif
return encode_message_to_buffer(msg, message_type, conn, remaining_size);
return encode_message_to_buffer(msg, message_type, conn, remaining_size, is_single);
}
#ifdef USE_VOICE_ASSISTANT
@@ -367,108 +370,141 @@ class APIConnection final : public APIServerConnection {
}
#ifdef USE_BINARY_SENSOR
static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_COVER
static uint16_t try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_FAN
static uint16_t try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_LIGHT
static uint16_t try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_SENSOR
static uint16_t try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_SWITCH
static uint16_t try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_switch_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_TEXT_SENSOR
static uint16_t try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_CLIMATE
static uint16_t try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_climate_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_NUMBER
static uint16_t try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_number_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_DATETIME_DATE
static uint16_t try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_date_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_DATETIME_TIME
static uint16_t try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_time_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_DATETIME_DATETIME
static uint16_t try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_datetime_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_TEXT
static uint16_t try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_text_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_SELECT
static uint16_t try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_BUTTON
static uint16_t try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_LOCK
static uint16_t try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
static uint16_t try_send_lock_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_VALVE
static uint16_t try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_valve_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_MEDIA_PLAYER
static uint16_t try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_media_player_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
static uint16_t try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_WATER_HEATER
static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_INFRARED
static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_EVENT
static uint16_t try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
uint32_t remaining_size);
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
uint32_t remaining_size, bool is_single);
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
#ifdef USE_UPDATE
static uint16_t try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_CAMERA
static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
// Method for ListEntitiesDone batching
static uint16_t try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
// Method for DisconnectRequest batching
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
// Batch message method for ping requests
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
// === Optimal member ordering for 32-bit systems ===
@@ -503,7 +539,7 @@ class APIConnection final : public APIServerConnection {
#endif
// Function pointer type for message encoding
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size);
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
// Generic batching mechanism for both state updates and entity info
struct DeferredBatch {
@@ -616,7 +652,7 @@ class APIConnection final : public APIServerConnection {
// Dispatch message encoding based on message_type - replaces function pointer storage
// Switch assigns pointer, single call site for smaller code size
uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool batch_first);
uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool is_single);
#ifdef HAS_PROTO_MESSAGE_DUMP
void log_batch_item_(const DeferredBatch::BatchItem &item) {
@@ -648,7 +684,19 @@ class APIConnection final : public APIServerConnection {
// Tries immediate send if should_send_immediately_() returns true and buffer has space
// Falls back to batching if immediate send fails or isn't applicable
bool send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED);
uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) {
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index};
if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_batch_item_(item);
#endif
return true;
}
}
return this->schedule_message_(entity, message_type, estimated_size, aux_data_index);
}
// Helper function to schedule a deferred message with known message type
bool schedule_message_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,

View File

@@ -23,8 +23,15 @@ static inline void append_field_prefix(DumpBuffer &out, const char *field_name,
out.append(indent, ' ').append(field_name).append(": ");
}
static inline void append_with_newline(DumpBuffer &out, const char *str) {
out.append(str);
out.append("\n");
}
static inline void append_uint(DumpBuffer &out, uint32_t value) {
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRIu32, value));
char buf[16];
snprintf(buf, sizeof(buf), "%" PRIu32, value);
out.append(buf);
}
// RAII helper for message dump formatting
@@ -42,23 +49,31 @@ class MessageDumpHelper {
// Helper functions to reduce code duplication in dump methods
static void dump_field(DumpBuffer &out, const char *field_name, int32_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRId32 "\n", value));
snprintf(buffer, 64, "%" PRId32, value);
append_with_newline(out, buffer);
}
static void dump_field(DumpBuffer &out, const char *field_name, uint32_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRIu32 "\n", value));
snprintf(buffer, 64, "%" PRIu32, value);
append_with_newline(out, buffer);
}
static void dump_field(DumpBuffer &out, const char *field_name, float value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%g\n", value));
snprintf(buffer, 64, "%g", value);
append_with_newline(out, buffer);
}
static void dump_field(DumpBuffer &out, const char *field_name, uint64_t value, int indent = 2) {
char buffer[64];
append_field_prefix(out, field_name, indent);
out.set_pos(buf_append_printf(out.data(), DumpBuffer::CAPACITY, out.pos(), "%" PRIu64 "\n", value));
snprintf(buffer, 64, "%" PRIu64, value);
append_with_newline(out, buffer);
}
static void dump_field(DumpBuffer &out, const char *field_name, bool value, int indent = 2) {
@@ -97,7 +112,7 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint
char hex_buf[format_hex_pretty_size(160)];
append_field_prefix(out, field_name, indent);
format_hex_pretty_to(hex_buf, data, len);
out.append(hex_buf).append("\n");
append_with_newline(out, hex_buf);
}
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {

View File

@@ -746,11 +746,6 @@ void APIServerConnection::on_update_command_request(const UpdateCommandRequest &
#ifdef USE_VALVE
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); }
#endif
#ifdef USE_WATER_HEATER
void APIServerConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
this->water_heater_command(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) {

View File

@@ -23,7 +23,7 @@ class APIServerConnectionBase : public ProtoService {
DumpBuffer dump_buf;
this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
#endif
return this->send_message_impl(msg, message_type);
return this->send_message_(msg, message_type);
}
virtual void on_hello_request(const HelloRequest &value){};
@@ -303,9 +303,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif
#ifdef USE_WATER_HEATER
virtual void water_heater_command(const WaterHeaterCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif
@@ -435,9 +432,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_WATER_HEATER
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif

View File

@@ -211,7 +211,7 @@ void APIServer::loop() {
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
// Fire trigger after client is removed so api.connected reflects the true state
this->client_disconnected_trigger_.trigger(client_name, client_peername);
this->client_disconnected_trigger_->trigger(client_name, client_peername);
#endif
// Don't increment client_index since we need to process the swapped element
}

View File

@@ -227,10 +227,12 @@ class APIServer : public Component,
#endif
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() { return &this->client_connected_trigger_; }
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_disconnected_trigger() { return &this->client_disconnected_trigger_; }
Trigger<std::string, std::string> *get_client_disconnected_trigger() const {
return this->client_disconnected_trigger_;
}
#endif
protected:
@@ -251,10 +253,10 @@ class APIServer : public Component,
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> client_connected_trigger_;
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
#endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> client_disconnected_trigger_;
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
#endif
// 4-byte aligned types

View File

@@ -136,10 +136,12 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
void set_wants_response() { this->flags_.wants_response = true; }
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() { return &this->success_trigger_with_response_; }
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() const {
return this->success_trigger_with_response_;
}
#endif
Trigger<Ts...> *get_success_trigger() { return &this->success_trigger_; }
Trigger<std::string, Ts...> *get_error_trigger() { return &this->error_trigger_; }
Trigger<Ts...> *get_success_trigger() const { return this->success_trigger_; }
Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
void play(const Ts &...x) override {
@@ -185,14 +187,14 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
if (response.is_success()) {
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
if (this->flags_.wants_response) {
this->success_trigger_with_response_.trigger(response.get_json(), args...);
this->success_trigger_with_response_->trigger(response.get_json(), args...);
} else
#endif
{
this->success_trigger_.trigger(args...);
this->success_trigger_->trigger(args...);
}
} else {
this->error_trigger_.trigger(response.get_error_message(), args...);
this->error_trigger_->trigger(response.get_error_message(), args...);
}
},
captured_args);
@@ -249,10 +251,10 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
TemplatableStringValue<Ts...> response_template_{""};
Trigger<JsonObjectConst, Ts...> success_trigger_with_response_;
Trigger<JsonObjectConst, Ts...> *success_trigger_with_response_ = new Trigger<JsonObjectConst, Ts...>();
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
Trigger<Ts...> success_trigger_;
Trigger<std::string, Ts...> error_trigger_;
Trigger<Ts...> *success_trigger_ = new Trigger<Ts...>();
Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>();
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
struct Flags {

View File

@@ -402,20 +402,6 @@ class DumpBuffer {
const char *c_str() const { return buf_; }
size_t size() const { return pos_; }
/// Get writable buffer pointer for use with buf_append_printf
char *data() { return buf_; }
/// Get current position for use with buf_append_printf
size_t pos() const { return pos_; }
/// Update position after buf_append_printf call
void set_pos(size_t pos) {
if (pos >= CAPACITY) {
pos_ = CAPACITY - 1;
} else {
pos_ = pos;
}
buf_[pos_] = '\0';
}
private:
void append_impl_(const char *str, size_t len) {
size_t space = CAPACITY - 1 - pos_;
@@ -957,16 +943,32 @@ class ProtoService {
virtual bool is_connection_setup() = 0;
virtual void on_fatal_error() = 0;
virtual void on_no_setup_connection() = 0;
/**
* Create a buffer with a reserved size.
* @param reserve_size The number of bytes to pre-allocate in the buffer. This is a hint
* to optimize memory usage and avoid reallocations during encoding.
* Implementations should aim to allocate at least this size.
* @return A ProtoWriteBuffer object with the reserved size.
*/
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 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;
/**
* Send a protobuf message by calculating its size, allocating a buffer, encoding, and sending.
* This is the implementation method - callers should use send_message() which adds logging.
* @param msg The protobuf message to send.
* @param message_type The message type identifier.
* @return True if the message was sent successfully, false otherwise.
*/
virtual bool send_message_impl(const ProtoMessage &msg, uint8_t message_type) = 0;
// Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
ProtoSize size;
msg.calculate_size(size);
uint32_t msg_size = size.get_size();
// Create a pre-sized buffer
auto buffer = this->create_buffer(msg_size);
// Encode message into the buffer
msg.encode(buffer);
// Send the buffer
return this->send_buffer(buffer, message_type);
}
// Authentication helper methods
inline bool check_connection_setup_() {

View File

@@ -264,9 +264,9 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
// Build and send JSON response
json::JsonBuilder builder;
this->json_builder_(x..., builder.root());
std::string json_str = builder.serialize();
auto json_buf = builder.serialize();
this->parent_->send_action_response(call_id, success, StringRef(error_message),
reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
reinterpret_cast<const uint8_t *>(json_buf.data()), json_buf.size());
return;
}
#endif

View File

@@ -10,6 +10,7 @@ class AQISensor : public sensor::Sensor, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }

View File

@@ -41,6 +41,8 @@ void AS3935Component::dump_config() {
#endif
}
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
void AS3935Component::loop() {
if (!this->irq_pin_->digital_read())
return;

View File

@@ -74,6 +74,7 @@ class AS3935Component : public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
void set_irq_pin(GPIOPin *irq_pin) { irq_pin_ = irq_pin; }

View File

@@ -22,6 +22,8 @@ static const uint8_t REGISTER_STATUS = 0x0B; // 8 bytes / R
static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
float AS5600Sensor::get_setup_priority() const { return setup_priority::DATA; }
void AS5600Sensor::dump_config() {
LOG_SENSOR("", "AS5600 Sensor", this);
ESP_LOGCONFIG(TAG, " Out of Range Mode: %u", this->out_of_range_mode_);

View File

@@ -14,6 +14,7 @@ class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>,
public:
void update() override;
void dump_config() override;
float get_setup_priority() const override;
void set_angle_sensor(sensor::Sensor *angle_sensor) { this->angle_sensor_ = angle_sensor; }
void set_raw_angle_sensor(sensor::Sensor *raw_angle_sensor) { this->raw_angle_sensor_ = raw_angle_sensor; }

View File

@@ -58,6 +58,8 @@ void AS7341Component::dump_config() {
LOG_SENSOR(" ", "NIR", this->nir_);
}
float AS7341Component::get_setup_priority() const { return setup_priority::DATA; }
void AS7341Component::update() {
this->read_channels(this->channel_readings_);

View File

@@ -78,6 +78,7 @@ class AS7341Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_f1_sensor(sensor::Sensor *f1_sensor) { this->f1_ = f1_sensor; }

View File

@@ -146,6 +146,7 @@ void ATM90E26Component::dump_config() {
LOG_SENSOR(" ", "Active Reverse Energy A", this->reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
}
float ATM90E26Component::get_setup_priority() const { return setup_priority::DATA; }
uint16_t ATM90E26Component::read16_(uint8_t a_register) {
uint8_t data[2];

View File

@@ -13,6 +13,7 @@ class ATM90E26Component : public PollingComponent,
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_voltage_sensor(sensor::Sensor *obj) { this->voltage_sensor_ = obj; }

View File

@@ -6,7 +6,8 @@ namespace bang_bang {
static const char *const TAG = "bang_bang.climate";
BangBangClimate::BangBangClimate() = default;
BangBangClimate::BangBangClimate()
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
void BangBangClimate::setup() {
this->sensor_->add_on_state_callback([this](float state) {
@@ -159,13 +160,13 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
switch (action) {
case climate::CLIMATE_ACTION_OFF:
case climate::CLIMATE_ACTION_IDLE:
trig = &this->idle_trigger_;
trig = this->idle_trigger_;
break;
case climate::CLIMATE_ACTION_COOLING:
trig = &this->cool_trigger_;
trig = this->cool_trigger_;
break;
case climate::CLIMATE_ACTION_HEATING:
trig = &this->heat_trigger_;
trig = this->heat_trigger_;
break;
default:
trig = nullptr;
@@ -203,9 +204,9 @@ void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &awa
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
Trigger<> *BangBangClimate::get_idle_trigger() { return &this->idle_trigger_; }
Trigger<> *BangBangClimate::get_cool_trigger() { return &this->cool_trigger_; }
Trigger<> *BangBangClimate::get_heat_trigger() { return &this->heat_trigger_; }
Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; }
Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; }
Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; }
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }

View File

@@ -30,9 +30,9 @@ class BangBangClimate : public climate::Climate, public Component {
void set_normal_config(const BangBangClimateTargetTempConfig &normal_config);
void set_away_config(const BangBangClimateTargetTempConfig &away_config);
Trigger<> *get_idle_trigger();
Trigger<> *get_cool_trigger();
Trigger<> *get_heat_trigger();
Trigger<> *get_idle_trigger() const;
Trigger<> *get_cool_trigger() const;
Trigger<> *get_heat_trigger() const;
protected:
/// Override control to change settings of the climate device.
@@ -57,13 +57,17 @@ class BangBangClimate : public climate::Climate, public Component {
*
* In idle mode, the controller is assumed to have both heating and cooling disabled.
*/
Trigger<> idle_trigger_;
Trigger<> *idle_trigger_{nullptr};
/** The trigger to call when the controller should switch to cooling mode.
*/
Trigger<> cool_trigger_;
Trigger<> *cool_trigger_{nullptr};
/** The trigger to call when the controller should switch to heating mode.
*
* A null value for this attribute means that the controller has no heating action
* For example window blinds, where only cooling (blinds closed) and not-cooling
* (blinds open) is possible.
*/
Trigger<> heat_trigger_;
Trigger<> *heat_trigger_{nullptr};
/** A reference to the trigger that was previously active.
*
* This is so that the previous trigger can be stopped before enabling a new one.

View File

@@ -265,4 +265,6 @@ void BH1750Sensor::fail_and_reset_() {
this->state_ = IDLE;
}
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace esphome::bh1750

View File

@@ -21,6 +21,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void dump_config() override;
void update() override;
void loop() override;
float get_setup_priority() const override;
protected:
// State machine states

View File

@@ -199,6 +199,7 @@ void BME280Component::dump_config() {
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->humidity_oversampling_));
}
float BME280Component::get_setup_priority() const { return setup_priority::DATA; }
inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (1 << uint8_t(over_sampling)) >> 1; }

View File

@@ -76,6 +76,7 @@ class BME280Component : public PollingComponent {
// (In most use cases you won't need these)
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:

View File

@@ -233,6 +233,8 @@ void BME680Component::dump_config() {
}
}
float BME680Component::get_setup_priority() const { return setup_priority::DATA; }
void BME680Component::update() {
uint8_t meas_control = 0; // No need to fetch, we're setting all fields
meas_control |= (this->temperature_oversampling_ & 0b111) << 5;

View File

@@ -99,6 +99,7 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice {
// (In most use cases you won't need these)
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:

View File

@@ -89,9 +89,8 @@ async def to_code(config):
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
)
# Although this component does not use SPI/Wire directly, the BSEC library requires them
# Although this component does not use SPI, the BSEC library requires the SPI library
cg.add_library("SPI", None)
cg.add_library("Wire", None)
cg.add_define("USE_BSEC")
cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")

View File

@@ -181,6 +181,8 @@ void BME680BSECComponent::dump_config() {
LOG_SENSOR(" ", "Breath VOC Equivalent", this->breath_voc_equivalent_sensor_);
}
float BME680BSECComponent::get_setup_priority() const { return setup_priority::DATA; }
void BME680BSECComponent::loop() {
this->run_();

View File

@@ -64,6 +64,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
protected:

View File

@@ -106,6 +106,8 @@ void BME68xBSEC2Component::dump_config() {
#endif
}
float BME68xBSEC2Component::get_setup_priority() const { return setup_priority::DATA; }
void BME68xBSEC2Component::loop() {
this->run_();

View File

@@ -48,6 +48,7 @@ class BME68xBSEC2Component : public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
void set_algorithm_output(AlgorithmOutput algorithm_output) { this->algorithm_output_ = algorithm_output; }

View File

@@ -263,6 +263,7 @@ void BMI160Component::update() {
this->status_clear_warning();
}
float BMI160Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace bmi160
} // namespace esphome

View File

@@ -14,6 +14,8 @@ class BMI160Component : public PollingComponent, public i2c::I2CDevice {
void update() override;
float get_setup_priority() const override;
void set_accel_x_sensor(sensor::Sensor *accel_x_sensor) { accel_x_sensor_ = accel_x_sensor; }
void set_accel_y_sensor(sensor::Sensor *accel_y_sensor) { accel_y_sensor_ = accel_y_sensor; }
void set_accel_z_sensor(sensor::Sensor *accel_z_sensor) { accel_z_sensor_ = accel_z_sensor; }

View File

@@ -131,6 +131,7 @@ bool BMP085Component::set_mode_(uint8_t mode) {
ESP_LOGV(TAG, "Setting mode to 0x%02X", mode);
return this->write_byte(BMP085_REGISTER_CONTROL, mode);
}
float BMP085Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace bmp085
} // namespace esphome

View File

@@ -18,6 +18,8 @@ class BMP085Component : public PollingComponent, public i2c::I2CDevice {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
protected:
struct CalibrationData {
int16_t ac1, ac2, ac3;

View File

@@ -148,6 +148,7 @@ void BMP280Component::dump_config() {
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
}
float BMP280Component::get_setup_priority() const { return setup_priority::DATA; }
inline uint8_t oversampling_to_time(BMP280Oversampling over_sampling) { return (1 << uint8_t(over_sampling)) >> 1; }

View File

@@ -64,6 +64,7 @@ class BMP280Component : public PollingComponent {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:

View File

@@ -179,6 +179,7 @@ void BMP3XXComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
}
}
float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; }
inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); }

View File

@@ -73,6 +73,7 @@ class BMP3XXComponent : public PollingComponent {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

View File

@@ -156,7 +156,7 @@ void CC1101Component::call_listeners_(const std::vector<uint8_t> &packet, float
for (auto &listener : this->listeners_) {
listener->on_packet(packet, freq_offset, rssi, lqi);
}
this->packet_trigger_.trigger(packet, freq_offset, rssi, lqi);
this->packet_trigger_->trigger(packet, freq_offset, rssi, lqi);
}
void CC1101Component::loop() {

View File

@@ -79,7 +79,7 @@ class CC1101Component : public Component,
// Packet mode operations
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() { return &this->packet_trigger_; }
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
protected:
uint16_t chip_id_{0};
@@ -96,7 +96,8 @@ class CC1101Component : public Component,
// Packet handling
void call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi);
Trigger<std::vector<uint8_t>, float, float, uint8_t> packet_trigger_;
Trigger<std::vector<uint8_t>, float, float, uint8_t> *packet_trigger_{
new Trigger<std::vector<uint8_t>, float, float, uint8_t>()};
std::vector<uint8_t> packet_;
std::vector<CC1101Listener *> listeners_;

View File

@@ -7,6 +7,8 @@ namespace cd74hc4067 {
static const char *const TAG = "cd74hc4067";
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
void CD74HC4067Component::setup() {
this->pin_s0_->setup();
this->pin_s1_->setup();

View File

@@ -13,6 +13,7 @@ class CD74HC4067Component : public Component {
/// Set up the internal sensor array.
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
/// setting pin active by setting the right combination of the four multiplexer input pins
void activate_pin(uint8_t pin);

View File

@@ -1,44 +1,109 @@
#include "climate_mode.h"
#include "esphome/core/progmem.h"
namespace esphome::climate {
// Climate mode strings indexed by ClimateMode enum (0-6): OFF, HEAT_COOL, COOL, HEAT, FAN_ONLY, DRY, AUTO
PROGMEM_STRING_TABLE(ClimateModeStrings, "OFF", "HEAT_COOL", "COOL", "HEAT", "FAN_ONLY", "DRY", "AUTO", "UNKNOWN");
const LogString *climate_mode_to_string(ClimateMode mode) {
return ClimateModeStrings::get_log_str(static_cast<uint8_t>(mode), ClimateModeStrings::LAST_INDEX);
switch (mode) {
case CLIMATE_MODE_OFF:
return LOG_STR("OFF");
case CLIMATE_MODE_HEAT_COOL:
return LOG_STR("HEAT_COOL");
case CLIMATE_MODE_AUTO:
return LOG_STR("AUTO");
case CLIMATE_MODE_COOL:
return LOG_STR("COOL");
case CLIMATE_MODE_HEAT:
return LOG_STR("HEAT");
case CLIMATE_MODE_FAN_ONLY:
return LOG_STR("FAN_ONLY");
case CLIMATE_MODE_DRY:
return LOG_STR("DRY");
default:
return LOG_STR("UNKNOWN");
}
}
// Climate action strings indexed by ClimateAction enum (0,2-6): OFF, (gap), COOLING, HEATING, IDLE, DRYING, FAN
PROGMEM_STRING_TABLE(ClimateActionStrings, "OFF", "UNKNOWN", "COOLING", "HEATING", "IDLE", "DRYING", "FAN", "UNKNOWN");
const LogString *climate_action_to_string(ClimateAction action) {
return ClimateActionStrings::get_log_str(static_cast<uint8_t>(action), ClimateActionStrings::LAST_INDEX);
switch (action) {
case CLIMATE_ACTION_OFF:
return LOG_STR("OFF");
case CLIMATE_ACTION_COOLING:
return LOG_STR("COOLING");
case CLIMATE_ACTION_HEATING:
return LOG_STR("HEATING");
case CLIMATE_ACTION_IDLE:
return LOG_STR("IDLE");
case CLIMATE_ACTION_DRYING:
return LOG_STR("DRYING");
case CLIMATE_ACTION_FAN:
return LOG_STR("FAN");
default:
return LOG_STR("UNKNOWN");
}
}
// Climate fan mode strings indexed by ClimateFanMode enum (0-9): ON, OFF, AUTO, LOW, MEDIUM, HIGH, MIDDLE, FOCUS,
// DIFFUSE, QUIET
PROGMEM_STRING_TABLE(ClimateFanModeStrings, "ON", "OFF", "AUTO", "LOW", "MEDIUM", "HIGH", "MIDDLE", "FOCUS", "DIFFUSE",
"QUIET", "UNKNOWN");
const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
return ClimateFanModeStrings::get_log_str(static_cast<uint8_t>(fan_mode), ClimateFanModeStrings::LAST_INDEX);
switch (fan_mode) {
case climate::CLIMATE_FAN_ON:
return LOG_STR("ON");
case climate::CLIMATE_FAN_OFF:
return LOG_STR("OFF");
case climate::CLIMATE_FAN_AUTO:
return LOG_STR("AUTO");
case climate::CLIMATE_FAN_LOW:
return LOG_STR("LOW");
case climate::CLIMATE_FAN_MEDIUM:
return LOG_STR("MEDIUM");
case climate::CLIMATE_FAN_HIGH:
return LOG_STR("HIGH");
case climate::CLIMATE_FAN_MIDDLE:
return LOG_STR("MIDDLE");
case climate::CLIMATE_FAN_FOCUS:
return LOG_STR("FOCUS");
case climate::CLIMATE_FAN_DIFFUSE:
return LOG_STR("DIFFUSE");
case climate::CLIMATE_FAN_QUIET:
return LOG_STR("QUIET");
default:
return LOG_STR("UNKNOWN");
}
}
// Climate swing mode strings indexed by ClimateSwingMode enum (0-3): OFF, BOTH, VERTICAL, HORIZONTAL
PROGMEM_STRING_TABLE(ClimateSwingModeStrings, "OFF", "BOTH", "VERTICAL", "HORIZONTAL", "UNKNOWN");
const LogString *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
return ClimateSwingModeStrings::get_log_str(static_cast<uint8_t>(swing_mode), ClimateSwingModeStrings::LAST_INDEX);
switch (swing_mode) {
case climate::CLIMATE_SWING_OFF:
return LOG_STR("OFF");
case climate::CLIMATE_SWING_BOTH:
return LOG_STR("BOTH");
case climate::CLIMATE_SWING_VERTICAL:
return LOG_STR("VERTICAL");
case climate::CLIMATE_SWING_HORIZONTAL:
return LOG_STR("HORIZONTAL");
default:
return LOG_STR("UNKNOWN");
}
}
// Climate preset strings indexed by ClimatePreset enum (0-7): NONE, HOME, AWAY, BOOST, COMFORT, ECO, SLEEP, ACTIVITY
PROGMEM_STRING_TABLE(ClimatePresetStrings, "NONE", "HOME", "AWAY", "BOOST", "COMFORT", "ECO", "SLEEP", "ACTIVITY",
"UNKNOWN");
const LogString *climate_preset_to_string(ClimatePreset preset) {
return ClimatePresetStrings::get_log_str(static_cast<uint8_t>(preset), ClimatePresetStrings::LAST_INDEX);
switch (preset) {
case climate::CLIMATE_PRESET_NONE:
return LOG_STR("NONE");
case climate::CLIMATE_PRESET_HOME:
return LOG_STR("HOME");
case climate::CLIMATE_PRESET_ECO:
return LOG_STR("ECO");
case climate::CLIMATE_PRESET_AWAY:
return LOG_STR("AWAY");
case climate::CLIMATE_PRESET_BOOST:
return LOG_STR("BOOST");
case climate::CLIMATE_PRESET_COMFORT:
return LOG_STR("COMFORT");
case climate::CLIMATE_PRESET_SLEEP:
return LOG_STR("SLEEP");
case climate::CLIMATE_PRESET_ACTIVITY:
return LOG_STR("ACTIVITY");
default:
return LOG_STR("UNKNOWN");
}
}
} // namespace esphome::climate

View File

@@ -10,6 +10,8 @@ namespace cm1106 {
class CM1106Component : public PollingComponent, public uart::UARTDevice {
public:
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
void setup() override;
void update() override;
void dump_config() override;

View File

@@ -10,6 +10,8 @@ namespace combination {
class CombinationComponent : public Component, public sensor::Sensor {
public:
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
/// @brief Logs all source sensor's names
virtual void log_source_sensors() = 0;

View File

@@ -17,9 +17,3 @@ CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers"
CONF_ROWS = "rows"
CONF_USE_PSRAM = "use_psram"
ICON_CURRENT_DC = "mdi:current-dc"
ICON_SOLAR_PANEL = "mdi:solar-panel"
ICON_SOLAR_POWER = "mdi:solar-power"
UNIT_AMPERE_HOUR = "Ah"

View File

@@ -1,5 +1,3 @@
import logging
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
@@ -11,7 +9,6 @@ from esphome.const import (
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_IDLE,
CONF_ON_OPEN,
CONF_POSITION,
CONF_POSITION_COMMAND_TOPIC,
@@ -35,10 +32,9 @@ from esphome.const import (
DEVICE_CLASS_SHUTTER,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObj, MockObjClass
from esphome.types import ConfigType, TemplateArgsType
from esphome.cpp_generator import MockObjClass
IS_PLATFORM_COMPONENT = True
@@ -57,8 +53,6 @@ DEVICE_CLASSES = [
DEVICE_CLASS_WINDOW,
]
_LOGGER = logging.getLogger(__name__)
cover_ns = cg.esphome_ns.namespace("cover")
Cover = cover_ns.class_("Cover", cg.EntityBase)
@@ -89,29 +83,14 @@ ControlAction = cover_ns.class_("ControlAction", automation.Action)
CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
CoverOpenedTrigger = cover_ns.class_(
"CoverOpenedTrigger", automation.Trigger.template()
)
# Triggers
CoverOpenTrigger = cover_ns.class_("CoverOpenTrigger", automation.Trigger.template())
CoverClosedTrigger = cover_ns.class_(
"CoverClosedTrigger", automation.Trigger.template()
)
CoverTrigger = cover_ns.class_("CoverTrigger", automation.Trigger.template())
# Cover-specific constants
CONF_ON_CLOSED = "on_closed"
CONF_ON_OPENED = "on_opened"
CONF_ON_OPENING = "on_opening"
CONF_ON_CLOSING = "on_closing"
TRIGGERS = {
CONF_ON_OPEN: CoverOpenedTrigger, # Deprecated, use on_opened
CONF_ON_OPENED: CoverOpenedTrigger,
CONF_ON_CLOSED: CoverClosedTrigger,
CONF_ON_CLOSING: CoverTrigger.template(CoverOperation.COVER_OPERATION_CLOSING),
CONF_ON_OPENING: CoverTrigger.template(CoverOperation.COVER_OPERATION_OPENING),
CONF_ON_IDLE: CoverTrigger.template(CoverOperation.COVER_OPERATION_IDLE),
}
_COVER_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
@@ -132,14 +111,16 @@ _COVER_SCHEMA = (
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
**{
cv.Optional(conf): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class),
}
)
for conf, trigger_class in TRIGGERS.items()
},
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
}
),
}
)
)
@@ -176,14 +157,12 @@ async def setup_cover_core_(var, config):
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class))
if CONF_ON_OPEN in config:
_LOGGER.warning(
"'on_open' is deprecated, use 'on_opened'. Will be removed in 2026.8.0"
)
for trigger_conf in TRIGGERS:
for conf in config.get(trigger_conf, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_OPEN, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLOSED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
@@ -279,26 +258,6 @@ async def cover_control_to_code(config, action_id, template_arg, args):
return var
COVER_CONDITION_SCHEMA = cv.maybe_simple_value(
{cv.Required(CONF_ID): cv.use_id(Cover)}, key=CONF_ID
)
async def cover_condition_to_code(
config: ConfigType, condition_id: ID, template_arg: MockObj, args: TemplateArgsType
) -> MockObj:
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, template_arg, paren)
automation.register_condition(
"cover.is_open", CoverIsOpenCondition, COVER_CONDITION_SCHEMA
)(cover_condition_to_code)
automation.register_condition(
"cover.is_closed", CoverIsClosedCondition, COVER_CONDITION_SCHEMA
)(cover_condition_to_code)
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config):
cg.add_global(cover_ns.using)

View File

@@ -90,53 +90,44 @@ template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
Cover *cover_;
};
template<bool OPEN, typename... Ts> class CoverPositionCondition : public Condition<Ts...> {
template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
public:
CoverPositionCondition(Cover *cover) : cover_(cover) {}
bool check(const Ts &...x) override { return this->cover_->position == (OPEN ? COVER_OPEN : COVER_CLOSED); }
CoverIsOpenCondition(Cover *cover) : cover_(cover) {}
bool check(const Ts &...x) override { return this->cover_->is_fully_open(); }
protected:
Cover *cover_;
};
template<typename... Ts> using CoverIsOpenCondition = CoverPositionCondition<true, Ts...>;
template<typename... Ts> using CoverIsClosedCondition = CoverPositionCondition<false, Ts...>;
template<bool OPEN> class CoverPositionTrigger : public Trigger<> {
template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...> {
public:
CoverPositionTrigger(Cover *a_cover) {
CoverIsClosedCondition(Cover *cover) : cover_(cover) {}
bool check(const Ts &...x) override { return this->cover_->is_fully_closed(); }
protected:
Cover *cover_;
};
class CoverOpenTrigger : public Trigger<> {
public:
CoverOpenTrigger(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))
this->trigger();
if (a_cover->is_fully_open()) {
this->trigger();
}
});
}
protected:
float last_position_{NAN};
};
using CoverOpenedTrigger = CoverPositionTrigger<true>;
using CoverClosedTrigger = CoverPositionTrigger<false>;
template<CoverOperation OP> class CoverTrigger : public Trigger<> {
class CoverClosedTrigger : public Trigger<> {
public:
CoverTrigger(Cover *a_cover) {
CoverClosedTrigger(Cover *a_cover) {
a_cover->add_on_state_callback([this, a_cover]() {
auto current_op = a_cover->current_operation;
if (current_op == OP) {
if (!this->last_operation_.has_value() || this->last_operation_.value() != OP) {
this->trigger();
}
if (a_cover->is_fully_closed()) {
this->trigger();
}
this->last_operation_ = current_op;
});
}
protected:
optional<CoverOperation> last_operation_{};
};
} // namespace esphome::cover

View File

@@ -10,6 +10,9 @@ namespace esphome::cover {
static const char *const TAG = "cover";
const float COVER_OPEN = 1.0f;
const float COVER_CLOSED = 0.0f;
const LogString *cover_command_to_str(float pos) {
if (pos == COVER_OPEN) {
return LOG_STR("OPEN");
@@ -19,11 +22,17 @@ const LogString *cover_command_to_str(float pos) {
return LOG_STR("UNKNOWN");
}
}
// Cover operation strings indexed by CoverOperation enum (0-2): IDLE, OPENING, CLOSING, plus UNKNOWN
PROGMEM_STRING_TABLE(CoverOperationStrings, "IDLE", "OPENING", "CLOSING", "UNKNOWN");
const LogString *cover_operation_to_str(CoverOperation op) {
return CoverOperationStrings::get_log_str(static_cast<uint8_t>(op), CoverOperationStrings::LAST_INDEX);
switch (op) {
case COVER_OPERATION_IDLE:
return LOG_STR("IDLE");
case COVER_OPERATION_OPENING:
return LOG_STR("OPENING");
case COVER_OPERATION_CLOSING:
return LOG_STR("CLOSING");
default:
return LOG_STR("UNKNOWN");
}
}
Cover::Cover() : position{COVER_OPEN} {}

View File

@@ -10,8 +10,8 @@
namespace esphome::cover {
static constexpr float COVER_OPEN = 1.0f;
static constexpr float COVER_CLOSED = 0.0f;
const extern float COVER_OPEN;
const extern float COVER_CLOSED;
#define LOG_COVER(prefix, type, obj) \
if ((obj) != nullptr) { \

View File

@@ -62,6 +62,8 @@ void CSE7761Component::dump_config() {
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
}
float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
void CSE7761Component::update() {
if (this->data_.ready) {
this->get_data_();

View File

@@ -28,6 +28,7 @@ class CSE7761Component : public PollingComponent, public uart::UARTDevice {
void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:

View File

@@ -37,6 +37,7 @@ void CSE7766Component::loop() {
this->raw_data_index_ = (this->raw_data_index_ + 1) % 24;
}
}
float CSE7766Component::get_setup_priority() const { return setup_priority::DATA; }
bool CSE7766Component::check_byte_() {
uint8_t index = this->raw_data_index_;
@@ -151,10 +152,6 @@ void CSE7766Component::parse_data_() {
if (this->power_sensor_ != nullptr) {
this->power_sensor_->publish_state(power);
}
} else if (this->power_sensor_ != nullptr) {
// No valid power measurement from chip - publish 0W to avoid stale readings
// This typically happens when current is below the measurable threshold (~50mA)
this->power_sensor_->publish_state(0.0f);
}
float current = 0.0f;

View File

@@ -23,6 +23,7 @@ class CSE7766Component : public Component, public uart::UARTDevice {
void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
void loop() override;
float get_setup_priority() const override;
void dump_config() override;
protected:

View File

@@ -66,7 +66,7 @@ void CurrentBasedCover::loop() {
if (this->current_operation == COVER_OPERATION_OPENING) {
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
this->direction_idle_();
this->malfunction_trigger_.trigger();
this->malfunction_trigger_->trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit",
this->name_.c_str());
} else if (this->is_opening_blocked_()) { // Blocked
@@ -87,7 +87,7 @@ void CurrentBasedCover::loop() {
} else if (this->current_operation == COVER_OPERATION_CLOSING) {
if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction
this->direction_idle_();
this->malfunction_trigger_.trigger();
this->malfunction_trigger_->trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit",
this->name_.c_str());
} else if (this->is_closing_blocked_()) { // Blocked
@@ -159,6 +159,7 @@ void CurrentBasedCover::dump_config() {
this->start_sensing_delay_ / 1e3f, YESNO(this->malfunction_detection_));
}
float CurrentBasedCover::get_setup_priority() const { return setup_priority::DATA; }
void CurrentBasedCover::stop_prev_trigger_() {
if (this->prev_command_trigger_ != nullptr) {
this->prev_command_trigger_->stop_action();
@@ -220,15 +221,15 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
Trigger<> *trig;
switch (dir) {
case COVER_OPERATION_IDLE:
trig = &this->stop_trigger_;
trig = this->stop_trigger_;
break;
case COVER_OPERATION_OPENING:
this->last_operation_ = dir;
trig = &this->open_trigger_;
trig = this->open_trigger_;
break;
case COVER_OPERATION_CLOSING:
this->last_operation_ = dir;
trig = &this->close_trigger_;
trig = this->close_trigger_;
break;
default:
return;

View File

@@ -14,10 +14,11 @@ class CurrentBasedCover : public cover::Cover, public Component {
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
Trigger<> *get_stop_trigger() { return &this->stop_trigger_; }
Trigger<> *get_stop_trigger() const { return this->stop_trigger_; }
Trigger<> *get_open_trigger() { return &this->open_trigger_; }
Trigger<> *get_open_trigger() const { return this->open_trigger_; }
void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; }
void set_open_moving_current_threshold(float open_moving_current_threshold) {
this->open_moving_current_threshold_ = open_moving_current_threshold;
@@ -27,7 +28,7 @@ class CurrentBasedCover : public cover::Cover, public Component {
}
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
Trigger<> *get_close_trigger() { return &this->close_trigger_; }
Trigger<> *get_close_trigger() const { return this->close_trigger_; }
void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; }
void set_close_moving_current_threshold(float close_moving_current_threshold) {
this->close_moving_current_threshold_ = close_moving_current_threshold;
@@ -43,7 +44,7 @@ class CurrentBasedCover : public cover::Cover, public Component {
void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; }
void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; }
Trigger<> *get_malfunction_trigger() { return &this->malfunction_trigger_; }
Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; }
cover::CoverTraits get_traits() override;
@@ -63,23 +64,23 @@ class CurrentBasedCover : public cover::Cover, public Component {
void recompute_position_();
Trigger<> stop_trigger_;
Trigger<> *stop_trigger_{new Trigger<>()};
sensor::Sensor *open_sensor_{nullptr};
Trigger<> open_trigger_;
Trigger<> *open_trigger_{new Trigger<>()};
float open_moving_current_threshold_;
float open_obstacle_current_threshold_{FLT_MAX};
uint32_t open_duration_;
sensor::Sensor *close_sensor_{nullptr};
Trigger<> close_trigger_;
Trigger<> *close_trigger_{new Trigger<>()};
float close_moving_current_threshold_;
float close_obstacle_current_threshold_{FLT_MAX};
uint32_t close_duration_;
uint32_t max_duration_{UINT32_MAX};
bool malfunction_detection_{true};
Trigger<> malfunction_trigger_;
Trigger<> *malfunction_trigger_{new Trigger<>()};
uint32_t start_sensing_delay_;
float obstacle_rollback_;

View File

@@ -104,6 +104,8 @@ void DalyBmsComponent::loop() {
}
}
float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA; }
void DalyBmsComponent::request_data_(uint8_t data_id) {
uint8_t request_message[DALY_FRAME_SIZE];

View File

@@ -72,6 +72,7 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice {
void update() override;
void loop() override;
float get_setup_priority() const override;
void set_address(uint8_t address) { this->addr_ = address; }
protected:

View File

@@ -1,6 +1,5 @@
import esphome.codegen as cg
from esphome.components import sensor
from esphome.components.const import ICON_CURRENT_DC, UNIT_AMPERE_HOUR
import esphome.config_validation as cv
from esphome.const import (
CONF_BATTERY_LEVEL,
@@ -56,11 +55,14 @@ CONF_CELL_15_VOLTAGE = "cell_15_voltage"
CONF_CELL_16_VOLTAGE = "cell_16_voltage"
CONF_CELL_17_VOLTAGE = "cell_17_voltage"
CONF_CELL_18_VOLTAGE = "cell_18_voltage"
ICON_CURRENT_DC = "mdi:current-dc"
ICON_BATTERY_OUTLINE = "mdi:battery-outline"
ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up"
ICON_THERMOMETER_CHEVRON_DOWN = "mdi:thermometer-chevron-down"
ICON_CAR_BATTERY = "mdi:car-battery"
UNIT_AMPERE_HOUR = "Ah"
TYPES = [
CONF_VOLTAGE,
CONF_CURRENT,

View File

@@ -63,6 +63,8 @@ void DHT::update() {
}
}
float DHT::get_setup_priority() const { return setup_priority::DATA; }
void DHT::set_dht_model(DHTModel model) {
this->model_ = model;
this->is_auto_detect_ = model == DHT_MODEL_AUTO_DETECT;

View File

@@ -51,6 +51,8 @@ class DHT : public PollingComponent {
void dump_config() override;
/// Update sensor values and push them to the frontend.
void update() override;
/// HARDWARE_LATE setup priority.
float get_setup_priority() const override;
protected:
bool read_sensor_(float *temperature, float *humidity, bool report_errors);

View File

@@ -49,7 +49,7 @@ void DHT12Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
float DHT12Component::get_setup_priority() const { return setup_priority::DATA; }
bool DHT12Component::read_data_(uint8_t *data) {
if (!this->read_bytes(0, data, 5)) {
ESP_LOGW(TAG, "Updating DHT12 failed!");

View File

@@ -11,6 +11,7 @@ class DHT12Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

View File

@@ -1,57 +0,0 @@
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266
CODEOWNERS = ["@SimonFischer04"]
DEPENDENCIES = ["uart"]
CONF_DLMS_METER_ID = "dlms_meter_id"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_PROVIDER = "provider"
PROVIDERS = {"generic": 0, "netznoe": 1}
dlms_meter_component_ns = cg.esphome_ns.namespace("dlms_meter")
DlmsMeterComponent = dlms_meter_component_ns.class_(
"DlmsMeterComponent", cg.Component, uart.UARTDevice
)
def validate_key(value):
value = cv.string_strict(value)
if len(value) != 32:
raise cv.Invalid("Decryption key must be 32 hex characters (16 bytes)")
try:
return [int(value[i : i + 2], 16) for i in range(0, 32, 2)]
except ValueError as exc:
raise cv.Invalid("Decryption key must be hex values from 00 to FF") from exc
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DlmsMeterComponent),
cv.Required(CONF_DECRYPTION_KEY): validate_key,
cv.Optional(CONF_PROVIDER, default="generic"): cv.enum(
PROVIDERS, lower=True
),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32]),
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"dlms_meter", baud_rate=2400, require_rx=True
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
key = ", ".join(str(b) for b in config[CONF_DECRYPTION_KEY])
cg.add(var.set_decryption_key(cg.RawExpression(f"{{{key}}}")))
cg.add(var.set_provider(PROVIDERS[config[CONF_PROVIDER]]))

View File

@@ -1,71 +0,0 @@
#pragma once
#include <cstdint>
namespace esphome::dlms_meter {
/*
+-------------------------------+
| Ciphering Service |
+-------------------------------+
| System Title Length |
+-------------------------------+
| |
| |
| |
| System |
| Title |
| |
| |
| |
+-------------------------------+
| Length | (1 or 3 Bytes)
+-------------------------------+
| Security Control Byte |
+-------------------------------+
| |
| Frame |
| Counter |
| |
+-------------------------------+
| |
~ ~
Encrypted Payload
~ ~
| |
+-------------------------------+
Ciphering Service: 0xDB (General-Glo-Ciphering)
System Title Length: 0x08
System Title: Unique ID of meter
Length: 1 Byte=Length <= 127, 3 Bytes=Length > 127 (0x82 & 2 Bytes length)
Security Control Byte:
- Bit 3…0: Security_Suite_Id
- Bit 4: "A" subfield: indicates that authentication is applied
- Bit 5: "E" subfield: indicates that encryption is applied
- Bit 6: Key_Set subfield: 0 = Unicast, 1 = Broadcast
- Bit 7: Indicates the use of compression.
*/
static constexpr uint8_t DLMS_HEADER_LENGTH = 16;
static constexpr uint8_t DLMS_HEADER_EXT_OFFSET = 2; // Extra offset for extended length header
static constexpr uint8_t DLMS_CIPHER_OFFSET = 0;
static constexpr uint8_t DLMS_SYST_OFFSET = 1;
static constexpr uint8_t DLMS_LENGTH_OFFSET = 10;
static constexpr uint8_t TWO_BYTE_LENGTH = 0x82;
static constexpr uint8_t DLMS_LENGTH_CORRECTION = 5; // Header bytes included in length field
static constexpr uint8_t DLMS_SECBYTE_OFFSET = 11;
static constexpr uint8_t DLMS_FRAMECOUNTER_OFFSET = 12;
static constexpr uint8_t DLMS_FRAMECOUNTER_LENGTH = 4;
static constexpr uint8_t DLMS_PAYLOAD_OFFSET = 16;
static constexpr uint8_t GLO_CIPHERING = 0xDB;
static constexpr uint8_t DATA_NOTIFICATION = 0x0F;
static constexpr uint8_t TIMESTAMP_DATETIME = 0x0C;
static constexpr uint16_t MAX_MESSAGE_LENGTH = 512; // Maximum size of message (when having 2 bytes length in header).
// Provider specific quirks
static constexpr uint8_t NETZ_NOE_MAGIC_BYTE = 0x81; // Magic length byte used by Netz NOE
static constexpr uint8_t NETZ_NOE_EXPECTED_MESSAGE_LENGTH = 0xF8;
static constexpr uint8_t NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE = 0x20;
} // namespace esphome::dlms_meter

View File

@@ -1,468 +0,0 @@
#include "dlms_meter.h"
#include <cmath>
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
#include <bearssl/bearssl.h>
#elif defined(USE_ESP32)
#include "mbedtls/esp_config.h"
#include "mbedtls/gcm.h"
#endif
namespace esphome::dlms_meter {
static constexpr const char *TAG = "dlms_meter";
void DlmsMeterComponent::dump_config() {
const char *provider_name = this->provider_ == PROVIDER_NETZNOE ? "Netz NOE" : "Generic";
ESP_LOGCONFIG(TAG,
"DLMS Meter:\n"
" Provider: %s\n"
" Read Timeout: %u ms",
provider_name, this->read_timeout_);
#define DLMS_METER_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s##_sensor_);
DLMS_METER_SENSOR_LIST(DLMS_METER_LOG_SENSOR, )
#define DLMS_METER_LOG_TEXT_SENSOR(s) LOG_TEXT_SENSOR(" ", #s, this->s##_text_sensor_);
DLMS_METER_TEXT_SENSOR_LIST(DLMS_METER_LOG_TEXT_SENSOR, )
}
void DlmsMeterComponent::loop() {
// Read while data is available, netznoe uses two frames so allow 2x max frame length
while (this->available()) {
if (this->receive_buffer_.size() >= MBUS_MAX_FRAME_LENGTH * 2) {
ESP_LOGW(TAG, "Receive buffer full, dropping remaining bytes");
break;
}
uint8_t c;
this->read_byte(&c);
this->receive_buffer_.push_back(c);
this->last_read_ = millis();
}
if (!this->receive_buffer_.empty() && millis() - this->last_read_ > this->read_timeout_) {
this->mbus_payload_.clear();
if (!this->parse_mbus_(this->mbus_payload_))
return;
uint16_t message_length;
uint8_t systitle_length;
uint16_t header_offset;
if (!this->parse_dlms_(this->mbus_payload_, message_length, systitle_length, header_offset))
return;
if (message_length < DECODER_START_OFFSET || message_length > MAX_MESSAGE_LENGTH) {
ESP_LOGE(TAG, "DLMS: Message length invalid: %u", message_length);
this->receive_buffer_.clear();
return;
}
// Decrypt in place and then decode the OBIS codes
if (!this->decrypt_(this->mbus_payload_, message_length, systitle_length, header_offset))
return;
this->decode_obis_(&this->mbus_payload_[header_offset + DLMS_PAYLOAD_OFFSET], message_length);
}
}
bool DlmsMeterComponent::parse_mbus_(std::vector<uint8_t> &mbus_payload) {
ESP_LOGV(TAG, "Parsing M-Bus frames");
uint16_t frame_offset = 0; // Offset is used if the M-Bus message is split into multiple frames
while (frame_offset < this->receive_buffer_.size()) {
// Ensure enough bytes remain for the minimal intro header before accessing indices
if (this->receive_buffer_.size() - frame_offset < MBUS_HEADER_INTRO_LENGTH) {
ESP_LOGE(TAG, "MBUS: Not enough data for frame header (need %d, have %d)", MBUS_HEADER_INTRO_LENGTH,
(this->receive_buffer_.size() - frame_offset));
this->receive_buffer_.clear();
return false;
}
// Check start bytes
if (this->receive_buffer_[frame_offset + MBUS_START1_OFFSET] != START_BYTE_LONG_FRAME ||
this->receive_buffer_[frame_offset + MBUS_START2_OFFSET] != START_BYTE_LONG_FRAME) {
ESP_LOGE(TAG, "MBUS: Start bytes do not match");
this->receive_buffer_.clear();
return false;
}
// Both length bytes must be identical
if (this->receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET] !=
this->receive_buffer_[frame_offset + MBUS_LENGTH2_OFFSET]) {
ESP_LOGE(TAG, "MBUS: Length bytes do not match");
this->receive_buffer_.clear();
return false;
}
uint8_t frame_length = this->receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET]; // Get length of this frame
// Check if received data is enough for the given frame length
if (this->receive_buffer_.size() - frame_offset <
frame_length + 3) { // length field inside packet does not account for second start- + checksum- + stop- byte
ESP_LOGE(TAG, "MBUS: Frame too big for received data");
this->receive_buffer_.clear();
return false;
}
// Ensure we have full frame (header + payload + checksum + stop byte) before accessing stop byte
size_t required_total =
frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH; // payload + header + 2 footer bytes
if (this->receive_buffer_.size() - frame_offset < required_total) {
ESP_LOGE(TAG, "MBUS: Incomplete frame (need %d, have %d)", (unsigned int) required_total,
this->receive_buffer_.size() - frame_offset);
this->receive_buffer_.clear();
return false;
}
if (this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH - 1] !=
STOP_BYTE) {
ESP_LOGE(TAG, "MBUS: Invalid stop byte");
this->receive_buffer_.clear();
return false;
}
// Verify checksum: sum of all bytes starting at MBUS_HEADER_INTRO_LENGTH, take last byte
uint8_t checksum = 0; // use uint8_t so only the 8 least significant bits are stored
for (uint16_t i = 0; i < frame_length; i++) {
checksum += this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + i];
}
if (checksum != this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]) {
ESP_LOGE(TAG, "MBUS: Invalid checksum: %x != %x", checksum,
this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]);
this->receive_buffer_.clear();
return false;
}
mbus_payload.insert(mbus_payload.end(), &this->receive_buffer_[frame_offset + MBUS_FULL_HEADER_LENGTH],
&this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + frame_length]);
frame_offset += MBUS_HEADER_INTRO_LENGTH + frame_length + MBUS_FOOTER_LENGTH;
}
return true;
}
bool DlmsMeterComponent::parse_dlms_(const std::vector<uint8_t> &mbus_payload, uint16_t &message_length,
uint8_t &systitle_length, uint16_t &header_offset) {
ESP_LOGV(TAG, "Parsing DLMS header");
if (mbus_payload.size() < DLMS_HEADER_LENGTH + DLMS_HEADER_EXT_OFFSET) {
ESP_LOGE(TAG, "DLMS: Payload too short");
this->receive_buffer_.clear();
return false;
}
if (mbus_payload[DLMS_CIPHER_OFFSET] != GLO_CIPHERING) { // Only general-glo-ciphering is supported (0xDB)
ESP_LOGE(TAG, "DLMS: Unsupported cipher");
this->receive_buffer_.clear();
return false;
}
systitle_length = mbus_payload[DLMS_SYST_OFFSET];
if (systitle_length != 0x08) { // Only system titles with length of 8 are supported
ESP_LOGE(TAG, "DLMS: Unsupported system title length");
this->receive_buffer_.clear();
return false;
}
message_length = mbus_payload[DLMS_LENGTH_OFFSET];
header_offset = 0;
if (this->provider_ == PROVIDER_NETZNOE) {
// for some reason EVN seems to set the standard "length" field to 0x81 and then the actual length is in the next
// byte. Check some bytes to see if received data still matches expectation
if (message_length == NETZ_NOE_MAGIC_BYTE &&
mbus_payload[DLMS_LENGTH_OFFSET + 1] == NETZ_NOE_EXPECTED_MESSAGE_LENGTH &&
mbus_payload[DLMS_LENGTH_OFFSET + 2] == NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE) {
message_length = mbus_payload[DLMS_LENGTH_OFFSET + 1];
header_offset = 1;
} else {
ESP_LOGE(TAG, "Wrong Length - Security Control Byte sequence detected for provider EVN");
}
} else {
if (message_length == TWO_BYTE_LENGTH) {
message_length = encode_uint16(mbus_payload[DLMS_LENGTH_OFFSET + 1], mbus_payload[DLMS_LENGTH_OFFSET + 2]);
header_offset = DLMS_HEADER_EXT_OFFSET;
}
}
if (message_length < DLMS_LENGTH_CORRECTION) {
ESP_LOGE(TAG, "DLMS: Message length too short: %u", message_length);
this->receive_buffer_.clear();
return false;
}
message_length -= DLMS_LENGTH_CORRECTION; // Correct message length due to part of header being included in length
if (mbus_payload.size() - DLMS_HEADER_LENGTH - header_offset != message_length) {
ESP_LOGV(TAG, "DLMS: Length mismatch - payload=%d, header=%d, offset=%d, message=%d", mbus_payload.size(),
DLMS_HEADER_LENGTH, header_offset, message_length);
ESP_LOGE(TAG, "DLMS: Message has invalid length");
this->receive_buffer_.clear();
return false;
}
if (mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] != 0x21 &&
mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] !=
0x20) { // Only certain security suite is supported (0x21 || 0x20)
ESP_LOGE(TAG, "DLMS: Unsupported security control byte");
this->receive_buffer_.clear();
return false;
}
return true;
}
bool DlmsMeterComponent::decrypt_(std::vector<uint8_t> &mbus_payload, uint16_t message_length, uint8_t systitle_length,
uint16_t header_offset) {
ESP_LOGV(TAG, "Decrypting payload");
uint8_t iv[12]; // Reserve space for the IV, always 12 bytes
// Copy system title to IV (System title is before length; no header offset needed!)
// Add 1 to the offset in order to skip the system title length byte
memcpy(&iv[0], &mbus_payload[DLMS_SYST_OFFSET + 1], systitle_length);
memcpy(&iv[8], &mbus_payload[header_offset + DLMS_FRAMECOUNTER_OFFSET],
DLMS_FRAMECOUNTER_LENGTH); // Copy frame counter to IV
uint8_t *payload_ptr = &mbus_payload[header_offset + DLMS_PAYLOAD_OFFSET];
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
br_gcm_context gcm_ctx;
br_aes_ct_ctr_keys bc;
br_aes_ct_ctr_init(&bc, this->decryption_key_.data(), this->decryption_key_.size());
br_gcm_init(&gcm_ctx, &bc.vtable, br_ghash_ctmul32);
br_gcm_reset(&gcm_ctx, iv, sizeof(iv));
br_gcm_flip(&gcm_ctx);
br_gcm_run(&gcm_ctx, 0, payload_ptr, message_length);
#elif defined(USE_ESP32)
size_t outlen = 0;
mbedtls_gcm_context gcm_ctx;
mbedtls_gcm_init(&gcm_ctx);
mbedtls_gcm_setkey(&gcm_ctx, MBEDTLS_CIPHER_ID_AES, this->decryption_key_.data(), this->decryption_key_.size() * 8);
mbedtls_gcm_starts(&gcm_ctx, MBEDTLS_GCM_DECRYPT, iv, sizeof(iv));
auto ret = mbedtls_gcm_update(&gcm_ctx, payload_ptr, message_length, payload_ptr, message_length, &outlen);
mbedtls_gcm_free(&gcm_ctx);
if (ret != 0) {
ESP_LOGE(TAG, "Decryption failed with error: %d", ret);
this->receive_buffer_.clear();
return false;
}
#else
#error "Invalid Platform"
#endif
if (payload_ptr[0] != DATA_NOTIFICATION || payload_ptr[5] != TIMESTAMP_DATETIME) {
ESP_LOGE(TAG, "OBIS: Packet was decrypted but data is invalid");
this->receive_buffer_.clear();
return false;
}
ESP_LOGV(TAG, "Decrypted payload: %d bytes", message_length);
return true;
}
void DlmsMeterComponent::decode_obis_(uint8_t *plaintext, uint16_t message_length) {
ESP_LOGV(TAG, "Decoding payload");
MeterData data{};
uint16_t current_position = DECODER_START_OFFSET;
bool power_factor_found = false;
while (current_position + OBIS_CODE_OFFSET <= message_length) {
if (plaintext[current_position + OBIS_TYPE_OFFSET] != DataType::OCTET_STRING) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS header type: %x", plaintext[current_position + OBIS_TYPE_OFFSET]);
this->receive_buffer_.clear();
return;
}
uint8_t obis_code_length = plaintext[current_position + OBIS_LENGTH_OFFSET];
if (obis_code_length != OBIS_CODE_LENGTH_STANDARD && obis_code_length != OBIS_CODE_LENGTH_EXTENDED) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS header length: %x", obis_code_length);
this->receive_buffer_.clear();
return;
}
if (current_position + OBIS_CODE_OFFSET + obis_code_length > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for OBIS code");
this->receive_buffer_.clear();
return;
}
uint8_t *obis_code = &plaintext[current_position + OBIS_CODE_OFFSET];
uint8_t obis_medium = obis_code[OBIS_A];
uint16_t obis_cd = encode_uint16(obis_code[OBIS_C], obis_code[OBIS_D]);
bool timestamp_found = false;
bool meter_number_found = false;
if (this->provider_ == PROVIDER_NETZNOE) {
// Do not advance Position when reading the Timestamp at DECODER_START_OFFSET
if ((obis_code_length == OBIS_CODE_LENGTH_EXTENDED) && (current_position == DECODER_START_OFFSET)) {
timestamp_found = true;
} else if (power_factor_found) {
meter_number_found = true;
power_factor_found = false;
} else {
current_position += obis_code_length + OBIS_CODE_OFFSET; // Advance past code and position
}
} else {
current_position += obis_code_length + OBIS_CODE_OFFSET; // Advance past code, position and type
}
if (!timestamp_found && !meter_number_found && obis_medium != Medium::ELECTRICITY &&
obis_medium != Medium::ABSTRACT) {
ESP_LOGE(TAG, "OBIS: Unsupported OBIS medium: %x", obis_medium);
this->receive_buffer_.clear();
return;
}
if (current_position >= message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for data type");
this->receive_buffer_.clear();
return;
}
float value = 0.0f;
uint8_t value_size = 0;
uint8_t data_type = plaintext[current_position];
current_position++;
switch (data_type) {
case DataType::DOUBLE_LONG_UNSIGNED: {
value_size = 4;
if (current_position + value_size > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for DOUBLE_LONG_UNSIGNED");
this->receive_buffer_.clear();
return;
}
value = encode_uint32(plaintext[current_position + 0], plaintext[current_position + 1],
plaintext[current_position + 2], plaintext[current_position + 3]);
current_position += value_size;
break;
}
case DataType::LONG_UNSIGNED: {
value_size = 2;
if (current_position + value_size > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for LONG_UNSIGNED");
this->receive_buffer_.clear();
return;
}
value = encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]);
current_position += value_size;
break;
}
case DataType::OCTET_STRING: {
uint8_t data_length = plaintext[current_position];
current_position++; // Advance past string length
if (current_position + data_length > message_length) {
ESP_LOGE(TAG, "OBIS: Buffer too short for OCTET_STRING");
this->receive_buffer_.clear();
return;
}
// Handle timestamp (normal OBIS code or NETZNOE special case)
if (obis_cd == OBIS_TIMESTAMP || timestamp_found) {
if (data_length < 8) {
ESP_LOGE(TAG, "OBIS: Timestamp data too short: %u", data_length);
this->receive_buffer_.clear();
return;
}
uint16_t year = encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]);
uint8_t month = plaintext[current_position + 2];
uint8_t day = plaintext[current_position + 3];
uint8_t hour = plaintext[current_position + 5];
uint8_t minute = plaintext[current_position + 6];
uint8_t second = plaintext[current_position + 7];
if (year > 9999 || month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59) {
ESP_LOGE(TAG, "Invalid timestamp values: %04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour, minute,
second);
this->receive_buffer_.clear();
return;
}
snprintf(data.timestamp, sizeof(data.timestamp), "%04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour,
minute, second);
} else if (meter_number_found) {
snprintf(data.meternumber, sizeof(data.meternumber), "%.*s", data_length, &plaintext[current_position]);
}
current_position += data_length;
break;
}
default:
ESP_LOGE(TAG, "OBIS: Unsupported OBIS data type: %x", data_type);
this->receive_buffer_.clear();
return;
}
// Skip break after data
if (this->provider_ == PROVIDER_NETZNOE) {
// Don't skip the break on the first timestamp, as there's none
if (!timestamp_found) {
current_position += 2;
}
} else {
current_position += 2;
}
// Check for additional data (scaler-unit structure)
if (current_position < message_length && plaintext[current_position] == DataType::INTEGER) {
// Apply scaler: real_value = raw_value × 10^scaler
if (current_position + 1 < message_length) {
int8_t scaler = static_cast<int8_t>(plaintext[current_position + 1]);
if (scaler != 0) {
value *= powf(10.0f, scaler);
}
}
// on EVN Meters there is no additional break
if (this->provider_ == PROVIDER_NETZNOE) {
current_position += 4;
} else {
current_position += 6;
}
}
// Handle numeric values (LONG_UNSIGNED and DOUBLE_LONG_UNSIGNED)
if (value_size > 0) {
switch (obis_cd) {
case OBIS_VOLTAGE_L1:
data.voltage_l1 = value;
break;
case OBIS_VOLTAGE_L2:
data.voltage_l2 = value;
break;
case OBIS_VOLTAGE_L3:
data.voltage_l3 = value;
break;
case OBIS_CURRENT_L1:
data.current_l1 = value;
break;
case OBIS_CURRENT_L2:
data.current_l2 = value;
break;
case OBIS_CURRENT_L3:
data.current_l3 = value;
break;
case OBIS_ACTIVE_POWER_PLUS:
data.active_power_plus = value;
break;
case OBIS_ACTIVE_POWER_MINUS:
data.active_power_minus = value;
break;
case OBIS_ACTIVE_ENERGY_PLUS:
data.active_energy_plus = value;
break;
case OBIS_ACTIVE_ENERGY_MINUS:
data.active_energy_minus = value;
break;
case OBIS_REACTIVE_ENERGY_PLUS:
data.reactive_energy_plus = value;
break;
case OBIS_REACTIVE_ENERGY_MINUS:
data.reactive_energy_minus = value;
break;
case OBIS_POWER_FACTOR:
data.power_factor = value;
power_factor_found = true;
break;
default:
ESP_LOGW(TAG, "Unsupported OBIS code 0x%04X", obis_cd);
}
}
}
this->receive_buffer_.clear();
ESP_LOGI(TAG, "Received valid data");
this->publish_sensors(data);
this->status_clear_warning();
}
} // namespace esphome::dlms_meter

View File

@@ -1,96 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#include "esphome/components/uart/uart.h"
#include "mbus.h"
#include "dlms.h"
#include "obis.h"
#include <array>
#include <vector>
namespace esphome::dlms_meter {
#ifndef DLMS_METER_SENSOR_LIST
#define DLMS_METER_SENSOR_LIST(F, SEP)
#endif
#ifndef DLMS_METER_TEXT_SENSOR_LIST
#define DLMS_METER_TEXT_SENSOR_LIST(F, SEP)
#endif
struct MeterData {
float voltage_l1 = 0.0f; // Voltage L1
float voltage_l2 = 0.0f; // Voltage L2
float voltage_l3 = 0.0f; // Voltage L3
float current_l1 = 0.0f; // Current L1
float current_l2 = 0.0f; // Current L2
float current_l3 = 0.0f; // Current L3
float active_power_plus = 0.0f; // Active power taken from grid
float active_power_minus = 0.0f; // Active power put into grid
float active_energy_plus = 0.0f; // Active energy taken from grid
float active_energy_minus = 0.0f; // Active energy put into grid
float reactive_energy_plus = 0.0f; // Reactive energy taken from grid
float reactive_energy_minus = 0.0f; // Reactive energy put into grid
char timestamp[27]{}; // Text sensor for the timestamp value
// Netz NOE
float power_factor = 0.0f; // Power Factor
char meternumber[13]{}; // Text sensor for the meterNumber value
};
// Provider constants
enum Providers : uint32_t { PROVIDER_GENERIC = 0x00, PROVIDER_NETZNOE = 0x01 };
class DlmsMeterComponent : public Component, public uart::UARTDevice {
public:
DlmsMeterComponent() = default;
void dump_config() override;
void loop() override;
void set_decryption_key(const std::array<uint8_t, 16> &key) { this->decryption_key_ = key; }
void set_provider(uint32_t provider) { this->provider_ = provider; }
void publish_sensors(MeterData &data) {
#define DLMS_METER_PUBLISH_SENSOR(s) \
if (this->s##_sensor_ != nullptr) \
s##_sensor_->publish_state(data.s);
DLMS_METER_SENSOR_LIST(DLMS_METER_PUBLISH_SENSOR, )
#define DLMS_METER_PUBLISH_TEXT_SENSOR(s) \
if (this->s##_text_sensor_ != nullptr) \
s##_text_sensor_->publish_state(data.s);
DLMS_METER_TEXT_SENSOR_LIST(DLMS_METER_PUBLISH_TEXT_SENSOR, )
}
DLMS_METER_SENSOR_LIST(SUB_SENSOR, )
DLMS_METER_TEXT_SENSOR_LIST(SUB_TEXT_SENSOR, )
protected:
bool parse_mbus_(std::vector<uint8_t> &mbus_payload);
bool parse_dlms_(const std::vector<uint8_t> &mbus_payload, uint16_t &message_length, uint8_t &systitle_length,
uint16_t &header_offset);
bool decrypt_(std::vector<uint8_t> &mbus_payload, uint16_t message_length, uint8_t systitle_length,
uint16_t header_offset);
void decode_obis_(uint8_t *plaintext, uint16_t message_length);
std::vector<uint8_t> receive_buffer_; // Stores the packet currently being received
std::vector<uint8_t> mbus_payload_; // Parsed M-Bus payload, reused to avoid heap churn
uint32_t last_read_ = 0; // Timestamp when data was last read
uint32_t read_timeout_ = 1000; // Time to wait after last byte before considering data complete
uint32_t provider_ = PROVIDER_GENERIC; // Provider of the meter / your grid operator
std::array<uint8_t, 16> decryption_key_;
};
} // namespace esphome::dlms_meter

View File

@@ -1,69 +0,0 @@
#pragma once
#include <cstdint>
namespace esphome::dlms_meter {
/*
+----------------------------------------------------+ -
| Start Character [0x68] | \
+----------------------------------------------------+ |
| Data Length (L) | |
+----------------------------------------------------+ |
| Data Length Repeat (L) | |
+----------------------------------------------------+ > M-Bus Data link layer
| Start Character Repeat [0x68] | |
+----------------------------------------------------+ |
| Control/Function Field (C) | |
+----------------------------------------------------+ |
| Address Field (A) | /
+----------------------------------------------------+ -
| Control Information Field (CI) | \
+----------------------------------------------------+ |
| Source Transport Service Access Point (STSAP) | > DLMS/COSEM M-Bus transport layer
+----------------------------------------------------+ |
| Destination Transport Service Access Point (DTSAP) | /
+----------------------------------------------------+ -
| | \
~ ~ |
Data > DLMS/COSEM Application Layer
~ ~ |
| | /
+----------------------------------------------------+ -
| Checksum | \
+----------------------------------------------------+ > M-Bus Data link layer
| Stop Character [0x16] | /
+----------------------------------------------------+ -
Data_Length = L - C - A - CI
Each line (except Data) is one Byte
Possible Values found in publicly available docs:
- C: 0x53/0x73 (SND_UD)
- A: FF (Broadcast)
- CI: 0x00-0x1F/0x60/0x61/0x7C/0x7D
- STSAP: 0x01 (Management Logical Device ID 1 of the meter)
- DTSAP: 0x67 (Consumer Information Push Client ID 103)
*/
// MBUS start bytes for different telegram formats:
// - Single Character: 0xE5 (length=1)
// - Short Frame: 0x10 (length=5)
// - Control Frame: 0x68 (length=9)
// - Long Frame: 0x68 (length=9+data_length)
// This component currently only uses Long Frame.
static constexpr uint8_t START_BYTE_SINGLE_CHARACTER = 0xE5;
static constexpr uint8_t START_BYTE_SHORT_FRAME = 0x10;
static constexpr uint8_t START_BYTE_CONTROL_FRAME = 0x68;
static constexpr uint8_t START_BYTE_LONG_FRAME = 0x68;
static constexpr uint8_t MBUS_HEADER_INTRO_LENGTH = 4; // Header length for the intro (0x68, length, length, 0x68)
static constexpr uint8_t MBUS_FULL_HEADER_LENGTH = 9; // Total header length
static constexpr uint8_t MBUS_FOOTER_LENGTH = 2; // Footer after frame
static constexpr uint8_t MBUS_MAX_FRAME_LENGTH = 250; // Maximum size of frame
static constexpr uint8_t MBUS_START1_OFFSET = 0; // Offset of first start byte
static constexpr uint8_t MBUS_LENGTH1_OFFSET = 1; // Offset of first length byte
static constexpr uint8_t MBUS_LENGTH2_OFFSET = 2; // Offset of (duplicated) second length byte
static constexpr uint8_t MBUS_START2_OFFSET = 3; // Offset of (duplicated) second start byte
static constexpr uint8_t STOP_BYTE = 0x16;
} // namespace esphome::dlms_meter

View File

@@ -1,94 +0,0 @@
#pragma once
#include <cstdint>
namespace esphome::dlms_meter {
// Data types as per specification
enum DataType {
NULL_DATA = 0x00,
BOOLEAN = 0x03,
BIT_STRING = 0x04,
DOUBLE_LONG = 0x05,
DOUBLE_LONG_UNSIGNED = 0x06,
OCTET_STRING = 0x09,
VISIBLE_STRING = 0x0A,
UTF8_STRING = 0x0C,
BINARY_CODED_DECIMAL = 0x0D,
INTEGER = 0x0F,
LONG = 0x10,
UNSIGNED = 0x11,
LONG_UNSIGNED = 0x12,
LONG64 = 0x14,
LONG64_UNSIGNED = 0x15,
ENUM = 0x16,
FLOAT32 = 0x17,
FLOAT64 = 0x18,
DATE_TIME = 0x19,
DATE = 0x1A,
TIME = 0x1B,
ARRAY = 0x01,
STRUCTURE = 0x02,
COMPACT_ARRAY = 0x13
};
enum Medium {
ABSTRACT = 0x00,
ELECTRICITY = 0x01,
HEAT_COST_ALLOCATOR = 0x04,
COOLING = 0x05,
HEAT = 0x06,
GAS = 0x07,
COLD_WATER = 0x08,
HOT_WATER = 0x09,
OIL = 0x10,
COMPRESSED_AIR = 0x11,
NITROGEN = 0x12
};
// Data structure
static constexpr uint8_t DECODER_START_OFFSET = 20; // Skip header, timestamp and break block
static constexpr uint8_t OBIS_TYPE_OFFSET = 0;
static constexpr uint8_t OBIS_LENGTH_OFFSET = 1;
static constexpr uint8_t OBIS_CODE_OFFSET = 2;
static constexpr uint8_t OBIS_CODE_LENGTH_STANDARD = 0x06; // 6-byte OBIS code (A.B.C.D.E.F)
static constexpr uint8_t OBIS_CODE_LENGTH_EXTENDED = 0x0C; // 12-byte extended OBIS code
static constexpr uint8_t OBIS_A = 0;
static constexpr uint8_t OBIS_B = 1;
static constexpr uint8_t OBIS_C = 2;
static constexpr uint8_t OBIS_D = 3;
static constexpr uint8_t OBIS_E = 4;
static constexpr uint8_t OBIS_F = 5;
// Metadata
static constexpr uint16_t OBIS_TIMESTAMP = 0x0100;
static constexpr uint16_t OBIS_SERIAL_NUMBER = 0x6001;
static constexpr uint16_t OBIS_DEVICE_NAME = 0x2A00;
// Voltage
static constexpr uint16_t OBIS_VOLTAGE_L1 = 0x2007;
static constexpr uint16_t OBIS_VOLTAGE_L2 = 0x3407;
static constexpr uint16_t OBIS_VOLTAGE_L3 = 0x4807;
// Current
static constexpr uint16_t OBIS_CURRENT_L1 = 0x1F07;
static constexpr uint16_t OBIS_CURRENT_L2 = 0x3307;
static constexpr uint16_t OBIS_CURRENT_L3 = 0x4707;
// Power
static constexpr uint16_t OBIS_ACTIVE_POWER_PLUS = 0x0107;
static constexpr uint16_t OBIS_ACTIVE_POWER_MINUS = 0x0207;
// Active energy
static constexpr uint16_t OBIS_ACTIVE_ENERGY_PLUS = 0x0108;
static constexpr uint16_t OBIS_ACTIVE_ENERGY_MINUS = 0x0208;
// Reactive energy
static constexpr uint16_t OBIS_REACTIVE_ENERGY_PLUS = 0x0308;
static constexpr uint16_t OBIS_REACTIVE_ENERGY_MINUS = 0x0408;
// Netz NOE specific
static constexpr uint16_t OBIS_POWER_FACTOR = 0x0D07;
} // namespace esphome::dlms_meter

View File

@@ -1,124 +0,0 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT,
UNIT_WATT_HOURS,
)
from .. import CONF_DLMS_METER_ID, DlmsMeterComponent
AUTO_LOAD = ["dlms_meter"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent),
cv.Optional("voltage_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("voltage_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l1"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l2"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("current_l3"): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_power_plus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_power_minus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional("active_energy_plus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("active_energy_minus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("reactive_energy_plus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
cv.Optional("reactive_energy_minus"): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT_HOURS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
# Netz NOE
cv.Optional("power_factor"): sensor.sensor_schema(
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
hub = await cg.get_variable(config[CONF_DLMS_METER_ID])
sensors = []
for key, conf in config.items():
if not isinstance(conf, dict):
continue
id = conf[CONF_ID]
if id and id.type == sensor.Sensor:
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
sensors.append(f"F({key})")
if sensors:
cg.add_define(
"DLMS_METER_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors))
)

View File

@@ -1,37 +0,0 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID
from .. import CONF_DLMS_METER_ID, DlmsMeterComponent
AUTO_LOAD = ["dlms_meter"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent),
cv.Optional("timestamp"): text_sensor.text_sensor_schema(),
# Netz NOE
cv.Optional("meternumber"): text_sensor.text_sensor_schema(),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
hub = await cg.get_variable(config[CONF_DLMS_METER_ID])
text_sensors = []
for key, conf in config.items():
if not isinstance(conf, dict):
continue
id = conf[CONF_ID]
if id and id.type == text_sensor.TextSensor:
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
text_sensors.append(f"F({key})")
if text_sensors:
cg.add_define(
"DLMS_METER_TEXT_SENSOR_LIST(F, sep)",
cg.RawExpression(" sep ".join(text_sensors)),
)

View File

@@ -98,6 +98,8 @@ void DPS310Component::dump_config() {
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
}
float DPS310Component::get_setup_priority() const { return setup_priority::DATA; }
void DPS310Component::update() {
if (!this->update_in_progress_) {
this->update_in_progress_ = true;

View File

@@ -40,6 +40,7 @@ class DPS310Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }

Some files were not shown because too many files have changed in this diff Show More