Compare commits

..

79 Commits

Author SHA1 Message Date
J. Nick Koston
c8cc29a991 [api] Reduce batch RAM usage by 33% via switch dispatch (#13199) 2026-01-14 03:58:06 +00:00
J. Nick Koston
8b49d465f8 [bh1750] Eliminate heap allocations by replacing callbacks with state machine (#11950) 2026-01-13 17:44:43 -10:00
J. Nick Koston
47ee2f4ad9 [wifi] Use StaticVector for WiFi listeners with per-type compile-time sizing (#13197) 2026-01-14 02:20:39 +00:00
J. Nick Koston
2793e33baf [logger] Use StaticVector for log listeners with compile-time sizing (#13196)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-13 15:43:17 -10:00
J. Nick Koston
5dfdd05122 [logger] Use RAII guards for recursion protection and optimize hot path (#13194)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-13 15:43:02 -10:00
Cougar
be12e3667a [ssd1306_i2c] fix "SSD1306 72x40" display initialization (add SSD1306B Iref setup) (#13148) 2026-01-13 18:30:15 -05:00
Clyde Stubbs
52c631384a [epaper_spi] Add Waveshare 2.13v3 (#13117) 2026-01-13 18:28:24 -05:00
Jonathan Swoboda
45e000f091 [ota] Mark partition valid when OTA begins to prevent rollback blocking (#13195)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 18:27:45 -05:00
tomaszduda23
e45cad45fe [nrf52,zigbee] Add binary output as switch (#13083)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-13 17:39:28 -05:00
J. Nick Koston
3d74d1e7f0 [libretiny] Regenerate boards, enable Cortex-M4 atomics, and consolidate platform code (#13191) 2026-01-13 21:39:11 +00:00
J. Nick Koston
a060d1d044 [wifi] Fix ESP8266 disconnect callback order to set error flag before notifying listeners (#13189) 2026-01-13 11:33:36 -10:00
Kevin Ahrendt
733f57da50 [i2s_audio] Bugfix: Buffer overflow in software volume control (#13190) 2026-01-13 09:42:36 -10:00
dependabot[bot]
4d96c60696 Bump yamllint from 1.37.1 to 1.38.0 (#13192)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 09:36:58 -10:00
J. Nick Koston
3d40979c96 [mqtt] Avoid intermediate string allocations in publish calls (#13174) 2026-01-13 08:05:04 -10:00
J. Nick Koston
7fed9144a6 [api] Use stack buffer for VERY_VERBOSE proto message dumps (#13176) 2026-01-13 08:04:48 -10:00
J. Nick Koston
7abb374f2a [improv_serial] Use stack buffers for webserver URL formatting (#13175) 2026-01-13 08:04:33 -10:00
Jonathan Swoboda
5d90f170e5 Merge branch 'release' into dev 2026-01-13 11:55:58 -05:00
Jonathan Swoboda
6e01c4f86e Merge pull request #13188 from esphome/bump-2025.12.6
2025.12.6
2026-01-13 11:55:44 -05:00
Jonathan Swoboda
f4c17e15ea Bump version to 2025.12.6 2026-01-13 11:01:21 -05:00
J. Nick Koston
d6507ce329 [esphome] Fix OTA backend abort not being called on error (#13182) 2026-01-13 11:01:21 -05:00
Jonathan Swoboda
9504e92458 [remote_transmitter] Fix ESP8266 timing by using busy loop (#13172)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 11:01:21 -05:00
Jonathan Swoboda
3911991de2 [packet_transport] Fix packet size check to account for round4 padding (#13165)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:01:21 -05:00
Jonathan Swoboda
dede47477b [ltr_als_ps] Remove incorrect device_class from count sensors (#13167)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:01:21 -05:00
Jonathan Swoboda
dca8def0f2 [seeed_mr24hpc1] Add ifdef guards for conditional entity types (#13147)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:01:21 -05:00
Samuel Sieb
a1727a8901 [espnow] fix channel validation (#13057) 2026-01-13 11:01:20 -05:00
Samuel Sieb
48f5296d24 [ld24xx] add id to support extending (#13183)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-01-12 22:32:20 -10:00
Samuel Sieb
1327776d5b [bme68x_bsec2] use EntityBase instead of Component for the id (#13185)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-01-12 22:32:11 -10:00
J. Nick Koston
df4a3e8915 [socket] Call lwip_read/lwip_write directly on ESP32 to reduce network I/O latency (#13179) 2026-01-13 01:47:11 -06:00
Keith Burzinski
6823e17b3b [ir_rf_proxy] New component (#12985)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-13 07:44:24 +00:00
J. Nick Koston
675103bed0 [esphome] Fix OTA backend abort not being called on error (#13182) 2026-01-12 20:55:40 -10:00
J. Nick Koston
6c043be4d3 [ci] Add format_hex_pretty to heap-allocating helper lint check (#13178) 2026-01-12 20:55:23 -10:00
Rodrigo Martín
e9469cbe48 [mqtt] templatable state and command topics (#12441)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-12 17:40:27 -10:00
dependabot[bot]
5890cdf69a Bump github/codeql-action from 4.31.9 to 4.31.10 (#13173)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-12 16:31:51 -10:00
lullius
297f05d600 [tuya] add color_type_lowercase option (#13101)
Co-authored-by: lullius <>
2026-01-12 18:08:33 -05:00
Jonathan Swoboda
54fc10714d [remote_transmitter] Fix ESP8266 timing by using busy loop (#13172)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 18:06:41 -05:00
J. Nick Koston
889886909b [core] Soft deprecate heap-allocating string helpers to prevent fragmentation patterns (#13156) 2026-01-12 12:48:54 -10:00
J. Nick Koston
655e2b43cb [infrared] Use set_data() for vector timings in control() (#13171) 2026-01-12 15:27:42 -06:00
J. Nick Koston
81e639a6ba [core] Migrate callers and soft deprecate get_mac_address()/get_mac_address_pretty() (#13157) 2026-01-12 19:35:49 +00:00
Jonathan Swoboda
f9ffd134df [packet_transport] Fix packet size check to account for round4 padding (#13165)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 14:10:15 -05:00
Jonathan Swoboda
c50bf45496 [ltr_als_ps] Remove incorrect device_class from count sensors (#13167)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 14:09:54 -05:00
J. Nick Koston
9f9341a700 [web_server] Fix select compilation error in v1 (#13169) 2026-01-12 18:42:10 +00:00
tomaszduda23
71d532a349 [nrf52,sdk] Add framework version support (#12489) 2026-01-12 13:31:09 -05:00
Jasper van der Neut - Stulen
61a89a97d7 [deep_sleep] Fix GPIO wakeup on ESP32-C3/C6 (#12803) 2026-01-12 13:03:13 -05:00
Jasper van der Neut - Stulen
0c3433d056 [deep_sleep] Fix GPIO wakeup comment (#12815) 2026-01-12 12:57:58 -05:00
mikaabra
7e1cda8f9f [esp32_can] Add listen-only mode to esp32_can component (#13084)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 12:50:59 -05:00
J. Nick Koston
7f0e4eaa84 [nfc] Use stack-based hex formatting in pn7150/pn7160 components (#13163) 2026-01-12 07:38:39 -10:00
J. Nick Koston
8cccfa5369 [mqtt][prometheus][graph] Migrate value_accuracy_to_string() to stack-based alternative (#13159)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-12 07:38:20 -10:00
J. Nick Koston
7ea6bcef88 [api] Use stack buffer for bytes field dumping in proto message logs (#13162) 2026-01-12 07:37:58 -10:00
tomaszduda23
353daa97d0 [nrf52,zigbee] Warning if spaces in description (#13114)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-01-12 14:45:15 +00:00
Jas Strong
6c68ebe86e [rd03d] Filter targets with sentinel speed values (#13146)
Co-authored-by: jas <jas@asspa.in>
2026-01-12 09:25:43 -05:00
dependabot[bot]
29cef3bc5d Bump aioesphomeapi from 43.12.0 to 43.13.0 (#13160)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-11 19:26:40 -10:00
Keith Burzinski
83eebdf15d [infrared] Implement experimental API/Core/component for new component/entity type (#13129)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-01-12 05:01:23 +00:00
J. Nick Koston
595217786c [tuya][rc522][remote_base] Migrate format_hex_pretty() to stack-based alternatives (#13158) 2026-01-12 04:47:57 +00:00
J. Nick Koston
912f94d1e8 [api] Use StringRef for HomeassistantServiceMap.value to eliminate heap allocations (#13154)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 17:54:06 -10:00
J. Nick Koston
ea8ae2ae60 [climate] Return StringRef from get_custom_fan_mode() and get_custom_preset() (#13103) 2026-01-11 17:53:06 -10:00
J. Nick Koston
e1aac7601d [event] Return StringRef from get_last_event_type() (#13104) 2026-01-11 17:52:54 -10:00
J. Nick Koston
f1b11b1855 [light] Return StringRef from LightEffect::get_name() and LightState::get_effect_name() (#13105) 2026-01-11 17:52:39 -10:00
J. Nick Koston
23f9f70b71 [select] Return StringRef from current_option() (#13095) 2026-01-11 17:40:43 -10:00
J. Nick Koston
eeeae53f76 [fan] Return StringRef from get_preset_mode() for safety and modern API (#13092) 2026-01-11 17:40:09 -10:00
J. Nick Koston
45c0796e40 [ci] Add RP2040 to memory impact analysis (#13134) 2026-01-11 17:19:00 -10:00
J. Nick Koston
38e2e4a56d [runtime_stats] Fix log output formatting alignment (#13155) 2026-01-11 17:18:49 -10:00
J. Nick Koston
52132ea3bc [ch422g][lc709203f][qmc5883l] Avoid heap allocation in status_set_warning calls (#13152) 2026-01-11 17:18:37 -10:00
J. Nick Koston
ace3ff2170 [safe_mode] Conditionally compile callback when on_safe_mode is configured (#13136) 2026-01-11 17:18:24 -10:00
J. Nick Koston
26e90b4ca6 [light] Move LightColorValues::lerp() out of header to reduce code duplication (#13138) 2026-01-11 17:18:13 -10:00
J. Nick Koston
684790c2ab [web_server_idf] Reduce heap usage in DefaultHeaders and auth (#13141) 2026-01-11 17:17:57 -10:00
J. Nick Koston
6a3737bac3 [improv_serial] Use int8_to_str to avoid heap allocation for RSSI formatting (#13149) 2026-01-11 17:17:44 -10:00
J. Nick Koston
723ca57617 [uptime] Format text sensor output on stack to avoid heap allocations (#13150)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 17:17:32 -10:00
J. Nick Koston
909bd1074a [wifi] Fix captive portal/improv only attempting last configured network (#13086)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-11 17:17:18 -10:00
J. Nick Koston
68064dc974 [web_server] Fix v1 compilation on ESP-IDF by adding missing write method (#13153) 2026-01-11 17:17:07 -10:00
Jonathan Swoboda
742d724e65 [seeed_mr24hpc1] Add ifdef guards for conditional entity types (#13147)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:16:55 -05:00
dependabot[bot]
5ae46a4369 Bump aioesphomeapi from 43.11.0 to 43.12.0 (#13139)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-11 09:49:17 -10:00
J. Nick Koston
a1395af763 [helpers] Add format_hex_prefixed_to for "0x" prefixed hex formatting (#13115) 2026-01-10 17:07:21 -10:00
J. Nick Koston
6222fae907 [libretiny] Disable BLE stack on BK7231N to save ~21KB RAM (#13131) 2026-01-10 16:43:15 -10:00
J. Nick Koston
e34532f283 [sensor] Use C++17 nested namespace syntax (#13116) 2026-01-10 21:42:35 -05:00
Keith Burzinski
f2eb61a767 [api] Proto code generator changes for #12985 (#13100)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-01-10 15:43:27 -10:00
dependabot[bot]
5725a4840e Bump aioesphomeapi from 43.10.1 to 43.11.0 (#13132)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-11 01:09:25 +00:00
J. Nick Koston
de82f96ccb [core] Rename FixedVector::shrink_to_fit() to release() for clarity (#13130) 2026-01-11 00:43:31 +00:00
Jonathan Swoboda
6c981d8b71 [esp32_hosted] Bump component versions (#13118)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 10:26:42 -10:00
Jas Strong
c03faf2d9a [aqi] Fix precision loss for low PM concentration values (#13120)
Co-authored-by: jas <jas@asspa.in>
2026-01-10 09:40:14 -10:00
270 changed files with 7450 additions and 3789 deletions

View File

@@ -293,6 +293,12 @@ This document provides essential context for AI models interacting with this pro
* **Configuration Design:** Aim for simplicity with sensible defaults, while allowing for advanced customization.
* **Embedded Systems Optimization:** ESPHome targets resource-constrained microcontrollers. Be mindful of flash size and RAM usage.
**Why Heap Allocation Matters:**
ESP devices run for months with small heaps shared between Wi-Fi, BLE, LWIP, and application code. Over time, repeated allocations of different sizes fragment the heap. Failures happen when the largest contiguous block shrinks, even if total free heap is still large. We have seen field crashes caused by this.
**Heap allocation after `setup()` should be avoided unless absolutely unavoidable.** Every allocation/deallocation cycle contributes to fragmentation. ESPHome treats runtime heap allocation as a long-term reliability bug, not a performance issue. Helpers that hide allocation (`std::string`, `std::to_string`, string-returning helpers) are being deprecated and replaced with buffer and view based APIs.
**STL Container Guidelines:**
ESPHome runs on embedded systems with limited resources. Choose containers carefully:
@@ -322,15 +328,15 @@ This document provides essential context for AI models interacting with this pro
std::array<uint8_t, 256> buffer;
```
2. **Compile-time-known fixed sizes with vector-like API:** Use `StaticVector` from `esphome/core/helpers.h` for fixed-size stack allocation with `push_back()` interface.
2. **Compile-time-known fixed sizes with vector-like API:** Use `StaticVector` from `esphome/core/helpers.h` for compile-time fixed size with `push_back()` interface (no dynamic allocation).
```cpp
// Bad - generates STL realloc code (_M_realloc_insert)
std::vector<ServiceRecord> services;
services.reserve(5); // Still includes reallocation machinery
// Good - compile-time fixed size, stack allocated, no reallocation machinery
StaticVector<ServiceRecord, MAX_SERVICES> services; // Allocates all MAX_SERVICES on stack
services.push_back(record1); // Tracks count but all slots allocated
// Good - compile-time fixed size, no dynamic allocation
StaticVector<ServiceRecord, MAX_SERVICES> services;
services.push_back(record1);
```
Use `cg.add_define("MAX_SERVICES", count)` to set the size from Python configuration.
Like `std::array` but with vector-like API (`push_back()`, `size()`) and no STL reallocation code.
@@ -372,22 +378,21 @@ This document provides essential context for AI models interacting with this pro
```
Linear search on small datasets (1-16 elements) is often faster than hashing/tree overhead, but this depends on lookup frequency and access patterns. For frequent lookups in hot code paths, the O(1) vs O(n) complexity difference may still matter even for small datasets. `std::vector` with simple structs is usually fine—it's the heavy containers (`map`, `set`, `unordered_map`) that should be avoided for small datasets unless profiling shows otherwise.
5. **Detection:** Look for these patterns in compiler output:
5. **Avoid `std::deque`:** It allocates in 512-byte blocks regardless of element size, guaranteeing at least 512 bytes of RAM usage immediately. This is a major source of crashes on memory-constrained devices.
6. **Detection:** Look for these patterns in compiler output:
- Large code sections with STL symbols (vector, map, set)
- `alloc`, `realloc`, `dealloc` in symbol names
- `_M_realloc_insert`, `_M_default_append` (vector reallocation)
- Red-black tree code (`rb_tree`, `_Rb_tree`)
- Hash table infrastructure (`unordered_map`, `hash`)
**When to optimize:**
**Prioritize optimization effort for:**
- Core components (API, network, logger)
- Widely-used components (mdns, wifi, ble)
- Components causing flash size complaints
**When not to optimize:**
- Single-use niche components
- Code where readability matters more than bytes
- Already using appropriate containers
Note: Avoiding heap allocation after `setup()` is always required regardless of component type. The prioritization above is about the effort spent on container optimization (e.g., migrating from `std::vector` to `StaticVector`).
* **State Management:** Use `CORE.data` for component state that needs to persist during configuration generation. Avoid module-level mutable globals.

View File

@@ -27,6 +27,7 @@
- [ ] RP2040
- [ ] BK72xx
- [ ] RTL87xx
- [ ] LN882x
- [ ] nRF52840
## Example entry for `config.yaml`:

View File

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

View File

@@ -249,11 +249,13 @@ esphome/components/ina260/* @mreditor97
esphome/components/ina2xx_base/* @latonita
esphome/components/ina2xx_i2c/* @latonita
esphome/components/ina2xx_spi/* @latonita
esphome/components/infrared/* @kbx81
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate/* @jesserockz @JosipKuci
esphome/components/integration/* @OttoWinter
esphome/components/internal_temperature/* @Mat931
esphome/components/interval/* @esphome/core
esphome/components/ir_rf_proxy/* @kbx81
esphome/components/jsn_sr04t/* @Mafus1
esphome/components/json/* @esphome/core
esphome/components/kamstrup_kmp/* @cfeenstra1024

View File

@@ -34,7 +34,6 @@ from esphome.const import (
CONF_MDNS,
CONF_MQTT,
CONF_NAME,
CONF_NAME_ADD_MAC_SUFFIX,
CONF_OTA,
CONF_PASSWORD,
CONF_PLATFORM,
@@ -60,7 +59,6 @@ from esphome.util import (
run_external_process,
safe_print,
)
from esphome.zeroconf import discover_mdns_devices
_LOGGER = logging.getLogger(__name__)
@@ -234,34 +232,22 @@ def choose_upload_log_host(
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
]
def add_ota_options() -> None:
"""Add OTA options, using mDNS discovery if name_add_mac_suffix is enabled."""
if has_name_add_mac_suffix() and has_mdns() and has_non_ip_address():
# Discover devices via mDNS when name_add_mac_suffix is enabled
safe_print("Discovering devices...")
discovered = discover_mdns_devices(CORE.name)
for device_addr in discovered:
options.append((f"Over The Air ({device_addr})", device_addr))
if not discovered and has_resolvable_address():
# No devices found, show base address as fallback
options.append(
(f"Over The Air ({CORE.address}) (no devices found)", CORE.address)
)
elif has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
if purpose == Purpose.LOGGING:
if has_mqtt_logging():
mqtt_config = CORE.config[CONF_MQTT]
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
if has_api():
add_ota_options()
if has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
elif purpose == Purpose.UPLOADING and has_ota():
add_ota_options()
if has_resolvable_address():
options.append((f"Over The Air ({CORE.address})", CORE.address))
if has_mqtt_ip_lookup():
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
if check_default is not None and check_default in [opt[1] for opt in options]:
return [check_default]
@@ -348,14 +334,6 @@ def has_resolvable_address() -> bool:
return not CORE.address.endswith(".local")
def has_name_add_mac_suffix() -> bool:
"""Check if name_add_mac_suffix is enabled in the config."""
if CORE.config is None:
return False
esphome_config = CORE.config.get(CONF_ESPHOME, {})
return esphome_config.get(CONF_NAME_ADD_MAC_SUFFIX, False)
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
from esphome import mqtt

View File

@@ -4,6 +4,7 @@ import logging
from esphome import automation
from esphome.automation import Condition
import esphome.codegen as cg
from esphome.components.logger import request_log_listener
from esphome.config_helpers import get_logger_level
import esphome.config_validation as cv
from esphome.const import (
@@ -326,6 +327,9 @@ async def to_code(config: ConfigType) -> None:
# Track controller registration for StaticVector sizing
CORE.register_controller()
# Request a log listener slot for API log streaming
request_log_listener()
cg.add(var.set_port(config[CONF_PORT]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))

View File

@@ -66,6 +66,8 @@ service APIConnection {
rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {}
rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {}
rpc infrared_rf_transmit_raw_timings(InfraredRFTransmitRawTimingsRequest) returns (void) {}
}
@@ -763,7 +765,7 @@ message SubscribeHomeassistantServicesRequest {
message HomeassistantServiceMap {
string key = 1;
string value = 2 [(no_zero_copy) = true];
string value = 2;
}
message HomeassistantActionRequest {
@@ -779,7 +781,7 @@ message HomeassistantActionRequest {
bool is_event = 5;
uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"];
bool wants_response = 7 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
string response_template = 8 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
string response_template = 8 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
}
// Message sent by Home Assistant to ESPHome with service call response data
@@ -2437,3 +2439,49 @@ message ZWaveProxyRequest {
ZWaveProxyRequestType type = 1;
bytes data = 2;
}
// ==================== INFRARED ====================
// Note: Feature and capability flag enums are defined in
// esphome/components/infrared/infrared.h
// Listing of infrared instances
message ListEntitiesInfraredResponse {
option (id) = 135;
option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_INFRARED";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
bool disabled_by_default = 5;
EntityCategory entity_category = 6;
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
uint32 capabilities = 8; // Bitfield of InfraredCapabilityFlags
}
// Command to transmit infrared/RF data using raw timings
message InfraredRFTransmitRawTimingsRequest {
option (id) = 136;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_IR_RF";
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
fixed32 key = 2; // Key identifying the transmitter instance
uint32 carrier_frequency = 3; // Carrier frequency in Hz
uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.)
repeated sint32 timings = 5 [packed = true, (packed_buffer) = true]; // Raw timings in microseconds (zigzag-encoded): positive = mark (LED/TX on), negative = space (LED/TX off)
}
// Event message for received infrared/RF data
message InfraredRFReceiveEvent {
option (id) = 137;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_IR_RF";
option (no_delay) = true;
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
fixed32 key = 2; // Key identifying the receiver instance
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector<int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
}

View File

@@ -46,6 +46,9 @@
#ifdef USE_WATER_HEATER
#include "esphome/components/water_heater/water_heater.h"
#endif
#ifdef USE_INFRARED
#include "esphome/components/infrared/infrared.h"
#endif
namespace esphome::api {
@@ -262,8 +265,7 @@ void APIConnection::loop() {
// If we can't send the ping request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority
ESP_LOGW(TAG, "Buffer full, ping queued");
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE,
PingRequest::ESTIMATED_SIZE);
this->schedule_message_front_(nullptr, PingRequest::MESSAGE_TYPE, PingRequest::ESTIMATED_SIZE);
this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
}
}
@@ -302,7 +304,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
#ifdef HAS_PROTO_MESSAGE_DUMP
// If in log-only mode, just log and return
if (conn->flags_.log_only_mode) {
conn->log_send_message_(msg.message_name(), msg.dump());
DumpBuffer dump_buf;
conn->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
return 1; // Return non-zero to indicate "success" for logging
}
#endif
@@ -358,8 +361,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
#ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE);
return this->send_message_smart_(binary_sensor, BinarySensorStateResponse::MESSAGE_TYPE,
BinarySensorStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -385,8 +388,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
#ifdef USE_COVER
bool APIConnection::send_cover_state(cover::Cover *cover) {
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE,
CoverStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -426,8 +428,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
#ifdef USE_FAN
bool APIConnection::send_fan_state(fan::Fan *fan) {
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE,
FanStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -443,7 +444,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
if (traits.supports_direction())
msg.direction = static_cast<enums::FanDirection>(fan->direction);
if (traits.supports_preset_modes() && fan->has_preset_mode())
msg.preset_mode = StringRef(fan->get_preset_mode());
msg.preset_mode = fan->get_preset_mode();
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -478,8 +479,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
#ifdef USE_LIGHT
bool APIConnection::send_light_state(light::LightState *light) {
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE,
LightStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -499,7 +499,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
if (light->supports_effects()) {
resp.effect = light->get_effect_name_ref();
resp.effect = light->get_effect_name();
}
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -522,7 +522,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
effects_list.init(light_effects.size() + 1);
effects_list.push_back("None");
for (auto *effect : light_effects) {
effects_list.push_back(effect->get_name());
// c_str() is safe as effect names are null-terminated strings from codegen
effects_list.push_back(effect->get_name().c_str());
}
}
msg.effects = &effects_list;
@@ -564,8 +565,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
#ifdef USE_SENSOR
bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE,
SensorStateResponse::ESTIMATED_SIZE);
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,
@@ -593,8 +593,7 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
#ifdef USE_SWITCH
bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE,
SwitchStateResponse::ESTIMATED_SIZE);
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,
@@ -628,8 +627,8 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
#ifdef USE_TEXT_SENSOR
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE);
return this->send_message_smart_(text_sensor, TextSensorStateResponse::MESSAGE_TYPE,
TextSensorStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -653,8 +652,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
#ifdef USE_CLIMATE
bool APIConnection::send_climate_state(climate::Climate *climate) {
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE,
ClimateStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -675,13 +673,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
resp.custom_fan_mode = StringRef(climate->get_custom_fan_mode());
resp.custom_fan_mode = climate->get_custom_fan_mode();
}
if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
}
if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
resp.custom_preset = StringRef(climate->get_custom_preset());
resp.custom_preset = climate->get_custom_preset();
}
if (traits.get_supports_swing_modes())
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
@@ -749,8 +747,7 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
#ifdef USE_NUMBER
bool APIConnection::send_number_state(number::Number *number) {
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE,
NumberStateResponse::ESTIMATED_SIZE);
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,
@@ -784,8 +781,7 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
#ifdef USE_DATETIME_DATE
bool APIConnection::send_date_state(datetime::DateEntity *date) {
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE,
DateStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -813,8 +809,7 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
#ifdef USE_DATETIME_TIME
bool APIConnection::send_time_state(datetime::TimeEntity *time) {
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE,
TimeStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -842,8 +837,8 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
#ifdef USE_DATETIME_DATETIME
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -873,8 +868,7 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
#ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text) {
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE,
TextStateResponse::ESTIMATED_SIZE);
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,
@@ -906,15 +900,14 @@ void APIConnection::text_command(const TextCommandRequest &msg) {
#ifdef USE_SELECT
bool APIConnection::send_select_state(select::Select *select) {
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE,
SelectStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
auto *select = static_cast<select::Select *>(entity);
SelectStateResponse resp;
resp.state = StringRef(select->current_option());
resp.state = select->current_option();
resp.missing_state = !select->has_state();
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -951,8 +944,7 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg
#ifdef USE_LOCK
bool APIConnection::send_lock_state(lock::Lock *a_lock) {
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE,
LockStateResponse::ESTIMATED_SIZE);
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,
@@ -992,8 +984,7 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
#ifdef USE_VALVE
bool APIConnection::send_valve_state(valve::Valve *valve) {
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE,
ValveStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -1027,8 +1018,8 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) {
#ifdef USE_MEDIA_PLAYER
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -1310,8 +1301,7 @@ void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) {
#ifdef USE_ALARM_CONTROL_PANEL
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
AlarmControlPanelStateResponse::MESSAGE_TYPE,
return this->send_message_smart_(a_alarm_control_panel, AlarmControlPanelStateResponse::MESSAGE_TYPE,
AlarmControlPanelStateResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
@@ -1364,8 +1354,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#ifdef USE_WATER_HEATER
bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
return this->send_message_smart_(water_heater, &APIConnection::try_send_water_heater_state,
WaterHeaterStateResponse::MESSAGE_TYPE, WaterHeaterStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -1414,14 +1404,16 @@ void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequ
#endif
#ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, const char *event_type) {
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
EventResponse::ESTIMATED_SIZE);
// Event is a special case - unlike other entities with simple state fields,
// events store their state in a member accessed via obj->get_last_event_type()
void APIConnection::send_event(event::Event *event) {
this->send_message_smart_(event, EventResponse::MESSAGE_TYPE, EventResponse::ESTIMATED_SIZE,
event->get_last_event_type_index());
}
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) {
EventResponse resp;
resp.event_type = StringRef(event_type);
resp.event_type = event_type;
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1436,10 +1428,38 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
}
#endif
#ifdef USE_IR_RF
void APIConnection::infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) {
// TODO: When RF is implemented, add a field to the message to distinguish IR vs RF
// and dispatch to the appropriate entity type based on that field.
#ifdef USE_INFRARED
ENTITY_COMMAND_MAKE_CALL(infrared::Infrared, infrared, infrared)
call.set_carrier_frequency(msg.carrier_frequency);
call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
call.set_repeat_count(msg.repeat_count);
call.perform();
#endif
}
void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) {
this->send_message(msg, InfraredRFReceiveEvent::MESSAGE_TYPE);
}
#endif
#ifdef USE_INFRARED
uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) {
auto *infrared = static_cast<infrared::Infrared *>(entity);
ListEntitiesInfraredResponse msg;
msg.capabilities = infrared->get_capability_flags();
return fill_and_encode_entity_info(infrared, msg, ListEntitiesInfraredResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
#endif
#ifdef USE_UPDATE
bool APIConnection::send_update_state(update::UpdateEntity *update) {
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
UpdateStateResponse::ESTIMATED_SIZE);
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,
bool is_single) {
@@ -1862,30 +1882,31 @@ void APIConnection::on_fatal_error() {
this->flags_.remove = true;
}
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type,
uint8_t estimated_size) {
void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index) {
// Check if we already have a message of this type for this entity
// This provides deduplication per entity/message_type combination
// O(n) but optimized for RAM and not performance.
for (auto &item : items) {
if (item.entity == entity && item.message_type == message_type) {
// Replace with new creator
item.creator = creator;
return;
// Skip deduplication for events - they are edge-triggered, every occurrence matters
#ifdef USE_EVENT
if (message_type != EventResponse::MESSAGE_TYPE)
#endif
{
for (const auto &item : items) {
if (item.entity == entity && item.message_type == message_type)
return; // Already queued
}
}
// No existing item found, add new one
items.emplace_back(entity, creator, message_type, estimated_size);
// No existing item found (or event), add new one
items.push_back({entity, message_type, estimated_size, aux_data_index});
}
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
uint8_t estimated_size) {
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
// Add high priority message and swap to front
// This avoids expensive vector::insert which shifts all elements
// Note: We only ever have one high-priority message at a time (ping OR disconnect)
// If we're disconnecting, pings are blocked, so this simple swap is sufficient
items.emplace_back(entity, creator, message_type, estimated_size);
items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED});
if (items.size() > 1) {
// Swap the new high-priority item to the front
std::swap(items.front(), items.back());
@@ -1924,19 +1945,17 @@ void APIConnection::process_batch_() {
if (num_items == 1) {
const auto &item = this->deferred_batch_[0];
// Let the creator calculate size and encode if it fits
uint16_t payload_size =
item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
// 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);
if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// Log messages after send attempt for VV debugging
// It's safe to use the buffer for logging at this point regardless of send result
// Log message after send attempt for VV debugging
this->log_batch_item_(item);
#endif
this->clear_batch_();
} else if (payload_size == 0) {
// Message too large
// Message too large to fit in available space
ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type);
this->clear_batch_();
}
@@ -1981,9 +2000,9 @@ void APIConnection::process_batch_() {
// Process items and encode directly to buffer (up to our limit)
for (size_t i = 0; i < messages_to_process; i++) {
const auto &item = this->deferred_batch_[i];
// Try to encode message
// The creator will calculate overhead to determine if the message fits
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
// 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, false);
if (payload_size == 0) {
// Message won't fit, stop processing
@@ -2049,18 +2068,129 @@ void APIConnection::process_batch_() {
}
}
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single, uint8_t message_type) const {
// 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 is_single) {
#ifdef USE_EVENT
// Special case: EventResponse uses const char * pointer
if (message_type == EventResponse::MESSAGE_TYPE) {
auto *e = static_cast<event::Event *>(entity);
return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single);
// Events need aux_data_index to look up event type from entity
if (item.message_type == EventResponse::MESSAGE_TYPE) {
// Skip if aux_data_index is invalid (should never happen in normal operation)
if (item.aux_data_index == DeferredBatch::AUX_DATA_UNUSED)
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, is_single);
}
#endif
// All other message types use function pointers
return data_.function_ptr(entity, conn, remaining_size, is_single);
// All other message types use function pointer lookup via switch
MessageCreatorPtr func = nullptr;
// Macros to reduce repetitive switch cases
#define CASE_STATE_INFO(entity_name, StateResp, InfoResp) \
case StateResp::MESSAGE_TYPE: \
func = &try_send_##entity_name##_state; \
break; \
case InfoResp::MESSAGE_TYPE: \
func = &try_send_##entity_name##_info; \
break;
#define CASE_INFO_ONLY(entity_name, InfoResp) \
case InfoResp::MESSAGE_TYPE: \
func = &try_send_##entity_name##_info; \
break;
switch (item.message_type) {
#ifdef USE_BINARY_SENSOR
CASE_STATE_INFO(binary_sensor, BinarySensorStateResponse, ListEntitiesBinarySensorResponse)
#endif
#ifdef USE_COVER
CASE_STATE_INFO(cover, CoverStateResponse, ListEntitiesCoverResponse)
#endif
#ifdef USE_FAN
CASE_STATE_INFO(fan, FanStateResponse, ListEntitiesFanResponse)
#endif
#ifdef USE_LIGHT
CASE_STATE_INFO(light, LightStateResponse, ListEntitiesLightResponse)
#endif
#ifdef USE_SENSOR
CASE_STATE_INFO(sensor, SensorStateResponse, ListEntitiesSensorResponse)
#endif
#ifdef USE_SWITCH
CASE_STATE_INFO(switch, SwitchStateResponse, ListEntitiesSwitchResponse)
#endif
#ifdef USE_BUTTON
CASE_INFO_ONLY(button, ListEntitiesButtonResponse)
#endif
#ifdef USE_TEXT_SENSOR
CASE_STATE_INFO(text_sensor, TextSensorStateResponse, ListEntitiesTextSensorResponse)
#endif
#ifdef USE_CLIMATE
CASE_STATE_INFO(climate, ClimateStateResponse, ListEntitiesClimateResponse)
#endif
#ifdef USE_NUMBER
CASE_STATE_INFO(number, NumberStateResponse, ListEntitiesNumberResponse)
#endif
#ifdef USE_DATETIME_DATE
CASE_STATE_INFO(date, DateStateResponse, ListEntitiesDateResponse)
#endif
#ifdef USE_DATETIME_TIME
CASE_STATE_INFO(time, TimeStateResponse, ListEntitiesTimeResponse)
#endif
#ifdef USE_DATETIME_DATETIME
CASE_STATE_INFO(datetime, DateTimeStateResponse, ListEntitiesDateTimeResponse)
#endif
#ifdef USE_TEXT
CASE_STATE_INFO(text, TextStateResponse, ListEntitiesTextResponse)
#endif
#ifdef USE_SELECT
CASE_STATE_INFO(select, SelectStateResponse, ListEntitiesSelectResponse)
#endif
#ifdef USE_LOCK
CASE_STATE_INFO(lock, LockStateResponse, ListEntitiesLockResponse)
#endif
#ifdef USE_VALVE
CASE_STATE_INFO(valve, ValveStateResponse, ListEntitiesValveResponse)
#endif
#ifdef USE_MEDIA_PLAYER
CASE_STATE_INFO(media_player, MediaPlayerStateResponse, ListEntitiesMediaPlayerResponse)
#endif
#ifdef USE_ALARM_CONTROL_PANEL
CASE_STATE_INFO(alarm_control_panel, AlarmControlPanelStateResponse, ListEntitiesAlarmControlPanelResponse)
#endif
#ifdef USE_WATER_HEATER
CASE_STATE_INFO(water_heater, WaterHeaterStateResponse, ListEntitiesWaterHeaterResponse)
#endif
#ifdef USE_CAMERA
CASE_INFO_ONLY(camera, ListEntitiesCameraResponse)
#endif
#ifdef USE_INFRARED
CASE_INFO_ONLY(infrared, ListEntitiesInfraredResponse)
#endif
#ifdef USE_EVENT
CASE_INFO_ONLY(event, ListEntitiesEventResponse)
#endif
#ifdef USE_UPDATE
CASE_STATE_INFO(update, UpdateStateResponse, ListEntitiesUpdateResponse)
#endif
// Special messages (not entity state/info)
case ListEntitiesDoneResponse::MESSAGE_TYPE:
func = &try_send_list_info_done;
break;
case DisconnectRequest::MESSAGE_TYPE:
func = &try_send_disconnect_request;
break;
case PingRequest::MESSAGE_TYPE:
func = &try_send_ping_request;
break;
default:
return 0;
}
#undef CASE_STATE_INFO
#undef CASE_INFO_ONLY
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,

View File

@@ -12,6 +12,7 @@
#include "esphome/core/string_ref.h"
#include <functional>
#include <limits>
#include <vector>
namespace esphome::api {
@@ -38,8 +39,8 @@ class APIConnection final : public APIServerConnection {
void loop();
bool send_list_info_done() {
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done,
ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE);
return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE,
ListEntitiesDoneResponse::ESTIMATED_SIZE);
}
#ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
@@ -172,8 +173,13 @@ class APIConnection final : public APIServerConnection {
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
#endif
#ifdef USE_IR_RF
void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) override;
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
#endif
#ifdef USE_EVENT
void send_event(event::Event *event, const char *event_type);
void send_event(event::Event *event);
#endif
#ifdef USE_UPDATE
@@ -468,8 +474,12 @@ class APIConnection final : public APIServerConnection {
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_INFRARED
static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single);
#endif
#ifdef USE_EVENT
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
static uint16_t try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
@@ -531,33 +541,17 @@ class APIConnection final : public APIServerConnection {
// Function pointer type for message encoding
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
class MessageCreator {
public:
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
// Call operator - uses message_type to determine union type
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint8_t message_type) const;
private:
union Data {
MessageCreatorPtr function_ptr;
const char *const_char_ptr;
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit
};
// Generic batching mechanism for both state updates and entity info
struct DeferredBatch {
struct BatchItem {
EntityBase *entity; // Entity pointer
MessageCreator creator; // Function that creates the message when needed
uint8_t message_type; // Message type for overhead calculation (max 255)
uint8_t estimated_size; // Estimated message size (max 255 bytes)
// Sentinel value for unused aux_data_index
static constexpr uint8_t AUX_DATA_UNUSED = std::numeric_limits<uint8_t>::max();
// Constructor for creating BatchItem
BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size)
: entity(entity), creator(creator), message_type(message_type), estimated_size(estimated_size) {}
struct BatchItem {
EntityBase *entity; // 4 bytes - Entity pointer
uint8_t message_type; // 1 byte - Message type for protocol and dispatch
uint8_t estimated_size; // 1 byte - Estimated message size (max 255 bytes)
uint8_t aux_data_index{AUX_DATA_UNUSED}; // 1 byte - For events: index into entity's event_types
// 1 byte padding
};
std::vector<BatchItem> items;
@@ -566,10 +560,11 @@ class APIConnection final : public APIServerConnection {
// No pre-allocation - log connections never use batching, and for
// connections that do, buffers are released after initial sync anyway
// Add item to the batch
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Add item to the batch (with deduplication)
void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index = AUX_DATA_UNUSED);
// Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size);
// Clear all items
void clear() {
@@ -583,6 +578,7 @@ class APIConnection final : public APIServerConnection {
bool empty() const { return items.empty(); }
size_t size() const { return items.size(); }
const BatchItem &operator[](size_t index) const { return items[index]; }
// Release excess capacity - only releases if items already empty
void release_buffer() {
// Safe to call: batch is processed before release_buffer is called,
@@ -654,17 +650,15 @@ class APIConnection final : public APIServerConnection {
this->flags_.batch_scheduled = false;
}
#ifdef HAS_PROTO_MESSAGE_DUMP
// Helper to log a proto message from a MessageCreator object
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) {
this->flags_.log_only_mode = true;
creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type);
this->flags_.log_only_mode = false;
}
// 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 is_single);
#ifdef HAS_PROTO_MESSAGE_DUMP
void log_batch_item_(const DeferredBatch::BatchItem &item) {
// Use the helper to log the message
this->log_proto_message_(item.entity, item.creator, item.message_type);
this->flags_.log_only_mode = true;
this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true);
this->flags_.log_only_mode = false;
}
#endif
@@ -689,63 +683,31 @@ class APIConnection final : public APIServerConnection {
// Helper method to send a message either immediately or via batching
// 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, MessageCreatorPtr creator, uint8_t message_type,
uint8_t estimated_size) {
bool send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) {
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
// Now actually encode and send
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
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
// Log the message in verbose mode
this->log_proto_message_(entity, MessageCreator(creator), message_type);
this->log_batch_item_(item);
#endif
return true;
}
// If immediate send failed, fall through to batching
}
// Fall back to scheduled batching
return this->schedule_message_(entity, creator, message_type, estimated_size);
}
// Overload for MessageCreator (used by events which need to capture event_type)
bool send_message_smart_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
// Try to send immediately if message type should bypass batching and buffer has space
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
// Now actually encode and send
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type) &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP
// Log the message in verbose mode
this->log_proto_message_(entity, creator, message_type);
#endif
return true;
}
// If immediate send failed, fall through to batching
}
// Fall back to scheduled batching
return this->schedule_message_(entity, creator, message_type, estimated_size);
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, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
this->deferred_batch_.add_item(entity, creator, message_type, estimated_size);
bool schedule_message_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) {
this->deferred_batch_.add_item(entity, message_type, estimated_size, aux_data_index);
return this->schedule_batch_();
}
// Overload for function pointers (for info messages and current state reads)
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
uint8_t estimated_size) {
return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size);
}
// Helper function to schedule a high priority message at the front of the batch
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
uint8_t estimated_size) {
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
return this->schedule_batch_();
}

View File

@@ -27,7 +27,6 @@ extend google.protobuf.MessageOptions {
extend google.protobuf.FieldOptions {
optional string field_ifdef = 1042;
optional uint32 fixed_array_size = 50007;
optional bool no_zero_copy = 50008 [default=false];
optional bool fixed_array_skip_zero = 50009 [default=false];
optional string fixed_array_size_define = 50010;
optional string fixed_array_with_length_define = 50011;
@@ -80,4 +79,15 @@ extend google.protobuf.FieldOptions {
// Example: [(container_pointer_no_template) = "light::ColorModeMask"]
// generates: const light::ColorModeMask *supported_color_modes{};
optional string container_pointer_no_template = 50014;
// packed_buffer: Expose raw packed buffer instead of decoding into container
// When set on a packed repeated field, the generated code stores a pointer
// to the raw protobuf buffer instead of decoding values. This enables
// zero-copy passthrough when the consumer can decode on-demand.
// The field must be a packed repeated field (packed=true).
// Generates three fields:
// - const uint8_t *<field>_data_{nullptr};
// - uint16_t <field>_length_{0};
// - uint16_t <field>_count_{0};
optional bool packed_buffer = 50015 [default=false];
}

View File

@@ -3347,5 +3347,98 @@ void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
size.add_length(1, this->data_len);
}
#endif
#ifdef USE_INFRARED
void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(4, this->icon);
#endif
buffer.encode_bool(5, this->disabled_by_default);
buffer.encode_uint32(6, static_cast<uint32_t>(this->entity_category));
#ifdef USE_DEVICES
buffer.encode_uint32(7, this->device_id);
#endif
buffer.encode_uint32(8, this->capabilities);
}
void ListEntitiesInfraredResponse::calculate_size(ProtoSize &size) const {
size.add_length(1, this->object_id.size());
size.add_fixed32(1, this->key);
size.add_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size.add_length(1, this->icon.size());
#endif
size.add_bool(1, this->disabled_by_default);
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
#ifdef USE_DEVICES
size.add_uint32(1, this->device_id);
#endif
size.add_uint32(1, this->capabilities);
}
#endif
#ifdef USE_IR_RF
bool InfraredRFTransmitRawTimingsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
#ifdef USE_DEVICES
case 1:
this->device_id = value.as_uint32();
break;
#endif
case 3:
this->carrier_frequency = value.as_uint32();
break;
case 4:
this->repeat_count = value.as_uint32();
break;
default:
return false;
}
return true;
}
bool InfraredRFTransmitRawTimingsRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 5: {
this->timings_data_ = value.data();
this->timings_length_ = value.size();
this->timings_count_ = count_packed_varints(value.data(), value.size());
break;
}
default:
return false;
}
return true;
}
bool InfraredRFTransmitRawTimingsRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2:
this->key = value.as_fixed32();
break;
default:
return false;
}
return true;
}
void InfraredRFReceiveEvent::encode(ProtoWriteBuffer buffer) const {
#ifdef USE_DEVICES
buffer.encode_uint32(1, this->device_id);
#endif
buffer.encode_fixed32(2, this->key);
for (const auto &it : *this->timings) {
buffer.encode_sint32(3, it, true);
}
}
void InfraredRFReceiveEvent::calculate_size(ProtoSize &size) const {
#ifdef USE_DEVICES
size.add_uint32(1, this->device_id);
#endif
size.add_fixed32(1, this->key);
if (!this->timings->empty()) {
for (const auto &it : *this->timings) {
size.add_sint32_force(1, it);
}
}
}
#endif
} // namespace esphome::api

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,12 @@ namespace esphome::api {
static const char *const TAG = "api.service";
#ifdef HAS_PROTO_MESSAGE_DUMP
void APIServerConnectionBase::log_send_message_(const char *name, const std::string &dump) {
ESP_LOGVV(TAG, "send_message %s: %s", name, dump.c_str());
void APIServerConnectionBase::log_send_message_(const char *name, const char *dump) {
ESP_LOGVV(TAG, "send_message %s: %s", name, dump);
}
void APIServerConnectionBase::log_receive_message_(const LogString *name, const ProtoMessage &msg) {
DumpBuffer dump_buf;
ESP_LOGVV(TAG, "%s: %s", LOG_STR_ARG(name), msg.dump_to(dump_buf));
}
#endif
@@ -19,7 +23,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
HelloRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_hello_request"), msg);
#endif
this->on_hello_request(msg);
break;
@@ -28,7 +32,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DisconnectRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_disconnect_request"), msg);
#endif
this->on_disconnect_request(msg);
break;
@@ -37,7 +41,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DisconnectResponse msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_disconnect_response"), msg);
#endif
this->on_disconnect_response(msg);
break;
@@ -46,7 +50,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
PingRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_ping_request"), msg);
#endif
this->on_ping_request(msg);
break;
@@ -55,7 +59,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
PingResponse msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_ping_response"), msg);
#endif
this->on_ping_response(msg);
break;
@@ -64,7 +68,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DeviceInfoRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_device_info_request"), msg);
#endif
this->on_device_info_request(msg);
break;
@@ -73,7 +77,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ListEntitiesRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_list_entities_request"), msg);
#endif
this->on_list_entities_request(msg);
break;
@@ -82,7 +86,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeStatesRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_subscribe_states_request"), msg);
#endif
this->on_subscribe_states_request(msg);
break;
@@ -91,7 +95,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeLogsRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_subscribe_logs_request"), msg);
#endif
this->on_subscribe_logs_request(msg);
break;
@@ -101,7 +105,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
CoverCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_cover_command_request"), msg);
#endif
this->on_cover_command_request(msg);
break;
@@ -112,7 +116,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
FanCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_fan_command_request"), msg);
#endif
this->on_fan_command_request(msg);
break;
@@ -123,7 +127,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
LightCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_light_command_request"), msg);
#endif
this->on_light_command_request(msg);
break;
@@ -134,7 +138,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SwitchCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_switch_command_request"), msg);
#endif
this->on_switch_command_request(msg);
break;
@@ -145,7 +149,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeHomeassistantServicesRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"), msg);
#endif
this->on_subscribe_homeassistant_services_request(msg);
break;
@@ -155,7 +159,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
GetTimeResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_get_time_response"), msg);
#endif
this->on_get_time_response(msg);
break;
@@ -165,7 +169,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeHomeAssistantStatesRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"), msg);
#endif
this->on_subscribe_home_assistant_states_request(msg);
break;
@@ -176,7 +180,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
HomeAssistantStateResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_home_assistant_state_response"), msg);
#endif
this->on_home_assistant_state_response(msg);
break;
@@ -187,7 +191,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ExecuteServiceRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_execute_service_request"), msg);
#endif
this->on_execute_service_request(msg);
break;
@@ -198,7 +202,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
CameraImageRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_camera_image_request"), msg);
#endif
this->on_camera_image_request(msg);
break;
@@ -209,7 +213,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ClimateCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_climate_command_request"), msg);
#endif
this->on_climate_command_request(msg);
break;
@@ -220,7 +224,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
NumberCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_number_command_request"), msg);
#endif
this->on_number_command_request(msg);
break;
@@ -231,7 +235,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SelectCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_select_command_request"), msg);
#endif
this->on_select_command_request(msg);
break;
@@ -242,7 +246,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SirenCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_siren_command_request"), msg);
#endif
this->on_siren_command_request(msg);
break;
@@ -253,7 +257,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
LockCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_lock_command_request"), msg);
#endif
this->on_lock_command_request(msg);
break;
@@ -264,7 +268,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ButtonCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_button_command_request"), msg);
#endif
this->on_button_command_request(msg);
break;
@@ -275,7 +279,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
MediaPlayerCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_media_player_command_request"), msg);
#endif
this->on_media_player_command_request(msg);
break;
@@ -286,7 +290,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeBluetoothLEAdvertisementsRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_le_advertisements_request"), msg);
#endif
this->on_subscribe_bluetooth_le_advertisements_request(msg);
break;
@@ -297,7 +301,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothDeviceRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_bluetooth_device_request"), msg);
#endif
this->on_bluetooth_device_request(msg);
break;
@@ -308,7 +312,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTGetServicesRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_get_services_request"), msg);
#endif
this->on_bluetooth_gatt_get_services_request(msg);
break;
@@ -319,7 +323,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTReadRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_read_request"), msg);
#endif
this->on_bluetooth_gatt_read_request(msg);
break;
@@ -330,7 +334,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTWriteRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_write_request"), msg);
#endif
this->on_bluetooth_gatt_write_request(msg);
break;
@@ -341,7 +345,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTReadDescriptorRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_read_descriptor_request"), msg);
#endif
this->on_bluetooth_gatt_read_descriptor_request(msg);
break;
@@ -352,7 +356,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTWriteDescriptorRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_write_descriptor_request"), msg);
#endif
this->on_bluetooth_gatt_write_descriptor_request(msg);
break;
@@ -363,7 +367,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothGATTNotifyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_notify_request"), msg);
#endif
this->on_bluetooth_gatt_notify_request(msg);
break;
@@ -374,7 +378,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeBluetoothConnectionsFreeRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"), msg);
#endif
this->on_subscribe_bluetooth_connections_free_request(msg);
break;
@@ -385,7 +389,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
UnsubscribeBluetoothLEAdvertisementsRequest msg;
// Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"), msg);
#endif
this->on_unsubscribe_bluetooth_le_advertisements_request(msg);
break;
@@ -396,7 +400,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
SubscribeVoiceAssistantRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_subscribe_voice_assistant_request"), msg);
#endif
this->on_subscribe_voice_assistant_request(msg);
break;
@@ -407,7 +411,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_voice_assistant_response"), msg);
#endif
this->on_voice_assistant_response(msg);
break;
@@ -418,7 +422,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantEventResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_voice_assistant_event_response"), msg);
#endif
this->on_voice_assistant_event_response(msg);
break;
@@ -429,7 +433,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
AlarmControlPanelCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_alarm_control_panel_command_request"), msg);
#endif
this->on_alarm_control_panel_command_request(msg);
break;
@@ -440,7 +444,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
TextCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_text_command_request"), msg);
#endif
this->on_text_command_request(msg);
break;
@@ -451,7 +455,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DateCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_date_command_request"), msg);
#endif
this->on_date_command_request(msg);
break;
@@ -462,7 +466,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
TimeCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_time_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_time_command_request"), msg);
#endif
this->on_time_command_request(msg);
break;
@@ -473,7 +477,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantAudio msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_voice_assistant_audio"), msg);
#endif
this->on_voice_assistant_audio(msg);
break;
@@ -484,7 +488,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ValveCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_valve_command_request"), msg);
#endif
this->on_valve_command_request(msg);
break;
@@ -495,7 +499,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
DateTimeCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_date_time_command_request"), msg);
#endif
this->on_date_time_command_request(msg);
break;
@@ -506,7 +510,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantTimerEventResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_voice_assistant_timer_event_response"), msg);
#endif
this->on_voice_assistant_timer_event_response(msg);
break;
@@ -517,7 +521,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
UpdateCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_update_command_request"), msg);
#endif
this->on_update_command_request(msg);
break;
@@ -528,7 +532,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantAnnounceRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_voice_assistant_announce_request"), msg);
#endif
this->on_voice_assistant_announce_request(msg);
break;
@@ -539,7 +543,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantConfigurationRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_voice_assistant_configuration_request"), msg);
#endif
this->on_voice_assistant_configuration_request(msg);
break;
@@ -550,7 +554,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
VoiceAssistantSetConfiguration msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_voice_assistant_set_configuration"), msg);
#endif
this->on_voice_assistant_set_configuration(msg);
break;
@@ -561,7 +565,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
NoiseEncryptionSetKeyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_noise_encryption_set_key_request"), msg);
#endif
this->on_noise_encryption_set_key_request(msg);
break;
@@ -572,7 +576,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
BluetoothScannerSetModeRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_bluetooth_scanner_set_mode_request"), msg);
#endif
this->on_bluetooth_scanner_set_mode_request(msg);
break;
@@ -583,7 +587,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ZWaveProxyFrame msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_z_wave_proxy_frame"), msg);
#endif
this->on_z_wave_proxy_frame(msg);
break;
@@ -594,7 +598,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ZWaveProxyRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_z_wave_proxy_request"), msg);
#endif
this->on_z_wave_proxy_request(msg);
break;
@@ -605,7 +609,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
HomeassistantActionResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_homeassistant_action_response: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_homeassistant_action_response"), msg);
#endif
this->on_homeassistant_action_response(msg);
break;
@@ -616,11 +620,22 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
WaterHeaterCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_water_heater_command_request: %s", msg.dump().c_str());
this->log_receive_message_(LOG_STR("on_water_heater_command_request"), msg);
#endif
this->on_water_heater_command_request(msg);
break;
}
#endif
#ifdef USE_IR_RF
case InfraredRFTransmitRawTimingsRequest::MESSAGE_TYPE: {
InfraredRFTransmitRawTimingsRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_infrared_rf_transmit_raw_timings_request"), msg);
#endif
this->on_infrared_rf_transmit_raw_timings_request(msg);
break;
}
#endif
default:
break;
@@ -819,6 +834,11 @@ void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { th
#ifdef USE_ZWAVE_PROXY
void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
#endif
#ifdef USE_IR_RF
void APIServerConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) {
this->infrared_rf_transmit_raw_timings(msg);
}
#endif
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
// Check authentication/connection requirements for messages

View File

@@ -12,14 +12,16 @@ class APIServerConnectionBase : public ProtoService {
public:
#ifdef HAS_PROTO_MESSAGE_DUMP
protected:
void log_send_message_(const char *name, const std::string &dump);
void log_send_message_(const char *name, const char *dump);
void log_receive_message_(const LogString *name, const ProtoMessage &msg);
public:
#endif
bool send_message(const ProtoMessage &msg, uint8_t message_type) {
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_send_message_(msg.message_name(), msg.dump());
DumpBuffer dump_buf;
this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
#endif
return this->send_message_(msg, message_type);
}
@@ -217,6 +219,11 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_ZWAVE_PROXY
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
#endif
#ifdef USE_IR_RF
virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
#endif
protected:
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
};
@@ -347,6 +354,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_ZWAVE_PROXY
virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0;
#endif
#ifdef USE_IR_RF
virtual void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) = 0;
#endif
protected:
void on_hello_request(const HelloRequest &msg) override;
@@ -473,6 +483,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif
#ifdef USE_ZWAVE_PROXY
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
#endif
#ifdef USE_IR_RF
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override;
#endif
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
};

View File

@@ -318,13 +318,11 @@ API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
#endif
#ifdef USE_EVENT
// Event is a special case - unlike other entities with simple state fields,
// events store their state in a member accessed via obj->get_last_event_type()
void APIServer::on_event(event::Event *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_event(obj, obj->get_last_event_type());
c->send_event(obj);
}
#endif
@@ -347,6 +345,21 @@ void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) {
}
#endif
#ifdef USE_IR_RF
void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_id, uint32_t key,
const std::vector<int32_t> *timings) {
InfraredRFReceiveEvent resp{};
#ifdef USE_DEVICES
resp.device_id = device_id;
#endif
resp.key = key;
resp.timings = timings;
for (auto &c : this->clients_)
c->send_infrared_rf_receive_event(resp);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
#endif
@@ -600,8 +613,7 @@ void APIServer::on_shutdown() {
if (!c->send_message(req, DisconnectRequest::MESSAGE_TYPE)) {
// If we can't send the disconnect request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE,
DisconnectRequest::ESTIMATED_SIZE);
c->schedule_message_front_(nullptr, DisconnectRequest::MESSAGE_TYPE, DisconnectRequest::ESTIMATED_SIZE);
}
}
}

View File

@@ -185,6 +185,9 @@ class APIServer : public Component,
#ifdef USE_ZWAVE_PROXY
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
#endif
#ifdef USE_IR_RF
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
#endif
bool is_connected(bool state_subscription_only = false) const;

View File

@@ -265,7 +265,7 @@ class CustomAPIDevice {
for (auto &it : data) {
auto &kv = resp.data.emplace_back();
kv.key = StringRef(it.first);
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
kv.value = StringRef(it.second); // data map lives until send completes
}
global_api_server->send_homeassistant_action(resp);
}
@@ -308,7 +308,7 @@ class CustomAPIDevice {
for (auto &it : data) {
auto &kv = resp.data.emplace_back();
kv.key = StringRef(it.first);
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
kv.value = StringRef(it.second); // data map lives until send completes
}
global_api_server->send_homeassistant_action(resp);
}

View File

@@ -149,11 +149,21 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
std::string service_value = this->service_.value(x...);
resp.service = StringRef(service_value);
resp.is_event = this->flags_.is_event;
this->populate_service_map(resp.data, this->data_, x...);
this->populate_service_map(resp.data_template, this->data_template_, x...);
this->populate_service_map(resp.variables, this->variables_, x...);
// Local storage for lambda-evaluated strings - lives until after send
FixedVector<std::string> data_storage;
FixedVector<std::string> data_template_storage;
FixedVector<std::string> variables_storage;
this->populate_service_map(resp.data, this->data_, data_storage, x...);
this->populate_service_map(resp.data_template, this->data_template_, data_template_storage, x...);
this->populate_service_map(resp.variables, this->variables_, variables_storage, x...);
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
// IMPORTANT: Declare at outer scope so it lives until send_homeassistant_action returns.
std::string response_template_value;
#endif
if (this->flags_.wants_status) {
// Generate a unique call ID for this service call
static uint32_t call_id_counter = 1;
@@ -164,8 +174,8 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
resp.wants_response = true;
// Set response template if provided
if (this->flags_.has_response_template) {
std::string response_template_value = this->response_template_.value(x...);
resp.response_template = response_template_value;
response_template_value = this->response_template_.value(x...);
resp.response_template = StringRef(response_template_value);
}
}
#endif
@@ -205,12 +215,31 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
}
template<typename VectorType, typename SourceType>
static void populate_service_map(VectorType &dest, SourceType &source, Ts... x) {
static void populate_service_map(VectorType &dest, SourceType &source, FixedVector<std::string> &value_storage,
Ts... x) {
dest.init(source.size());
// Count non-static strings to allocate exact storage needed
size_t lambda_count = 0;
for (const auto &it : source) {
if (!it.value.is_static_string()) {
lambda_count++;
}
}
value_storage.init(lambda_count);
for (auto &it : source) {
auto &kv = dest.emplace_back();
kv.key = StringRef(it.key);
kv.value = it.value.value(x...);
if (it.value.is_static_string()) {
// Static string from YAML - zero allocation
kv.value = StringRef(it.value.get_static_string());
} else {
// Lambda evaluation - store result, reference it
value_storage.push_back(it.value.value(x...));
kv.value = StringRef(value_storage.back());
}
}
}

View File

@@ -76,6 +76,9 @@ LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPane
#ifdef USE_WATER_HEATER
LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWaterHeaterResponse)
#endif
#ifdef USE_INFRARED
LIST_ENTITIES_HANDLER(infrared, infrared::Infrared, ListEntitiesInfraredResponse)
#endif
#ifdef USE_EVENT
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
#endif

View File

@@ -9,11 +9,10 @@ namespace esphome::api {
class APIConnection;
// Macro for generating ListEntitiesIterator handlers
// Calls schedule_message_ with try_send_*_info
// Calls schedule_message_ which dispatches to try_send_*_info
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
return this->client_->schedule_message_(entity, ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
}
class ListEntitiesIterator : public ComponentIterator {
@@ -85,6 +84,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_WATER_HEATER
bool on_water_heater(water_heater::WaterHeater *entity) override;
#endif
#ifdef USE_INFRARED
bool on_infrared(infrared::Infrared *entity) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *entity) override;
#endif

View File

@@ -139,12 +139,4 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string ProtoMessage::dump() const {
std::string out;
this->dump_to(out);
return out;
}
#endif
} // namespace esphome::api

View File

@@ -362,6 +362,63 @@ class ProtoWriteBuffer {
std::vector<uint8_t> *buffer_;
};
#ifdef HAS_PROTO_MESSAGE_DUMP
/**
* Fixed-size buffer for message dumps - avoids heap allocation.
* Sized to match the logger's default tx_buffer_size (512 bytes)
* since anything larger gets truncated anyway.
*/
class DumpBuffer {
public:
// Matches default tx_buffer_size in logger component
static constexpr size_t CAPACITY = 512;
DumpBuffer() : pos_(0) { buf_[0] = '\0'; }
DumpBuffer &append(const char *str) {
if (str) {
append_impl_(str, strlen(str));
}
return *this;
}
DumpBuffer &append(const char *str, size_t len) {
append_impl_(str, len);
return *this;
}
DumpBuffer &append(size_t n, char c) {
size_t space = CAPACITY - 1 - pos_;
if (n > space)
n = space;
if (n > 0) {
memset(buf_ + pos_, c, n);
pos_ += n;
buf_[pos_] = '\0';
}
return *this;
}
const char *c_str() const { return buf_; }
size_t size() const { return pos_; }
private:
void append_impl_(const char *str, size_t len) {
size_t space = CAPACITY - 1 - pos_;
if (len > space)
len = space;
if (len > 0) {
memcpy(buf_ + pos_, str, len);
pos_ += len;
buf_[pos_] = '\0';
}
}
char buf_[CAPACITY];
size_t pos_;
};
#endif
class ProtoMessage {
public:
virtual ~ProtoMessage() = default;
@@ -370,8 +427,7 @@ class ProtoMessage {
// Default implementation for messages with no fields
virtual void calculate_size(ProtoSize &size) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const;
virtual void dump_to(std::string &out) const = 0;
virtual const char *dump_to(DumpBuffer &out) const = 0;
virtual const char *message_name() const { return "unknown"; }
#endif
};

View File

@@ -79,6 +79,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_WATER_HEATER
bool on_water_heater(water_heater::WaterHeater *entity) override;
#endif
#ifdef USE_INFRARED
bool on_infrared(infrared::Infrared *infrared) override { return true; };
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override { return true; };
#endif

View File

@@ -6,7 +6,7 @@ namespace esphome::aqi {
class AbstractAQICalculator {
public:
virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0;
};
} // namespace esphome::aqi

View File

@@ -1,6 +1,7 @@
#pragma once
#include <climits>
#include <cmath>
#include <limits>
#include "abstract_aqi_calculator.h"
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
@@ -9,11 +10,11 @@ namespace esphome::aqi {
class AQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
}
protected:
@@ -21,25 +22,28 @@ class AQICalculator : public AbstractAQICalculator {
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}};
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {{0.0f, 9.0f}, {9.1f, 35.4f},
{35.5f, 55.4f}, {55.5f, 125.4f},
{125.5f, 225.4f}, {225.5f, std::numeric_limits<float>::max()}};
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
{255, 354}, {355, 424}, {425, INT_MAX}};
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {{0.0f, 54.0f}, {55.0f, 154.0f},
{155.0f, 254.0f}, {255.0f, 354.0f},
{355.0f, 424.0f}, {425.0f, std::numeric_limits<float>::max()}};
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
int grid_index = get_grid_index(value, array);
if (grid_index == -1) {
return -1;
return -1.0f;
}
int aqi_lo = INDEX_GRID[grid_index][0];
int aqi_hi = INDEX_GRID[grid_index][1];
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
float aqi_lo = INDEX_GRID[grid_index][0];
float aqi_hi = INDEX_GRID[grid_index][1];
float conc_lo = array[grid_index][0];
float conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
return i;

View File

@@ -44,8 +44,7 @@ void AQISensor::calculate_aqi_() {
return;
}
uint16_t aqi =
calculator->get_aqi(static_cast<uint16_t>(this->pm_2_5_value_), static_cast<uint16_t>(this->pm_10_0_value_));
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_);
this->publish_state(aqi);
}

View File

@@ -1,16 +1,18 @@
#pragma once
#include <cmath>
#include <limits>
#include "abstract_aqi_calculator.h"
namespace esphome::aqi {
class CAQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
}
protected:
@@ -18,25 +20,27 @@ class CAQICalculator : public AbstractAQICalculator {
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}};
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
{0.0f, 15.0f}, {15.1f, 30.0f}, {30.1f, 55.0f}, {55.1f, 110.0f}, {110.1f, std::numeric_limits<float>::max()}};
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
{0.0f, 25.0f}, {25.1f, 50.0f}, {50.1f, 90.0f}, {90.1f, 180.0f}, {180.1f, std::numeric_limits<float>::max()}};
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
int grid_index = get_grid_index(value, array);
if (grid_index == -1) {
return -1;
return -1.0f;
}
int aqi_lo = INDEX_GRID[grid_index][0];
int aqi_hi = INDEX_GRID[grid_index][1];
int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1];
float aqi_lo = INDEX_GRID[grid_index][0];
float aqi_hi = INDEX_GRID[grid_index][1];
float conc_lo = array[grid_index][0];
float conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
return i;

View File

@@ -164,21 +164,21 @@ void BedJetClimate::control(const ClimateCall &call) {
return;
}
} else if (call.has_custom_preset()) {
const char *preset = call.get_custom_preset();
auto preset = call.get_custom_preset();
bool result;
if (strcmp(preset, "M1") == 0) {
if (preset == "M1") {
result = this->parent_->button_memory1();
} else if (strcmp(preset, "M2") == 0) {
} else if (preset == "M2") {
result = this->parent_->button_memory2();
} else if (strcmp(preset, "M3") == 0) {
} else if (preset == "M3") {
result = this->parent_->button_memory3();
} else if (strcmp(preset, "LTD HT") == 0) {
} else if (preset == "LTD HT") {
result = this->parent_->button_heat();
} else if (strcmp(preset, "EXT HT") == 0) {
} else if (preset == "EXT HT") {
result = this->parent_->button_ext_heat();
} else {
ESP_LOGW(TAG, "Unsupported preset: %s", preset);
ESP_LOGW(TAG, "Unsupported preset: %.*s", (int) preset.size(), preset.c_str());
return;
}
@@ -208,10 +208,11 @@ void BedJetClimate::control(const ClimateCall &call) {
this->set_fan_mode_(fan_mode);
}
} else if (call.has_custom_fan_mode()) {
const char *fan_mode = call.get_custom_fan_mode();
auto fan_index = bedjet_fan_speed_to_step(fan_mode);
auto fan_mode = call.get_custom_fan_mode();
auto fan_index = bedjet_fan_speed_to_step(fan_mode.c_str());
if (fan_index <= 19) {
ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode, fan_index);
ESP_LOGV(TAG, "[%s] Converted fan mode %.*s to bedjet fan step %d", this->get_name().c_str(),
(int) fan_mode.size(), fan_mode.c_str(), fan_index);
bool result = this->parent_->set_fan_index(fan_index);
if (result) {
this->set_custom_fan_mode_(fan_mode);

View File

@@ -1,8 +1,8 @@
#include "bh1750.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace bh1750 {
namespace esphome::bh1750 {
static const char *const TAG = "bh1750.sensor";
@@ -13,6 +13,31 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
// Measurement time constants (datasheet values)
static constexpr uint16_t MTREG_DEFAULT = 69;
static constexpr uint16_t MTREG_MIN = 31;
static constexpr uint16_t MTREG_MAX = 254;
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
// Conversion constants (datasheet formulas)
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
// MTreg calculation constants
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
static constexpr int COUNTS_NUMERATOR = 10;
static constexpr int COUNTS_DENOMINATOR = 12;
// MTreg register bit manipulation constants
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
/*
bh1750 properties:
@@ -43,74 +68,7 @@ void BH1750Sensor::setup() {
this->mark_failed();
return;
}
}
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
// turn on (after one-shot sensor automatically powers down)
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
f(NAN);
return;
}
if (active_mtreg_ != mtreg) {
// set mtreg
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
active_mtreg_ = 0;
f(NAN);
return;
}
active_mtreg_ = mtreg;
}
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = 24 * mtreg / 69;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = 180 * mtreg / 69;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = 180 * mtreg / 69;
break;
default:
f(NAN);
return;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
f(NAN);
return;
}
// probably not needed, but adjust for rounding
meas_time++;
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / mtreg;
if (mode == BH1750_MODE_H2)
lx /= 2.0f;
f(lx);
});
this->state_ = IDLE;
}
void BH1750Sensor::dump_config() {
@@ -124,45 +82,189 @@ void BH1750Sensor::dump_config() {
}
void BH1750Sensor::update() {
// first do a quick measurement in L-mode with full range
// to find right range
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
const uint32_t now = millis();
// Start coarse measurement to determine optimal mode/mtreg
if (this->state_ != IDLE) {
// Safety timeout: reset if stuck
if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Measurement timeout, resetting state");
this->state_ = IDLE;
} else {
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
return;
}
}
BH1750Mode use_mode;
uint8_t use_mtreg;
if (val <= 7000) {
use_mode = BH1750_MODE_H2;
use_mtreg = 254;
} else {
use_mode = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
this->read_lx_(use_mode, use_mtreg, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
this->state_ = WAITING_COARSE_MEASUREMENT;
this->enable_loop(); // Enable loop while measurement in progress
}
void BH1750Sensor::loop() {
const uint32_t now = App.get_loop_component_start_time();
switch (this->state_) {
case IDLE:
// Disable loop when idle to save cycles
this->disable_loop();
break;
case WAITING_COARSE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_COARSE_RESULT;
}
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
break;
case READING_COARSE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
this->process_coarse_result_(lx);
// Start fine measurement with optimal settings
// fetch millis() again since the read can take a bit
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
this->fail_and_reset_();
break;
}
this->state_ = WAITING_FINE_MEASUREMENT;
break;
}
case WAITING_FINE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_FINE_RESULT;
}
break;
case READING_FINE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
this->status_clear_warning();
this->publish_state(val);
});
});
this->publish_state(lx);
this->state_ = IDLE;
break;
}
}
}
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
// Power on
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
return false;
}
// Set MTreg if changed
if (this->active_mtreg_ != mtreg) {
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
this->active_mtreg_ = 0;
return false;
}
this->active_mtreg_ = mtreg;
}
// Start measurement
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
default:
return false;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
return false;
}
// Store current measurement parameters
this->current_mode_ = mode;
this->current_mtreg_ = mtreg;
this->measurement_start_time_ = now;
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
return true;
}
bool BH1750Sensor::read_measurement_(float &lx_out) {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
return false;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / RESOLUTION_DIVISOR;
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
if (this->current_mode_ == BH1750_MODE_H2) {
lx /= MODE_H2_DIVISOR;
}
lx_out = lx;
return true;
}
void BH1750Sensor::process_coarse_result_(float lx) {
if (std::isnan(lx)) {
// Use defaults if coarse measurement failed
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
return;
}
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
} else {
this->fine_mode_ = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
}
void BH1750Sensor::fail_and_reset_() {
this->status_set_warning();
this->publish_state(NAN);
this->state_ = IDLE;
}
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace bh1750
} // namespace esphome
} // namespace esphome::bh1750

View File

@@ -4,10 +4,9 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bh1750 {
namespace esphome::bh1750 {
enum BH1750Mode {
enum BH1750Mode : uint8_t {
BH1750_MODE_L,
BH1750_MODE_H,
BH1750_MODE_H2,
@@ -21,13 +20,36 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void setup() override;
void dump_config() override;
void update() override;
void loop() override;
float get_setup_priority() const override;
protected:
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f);
// State machine states
enum State : uint8_t {
IDLE,
WAITING_COARSE_MEASUREMENT,
READING_COARSE_RESULT,
WAITING_FINE_MEASUREMENT,
READING_FINE_RESULT,
};
// 4-byte aligned members
uint32_t measurement_start_time_{0};
uint32_t measurement_duration_{0};
// 1-byte members grouped together to minimize padding
State state_{IDLE};
BH1750Mode current_mode_{BH1750_MODE_L};
uint8_t current_mtreg_{31};
BH1750Mode fine_mode_{BH1750_MODE_H2};
uint8_t fine_mtreg_{254};
uint8_t active_mtreg_{0};
// Helper methods
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
bool read_measurement_(float &lx_out);
void process_coarse_result_(float lx);
void fail_and_reset_();
};
} // namespace bh1750
} // namespace esphome
} // namespace esphome::bh1750

View File

@@ -1,9 +1,23 @@
# This file was auto-generated by libretiny/generate_components.py
# Do not modify its contents.
# For custom pin validators, put validate_pin() or validate_usage()
# in gpio.py file in this directory.
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
# in schema.py file in this directory.
"""
██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗
██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝
██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗
██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║
╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝
AUTO-GENERATED FILE - DO NOT EDIT!
This file was auto-generated by libretiny/generate_components.py.
Any manual changes WILL BE LOST on regeneration.
To customize this component:
- Pin validators: Create gpio.py with validate_pin() or validate_usage()
- Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
Platform-specific code should be added to the main libretiny component
(__init__.py in esphome/components/libretiny/) rather than here.
"""
from esphome import pins
from esphome.components import libretiny
@@ -27,6 +41,7 @@ COMPONENT_DATA = LibreTinyComponent(
board_pins=BK72XX_BOARD_PINS,
pin_validation=None,
usage_validation=None,
supports_atomics=False,
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
import esphome.codegen as cg
from esphome.components.logger import request_log_listener
from esphome.components.zephyr import zephyr_add_prj_conf
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_LOGS, CONF_TYPE
@@ -25,5 +26,8 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
zephyr_add_prj_conf("BT_NUS", True)
cg.add(var.set_expose_log(config[CONF_TYPE] == CONF_LOGS))
expose_log = config[CONF_TYPE] == CONF_LOGS
cg.add(var.set_expose_log(expose_log))
if expose_log:
request_log_listener() # Request a log listener slot for BLE NUS log streaming
await cg.register_component(var, config)

View File

@@ -50,7 +50,7 @@ TYPES = [
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(cg.Component),
cv.GenerateID(): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,

View File

@@ -93,7 +93,9 @@ bool CH422GComponent::read_inputs_() {
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0);
if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
char buf[64];
snprintf(buf, sizeof(buf), "write failed for register 0x%X, error %d", reg, err);
this->status_set_warning(buf);
return false;
}
this->status_clear_warning();
@@ -104,7 +106,9 @@ uint8_t CH422GComponent::read_reg_(uint8_t reg) {
uint8_t value;
auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1);
if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
char buf[64];
snprintf(buf, sizeof(buf), "read failed for register 0x%X, error %d", reg, err);
this->status_set_warning(buf);
return 0;
}
this->status_clear_warning();

View File

@@ -682,19 +682,19 @@ bool Climate::set_fan_mode_(ClimateFanMode mode) {
return set_primary_mode(this->fan_mode, this->custom_fan_mode_, mode);
}
bool Climate::set_custom_fan_mode_(const char *mode) {
bool Climate::set_custom_fan_mode_(const char *mode, size_t len) {
auto traits = this->get_traits();
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode, traits.find_custom_fan_mode_(mode),
this->has_custom_fan_mode());
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode,
traits.find_custom_fan_mode_(mode, len), this->has_custom_fan_mode());
}
void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; }
bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); }
bool Climate::set_custom_preset_(const char *preset) {
bool Climate::set_custom_preset_(const char *preset, size_t len) {
auto traits = this->get_traits();
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, traits.find_custom_preset_(preset),
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, traits.find_custom_preset_(preset, len),
this->has_custom_preset());
}

View File

@@ -5,6 +5,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
#include "esphome/core/string_ref.h"
#include "climate_mode.h"
#include "climate_traits.h"
@@ -110,8 +111,8 @@ class ClimateCall {
const optional<ClimateFanMode> &get_fan_mode() const;
const optional<ClimateSwingMode> &get_swing_mode() const;
const optional<ClimatePreset> &get_preset() const;
const char *get_custom_fan_mode() const { return this->custom_fan_mode_; }
const char *get_custom_preset() const { return this->custom_preset_; }
StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); }
StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); }
bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; }
bool has_custom_preset() const { return this->custom_preset_ != nullptr; }
@@ -266,11 +267,11 @@ class Climate : public EntityBase {
/// The active swing mode of the climate device.
ClimateSwingMode swing_mode{CLIMATE_SWING_OFF};
/// Get the active custom fan mode (read-only access).
const char *get_custom_fan_mode() const { return this->custom_fan_mode_; }
/// Get the active custom fan mode (read-only access). Returns StringRef.
StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); }
/// Get the active custom preset (read-only access).
const char *get_custom_preset() const { return this->custom_preset_; }
/// Get the active custom preset (read-only access). Returns StringRef.
StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); }
protected:
friend ClimateCall;
@@ -280,7 +281,9 @@ class Climate : public EntityBase {
bool set_fan_mode_(ClimateFanMode mode);
/// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed.
bool set_custom_fan_mode_(const char *mode);
bool set_custom_fan_mode_(const char *mode) { return this->set_custom_fan_mode_(mode, strlen(mode)); }
bool set_custom_fan_mode_(const char *mode, size_t len);
bool set_custom_fan_mode_(StringRef mode) { return this->set_custom_fan_mode_(mode.c_str(), mode.size()); }
/// Clear custom fan mode.
void clear_custom_fan_mode_();
@@ -288,7 +291,9 @@ class Climate : public EntityBase {
bool set_preset_(ClimatePreset preset);
/// Set custom preset. Reset primary preset. Return true if preset has been changed.
bool set_custom_preset_(const char *preset);
bool set_custom_preset_(const char *preset) { return this->set_custom_preset_(preset, strlen(preset)); }
bool set_custom_preset_(const char *preset, size_t len);
bool set_custom_preset_(StringRef preset) { return this->set_custom_preset_(preset.c_str(), preset.size()); }
/// Clear custom preset.
void clear_custom_preset_();

View File

@@ -8,20 +8,24 @@ static const char *const TAG = "copy.fan";
void CopyFan::setup() {
source_->add_on_state_callback([this]() {
this->state = source_->state;
this->oscillating = source_->oscillating;
this->speed = source_->speed;
this->direction = source_->direction;
this->set_preset_mode_(source_->get_preset_mode());
this->copy_state_from_source_();
this->publish_state();
});
this->copy_state_from_source_();
this->publish_state();
}
void CopyFan::copy_state_from_source_() {
this->state = source_->state;
this->oscillating = source_->oscillating;
this->speed = source_->speed;
this->direction = source_->direction;
this->set_preset_mode_(source_->get_preset_mode());
this->publish_state();
if (source_->has_preset_mode()) {
this->set_preset_mode_(source_->get_preset_mode());
} else {
this->clear_preset_mode_();
}
}
void CopyFan::dump_config() { LOG_FAN("", "Copy Fan", this); }

View File

@@ -16,7 +16,7 @@ class CopyFan : public fan::Fan, public Component {
protected:
void control(const fan::FanCall &call) override;
;
void copy_state_from_source_();
fan::Fan *source_;
};

View File

@@ -322,6 +322,8 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
return "Unspecified";
};
char mac_pretty[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
get_mac_address_pretty_into_buffer(mac_pretty);
ESP_LOGD(TAG,
"Code page size: %u, code size: %u, device id: 0x%08x%08x\n"
"Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x\n"
@@ -330,10 +332,10 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
"RAM: %ukB, Flash: %ukB, production test: %sdone",
NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0], NRF_FICR->ER[0],
NRF_FICR->ER[1], NRF_FICR->ER[2], NRF_FICR->ER[3], NRF_FICR->IR[0], NRF_FICR->IR[1], NRF_FICR->IR[2],
NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), get_mac_address_pretty().c_str(),
NRF_FICR->INFO.PART, NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF,
NRF_FICR->INFO.VARIANT >> 8 & 0xFF, NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE),
NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not "));
NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), mac_pretty, NRF_FICR->INFO.PART,
NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF, NRF_FICR->INFO.VARIANT >> 8 & 0xFF,
NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE), NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH,
(NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not "));
bool n_reset_enabled = NRF_UICR->PSELRESET[0] == NRF_UICR->PSELRESET[1] &&
(NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) == UICR_PSELRESET_CONNECT_Connected
<< UICR_PSELRESET_CONNECT_Pos;

View File

@@ -26,7 +26,7 @@ namespace deep_sleep {
// - ext0: Single pin wakeup using RTC GPIO (esp_sleep_enable_ext0_wakeup)
// - ext1: Multiple pin wakeup (esp_sleep_enable_ext1_wakeup)
// - Touch: Touch pad wakeup (esp_sleep_enable_touchpad_wakeup)
// - GPIO wakeup: GPIO wakeup for non-RTC pins (esp_deep_sleep_enable_gpio_wakeup)
// - GPIO wakeup: GPIO wakeup for RTC pins (esp_deep_sleep_enable_gpio_wakeup)
static const char *const TAG = "deep_sleep";
@@ -127,22 +127,14 @@ void DeepSleepComponent::deep_sleep_() {
defined(USE_ESP32_VARIANT_ESP32C61)
if (this->wakeup_pin_ != nullptr) {
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
} else if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLDOWN) {
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
}
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
gpio_hold_en(gpio_pin);
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
gpio_deep_sleep_hold_en();
#endif
// Make sure GPIO is in input mode, not all RTC GPIO pins are input by default
gpio_set_direction(gpio_pin, GPIO_MODE_INPUT);
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
}
// Internal pullup/pulldown resistors are enabled automatically, when
// ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS is set (by default it is)
esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(),
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
}

View File

@@ -42,7 +42,8 @@ std::string MenuItemSelect::get_value_text() const {
result = this->value_getter_.value()(this);
} else {
if (this->select_var_ != nullptr) {
result = this->select_var_->current_option();
auto option = this->select_var_->current_option();
result.assign(option.c_str(), option.size());
}
}

View File

@@ -82,8 +82,9 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
return;
}
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
light_effect->get_last_universe());
auto effect_name = light_effect->get_name();
ESP_LOGD(TAG, "Registering '%.*s' for universes %d-%d.", (int) effect_name.size(), effect_name.c_str(),
light_effect->get_first_universe(), light_effect->get_last_universe());
light_effects_.push_back(light_effect);
@@ -98,8 +99,9 @@ void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
return;
}
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
light_effect->get_last_universe());
auto effect_name = light_effect->get_name();
ESP_LOGD(TAG, "Unregistering '%.*s' for universes %d-%d.", (int) effect_name.size(), effect_name.c_str(),
light_effect->get_first_universe(), light_effect->get_last_universe());
// Swap with last element and pop for O(1) removal (order doesn't matter)
*it = light_effects_.back();

View File

@@ -58,8 +58,9 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
auto *input_data = packet.values + 1;
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name(), universe, output_offset,
output_end);
auto effect_name = get_name();
ESP_LOGV(TAG, "Applying data for '%.*s' on %d universe, for %" PRId32 "-%d.", (int) effect_name.size(),
effect_name.c_str(), universe, output_offset, output_end);
switch (channels_) {
case E131_MONO:

View File

@@ -184,6 +184,7 @@ async def to_code(config):
height,
init_sequence_id,
init_sequence_length,
*model.get_constructor_args(config),
)
# Rotation is handled by setting the transform

View File

@@ -54,20 +54,14 @@ void EPaperBase::setup_pins_() const {
float EPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; }
void EPaperBase::command(uint8_t value) {
this->start_command_();
ESP_LOGV(TAG, "Command: 0x%02X", value);
this->dc_pin_->digital_write(false);
this->enable();
this->write_byte(value);
this->end_command_();
}
void EPaperBase::data(uint8_t value) {
this->start_data_();
this->write_byte(value);
this->end_data_();
this->disable();
}
// write a command followed by zero or more bytes of data.
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
// [COMMAND, LENGTH, DATA...]
void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
@@ -130,14 +124,10 @@ void EPaperBase::wait_for_idle_(bool should_wait) {
void EPaperBase::loop() {
auto now = millis();
if (this->delay_until_ != 0) {
// using modulus arithmetic to handle wrap-around
int diff = now - this->delay_until_;
if (diff < 0) {
return;
}
this->delay_until_ = 0;
}
// using modulus arithmetic to handle wrap-around
int diff = now - this->delay_until_;
if (diff < 0)
return;
if (this->waiting_for_idle_) {
if (this->is_idle_()) {
this->waiting_for_idle_ = false;
@@ -192,7 +182,7 @@ void EPaperBase::process_state_() {
this->set_state_(EPaperState::RESET);
break;
case EPaperState::INITIALISE:
this->initialise_();
this->initialise(this->update_count_ != 0);
this->set_state_(EPaperState::TRANSFER_DATA);
break;
case EPaperState::TRANSFER_DATA:
@@ -230,11 +220,11 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
this->state_ = state;
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
if (delay != 0) {
this->delay_until_ = millis() + delay;
} else {
this->delay_until_ = 0;
}
// allow subclasses to nominate delays
if (delay == 0)
delay = this->next_delay_;
this->next_delay_ = 0;
this->delay_until_ = millis() + delay;
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
TRUEFALSE(this->waiting_for_idle_));
if (state == EPaperState::IDLE) {
@@ -242,22 +232,14 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
}
}
void EPaperBase::start_command_() {
this->dc_pin_->digital_write(false);
this->enable();
}
void EPaperBase::end_command_() { this->disable(); }
void EPaperBase::start_data_() {
this->dc_pin_->digital_write(true);
this->enable();
}
void EPaperBase::end_data_() { this->disable(); }
void EPaperBase::on_safe_shutdown() { this->deep_sleep(); }
void EPaperBase::initialise_() {
void EPaperBase::initialise(bool partial) {
size_t index = 0;
auto *sequence = this->init_sequence_;
@@ -317,9 +299,8 @@ bool EPaperBase::rotate_coordinates_(int &x, int &y) {
void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
if (!rotate_coordinates_(x, y))
return;
const size_t pixel_position = y * this->width_ + x;
const size_t byte_position = pixel_position / 8;
const uint8_t bit_position = pixel_position % 8;
const size_t byte_position = y * this->row_width_ + x / 8;
const uint8_t bit_position = x % 8;
const uint8_t pixel_bit = 0x80 >> bit_position;
const auto original = this->buffer_[byte_position];
if ((color_to_bit(color) == 0)) {

View File

@@ -36,14 +36,16 @@ class EPaperBase : public Display,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_2MHZ> {
public:
EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY)
EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence = nullptr,
size_t init_sequence_length = 0, DisplayType display_type = DISPLAY_TYPE_BINARY)
: name_(name),
width_(width),
height_(height),
init_sequence_(init_sequence),
init_sequence_length_(init_sequence_length),
display_type_(display_type) {}
display_type_(display_type) {
this->row_width_ = (this->width_ + 7) / 8; // width of a row in bytes
}
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
float get_setup_priority() const override;
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
@@ -54,9 +56,13 @@ class EPaperBase : public Display,
void dump_config() override;
void command(uint8_t value);
void data(uint8_t value);
void cmd_data(uint8_t command, const uint8_t *ptr, size_t length);
// variant with in-place initializer list
void cmd_data(uint8_t command, std::initializer_list<uint8_t> data) {
this->cmd_data(command, data.begin(), data.size());
}
void update() override;
void loop() override;
@@ -109,7 +115,7 @@ class EPaperBase : public Display,
bool is_idle_() const;
void setup_pins_() const;
virtual bool reset();
void initialise_();
virtual void initialise(bool partial);
void wait_for_idle_(bool should_wait);
bool init_buffer_(size_t buffer_length);
bool rotate_coordinates_(int &x, int &y);
@@ -143,14 +149,12 @@ class EPaperBase : public Display,
void set_state_(EPaperState state, uint16_t delay = 0);
void start_command_();
void end_command_();
void start_data_();
void end_data_();
// properties initialised in the constructor
const char *name_;
uint16_t width_;
uint16_t row_width_; // width of a row in bytes
uint16_t height_;
const uint8_t *init_sequence_;
size_t init_sequence_length_;
@@ -163,7 +167,8 @@ class EPaperBase : public Display,
GPIOPin *busy_pin_{};
GPIOPin *reset_pin_{};
bool waiting_for_idle_{};
uint32_t delay_until_{};
uint32_t delay_until_{}; // timestamp until which to delay processing
uint16_t next_delay_{}; // milliseconds to delay before next state
uint8_t transform_{};
uint8_t update_count_{};
// these values represent the bounds of the updated buffer. Note that x_high and y_high

View File

@@ -1,25 +1,24 @@
#include "epaper_spi_ssd1677.h"
#include "epaper_spi_mono.h"
#include <algorithm>
#include "esphome/core/log.h"
namespace esphome::epaper_spi {
static constexpr const char *const TAG = "epaper_spi.ssd1677";
static constexpr const char *const TAG = "epaper_spi.mono";
void EPaperSSD1677::refresh_screen(bool partial) {
void EPaperMono::refresh_screen(bool partial) {
ESP_LOGV(TAG, "Refresh screen");
this->command(0x22);
this->data(partial ? 0xFF : 0xF7);
this->cmd_data(0x22, {partial ? (uint8_t) 0xFF : (uint8_t) 0xF7});
this->command(0x20);
}
void EPaperSSD1677::deep_sleep() {
void EPaperMono::deep_sleep() {
ESP_LOGV(TAG, "Deep sleep");
this->command(0x10);
}
bool EPaperSSD1677::reset() {
bool EPaperMono::reset() {
if (EPaperBase::reset()) {
this->command(0x12);
return true;
@@ -27,29 +26,24 @@ bool EPaperSSD1677::reset() {
return false;
}
bool HOT EPaperSSD1677::transfer_data() {
void EPaperMono::set_window() {
// round x-coordinates to byte boundaries
this->x_low_ &= ~7;
this->x_high_ += 7;
this->x_high_ &= ~7;
this->cmd_data(0x44, {(uint8_t) this->x_low_, (uint8_t) (this->x_low_ / 256), (uint8_t) (this->x_high_ - 1),
(uint8_t) ((this->x_high_ - 1) / 256)});
this->cmd_data(0x4E, {(uint8_t) this->x_low_, (uint8_t) (this->x_low_ / 256)});
this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1),
(uint8_t) ((this->y_high_ - 1) / 256)});
this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)});
}
bool HOT EPaperMono::transfer_data() {
auto start_time = millis();
if (this->current_data_index_ == 0) {
uint8_t data[4]{};
// round to byte boundaries
this->x_low_ &= ~7;
this->y_low_ &= ~7;
this->x_high_ += 7;
this->x_high_ &= ~7;
this->y_high_ += 7;
this->y_high_ &= ~7;
data[0] = this->x_low_;
data[1] = this->x_low_ / 256;
data[2] = this->x_high_ - 1;
data[3] = (this->x_high_ - 1) / 256;
cmd_data(0x4E, data, 2);
cmd_data(0x44, data, sizeof(data));
data[0] = this->y_low_;
data[1] = this->y_low_ / 256;
data[2] = this->y_high_ - 1;
data[3] = (this->y_high_ - 1) / 256;
cmd_data(0x4F, data, 2);
this->cmd_data(0x45, data, sizeof(data));
this->set_window();
// for monochrome, we still need to clear the red data buffer at least once to prevent it
// causing dirty pixels after partial refresh.
this->command(this->send_red_ ? 0x26 : 0x24);
@@ -58,10 +52,10 @@ bool HOT EPaperSSD1677::transfer_data() {
size_t row_length = (this->x_high_ - this->x_low_) / 8;
FixedVector<uint8_t> bytes_to_send{};
bytes_to_send.init(row_length);
ESP_LOGV(TAG, "Writing bytes at line %zu at %ums", this->current_data_index_, (unsigned) millis());
ESP_LOGV(TAG, "Writing %u bytes at line %zu at %ums", row_length, this->current_data_index_, (unsigned) millis());
this->start_data_();
while (this->current_data_index_ != this->y_high_) {
size_t data_idx = (this->current_data_index_ * this->width_ + this->x_low_) / 8;
size_t data_idx = this->current_data_index_ * this->row_width_ + this->x_low_ / 8;
for (size_t i = 0; i != row_length; i++) {
bytes_to_send[i] = this->send_red_ ? 0 : this->buffer_[data_idx++];
}
@@ -69,12 +63,12 @@ bool HOT EPaperSSD1677::transfer_data() {
this->write_array(&bytes_to_send.front(), row_length); // NOLINT
if (millis() - start_time > MAX_TRANSFER_TIME) {
// Let the main loop run and come back next loop
this->end_data_();
this->disable();
return false;
}
}
this->end_data_();
this->disable();
this->current_data_index_ = 0;
if (this->send_red_) {
this->send_red_ = false;

View File

@@ -3,13 +3,15 @@
#include "epaper_spi.h"
namespace esphome::epaper_spi {
class EPaperSSD1677 : public EPaperBase {
/**
* A class for monochrome epaper displays.
*/
class EPaperMono : public EPaperBase {
public:
EPaperSSD1677(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
EPaperMono(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {
this->buffer_length_ = width * height / 8; // 8 pixels per byte
this->buffer_length_ = (width + 7) / 8 * height; // 8 pixels per byte, rounded up
}
protected:
@@ -18,6 +20,7 @@ class EPaperSSD1677 : public EPaperBase {
void power_off() override{};
void deep_sleep() override;
bool reset() override;
virtual void set_window();
bool transfer_data() override;
bool send_red_{true};
};

View File

@@ -80,20 +80,17 @@ void EPaperSpectraE6::power_on() {
void EPaperSpectraE6::power_off() {
ESP_LOGV(TAG, "Power off");
this->command(0x02);
this->data(0x00);
this->cmd_data(0x02, {0x00});
}
void EPaperSpectraE6::refresh_screen(bool partial) {
ESP_LOGV(TAG, "Refresh");
this->command(0x12);
this->data(0x00);
this->cmd_data(0x12, {0x00});
}
void EPaperSpectraE6::deep_sleep() {
ESP_LOGV(TAG, "Deep sleep");
this->command(0x07);
this->data(0xA5);
this->cmd_data(0x07, {0xA5});
}
void EPaperSpectraE6::fill(Color color) {
@@ -143,7 +140,7 @@ bool HOT EPaperSpectraE6::transfer_data() {
if (buf_idx == sizeof bytes_to_send) {
this->start_data_();
this->write_array(bytes_to_send, buf_idx);
this->end_data_();
this->disable();
ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis());
buf_idx = 0;
@@ -157,7 +154,7 @@ bool HOT EPaperSpectraE6::transfer_data() {
if (buf_idx != 0) {
this->start_data_();
this->write_array(bytes_to_send, buf_idx);
this->end_data_();
this->disable();
}
this->current_data_index_ = 0;
return true;

View File

@@ -0,0 +1,47 @@
#include "epaper_waveshare.h"
namespace esphome::epaper_spi {
static const char *const TAG = "epaper_spi.waveshare";
void EpaperWaveshare::initialise(bool partial) {
EPaperBase::initialise(partial);
if (partial) {
this->cmd_data(0x32, this->partial_lut_, this->partial_lut_length_);
this->cmd_data(0x3C, {0x80});
this->cmd_data(0x22, {0xC0});
this->command(0x20);
this->next_delay_ = 100;
} else {
this->cmd_data(0x32, this->lut_, this->lut_length_);
this->cmd_data(0x3C, {0x05});
}
this->send_red_ = true;
}
void EpaperWaveshare::set_window() {
this->x_low_ &= ~7;
this->x_high_ += 7;
this->x_high_ &= ~7;
uint16_t x_start = this->x_low_ / 8;
uint16_t x_end = (this->x_high_ - 1) / 8;
this->cmd_data(0x44, {(uint8_t) x_start, (uint8_t) (x_end)});
this->cmd_data(0x4E, {(uint8_t) x_start});
this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1),
(uint8_t) ((this->y_high_ - 1) / 256)});
this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)});
ESP_LOGV(TAG, "Set window X: %u-%u, Y: %u-%u", this->x_low_, this->x_high_, this->y_low_, this->y_high_);
}
void EpaperWaveshare::refresh_screen(bool partial) {
if (partial) {
this->cmd_data(0x22, {0x0F});
} else {
this->cmd_data(0x22, {0xC7});
}
this->command(0x20);
this->next_delay_ = partial ? 100 : 3000;
}
void EpaperWaveshare::deep_sleep() { this->cmd_data(0x10, {0x01}); }
} // namespace esphome::epaper_spi

View File

@@ -0,0 +1,30 @@
#pragma once
#include "epaper_spi.h"
#include "epaper_spi_mono.h"
namespace esphome::epaper_spi {
/**
* An epaper display that needs LUTs to be sent to it.
*/
class EpaperWaveshare : public EPaperMono {
public:
EpaperWaveshare(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, const uint8_t *lut, size_t lut_length, const uint8_t *partial_lut,
uint16_t partial_lut_length)
: EPaperMono(name, width, height, init_sequence, init_sequence_length),
lut_(lut),
lut_length_(lut_length),
partial_lut_(partial_lut),
partial_lut_length_(partial_lut_length) {}
protected:
void initialise(bool partial) override;
void set_window() override;
void refresh_screen(bool partial) override;
void deep_sleep() override;
const uint8_t *lut_;
size_t lut_length_;
const uint8_t *partial_lut_;
uint16_t partial_lut_length_;
};
} // namespace esphome::epaper_spi

View File

@@ -32,6 +32,9 @@ class EpaperModel:
return cv.Required(name)
return cv.Optional(name, default=self.get_default(name, fallback))
def get_constructor_args(self, config) -> tuple:
return ()
def get_dimensions(self, config) -> tuple[int, int]:
if CONF_DIMENSIONS in config:
# Explicit dimensions, just use as is

View File

@@ -4,10 +4,9 @@ from . import EpaperModel
class SSD1677(EpaperModel):
def __init__(self, name, class_name="EPaperSSD1677", **kwargs):
if CONF_DATA_RATE not in kwargs:
kwargs[CONF_DATA_RATE] = "20MHz"
super().__init__(name, class_name, **kwargs)
def __init__(self, name, class_name="EPaperMono", data_rate="20MHz", **defaults):
defaults[CONF_DATA_RATE] = data_rate
super().__init__(name, class_name, **defaults)
# fmt: off
def get_init_sequence(self, config: dict):
@@ -23,11 +22,15 @@ class SSD1677(EpaperModel):
ssd1677 = SSD1677("ssd1677")
ssd1677.extend(
"seeed-ee04-mono-4.26",
wave_4_26 = ssd1677.extend(
"waveshare-4.26in",
width=800,
height=480,
mirror_x=True,
)
wave_4_26.extend(
"seeed-ee04-mono-4.26",
cs_pin=44,
dc_pin=10,
reset_pin=38,

View File

@@ -0,0 +1,88 @@
import esphome.codegen as cg
from esphome.core import ID
from ..display import CONF_INIT_SEQUENCE_ID
from . import EpaperModel
class WaveshareModel(EpaperModel):
def __init__(self, name, lut, lut_partial=None, **defaults):
super().__init__(name, "EpaperWaveshare", **defaults)
self.lut = lut
self.lut_partial = lut_partial
def get_constructor_args(self, config) -> tuple:
lut = (
cg.static_const_array(
ID(config[CONF_INIT_SEQUENCE_ID].id + "_lut", type=cg.uint8), self.lut
),
len(self.lut),
)
if self.lut_partial is None:
lut_partial = cg.nullptr, 0
else:
lut_partial = (
cg.static_const_array(
ID(
config[CONF_INIT_SEQUENCE_ID].id + "_lut_partial", type=cg.uint8
),
self.lut_partial,
),
len(self.lut_partial),
)
return *lut, *lut_partial
# fmt: off
WaveshareModel(
"waveshare-2.13in-v3",
width=122,
height=250,
initsequence=(
(0x01, 0x27, 0x01, 0x00), # driver output control
(0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00),
(0x11, 0x03), # Data entry mode
(0x3F, 0x22), # Undocumented command
(0x2C, 0x36), # write VCOM register
(0x04, 0x41, 0x0C, 0x32), # SRC voltage
(0x03, 0x17), # Gate voltage
(0x21, 0x00, 0x80), # Display update control
(0x18, 0x80), # Select internal temperature sensor
),
lut=(
0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0,
0xF, 0x0, 0x0, 0x2, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
0x0, 0x0, 0x0,
),
lut_partial=(
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
0x0, 0x0, 0x0,
),
)

View File

@@ -19,6 +19,7 @@ from esphome.components.esp32 import (
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MODE,
CONF_RX_PIN,
CONF_RX_QUEUE_LEN,
CONF_TX_PIN,
@@ -33,6 +34,13 @@ CONF_TX_ENQUEUE_TIMEOUT = "tx_enqueue_timeout"
esp32_can_ns = cg.esphome_ns.namespace("esp32_can")
esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent)
# Mode options - consistent with MCP2515 component
CanMode = esp32_can_ns.enum("CanMode")
CAN_MODES = {
"NORMAL": CanMode.CAN_MODE_NORMAL,
"LISTENONLY": CanMode.CAN_MODE_LISTEN_ONLY,
}
# Currently the driver only supports a subset of the bit rates defined in canbus
# The supported bit rates differ between ESP32 variants.
# See ESP-IDF Programming Guide --> API Reference --> Two-Wire Automotive Interface (TWAI)
@@ -95,6 +103,7 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate,
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number,
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_MODE, default="NORMAL"): cv.enum(CAN_MODES, upper=True),
cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t,
cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t,
cv.Optional(CONF_TX_ENQUEUE_TIMEOUT): cv.positive_time_period_milliseconds,
@@ -117,6 +126,7 @@ async def to_code(config):
cg.add(var.set_rx(config[CONF_RX_PIN]))
cg.add(var.set_tx(config[CONF_TX_PIN]))
cg.add(var.set_mode(config[CONF_MODE]))
if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None:
cg.add(var.set_rx_queue_len(rx_queue_len))
if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None:

View File

@@ -75,8 +75,15 @@ bool ESP32Can::setup_internal() {
return false;
}
// Select TWAI mode based on configuration
twai_mode_t twai_mode = (this->mode_ == CAN_MODE_LISTEN_ONLY) ? TWAI_MODE_LISTEN_ONLY : TWAI_MODE_NORMAL;
if (this->mode_ == CAN_MODE_LISTEN_ONLY) {
ESP_LOGI(TAG, "CAN bus configured in LISTEN_ONLY mode (passive, no ACKs)");
}
twai_general_config_t g_config =
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL);
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, twai_mode);
g_config.controller_id = next_twai_ctrl_num++;
if (this->tx_queue_len_.has_value()) {
g_config.tx_queue_len = this->tx_queue_len_.value();
@@ -111,6 +118,12 @@ bool ESP32Can::setup_internal() {
}
canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
// In listen-only mode, we cannot transmit
if (this->mode_ == CAN_MODE_LISTEN_ONLY) {
ESP_LOGW(TAG, "Cannot send messages in LISTEN_ONLY mode");
return canbus::ERROR_FAIL;
}
if (this->twai_handle_ == nullptr) {
// not setup yet or setup failed
return canbus::ERROR_FAIL;

View File

@@ -10,10 +10,16 @@
namespace esphome {
namespace esp32_can {
enum CanMode : uint8_t {
CAN_MODE_NORMAL = 0,
CAN_MODE_LISTEN_ONLY = 1,
};
class ESP32Can : public canbus::Canbus {
public:
void set_rx(int rx) { rx_ = rx; }
void set_tx(int tx) { tx_ = tx; }
void set_mode(CanMode mode) { mode_ = mode; }
void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; }
void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; }
void set_tx_enqueue_timeout_ms(uint32_t tx_enqueue_timeout_ms) {
@@ -28,6 +34,7 @@ class ESP32Can : public canbus::Canbus {
int rx_{-1};
int tx_{-1};
CanMode mode_{CAN_MODE_NORMAL};
TickType_t tx_enqueue_timeout_ticks_{};
optional<uint32_t> tx_queue_len_{};
optional<uint32_t> rx_queue_len_{};

View File

@@ -93,9 +93,9 @@ async def to_code(config):
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}"
if framework_ver >= cv.Version(5, 5, 0):
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.2")
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.3")
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.7.0")
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.4")
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.4")
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.9.3")
else:
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.13.0")
esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0")

View File

@@ -8,6 +8,7 @@
#include <esp_app_desc.h>
#include <esp_hosted.h>
#include <esp_hosted_host_fw_ver.h>
#include <esp_ota_ops.h>
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
#include "esphome/components/json/json_util.h"
@@ -442,6 +443,12 @@ void Esp32HostedUpdate::perform(bool force) {
this->status_clear_error();
this->publish_state();
#ifdef USE_OTA_ROLLBACK
// Mark the host partition as valid before rebooting, in case the safe mode
// timer hasn't expired yet.
esp_ota_mark_app_valid_cancel_rollback();
#endif
// Schedule a restart to ensure everything is in sync
ESP_LOGI(TAG, "Restarting in 1 second");
this->set_timeout(1000, []() { App.safe_reboot(); });

View File

@@ -370,12 +370,14 @@ void ESPHomeOTAComponent::handle_data_() {
error:
this->write_byte_(static_cast<uint8_t>(error_code));
this->cleanup_connection_();
// Abort backend before cleanup - cleanup_connection_() destroys the backend
if (this->backend_ != nullptr && update_started) {
this->backend_->abort();
}
this->cleanup_connection_();
this->status_momentary_error("err", 5000);
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));

View File

@@ -1,12 +1,14 @@
#pragma once
#include <cstring>
#include <limits>
#include <string>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
namespace esphome {
namespace event {
@@ -44,8 +46,29 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
/// Return the event types supported by this event.
const FixedVector<const char *> &get_event_types() const { return this->types_; }
/// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet.
const char *get_last_event_type() const { return this->last_event_type_; }
/// Return the last triggered event type, or empty StringRef if no event triggered yet.
StringRef get_last_event_type() const { return StringRef::from_maybe_nullptr(this->last_event_type_); }
/// Return event type by index, or nullptr if index is out of bounds.
const char *get_event_type(uint8_t index) const {
return index < this->types_.size() ? this->types_[index] : nullptr;
}
/// Return index of last triggered event type, or max uint8_t if no event triggered yet.
uint8_t get_last_event_type_index() const {
if (this->last_event_type_ == nullptr)
return std::numeric_limits<uint8_t>::max();
// Most events have <3 types, uint8_t is sufficient for all reasonable scenarios
const uint8_t size = static_cast<uint8_t>(this->types_.size());
for (uint8_t i = 0; i < size; i++) {
if (this->types_[i] == this->last_event_type_)
return i;
}
return std::numeric_limits<uint8_t>::max();
}
/// Check if an event has been triggered.
bool has_event() const { return this->last_event_type_ != nullptr; }
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);

View File

@@ -212,19 +212,18 @@ class FanPresetSetTrigger : public Trigger<std::string> {
public:
FanPresetSetTrigger(Fan *state) {
state->add_on_state_callback([this, state]() {
const auto *preset_mode = state->get_preset_mode();
auto preset_mode = state->get_preset_mode();
auto should_trigger = preset_mode != this->last_preset_mode_;
this->last_preset_mode_ = preset_mode;
if (should_trigger) {
// Trigger with empty string when nullptr to maintain backward compatibility
this->trigger(preset_mode != nullptr ? preset_mode : "");
this->trigger(std::string(preset_mode));
}
});
this->last_preset_mode_ = state->get_preset_mode();
}
protected:
const char *last_preset_mode_{nullptr};
StringRef last_preset_mode_{};
};
} // namespace fan

View File

@@ -61,7 +61,7 @@ void FanCall::perform() {
if (this->direction_.has_value()) {
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
}
if (this->has_preset_mode()) {
if (this->preset_mode_ != nullptr) {
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
}
this->parent_.control(*this);
@@ -83,7 +83,7 @@ void FanCall::validate_() {
*this->binary_state_
// ..,and no preset mode will be active...
&& !this->has_preset_mode() &&
this->parent_.get_preset_mode() == nullptr
!this->parent_.has_preset_mode()
// ...and neither current nor new speed is available...
&& traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) {
// ...set speed to 100%
@@ -154,16 +154,16 @@ const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) {
return this->get_traits().find_preset_mode(preset_mode, len);
}
bool Fan::set_preset_mode_(const char *preset_mode) {
if (preset_mode == nullptr) {
// Treat nullptr as clearing the preset mode
bool Fan::set_preset_mode_(const char *preset_mode, size_t len) {
if (preset_mode == nullptr || len == 0) {
// Treat nullptr or empty string as clearing the preset mode (no valid preset is "")
if (this->preset_mode_ == nullptr) {
return false; // No change
}
this->clear_preset_mode_();
return true;
}
const char *validated = this->find_preset_mode_(preset_mode);
const char *validated = this->find_preset_mode_(preset_mode, len);
if (validated == nullptr || this->preset_mode_ == validated) {
return false; // Preset mode not supported or no change
}
@@ -171,10 +171,31 @@ bool Fan::set_preset_mode_(const char *preset_mode) {
return true;
}
bool Fan::set_preset_mode_(const std::string &preset_mode) { return this->set_preset_mode_(preset_mode.c_str()); }
bool Fan::set_preset_mode_(const char *preset_mode) {
return this->set_preset_mode_(preset_mode, preset_mode ? strlen(preset_mode) : 0);
}
bool Fan::set_preset_mode_(const std::string &preset_mode) {
return this->set_preset_mode_(preset_mode.data(), preset_mode.size());
}
bool Fan::set_preset_mode_(StringRef preset_mode) {
// Safe: find_preset_mode_ only uses the input for comparison and returns
// a pointer from traits, so the input StringRef's lifetime doesn't matter.
return this->set_preset_mode_(preset_mode.c_str(), preset_mode.size());
}
void Fan::clear_preset_mode_() { this->preset_mode_ = nullptr; }
void Fan::apply_preset_mode_(const FanCall &call) {
if (call.has_preset_mode()) {
this->set_preset_mode_(call.get_preset_mode());
} else if (call.get_speed().has_value()) {
// Manually setting speed clears preset (per Home Assistant convention)
this->clear_preset_mode_();
}
}
void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
void Fan::publish_state() {
auto traits = this->get_traits();
@@ -192,9 +213,8 @@ void Fan::publish_state() {
if (traits.supports_direction()) {
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
}
const char *preset = this->get_preset_mode();
if (preset != nullptr) {
ESP_LOGD(TAG, " Preset Mode: %s", preset);
if (this->preset_mode_ != nullptr) {
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
}
this->state_callback_.call();
#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY)
@@ -249,12 +269,11 @@ void Fan::save_state_() {
state.speed = this->speed;
state.direction = this->direction;
const char *preset = this->get_preset_mode();
if (preset != nullptr) {
if (this->has_preset_mode()) {
const auto &preset_modes = traits.supported_preset_modes();
// Find index of current preset mode (pointer comparison is safe since preset is from traits)
for (size_t i = 0; i < preset_modes.size(); i++) {
if (preset_modes[i] == preset) {
if (preset_modes[i] == this->preset_mode_) {
state.preset_mode = i;
break;
}

View File

@@ -5,6 +5,7 @@
#include "esphome/core/log.h"
#include "esphome/core/optional.h"
#include "esphome/core/preferences.h"
#include "esphome/core/string_ref.h"
#include "fan_traits.h"
namespace esphome {
@@ -128,8 +129,11 @@ class Fan : public EntityBase {
/// Set the restore mode of this fan.
void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
/// Get the current preset mode (returns pointer to string stored in traits, or nullptr if not set)
const char *get_preset_mode() const { return this->preset_mode_; }
/// Get the current preset mode.
/// Returns a StringRef of the string stored in traits, or empty ref if not set.
/// The returned ref points to string literals from codegen (static storage).
/// Traits are set once at startup and valid for the lifetime of the program.
StringRef get_preset_mode() const { return StringRef::from_maybe_nullptr(this->preset_mode_); }
/// Check if a preset mode is currently active
bool has_preset_mode() const { return this->preset_mode_ != nullptr; }
@@ -146,11 +150,15 @@ class Fan : public EntityBase {
void dump_traits_(const char *tag, const char *prefix);
/// Set the preset mode (finds and stores pointer from traits). Returns true if changed.
/// Passing nullptr or empty string clears the preset mode.
bool set_preset_mode_(const char *preset_mode, size_t len);
bool set_preset_mode_(const char *preset_mode);
/// Set the preset mode (finds and stores pointer from traits). Returns true if changed.
bool set_preset_mode_(const std::string &preset_mode);
bool set_preset_mode_(StringRef preset_mode);
/// Clear the preset mode
void clear_preset_mode_();
/// Apply preset mode from a FanCall (handles speed-clears-preset convention)
void apply_preset_mode_(const FanCall &call);
/// Find and return the matching preset mode pointer from traits, or nullptr if not found.
const char *find_preset_mode_(const char *preset_mode);
const char *find_preset_mode_(const char *preset_mode, size_t len);

View File

@@ -232,17 +232,19 @@ void GraphLegend::init(Graph *g) {
ESP_LOGI(TAGL, " %s %d %d", txtstr.c_str(), fw, fh);
if (this->values_ != VALUE_POSITION_TYPE_NONE) {
std::string valstr =
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
char valstr[VALUE_ACCURACY_MAX_LEN];
if (this->units_) {
valstr += trace->sensor_->get_unit_of_measurement_ref();
value_accuracy_with_uom_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals(),
trace->sensor_->get_unit_of_measurement_ref());
} else {
value_accuracy_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
}
this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
this->font_value_->measure(valstr, &fw, &fos, &fbl, &fh);
if (fw > valw)
valw = fw;
if (fh > valh)
valh = fh;
ESP_LOGI(TAGL, " %s %d %d", valstr.c_str(), fw, fh);
ESP_LOGI(TAGL, " %s %d %d", valstr, fw, fh);
}
}
// Add extra margin
@@ -368,13 +370,15 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
if (legend_->values_ != VALUE_POSITION_TYPE_NONE) {
int xv = x + legend_->xv_;
int yv = y + legend_->yv_;
std::string valstr =
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
char valstr[VALUE_ACCURACY_MAX_LEN];
if (legend_->units_) {
valstr += trace->sensor_->get_unit_of_measurement_ref();
value_accuracy_with_uom_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals(),
trace->sensor_->get_unit_of_measurement_ref());
} else {
value_accuracy_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
}
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str());
ESP_LOGV(TAG, " value: %s", valstr.c_str());
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr);
ESP_LOGV(TAG, " value: %s", valstr);
}
x += legend_->xs_;
y += legend_->ys_;

View File

@@ -57,7 +57,7 @@ void HBridgeFan::control(const fan::FanCall &call) {
this->oscillating = *call.get_oscillating();
if (call.get_direction().has_value())
this->direction = *call.get_direction();
this->set_preset_mode_(call.get_preset_mode());
this->apply_preset_mode_(call);
this->write_state_();
this->publish_state();

View File

@@ -91,11 +91,14 @@ void HomeassistantNumber::control(float value) {
resp.data.init(2);
auto &entity_id = resp.data.emplace_back();
entity_id.key = ENTITY_ID_KEY;
entity_id.value = this->entity_id_;
entity_id.value = StringRef(this->entity_id_);
auto &entity_value = resp.data.emplace_back();
entity_value.key = VALUE_KEY;
entity_value.value = to_string(value);
// Stack buffer - no heap allocation; %g produces shortest representation
char value_buf[16];
snprintf(value_buf, sizeof(value_buf), "%g", value);
entity_value.value = StringRef(value_buf);
api::global_api_server->send_homeassistant_action(resp);
}

View File

@@ -55,7 +55,7 @@ void HomeassistantSwitch::write_state(bool state) {
resp.data.init(1);
auto &entity_id_kv = resp.data.emplace_back();
entity_id_kv.key = ENTITY_ID_KEY;
entity_id_kv.value = this->entity_id_;
entity_id_kv.value = StringRef(this->entity_id_);
api::global_api_server->send_homeassistant_action(resp);
}

View File

@@ -340,8 +340,8 @@ void I2SAudioSpeaker::speaker_task(void *params) {
const uint32_t read_delay =
(this_speaker->current_stream_info_.frames_to_microseconds(frames_written) / 1000) / 2;
uint8_t *new_data = transfer_buffer->get_buffer_end(); // track start of any newly copied bytes
size_t bytes_read = transfer_buffer->transfer_data_from_source(pdMS_TO_TICKS(read_delay));
uint8_t *new_data = transfer_buffer->get_buffer_end() - bytes_read;
if (bytes_read > 0) {
if (this_speaker->q15_volume_factor_ < INT16_MAX) {

View File

@@ -193,8 +193,12 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
#ifdef USE_WEBSERVER
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
if (ip.is_ip4()) {
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
urls.push_back(webserver_url);
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
ip.str_to(ip_buf);
// "http://" (7) + IP (40) + ":" (1) + port (5) + null (1) = 54
char webserver_url[7 + network::IP_ADDRESS_BUFFER_SIZE + 1 + 5 + 1];
snprintf(webserver_url, sizeof(webserver_url), "http://%s:%u", ip_buf, USE_WEBSERVER_PORT);
urls.emplace_back(webserver_url);
break;
}
}
@@ -267,8 +271,10 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
continue;
// Send each ssid separately to avoid overflowing the buffer
std::vector<uint8_t> data = improv::build_rpc_response(
improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
char rssi_buf[5]; // int8_t: -128 to 127, max 4 chars + null
*int8_to_str(rssi_buf, scan.get_rssi()) = '\0';
std::vector<uint8_t> data =
improv::build_rpc_response(improv::GET_WIFI_NETWORKS, {ssid, rssi_buf, YESNO(scan.get_with_auth())}, false);
this->send_response_(data);
networks.push_back(ssid);
}

View File

@@ -0,0 +1,76 @@
"""
Infrared component for ESPHome.
WARNING: This component is EXPERIMENTAL. The API (both Python configuration
and C++ interfaces) may change at any time without following the normal
breaking changes policy. Use at your own risk.
Once the API is considered stable, this warning will be removed.
"""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import setup_entity
from esphome.coroutine import CoroPriority
from esphome.types import ConfigType
CODEOWNERS = ["@kbx81"]
AUTO_LOAD = ["remote_base"]
IS_PLATFORM_COMPONENT = True
infrared_ns = cg.esphome_ns.namespace("infrared")
Infrared = infrared_ns.class_("Infrared", cg.EntityBase, cg.Component)
InfraredCall = infrared_ns.class_("InfraredCall")
InfraredTraits = infrared_ns.class_("InfraredTraits")
CONF_INFRARED_ID = "infrared_id"
CONF_SUPPORTS_TRANSMITTER = "supports_transmitter"
CONF_SUPPORTS_RECEIVER = "supports_receiver"
def infrared_schema(class_: type[cg.MockObjClass]) -> cv.Schema:
"""Create a schema for an infrared platform.
:param class_: The infrared class to use for this schema.
:return: An extended schema for infrared configuration.
"""
entity_schema = cv.ENTITY_BASE_SCHEMA.extend(cv.COMPONENT_SCHEMA)
return entity_schema.extend(
{
cv.GenerateID(): cv.declare_id(class_),
}
)
async def setup_infrared_core_(var: cg.Pvariable, config: ConfigType) -> None:
"""Set up core infrared configuration."""
await setup_entity(var, config, "infrared")
async def register_infrared(var: cg.Pvariable, config: ConfigType) -> None:
"""Register an infrared device with the core."""
cg.add_define("USE_IR_RF")
await cg.register_component(var, config)
await setup_infrared_core_(var, config)
cg.add(cg.App.register_infrared(var))
CORE.register_platform_component("infrared", var)
async def new_infrared(config: ConfigType, *args) -> cg.Pvariable:
"""Create a new Infrared instance.
:param config: Configuration dictionary.
:param args: Additional arguments to pass to new_Pvariable.
:return: The created Infrared instance.
"""
var = cg.new_Pvariable(config[CONF_ID], *args)
await register_infrared(var, config)
return var
@coroutine_with_priority(CoroPriority.CORE)
async def to_code(config: ConfigType) -> None:
cg.add_global(infrared_ns.using)

View File

@@ -0,0 +1,138 @@
#include "infrared.h"
#include "esphome/core/log.h"
#ifdef USE_API
#include "esphome/components/api/api_server.h"
#endif
namespace esphome::infrared {
static const char *const TAG = "infrared";
// ========== InfraredCall ==========
InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
this->carrier_frequency_ = frequency;
return *this;
}
InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
this->raw_timings_ = &timings;
this->packed_data_ = nullptr; // Clear packed if vector is set
return *this;
}
InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count) {
this->packed_data_ = data;
this->packed_length_ = length;
this->packed_count_ = count;
this->raw_timings_ = nullptr; // Clear vector if packed is set
return *this;
}
InfraredCall &InfraredCall::set_repeat_count(uint32_t count) {
this->repeat_count_ = count;
return *this;
}
void InfraredCall::perform() {
if (this->parent_ != nullptr) {
this->parent_->control(*this);
}
}
// ========== Infrared ==========
void Infrared::setup() {
// Set up traits based on configuration
this->traits_.set_supports_transmitter(this->has_transmitter());
this->traits_.set_supports_receiver(this->has_receiver());
// Register as listener for received IR data
if (this->receiver_ != nullptr) {
this->receiver_->register_listener(this);
}
}
void Infrared::dump_config() {
ESP_LOGCONFIG(TAG,
"Infrared '%s'\n"
" Supports Transmitter: %s\n"
" Supports Receiver: %s",
this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()),
YESNO(this->traits_.get_supports_receiver()));
}
InfraredCall Infrared::make_call() { return InfraredCall(this); }
void Infrared::control(const InfraredCall &call) {
if (this->transmitter_ == nullptr) {
ESP_LOGW(TAG, "No transmitter configured");
return;
}
if (!call.has_raw_timings()) {
ESP_LOGE(TAG, "No raw timings provided");
return;
}
// Create transmit data object
auto transmit_call = this->transmitter_->transmit();
auto *transmit_data = transmit_call.get_data();
// Set carrier frequency
if (call.get_carrier_frequency().has_value()) {
transmit_data->set_carrier_frequency(call.get_carrier_frequency().value());
}
// Set timings based on format
if (call.is_packed()) {
// Zero-copy from packed protobuf data
transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(),
call.get_packed_count());
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
call.get_repeat_count());
} else {
// From vector (lambdas/automations)
transmit_data->set_data(call.get_raw_timings());
ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%u", call.get_raw_timings().size(),
call.get_repeat_count());
}
// Set repeat count
if (call.get_repeat_count() > 0) {
transmit_call.set_send_times(call.get_repeat_count());
}
// Perform transmission
transmit_call.perform();
}
uint32_t Infrared::get_capability_flags() const {
uint32_t flags = 0;
// Add transmit/receive capability based on traits
if (this->traits_.get_supports_transmitter())
flags |= InfraredCapability::CAPABILITY_TRANSMITTER;
if (this->traits_.get_supports_receiver())
flags |= InfraredCapability::CAPABILITY_RECEIVER;
return flags;
}
bool Infrared::on_receive(remote_base::RemoteReceiveData data) {
// Forward received IR data to API server
#if defined(USE_API) && defined(USE_IR_RF)
if (api::global_api_server != nullptr) {
#ifdef USE_DEVICES
uint32_t device_id = this->get_device_id();
#else
uint32_t device_id = 0;
#endif
api::global_api_server->send_infrared_rf_receive_event(device_id, this->get_object_id_hash(), &data.get_raw_data());
}
#endif
return false; // Don't consume the event, allow other listeners to process it
}
} // namespace esphome::infrared

View File

@@ -0,0 +1,130 @@
#pragma once
// WARNING: This component is EXPERIMENTAL. The API may change at any time
// without following the normal breaking changes policy. Use at your own risk.
// Once the API is considered stable, this warning will be removed.
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/components/remote_base/remote_base.h"
#include <vector>
namespace esphome::infrared {
/// Capability flags for individual infrared instances
enum InfraredCapability : uint32_t {
CAPABILITY_TRANSMITTER = 1 << 0, // Can transmit signals
CAPABILITY_RECEIVER = 1 << 1, // Can receive signals
};
/// Forward declarations
class Infrared;
/// InfraredCall - Builder pattern for transmitting infrared signals
class InfraredCall {
public:
explicit InfraredCall(Infrared *parent) : parent_(parent) {}
/// Set the carrier frequency in Hz
InfraredCall &set_carrier_frequency(uint32_t frequency);
/// Set the raw timings (positive = mark, negative = space)
/// Note: The timings vector must outlive the InfraredCall (zero-copy reference)
InfraredCall &set_raw_timings(const std::vector<int32_t> &timings);
/// Set the raw timings from packed protobuf sint32 data (zero-copy from wire)
/// Note: The data must outlive the InfraredCall
InfraredCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count);
/// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.)
InfraredCall &set_repeat_count(uint32_t count);
/// Perform the transmission
void perform();
/// Get the carrier frequency
const optional<uint32_t> &get_carrier_frequency() const { return this->carrier_frequency_; }
/// Get the raw timings (only valid if set via set_raw_timings, not packed)
const std::vector<int32_t> &get_raw_timings() const { return *this->raw_timings_; }
/// Check if raw timings have been set (either vector or packed)
bool has_raw_timings() const { return this->raw_timings_ != nullptr || this->packed_data_ != nullptr; }
/// Check if using packed data format
bool is_packed() const { return this->packed_data_ != nullptr; }
/// Get packed data (only valid if set via set_raw_timings_packed)
const uint8_t *get_packed_data() const { return this->packed_data_; }
uint16_t get_packed_length() const { return this->packed_length_; }
uint16_t get_packed_count() const { return this->packed_count_; }
/// Get the repeat count
uint32_t get_repeat_count() const { return this->repeat_count_; }
protected:
uint32_t repeat_count_{1};
Infrared *parent_;
optional<uint32_t> carrier_frequency_;
// Vector-based timings (for lambdas/automations)
const std::vector<int32_t> *raw_timings_{nullptr};
// Packed protobuf timings (for API zero-copy)
const uint8_t *packed_data_{nullptr};
uint16_t packed_length_{0};
uint16_t packed_count_{0};
};
/// InfraredTraits - Describes the capabilities of an infrared implementation
class InfraredTraits {
public:
bool get_supports_transmitter() const { return this->supports_transmitter_; }
void set_supports_transmitter(bool supports) { this->supports_transmitter_ = supports; }
bool get_supports_receiver() const { return this->supports_receiver_; }
void set_supports_receiver(bool supports) { this->supports_receiver_ = supports; }
protected:
bool supports_transmitter_{false};
bool supports_receiver_{false};
};
/// Infrared - Base class for infrared remote control implementations
class Infrared : public Component, public EntityBase, public remote_base::RemoteReceiverListener {
public:
Infrared() = default;
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
/// Set the remote receiver component
void set_receiver(remote_base::RemoteReceiverBase *receiver) { this->receiver_ = receiver; }
/// Set the remote transmitter component
void set_transmitter(remote_base::RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
/// Check if this infrared has a transmitter configured
bool has_transmitter() const { return this->transmitter_ != nullptr; }
/// Check if this infrared has a receiver configured
bool has_receiver() const { return this->receiver_ != nullptr; }
/// Get the traits for this infrared implementation
InfraredTraits &get_traits() { return this->traits_; }
const InfraredTraits &get_traits() const { return this->traits_; }
/// Create a call object for transmitting
InfraredCall make_call();
/// Get capability flags for this infrared instance
uint32_t get_capability_flags() const;
/// Called when IR data is received (from RemoteReceiverListener)
bool on_receive(remote_base::RemoteReceiveData data) override;
protected:
friend class InfraredCall;
/// Perform the actual transmission (called by InfraredCall)
virtual void control(const InfraredCall &call);
// Underlying hardware components
remote_base::RemoteReceiverBase *receiver_{nullptr};
remote_base::RemoteTransmitterBase *transmitter_{nullptr};
// Traits describing capabilities
InfraredTraits traits_;
};
} // namespace esphome::infrared

View File

@@ -0,0 +1,11 @@
"""IR/RF Proxy component - provides remote_base backend for infrared platform."""
import esphome.codegen as cg
CODEOWNERS = ["@kbx81"]
# Namespace and constants exported for infrared.py platform
ir_rf_proxy_ns = cg.esphome_ns.namespace("ir_rf_proxy")
CONF_REMOTE_RECEIVER_ID = "remote_receiver_id"
CONF_REMOTE_TRANSMITTER_ID = "remote_transmitter_id"

View File

@@ -0,0 +1,77 @@
"""Infrared platform implementation using remote_base (remote_transmitter/receiver)."""
from typing import Any
import esphome.codegen as cg
from esphome.components import infrared, remote_receiver, remote_transmitter
import esphome.config_validation as cv
from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY
import esphome.final_validate as fv
from . import CONF_REMOTE_RECEIVER_ID, CONF_REMOTE_TRANSMITTER_ID, ir_rf_proxy_ns
CODEOWNERS = ["@kbx81"]
DEPENDENCIES = ["infrared"]
IrRfProxy = ir_rf_proxy_ns.class_("IrRfProxy", infrared.Infrared)
CONFIG_SCHEMA = cv.All(
infrared.infrared_schema(IrRfProxy).extend(
{
cv.Optional(CONF_FREQUENCY, default=0): cv.frequency,
cv.Optional(CONF_REMOTE_RECEIVER_ID): cv.use_id(
remote_receiver.RemoteReceiverComponent
),
cv.Optional(CONF_REMOTE_TRANSMITTER_ID): cv.use_id(
remote_transmitter.RemoteTransmitterComponent
),
}
),
cv.has_exactly_one_key(CONF_REMOTE_RECEIVER_ID, CONF_REMOTE_TRANSMITTER_ID),
)
def _final_validate(config: dict[str, Any]) -> None:
"""Validate that transmitters have a proper carrier duty cycle."""
# Only validate if this is an infrared (not RF) configuration with a transmitter
if config.get(CONF_FREQUENCY, 0) != 0 or CONF_REMOTE_TRANSMITTER_ID not in config:
return
# Get the transmitter configuration
transmitter_id = config[CONF_REMOTE_TRANSMITTER_ID]
full_config = fv.full_config.get()
transmitter_path = full_config.get_path_for_id(transmitter_id)[:-1]
transmitter_config = full_config.get_config_for_path(transmitter_path)
# Check if carrier_duty_percent set to 0 or 100
# Note: remote_transmitter schema requires this field and validates 1-100%,
# but we double-check here for infrared to provide a helpful error message
duty_percent = transmitter_config.get(CONF_CARRIER_DUTY_PERCENT)
if duty_percent in {0, 100}:
raise cv.Invalid(
f"Transmitter '{transmitter_id}' must have '{CONF_CARRIER_DUTY_PERCENT}' configured with "
"an intermediate value (typically 30-50%) for infrared transmission. If this is an RF "
f"transmitter, configure this infrared with a '{CONF_FREQUENCY}' value greater than 0"
)
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config: dict[str, Any]) -> None:
"""Code generation for remote_base infrared platform."""
# Create and register the infrared entity
var = await infrared.new_infrared(config)
# Set frequency / 1000; zero indicates infrared hardware
cg.add(var.set_frequency(config[CONF_FREQUENCY] / 1000))
# Link transmitter if specified
if CONF_REMOTE_TRANSMITTER_ID in config:
transmitter = await cg.get_variable(config[CONF_REMOTE_TRANSMITTER_ID])
cg.add(var.set_transmitter(transmitter))
# Link receiver if specified
if CONF_REMOTE_RECEIVER_ID in config:
receiver = await cg.get_variable(config[CONF_REMOTE_RECEIVER_ID])
cg.add(var.set_receiver(receiver))

View File

@@ -0,0 +1,23 @@
#include "ir_rf_proxy.h"
#include "esphome/core/log.h"
namespace esphome::ir_rf_proxy {
static const char *const TAG = "ir_rf_proxy";
void IrRfProxy::dump_config() {
ESP_LOGCONFIG(TAG,
"IR/RF Proxy '%s'\n"
" Supports Transmitter: %s\n"
" Supports Receiver: %s",
this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()),
YESNO(this->traits_.get_supports_receiver()));
if (this->is_rf()) {
ESP_LOGCONFIG(TAG, " Hardware Type: RF (%.3f MHz)", this->frequency_khz_ / 1e3f);
} else {
ESP_LOGCONFIG(TAG, " Hardware Type: Infrared");
}
}
} // namespace esphome::ir_rf_proxy

View File

@@ -0,0 +1,32 @@
#pragma once
// WARNING: This component is EXPERIMENTAL. The API may change at any time
// without following the normal breaking changes policy. Use at your own risk.
// Once the API is considered stable, this warning will be removed.
#include "esphome/components/infrared/infrared.h"
#include "esphome/components/remote_transmitter/remote_transmitter.h"
#include "esphome/components/remote_receiver/remote_receiver.h"
namespace esphome::ir_rf_proxy {
/// IrRfProxy - Infrared platform implementation using remote_transmitter/receiver as backend
class IrRfProxy : public infrared::Infrared {
public:
IrRfProxy() = default;
void dump_config() override;
/// Set RF frequency in kHz (0 = infrared, non-zero = RF)
void set_frequency(uint32_t frequency_khz) { this->frequency_khz_ = frequency_khz; }
/// Get RF frequency in kHz
uint32_t get_frequency() const { return this->frequency_khz_; }
/// Check if this is RF mode (non-zero frequency)
bool is_rf() const { return this->frequency_khz_ > 0; }
protected:
// RF frequency in kHz (Hz / 1000); 0 = infrared, non-zero = RF
uint32_t frequency_khz_{0};
};
} // namespace esphome::ir_rf_proxy

View File

@@ -183,11 +183,14 @@ uint8_t Lc709203f::get_register_(uint8_t register_to_read, uint16_t *register_va
return_code = this->read_register(register_to_read, &read_buffer[3], 3);
if (return_code != i2c::NO_ERROR) {
// Error on the i2c bus
this->status_set_warning(
str_sprintf("Error code %d when reading from register 0x%02X", return_code, register_to_read).c_str());
char buf[64];
snprintf(buf, sizeof(buf), "Error code %d when reading from register 0x%02X", return_code, register_to_read);
this->status_set_warning(buf);
} else if (crc8(read_buffer, 5, 0x00, 0x07, true) != read_buffer[5]) {
// I2C indicated OK, but the CRC of the data does not matcth.
this->status_set_warning(str_sprintf("CRC error reading from register 0x%02X", register_to_read).c_str());
char buf[64];
snprintf(buf, sizeof(buf), "CRC error reading from register 0x%02X", register_to_read);
this->status_set_warning(buf);
} else {
*register_value = ((uint16_t) read_buffer[4] << 8) | (uint16_t) read_buffer[3];
return i2c::NO_ERROR;
@@ -225,8 +228,9 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set)
if (return_code == i2c::NO_ERROR) {
return return_code;
} else {
this->status_set_warning(
str_sprintf("Error code %d when writing to register 0x%02X", return_code, register_to_set).c_str());
char buf[64];
snprintf(buf, sizeof(buf), "Error code %d when writing to register 0x%02X", return_code, register_to_set);
this->status_set_warning(buf);
}
}

View File

@@ -5,6 +5,7 @@ from esphome.const import (
CONF_HAS_MOVING_TARGET,
CONF_HAS_STILL_TARGET,
CONF_HAS_TARGET,
CONF_ID,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_OCCUPANCY,
DEVICE_CLASS_PRESENCE,
@@ -19,6 +20,7 @@ DEPENDENCIES = ["ld2410"]
CONF_OUT_PIN_PRESENCE_STATUS = "out_pin_presence_status"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY,

View File

@@ -3,6 +3,7 @@ from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
CONF_FACTORY_RESET,
CONF_ID,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
@@ -21,6 +22,7 @@ RestartButton = ld2410_ns.class_("RestartButton", button.Button)
CONF_QUERY_PARAMS = "query_params"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton,

View File

@@ -442,7 +442,8 @@ bool LD2410Component::handle_ack_data_() {
ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option());
auto baud = this->baud_rate_select_->current_option();
ESP_LOGE(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str());
}
#endif
break;
@@ -766,10 +767,10 @@ void LD2410Component::set_light_out_control() {
#endif
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option());
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str());
}
if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) {
this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option());
this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str());
}
#endif
this->set_config_mode_(true);

View File

@@ -31,6 +31,7 @@ TIMEOUT_GROUP = "timeout"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Inclusive(CONF_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
MaxDistanceTimeoutNumber,

View File

@@ -3,6 +3,7 @@ from esphome.components import select
import esphome.config_validation as cv
from esphome.const import (
CONF_BAUD_RATE,
CONF_ID,
ENTITY_CATEGORY_CONFIG,
ICON_LIGHTBULB,
ICON_RULER,
@@ -22,6 +23,7 @@ CONF_OUT_PIN_LEVEL = "out_pin_level"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_DISTANCE_RESOLUTION): select.select_schema(
DistanceResolutionSelect,

View File

@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_LIGHT,
CONF_MOVING_DISTANCE,
DEVICE_CLASS_DISTANCE,
@@ -28,6 +29,7 @@ CONF_STILL_ENERGY = "still_energy"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,

View File

@@ -3,6 +3,7 @@ from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import (
CONF_BLUETOOTH,
CONF_ID,
DEVICE_CLASS_SWITCH,
ENTITY_CATEGORY_CONFIG,
ICON_BLUETOOTH,
@@ -17,6 +18,7 @@ EngineeringModeSwitch = ld2410_ns.class_("EngineeringModeSwitch", switch.Switch)
CONF_ENGINEERING_MODE = "engineering_mode"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
EngineeringModeSwitch,

View File

@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MAC_ADDRESS,
CONF_VERSION,
ENTITY_CATEGORY_DIAGNOSTIC,
@@ -14,6 +15,7 @@ from . import CONF_LD2410_ID, LD2410Component
DEPENDENCIES = ["ld2410"]
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP

View File

@@ -5,6 +5,7 @@ from esphome.const import (
CONF_HAS_MOVING_TARGET,
CONF_HAS_STILL_TARGET,
CONF_HAS_TARGET,
CONF_ID,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_OCCUPANCY,
DEVICE_CLASS_RUNNING,
@@ -20,6 +21,7 @@ DEPENDENCIES = ["ld2412"]
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS = "dynamic_background_correction_status"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS

View File

@@ -3,6 +3,7 @@ from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
CONF_FACTORY_RESET,
CONF_ID,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
@@ -26,6 +27,7 @@ CONF_QUERY_PARAMS = "query_params"
CONF_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton,

View File

@@ -486,7 +486,8 @@ bool LD2412Component::handle_ack_data_() {
ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGW(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option());
auto baud = this->baud_rate_select_->current_option();
ESP_LOGW(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str());
}
#endif
break;
@@ -790,7 +791,7 @@ void LD2412Component::set_basic_config() {
1, TOTAL_GATES, DEFAULT_PRESENCE_TIMEOUT, 0,
#endif
#ifdef USE_SELECT
find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option()),
find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str()),
#else
0x01, // Default value if not using select
#endif
@@ -844,7 +845,7 @@ void LD2412Component::set_light_out_control() {
#endif
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option());
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str());
}
#endif
uint8_t value[2] = {this->light_function_, this->light_threshold_};

View File

@@ -31,6 +31,7 @@ TIMEOUT_GROUP = "timeout"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
LightThresholdNumber,

View File

@@ -3,6 +3,7 @@ from esphome.components import select
import esphome.config_validation as cv
from esphome.const import (
CONF_BAUD_RATE,
CONF_ID,
ENTITY_CATEGORY_CONFIG,
ICON_LIGHTBULB,
ICON_RULER,
@@ -22,6 +23,7 @@ CONF_OUT_PIN_LEVEL = "out_pin_level"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_BAUD_RATE): select.select_schema(
BaudRateSelect,

View File

@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_LIGHT,
CONF_MOVING_DISTANCE,
DEVICE_CLASS_DISTANCE,
@@ -28,6 +29,7 @@ CONF_STILL_ENERGY = "still_energy"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,

View File

@@ -3,6 +3,7 @@ from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import (
CONF_BLUETOOTH,
CONF_ID,
DEVICE_CLASS_SWITCH,
ENTITY_CATEGORY_CONFIG,
ICON_BLUETOOTH,
@@ -17,6 +18,7 @@ EngineeringModeSwitch = LD2412_ns.class_("EngineeringModeSwitch", switch.Switch)
CONF_ENGINEERING_MODE = "engineering_mode"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
BluetoothSwitch,

View File

@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MAC_ADDRESS,
CONF_VERSION,
ENTITY_CATEGORY_DIAGNOSTIC,
@@ -14,6 +15,7 @@ from . import CONF_LD2412_ID, LD2412Component
DEPENDENCIES = ["ld2412"]
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP

View File

@@ -5,6 +5,7 @@ from esphome.const import (
CONF_HAS_MOVING_TARGET,
CONF_HAS_STILL_TARGET,
CONF_HAS_TARGET,
CONF_ID,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_OCCUPANCY,
)
@@ -18,6 +19,7 @@ ICON_SHIELD_ACCOUNT = "mdi:shield-account"
ICON_TARGET_ACCOUNT = "mdi:target-account"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY,

View File

@@ -3,6 +3,7 @@ from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
CONF_FACTORY_RESET,
CONF_ID,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
@@ -17,6 +18,7 @@ FactoryResetButton = ld2450_ns.class_("FactoryResetButton", button.Button)
RestartButton = ld2450_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton,

View File

@@ -637,7 +637,8 @@ bool LD2450Component::handle_ack_data_() {
ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option());
auto baud = this->baud_rate_select_->current_option();
ESP_LOGE(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str());
}
#endif
break;
@@ -718,7 +719,8 @@ bool LD2450Component::handle_ack_data_() {
this->publish_zone_type();
#ifdef USE_SELECT
if (this->zone_type_select_ != nullptr) {
ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->current_option());
auto zone = this->zone_type_select_->current_option();
ESP_LOGV(TAG, "Change zone type to: %.*s", (int) zone.size(), zone.c_str());
}
#endif
if (this->buffer_data_[10] == 0x00) {

View File

@@ -28,6 +28,7 @@ ZoneCoordinateNumber = ld2450_ns.class_("ZoneCoordinateNumber", number.Number)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Required(CONF_PRESENCE_TIMEOUT): number.number_schema(
PresenceTimeoutNumber,

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