mirror of
https://github.com/esphome/esphome.git
synced 2026-01-13 05:27:53 -07:00
Compare commits
429 Commits
integratio
...
web_server
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c12b116176 | ||
|
|
a1044664ab | ||
|
|
4365e7a963 | ||
|
|
b0dfceb6d2 | ||
|
|
883d2c506f | ||
|
|
8bb201262f | ||
|
|
e54060e296 | ||
|
|
d3c3454778 | ||
|
|
32e0f2624d | ||
|
|
80d3adf703 | ||
|
|
b7f95f5fc0 | ||
|
|
6640205150 | ||
|
|
8de9be679c | ||
|
|
72983bd83d | ||
|
|
fc2324c002 | ||
|
|
e227fc4f1a | ||
|
|
59bd60b4e2 | ||
|
|
6b02f9f1ec | ||
|
|
45cab9b5b8 | ||
|
|
12c8c5cd7f | ||
|
|
be059b3368 | ||
|
|
32f90b2855 | ||
|
|
2fb7c0d453 | ||
|
|
7935fba4b1 | ||
|
|
ab32b93928 | ||
|
|
3d54ccac65 | ||
|
|
c40f44f4bd | ||
|
|
62cb08c3dc | ||
|
|
7576e032f8 | ||
|
|
cd43b4114e | ||
|
|
2c165e4817 | ||
|
|
5afe4b7b12 | ||
|
|
10ed44165d | ||
|
|
dcb8c994cc | ||
|
|
012a1e2afd | ||
|
|
d4969f581a | ||
|
|
40f108116b | ||
|
|
52459d1bc7 | ||
|
|
325c938074 | ||
|
|
423a617b15 | ||
|
|
eb5c4f34e2 | ||
|
|
c9ab4ca018 | ||
|
|
da0b01f4d0 | ||
|
|
e301b8d0e0 | ||
|
|
738678e87b | ||
|
|
0ce3ac438b | ||
|
|
afa4fe9820 | ||
|
|
a66df9ab0f | ||
|
|
1339f3e77e | ||
|
|
e29523e248 | ||
|
|
44eac36e05 | ||
|
|
050e9b0d4a | ||
|
|
25ac89e9b5 | ||
|
|
d86d1f9f52 | ||
|
|
fd19280df9 | ||
|
|
b7dbda497a | ||
|
|
815543b77e | ||
|
|
0948e0359f | ||
|
|
2830c7dab8 | ||
|
|
a03c13f304 | ||
|
|
ef64226ed0 | ||
|
|
ed39a130a8 | ||
|
|
21687a1f58 | ||
|
|
bf75f77eee | ||
|
|
39526e5360 | ||
|
|
8e40a55d5d | ||
|
|
20927674da | ||
|
|
8464307a43 | ||
|
|
546cdbde0d | ||
|
|
ada4e6d5e9 | ||
|
|
d6554702d8 | ||
|
|
b083c33857 | ||
|
|
f8309b007c | ||
|
|
ac672e4b8f | ||
|
|
c387c03944 | ||
|
|
fb47bfe92a | ||
|
|
5b9be7c169 | ||
|
|
6d1f6a1084 | ||
|
|
f9ed2aa17f | ||
|
|
35118da606 | ||
|
|
498477c5a2 | ||
|
|
3a84e4a0b4 | ||
|
|
4391457a96 | ||
|
|
68b4bc9d9e | ||
|
|
b052c9f562 | ||
|
|
a19597626b | ||
|
|
2147ddf8c7 | ||
|
|
412ab5dbbf | ||
|
|
4419bf02b1 | ||
|
|
8eb28a7724 | ||
|
|
d6c2dd3c26 | ||
|
|
ac42102320 | ||
|
|
2c6584baf5 | ||
|
|
c1ad39a072 | ||
|
|
d3e193cd71 | ||
|
|
11aed601b8 | ||
|
|
e0981323bd | ||
|
|
1e56325b33 | ||
|
|
a8a26f4ea8 | ||
|
|
a94eef3a60 | ||
|
|
b6f3a5d8b7 | ||
|
|
3322b04e00 | ||
|
|
47d0d3cfeb | ||
|
|
8255c02d5d | ||
|
|
8b4ba8dfe6 | ||
|
|
178a61b6fd | ||
|
|
b5df4cdf1d | ||
|
|
484f4b3aad | ||
|
|
22cb0da903 | ||
|
|
28cf3b7a9b | ||
|
|
84dd17187d | ||
|
|
110c892c3c | ||
|
|
8518424a88 | ||
|
|
7ba4dc0f1a | ||
|
|
95573bc106 | ||
|
|
a6adc29b14 | ||
|
|
8251513556 | ||
|
|
e6e0be3345 | ||
|
|
64da6d46e9 | ||
|
|
9b9a341db0 | ||
|
|
18217fbe10 | ||
|
|
c3e6a4178c | ||
|
|
2d4cd4ce7e | ||
|
|
0290ed5d23 | ||
|
|
b402e403a0 | ||
|
|
b2c22a02b1 | ||
|
|
7ed4922d28 | ||
|
|
21aa245cff | ||
|
|
94bedd83be | ||
|
|
c8f5a97cef | ||
|
|
fc7e55bfdc | ||
|
|
6aaaae5d0e | ||
|
|
e87a3b3916 | ||
|
|
3fb5b28930 | ||
|
|
1bb4be435c | ||
|
|
0990a9c2b0 | ||
|
|
086eb4b930 | ||
|
|
d107b37d3b | ||
|
|
80ab9485e0 | ||
|
|
28d30fdddb | ||
|
|
0bd8a7e1a0 | ||
|
|
2381ea7ff5 | ||
|
|
06101c54a5 | ||
|
|
ed332a034b | ||
|
|
a2bb9468ff | ||
|
|
d8387799d9 | ||
|
|
ae3cdeda99 | ||
|
|
9cd003034c | ||
|
|
f67a8d0d1f | ||
|
|
47223965b6 | ||
|
|
9128fc3120 | ||
|
|
c742db48b8 | ||
|
|
4bc1a02fc2 | ||
|
|
1d0f36ba35 | ||
|
|
405b26426c | ||
|
|
2295f57dec | ||
|
|
a011d5ea96 | ||
|
|
12027569d3 | ||
|
|
ab0e15e4bb | ||
|
|
9f7925c1d5 | ||
|
|
7449421cea | ||
|
|
4f20c1ceb1 | ||
|
|
452fcd56dd | ||
|
|
44fc156ef6 | ||
|
|
3ec05a5a13 | ||
|
|
e6a630ae64 | ||
|
|
3c8fd5c5c0 | ||
|
|
a635c82830 | ||
|
|
0b9fcf9ed3 | ||
|
|
2d8abbb2ac | ||
|
|
6d8142c539 | ||
|
|
50f27cdd77 | ||
|
|
6c809583d3 | ||
|
|
f41f0506c1 | ||
|
|
850f189225 | ||
|
|
a37d4b17eb | ||
|
|
7309a65167 | ||
|
|
7fde110ac5 | ||
|
|
9ed107bc33 | ||
|
|
b291f359ae | ||
|
|
161545584d | ||
|
|
3ea11d4e59 | ||
|
|
5713d69efe | ||
|
|
9d9f9c3c84 | ||
|
|
29d332af92 | ||
|
|
c44d095f8a | ||
|
|
0b996616b8 | ||
|
|
557b6a9ef0 | ||
|
|
6e633f7f3b | ||
|
|
c59455e445 | ||
|
|
32b3d27c7c | ||
|
|
88cb5d9671 | ||
|
|
56d1d928f9 | ||
|
|
e9cab96cb7 | ||
|
|
022c42f9ca | ||
|
|
25ef9aff04 | ||
|
|
71940acc49 | ||
|
|
05695affff | ||
|
|
f2308c77c6 | ||
|
|
a5368d1d95 | ||
|
|
b8d93f2150 | ||
|
|
ca574a1550 | ||
|
|
b0855b4a0e | ||
|
|
1fccddf67f | ||
|
|
548600b47a | ||
|
|
9bbfad4a08 | ||
|
|
8ae1f26b6a | ||
|
|
9b2a36a313 | ||
|
|
aa4b274b3c | ||
|
|
d1d5c942ec | ||
|
|
ccc9d95c9d | ||
|
|
6d9d593e12 | ||
|
|
fc9683f024 | ||
|
|
61ecfb5f2b | ||
|
|
7e75826064 | ||
|
|
8287484a36 | ||
|
|
dd8259b2ce | ||
|
|
449e478bec | ||
|
|
9ae19d53dc | ||
|
|
77b3ffee00 | ||
|
|
dff8dc0ed1 | ||
|
|
5a8b0f59b8 | ||
|
|
25a325da61 | ||
|
|
a6db5a2ed8 | ||
|
|
9e5dbb073a | ||
|
|
cf513975f3 | ||
|
|
1e70091a27 | ||
|
|
766826cc9c | ||
|
|
8a4ee19c0b | ||
|
|
b1f9c08f51 | ||
|
|
facf4777a4 | ||
|
|
096de869b6 | ||
|
|
c59314ec09 | ||
|
|
e94158a12f | ||
|
|
cb598c43e8 | ||
|
|
6e8817cbc4 | ||
|
|
9f06f046d6 | ||
|
|
44fa6bae95 | ||
|
|
bc9093127e | ||
|
|
cf93b66306 | ||
|
|
16ada4d477 | ||
|
|
c96d0015a0 | ||
|
|
12c6f5749e | ||
|
|
5f1eacf4ec | ||
|
|
5d384c77c5 | ||
|
|
32562ca991 | ||
|
|
6b4b1272db | ||
|
|
7b74f94360 | ||
|
|
997ab553c1 | ||
|
|
8b80fe9c6b | ||
|
|
ee65f2f0cd | ||
|
|
723ccd7547 | ||
|
|
102862e99d | ||
|
|
9cb265347c | ||
|
|
d84562f878 | ||
|
|
6bbee3cfc6 | ||
|
|
41e7ecb29f | ||
|
|
0196d6ee55 | ||
|
|
ea848db683 | ||
|
|
41a188ac35 | ||
|
|
8ddfeb2d38 | ||
|
|
d364432e3a | ||
|
|
2a6b192af8 | ||
|
|
07a581e13a | ||
|
|
5f5edf90e9 | ||
|
|
5e24469ce3 | ||
|
|
d7a1ac83ca | ||
|
|
f11abc7dbf | ||
|
|
ec05692f0d | ||
|
|
2e2e54811a | ||
|
|
c29aa61e2a | ||
|
|
cb3edfc654 | ||
|
|
6685fa1da9 | ||
|
|
d505f0316b | ||
|
|
9781073f2a | ||
|
|
0a0501c140 | ||
|
|
a6e9aa7876 | ||
|
|
ede7391582 | ||
|
|
5cfcf8d104 | ||
|
|
c34665f650 | ||
|
|
69867bf818 | ||
|
|
1d323c2d71 | ||
|
|
95a7356ea0 | ||
|
|
89b550b74a | ||
|
|
538c6544a0 | ||
|
|
98e3695c89 | ||
|
|
00fd4f2fdd | ||
|
|
2a5be725c8 | ||
|
|
c4d339a4c9 | ||
|
|
6409970f6e | ||
|
|
bc1af007b4 | ||
|
|
c3ffc1635d | ||
|
|
016eeef04a | ||
|
|
ace48464a8 | ||
|
|
64ba376330 | ||
|
|
d946ddabfd | ||
|
|
a57011b50b | ||
|
|
1240e7907e | ||
|
|
f0391f0213 | ||
|
|
3cc6810be5 | ||
|
|
916370a943 | ||
|
|
e2f45c590e | ||
|
|
7d21411ca4 | ||
|
|
56ed5af27d | ||
|
|
c8241b0122 | ||
|
|
30efd7fb07 | ||
|
|
1703343694 | ||
|
|
7fa04b6c25 | ||
|
|
61b6476de4 | ||
|
|
b4e5e0bc9b | ||
|
|
f9b4e0e489 | ||
|
|
9ccb100cca | ||
|
|
20b66cba23 | ||
|
|
b711172b33 | ||
|
|
0c4184b129 | ||
|
|
0e108c2178 | ||
|
|
2230e56347 | ||
|
|
2ff9535f5f | ||
|
|
ddb6c6cfd4 | ||
|
|
00ab64a3c7 | ||
|
|
e732f8469e | ||
|
|
023be88a87 | ||
|
|
25e60d62cf | ||
|
|
167a42aa27 | ||
|
|
0ef49a8b73 | ||
|
|
51259888bf | ||
|
|
0b7ff09657 | ||
|
|
f394cf3f4d | ||
|
|
4cb066bcbf | ||
|
|
e7001c5eea | ||
|
|
5bb9ffa0cb | ||
|
|
c6713eaccb | ||
|
|
087f521b19 | ||
|
|
763515d3a1 | ||
|
|
6d4f4d8d23 | ||
|
|
d7fd85e610 | ||
|
|
8acaa16987 | ||
|
|
4e8c02b396 | ||
|
|
a828abf53d | ||
|
|
ebfa0149cc | ||
|
|
3a4cca0027 | ||
|
|
7702a9ae85 | ||
|
|
2e8baa0493 | ||
|
|
69ec311d21 | ||
|
|
1cc18055ef | ||
|
|
bcc6bbbf5f | ||
|
|
71c3d4ca27 | ||
|
|
c6f3860f90 | ||
|
|
0049c8ad38 | ||
|
|
e1788bba45 | ||
|
|
4fcd263ea8 | ||
|
|
c81ce243cc | ||
|
|
7df41124b2 | ||
|
|
b5188731f8 | ||
|
|
0924281545 | ||
|
|
14e97642f7 | ||
|
|
544aaeaa66 | ||
|
|
7483bbd6ea | ||
|
|
2841b5fe44 | ||
|
|
ed435241b1 | ||
|
|
9847e51fbc | ||
|
|
dc320f455a | ||
|
|
1945e85ddc | ||
|
|
4313130f2e | ||
|
|
3c9ed126a6 | ||
|
|
d8c23d4fc9 | ||
|
|
1d96de986e | ||
|
|
e9e0712959 | ||
|
|
062840dd7b | ||
|
|
f0f01c081a | ||
|
|
dd855985be | ||
|
|
4633803d5d | ||
|
|
476d00d0e5 | ||
|
|
98cdef2568 | ||
|
|
bd3ecad3a1 | ||
|
|
dae7ba604a | ||
|
|
96c47f3b4d | ||
|
|
5b5cede5f9 | ||
|
|
c737033cc4 | ||
|
|
0194bfd9ea | ||
|
|
339399eb70 | ||
|
|
a615b28ecf | ||
|
|
468bd7b04f | ||
|
|
4c16afeacb | ||
|
|
d86c05bfe6 | ||
|
|
63464a13c3 | ||
|
|
20e43398fa | ||
|
|
2e7cdad532 | ||
|
|
636cccc6a3 | ||
|
|
93e2a1bd1a | ||
|
|
dd3beb5841 | ||
|
|
97af01c5ed | ||
|
|
7e362cdafc | ||
|
|
890d531cea | ||
|
|
6a6c6b648f | ||
|
|
d0673122a8 | ||
|
|
5cbef3ef95 | ||
|
|
a1e0121330 | ||
|
|
eb050ff13e | ||
|
|
45e61f100c | ||
|
|
5e99dd14ae | ||
|
|
a6097f4a0f | ||
|
|
f243e609a5 | ||
|
|
be0bf1e5b9 | ||
|
|
a275f37135 | ||
|
|
e9f2d75aab | ||
|
|
34067f8b15 | ||
|
|
bdc087148a | ||
|
|
5a2e0612a8 | ||
|
|
f1fecd22e3 | ||
|
|
0919017d49 | ||
|
|
963f594c9e | ||
|
|
4f70663658 | ||
|
|
958a35e262 | ||
|
|
0c566c6f00 | ||
|
|
ba73289b28 | ||
|
|
99f7e9aeb7 | ||
|
|
ebb6babb3d | ||
|
|
0922f240e0 | ||
|
|
c8fb694dcb | ||
|
|
6054685dae | ||
|
|
61ec3508ed | ||
|
|
086ec770ea | ||
|
|
b055f5b4bf | ||
|
|
726db746c8 | ||
|
|
1922455fa7 | ||
|
|
dc943d7e7a | ||
|
|
ffefa8929e | ||
|
|
7d5342bca5 |
@@ -1 +1 @@
|
||||
4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9
|
||||
d272a88e8ca28ae9340a9a03295a566432a52cb696501908f57764475bf7ca65
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.10
|
||||
rev: v0.14.11
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -91,6 +91,7 @@ esphome/components/bmp3xx_spi/* @latonita
|
||||
esphome/components/bmp581/* @kahrendt
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/bthome_mithermometer/* @nagyrobi
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/bytebuffer/* @clydebarrow
|
||||
esphome/components/camera/* @bdraco @DT-art1
|
||||
@@ -134,7 +135,7 @@ esphome/components/display_menu_base/* @numo68
|
||||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/ds2484/* @mrk-its
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/dsmr/* @glmnet @PolarGoose @zuidwijk
|
||||
esphome/components/duty_time/* @dudanov
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/touchscreen/* @jesserockz
|
||||
@@ -394,6 +395,7 @@ esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
esphome/components/rd03d/* @jasstrong
|
||||
esphome/components/resampler/speaker/* @kahrendt
|
||||
esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
@@ -574,5 +576,6 @@ esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
|
||||
esphome/components/xxtea/* @clydebarrow
|
||||
esphome/components/zephyr/* @tomaszduda23
|
||||
esphome/components/zhlt01/* @cfeenstra1024
|
||||
esphome/components/zigbee/* @tomaszduda23
|
||||
esphome/components/zio_ultrasonic/* @kahrendt
|
||||
esphome/components/zwave_proxy/* @kbx81
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include requirements.txt
|
||||
recursive-include esphome *.yaml
|
||||
recursive-include esphome *.cpp *.h *.tcc *.c
|
||||
recursive-include esphome *.py.script
|
||||
recursive-include esphome LICENSE.txt
|
||||
|
||||
@@ -11,6 +11,16 @@ FROM base-source-${BUILD_TYPE} AS base
|
||||
|
||||
RUN git config --system --add safe.directory "*"
|
||||
|
||||
# Install build tools for Python packages that require compilation
|
||||
# (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager)
|
||||
RUN if command -v apk > /dev/null; then \
|
||||
apk add --no-cache build-base; \
|
||||
else \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
|
||||
RUN pip install --no-cache-dir -U pip uv==0.6.14
|
||||
|
||||
@@ -62,6 +62,9 @@ from esphome.util import (
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Maximum buffer size for serial log reading to prevent unbounded memory growth
|
||||
SERIAL_BUFFER_MAX_SIZE = 65536
|
||||
|
||||
# Special non-component keys that appear in configs
|
||||
_NON_COMPONENT_KEYS = frozenset(
|
||||
{
|
||||
@@ -431,25 +434,37 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||
while tries < 5:
|
||||
try:
|
||||
with ser:
|
||||
buffer = b""
|
||||
ser.timeout = 0.1 # 100ms timeout for non-blocking reads
|
||||
while True:
|
||||
try:
|
||||
raw = ser.readline()
|
||||
# Read all available data and timestamp it
|
||||
chunk = ser.read(ser.in_waiting or 1)
|
||||
if not chunk:
|
||||
continue
|
||||
time_ = datetime.now()
|
||||
milliseconds = time_.microsecond // 1000
|
||||
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{milliseconds:03}]"
|
||||
|
||||
# Add to buffer and process complete lines
|
||||
# Limit buffer size to prevent unbounded memory growth
|
||||
# if device sends data without newlines
|
||||
buffer += chunk
|
||||
if len(buffer) > SERIAL_BUFFER_MAX_SIZE:
|
||||
buffer = buffer[-SERIAL_BUFFER_MAX_SIZE:]
|
||||
while b"\n" in buffer:
|
||||
raw_line, buffer = buffer.split(b"\n", 1)
|
||||
line = raw_line.replace(b"\r", b"").decode(
|
||||
"utf8", "backslashreplace"
|
||||
)
|
||||
safe_print(parser.parse_line(line, time_str))
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return 0
|
||||
line = (
|
||||
raw.replace(b"\r", b"")
|
||||
.replace(b"\n", b"")
|
||||
.decode("utf8", "backslashreplace")
|
||||
)
|
||||
time_ = datetime.now()
|
||||
nanoseconds = time_.microsecond // 1000
|
||||
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
|
||||
safe_print(parser.parse_line(line, time_str))
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
except serial.SerialException:
|
||||
tries += 1
|
||||
time.sleep(1)
|
||||
@@ -789,7 +804,13 @@ def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
exit_code = compile_program(args, config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
if CORE.is_host:
|
||||
from esphome.platformio_api import get_idedata
|
||||
|
||||
program_path = str(get_idedata(config).firmware_elf_path)
|
||||
_LOGGER.info("Successfully compiled program to path '%s'", program_path)
|
||||
else:
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -839,10 +860,8 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
if CORE.is_host:
|
||||
from esphome.platformio_api import get_idedata
|
||||
|
||||
idedata = get_idedata(config)
|
||||
if idedata is None:
|
||||
return 1
|
||||
program_path = idedata.raw["prog_path"]
|
||||
program_path = str(get_idedata(config).firmware_elf_path)
|
||||
_LOGGER.info("Running program from path '%s'", program_path)
|
||||
return run_external_process(program_path)
|
||||
|
||||
# Get devices, resolving special identifiers like OTA
|
||||
@@ -1013,6 +1032,7 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
idedata.objdump_path,
|
||||
idedata.readelf_path,
|
||||
external_components,
|
||||
idedata=idedata,
|
||||
)
|
||||
analyzer.analyze()
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from .helpers import (
|
||||
map_section_name,
|
||||
parse_symbol_line,
|
||||
)
|
||||
from .toolchain import find_tool, run_tool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from esphome.platformio_api import IDEData
|
||||
@@ -53,6 +54,9 @@ _NAMESPACE_STD = "std::"
|
||||
# Type alias for symbol information: (symbol_name, size, component)
|
||||
SymbolInfoType = tuple[str, int, str]
|
||||
|
||||
# RAM sections - symbols in these sections consume RAM
|
||||
RAM_SECTIONS = frozenset([".data", ".bss"])
|
||||
|
||||
|
||||
@dataclass
|
||||
class MemorySection:
|
||||
@@ -60,7 +64,20 @@ class MemorySection:
|
||||
|
||||
name: str
|
||||
symbols: list[SymbolInfoType] = field(default_factory=list)
|
||||
total_size: int = 0
|
||||
total_size: int = 0 # Actual section size from ELF headers
|
||||
symbol_size: int = 0 # Sum of symbol sizes (may be less than total_size)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SDKSymbol:
|
||||
"""Represents a symbol from an SDK library that's not in the ELF symbol table."""
|
||||
|
||||
name: str
|
||||
size: int
|
||||
library: str # Name of the .a file (e.g., "libpp.a")
|
||||
section: str # ".bss" or ".data"
|
||||
is_local: bool # True if static/local symbol (lowercase in nm output)
|
||||
demangled: str = "" # Demangled name (populated after analysis)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -118,6 +135,10 @@ class MemoryAnalyzer:
|
||||
self.objdump_path = objdump_path or "objdump"
|
||||
self.readelf_path = readelf_path or "readelf"
|
||||
self.external_components = external_components or set()
|
||||
self._idedata = idedata
|
||||
|
||||
# Derive nm path from objdump path using shared toolchain utility
|
||||
self.nm_path = find_tool("nm", self.objdump_path)
|
||||
|
||||
self.sections: dict[str, MemorySection] = {}
|
||||
self.components: dict[str, ComponentMemory] = defaultdict(
|
||||
@@ -128,15 +149,25 @@ class MemoryAnalyzer:
|
||||
self._esphome_core_symbols: list[
|
||||
tuple[str, str, int]
|
||||
] = [] # Track core symbols
|
||||
self._component_symbols: dict[str, list[tuple[str, str, int]]] = defaultdict(
|
||||
# Track symbols for all components: (symbol_name, demangled, size, section)
|
||||
self._component_symbols: dict[str, list[tuple[str, str, int, str]]] = (
|
||||
defaultdict(list)
|
||||
)
|
||||
# Track RAM symbols separately for detailed analysis: (symbol_name, demangled, size, section)
|
||||
self._ram_symbols: dict[str, list[tuple[str, str, int, str]]] = defaultdict(
|
||||
list
|
||||
) # Track symbols for all components
|
||||
)
|
||||
# Track ELF symbol names for SDK cross-reference
|
||||
self._elf_symbol_names: set[str] = set()
|
||||
# SDK symbols not in ELF (static/local symbols from closed-source libs)
|
||||
self._sdk_symbols: list[SDKSymbol] = []
|
||||
|
||||
def analyze(self) -> dict[str, ComponentMemory]:
|
||||
"""Analyze the ELF file and return component memory usage."""
|
||||
self._parse_sections()
|
||||
self._parse_symbols()
|
||||
self._categorize_symbols()
|
||||
self._analyze_sdk_libraries()
|
||||
return dict(self.components)
|
||||
|
||||
def _parse_sections(self) -> None:
|
||||
@@ -190,6 +221,8 @@ class MemoryAnalyzer:
|
||||
continue
|
||||
|
||||
self.sections[section].symbols.append((name, size, ""))
|
||||
self.sections[section].symbol_size += size
|
||||
self._elf_symbol_names.add(name)
|
||||
seen_addresses.add(address)
|
||||
|
||||
def _categorize_symbols(self) -> None:
|
||||
@@ -233,8 +266,13 @@ class MemoryAnalyzer:
|
||||
if size > 0:
|
||||
demangled = self._demangle_symbol(symbol_name)
|
||||
self._component_symbols[component].append(
|
||||
(symbol_name, demangled, size)
|
||||
(symbol_name, demangled, size, section_name)
|
||||
)
|
||||
# Track RAM symbols separately for detailed RAM analysis
|
||||
if section_name in RAM_SECTIONS:
|
||||
self._ram_symbols[component].append(
|
||||
(symbol_name, demangled, size, section_name)
|
||||
)
|
||||
|
||||
def _identify_component(self, symbol_name: str) -> str:
|
||||
"""Identify which component a symbol belongs to."""
|
||||
@@ -328,6 +366,247 @@ class MemoryAnalyzer:
|
||||
|
||||
return "Other Core"
|
||||
|
||||
def get_unattributed_ram(self) -> tuple[int, int, int]:
|
||||
"""Get unattributed RAM sizes (SDK/framework overhead).
|
||||
|
||||
Returns:
|
||||
Tuple of (unattributed_bss, unattributed_data, total_unattributed)
|
||||
These are bytes in RAM sections that have no corresponding symbols.
|
||||
"""
|
||||
bss_section = self.sections.get(".bss")
|
||||
data_section = self.sections.get(".data")
|
||||
|
||||
unattributed_bss = 0
|
||||
unattributed_data = 0
|
||||
|
||||
if bss_section:
|
||||
unattributed_bss = max(0, bss_section.total_size - bss_section.symbol_size)
|
||||
if data_section:
|
||||
unattributed_data = max(
|
||||
0, data_section.total_size - data_section.symbol_size
|
||||
)
|
||||
|
||||
return unattributed_bss, unattributed_data, unattributed_bss + unattributed_data
|
||||
|
||||
def _find_sdk_library_dirs(self) -> list[Path]:
|
||||
"""Find SDK library directories based on platform.
|
||||
|
||||
Returns:
|
||||
List of paths to SDK library directories containing .a files.
|
||||
"""
|
||||
sdk_dirs: list[Path] = []
|
||||
|
||||
if self._idedata is None:
|
||||
return sdk_dirs
|
||||
|
||||
# Get the CC path to determine the framework location
|
||||
cc_path = getattr(self._idedata, "cc_path", None)
|
||||
if not cc_path:
|
||||
return sdk_dirs
|
||||
|
||||
cc_path = Path(cc_path)
|
||||
|
||||
# For ESP8266 Arduino framework
|
||||
# CC is like: ~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc
|
||||
# SDK libs are in: ~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib/
|
||||
if "xtensa-lx106" in str(cc_path):
|
||||
platformio_dir = cc_path.parent.parent.parent
|
||||
esp8266_sdk = (
|
||||
platformio_dir
|
||||
/ "framework-arduinoespressif8266"
|
||||
/ "tools"
|
||||
/ "sdk"
|
||||
/ "lib"
|
||||
)
|
||||
if esp8266_sdk.exists():
|
||||
sdk_dirs.append(esp8266_sdk)
|
||||
# Also check for NONOSDK subdirectories (closed-source libs)
|
||||
sdk_dirs.extend(
|
||||
subdir
|
||||
for subdir in esp8266_sdk.iterdir()
|
||||
if subdir.is_dir() and subdir.name.startswith("NONOSDK")
|
||||
)
|
||||
|
||||
# For ESP32 IDF framework
|
||||
# CC is like: ~/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32-elf-gcc
|
||||
# or: ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-gcc
|
||||
elif "xtensa-esp" in str(cc_path) or "riscv32-esp" in str(cc_path):
|
||||
# Detect ESP32 variant from CC path or defines
|
||||
variant = self._detect_esp32_variant()
|
||||
if variant:
|
||||
platformio_dir = cc_path.parent.parent.parent
|
||||
espidf_dir = platformio_dir / "framework-espidf" / "components"
|
||||
if espidf_dir.exists():
|
||||
# Find all directories named after the variant that contain .a files
|
||||
# This handles various ESP-IDF library layouts:
|
||||
# - components/*/lib/<variant>/
|
||||
# - components/*/<variant>/
|
||||
# - components/*/lib/lib/<variant>/
|
||||
# - components/*/*/lib_*/<variant>/
|
||||
sdk_dirs.extend(
|
||||
variant_dir
|
||||
for variant_dir in espidf_dir.rglob(variant)
|
||||
if variant_dir.is_dir() and any(variant_dir.glob("*.a"))
|
||||
)
|
||||
|
||||
return sdk_dirs
|
||||
|
||||
def _detect_esp32_variant(self) -> str | None:
|
||||
"""Detect ESP32 variant from idedata defines.
|
||||
|
||||
Returns:
|
||||
Variant string like 'esp32', 'esp32s2', 'esp32c3', etc. or None.
|
||||
"""
|
||||
if self._idedata is None:
|
||||
return None
|
||||
|
||||
defines = getattr(self._idedata, "defines", [])
|
||||
if not defines:
|
||||
return None
|
||||
|
||||
# ESPHome always adds USE_ESP32_VARIANT_xxx defines
|
||||
variant_prefix = "USE_ESP32_VARIANT_"
|
||||
for define in defines:
|
||||
if define.startswith(variant_prefix):
|
||||
# Extract variant name and convert to lowercase
|
||||
# USE_ESP32_VARIANT_ESP32 -> esp32
|
||||
# USE_ESP32_VARIANT_ESP32S3 -> esp32s3
|
||||
return define[len(variant_prefix) :].lower()
|
||||
|
||||
return None
|
||||
|
||||
def _parse_sdk_library(
|
||||
self, lib_path: Path
|
||||
) -> tuple[list[tuple[str, int, str, bool]], set[str]]:
|
||||
"""Parse a single SDK library for symbols.
|
||||
|
||||
Args:
|
||||
lib_path: Path to the .a library file
|
||||
|
||||
Returns:
|
||||
Tuple of:
|
||||
- List of BSS/DATA symbols: (symbol_name, size, section, is_local)
|
||||
- Set of global BSS/DATA symbol names (for checking if RAM is linked)
|
||||
"""
|
||||
ram_symbols: list[tuple[str, int, str, bool]] = []
|
||||
global_ram_symbols: set[str] = set()
|
||||
|
||||
result = run_tool([self.nm_path, "--size-sort", str(lib_path)], timeout=10)
|
||||
if result is None:
|
||||
return ram_symbols, global_ram_symbols
|
||||
|
||||
for line in result.stdout.splitlines():
|
||||
parts = line.split()
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
|
||||
try:
|
||||
size = int(parts[0], 16)
|
||||
sym_type = parts[1]
|
||||
name = parts[2]
|
||||
|
||||
# Only collect BSS (b/B) and DATA (d/D) for RAM analysis
|
||||
if sym_type in ("b", "B"):
|
||||
section = ".bss"
|
||||
is_local = sym_type == "b"
|
||||
ram_symbols.append((name, size, section, is_local))
|
||||
# Track global RAM symbols (B/D) for linking check
|
||||
if sym_type == "B":
|
||||
global_ram_symbols.add(name)
|
||||
elif sym_type in ("d", "D"):
|
||||
section = ".data"
|
||||
is_local = sym_type == "d"
|
||||
ram_symbols.append((name, size, section, is_local))
|
||||
if sym_type == "D":
|
||||
global_ram_symbols.add(name)
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
return ram_symbols, global_ram_symbols
|
||||
|
||||
def _analyze_sdk_libraries(self) -> None:
|
||||
"""Analyze SDK libraries to find symbols not in the ELF.
|
||||
|
||||
This finds static/local symbols from closed-source SDK libraries
|
||||
that consume RAM but don't appear in the final ELF symbol table.
|
||||
Only includes symbols from libraries that have RAM actually linked
|
||||
(at least one global BSS/DATA symbol in the ELF).
|
||||
"""
|
||||
sdk_dirs = self._find_sdk_library_dirs()
|
||||
if not sdk_dirs:
|
||||
_LOGGER.debug("No SDK library directories found")
|
||||
return
|
||||
|
||||
_LOGGER.debug("Analyzing SDK libraries in %d directories", len(sdk_dirs))
|
||||
|
||||
# Track seen symbols to avoid duplicates from multiple SDK versions
|
||||
seen_symbols: set[str] = set()
|
||||
|
||||
for sdk_dir in sdk_dirs:
|
||||
for lib_path in sorted(sdk_dir.glob("*.a")):
|
||||
lib_name = lib_path.name
|
||||
ram_symbols, global_ram_symbols = self._parse_sdk_library(lib_path)
|
||||
|
||||
# Check if this library's RAM is actually linked by seeing if any
|
||||
# of its global BSS/DATA symbols appear in the ELF
|
||||
if not global_ram_symbols & self._elf_symbol_names:
|
||||
# No RAM from this library is in the ELF - skip it
|
||||
continue
|
||||
|
||||
for name, size, section, is_local in ram_symbols:
|
||||
# Skip if already in ELF or already seen from another lib
|
||||
if name in self._elf_symbol_names or name in seen_symbols:
|
||||
continue
|
||||
|
||||
# Only track symbols with non-zero size
|
||||
if size > 0:
|
||||
self._sdk_symbols.append(
|
||||
SDKSymbol(
|
||||
name=name,
|
||||
size=size,
|
||||
library=lib_name,
|
||||
section=section,
|
||||
is_local=is_local,
|
||||
)
|
||||
)
|
||||
seen_symbols.add(name)
|
||||
|
||||
# Demangle SDK symbols for better readability
|
||||
if self._sdk_symbols:
|
||||
sdk_names = [sym.name for sym in self._sdk_symbols]
|
||||
demangled_map = batch_demangle(sdk_names, objdump_path=self.objdump_path)
|
||||
for sym in self._sdk_symbols:
|
||||
sym.demangled = demangled_map.get(sym.name, sym.name)
|
||||
|
||||
# Sort by size descending for reporting
|
||||
self._sdk_symbols.sort(key=lambda s: s.size, reverse=True)
|
||||
|
||||
total_sdk_ram = sum(s.size for s in self._sdk_symbols)
|
||||
_LOGGER.debug(
|
||||
"Found %d SDK symbols not in ELF, totaling %d bytes",
|
||||
len(self._sdk_symbols),
|
||||
total_sdk_ram,
|
||||
)
|
||||
|
||||
def get_sdk_ram_symbols(self) -> list[SDKSymbol]:
|
||||
"""Get SDK symbols that consume RAM but aren't in the ELF symbol table.
|
||||
|
||||
Returns:
|
||||
List of SDKSymbol objects sorted by size descending.
|
||||
"""
|
||||
return self._sdk_symbols
|
||||
|
||||
def get_sdk_ram_by_library(self) -> dict[str, list[SDKSymbol]]:
|
||||
"""Get SDK RAM symbols grouped by library.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping library name to list of symbols.
|
||||
"""
|
||||
by_lib: dict[str, list[SDKSymbol]] = defaultdict(list)
|
||||
for sym in self._sdk_symbols:
|
||||
by_lib[sym.library].append(sym)
|
||||
return dict(by_lib)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .cli import main
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
"""CLI interface for memory analysis with report generation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from . import (
|
||||
_COMPONENT_API,
|
||||
_COMPONENT_CORE,
|
||||
_COMPONENT_PREFIX_ESPHOME,
|
||||
_COMPONENT_PREFIX_EXTERNAL,
|
||||
RAM_SECTIONS,
|
||||
MemoryAnalyzer,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ComponentMemory
|
||||
|
||||
|
||||
class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
"""Memory analyzer with CLI-specific report generation."""
|
||||
@@ -19,6 +27,8 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
SYMBOL_SIZE_THRESHOLD: int = (
|
||||
100 # Show symbols larger than this in detailed analysis
|
||||
)
|
||||
# Lower threshold for RAM symbols (RAM is more constrained)
|
||||
RAM_SYMBOL_SIZE_THRESHOLD: int = 24
|
||||
|
||||
# Column width constants
|
||||
COL_COMPONENT: int = 29
|
||||
@@ -83,6 +93,60 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
COL_CORE_PERCENT,
|
||||
)
|
||||
|
||||
def _add_section_header(self, lines: list[str], title: str) -> None:
|
||||
"""Add a section header with title centered between separator lines."""
|
||||
lines.append("")
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append(title.center(self.TABLE_WIDTH))
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append("")
|
||||
|
||||
def _add_top_consumers(
|
||||
self,
|
||||
lines: list[str],
|
||||
title: str,
|
||||
components: list[tuple[str, ComponentMemory]],
|
||||
get_size: Callable[[ComponentMemory], int],
|
||||
total: int,
|
||||
memory_type: str,
|
||||
limit: int = 25,
|
||||
) -> None:
|
||||
"""Add a formatted list of top memory consumers to the report.
|
||||
|
||||
Args:
|
||||
lines: List of report lines to append the output to.
|
||||
title: Section title to print before the list.
|
||||
components: Sequence of (name, ComponentMemory) tuples to analyze.
|
||||
get_size: Callable that takes a ComponentMemory and returns the
|
||||
size in bytes to use for ranking and display.
|
||||
total: Total size in bytes for computing percentage usage.
|
||||
memory_type: Label for the memory region (e.g., "flash" or "RAM").
|
||||
limit: Maximum number of components to include in the list.
|
||||
"""
|
||||
lines.append("")
|
||||
lines.append(f"{title}:")
|
||||
for i, (name, mem) in enumerate(components[:limit]):
|
||||
size = get_size(mem)
|
||||
if size > 0:
|
||||
percentage = (size / total * 100) if total > 0 else 0
|
||||
lines.append(
|
||||
f"{i + 1}. {name} ({size:,} B) - {percentage:.1f}% of analyzed {memory_type}"
|
||||
)
|
||||
|
||||
def _format_symbol_with_section(
|
||||
self, demangled: str, size: int, section: str | None = None
|
||||
) -> str:
|
||||
"""Format a symbol entry, optionally adding a RAM section label.
|
||||
|
||||
If section is one of the RAM sections (.data or .bss), a label like
|
||||
" [data]" or " [bss]" is appended. For non-RAM sections or when
|
||||
section is None, no section label is added.
|
||||
"""
|
||||
section_label = ""
|
||||
if section in RAM_SECTIONS:
|
||||
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
|
||||
return f"{demangled} ({size:,} B){section_label}"
|
||||
|
||||
def generate_report(self, detailed: bool = False) -> str:
|
||||
"""Generate a formatted memory report."""
|
||||
components = sorted(
|
||||
@@ -123,43 +187,70 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
f"{total_flash:>{self.COL_TOTAL_FLASH - 2},} B | {total_ram:>{self.COL_TOTAL_RAM - 2},} B"
|
||||
)
|
||||
|
||||
# Top consumers
|
||||
lines.append("")
|
||||
lines.append("Top Flash Consumers:")
|
||||
for i, (name, mem) in enumerate(components[:25]):
|
||||
if mem.flash_total > 0:
|
||||
percentage = (
|
||||
(mem.flash_total / total_flash * 100) if total_flash > 0 else 0
|
||||
)
|
||||
lines.append(
|
||||
f"{i + 1}. {name} ({mem.flash_total:,} B) - {percentage:.1f}% of analyzed flash"
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append("Top RAM Consumers:")
|
||||
ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True)
|
||||
for i, (name, mem) in enumerate(ram_components[:25]):
|
||||
if mem.ram_total > 0:
|
||||
percentage = (mem.ram_total / total_ram * 100) if total_ram > 0 else 0
|
||||
lines.append(
|
||||
f"{i + 1}. {name} ({mem.ram_total:,} B) - {percentage:.1f}% of analyzed RAM"
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
|
||||
# Show unattributed RAM (SDK/framework overhead)
|
||||
unattributed_bss, unattributed_data, unattributed_total = (
|
||||
self.get_unattributed_ram()
|
||||
)
|
||||
if unattributed_total > 0:
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"Unattributed RAM: {unattributed_total:,} B (SDK/framework overhead)"
|
||||
)
|
||||
if unattributed_bss > 0 and unattributed_data > 0:
|
||||
lines.append(
|
||||
f" .bss: {unattributed_bss:,} B | .data: {unattributed_data:,} B"
|
||||
)
|
||||
|
||||
# Show SDK symbol breakdown if available
|
||||
sdk_by_lib = self.get_sdk_ram_by_library()
|
||||
if sdk_by_lib:
|
||||
lines.append("")
|
||||
lines.append("SDK library breakdown (static symbols not in ELF):")
|
||||
# Sort libraries by total size
|
||||
lib_totals = [
|
||||
(lib, sum(s.size for s in syms), syms)
|
||||
for lib, syms in sdk_by_lib.items()
|
||||
]
|
||||
lib_totals.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
for lib_name, lib_total, syms in lib_totals:
|
||||
if lib_total == 0:
|
||||
continue
|
||||
lines.append(f" {lib_name}: {lib_total:,} B")
|
||||
# Show top symbols from this library
|
||||
for sym in sorted(syms, key=lambda s: s.size, reverse=True)[:3]:
|
||||
section_label = sym.section.lstrip(".")
|
||||
# Use demangled name (falls back to original if not demangled)
|
||||
display_name = sym.demangled or sym.name
|
||||
if len(display_name) > 50:
|
||||
display_name = f"{display_name[:47]}..."
|
||||
lines.append(
|
||||
f" {sym.size:>6,} B [{section_label}] {display_name}"
|
||||
)
|
||||
|
||||
# Top consumers
|
||||
self._add_top_consumers(
|
||||
lines,
|
||||
"Top Flash Consumers",
|
||||
components,
|
||||
lambda m: m.flash_total,
|
||||
total_flash,
|
||||
"flash",
|
||||
)
|
||||
|
||||
ram_components = sorted(components, key=lambda x: x[1].ram_total, reverse=True)
|
||||
self._add_top_consumers(
|
||||
lines,
|
||||
"Top RAM Consumers",
|
||||
ram_components,
|
||||
lambda m: m.ram_total,
|
||||
total_ram,
|
||||
"RAM",
|
||||
)
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
|
||||
# Add ESPHome core detailed analysis if there are core symbols
|
||||
if self._esphome_core_symbols:
|
||||
lines.append("")
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append(
|
||||
f"{_COMPONENT_CORE} Detailed Analysis".center(self.TABLE_WIDTH)
|
||||
)
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append("")
|
||||
self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis")
|
||||
|
||||
# Group core symbols by subcategory
|
||||
core_subcategories: dict[str, list[tuple[str, str, int]]] = defaultdict(
|
||||
@@ -211,7 +302,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):"
|
||||
)
|
||||
for i, (symbol, demangled, size) in enumerate(large_core_symbols):
|
||||
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
|
||||
# Core symbols only track (symbol, demangled, size) without section info,
|
||||
# so we don't show section labels here
|
||||
lines.append(
|
||||
f"{i + 1}. {self._format_symbol_with_section(demangled, size)}"
|
||||
)
|
||||
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
|
||||
@@ -267,11 +362,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
for comp_name, comp_mem in components_to_analyze:
|
||||
if not (comp_symbols := self._component_symbols.get(comp_name, [])):
|
||||
continue
|
||||
lines.append("")
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append(f"{comp_name} Detailed Analysis".center(self.TABLE_WIDTH))
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
lines.append("")
|
||||
self._add_section_header(lines, f"{comp_name} Detailed Analysis")
|
||||
|
||||
# Sort symbols by size
|
||||
sorted_symbols = sorted(comp_symbols, key=lambda x: x[2], reverse=True)
|
||||
@@ -282,19 +373,69 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
|
||||
# Show all symbols above threshold for better visibility
|
||||
large_symbols = [
|
||||
(sym, dem, size)
|
||||
for sym, dem, size in sorted_symbols
|
||||
(sym, dem, size, sec)
|
||||
for sym, dem, size, sec in sorted_symbols
|
||||
if size > self.SYMBOL_SIZE_THRESHOLD
|
||||
]
|
||||
|
||||
lines.append(
|
||||
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):"
|
||||
)
|
||||
for i, (symbol, demangled, size) in enumerate(large_symbols):
|
||||
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
|
||||
for i, (symbol, demangled, size, section) in enumerate(large_symbols):
|
||||
lines.append(
|
||||
f"{i + 1}. {self._format_symbol_with_section(demangled, size, section)}"
|
||||
)
|
||||
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
|
||||
# Detailed RAM analysis by component (at end, before RAM strings analysis)
|
||||
self._add_section_header(lines, "RAM Symbol Analysis by Component")
|
||||
|
||||
# Show top 15 RAM consumers with their large symbols
|
||||
for name, mem in ram_components[:15]:
|
||||
if mem.ram_total == 0:
|
||||
continue
|
||||
ram_syms = self._ram_symbols.get(name, [])
|
||||
if not ram_syms:
|
||||
continue
|
||||
|
||||
# Sort by size descending
|
||||
sorted_ram_syms = sorted(ram_syms, key=lambda x: x[2], reverse=True)
|
||||
large_ram_syms = [
|
||||
s for s in sorted_ram_syms if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD
|
||||
]
|
||||
|
||||
lines.append(f"{name} ({mem.ram_total:,} B total RAM):")
|
||||
|
||||
# Show breakdown by section type
|
||||
data_size = sum(s[2] for s in ram_syms if s[3] == ".data")
|
||||
bss_size = sum(s[2] for s in ram_syms if s[3] == ".bss")
|
||||
lines.append(f" .data (initialized): {data_size:,} B")
|
||||
lines.append(f" .bss (uninitialized): {bss_size:,} B")
|
||||
|
||||
if large_ram_syms:
|
||||
lines.append(
|
||||
f" Symbols > {self.RAM_SYMBOL_SIZE_THRESHOLD} B ({len(large_ram_syms)}):"
|
||||
)
|
||||
for symbol, demangled, size, section in large_ram_syms[:10]:
|
||||
# Format section label consistently by stripping leading dot
|
||||
section_label = section.lstrip(".") if section else ""
|
||||
# Add ellipsis if name is truncated
|
||||
demangled_display = (
|
||||
f"{demangled[:70]}..." if len(demangled) > 70 else demangled
|
||||
)
|
||||
lines.append(
|
||||
f" {size:>6,} B [{section_label}] {demangled_display}"
|
||||
)
|
||||
if len(large_ram_syms) > 10:
|
||||
lines.append(f" ... and {len(large_ram_syms) - 10} more")
|
||||
lines.append("")
|
||||
|
||||
lines.append(
|
||||
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
|
||||
)
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def dump_uncategorized_symbols(self, output_file: str | None = None) -> None:
|
||||
|
||||
@@ -7,11 +7,13 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::")
|
||||
|
||||
# Section mapping for ELF file sections
|
||||
# Maps standard section names to their various platform-specific variants
|
||||
# Note: Order matters! More specific patterns (.bss) must come before general ones (.dram)
|
||||
# because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise
|
||||
SECTION_MAPPING = {
|
||||
".text": frozenset([".text", ".iram"]),
|
||||
".rodata": frozenset([".rodata"]),
|
||||
".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss"
|
||||
".data": frozenset([".data", ".dram"]),
|
||||
".bss": frozenset([".bss"]),
|
||||
}
|
||||
|
||||
# Section to ComponentMemory attribute mapping
|
||||
@@ -88,6 +90,77 @@ SYMBOL_PATTERNS = {
|
||||
"sys_mbox_new",
|
||||
"sys_arch_mbox_tryfetch",
|
||||
],
|
||||
# LibreTiny/Beken BK7231 radio calibration
|
||||
"bk_radio_cal": [
|
||||
"bk7011_",
|
||||
"calibration_main",
|
||||
"gcali_",
|
||||
"rwnx_cal",
|
||||
],
|
||||
# LibreTiny/Beken WiFi MAC layer
|
||||
"bk_wifi_mac": [
|
||||
"rxu_", # RX upper layer
|
||||
"txu_", # TX upper layer
|
||||
"txl_", # TX lower layer
|
||||
"rxl_", # RX lower layer
|
||||
"scanu_", # Scan unit
|
||||
"mm_hw_", # MAC management hardware
|
||||
"mm_bcn", # MAC management beacon
|
||||
"mm_tim", # MAC management TIM
|
||||
"mm_check", # MAC management checks
|
||||
"sm_connect", # Station management
|
||||
"me_beacon", # Management entity beacon
|
||||
"me_build", # Management entity build
|
||||
"hapd_", # Host AP daemon
|
||||
"chan_pre_", # Channel management
|
||||
"handle_probe_", # Probe handling
|
||||
],
|
||||
# LibreTiny/Beken system control
|
||||
"bk_system": [
|
||||
"sctrl_", # System control
|
||||
"icu_ctrl", # Interrupt control unit
|
||||
"gdma_ctrl", # DMA control
|
||||
"mpb_ctrl", # MPB control
|
||||
"uf2_", # UF2 OTA
|
||||
"bkreg_", # Beken registers
|
||||
],
|
||||
# LibreTiny/Beken BLE stack
|
||||
"bk_ble": [
|
||||
"gapc_", # GAP client
|
||||
"gattc_", # GATT client
|
||||
"attc_", # ATT client
|
||||
"attmdb_", # ATT database
|
||||
"atts_", # ATT server
|
||||
"l2cc_", # L2CAP
|
||||
"prf_env", # Profile environment
|
||||
],
|
||||
# LibreTiny/Beken scheduler
|
||||
"bk_scheduler": [
|
||||
"sch_plan_", # Scheduler plan
|
||||
"sch_prog_", # Scheduler program
|
||||
"sch_arb_", # Scheduler arbiter
|
||||
],
|
||||
# LibreTiny/Beken DMA descriptors
|
||||
"bk_dma": [
|
||||
"rx_payload_desc",
|
||||
"rx_dma_hdrdesc",
|
||||
"tx_hw_desc",
|
||||
"host_event_data",
|
||||
"host_cmd_data",
|
||||
],
|
||||
# ARM EABI compiler runtime (LibreTiny uses ARM Cortex-M)
|
||||
"arm_runtime": [
|
||||
"__aeabi_",
|
||||
"__adddf3",
|
||||
"__subdf3",
|
||||
"__muldf3",
|
||||
"__divdf3",
|
||||
"__addsf3",
|
||||
"__subsf3",
|
||||
"__mulsf3",
|
||||
"__divsf3",
|
||||
"__gnu_unwind",
|
||||
],
|
||||
"xtensa": ["xt_", "_xt_", "xPortEnterCriticalTimeout"],
|
||||
"heap": ["heap_", "multi_heap"],
|
||||
"spi_flash": ["spi_flash"],
|
||||
@@ -782,7 +855,22 @@ SYMBOL_PATTERNS = {
|
||||
"math_internal": ["__mdiff", "__lshift", "__mprec_tens", "quorem"],
|
||||
"character_class": ["__chclass"],
|
||||
"camellia": ["camellia_", "camellia_feistel"],
|
||||
"crypto_tables": ["FSb", "FSb2", "FSb3", "FSb4"],
|
||||
"crypto_tables": [
|
||||
"FSb",
|
||||
"FSb2",
|
||||
"FSb3",
|
||||
"FSb4",
|
||||
"Te0", # AES encryption table
|
||||
"Td0", # AES decryption table
|
||||
"crc32_table", # CRC32 lookup table
|
||||
"crc_tab", # CRC lookup table
|
||||
],
|
||||
"crypto_hash": [
|
||||
"SHA1Transform", # SHA1 hash function
|
||||
"MD5Transform", # MD5 hash function
|
||||
"SHA256",
|
||||
"SHA512",
|
||||
],
|
||||
"event_buffer": ["g_eb_list_desc", "eb_space"],
|
||||
"base_node": ["base_node_", "base_node_add_handler"],
|
||||
"file_descriptor": ["s_fd_table"],
|
||||
|
||||
@@ -5,6 +5,10 @@ from __future__ import annotations
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -55,3 +59,35 @@ def find_tool(
|
||||
|
||||
_LOGGER.warning("Could not find %s tool", tool_name)
|
||||
return None
|
||||
|
||||
|
||||
def run_tool(
|
||||
cmd: Sequence[str],
|
||||
timeout: int = 30,
|
||||
) -> subprocess.CompletedProcess[str] | None:
|
||||
"""Run a toolchain command and return the result.
|
||||
|
||||
Args:
|
||||
cmd: Command and arguments to run
|
||||
timeout: Timeout in seconds
|
||||
|
||||
Returns:
|
||||
CompletedProcess on success, None on failure
|
||||
"""
|
||||
try:
|
||||
return subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
_LOGGER.warning("Command timed out: %s", " ".join(cmd))
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
_LOGGER.warning("Command not found: %s", cmd[0])
|
||||
return None
|
||||
except OSError as e:
|
||||
_LOGGER.warning("Failed to run command %s: %s", cmd[0], e)
|
||||
return None
|
||||
|
||||
@@ -30,7 +30,9 @@ void A01nyubComponent::check_buffer_() {
|
||||
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
|
||||
this->publish_state(meters);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
|
||||
char hex_buf[format_hex_pretty_size(4)];
|
||||
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
|
||||
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);
|
||||
|
||||
@@ -29,7 +29,9 @@ void A02yyuwComponent::check_buffer_() {
|
||||
ESP_LOGV(TAG, "Distance from sensor: %f mm", distance);
|
||||
this->publish_state(distance);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
|
||||
char hex_buf[format_hex_pretty_size(4)];
|
||||
ESP_LOGW(TAG, "Invalid data read from sensor: %s",
|
||||
format_hex_pretty_to(hex_buf, this->buffer_.data(), this->buffer_.size()));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "checksum failed: %02x != %02x", checksum, this->buffer_[3]);
|
||||
|
||||
@@ -90,13 +90,16 @@ void AbsoluteHumidityComponent::loop() {
|
||||
this->status_set_error(LOG_STR("Invalid saturation vapor pressure equation selection!"));
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
||||
|
||||
// Calculate absolute humidity
|
||||
const float absolute_humidity = vapor_density(es, hr, temperature_k);
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"Saturation vapor pressure %f kPa\n"
|
||||
"Publishing absolute humidity %f g/m³",
|
||||
es, absolute_humidity);
|
||||
|
||||
// Publish absolute humidity
|
||||
ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity);
|
||||
this->status_clear_warning();
|
||||
this->publish_state(absolute_humidity);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "ac_dimmer.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -9,12 +7,12 @@
|
||||
#ifdef USE_ESP8266
|
||||
#include <core_esp8266_waveform.h>
|
||||
#endif
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#include <esp32-hal-timer.h>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "hw_timer_esp_idf.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
static const char *const TAG = "ac_dimmer";
|
||||
|
||||
@@ -27,7 +25,14 @@ static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-no
|
||||
/// However other factors like gate driver propagation time
|
||||
/// are also considered and a really low value is not important
|
||||
/// See also: https://github.com/esphome/issues/issues/1632
|
||||
static const uint32_t GATE_ENABLE_TIME = 50;
|
||||
static constexpr uint32_t GATE_ENABLE_TIME = 50;
|
||||
|
||||
#ifdef USE_ESP32
|
||||
/// Timer frequency in Hz (1 MHz = 1µs resolution)
|
||||
static constexpr uint32_t TIMER_FREQUENCY_HZ = 1000000;
|
||||
/// Timer interrupt interval in microseconds
|
||||
static constexpr uint64_t TIMER_INTERVAL_US = 50;
|
||||
#endif
|
||||
|
||||
/// Function called from timer interrupt
|
||||
/// Input is current time in microseconds (micros())
|
||||
@@ -154,7 +159,7 @@ void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
#ifdef USE_ESP32
|
||||
// ESP32 implementation, uses basically the same code but needs to wrap
|
||||
// timer_interrupt() function to auto-reschedule
|
||||
static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static HWTimer *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||
#endif
|
||||
|
||||
@@ -194,15 +199,15 @@ void AcDimmer::setup() {
|
||||
setTimer1Callback(&timer_interrupt);
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
// timer frequency of 1mhz
|
||||
dimmer_timer = timerBegin(1000000);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
||||
dimmer_timer = timer_begin(TIMER_FREQUENCY_HZ);
|
||||
timer_attach_interrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
||||
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
||||
// are not callable from ISR (placed in flash storage).
|
||||
// Here we just use an interrupt firing every 50 µs.
|
||||
timerAlarm(dimmer_timer, 50, true, 0);
|
||||
timer_alarm(dimmer_timer, TIMER_INTERVAL_US, true, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AcDimmer::write_state(float state) {
|
||||
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
@@ -210,14 +215,15 @@ void AcDimmer::write_state(float state) {
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
this->store_.value = new_value;
|
||||
}
|
||||
|
||||
void AcDimmer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AcDimmer:");
|
||||
LOG_PIN(" Output Pin: ", this->gate_pin_);
|
||||
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"AcDimmer:\n"
|
||||
" Min Power: %.1f%%\n"
|
||||
" Init with half cycle: %s",
|
||||
this->store_.min_power / 10.0f, YESNO(this->init_with_half_cycle_));
|
||||
LOG_PIN(" Output Pin: ", this->gate_pin_);
|
||||
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
|
||||
if (method_ == DIM_METHOD_LEADING_PULSE) {
|
||||
ESP_LOGCONFIG(TAG, " Method: leading pulse");
|
||||
} else if (method_ == DIM_METHOD_LEADING) {
|
||||
@@ -230,7 +236,4 @@ void AcDimmer::dump_config() {
|
||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||
}
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING };
|
||||
|
||||
@@ -64,7 +61,4 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
DimMethod method_;
|
||||
};
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
152
esphome/components/ac_dimmer/hw_timer_esp_idf.cpp
Normal file
152
esphome/components/ac_dimmer/hw_timer_esp_idf.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "hw_timer_esp_idf.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_clk_tree.h"
|
||||
#include "soc/clk_tree_defs.h"
|
||||
|
||||
static const char *const TAG = "hw_timer_esp_idf";
|
||||
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
// GPTimer divider constraints from ESP-IDF documentation
|
||||
static constexpr uint32_t GPTIMER_DIVIDER_MIN = 2;
|
||||
static constexpr uint32_t GPTIMER_DIVIDER_MAX = 65536;
|
||||
|
||||
using voidFuncPtr = void (*)();
|
||||
using voidFuncPtrArg = void (*)(void *);
|
||||
|
||||
struct InterruptConfigT {
|
||||
voidFuncPtr fn{nullptr};
|
||||
void *arg{nullptr};
|
||||
};
|
||||
|
||||
struct HWTimer {
|
||||
gptimer_handle_t timer_handle{nullptr};
|
||||
InterruptConfigT interrupt_handle{};
|
||||
bool timer_started{false};
|
||||
};
|
||||
|
||||
HWTimer *timer_begin(uint32_t frequency) {
|
||||
esp_err_t err = ESP_OK;
|
||||
uint32_t counter_src_hz = 0;
|
||||
uint32_t divider = 0;
|
||||
soc_module_clk_t clk;
|
||||
for (auto clk_candidate : SOC_GPTIMER_CLKS) {
|
||||
clk = clk_candidate;
|
||||
esp_clk_tree_src_get_freq_hz(clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &counter_src_hz);
|
||||
divider = counter_src_hz / frequency;
|
||||
if ((divider >= GPTIMER_DIVIDER_MIN) && (divider <= GPTIMER_DIVIDER_MAX)) {
|
||||
break;
|
||||
} else {
|
||||
divider = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (divider == 0) {
|
||||
ESP_LOGE(TAG, "Resolution not possible; aborting");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gptimer_config_t config = {
|
||||
.clk_src = static_cast<gptimer_clock_source_t>(clk),
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = frequency,
|
||||
.flags = {.intr_shared = true},
|
||||
};
|
||||
|
||||
HWTimer *timer = new HWTimer();
|
||||
|
||||
err = gptimer_new_timer(&config, &timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer creation failed; error %d", err);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err = gptimer_enable(timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer enable failed; error %d", err);
|
||||
gptimer_del_timer(timer->timer_handle);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err = gptimer_start(timer->timer_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "GPTimer start failed; error %d", err);
|
||||
gptimer_disable(timer->timer_handle);
|
||||
gptimer_del_timer(timer->timer_handle);
|
||||
delete timer;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
timer->timer_started = true;
|
||||
return timer;
|
||||
}
|
||||
|
||||
bool IRAM_ATTR timer_fn_wrapper(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *args) {
|
||||
auto *isr = static_cast<InterruptConfigT *>(args);
|
||||
if (isr->fn) {
|
||||
if (isr->arg) {
|
||||
reinterpret_cast<voidFuncPtrArg>(isr->fn)(isr->arg);
|
||||
} else {
|
||||
isr->fn();
|
||||
}
|
||||
}
|
||||
// Return false to indicate that no higher-priority task was woken and no context switch is requested.
|
||||
return false;
|
||||
}
|
||||
|
||||
static void timer_attach_interrupt_functional_arg(HWTimer *timer, void (*user_func)(void *), void *arg) {
|
||||
if (timer == nullptr) {
|
||||
ESP_LOGE(TAG, "Timer handle is nullptr");
|
||||
return;
|
||||
}
|
||||
gptimer_event_callbacks_t cbs = {
|
||||
.on_alarm = timer_fn_wrapper,
|
||||
};
|
||||
|
||||
timer->interrupt_handle.fn = reinterpret_cast<voidFuncPtr>(user_func);
|
||||
timer->interrupt_handle.arg = arg;
|
||||
|
||||
if (timer->timer_started) {
|
||||
gptimer_stop(timer->timer_handle);
|
||||
}
|
||||
gptimer_disable(timer->timer_handle);
|
||||
esp_err_t err = gptimer_register_event_callbacks(timer->timer_handle, &cbs, &timer->interrupt_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Timer Attach Interrupt failed; error %d", err);
|
||||
}
|
||||
gptimer_enable(timer->timer_handle);
|
||||
if (timer->timer_started) {
|
||||
gptimer_start(timer->timer_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void timer_attach_interrupt(HWTimer *timer, voidFuncPtr user_func) {
|
||||
timer_attach_interrupt_functional_arg(timer, reinterpret_cast<voidFuncPtrArg>(user_func), nullptr);
|
||||
}
|
||||
|
||||
void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count) {
|
||||
if (timer == nullptr) {
|
||||
ESP_LOGE(TAG, "Timer handle is nullptr");
|
||||
return;
|
||||
}
|
||||
gptimer_alarm_config_t alarm_cfg = {
|
||||
.alarm_count = alarm_value,
|
||||
.reload_count = reload_count,
|
||||
.flags = {.auto_reload_on_alarm = autoreload},
|
||||
};
|
||||
esp_err_t err = gptimer_set_alarm_action(timer->timer_handle, &alarm_cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Timer Alarm Write failed; error %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::ac_dimmer
|
||||
#endif
|
||||
17
esphome/components/ac_dimmer/hw_timer_esp_idf.h
Normal file
17
esphome/components/ac_dimmer/hw_timer_esp_idf.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "driver/gptimer_types.h"
|
||||
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
struct HWTimer;
|
||||
|
||||
HWTimer *timer_begin(uint32_t frequency);
|
||||
|
||||
void timer_attach_interrupt(HWTimer *timer, void (*user_func)());
|
||||
void timer_alarm(HWTimer *timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count);
|
||||
|
||||
} // namespace esphome::ac_dimmer
|
||||
|
||||
#endif
|
||||
@@ -3,6 +3,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@glmnet"]
|
||||
|
||||
@@ -31,11 +32,16 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
if CORE.is_esp8266:
|
||||
# ac_dimmer uses setTimer1Callback which requires the waveform generator
|
||||
from esphome.components.esp8266.const import require_waveform
|
||||
|
||||
require_waveform()
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -121,23 +121,21 @@ void ADCSensor::setup() {
|
||||
void ADCSensor::dump_config() {
|
||||
LOG_SENSOR("", "ADC Sensor", this);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Channel: %d\n"
|
||||
" Unit: %s\n"
|
||||
" Attenuation: %s\n"
|
||||
" Samples: %i\n"
|
||||
" Sampling mode: %s",
|
||||
this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)),
|
||||
this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_,
|
||||
LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
|
||||
|
||||
ESP_LOGCONFIG(
|
||||
TAG,
|
||||
" Channel: %d\n"
|
||||
" Unit: %s\n"
|
||||
" Attenuation: %s\n"
|
||||
" Samples: %i\n"
|
||||
" Sampling mode: %s\n"
|
||||
" Setup Status:\n"
|
||||
" Handle Init: %s\n"
|
||||
" Config: %s\n"
|
||||
" Calibration: %s\n"
|
||||
" Overall Init: %s",
|
||||
this->channel_, LOG_STR_ARG(adc_unit_to_str(this->adc_unit_)),
|
||||
this->autorange_ ? "Auto" : LOG_STR_ARG(attenuation_to_str(this->attenuation_)), this->sample_count_,
|
||||
LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)),
|
||||
this->setup_flags_.handle_init_complete ? "OK" : "FAILED", this->setup_flags_.config_complete ? "OK" : "FAILED",
|
||||
this->setup_flags_.calibration_complete ? "OK" : "FAILED", this->setup_flags_.init_complete ? "OK" : "FAILED");
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@ class AddressableLightDisplay : public display::DisplayBuffer {
|
||||
if (enabled_ && !enabled) { // enabled -> disabled
|
||||
// - Tell the parent light to refresh, effectively wiping the display. Also
|
||||
// restores the previous effect (if any).
|
||||
light_state_->make_call().set_effect(this->last_effect_).perform();
|
||||
if (this->last_effect_index_.has_value()) {
|
||||
light_state_->make_call().set_effect(*this->last_effect_index_).perform();
|
||||
}
|
||||
|
||||
} else if (!enabled_ && enabled) { // disabled -> enabled
|
||||
// - Save the current effect.
|
||||
this->last_effect_ = light_state_->get_effect_name();
|
||||
// - Save the current effect index.
|
||||
this->last_effect_index_ = light_state_->get_current_effect_index();
|
||||
// - Disable any current effect.
|
||||
light_state_->make_call().set_effect(0).perform();
|
||||
}
|
||||
@@ -56,7 +58,7 @@ class AddressableLightDisplay : public display::DisplayBuffer {
|
||||
int32_t width_;
|
||||
int32_t height_;
|
||||
std::vector<Color> addressable_light_buffer_;
|
||||
optional<std::string> last_effect_;
|
||||
optional<uint32_t> last_effect_index_;
|
||||
optional<std::function<int(int, int)>> pixel_mapper_f_;
|
||||
};
|
||||
} // namespace addressable_light
|
||||
|
||||
@@ -162,11 +162,13 @@ void ADE7880::update() {
|
||||
}
|
||||
|
||||
void ADE7880::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADE7880:");
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"ADE7880:\n"
|
||||
" Frequency: %.0f Hz",
|
||||
this->frequency_);
|
||||
LOG_PIN(" IRQ0 Pin: ", this->irq0_pin_);
|
||||
LOG_PIN(" IRQ1 Pin: ", this->irq1_pin_);
|
||||
LOG_PIN(" RESET Pin: ", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_);
|
||||
|
||||
if (this->channel_a_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Phase A:");
|
||||
|
||||
@@ -21,10 +21,12 @@ void ADS1115Sensor::update() {
|
||||
|
||||
void ADS1115Sensor::dump_config() {
|
||||
LOG_SENSOR(" ", "ADS1115 Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %u", this->resolution_);
|
||||
ESP_LOGCONFIG(TAG, " Sample rate: %u", this->samplerate_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Multiplexer: %u\n"
|
||||
" Gain: %u\n"
|
||||
" Resolution: %u\n"
|
||||
" Sample rate: %u",
|
||||
this->multiplexer_, this->gain_, this->resolution_, this->samplerate_);
|
||||
}
|
||||
|
||||
} // namespace ads1115
|
||||
|
||||
@@ -9,8 +9,10 @@ static const char *const TAG = "ads1118.sensor";
|
||||
|
||||
void ADS1118Sensor::dump_config() {
|
||||
LOG_SENSOR(" ", "ADS1118 Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", this->multiplexer_);
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", this->gain_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Multiplexer: %u\n"
|
||||
" Gain: %u",
|
||||
this->multiplexer_, this->gain_);
|
||||
}
|
||||
|
||||
float ADS1118Sensor::sample() {
|
||||
|
||||
@@ -20,7 +20,8 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
|
||||
sn |= ((uint32_t) it.data[2] << 16);
|
||||
sn |= ((uint32_t) it.data[3] << 24);
|
||||
|
||||
ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str());
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str_to(addr_buf));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "airthings_wave_base.h"
|
||||
#include "esphome/components/esp32_ble/ble_uuid.h"
|
||||
|
||||
// All information related to reading battery information came from the sensors.airthings_wave
|
||||
// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave)
|
||||
@@ -93,8 +94,10 @@ void AirthingsWaveBase::update() {
|
||||
bool AirthingsWaveBase::request_read_values_() {
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
|
||||
this->sensors_data_characteristic_uuid_.to_string().c_str());
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
|
||||
this->sensors_data_characteristic_uuid_.to_str(char_buf));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -117,17 +120,20 @@ bool AirthingsWaveBase::request_battery_() {
|
||||
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s",
|
||||
this->service_uuid_.to_string().c_str(),
|
||||
this->access_control_point_characteristic_uuid_.to_string().c_str());
|
||||
this->service_uuid_.to_str(service_buf), this->access_control_point_characteristic_uuid_.to_str(char_buf));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_,
|
||||
CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID);
|
||||
if (descr == nullptr) {
|
||||
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(),
|
||||
this->access_control_point_characteristic_uuid_.to_string().c_str());
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_str(service_buf),
|
||||
this->access_control_point_characteristic_uuid_.to_str(char_buf));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
static const char *const TAG = "alarm_control_panel";
|
||||
|
||||
@@ -115,5 +114,4 @@ void AlarmControlPanel::disarm(optional<std::string> code) {
|
||||
call.perform();
|
||||
}
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "alarm_control_panel_call.h"
|
||||
#include "alarm_control_panel_state.h"
|
||||
|
||||
@@ -9,8 +7,7 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
enum AlarmControlPanelFeature : uint8_t {
|
||||
// Matches Home Assistant values
|
||||
@@ -141,5 +138,4 @@ class AlarmControlPanel : public EntityBase {
|
||||
LazyCallbackManager<void()> ready_callback_{};
|
||||
};
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
static const char *const TAG = "alarm_control_panel";
|
||||
|
||||
@@ -99,5 +98,4 @@ void AlarmControlPanelCall::perform() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
class AlarmControlPanel;
|
||||
|
||||
@@ -36,5 +35,4 @@ class AlarmControlPanelCall {
|
||||
void validate_();
|
||||
};
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "alarm_control_panel_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state) {
|
||||
switch (state) {
|
||||
@@ -30,5 +29,4 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include <cstdint>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
enum AlarmControlPanelState : uint8_t {
|
||||
ACP_STATE_DISARMED = 0,
|
||||
@@ -25,5 +24,4 @@ enum AlarmControlPanelState : uint8_t {
|
||||
*/
|
||||
const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState state);
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "alarm_control_panel.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace alarm_control_panel {
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
/// Trigger on any state change
|
||||
class StateTrigger : public Trigger<> {
|
||||
@@ -165,5 +164,4 @@ template<typename... Ts> class AlarmControlPanelCondition : public Condition<Ts.
|
||||
AlarmControlPanel *parent_;
|
||||
};
|
||||
|
||||
} // namespace alarm_control_panel
|
||||
} // namespace esphome
|
||||
} // namespace esphome::alarm_control_panel
|
||||
|
||||
@@ -15,10 +15,8 @@ namespace alpha3 {
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
|
||||
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
|
||||
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
|
||||
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
|
||||
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
|
||||
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID = espbt::ESPBTUUID::from_raw(
|
||||
{0xa9, 0x7b, 0xb8, 0x85, 0x00, 0x1a, 0x28, 0xaa, 0x2a, 0x43, 0x6e, 0x03, 0xd1, 0xff, 0x9c, 0x85});
|
||||
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
|
||||
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;
|
||||
|
||||
|
||||
@@ -67,8 +67,10 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
|
||||
ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
|
||||
ESP_LOGW(TAG,
|
||||
"[%s] No control service found at device, not an Anova..?\n"
|
||||
"[%s] Note, this component does not currently support Anova Nano.",
|
||||
this->get_name().c_str(), this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
|
||||
@@ -226,32 +226,6 @@ def _encryption_schema(config):
|
||||
return ENCRYPTION_SCHEMA(config)
|
||||
|
||||
|
||||
def _validate_api_config(config: ConfigType) -> ConfigType:
|
||||
"""Validate API configuration with mutual exclusivity check and deprecation warning."""
|
||||
# Check if both password and encryption are configured
|
||||
has_password = CONF_PASSWORD in config and config[CONF_PASSWORD]
|
||||
has_encryption = CONF_ENCRYPTION in config
|
||||
|
||||
if has_password and has_encryption:
|
||||
raise cv.Invalid(
|
||||
"The 'password' and 'encryption' options are mutually exclusive. "
|
||||
"The API client only supports one authentication method at a time. "
|
||||
"Please remove one of them. "
|
||||
"Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. "
|
||||
"We strongly recommend using 'encryption' instead for better security."
|
||||
)
|
||||
|
||||
# Warn about password deprecation
|
||||
if has_password:
|
||||
_LOGGER.warning(
|
||||
"API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. "
|
||||
"Please migrate to the 'encryption' configuration. "
|
||||
"See https://esphome.io/components/api/#configuration-variables"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def _consume_api_sockets(config: ConfigType) -> ConfigType:
|
||||
"""Register socket needs for API component."""
|
||||
from esphome.components import socket
|
||||
@@ -268,7 +242,17 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
|
||||
# Removed in 2026.1.0 - kept to provide helpful error message
|
||||
cv.Optional(CONF_PASSWORD): cv.invalid(
|
||||
"The 'password' option has been removed in ESPHome 2026.1.0.\n"
|
||||
"Password authentication was deprecated in May 2022.\n"
|
||||
"Please migrate to encryption for secure API communication:\n\n"
|
||||
"api:\n"
|
||||
" encryption:\n"
|
||||
" key: !secret api_encryption_key\n\n"
|
||||
"Generate a key with: openssl rand -base64 32\n"
|
||||
"Or visit https://esphome.io/components/api/#configuration-variables"
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="15min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
@@ -330,7 +314,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
|
||||
_validate_api_config,
|
||||
_consume_api_sockets,
|
||||
)
|
||||
|
||||
@@ -344,9 +327,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
CORE.register_controller()
|
||||
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
if config[CONF_PASSWORD]:
|
||||
cg.add_define("USE_API_PASSWORD")
|
||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||
if CONF_LISTEN_BACKLOG in config:
|
||||
|
||||
@@ -7,10 +7,7 @@ service APIConnection {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
// REMOVED in ESPHome 2026.1.0: rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse)
|
||||
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
@@ -82,14 +79,13 @@ service APIConnection {
|
||||
// * VarInt denoting the type of message.
|
||||
// * The message object encoded as a ProtoBuf message
|
||||
|
||||
// The connection is established in 4 steps:
|
||||
// The connection is established in 2 steps:
|
||||
// * First, the client connects to the server and sends a "Hello Request" identifying itself
|
||||
// * The server responds with a "Hello Response" and selects the protocol version
|
||||
// * After receiving this message, the client attempts to authenticate itself using
|
||||
// the password and a "Connect Request"
|
||||
// * The server responds with a "Connect Response" and notifies of invalid password.
|
||||
// * The server responds with a "Hello Response" and the connection is authenticated
|
||||
// If anything in this initial process fails, the connection must immediately closed
|
||||
// by both sides and _no_ disconnection message is to be sent.
|
||||
// Note: Password authentication via AuthenticationRequest/AuthenticationResponse (message IDs 3, 4)
|
||||
// was removed in ESPHome 2026.1.0. Those message IDs are reserved and should not be reused.
|
||||
|
||||
// Message sent at the beginning of each connection
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
@@ -102,7 +98,7 @@ message HelloRequest {
|
||||
// For example "Home Assistant"
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1 [(pointer_to_buffer) = true];
|
||||
string client_info = 1;
|
||||
uint32 api_version_major = 2;
|
||||
uint32 api_version_minor = 3;
|
||||
}
|
||||
@@ -130,25 +126,23 @@ message HelloResponse {
|
||||
string name = 4;
|
||||
}
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
// DEPRECATED in ESPHome 2026.1.0 - Password authentication is no longer supported.
|
||||
// These messages are kept for protocol documentation but are not processed by the server.
|
||||
// Use noise encryption instead: https://esphome.io/components/api/#configuration-variables
|
||||
message AuthenticationRequest {
|
||||
option (id) = 3;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
option (ifdef) = "USE_API_PASSWORD";
|
||||
option deprecated = true;
|
||||
|
||||
// The password to log in with
|
||||
string password = 1 [(pointer_to_buffer) = true];
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message AuthenticationResponse {
|
||||
option (id) = 4;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
option (ifdef) = "USE_API_PASSWORD";
|
||||
option deprecated = true;
|
||||
|
||||
bool invalid_password = 1;
|
||||
}
|
||||
@@ -205,7 +199,9 @@ message DeviceInfoResponse {
|
||||
option (id) = 10;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"];
|
||||
// Deprecated in ESPHome 2026.1.0, but kept for backward compatibility
|
||||
// with older ESPHome versions that still send this field.
|
||||
bool uses_password = 1 [deprecated = true];
|
||||
|
||||
// The name of the node, given by "App.set_name()"
|
||||
string name = 2;
|
||||
@@ -477,7 +473,7 @@ message FanCommandRequest {
|
||||
bool has_speed_level = 10;
|
||||
int32 speed_level = 11;
|
||||
bool has_preset_mode = 12;
|
||||
string preset_mode = 13 [(pointer_to_buffer) = true];
|
||||
string preset_mode = 13;
|
||||
uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
@@ -579,7 +575,7 @@ message LightCommandRequest {
|
||||
bool has_flash_length = 16;
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19 [(pointer_to_buffer) = true];
|
||||
string effect = 19;
|
||||
uint32 device_id = 28 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
@@ -747,7 +743,7 @@ message NoiseEncryptionSetKeyRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_API_NOISE";
|
||||
|
||||
bytes key = 1 [(pointer_to_buffer) = true];
|
||||
bytes key = 1;
|
||||
}
|
||||
|
||||
message NoiseEncryptionSetKeyResponse {
|
||||
@@ -796,7 +792,7 @@ message HomeassistantActionResponse {
|
||||
uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest
|
||||
bool success = 2; // Whether the service call succeeded
|
||||
string error_message = 3; // Error message if success = false
|
||||
bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||
bytes response_data = 4 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||
}
|
||||
|
||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||
@@ -824,9 +820,9 @@ message HomeAssistantStateResponse {
|
||||
option (no_delay) = true;
|
||||
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
|
||||
|
||||
string entity_id = 1 [(pointer_to_buffer) = true];
|
||||
string state = 2 [(pointer_to_buffer) = true];
|
||||
string attribute = 3 [(pointer_to_buffer) = true];
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
string attribute = 3;
|
||||
}
|
||||
|
||||
// ==================== IMPORT TIME ====================
|
||||
@@ -841,7 +837,7 @@ message GetTimeResponse {
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 epoch_seconds = 1;
|
||||
string timezone = 2 [(pointer_to_buffer) = true];
|
||||
string timezone = 2;
|
||||
}
|
||||
|
||||
// ==================== USER-DEFINES SERVICES ====================
|
||||
@@ -1091,11 +1087,11 @@ message ClimateCommandRequest {
|
||||
bool has_swing_mode = 14;
|
||||
ClimateSwingMode swing_mode = 15;
|
||||
bool has_custom_fan_mode = 16;
|
||||
string custom_fan_mode = 17 [(pointer_to_buffer) = true];
|
||||
string custom_fan_mode = 17;
|
||||
bool has_preset = 18;
|
||||
ClimatePreset preset = 19;
|
||||
bool has_custom_preset = 20;
|
||||
string custom_preset = 21 [(pointer_to_buffer) = true];
|
||||
string custom_preset = 21;
|
||||
bool has_target_humidity = 22;
|
||||
float target_humidity = 23;
|
||||
uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"];
|
||||
@@ -1274,7 +1270,7 @@ message SelectCommandRequest {
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2 [(pointer_to_buffer) = true];
|
||||
string state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
@@ -1292,7 +1288,7 @@ message ListEntitiesSirenResponse {
|
||||
|
||||
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 6;
|
||||
repeated string tones = 7;
|
||||
repeated string tones = 7 [(container_pointer_no_template) = "FixedVector<const char *>"];
|
||||
bool supports_duration = 8;
|
||||
bool supports_volume = 9;
|
||||
EntityCategory entity_category = 10;
|
||||
@@ -1692,7 +1688,7 @@ message BluetoothGATTWriteRequest {
|
||||
uint32 handle = 2;
|
||||
bool response = 3;
|
||||
|
||||
bytes data = 4 [(pointer_to_buffer) = true];
|
||||
bytes data = 4;
|
||||
}
|
||||
|
||||
message BluetoothGATTReadDescriptorRequest {
|
||||
@@ -1712,7 +1708,7 @@ message BluetoothGATTWriteDescriptorRequest {
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3 [(pointer_to_buffer) = true];
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTNotifyRequest {
|
||||
@@ -1937,7 +1933,7 @@ message VoiceAssistantAudio {
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
bytes data = 1;
|
||||
bytes data = 1 [(pointer_to_buffer) = true];
|
||||
bool end = 2;
|
||||
}
|
||||
|
||||
@@ -2425,7 +2421,7 @@ message ZWaveProxyFrame {
|
||||
option (ifdef) = "USE_ZWAVE_PROXY";
|
||||
option (no_delay) = true;
|
||||
|
||||
bytes data = 1 [(pointer_to_buffer) = true];
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
enum ZWaveProxyRequestType {
|
||||
@@ -2439,5 +2435,5 @@ message ZWaveProxyRequest {
|
||||
option (ifdef) = "USE_ZWAVE_PROXY";
|
||||
|
||||
ZWaveProxyRequestType type = 1;
|
||||
bytes data = 2 [(pointer_to_buffer) = true];
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
@@ -101,16 +101,14 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||
auto &noise_ctx = parent->get_noise_ctx();
|
||||
if (noise_ctx.has_psk()) {
|
||||
this->helper_ =
|
||||
std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)};
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
|
||||
} else {
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
}
|
||||
#elif defined(USE_API_PLAINTEXT)
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
#elif defined(USE_API_NOISE)
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{
|
||||
new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)};
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
||||
#else
|
||||
#error "No frame helper defined"
|
||||
#endif
|
||||
@@ -131,8 +129,9 @@ void APIConnection::start() {
|
||||
this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
|
||||
return;
|
||||
}
|
||||
this->client_info_.peername = helper_->getpeername();
|
||||
this->client_info_.name = this->client_info_.peername;
|
||||
// Initialize client name with peername (IP address) until Hello message provides actual name
|
||||
const char *peername = this->helper_->get_client_peername();
|
||||
this->helper_->set_client_name(peername, strlen(peername));
|
||||
}
|
||||
|
||||
APIConnection::~APIConnection() {
|
||||
@@ -252,8 +251,7 @@ void APIConnection::loop() {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
|
||||
this->client_info_.peername.c_str());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting"));
|
||||
}
|
||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
|
||||
// Only send ping if we're not disconnecting
|
||||
@@ -287,7 +285,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
|
||||
// remote initiated disconnect_client
|
||||
// don't close yet, we still need to send the disconnect response
|
||||
// close will happen on next loop
|
||||
ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected"));
|
||||
this->flags_.next_close = true;
|
||||
DisconnectResponse resp;
|
||||
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
|
||||
@@ -378,7 +376,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
|
||||
bool is_single) {
|
||||
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
|
||||
ListEntitiesBinarySensorResponse msg;
|
||||
msg.set_device_class(binary_sensor->get_device_class_ref());
|
||||
msg.device_class = binary_sensor->get_device_class_ref();
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
|
||||
remaining_size, is_single);
|
||||
@@ -410,7 +408,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
msg.supports_tilt = traits.get_supports_tilt();
|
||||
msg.supports_stop = traits.get_supports_stop();
|
||||
msg.set_device_class(cover->get_device_class_ref());
|
||||
msg.device_class = cover->get_device_class_ref();
|
||||
return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
@@ -445,7 +443,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.set_preset_mode(StringRef(fan->get_preset_mode()));
|
||||
msg.preset_mode = StringRef(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,
|
||||
@@ -473,7 +471,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
if (msg.has_direction)
|
||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||
if (msg.has_preset_mode)
|
||||
call.set_preset_mode(reinterpret_cast<const char *>(msg.preset_mode), msg.preset_mode_len);
|
||||
call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
@@ -501,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.set_effect(light->get_effect_name_ref());
|
||||
resp.effect = light->get_effect_name_ref();
|
||||
}
|
||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -559,7 +557,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
if (msg.has_flash_length)
|
||||
call.set_flash_length(msg.flash_length);
|
||||
if (msg.has_effect)
|
||||
call.set_effect(reinterpret_cast<const char *>(msg.effect), msg.effect_len);
|
||||
call.set_effect(msg.effect.c_str(), msg.effect.size());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
@@ -583,10 +581,10 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
|
||||
bool is_single) {
|
||||
auto *sensor = static_cast<sensor::Sensor *>(entity);
|
||||
ListEntitiesSensorResponse msg;
|
||||
msg.set_unit_of_measurement(sensor->get_unit_of_measurement_ref());
|
||||
msg.unit_of_measurement = sensor->get_unit_of_measurement_ref();
|
||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||
msg.force_update = sensor->get_force_update();
|
||||
msg.set_device_class(sensor->get_device_class_ref());
|
||||
msg.device_class = sensor->get_device_class_ref();
|
||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
|
||||
return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
@@ -613,7 +611,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
|
||||
auto *a_switch = static_cast<switch_::Switch *>(entity);
|
||||
ListEntitiesSwitchResponse msg;
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
msg.set_device_class(a_switch->get_device_class_ref());
|
||||
msg.device_class = a_switch->get_device_class_ref();
|
||||
return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
@@ -638,7 +636,7 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec
|
||||
bool is_single) {
|
||||
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
|
||||
TextSensorStateResponse resp;
|
||||
resp.set_state(StringRef(text_sensor->state));
|
||||
resp.state = StringRef(text_sensor->state);
|
||||
resp.missing_state = !text_sensor->has_state();
|
||||
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
@@ -647,7 +645,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
|
||||
bool is_single) {
|
||||
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
|
||||
ListEntitiesTextSensorResponse msg;
|
||||
msg.set_device_class(text_sensor->get_device_class_ref());
|
||||
msg.device_class = text_sensor->get_device_class_ref();
|
||||
return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
|
||||
remaining_size, is_single);
|
||||
}
|
||||
@@ -677,13 +675,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.set_custom_fan_mode(StringRef(climate->get_custom_fan_mode()));
|
||||
resp.custom_fan_mode = StringRef(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.set_custom_preset(StringRef(climate->get_custom_preset()));
|
||||
resp.custom_preset = StringRef(climate->get_custom_preset());
|
||||
}
|
||||
if (traits.get_supports_swing_modes())
|
||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||
@@ -738,11 +736,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
if (msg.has_fan_mode)
|
||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||
if (msg.has_custom_fan_mode)
|
||||
call.set_fan_mode(reinterpret_cast<const char *>(msg.custom_fan_mode), msg.custom_fan_mode_len);
|
||||
call.set_fan_mode(msg.custom_fan_mode.c_str(), msg.custom_fan_mode.size());
|
||||
if (msg.has_preset)
|
||||
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
|
||||
if (msg.has_custom_preset)
|
||||
call.set_preset(reinterpret_cast<const char *>(msg.custom_preset), msg.custom_preset_len);
|
||||
call.set_preset(msg.custom_preset.c_str(), msg.custom_preset.size());
|
||||
if (msg.has_swing_mode)
|
||||
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
||||
call.perform();
|
||||
@@ -768,9 +766,9 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
|
||||
bool is_single) {
|
||||
auto *number = static_cast<number::Number *>(entity);
|
||||
ListEntitiesNumberResponse msg;
|
||||
msg.set_unit_of_measurement(number->traits.get_unit_of_measurement_ref());
|
||||
msg.unit_of_measurement = number->traits.get_unit_of_measurement_ref();
|
||||
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
|
||||
msg.set_device_class(number->traits.get_device_class_ref());
|
||||
msg.device_class = number->traits.get_device_class_ref();
|
||||
msg.min_value = number->traits.get_min_value();
|
||||
msg.max_value = number->traits.get_max_value();
|
||||
msg.step = number->traits.get_step();
|
||||
@@ -883,7 +881,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c
|
||||
bool is_single) {
|
||||
auto *text = static_cast<text::Text *>(entity);
|
||||
TextStateResponse resp;
|
||||
resp.set_state(StringRef(text->state));
|
||||
resp.state = StringRef(text->state);
|
||||
resp.missing_state = !text->has_state();
|
||||
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -895,7 +893,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
|
||||
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
|
||||
msg.min_length = text->traits.get_min_length();
|
||||
msg.max_length = text->traits.get_max_length();
|
||||
msg.set_pattern(text->traits.get_pattern_ref());
|
||||
msg.pattern = text->traits.get_pattern_ref();
|
||||
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
@@ -916,7 +914,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
|
||||
bool is_single) {
|
||||
auto *select = static_cast<select::Select *>(entity);
|
||||
SelectStateResponse resp;
|
||||
resp.set_state(StringRef(select->current_option()));
|
||||
resp.state = StringRef(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);
|
||||
}
|
||||
@@ -931,7 +929,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
|
||||
}
|
||||
void APIConnection::select_command(const SelectCommandRequest &msg) {
|
||||
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select)
|
||||
call.set_option(reinterpret_cast<const char *>(msg.state), msg.state_len);
|
||||
call.set_option(msg.state.c_str(), msg.state.size());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
@@ -941,7 +939,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
|
||||
bool is_single) {
|
||||
auto *button = static_cast<button::Button *>(entity);
|
||||
ListEntitiesButtonResponse msg;
|
||||
msg.set_device_class(button->get_device_class_ref());
|
||||
msg.device_class = button->get_device_class_ref();
|
||||
return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
@@ -1010,7 +1008,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
|
||||
auto *valve = static_cast<valve::Valve *>(entity);
|
||||
ListEntitiesValveResponse msg;
|
||||
auto traits = valve->get_traits();
|
||||
msg.set_device_class(valve->get_device_class_ref());
|
||||
msg.device_class = valve->get_device_class_ref();
|
||||
msg.assumed_state = traits.get_is_assumed_state();
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
msg.supports_stop = traits.get_supports_stop();
|
||||
@@ -1055,7 +1053,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
|
||||
for (auto &supported_format : traits.get_supported_formats()) {
|
||||
msg.supported_formats.emplace_back();
|
||||
auto &media_format = msg.supported_formats.back();
|
||||
media_format.set_format(StringRef(supported_format.format));
|
||||
media_format.format = StringRef(supported_format.format);
|
||||
media_format.sample_rate = supported_format.sample_rate;
|
||||
media_format.num_channels = supported_format.num_channels;
|
||||
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
|
||||
@@ -1153,9 +1151,8 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||
#ifdef USE_TIME_TIMEZONE
|
||||
if (value.timezone_len > 0) {
|
||||
homeassistant::global_homeassistant_time->set_timezone(reinterpret_cast<const char *>(value.timezone),
|
||||
value.timezone_len);
|
||||
if (!value.timezone.empty()) {
|
||||
homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1266,8 +1263,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
|
||||
for (auto &wake_word : config.available_wake_words) {
|
||||
resp.available_wake_words.emplace_back();
|
||||
auto &resp_wake_word = resp.available_wake_words.back();
|
||||
resp_wake_word.set_id(StringRef(wake_word.id));
|
||||
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
|
||||
resp_wake_word.id = StringRef(wake_word.id);
|
||||
resp_wake_word.wake_word = StringRef(wake_word.wake_word);
|
||||
for (const auto &lang : wake_word.trained_languages) {
|
||||
resp_wake_word.trained_languages.push_back(lang);
|
||||
}
|
||||
@@ -1282,8 +1279,8 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
|
||||
|
||||
resp.available_wake_words.emplace_back();
|
||||
auto &resp_wake_word = resp.available_wake_words.back();
|
||||
resp_wake_word.set_id(StringRef(wake_word.id));
|
||||
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
|
||||
resp_wake_word.id = StringRef(wake_word.id);
|
||||
resp_wake_word.wake_word = StringRef(wake_word.wake_word);
|
||||
for (const auto &lang : wake_word.trained_languages) {
|
||||
resp_wake_word.trained_languages.push_back(lang);
|
||||
}
|
||||
@@ -1424,7 +1421,7 @@ void APIConnection::send_event(event::Event *event, const char *event_type) {
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single) {
|
||||
EventResponse resp;
|
||||
resp.set_event_type(StringRef(event_type));
|
||||
resp.event_type = StringRef(event_type);
|
||||
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -1432,7 +1429,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
||||
bool is_single) {
|
||||
auto *event = static_cast<event::Event *>(entity);
|
||||
ListEntitiesEventResponse msg;
|
||||
msg.set_device_class(event->get_device_class_ref());
|
||||
msg.device_class = event->get_device_class_ref();
|
||||
msg.event_types = &event->get_event_types();
|
||||
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
@@ -1455,11 +1452,11 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
|
||||
resp.has_progress = true;
|
||||
resp.progress = update->update_info.progress;
|
||||
}
|
||||
resp.set_current_version(StringRef(update->update_info.current_version));
|
||||
resp.set_latest_version(StringRef(update->update_info.latest_version));
|
||||
resp.set_title(StringRef(update->update_info.title));
|
||||
resp.set_release_summary(StringRef(update->update_info.summary));
|
||||
resp.set_release_url(StringRef(update->update_info.release_url));
|
||||
resp.current_version = StringRef(update->update_info.current_version);
|
||||
resp.latest_version = StringRef(update->update_info.latest_version);
|
||||
resp.title = StringRef(update->update_info.title);
|
||||
resp.release_summary = StringRef(update->update_info.summary);
|
||||
resp.release_url = StringRef(update->update_info.release_url);
|
||||
}
|
||||
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -1467,7 +1464,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
|
||||
bool is_single) {
|
||||
auto *update = static_cast<update::UpdateEntity *>(entity);
|
||||
ListEntitiesUpdateResponse msg;
|
||||
msg.set_device_class(update->get_device_class_ref());
|
||||
msg.device_class = update->get_device_class_ref();
|
||||
return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
@@ -1505,9 +1502,10 @@ void APIConnection::complete_authentication_() {
|
||||
}
|
||||
|
||||
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
|
||||
ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
|
||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
|
||||
this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()),
|
||||
std::string(this->helper_->get_client_peername()));
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
@@ -1522,41 +1520,25 @@ void APIConnection::complete_authentication_() {
|
||||
}
|
||||
|
||||
bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
||||
this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
|
||||
this->client_info_.peername = this->helper_->getpeername();
|
||||
// Copy client name with truncation if needed (set_client_name handles truncation)
|
||||
this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size());
|
||||
this->client_api_version_major_ = msg.api_version_major;
|
||||
this->client_api_version_minor_ = msg.api_version_minor;
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(),
|
||||
this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->helper_->get_client_name(),
|
||||
this->helper_->get_client_peername(), this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 13;
|
||||
resp.api_version_minor = 14;
|
||||
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
|
||||
resp.set_server_info(ESPHOME_VERSION_REF);
|
||||
resp.set_name(StringRef(App.get_name()));
|
||||
resp.server_info = ESPHOME_VERSION_REF;
|
||||
resp.name = StringRef(App.get_name());
|
||||
|
||||
#ifdef USE_API_PASSWORD
|
||||
// Password required - wait for authentication
|
||||
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::CONNECTED);
|
||||
#else
|
||||
// No password configured - auto-authenticate
|
||||
// Auto-authenticate - password auth was removed in ESPHome 2026.1.0
|
||||
this->complete_authentication_();
|
||||
#endif
|
||||
|
||||
return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
|
||||
}
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
|
||||
AuthenticationResponse resp;
|
||||
// bool invalid_password = 1;
|
||||
resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
|
||||
if (!resp.invalid_password) {
|
||||
this->complete_authentication_();
|
||||
}
|
||||
return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE);
|
||||
}
|
||||
#endif // USE_API_PASSWORD
|
||||
|
||||
bool APIConnection::send_ping_response(const PingRequest &msg) {
|
||||
PingResponse resp;
|
||||
@@ -1565,27 +1547,24 @@ bool APIConnection::send_ping_response(const PingRequest &msg) {
|
||||
|
||||
bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
DeviceInfoResponse resp{};
|
||||
#ifdef USE_API_PASSWORD
|
||||
resp.uses_password = true;
|
||||
#endif
|
||||
resp.set_name(StringRef(App.get_name()));
|
||||
resp.set_friendly_name(StringRef(App.get_friendly_name()));
|
||||
resp.name = StringRef(App.get_name());
|
||||
resp.friendly_name = StringRef(App.get_friendly_name());
|
||||
#ifdef USE_AREAS
|
||||
resp.set_suggested_area(StringRef(App.get_area()));
|
||||
resp.suggested_area = StringRef(App.get_area());
|
||||
#endif
|
||||
// Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
|
||||
char mac_address[18];
|
||||
uint8_t mac[6];
|
||||
get_mac_address_raw(mac);
|
||||
format_mac_addr_upper(mac, mac_address);
|
||||
resp.set_mac_address(StringRef(mac_address));
|
||||
resp.mac_address = StringRef(mac_address);
|
||||
|
||||
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||
resp.esphome_version = ESPHOME_VERSION_REF;
|
||||
|
||||
// Stack buffer for build time string
|
||||
char build_time_str[Application::BUILD_TIME_STR_SIZE];
|
||||
App.get_build_time_string(build_time_str);
|
||||
resp.set_compilation_time(StringRef(build_time_str));
|
||||
resp.compilation_time = StringRef(build_time_str);
|
||||
|
||||
// Manufacturer string - define once, handle ESP8266 PROGMEM separately
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
@@ -1609,10 +1588,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
static const char MANUFACTURER_PROGMEM[] PROGMEM = ESPHOME_MANUFACTURER;
|
||||
char manufacturer_buf[sizeof(MANUFACTURER_PROGMEM)];
|
||||
memcpy_P(manufacturer_buf, MANUFACTURER_PROGMEM, sizeof(MANUFACTURER_PROGMEM));
|
||||
resp.set_manufacturer(StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1));
|
||||
resp.manufacturer = StringRef(manufacturer_buf, sizeof(MANUFACTURER_PROGMEM) - 1);
|
||||
#else
|
||||
static constexpr auto MANUFACTURER = StringRef::from_lit(ESPHOME_MANUFACTURER);
|
||||
resp.set_manufacturer(MANUFACTURER);
|
||||
resp.manufacturer = MANUFACTURER;
|
||||
#endif
|
||||
#undef ESPHOME_MANUFACTURER
|
||||
|
||||
@@ -1620,10 +1599,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
static const char MODEL_PROGMEM[] PROGMEM = ESPHOME_BOARD;
|
||||
char model_buf[sizeof(MODEL_PROGMEM)];
|
||||
memcpy_P(model_buf, MODEL_PROGMEM, sizeof(MODEL_PROGMEM));
|
||||
resp.set_model(StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1));
|
||||
resp.model = StringRef(model_buf, sizeof(MODEL_PROGMEM) - 1);
|
||||
#else
|
||||
static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD);
|
||||
resp.set_model(MODEL);
|
||||
resp.model = MODEL;
|
||||
#endif
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||
@@ -1636,13 +1615,13 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
char project_version_buf[sizeof(PROJECT_VERSION_PROGMEM)];
|
||||
memcpy_P(project_name_buf, PROJECT_NAME_PROGMEM, sizeof(PROJECT_NAME_PROGMEM));
|
||||
memcpy_P(project_version_buf, PROJECT_VERSION_PROGMEM, sizeof(PROJECT_VERSION_PROGMEM));
|
||||
resp.set_project_name(StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1));
|
||||
resp.set_project_version(StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1));
|
||||
resp.project_name = StringRef(project_name_buf, sizeof(PROJECT_NAME_PROGMEM) - 1);
|
||||
resp.project_version = StringRef(project_version_buf, sizeof(PROJECT_VERSION_PROGMEM) - 1);
|
||||
#else
|
||||
static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME);
|
||||
static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION);
|
||||
resp.set_project_name(PROJECT_NAME);
|
||||
resp.set_project_version(PROJECT_VERSION);
|
||||
resp.project_name = PROJECT_NAME;
|
||||
resp.project_version = PROJECT_VERSION;
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER
|
||||
@@ -1653,7 +1632,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
// Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
|
||||
char bluetooth_mac[18];
|
||||
bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac);
|
||||
resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
|
||||
resp.bluetooth_mac_address = StringRef(bluetooth_mac);
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
|
||||
@@ -1672,7 +1651,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
break;
|
||||
auto &device_info = resp.devices[device_index++];
|
||||
device_info.device_id = device->get_device_id();
|
||||
device_info.set_name(StringRef(device->get_name()));
|
||||
device_info.name = StringRef(device->get_name());
|
||||
device_info.area_id = device->get_area_id();
|
||||
}
|
||||
#endif
|
||||
@@ -1683,7 +1662,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
break;
|
||||
auto &area_info = resp.areas[area_index++];
|
||||
area_info.area_id = area->get_area_id();
|
||||
area_info.set_name(StringRef(area->get_name()));
|
||||
area_info.name = StringRef(area->get_name());
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1693,28 +1672,37 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
// Skip if entity_id is empty (invalid message)
|
||||
if (msg.entity_id_len == 0) {
|
||||
if (msg.entity_id.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
// Compare entity_id: check length matches and content matches
|
||||
size_t entity_id_len = strlen(it.entity_id);
|
||||
if (entity_id_len != msg.entity_id_len || memcmp(it.entity_id, msg.entity_id, msg.entity_id_len) != 0) {
|
||||
if (entity_id_len != msg.entity_id.size() ||
|
||||
memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compare attribute: either both have matching attribute, or both have none
|
||||
size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
|
||||
if (sub_attr_len != msg.attribute_len ||
|
||||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute, sub_attr_len) != 0)) {
|
||||
if (sub_attr_len != msg.attribute.size() ||
|
||||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create temporary string for callback (callback takes const std::string &)
|
||||
// Handle empty state (nullptr with len=0)
|
||||
std::string state(msg.state_len > 0 ? reinterpret_cast<const char *>(msg.state) : "", msg.state_len);
|
||||
it.callback(state);
|
||||
// Create null-terminated state for callback (parse_number needs null-termination)
|
||||
// HA state max length is 255, so 256 byte buffer covers all cases
|
||||
char state_buf[256];
|
||||
size_t copy_len = msg.state.size();
|
||||
if (copy_len >= sizeof(state_buf)) {
|
||||
copy_len = sizeof(state_buf) - 1; // Truncate to leave space for null terminator
|
||||
}
|
||||
if (copy_len > 0) {
|
||||
memcpy(state_buf, msg.state.c_str(), copy_len);
|
||||
}
|
||||
state_buf[copy_len] = '\0';
|
||||
it.callback(StringRef(state_buf, copy_len));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1749,20 +1737,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
// the action list. This ensures async actions (delays, waits) complete first.
|
||||
}
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message) {
|
||||
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message) {
|
||||
ExecuteServiceResponse resp;
|
||||
resp.call_id = call_id;
|
||||
resp.success = success;
|
||||
resp.set_error_message(StringRef(error_message));
|
||||
resp.error_message = error_message;
|
||||
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
|
||||
}
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
|
||||
void APIConnection::send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
|
||||
const uint8_t *response_data, size_t response_data_len) {
|
||||
ExecuteServiceResponse resp;
|
||||
resp.call_id = call_id;
|
||||
resp.success = success;
|
||||
resp.set_error_message(StringRef(error_message));
|
||||
resp.error_message = error_message;
|
||||
resp.response_data = response_data;
|
||||
resp.response_data_len = response_data_len;
|
||||
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
|
||||
@@ -1831,10 +1819,30 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
||||
return false;
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
|
||||
const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
|
||||
|
||||
if (!this->try_to_clear_buffer(!is_log_message)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Toggle Nagle's algorithm based on message type to prevent log messages from
|
||||
// filling the TCP send buffer and crowding out important state updates.
|
||||
//
|
||||
// This honors the `no_delay` proto option - SubscribeLogsResponse is the only
|
||||
// message with `option (no_delay) = false;` in api.proto, indicating it should
|
||||
// allow Nagle coalescing. This option existed since 2019 but was never implemented.
|
||||
//
|
||||
// - Log messages: Enable Nagle (NODELAY=false) so small log packets coalesce
|
||||
// into fewer, larger packets. They flush naturally via TCP delayed ACK timer
|
||||
// (~200ms), buffer filling, or when a state update triggers a flush.
|
||||
//
|
||||
// - All other messages (state updates, responses): Disable Nagle (NODELAY=true)
|
||||
// for immediate delivery. These are time-sensitive and should not be delayed.
|
||||
//
|
||||
// This must be done proactively BEFORE the buffer fills up - checking buffer
|
||||
// state here would be too late since we'd already be in a degraded state.
|
||||
this->helper_->set_nodelay(!is_log_message);
|
||||
|
||||
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return false;
|
||||
@@ -1845,15 +1853,9 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
// Do not set last_traffic_ on send
|
||||
return true;
|
||||
}
|
||||
#ifdef USE_API_PASSWORD
|
||||
void APIConnection::on_unauthenticated_access() {
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||
}
|
||||
#endif
|
||||
void APIConnection::on_no_setup_connection() {
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup"));
|
||||
}
|
||||
void APIConnection::on_fatal_error() {
|
||||
this->helper_->close();
|
||||
@@ -1899,9 +1901,9 @@ bool APIConnection::schedule_batch_() {
|
||||
}
|
||||
|
||||
void APIConnection::process_batch_() {
|
||||
// Ensure PacketInfo remains trivially destructible for our placement new approach
|
||||
static_assert(std::is_trivially_destructible<PacketInfo>::value,
|
||||
"PacketInfo must remain trivially destructible with this placement-new approach");
|
||||
// Ensure MessageInfo remains trivially destructible for our placement new approach
|
||||
static_assert(std::is_trivially_destructible<MessageInfo>::value,
|
||||
"MessageInfo must remain trivially destructible with this placement-new approach");
|
||||
|
||||
if (this->deferred_batch_.empty()) {
|
||||
this->flags_.batch_scheduled = false;
|
||||
@@ -1941,12 +1943,12 @@ void APIConnection::process_batch_() {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t packets_to_process = std::min(num_items, MAX_PACKETS_PER_BATCH);
|
||||
size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH);
|
||||
|
||||
// Stack-allocated array for packet info
|
||||
alignas(PacketInfo) char packet_info_storage[MAX_PACKETS_PER_BATCH * sizeof(PacketInfo)];
|
||||
PacketInfo *packet_info = reinterpret_cast<PacketInfo *>(packet_info_storage);
|
||||
size_t packet_count = 0;
|
||||
// Stack-allocated array for message info
|
||||
alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)];
|
||||
MessageInfo *message_info = reinterpret_cast<MessageInfo *>(message_info_storage);
|
||||
size_t message_count = 0;
|
||||
|
||||
// Cache these values to avoid repeated virtual calls
|
||||
const uint8_t header_padding = this->helper_->frame_header_padding();
|
||||
@@ -1977,7 +1979,7 @@ void APIConnection::process_batch_() {
|
||||
uint32_t current_offset = 0;
|
||||
|
||||
// Process items and encode directly to buffer (up to our limit)
|
||||
for (size_t i = 0; i < packets_to_process; i++) {
|
||||
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
|
||||
@@ -1991,11 +1993,11 @@ void APIConnection::process_batch_() {
|
||||
// Message was encoded successfully
|
||||
// payload_size is header_padding + actual payload size + footer_size
|
||||
uint16_t proto_payload_size = payload_size - header_padding - footer_size;
|
||||
// Use placement new to construct PacketInfo in pre-allocated stack array
|
||||
// This avoids default-constructing all MAX_PACKETS_PER_BATCH elements
|
||||
// Explicit destruction is not needed because PacketInfo is trivially destructible,
|
||||
// Use placement new to construct MessageInfo in pre-allocated stack array
|
||||
// This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements
|
||||
// Explicit destruction is not needed because MessageInfo is trivially destructible,
|
||||
// as ensured by the static_assert in its definition.
|
||||
new (&packet_info[packet_count++]) PacketInfo(item.message_type, current_offset, proto_payload_size);
|
||||
new (&message_info[message_count++]) MessageInfo(item.message_type, current_offset, proto_payload_size);
|
||||
|
||||
// Update tracking variables
|
||||
items_processed++;
|
||||
@@ -2019,9 +2021,9 @@ void APIConnection::process_batch_() {
|
||||
shared_buf.resize(shared_buf.size() + footer_size);
|
||||
}
|
||||
|
||||
// Send all collected packets
|
||||
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
|
||||
std::span<const PacketInfo>(packet_info, packet_count));
|
||||
// Send all collected messages
|
||||
APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf},
|
||||
std::span<const MessageInfo>(message_info, message_count));
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
|
||||
}
|
||||
@@ -2089,10 +2091,10 @@ void APIConnection::process_state_subscriptions_() {
|
||||
|
||||
const auto &it = subs[this->state_subs_at_];
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.set_entity_id(StringRef(it.entity_id));
|
||||
resp.entity_id = StringRef(it.entity_id);
|
||||
|
||||
// Avoid string copy by using the const char* pointer if it exists
|
||||
resp.set_attribute(it.attribute != nullptr ? StringRef(it.attribute) : StringRef(""));
|
||||
resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef("");
|
||||
|
||||
resp.once = it.once;
|
||||
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
|
||||
@@ -2101,8 +2103,13 @@ void APIConnection::process_state_subscriptions_() {
|
||||
}
|
||||
#endif // USE_API_HOMEASSISTANT_STATES
|
||||
|
||||
void APIConnection::log_client_(int level, const LogString *message) {
|
||||
esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(),
|
||||
this->helper_->get_client_peername(), LOG_STR_ARG(message));
|
||||
}
|
||||
|
||||
void APIConnection::log_warning_(const LogString *message, APIError err) {
|
||||
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(),
|
||||
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_client_peername(),
|
||||
LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,32 +9,23 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
// Client information structure
|
||||
struct ClientInfo {
|
||||
std::string name; // Client name from Hello message
|
||||
std::string peername; // IP:port from socket
|
||||
};
|
||||
|
||||
// Keepalive timeout in milliseconds
|
||||
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
|
||||
// Maximum number of entities to process in a single batch during initial state/info sending
|
||||
// This was increased from 20 to 24 after removing the unique_id field from entity info messages,
|
||||
// which reduced message sizes allowing more entities per batch without exceeding packet limits
|
||||
static constexpr size_t MAX_INITIAL_PER_BATCH = 24;
|
||||
// Maximum number of packets to process in a single batch (platform-dependent)
|
||||
// This limit exists to prevent stack overflow from the PacketInfo array in process_batch_
|
||||
// Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HOST has plenty
|
||||
#else
|
||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
||||
#endif
|
||||
// API 1.14+ clients compute object_id client-side, so messages are smaller and we can fit more per batch
|
||||
// TODO: Remove MAX_INITIAL_PER_BATCH_LEGACY before 2026.7.0 - all clients should support API 1.14 by then
|
||||
static constexpr size_t MAX_INITIAL_PER_BATCH_LEGACY = 24; // For clients < API 1.14 (includes object_id)
|
||||
static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= API 1.14 (no object_id)
|
||||
// Verify MAX_MESSAGES_PER_BATCH (defined in api_frame_helper.h) can hold the initial batch
|
||||
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
|
||||
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
|
||||
|
||||
class APIConnection final : public APIServerConnection {
|
||||
public:
|
||||
@@ -202,9 +193,6 @@ class APIConnection final : public APIServerConnection {
|
||||
void on_get_time_response(const GetTimeResponse &value) override;
|
||||
#endif
|
||||
bool send_hello_response(const HelloRequest &msg) override;
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool send_authenticate_response(const AuthenticationRequest &msg) override;
|
||||
#endif
|
||||
bool send_disconnect_response(const DisconnectRequest &msg) override;
|
||||
bool send_ping_response(const PingRequest &msg) override;
|
||||
bool send_device_info_response(const DeviceInfoRequest &msg) override;
|
||||
@@ -233,9 +221,9 @@ class APIConnection final : public APIServerConnection {
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message);
|
||||
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
void send_execute_service_response(uint32_t call_id, bool success, const std::string &error_message,
|
||||
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message,
|
||||
const uint8_t *response_data, size_t response_data_len);
|
||||
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
@@ -260,9 +248,6 @@ class APIConnection final : public APIServerConnection {
|
||||
}
|
||||
|
||||
void on_fatal_error() override;
|
||||
#ifdef USE_API_PASSWORD
|
||||
void on_unauthenticated_access() override;
|
||||
#endif
|
||||
void on_no_setup_connection() override;
|
||||
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
|
||||
// FIXME: ensure no recursive writes can happen
|
||||
@@ -289,8 +274,9 @@ class APIConnection final : public APIServerConnection {
|
||||
bool try_to_clear_buffer(bool log_out_of_space);
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
|
||||
|
||||
const std::string &get_name() const { return this->client_info_.name; }
|
||||
const std::string &get_peername() const { return this->client_info_.peername; }
|
||||
const char *get_name() const { return this->helper_->get_client_name(); }
|
||||
/// Get peer name (IP address) - cached at connection init time
|
||||
const char *get_peername() const { return this->helper_->get_client_peername(); }
|
||||
|
||||
protected:
|
||||
// Helper function to handle authentication completion
|
||||
@@ -323,18 +309,24 @@ class APIConnection final : public APIServerConnection {
|
||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||
// Set common fields that are shared by all entity types
|
||||
msg.key = entity->get_object_id_hash();
|
||||
// Get object_id with zero heap allocation
|
||||
// Static case returns direct reference, dynamic case uses buffer
|
||||
|
||||
// API 1.14+ clients compute object_id client-side from the entity name
|
||||
// For older clients, we must send object_id for backward compatibility
|
||||
// See: https://github.com/esphome/backlog/issues/76
|
||||
// TODO: Remove this backward compat code before 2026.7.0 - all clients should support API 1.14 by then
|
||||
// Buffer must remain in scope until encode_message_to_buffer is called
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
msg.set_object_id(entity->get_object_id_to(object_id_buf));
|
||||
if (!conn->client_supports_api_version(1, 14)) {
|
||||
msg.object_id = entity->get_object_id_to(object_id_buf);
|
||||
}
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.set_name(entity->get_name());
|
||||
msg.name = entity->get_name();
|
||||
}
|
||||
|
||||
// Set common EntityBase properties
|
||||
#ifdef USE_ENTITY_ICON
|
||||
msg.set_icon(entity->get_icon_ref());
|
||||
msg.icon = entity->get_icon_ref();
|
||||
#endif
|
||||
msg.disabled_by_default = entity->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
|
||||
@@ -349,16 +341,24 @@ class APIConnection final : public APIServerConnection {
|
||||
inline bool check_voice_assistant_api_connection_() const;
|
||||
#endif
|
||||
|
||||
// Get the max batch size based on client API version
|
||||
// API 1.14+ clients don't receive object_id, so messages are smaller and more fit per batch
|
||||
// TODO: Remove this method before 2026.7.0 and use MAX_INITIAL_PER_BATCH directly
|
||||
size_t get_max_batch_size_() const {
|
||||
return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY;
|
||||
}
|
||||
|
||||
// Helper method to process multiple entities from an iterator in a batch
|
||||
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
|
||||
size_t initial_size = this->deferred_batch_.size();
|
||||
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) {
|
||||
size_t max_batch = this->get_max_batch_size_();
|
||||
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
|
||||
iterator.advance();
|
||||
}
|
||||
|
||||
// If the batch is full, process it immediately
|
||||
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
|
||||
if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) {
|
||||
if (this->deferred_batch_.size() >= max_batch) {
|
||||
this->process_batch_();
|
||||
}
|
||||
}
|
||||
@@ -522,10 +522,7 @@ class APIConnection final : public APIServerConnection {
|
||||
std::unique_ptr<camera::CameraImageReader> image_reader_;
|
||||
#endif
|
||||
|
||||
// Group 3: Client info struct (24 bytes on 32-bit: 2 strings × 12 bytes each)
|
||||
ClientInfo client_info_;
|
||||
|
||||
// Group 4: 4-byte types
|
||||
// Group 3: 4-byte types
|
||||
uint32_t last_traffic_;
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
int state_subs_at_ = -1;
|
||||
@@ -752,6 +749,8 @@ class APIConnection final : public APIServerConnection {
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
|
||||
// Helper function to log client messages with name and peername
|
||||
void log_client_(int level, const LogString *message);
|
||||
// Helper function to log API errors with errno
|
||||
void log_warning_(const LogString *message, APIError err);
|
||||
// Helper to handle fatal errors with logging
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "api_frame_helper.h"
|
||||
#ifdef USE_API
|
||||
#include "api_connection.h" // For ClientInfo struct
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -13,12 +12,29 @@ namespace esphome::api {
|
||||
|
||||
static const char *const TAG = "api.frame_helper";
|
||||
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t API_MAX_LOG_BYTES = 168;
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
|
||||
#else
|
||||
#define HELPER_LOG(msg, ...) ((void) 0)
|
||||
#endif
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
||||
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
|
||||
#define LOG_PACKET_RECEIVED(buffer) \
|
||||
do { \
|
||||
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
|
||||
ESP_LOGVV(TAG, "Received frame: %s", \
|
||||
format_hex_pretty_to(hex_buf_, (buffer).data(), \
|
||||
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#define LOG_PACKET_SENDING(data, len) \
|
||||
do { \
|
||||
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", \
|
||||
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
|
||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||
@@ -229,6 +245,8 @@ APIError APIFrameHelper::init_common_() {
|
||||
HELPER_LOG("Bad state for init %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
// Cache peername now while socket is valid - needed for error logging after socket failure
|
||||
this->socket_->getpeername_to(this->client_peername_);
|
||||
int err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
|
||||
@@ -29,24 +29,28 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 8192; // 8 KiB for ESP8266
|
||||
static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and other platforms
|
||||
#endif
|
||||
|
||||
// Forward declaration
|
||||
struct ClientInfo;
|
||||
// Maximum number of messages to batch in a single write operation
|
||||
// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there)
|
||||
static constexpr size_t MAX_MESSAGES_PER_BATCH = 34;
|
||||
|
||||
class ProtoWriteBuffer;
|
||||
|
||||
// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars)
|
||||
static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32;
|
||||
|
||||
struct ReadPacketBuffer {
|
||||
const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call)
|
||||
uint16_t data_len;
|
||||
uint16_t type;
|
||||
};
|
||||
|
||||
// Packed packet info structure to minimize memory usage
|
||||
struct PacketInfo {
|
||||
// Packed message info structure to minimize memory usage
|
||||
struct MessageInfo {
|
||||
uint16_t offset; // Offset in buffer where message starts
|
||||
uint16_t payload_size; // Size of the message payload
|
||||
uint8_t message_type; // Message type (0-255)
|
||||
|
||||
PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
|
||||
MessageInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
|
||||
};
|
||||
|
||||
enum class APIError : uint16_t {
|
||||
@@ -82,14 +86,23 @@ const LogString *api_error_to_logstr(APIError err);
|
||||
class APIFrameHelper {
|
||||
public:
|
||||
APIFrameHelper() = default;
|
||||
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
||||
: socket_(std::move(socket)), client_info_(client_info) {}
|
||||
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
|
||||
|
||||
// Get client name (null-terminated)
|
||||
const char *get_client_name() const { return this->client_name_; }
|
||||
// Get client peername/IP (null-terminated, cached at init time for availability after socket failure)
|
||||
const char *get_client_peername() const { return this->client_peername_; }
|
||||
// Set client name from buffer with length (truncates if needed)
|
||||
void set_client_name(const char *name, size_t len) {
|
||||
size_t copy_len = std::min(len, sizeof(this->client_name_) - 1);
|
||||
memcpy(this->client_name_, name, copy_len);
|
||||
this->client_name_[copy_len] = '\0';
|
||||
}
|
||||
virtual ~APIFrameHelper() = default;
|
||||
virtual APIError init() = 0;
|
||||
virtual APIError loop();
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
|
||||
std::string getpeername() { return socket_->getpeername(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
|
||||
APIError close() {
|
||||
state_ = State::CLOSED;
|
||||
@@ -107,11 +120,32 @@ class APIFrameHelper {
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
/// Toggle TCP_NODELAY socket option to control Nagle's algorithm.
|
||||
///
|
||||
/// This is used to allow log messages to coalesce (Nagle enabled) while keeping
|
||||
/// state updates low-latency (NODELAY enabled). Without this, many small log
|
||||
/// packets fill the TCP send buffer, crowding out important state updates.
|
||||
///
|
||||
/// State is tracked to minimize setsockopt() overhead - on lwip_raw (ESP8266/RP2040)
|
||||
/// this is just a boolean assignment; on other platforms it's a lightweight syscall.
|
||||
///
|
||||
/// @param enable true to enable NODELAY (disable Nagle), false to enable Nagle
|
||||
/// @return true if successful or already in desired state
|
||||
bool set_nodelay(bool enable) {
|
||||
if (this->nodelay_enabled_ == enable)
|
||||
return true;
|
||||
int val = enable ? 1 : 0;
|
||||
int err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||
if (err == 0) {
|
||||
this->nodelay_enabled_ = enable;
|
||||
}
|
||||
return err == 0;
|
||||
}
|
||||
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
||||
// Write multiple protobuf packets in a single operation
|
||||
// packets contains (message_type, offset, length) for each message in the buffer
|
||||
// Write multiple protobuf messages in a single operation
|
||||
// messages contains (message_type, offset, length) for each message in the buffer
|
||||
// The buffer contains all messages with appropriate padding before each
|
||||
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
||||
virtual APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) = 0;
|
||||
// Get the frame header padding required by this protocol
|
||||
uint8_t frame_header_padding() const { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
@@ -127,12 +161,6 @@ class APIFrameHelper {
|
||||
// Use swap trick since shrink_to_fit() is non-binding and may be ignored
|
||||
std::vector<uint8_t>().swap(this->rx_buf_);
|
||||
}
|
||||
// reusable_iovs_: Safe to release unconditionally.
|
||||
// Only used within write_protobuf_packets() calls - cleared at start,
|
||||
// populated with pointers, used for writev(), then function returns.
|
||||
// The iovecs contain stale pointers after the call (data was either sent
|
||||
// or copied to tx_buf_), and are cleared on next write_protobuf_packets().
|
||||
std::vector<struct iovec>().swap(this->reusable_iovs_);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -186,12 +214,12 @@ class APIFrameHelper {
|
||||
|
||||
// Containers (size varies, but typically 12+ bytes on 32-bit)
|
||||
std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
|
||||
std::vector<struct iovec> reusable_iovs_;
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
|
||||
// Pointer to client info (4 bytes on 32-bit)
|
||||
// Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance.
|
||||
const ClientInfo *client_info_{nullptr};
|
||||
// Client name buffer - stores name from Hello message or initial peername
|
||||
char client_name_[CLIENT_INFO_NAME_MAX_LEN]{};
|
||||
// Cached peername/IP address - captured at init time for availability after socket failure
|
||||
char client_peername_[socket::SOCKADDR_STR_LEN]{};
|
||||
|
||||
// Group smaller types together
|
||||
uint16_t rx_buf_len_ = 0;
|
||||
@@ -201,7 +229,10 @@ class APIFrameHelper {
|
||||
uint8_t tx_buf_head_{0};
|
||||
uint8_t tx_buf_tail_{0};
|
||||
uint8_t tx_buf_count_{0};
|
||||
// 8 bytes total, 0 bytes padding
|
||||
// Tracks TCP_NODELAY state to minimize setsockopt() calls. Initialized to true
|
||||
// since init_common_() enables NODELAY. Used by set_nodelay() to allow log
|
||||
// messages to coalesce while keeping state updates low-latency.
|
||||
bool nodelay_enabled_{true};
|
||||
|
||||
// Common initialization for both plaintext and noise protocols
|
||||
APIError init_common_();
|
||||
|
||||
@@ -24,12 +24,29 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||
#endif
|
||||
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t API_MAX_LOG_BYTES = 168;
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
|
||||
#else
|
||||
#define HELPER_LOG(msg, ...) ((void) 0)
|
||||
#endif
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
||||
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
|
||||
#define LOG_PACKET_RECEIVED(buffer) \
|
||||
do { \
|
||||
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
|
||||
ESP_LOGVV(TAG, "Received frame: %s", \
|
||||
format_hex_pretty_to(hex_buf_, (buffer).data(), \
|
||||
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#define LOG_PACKET_SENDING(data, len) \
|
||||
do { \
|
||||
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", \
|
||||
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
|
||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||
@@ -415,12 +432,12 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
// Resize to include MAC space (required for Noise encryption)
|
||||
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||
PacketInfo packet{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
|
||||
MessageInfo msg{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
|
||||
APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) {
|
||||
APIError aerr = state_action_();
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
@@ -430,20 +447,20 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
if (packets.empty()) {
|
||||
if (messages.empty()) {
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
uint8_t *buffer_data = buffer.get_buffer()->data();
|
||||
|
||||
this->reusable_iovs_.clear();
|
||||
this->reusable_iovs_.reserve(packets.size());
|
||||
// Stack-allocated iovec array - no heap allocation
|
||||
StaticVector<struct iovec, MAX_MESSAGES_PER_BATCH> iovs;
|
||||
uint16_t total_write_len = 0;
|
||||
|
||||
// We need to encrypt each packet in place
|
||||
for (const auto &packet : packets) {
|
||||
// We need to encrypt each message in place
|
||||
for (const auto &msg : messages) {
|
||||
// The buffer already has padding at offset
|
||||
uint8_t *buf_start = buffer_data + packet.offset;
|
||||
uint8_t *buf_start = buffer_data + msg.offset;
|
||||
|
||||
// Write noise header
|
||||
buf_start[0] = 0x01; // indicator
|
||||
@@ -451,10 +468,10 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
||||
|
||||
// Write message header (to be encrypted)
|
||||
const uint8_t msg_offset = 3;
|
||||
buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte
|
||||
buf_start[msg_offset + 2] = static_cast<uint8_t>(packet.payload_size >> 8); // data_len high byte
|
||||
buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte
|
||||
buf_start[msg_offset] = static_cast<uint8_t>(msg.message_type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = static_cast<uint8_t>(msg.message_type); // type low byte
|
||||
buf_start[msg_offset + 2] = static_cast<uint8_t>(msg.payload_size >> 8); // data_len high byte
|
||||
buf_start[msg_offset + 3] = static_cast<uint8_t>(msg.payload_size); // data_len low byte
|
||||
// payload data is already in the buffer starting at offset + 7
|
||||
|
||||
// Make sure we have space for MAC
|
||||
@@ -463,8 +480,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
||||
// Encrypt the message in place
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size,
|
||||
4 + packet.payload_size + frame_footer_size_);
|
||||
noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + msg.payload_size,
|
||||
4 + msg.payload_size + frame_footer_size_);
|
||||
|
||||
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
APIError aerr =
|
||||
@@ -476,14 +493,14 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
||||
buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
|
||||
buf_start[2] = static_cast<uint8_t>(mbuf.size);
|
||||
|
||||
// Add iovec for this encrypted packet
|
||||
size_t packet_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
|
||||
this->reusable_iovs_.push_back({buf_start, packet_len});
|
||||
total_write_len += packet_len;
|
||||
// Add iovec for this encrypted message
|
||||
size_t msg_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
|
||||
iovs.push_back({buf_start, msg_len});
|
||||
total_write_len += msg_len;
|
||||
}
|
||||
|
||||
// Send all encrypted packets in one writev call
|
||||
return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
|
||||
// Send all encrypted messages in one writev call
|
||||
return this->write_raw_(iovs.data(), iovs.size(), total_write_len);
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace esphome::api {
|
||||
|
||||
class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
public:
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx, const ClientInfo *client_info)
|
||||
: APIFrameHelper(std::move(socket), client_info), ctx_(ctx) {
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx)
|
||||
: APIFrameHelper(std::move(socket)), ctx_(ctx) {
|
||||
// Noise header structure:
|
||||
// Pos 0: indicator (0x01)
|
||||
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
||||
@@ -23,7 +23,7 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
|
||||
|
||||
protected:
|
||||
APIError state_action_();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "api_frame_helper_plaintext.h"
|
||||
#ifdef USE_API
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
#include "api_connection.h" // For ClientInfo struct
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -18,12 +17,29 @@ namespace esphome::api {
|
||||
|
||||
static const char *const TAG = "api.plaintext";
|
||||
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t API_MAX_LOG_BYTES = 168;
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
|
||||
#else
|
||||
#define HELPER_LOG(msg, ...) ((void) 0)
|
||||
#endif
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
||||
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
|
||||
#define LOG_PACKET_RECEIVED(buffer) \
|
||||
do { \
|
||||
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
|
||||
ESP_LOGVV(TAG, "Received frame: %s", \
|
||||
format_hex_pretty_to(hex_buf_, (buffer).data(), \
|
||||
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#define LOG_PACKET_SENDING(data, len) \
|
||||
do { \
|
||||
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", \
|
||||
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
|
||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||
@@ -216,29 +232,30 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
||||
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
|
||||
MessageInfo msg{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
||||
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer,
|
||||
std::span<const MessageInfo> messages) {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
|
||||
if (packets.empty()) {
|
||||
if (messages.empty()) {
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
uint8_t *buffer_data = buffer.get_buffer()->data();
|
||||
|
||||
this->reusable_iovs_.clear();
|
||||
this->reusable_iovs_.reserve(packets.size());
|
||||
// Stack-allocated iovec array - no heap allocation
|
||||
StaticVector<struct iovec, MAX_MESSAGES_PER_BATCH> iovs;
|
||||
uint16_t total_write_len = 0;
|
||||
|
||||
for (const auto &packet : packets) {
|
||||
for (const auto &msg : messages) {
|
||||
// Calculate varint sizes for header layout
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(msg.payload_size));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(msg.message_type));
|
||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
// Calculate where to start writing the header
|
||||
@@ -266,25 +283,25 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
||||
//
|
||||
// The message starts at offset + frame_header_padding_
|
||||
// So we write the header starting at offset + frame_header_padding_ - total_header_len
|
||||
uint8_t *buf_start = buffer_data + packet.offset;
|
||||
uint8_t *buf_start = buffer_data + msg.offset;
|
||||
uint32_t header_offset = frame_header_padding_ - total_header_len;
|
||||
|
||||
// Write the plaintext header
|
||||
buf_start[header_offset] = 0x00; // indicator
|
||||
|
||||
// Encode varints directly into buffer
|
||||
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||
ProtoVarInt(packet.message_type)
|
||||
ProtoVarInt(msg.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||
ProtoVarInt(msg.message_type)
|
||||
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||
|
||||
// Add iovec for this packet (header + payload)
|
||||
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
||||
this->reusable_iovs_.push_back({buf_start + header_offset, packet_len});
|
||||
total_write_len += packet_len;
|
||||
// Add iovec for this message (header + payload)
|
||||
size_t msg_len = static_cast<size_t>(total_header_len + msg.payload_size);
|
||||
iovs.push_back({buf_start + header_offset, msg_len});
|
||||
total_write_len += msg_len;
|
||||
}
|
||||
|
||||
// Send all packets in one writev call
|
||||
return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
|
||||
// Send all messages in one writev call
|
||||
return write_raw_(iovs.data(), iovs.size(), total_write_len);
|
||||
}
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -7,8 +7,7 @@ namespace esphome::api {
|
||||
|
||||
class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||
public:
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
||||
: APIFrameHelper(std::move(socket), client_info) {
|
||||
explicit APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
|
||||
// Plaintext header structure (worst case):
|
||||
// Pos 0: indicator (0x00)
|
||||
// Pos 1-3: payload size varint (up to 3 bytes)
|
||||
@@ -21,7 +20,7 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
|
||||
|
||||
protected:
|
||||
APIError try_read_frame_();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -315,15 +315,12 @@ enum ZWaveProxyRequestType : uint32_t {
|
||||
class InfoResponseProtoMessage : public ProtoMessage {
|
||||
public:
|
||||
~InfoResponseProtoMessage() override = default;
|
||||
StringRef object_id_ref_{};
|
||||
void set_object_id(const StringRef &ref) { this->object_id_ref_ = ref; }
|
||||
StringRef object_id{};
|
||||
uint32_t key{0};
|
||||
StringRef name_ref_{};
|
||||
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||
StringRef name{};
|
||||
bool disabled_by_default{false};
|
||||
#ifdef USE_ENTITY_ICON
|
||||
StringRef icon_ref_{};
|
||||
void set_icon(const StringRef &ref) { this->icon_ref_ = ref; }
|
||||
StringRef icon{};
|
||||
#endif
|
||||
enums::EntityCategory entity_category{};
|
||||
#ifdef USE_DEVICES
|
||||
@@ -357,12 +354,11 @@ class CommandProtoMessage : public ProtoDecodableMessage {
|
||||
class HelloRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 1;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 27;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 17;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "hello_request"; }
|
||||
#endif
|
||||
const uint8_t *client_info{nullptr};
|
||||
uint16_t client_info_len{0};
|
||||
StringRef client_info{};
|
||||
uint32_t api_version_major{0};
|
||||
uint32_t api_version_minor{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -382,10 +378,8 @@ class HelloResponse final : public ProtoMessage {
|
||||
#endif
|
||||
uint32_t api_version_major{0};
|
||||
uint32_t api_version_minor{0};
|
||||
StringRef server_info_ref_{};
|
||||
void set_server_info(const StringRef &ref) { this->server_info_ref_ = ref; }
|
||||
StringRef name_ref_{};
|
||||
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||
StringRef server_info{};
|
||||
StringRef name{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -394,40 +388,6 @@ class HelloResponse final : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
};
|
||||
#ifdef USE_API_PASSWORD
|
||||
class AuthenticationRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 3;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 19;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "authentication_request"; }
|
||||
#endif
|
||||
const uint8_t *password{nullptr};
|
||||
uint16_t password_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class AuthenticationResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 4;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 2;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "authentication_response"; }
|
||||
#endif
|
||||
bool invalid_password{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
class DisconnectRequest final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 5;
|
||||
@@ -497,8 +457,7 @@ class DeviceInfoRequest final : public ProtoMessage {
|
||||
class AreaInfo final : public ProtoMessage {
|
||||
public:
|
||||
uint32_t area_id{0};
|
||||
StringRef name_ref_{};
|
||||
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||
StringRef name{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -512,8 +471,7 @@ class AreaInfo final : public ProtoMessage {
|
||||
class DeviceInfo final : public ProtoMessage {
|
||||
public:
|
||||
uint32_t device_id{0};
|
||||
StringRef name_ref_{};
|
||||
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||
StringRef name{};
|
||||
uint32_t area_id{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -527,33 +485,23 @@ class DeviceInfo final : public ProtoMessage {
|
||||
class DeviceInfoResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 10;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 257;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 255;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "device_info_response"; }
|
||||
#endif
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool uses_password{false};
|
||||
#endif
|
||||
StringRef name_ref_{};
|
||||
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||
StringRef mac_address_ref_{};
|
||||
void set_mac_address(const StringRef &ref) { this->mac_address_ref_ = ref; }
|
||||
StringRef esphome_version_ref_{};
|
||||
void set_esphome_version(const StringRef &ref) { this->esphome_version_ref_ = ref; }
|
||||
StringRef compilation_time_ref_{};
|
||||
void set_compilation_time(const StringRef &ref) { this->compilation_time_ref_ = ref; }
|
||||
StringRef model_ref_{};
|
||||
void set_model(const StringRef &ref) { this->model_ref_ = ref; }
|
||||
StringRef name{};
|
||||
StringRef mac_address{};
|
||||
StringRef esphome_version{};
|
||||
StringRef compilation_time{};
|
||||
StringRef model{};
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
bool has_deep_sleep{false};
|
||||
#endif
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
StringRef project_name_ref_{};
|
||||
void set_project_name(const StringRef &ref) { this->project_name_ref_ = ref; }
|
||||
StringRef project_name{};
|
||||
#endif
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
StringRef project_version_ref_{};
|
||||
void set_project_version(const StringRef &ref) { this->project_version_ref_ = ref; }
|
||||
StringRef project_version{};
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER
|
||||
uint32_t webserver_port{0};
|
||||
@@ -561,20 +509,16 @@ class DeviceInfoResponse final : public ProtoMessage {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
uint32_t bluetooth_proxy_feature_flags{0};
|
||||
#endif
|
||||
StringRef manufacturer_ref_{};
|
||||
void set_manufacturer(const StringRef &ref) { this->manufacturer_ref_ = ref; }
|
||||
StringRef friendly_name_ref_{};
|
||||
void set_friendly_name(const StringRef &ref) { this->friendly_name_ref_ = ref; }
|
||||
StringRef manufacturer{};
|
||||
StringRef friendly_name{};
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
uint32_t voice_assistant_feature_flags{0};
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
StringRef suggested_area_ref_{};
|
||||
void set_suggested_area(const StringRef &ref) { this->suggested_area_ref_ = ref; }
|
||||
StringRef suggested_area{};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
StringRef bluetooth_mac_address_ref_{};
|
||||
void set_bluetooth_mac_address(const StringRef &ref) { this->bluetooth_mac_address_ref_ = ref; }
|
||||
StringRef bluetooth_mac_address{};
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
bool api_encryption_supported{false};
|
||||
@@ -649,8 +593,7 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_binary_sensor_response"; }
|
||||
#endif
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
bool is_status_binary_sensor{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -689,8 +632,7 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage {
|
||||
bool assumed_state{false};
|
||||
bool supports_position{false};
|
||||
bool supports_tilt{false};
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
bool supports_stop{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -771,8 +713,7 @@ class FanStateResponse final : public StateResponseProtoMessage {
|
||||
bool oscillating{false};
|
||||
enums::FanDirection direction{};
|
||||
int32_t speed_level{0};
|
||||
StringRef preset_mode_ref_{};
|
||||
void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; }
|
||||
StringRef preset_mode{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -784,7 +725,7 @@ class FanStateResponse final : public StateResponseProtoMessage {
|
||||
class FanCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 31;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 48;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 38;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "fan_command_request"; }
|
||||
#endif
|
||||
@@ -797,8 +738,7 @@ class FanCommandRequest final : public CommandProtoMessage {
|
||||
bool has_speed_level{false};
|
||||
int32_t speed_level{0};
|
||||
bool has_preset_mode{false};
|
||||
const uint8_t *preset_mode{nullptr};
|
||||
uint16_t preset_mode_len{0};
|
||||
StringRef preset_mode{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -847,8 +787,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
|
||||
float color_temperature{0.0f};
|
||||
float cold_white{0.0f};
|
||||
float warm_white{0.0f};
|
||||
StringRef effect_ref_{};
|
||||
void set_effect(const StringRef &ref) { this->effect_ref_ = ref; }
|
||||
StringRef effect{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -860,7 +799,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
|
||||
class LightCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 32;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 122;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 112;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "light_command_request"; }
|
||||
#endif
|
||||
@@ -889,8 +828,7 @@ class LightCommandRequest final : public CommandProtoMessage {
|
||||
bool has_flash_length{false};
|
||||
uint32_t flash_length{0};
|
||||
bool has_effect{false};
|
||||
const uint8_t *effect{nullptr};
|
||||
uint16_t effect_len{0};
|
||||
StringRef effect{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -909,12 +847,10 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_sensor_response"; }
|
||||
#endif
|
||||
StringRef unit_of_measurement_ref_{};
|
||||
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
|
||||
StringRef unit_of_measurement{};
|
||||
int32_t accuracy_decimals{0};
|
||||
bool force_update{false};
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
enums::SensorStateClass state_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -951,8 +887,7 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage {
|
||||
const char *message_name() const override { return "list_entities_switch_response"; }
|
||||
#endif
|
||||
bool assumed_state{false};
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1002,8 +937,7 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_text_sensor_response"; }
|
||||
#endif
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1019,8 +953,7 @@ class TextSensorStateResponse final : public StateResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "text_sensor_state_response"; }
|
||||
#endif
|
||||
StringRef state_ref_{};
|
||||
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
|
||||
StringRef state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -1050,7 +983,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage {
|
||||
class SubscribeLogsResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 29;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 11;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 21;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "subscribe_logs_response"; }
|
||||
#endif
|
||||
@@ -1119,8 +1052,7 @@ class SubscribeHomeassistantServicesRequest final : public ProtoMessage {
|
||||
};
|
||||
class HomeassistantServiceMap final : public ProtoMessage {
|
||||
public:
|
||||
StringRef key_ref_{};
|
||||
void set_key(const StringRef &ref) { this->key_ref_ = ref; }
|
||||
StringRef key{};
|
||||
std::string value{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -1137,8 +1069,7 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "homeassistant_action_request"; }
|
||||
#endif
|
||||
StringRef service_ref_{};
|
||||
void set_service(const StringRef &ref) { this->service_ref_ = ref; }
|
||||
StringRef service{};
|
||||
FixedVector<HomeassistantServiceMap> data{};
|
||||
FixedVector<HomeassistantServiceMap> data_template{};
|
||||
FixedVector<HomeassistantServiceMap> variables{};
|
||||
@@ -1171,7 +1102,7 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
|
||||
#endif
|
||||
uint32_t call_id{0};
|
||||
bool success{false};
|
||||
std::string error_message{};
|
||||
StringRef error_message{};
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
const uint8_t *response_data{nullptr};
|
||||
uint16_t response_data_len{0};
|
||||
@@ -1206,10 +1137,8 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "subscribe_home_assistant_state_response"; }
|
||||
#endif
|
||||
StringRef entity_id_ref_{};
|
||||
void set_entity_id(const StringRef &ref) { this->entity_id_ref_ = ref; }
|
||||
StringRef attribute_ref_{};
|
||||
void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; }
|
||||
StringRef entity_id{};
|
||||
StringRef attribute{};
|
||||
bool once{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -1222,16 +1151,13 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
|
||||
class HomeAssistantStateResponse final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 40;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 57;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 27;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "home_assistant_state_response"; }
|
||||
#endif
|
||||
const uint8_t *entity_id{nullptr};
|
||||
uint16_t entity_id_len{0};
|
||||
const uint8_t *state{nullptr};
|
||||
uint16_t state_len{0};
|
||||
const uint8_t *attribute{nullptr};
|
||||
uint16_t attribute_len{0};
|
||||
StringRef entity_id{};
|
||||
StringRef state{};
|
||||
StringRef attribute{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1256,13 +1182,12 @@ class GetTimeRequest final : public ProtoMessage {
|
||||
class GetTimeResponse final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 37;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 24;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 14;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "get_time_response"; }
|
||||
#endif
|
||||
uint32_t epoch_seconds{0};
|
||||
const uint8_t *timezone{nullptr};
|
||||
uint16_t timezone_len{0};
|
||||
StringRef timezone{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1274,8 +1199,7 @@ class GetTimeResponse final : public ProtoDecodableMessage {
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
class ListEntitiesServicesArgument final : public ProtoMessage {
|
||||
public:
|
||||
StringRef name_ref_{};
|
||||
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||
StringRef name{};
|
||||
enums::ServiceArgType type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -1292,8 +1216,7 @@ class ListEntitiesServicesResponse final : public ProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_services_response"; }
|
||||
#endif
|
||||
StringRef name_ref_{};
|
||||
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||
StringRef name{};
|
||||
uint32_t key{0};
|
||||
FixedVector<ListEntitiesServicesArgument> args{};
|
||||
enums::SupportsResponseType supports_response{};
|
||||
@@ -1310,7 +1233,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
|
||||
bool bool_{false};
|
||||
int32_t legacy_int{0};
|
||||
float float_{0.0f};
|
||||
std::string string_{};
|
||||
StringRef string_{};
|
||||
int32_t int_{0};
|
||||
FixedVector<bool> bool_array{};
|
||||
FixedVector<int32_t> int_array{};
|
||||
@@ -1362,8 +1285,7 @@ class ExecuteServiceResponse final : public ProtoMessage {
|
||||
#endif
|
||||
uint32_t call_id{0};
|
||||
bool success{false};
|
||||
StringRef error_message_ref_{};
|
||||
void set_error_message(const StringRef &ref) { this->error_message_ref_ = ref; }
|
||||
StringRef error_message{};
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
const uint8_t *response_data{nullptr};
|
||||
uint16_t response_data_len{0};
|
||||
@@ -1396,7 +1318,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage {
|
||||
class CameraImageResponse final : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 44;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 20;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 30;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "camera_image_response"; }
|
||||
#endif
|
||||
@@ -1481,11 +1403,9 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
|
||||
enums::ClimateAction action{};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
StringRef custom_fan_mode_ref_{};
|
||||
void set_custom_fan_mode(const StringRef &ref) { this->custom_fan_mode_ref_ = ref; }
|
||||
StringRef custom_fan_mode{};
|
||||
enums::ClimatePreset preset{};
|
||||
StringRef custom_preset_ref_{};
|
||||
void set_custom_preset(const StringRef &ref) { this->custom_preset_ref_ = ref; }
|
||||
StringRef custom_preset{};
|
||||
float current_humidity{0.0f};
|
||||
float target_humidity{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -1499,7 +1419,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
|
||||
class ClimateCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 48;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 104;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 84;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "climate_command_request"; }
|
||||
#endif
|
||||
@@ -1516,13 +1436,11 @@ class ClimateCommandRequest final : public CommandProtoMessage {
|
||||
bool has_swing_mode{false};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
bool has_custom_fan_mode{false};
|
||||
const uint8_t *custom_fan_mode{nullptr};
|
||||
uint16_t custom_fan_mode_len{0};
|
||||
StringRef custom_fan_mode{};
|
||||
bool has_preset{false};
|
||||
enums::ClimatePreset preset{};
|
||||
bool has_custom_preset{false};
|
||||
const uint8_t *custom_preset{nullptr};
|
||||
uint16_t custom_preset_len{0};
|
||||
StringRef custom_preset{};
|
||||
bool has_target_humidity{false};
|
||||
float target_humidity{0.0f};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1610,11 +1528,9 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
|
||||
float min_value{0.0f};
|
||||
float max_value{0.0f};
|
||||
float step{0.0f};
|
||||
StringRef unit_of_measurement_ref_{};
|
||||
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
|
||||
StringRef unit_of_measurement{};
|
||||
enums::NumberMode mode{};
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1681,8 +1597,7 @@ class SelectStateResponse final : public StateResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "select_state_response"; }
|
||||
#endif
|
||||
StringRef state_ref_{};
|
||||
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
|
||||
StringRef state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -1695,12 +1610,11 @@ class SelectStateResponse final : public StateResponseProtoMessage {
|
||||
class SelectCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 54;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 28;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 18;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "select_command_request"; }
|
||||
#endif
|
||||
const uint8_t *state{nullptr};
|
||||
uint16_t state_len{0};
|
||||
StringRef state{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1719,7 +1633,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_siren_response"; }
|
||||
#endif
|
||||
std::vector<std::string> tones{};
|
||||
const FixedVector<const char *> *tones{};
|
||||
bool supports_duration{false};
|
||||
bool supports_volume{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -1756,7 +1670,7 @@ class SirenCommandRequest final : public CommandProtoMessage {
|
||||
bool has_state{false};
|
||||
bool state{false};
|
||||
bool has_tone{false};
|
||||
std::string tone{};
|
||||
StringRef tone{};
|
||||
bool has_duration{false};
|
||||
uint32_t duration{0};
|
||||
bool has_volume{false};
|
||||
@@ -1782,8 +1696,7 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage {
|
||||
bool assumed_state{false};
|
||||
bool supports_open{false};
|
||||
bool requires_code{false};
|
||||
StringRef code_format_ref_{};
|
||||
void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; }
|
||||
StringRef code_format{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1817,7 +1730,7 @@ class LockCommandRequest final : public CommandProtoMessage {
|
||||
#endif
|
||||
enums::LockCommand command{};
|
||||
bool has_code{false};
|
||||
std::string code{};
|
||||
StringRef code{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1836,8 +1749,7 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_button_response"; }
|
||||
#endif
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1865,8 +1777,7 @@ class ButtonCommandRequest final : public CommandProtoMessage {
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
class MediaPlayerSupportedFormat final : public ProtoMessage {
|
||||
public:
|
||||
StringRef format_ref_{};
|
||||
void set_format(const StringRef &ref) { this->format_ref_ = ref; }
|
||||
StringRef format{};
|
||||
uint32_t sample_rate{0};
|
||||
uint32_t num_channels{0};
|
||||
enums::MediaPlayerFormatPurpose purpose{};
|
||||
@@ -1927,7 +1838,7 @@ class MediaPlayerCommandRequest final : public CommandProtoMessage {
|
||||
bool has_volume{false};
|
||||
float volume{0.0f};
|
||||
bool has_media_url{false};
|
||||
std::string media_url{};
|
||||
StringRef media_url{};
|
||||
bool has_announcement{false};
|
||||
bool announcement{false};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -2134,7 +2045,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage {
|
||||
class BluetoothGATTReadResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 74;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 17;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 27;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "bluetooth_gatt_read_response"; }
|
||||
#endif
|
||||
@@ -2229,7 +2140,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
|
||||
class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 79;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 17;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 27;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; }
|
||||
#endif
|
||||
@@ -2471,12 +2382,10 @@ class VoiceAssistantRequest final : public ProtoMessage {
|
||||
const char *message_name() const override { return "voice_assistant_request"; }
|
||||
#endif
|
||||
bool start{false};
|
||||
StringRef conversation_id_ref_{};
|
||||
void set_conversation_id(const StringRef &ref) { this->conversation_id_ref_ = ref; }
|
||||
StringRef conversation_id{};
|
||||
uint32_t flags{0};
|
||||
VoiceAssistantAudioSettings audio_settings{};
|
||||
StringRef wake_word_phrase_ref_{};
|
||||
void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; }
|
||||
StringRef wake_word_phrase{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -2503,8 +2412,8 @@ class VoiceAssistantResponse final : public ProtoDecodableMessage {
|
||||
};
|
||||
class VoiceAssistantEventData final : public ProtoDecodableMessage {
|
||||
public:
|
||||
std::string name{};
|
||||
std::string value{};
|
||||
StringRef name{};
|
||||
StringRef value{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -2532,17 +2441,12 @@ class VoiceAssistantEventResponse final : public ProtoDecodableMessage {
|
||||
class VoiceAssistantAudio final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 106;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 11;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 21;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "voice_assistant_audio"; }
|
||||
#endif
|
||||
std::string data{};
|
||||
const uint8_t *data_ptr_{nullptr};
|
||||
size_t data_len_{0};
|
||||
void set_data(const uint8_t *data, size_t len) {
|
||||
this->data_ptr_ = data;
|
||||
this->data_len_ = len;
|
||||
}
|
||||
const uint8_t *data{nullptr};
|
||||
uint16_t data_len{0};
|
||||
bool end{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -2562,8 +2466,8 @@ class VoiceAssistantTimerEventResponse final : public ProtoDecodableMessage {
|
||||
const char *message_name() const override { return "voice_assistant_timer_event_response"; }
|
||||
#endif
|
||||
enums::VoiceAssistantTimerEvent event_type{};
|
||||
std::string timer_id{};
|
||||
std::string name{};
|
||||
StringRef timer_id{};
|
||||
StringRef name{};
|
||||
uint32_t total_seconds{0};
|
||||
uint32_t seconds_left{0};
|
||||
bool is_active{false};
|
||||
@@ -2582,9 +2486,9 @@ class VoiceAssistantAnnounceRequest final : public ProtoDecodableMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "voice_assistant_announce_request"; }
|
||||
#endif
|
||||
std::string media_id{};
|
||||
std::string text{};
|
||||
std::string preannounce_media_id{};
|
||||
StringRef media_id{};
|
||||
StringRef text{};
|
||||
StringRef preannounce_media_id{};
|
||||
bool start_conversation{false};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -2612,10 +2516,8 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage {
|
||||
};
|
||||
class VoiceAssistantWakeWord final : public ProtoMessage {
|
||||
public:
|
||||
StringRef id_ref_{};
|
||||
void set_id(const StringRef &ref) { this->id_ref_ = ref; }
|
||||
StringRef wake_word_ref_{};
|
||||
void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; }
|
||||
StringRef id{};
|
||||
StringRef wake_word{};
|
||||
std::vector<std::string> trained_languages{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -2627,13 +2529,13 @@ class VoiceAssistantWakeWord final : public ProtoMessage {
|
||||
};
|
||||
class VoiceAssistantExternalWakeWord final : public ProtoDecodableMessage {
|
||||
public:
|
||||
std::string id{};
|
||||
std::string wake_word{};
|
||||
StringRef id{};
|
||||
StringRef wake_word{};
|
||||
std::vector<std::string> trained_languages{};
|
||||
std::string model_type{};
|
||||
StringRef model_type{};
|
||||
uint32_t model_size{0};
|
||||
std::string model_hash{};
|
||||
std::string url{};
|
||||
StringRef model_hash{};
|
||||
StringRef url{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -2734,7 +2636,7 @@ class AlarmControlPanelCommandRequest final : public CommandProtoMessage {
|
||||
const char *message_name() const override { return "alarm_control_panel_command_request"; }
|
||||
#endif
|
||||
enums::AlarmControlPanelStateCommand command{};
|
||||
std::string code{};
|
||||
StringRef code{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -2755,8 +2657,7 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage {
|
||||
#endif
|
||||
uint32_t min_length{0};
|
||||
uint32_t max_length{0};
|
||||
StringRef pattern_ref_{};
|
||||
void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; }
|
||||
StringRef pattern{};
|
||||
enums::TextMode mode{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -2773,8 +2674,7 @@ class TextStateResponse final : public StateResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "text_state_response"; }
|
||||
#endif
|
||||
StringRef state_ref_{};
|
||||
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
|
||||
StringRef state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -2791,7 +2691,7 @@ class TextCommandRequest final : public CommandProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "text_command_request"; }
|
||||
#endif
|
||||
std::string state{};
|
||||
StringRef state{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -2918,8 +2818,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_event_response"; }
|
||||
#endif
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
const FixedVector<const char *> *event_types{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -2936,8 +2835,7 @@ class EventResponse final : public StateResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "event_response"; }
|
||||
#endif
|
||||
StringRef event_type_ref_{};
|
||||
void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; }
|
||||
StringRef event_type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -2955,8 +2853,7 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_valve_response"; }
|
||||
#endif
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
bool assumed_state{false};
|
||||
bool supports_position{false};
|
||||
bool supports_stop{false};
|
||||
@@ -3062,8 +2959,7 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_update_response"; }
|
||||
#endif
|
||||
StringRef device_class_ref_{};
|
||||
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -3083,16 +2979,11 @@ class UpdateStateResponse final : public StateResponseProtoMessage {
|
||||
bool in_progress{false};
|
||||
bool has_progress{false};
|
||||
float progress{0.0f};
|
||||
StringRef current_version_ref_{};
|
||||
void set_current_version(const StringRef &ref) { this->current_version_ref_ = ref; }
|
||||
StringRef latest_version_ref_{};
|
||||
void set_latest_version(const StringRef &ref) { this->latest_version_ref_ = ref; }
|
||||
StringRef title_ref_{};
|
||||
void set_title(const StringRef &ref) { this->title_ref_ = ref; }
|
||||
StringRef release_summary_ref_{};
|
||||
void set_release_summary(const StringRef &ref) { this->release_summary_ref_ = ref; }
|
||||
StringRef release_url_ref_{};
|
||||
void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; }
|
||||
StringRef current_version{};
|
||||
StringRef latest_version{};
|
||||
StringRef title{};
|
||||
StringRef release_summary{};
|
||||
StringRef release_url{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
|
||||
@@ -735,9 +735,7 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
|
||||
|
||||
void HelloRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HelloRequest");
|
||||
out.append(" client_info: ");
|
||||
out.append(format_hex_pretty(this->client_info, this->client_info_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "client_info", this->client_info);
|
||||
dump_field(out, "api_version_major", this->api_version_major);
|
||||
dump_field(out, "api_version_minor", this->api_version_minor);
|
||||
}
|
||||
@@ -745,21 +743,9 @@ void HelloResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HelloResponse");
|
||||
dump_field(out, "api_version_major", this->api_version_major);
|
||||
dump_field(out, "api_version_minor", this->api_version_minor);
|
||||
dump_field(out, "server_info", this->server_info_ref_);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "server_info", this->server_info);
|
||||
dump_field(out, "name", this->name);
|
||||
}
|
||||
#ifdef USE_API_PASSWORD
|
||||
void AuthenticationRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "AuthenticationRequest");
|
||||
out.append(" password: ");
|
||||
out.append(format_hex_pretty(this->password, this->password_len));
|
||||
out.append("\n");
|
||||
}
|
||||
void AuthenticationResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "AuthenticationResponse");
|
||||
dump_field(out, "invalid_password", this->invalid_password);
|
||||
}
|
||||
#endif
|
||||
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
|
||||
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
|
||||
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
|
||||
@@ -769,35 +755,32 @@ void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfo
|
||||
void AreaInfo::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "AreaInfo");
|
||||
dump_field(out, "area_id", this->area_id);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
void DeviceInfo::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "DeviceInfo");
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "area_id", this->area_id);
|
||||
}
|
||||
#endif
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "DeviceInfoResponse");
|
||||
#ifdef USE_API_PASSWORD
|
||||
dump_field(out, "uses_password", this->uses_password);
|
||||
#endif
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "mac_address", this->mac_address_ref_);
|
||||
dump_field(out, "esphome_version", this->esphome_version_ref_);
|
||||
dump_field(out, "compilation_time", this->compilation_time_ref_);
|
||||
dump_field(out, "model", this->model_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "mac_address", this->mac_address);
|
||||
dump_field(out, "esphome_version", this->esphome_version);
|
||||
dump_field(out, "compilation_time", this->compilation_time);
|
||||
dump_field(out, "model", this->model);
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
dump_field(out, "has_deep_sleep", this->has_deep_sleep);
|
||||
#endif
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
dump_field(out, "project_name", this->project_name_ref_);
|
||||
dump_field(out, "project_name", this->project_name);
|
||||
#endif
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
dump_field(out, "project_version", this->project_version_ref_);
|
||||
dump_field(out, "project_version", this->project_version);
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER
|
||||
dump_field(out, "webserver_port", this->webserver_port);
|
||||
@@ -805,16 +788,16 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
dump_field(out, "bluetooth_proxy_feature_flags", this->bluetooth_proxy_feature_flags);
|
||||
#endif
|
||||
dump_field(out, "manufacturer", this->manufacturer_ref_);
|
||||
dump_field(out, "friendly_name", this->friendly_name_ref_);
|
||||
dump_field(out, "manufacturer", this->manufacturer);
|
||||
dump_field(out, "friendly_name", this->friendly_name);
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
dump_field(out, "voice_assistant_feature_flags", this->voice_assistant_feature_flags);
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
dump_field(out, "suggested_area", this->suggested_area_ref_);
|
||||
dump_field(out, "suggested_area", this->suggested_area);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address_ref_);
|
||||
dump_field(out, "bluetooth_mac_address", this->bluetooth_mac_address);
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
dump_field(out, "api_encryption_supported", this->api_encryption_supported);
|
||||
@@ -851,14 +834,14 @@ void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("Subsc
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
dump_field(out, "is_status_binary_sensor", this->is_status_binary_sensor);
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -878,16 +861,16 @@ void BinarySensorStateResponse::dump_to(std::string &out) const {
|
||||
#ifdef USE_COVER
|
||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesCoverResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "assumed_state", this->assumed_state);
|
||||
dump_field(out, "supports_position", this->supports_position);
|
||||
dump_field(out, "supports_tilt", this->supports_tilt);
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "supports_stop", this->supports_stop);
|
||||
@@ -921,16 +904,16 @@ void CoverCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_FAN
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesFanResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "supports_oscillation", this->supports_oscillation);
|
||||
dump_field(out, "supports_speed", this->supports_speed);
|
||||
dump_field(out, "supports_direction", this->supports_direction);
|
||||
dump_field(out, "supported_speed_count", this->supported_speed_count);
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
for (const auto &it : *this->supported_preset_modes) {
|
||||
@@ -947,7 +930,7 @@ void FanStateResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "oscillating", this->oscillating);
|
||||
dump_field(out, "direction", static_cast<enums::FanDirection>(this->direction));
|
||||
dump_field(out, "speed_level", this->speed_level);
|
||||
dump_field(out, "preset_mode", this->preset_mode_ref_);
|
||||
dump_field(out, "preset_mode", this->preset_mode);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -964,9 +947,7 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "has_speed_level", this->has_speed_level);
|
||||
dump_field(out, "speed_level", this->speed_level);
|
||||
dump_field(out, "has_preset_mode", this->has_preset_mode);
|
||||
out.append(" preset_mode: ");
|
||||
out.append(format_hex_pretty(this->preset_mode, this->preset_mode_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "preset_mode", this->preset_mode);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -975,9 +956,9 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_LIGHT
|
||||
void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesLightResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
for (const auto &it : *this->supported_color_modes) {
|
||||
dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4);
|
||||
}
|
||||
@@ -988,7 +969,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
}
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1009,7 +990,7 @@ void LightStateResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "color_temperature", this->color_temperature);
|
||||
dump_field(out, "cold_white", this->cold_white);
|
||||
dump_field(out, "warm_white", this->warm_white);
|
||||
dump_field(out, "effect", this->effect_ref_);
|
||||
dump_field(out, "effect", this->effect);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1042,9 +1023,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "has_flash_length", this->has_flash_length);
|
||||
dump_field(out, "flash_length", this->flash_length);
|
||||
dump_field(out, "has_effect", this->has_effect);
|
||||
out.append(" effect: ");
|
||||
out.append(format_hex_pretty(this->effect, this->effect_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "effect", this->effect);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1053,16 +1032,16 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_SENSOR
|
||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesSensorResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_);
|
||||
dump_field(out, "unit_of_measurement", this->unit_of_measurement);
|
||||
dump_field(out, "accuracy_decimals", this->accuracy_decimals);
|
||||
dump_field(out, "force_update", this->force_update);
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
dump_field(out, "state_class", static_cast<enums::SensorStateClass>(this->state_class));
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
@@ -1083,16 +1062,16 @@ void SensorStateResponse::dump_to(std::string &out) const {
|
||||
#ifdef USE_SWITCH
|
||||
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesSwitchResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "assumed_state", this->assumed_state);
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1117,15 +1096,15 @@ void SwitchCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesTextSensorResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1133,7 +1112,7 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
void TextSensorStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "TextSensorStateResponse");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "state", this->state_ref_);
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "missing_state", this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
@@ -1159,7 +1138,10 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
|
||||
out.append(format_hex_pretty(this->key, this->key_len));
|
||||
out.append("\n");
|
||||
}
|
||||
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
|
||||
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyResponse");
|
||||
dump_field(out, "success", this->success);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
|
||||
@@ -1167,12 +1149,12 @@ void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
|
||||
}
|
||||
void HomeassistantServiceMap::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HomeassistantServiceMap");
|
||||
dump_field(out, "key", this->key_ref_);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "value", this->value);
|
||||
}
|
||||
void HomeassistantActionRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HomeassistantActionRequest");
|
||||
dump_field(out, "service", this->service_ref_);
|
||||
dump_field(out, "service", this->service);
|
||||
for (const auto &it : this->data) {
|
||||
out.append(" data: ");
|
||||
it.dump_to(out);
|
||||
@@ -1219,40 +1201,32 @@ void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
|
||||
}
|
||||
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse");
|
||||
dump_field(out, "entity_id", this->entity_id_ref_);
|
||||
dump_field(out, "attribute", this->attribute_ref_);
|
||||
dump_field(out, "entity_id", this->entity_id);
|
||||
dump_field(out, "attribute", this->attribute);
|
||||
dump_field(out, "once", this->once);
|
||||
}
|
||||
void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HomeAssistantStateResponse");
|
||||
out.append(" entity_id: ");
|
||||
out.append(format_hex_pretty(this->entity_id, this->entity_id_len));
|
||||
out.append("\n");
|
||||
out.append(" state: ");
|
||||
out.append(format_hex_pretty(this->state, this->state_len));
|
||||
out.append("\n");
|
||||
out.append(" attribute: ");
|
||||
out.append(format_hex_pretty(this->attribute, this->attribute_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "entity_id", this->entity_id);
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "attribute", this->attribute);
|
||||
}
|
||||
#endif
|
||||
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
|
||||
void GetTimeResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "GetTimeResponse");
|
||||
dump_field(out, "epoch_seconds", this->epoch_seconds);
|
||||
out.append(" timezone: ");
|
||||
out.append(format_hex_pretty(this->timezone, this->timezone_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "timezone", this->timezone);
|
||||
}
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "type", static_cast<enums::ServiceArgType>(this->type));
|
||||
}
|
||||
void ListEntitiesServicesResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesServicesResponse");
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "key", this->key);
|
||||
for (const auto &it : this->args) {
|
||||
out.append(" args: ");
|
||||
@@ -1302,7 +1276,7 @@ void ExecuteServiceResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ExecuteServiceResponse");
|
||||
dump_field(out, "call_id", this->call_id);
|
||||
dump_field(out, "success", this->success);
|
||||
dump_field(out, "error_message", this->error_message_ref_);
|
||||
dump_field(out, "error_message", this->error_message);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
out.append(" response_data: ");
|
||||
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
||||
@@ -1313,12 +1287,12 @@ void ExecuteServiceResponse::dump_to(std::string &out) const {
|
||||
#ifdef USE_CAMERA
|
||||
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesCameraResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1345,9 +1319,9 @@ void CameraImageRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_CLIMATE
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesClimateResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "supports_current_temperature", this->supports_current_temperature);
|
||||
dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature);
|
||||
for (const auto &it : *this->supported_modes) {
|
||||
@@ -1374,7 +1348,7 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
}
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "visual_current_temperature_step", this->visual_current_temperature_step);
|
||||
@@ -1398,9 +1372,9 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "action", static_cast<enums::ClimateAction>(this->action));
|
||||
dump_field(out, "fan_mode", static_cast<enums::ClimateFanMode>(this->fan_mode));
|
||||
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
|
||||
dump_field(out, "custom_fan_mode", this->custom_fan_mode_ref_);
|
||||
dump_field(out, "custom_fan_mode", this->custom_fan_mode);
|
||||
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
|
||||
dump_field(out, "custom_preset", this->custom_preset_ref_);
|
||||
dump_field(out, "custom_preset", this->custom_preset);
|
||||
dump_field(out, "current_humidity", this->current_humidity);
|
||||
dump_field(out, "target_humidity", this->target_humidity);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1423,15 +1397,11 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "has_swing_mode", this->has_swing_mode);
|
||||
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
|
||||
dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode);
|
||||
out.append(" custom_fan_mode: ");
|
||||
out.append(format_hex_pretty(this->custom_fan_mode, this->custom_fan_mode_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "custom_fan_mode", this->custom_fan_mode);
|
||||
dump_field(out, "has_preset", this->has_preset);
|
||||
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
|
||||
dump_field(out, "has_custom_preset", this->has_custom_preset);
|
||||
out.append(" custom_preset: ");
|
||||
out.append(format_hex_pretty(this->custom_preset, this->custom_preset_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "custom_preset", this->custom_preset);
|
||||
dump_field(out, "has_target_humidity", this->has_target_humidity);
|
||||
dump_field(out, "target_humidity", this->target_humidity);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1442,11 +1412,11 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_WATER_HEATER
|
||||
void ListEntitiesWaterHeaterResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
@@ -1491,20 +1461,20 @@ void WaterHeaterCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_NUMBER
|
||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesNumberResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "min_value", this->min_value);
|
||||
dump_field(out, "max_value", this->max_value);
|
||||
dump_field(out, "step", this->step);
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "unit_of_measurement", this->unit_of_measurement_ref_);
|
||||
dump_field(out, "unit_of_measurement", this->unit_of_measurement);
|
||||
dump_field(out, "mode", static_cast<enums::NumberMode>(this->mode));
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1530,11 +1500,11 @@ void NumberCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_SELECT
|
||||
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesSelectResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
for (const auto &it : *this->options) {
|
||||
dump_field(out, "options", it, 4);
|
||||
@@ -1548,7 +1518,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
|
||||
void SelectStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "SelectStateResponse");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "state", this->state_ref_);
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "missing_state", this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
@@ -1557,9 +1527,7 @@ void SelectStateResponse::dump_to(std::string &out) const {
|
||||
void SelectCommandRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "SelectCommandRequest");
|
||||
dump_field(out, "key", this->key);
|
||||
out.append(" state: ");
|
||||
out.append(format_hex_pretty(this->state, this->state_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "state", this->state);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1568,14 +1536,14 @@ void SelectCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_SIREN
|
||||
void ListEntitiesSirenResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesSirenResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
for (const auto &it : this->tones) {
|
||||
for (const auto &it : *this->tones) {
|
||||
dump_field(out, "tones", it, 4);
|
||||
}
|
||||
dump_field(out, "supports_duration", this->supports_duration);
|
||||
@@ -1612,18 +1580,18 @@ void SirenCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_LOCK
|
||||
void ListEntitiesLockResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesLockResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "assumed_state", this->assumed_state);
|
||||
dump_field(out, "supports_open", this->supports_open);
|
||||
dump_field(out, "requires_code", this->requires_code);
|
||||
dump_field(out, "code_format", this->code_format_ref_);
|
||||
dump_field(out, "code_format", this->code_format);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1650,15 +1618,15 @@ void LockCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_BUTTON
|
||||
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesButtonResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1674,7 +1642,7 @@ void ButtonCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "MediaPlayerSupportedFormat");
|
||||
dump_field(out, "format", this->format_ref_);
|
||||
dump_field(out, "format", this->format);
|
||||
dump_field(out, "sample_rate", this->sample_rate);
|
||||
dump_field(out, "num_channels", this->num_channels);
|
||||
dump_field(out, "purpose", static_cast<enums::MediaPlayerFormatPurpose>(this->purpose));
|
||||
@@ -1682,11 +1650,11 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
|
||||
}
|
||||
void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesMediaPlayerResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
@@ -1763,7 +1731,10 @@ void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "mtu", this->mtu);
|
||||
dump_field(out, "error", this->error);
|
||||
}
|
||||
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const { dump_field(out, "address", this->address); }
|
||||
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTGetServicesRequest");
|
||||
dump_field(out, "address", this->address);
|
||||
}
|
||||
void BluetoothGATTDescriptor::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTDescriptor");
|
||||
for (const auto &it : this->uuid) {
|
||||
@@ -1935,12 +1906,12 @@ void VoiceAssistantAudioSettings::dump_to(std::string &out) const {
|
||||
void VoiceAssistantRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantRequest");
|
||||
dump_field(out, "start", this->start);
|
||||
dump_field(out, "conversation_id", this->conversation_id_ref_);
|
||||
dump_field(out, "conversation_id", this->conversation_id);
|
||||
dump_field(out, "flags", this->flags);
|
||||
out.append(" audio_settings: ");
|
||||
this->audio_settings.dump_to(out);
|
||||
out.append("\n");
|
||||
dump_field(out, "wake_word_phrase", this->wake_word_phrase_ref_);
|
||||
dump_field(out, "wake_word_phrase", this->wake_word_phrase);
|
||||
}
|
||||
void VoiceAssistantResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantResponse");
|
||||
@@ -1964,11 +1935,7 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
|
||||
void VoiceAssistantAudio::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantAudio");
|
||||
out.append(" data: ");
|
||||
if (this->data_ptr_ != nullptr) {
|
||||
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
|
||||
} else {
|
||||
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
|
||||
}
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "end", this->end);
|
||||
}
|
||||
@@ -1988,11 +1955,14 @@ void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "preannounce_media_id", this->preannounce_media_id);
|
||||
dump_field(out, "start_conversation", this->start_conversation);
|
||||
}
|
||||
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
|
||||
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantAnnounceFinished");
|
||||
dump_field(out, "success", this->success);
|
||||
}
|
||||
void VoiceAssistantWakeWord::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantWakeWord");
|
||||
dump_field(out, "id", this->id_ref_);
|
||||
dump_field(out, "wake_word", this->wake_word_ref_);
|
||||
dump_field(out, "id", this->id);
|
||||
dump_field(out, "wake_word", this->wake_word);
|
||||
for (const auto &it : this->trained_languages) {
|
||||
dump_field(out, "trained_languages", it, 4);
|
||||
}
|
||||
@@ -2039,11 +2009,11 @@ void VoiceAssistantSetConfiguration::dump_to(std::string &out) const {
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesAlarmControlPanelResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
@@ -2075,17 +2045,17 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_TEXT
|
||||
void ListEntitiesTextResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesTextResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "min_length", this->min_length);
|
||||
dump_field(out, "max_length", this->max_length);
|
||||
dump_field(out, "pattern", this->pattern_ref_);
|
||||
dump_field(out, "pattern", this->pattern);
|
||||
dump_field(out, "mode", static_cast<enums::TextMode>(this->mode));
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
@@ -2094,7 +2064,7 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const {
|
||||
void TextStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "TextStateResponse");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "state", this->state_ref_);
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "missing_state", this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
@@ -2112,11 +2082,11 @@ void TextCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void ListEntitiesDateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesDateResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
@@ -2149,11 +2119,11 @@ void DateCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void ListEntitiesTimeResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesTimeResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
@@ -2186,15 +2156,15 @@ void TimeCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_EVENT
|
||||
void ListEntitiesEventResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesEventResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
for (const auto &it : *this->event_types) {
|
||||
dump_field(out, "event_types", it, 4);
|
||||
}
|
||||
@@ -2205,7 +2175,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
|
||||
void EventResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "EventResponse");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "event_type", this->event_type_ref_);
|
||||
dump_field(out, "event_type", this->event_type);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -2214,15 +2184,15 @@ void EventResponse::dump_to(std::string &out) const {
|
||||
#ifdef USE_VALVE
|
||||
void ListEntitiesValveResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesValveResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
dump_field(out, "assumed_state", this->assumed_state);
|
||||
dump_field(out, "supports_position", this->supports_position);
|
||||
dump_field(out, "supports_stop", this->supports_stop);
|
||||
@@ -2253,11 +2223,11 @@ void ValveCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesDateTimeResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
@@ -2286,15 +2256,15 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_UPDATE
|
||||
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesUpdateResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
dump_field(out, "device_class", this->device_class_ref_);
|
||||
dump_field(out, "device_class", this->device_class);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -2306,11 +2276,11 @@ void UpdateStateResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "in_progress", this->in_progress);
|
||||
dump_field(out, "has_progress", this->has_progress);
|
||||
dump_field(out, "progress", this->progress);
|
||||
dump_field(out, "current_version", this->current_version_ref_);
|
||||
dump_field(out, "latest_version", this->latest_version_ref_);
|
||||
dump_field(out, "title", this->title_ref_);
|
||||
dump_field(out, "release_summary", this->release_summary_ref_);
|
||||
dump_field(out, "release_url", this->release_url_ref_);
|
||||
dump_field(out, "current_version", this->current_version);
|
||||
dump_field(out, "latest_version", this->latest_version);
|
||||
dump_field(out, "title", this->title);
|
||||
dump_field(out, "release_summary", this->release_summary);
|
||||
dump_field(out, "release_url", this->release_url);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
|
||||
@@ -24,17 +24,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
this->on_hello_request(msg);
|
||||
break;
|
||||
}
|
||||
#ifdef USE_API_PASSWORD
|
||||
case AuthenticationRequest::MESSAGE_TYPE: {
|
||||
AuthenticationRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_authentication_request(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case DisconnectRequest::MESSAGE_TYPE: {
|
||||
DisconnectRequest msg;
|
||||
// Empty message: no decode needed
|
||||
@@ -643,13 +632,6 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
#ifdef USE_API_PASSWORD
|
||||
void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) {
|
||||
if (!this->send_authenticate_response(msg)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
||||
if (!this->send_disconnect_response(msg)) {
|
||||
this->on_fatal_error();
|
||||
@@ -841,10 +823,7 @@ void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg)
|
||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
// Check authentication/connection requirements for messages
|
||||
switch (msg_type) {
|
||||
case HelloRequest::MESSAGE_TYPE: // No setup required
|
||||
#ifdef USE_API_PASSWORD
|
||||
case AuthenticationRequest::MESSAGE_TYPE: // No setup required
|
||||
#endif
|
||||
case HelloRequest::MESSAGE_TYPE: // No setup required
|
||||
case DisconnectRequest::MESSAGE_TYPE: // No setup required
|
||||
case PingRequest::MESSAGE_TYPE: // No setup required
|
||||
break; // Skip all checks for these messages
|
||||
|
||||
@@ -26,10 +26,6 @@ class APIServerConnectionBase : public ProtoService {
|
||||
|
||||
virtual void on_hello_request(const HelloRequest &value){};
|
||||
|
||||
#ifdef USE_API_PASSWORD
|
||||
virtual void on_authentication_request(const AuthenticationRequest &value){};
|
||||
#endif
|
||||
|
||||
virtual void on_disconnect_request(const DisconnectRequest &value){};
|
||||
virtual void on_disconnect_response(const DisconnectResponse &value){};
|
||||
virtual void on_ping_request(const PingRequest &value){};
|
||||
@@ -228,9 +224,6 @@ class APIServerConnectionBase : public ProtoService {
|
||||
class APIServerConnection : public APIServerConnectionBase {
|
||||
public:
|
||||
virtual bool send_hello_response(const HelloRequest &msg) = 0;
|
||||
#ifdef USE_API_PASSWORD
|
||||
virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0;
|
||||
#endif
|
||||
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
|
||||
virtual bool send_ping_response(const PingRequest &msg) = 0;
|
||||
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
|
||||
@@ -357,9 +350,6 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
#ifdef USE_API_PASSWORD
|
||||
void on_authentication_request(const AuthenticationRequest &msg) override;
|
||||
#endif
|
||||
void on_disconnect_request(const DisconnectRequest &msg) override;
|
||||
void on_ping_request(const PingRequest &msg) override;
|
||||
void on_device_info_request(const DeviceInfoRequest &msg) override;
|
||||
|
||||
@@ -125,15 +125,18 @@ void APIServer::loop() {
|
||||
if (!sock)
|
||||
break;
|
||||
|
||||
char peername[socket::SOCKADDR_STR_LEN];
|
||||
sock->getpeername_to(peername);
|
||||
|
||||
// Check if we're at the connection limit
|
||||
if (this->clients_.size() >= this->max_connections_) {
|
||||
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
|
||||
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername);
|
||||
// Immediately close - socket destructor will handle cleanup
|
||||
sock.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
||||
ESP_LOGD(TAG, "Accept %s", peername);
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
this->clients_.emplace_back(conn);
|
||||
@@ -166,8 +169,7 @@ void APIServer::loop() {
|
||||
// Network is down - disconnect all clients
|
||||
for (auto &client : this->clients_) {
|
||||
client->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
|
||||
client->client_info_.peername.c_str());
|
||||
client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect"));
|
||||
}
|
||||
// Continue to process and clean up the clients below
|
||||
}
|
||||
@@ -184,13 +186,16 @@ void APIServer::loop() {
|
||||
}
|
||||
|
||||
// Rare case: handle disconnection
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
|
||||
#endif
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
this->unregister_active_action_calls_for_connection(client.get());
|
||||
#endif
|
||||
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str());
|
||||
ESP_LOGV(TAG, "Remove connection %s", client->get_name());
|
||||
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
// Save client info before removal for the trigger
|
||||
std::string client_name(client->get_name());
|
||||
std::string client_peername(client->get_peername());
|
||||
#endif
|
||||
|
||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||
if (client_index < this->clients_.size() - 1) {
|
||||
@@ -203,6 +208,11 @@ void APIServer::loop() {
|
||||
this->status_set_warning();
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
// Fire trigger after client is removed so api.connected reflects the true state
|
||||
this->client_disconnected_trigger_->trigger(client_name, client_peername);
|
||||
#endif
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
}
|
||||
}
|
||||
@@ -224,38 +234,6 @@ void APIServer::dump_config() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
|
||||
// depend only on input password length
|
||||
const char *a = this->password_.c_str();
|
||||
uint32_t len_a = this->password_.length();
|
||||
const char *b = reinterpret_cast<const char *>(password_data);
|
||||
uint32_t len_b = password_len;
|
||||
|
||||
// disable optimization with volatile
|
||||
volatile uint32_t length = len_b;
|
||||
volatile const char *left = nullptr;
|
||||
volatile const char *right = b;
|
||||
uint8_t result = 0;
|
||||
|
||||
if (len_a == length) {
|
||||
left = *((volatile const char **) &a);
|
||||
result = 0;
|
||||
}
|
||||
if (len_a != length) {
|
||||
left = b;
|
||||
result = 1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
result |= *left++ ^ *right++; // NOLINT
|
||||
}
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
|
||||
// Macro for controller update dispatch
|
||||
@@ -377,10 +355,6 @@ float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI;
|
||||
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
|
||||
#ifdef USE_API_PASSWORD
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
#endif
|
||||
|
||||
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
@@ -394,7 +368,7 @@ void APIServer::register_action_response_callback(uint32_t call_id, ActionRespon
|
||||
this->action_response_callbacks_.push_back({call_id, std::move(callback)});
|
||||
}
|
||||
|
||||
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) {
|
||||
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message) {
|
||||
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
|
||||
if (it->call_id == call_id) {
|
||||
auto callback = std::move(it->callback);
|
||||
@@ -406,7 +380,7 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
|
||||
}
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
|
||||
void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef error_message,
|
||||
const uint8_t *response_data, size_t response_data_len) {
|
||||
for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
|
||||
if (it->call_id == call_id) {
|
||||
@@ -424,8 +398,8 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, const std
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
// Helper to add subscription (reduces duplication)
|
||||
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute,
|
||||
std::function<void(std::string)> f, bool once) {
|
||||
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
|
||||
bool once) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once,
|
||||
// entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation)
|
||||
@@ -434,7 +408,7 @@ void APIServer::add_state_subscription_(const char *entity_id, const char *attri
|
||||
|
||||
// Helper to add subscription with heap-allocated strings (reduces duplication)
|
||||
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f, bool once) {
|
||||
std::function<void(StringRef)> f, bool once) {
|
||||
HomeAssistantStateSubscription sub;
|
||||
// Allocate heap storage for the strings
|
||||
sub.entity_id_dynamic_storage = std::make_unique<std::string>(std::move(entity_id));
|
||||
@@ -454,23 +428,43 @@ void APIServer::add_state_subscription_(std::string entity_id, optional<std::str
|
||||
|
||||
// New const char* overload (for internal components - zero allocation)
|
||||
void APIServer::subscribe_home_assistant_state(const char *entity_id, const char *attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
std::function<void(StringRef)> f) {
|
||||
this->add_state_subscription_(entity_id, attribute, std::move(f), false);
|
||||
}
|
||||
|
||||
void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
std::function<void(StringRef)> f) {
|
||||
this->add_state_subscription_(entity_id, attribute, std::move(f), true);
|
||||
}
|
||||
|
||||
// Existing std::string overload (for custom_api_device.h - heap allocation)
|
||||
// std::string overload with StringRef callback (zero-allocation callback)
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
std::function<void(StringRef)> f) {
|
||||
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
|
||||
}
|
||||
|
||||
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
std::function<void(StringRef)> f) {
|
||||
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
|
||||
}
|
||||
|
||||
// Legacy helper: wraps std::string callback and delegates to StringRef version
|
||||
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(const std::string &)> f, bool once) {
|
||||
// Wrap callback to convert StringRef -> std::string, then delegate
|
||||
this->add_state_subscription_(std::move(entity_id), std::move(attribute),
|
||||
std::function<void(StringRef)>([f = std::move(f)](StringRef state) { f(state.str()); }),
|
||||
once);
|
||||
}
|
||||
|
||||
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string)
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(const std::string &)> f) {
|
||||
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
|
||||
}
|
||||
|
||||
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(const std::string &)> f) {
|
||||
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
|
||||
}
|
||||
|
||||
@@ -678,7 +672,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message) {
|
||||
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message) {
|
||||
for (auto &call : this->active_action_calls_) {
|
||||
if (call.action_call_id == action_call_id) {
|
||||
call.connection->send_execute_service_response(call.client_call_id, success, error_message);
|
||||
@@ -688,7 +682,7 @@ void APIServer::send_action_response(uint32_t action_call_id, bool success, cons
|
||||
ESP_LOGW(TAG, "Cannot send response: no active call found for action_call_id %u", action_call_id);
|
||||
}
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
void APIServer::send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
|
||||
void APIServer::send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
|
||||
const uint8_t *response_data, size_t response_data_len) {
|
||||
for (auto &call : this->active_action_calls_) {
|
||||
if (call.action_call_id == action_call_id) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "list_entities.h"
|
||||
#include "subscribe_state.h"
|
||||
#ifdef USE_LOGGER
|
||||
@@ -59,10 +60,6 @@ class APIServer : public Component,
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
|
||||
#endif
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool check_password(const uint8_t *password_data, size_t password_len) const;
|
||||
void set_password(const std::string &password);
|
||||
#endif
|
||||
void set_port(uint16_t port);
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
@@ -143,10 +140,10 @@ class APIServer : public Component,
|
||||
// Action response handling
|
||||
using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
|
||||
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
|
||||
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message);
|
||||
void handle_action_response(uint32_t call_id, bool success, StringRef error_message);
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
void handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
|
||||
const uint8_t *response_data, size_t response_data_len);
|
||||
void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data,
|
||||
size_t response_data_len);
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
@@ -165,9 +162,9 @@ class APIServer : public Component,
|
||||
void unregister_active_action_call(uint32_t action_call_id);
|
||||
void unregister_active_action_calls_for_connection(APIConnection *conn);
|
||||
// Send response for a specific action call (uses action_call_id, sends client_call_id in response)
|
||||
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message);
|
||||
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
void send_action_response(uint32_t action_call_id, bool success, const std::string &error_message,
|
||||
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
|
||||
const uint8_t *response_data, size_t response_data_len);
|
||||
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
@@ -195,7 +192,7 @@ class APIServer : public Component,
|
||||
struct HomeAssistantStateSubscription {
|
||||
const char *entity_id; // Pointer to flash (internal) or heap (external)
|
||||
const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute)
|
||||
std::function<void(std::string)> callback;
|
||||
std::function<void(StringRef)> callback;
|
||||
bool once;
|
||||
|
||||
// Dynamic storage for external components using std::string API (custom_api_device.h)
|
||||
@@ -205,14 +202,20 @@ class APIServer : public Component,
|
||||
};
|
||||
|
||||
// New const char* overload (for internal components - zero allocation)
|
||||
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
|
||||
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(std::string)> f);
|
||||
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
|
||||
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
|
||||
|
||||
// Existing std::string overload (for custom_api_device.h - heap allocation)
|
||||
// std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback)
|
||||
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f);
|
||||
std::function<void(StringRef)> f);
|
||||
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f);
|
||||
std::function<void(StringRef)> f);
|
||||
|
||||
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback)
|
||||
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(const std::string &)> f);
|
||||
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(const std::string &)> f);
|
||||
|
||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||
#endif
|
||||
@@ -236,10 +239,13 @@ class APIServer : public Component,
|
||||
#endif // USE_API_NOISE
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
// Helper methods to reduce code duplication
|
||||
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(std::string)> f,
|
||||
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
|
||||
bool once);
|
||||
void add_state_subscription_(std::string entity_id, optional<std::string> attribute, std::function<void(StringRef)> f,
|
||||
bool once);
|
||||
// Legacy helper: wraps std::string callback and delegates to StringRef version
|
||||
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f, bool once);
|
||||
std::function<void(const std::string &)> f, bool once);
|
||||
#endif // USE_API_HOMEASSISTANT_STATES
|
||||
// Pointers and pointer-like types first (4 bytes each)
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
@@ -256,9 +262,6 @@ class APIServer : public Component,
|
||||
|
||||
// Vectors and strings (12 bytes each on 32-bit)
|
||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||
#ifdef USE_API_PASSWORD
|
||||
std::string password_;
|
||||
#endif
|
||||
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
|
||||
@@ -16,7 +16,7 @@ with warnings.catch_warnings():
|
||||
|
||||
import contextlib
|
||||
|
||||
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
|
||||
from esphome.const import CONF_KEY, CONF_PORT, __version__
|
||||
from esphome.core import CORE
|
||||
|
||||
from . import CONF_ENCRYPTION
|
||||
@@ -35,7 +35,6 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
conf = config["api"]
|
||||
name = config["esphome"]["name"]
|
||||
port: int = int(conf[CONF_PORT])
|
||||
password: str = conf[CONF_PASSWORD]
|
||||
noise_psk: str | None = None
|
||||
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
|
||||
noise_psk = key
|
||||
@@ -50,7 +49,7 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
cli = APIClient(
|
||||
addresses[0], # Primary address for compatibility
|
||||
port,
|
||||
password,
|
||||
"", # Password auth removed in 2026.1.0
|
||||
client_info=f"ESPHome Logs {__version__}",
|
||||
noise_psk=noise_psk,
|
||||
addresses=addresses, # Pass all addresses for automatic retry
|
||||
|
||||
@@ -122,21 +122,36 @@ class CustomAPIDevice {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string state) {
|
||||
* // State of sensor.weather_forecast is `state`
|
||||
* void on_state_changed(StringRef state) {
|
||||
* // State of climate.kitchen current_temperature is `state`
|
||||
* // Use state.c_str() for C string, state.str() for std::string
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param callback The member function to call when the entity state changes (zero-allocation).
|
||||
* @param entity_id The entity_id to track.
|
||||
* @param attribute The entity state attribute to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
|
||||
*
|
||||
* @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0.
|
||||
*/
|
||||
template<typename T>
|
||||
ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
||||
// Explicit type to disambiguate overload resolution
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(f));
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||
@@ -148,23 +163,45 @@ class CustomAPIDevice {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string entity_id, std::string state) {
|
||||
* void on_state_changed(const std::string &entity_id, StringRef state) {
|
||||
* // State of `entity_id` is `state`
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param callback The member function to call when the entity state changes (zero-allocation for state).
|
||||
* @param entity_id The entity_id to track.
|
||||
* @param attribute The entity state attribute to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
|
||||
*
|
||||
* @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0.
|
||||
*/
|
||||
template<typename T>
|
||||
ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
||||
// Explicit type to disambiguate overload resolution
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(f));
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
static_assert(sizeof(T) == 0,
|
||||
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
||||
"of your YAML configuration");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
@@ -173,6 +210,14 @@ class CustomAPIDevice {
|
||||
"of your YAML configuration");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
static_assert(sizeof(T) == 0,
|
||||
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
||||
"of your YAML configuration");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
@@ -195,7 +240,7 @@ class CustomAPIDevice {
|
||||
*/
|
||||
void call_homeassistant_service(const std::string &service_name) {
|
||||
HomeassistantActionRequest resp;
|
||||
resp.set_service(StringRef(service_name));
|
||||
resp.service = StringRef(service_name);
|
||||
global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
@@ -215,12 +260,12 @@ class CustomAPIDevice {
|
||||
*/
|
||||
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
HomeassistantActionRequest resp;
|
||||
resp.set_service(StringRef(service_name));
|
||||
resp.service = StringRef(service_name);
|
||||
resp.data.init(data.size());
|
||||
for (auto &it : data) {
|
||||
auto &kv = resp.data.emplace_back();
|
||||
kv.set_key(StringRef(it.first));
|
||||
kv.value = it.second;
|
||||
kv.key = StringRef(it.first);
|
||||
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
|
||||
}
|
||||
global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
@@ -237,7 +282,7 @@ class CustomAPIDevice {
|
||||
*/
|
||||
void fire_homeassistant_event(const std::string &event_name) {
|
||||
HomeassistantActionRequest resp;
|
||||
resp.set_service(StringRef(event_name));
|
||||
resp.service = StringRef(event_name);
|
||||
resp.is_event = true;
|
||||
global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
@@ -257,13 +302,13 @@ class CustomAPIDevice {
|
||||
*/
|
||||
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
HomeassistantActionRequest resp;
|
||||
resp.set_service(StringRef(service_name));
|
||||
resp.service = StringRef(service_name);
|
||||
resp.is_event = true;
|
||||
resp.data.init(data.size());
|
||||
for (auto &it : data) {
|
||||
auto &kv = resp.data.emplace_back();
|
||||
kv.set_key(StringRef(it.first));
|
||||
kv.value = it.second;
|
||||
kv.key = StringRef(it.first);
|
||||
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
|
||||
}
|
||||
global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
@@ -67,10 +67,10 @@ template<typename... Ts> class TemplatableKeyValuePair {
|
||||
// the callback is invoked synchronously while the message is on the stack).
|
||||
class ActionResponse {
|
||||
public:
|
||||
ActionResponse(bool success, const std::string &error_message) : success_(success), error_message_(error_message) {}
|
||||
ActionResponse(bool success, StringRef error_message) : success_(success), error_message_(error_message) {}
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
ActionResponse(bool success, const std::string &error_message, const uint8_t *data, size_t data_len)
|
||||
ActionResponse(bool success, StringRef error_message, const uint8_t *data, size_t data_len)
|
||||
: success_(success), error_message_(error_message) {
|
||||
if (data == nullptr || data_len == 0)
|
||||
return;
|
||||
@@ -147,7 +147,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
void play(const Ts &...x) override {
|
||||
HomeassistantActionRequest resp;
|
||||
std::string service_value = this->service_.value(x...);
|
||||
resp.set_service(StringRef(service_value));
|
||||
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...);
|
||||
@@ -209,7 +209,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
dest.init(source.size());
|
||||
for (auto &it : source) {
|
||||
auto &kv = dest.emplace_back();
|
||||
kv.set_key(StringRef(it.key));
|
||||
kv.key = StringRef(it.key);
|
||||
kv.value = it.value.value(x...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,24 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) {
|
||||
return (value & 1) ? static_cast<int64_t>(~(value >> 1)) : static_cast<int64_t>(value >> 1);
|
||||
}
|
||||
|
||||
/// Count number of varints in a packed buffer
|
||||
inline uint16_t count_packed_varints(const uint8_t *data, size_t len) {
|
||||
uint16_t count = 0;
|
||||
while (len > 0) {
|
||||
// Skip varint bytes until we find one without continuation bit
|
||||
while (len > 0 && (*data & 0x80)) {
|
||||
data++;
|
||||
len--;
|
||||
}
|
||||
if (len > 0) {
|
||||
data++;
|
||||
len--;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* StringRef Ownership Model for API Protocol Messages
|
||||
* ===================================================
|
||||
@@ -54,16 +72,16 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) {
|
||||
* 3. Global/static strings: StringRef(GLOBAL_CONSTANT) - Always safe
|
||||
* 4. Local variables: Safe ONLY if encoding happens before function returns:
|
||||
* std::string temp = compute_value();
|
||||
* msg.set_field(StringRef(temp));
|
||||
* msg.field = StringRef(temp);
|
||||
* return this->send_message(msg); // temp is valid during encoding
|
||||
*
|
||||
* Unsafe Patterns (WILL cause crashes/corruption):
|
||||
* 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value
|
||||
* 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
|
||||
* 1. Temporaries: msg.field = StringRef(obj.get_string()) // get_string() returns by value
|
||||
* 2. Concatenation: msg.field = StringRef(str1 + str2) // Result is temporary
|
||||
*
|
||||
* For unsafe patterns, store in a local variable first:
|
||||
* std::string temp = get_string(); // or str1 + str2
|
||||
* msg.set_field(StringRef(temp));
|
||||
* msg.field = StringRef(temp);
|
||||
*
|
||||
* The send_*_response pattern ensures proper lifetime management by encoding
|
||||
* within the same function scope where temporaries are created.
|
||||
@@ -180,9 +198,10 @@ class ProtoVarInt {
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
// Forward declaration for decode_to_message and encode_to_writer
|
||||
class ProtoMessage;
|
||||
// Forward declarations for decode_to_message, encode_message and encode_packed_sint32
|
||||
class ProtoDecodableMessage;
|
||||
class ProtoMessage;
|
||||
class ProtoSize;
|
||||
|
||||
class ProtoLengthDelimited {
|
||||
public:
|
||||
@@ -334,6 +353,8 @@ class ProtoWriteBuffer {
|
||||
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
this->encode_uint64(field_id, encode_zigzag64(value), force);
|
||||
}
|
||||
/// Encode a packed repeated sint32 field (zero-copy from vector)
|
||||
void encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values);
|
||||
void encode_message(uint32_t field_id, const ProtoMessage &value);
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
@@ -341,9 +362,6 @@ class ProtoWriteBuffer {
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
class ProtoSize;
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual ~ProtoMessage() = default;
|
||||
@@ -792,8 +810,43 @@ class ProtoSize {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate size of a packed repeated sint32 field
|
||||
*/
|
||||
inline void add_packed_sint32(uint32_t field_id_size, const std::vector<int32_t> &values) {
|
||||
if (values.empty())
|
||||
return;
|
||||
|
||||
size_t packed_size = 0;
|
||||
for (int value : values) {
|
||||
packed_size += varint(encode_zigzag32(value));
|
||||
}
|
||||
|
||||
// field_id + length varint + packed data
|
||||
total_size_ += field_id_size + varint(static_cast<uint32_t>(packed_size)) + static_cast<uint32_t>(packed_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Implementation of encode_packed_sint32 - must be after ProtoSize is defined
|
||||
inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values) {
|
||||
if (values.empty())
|
||||
return;
|
||||
|
||||
// Calculate packed size
|
||||
size_t packed_size = 0;
|
||||
for (int value : values) {
|
||||
packed_size += ProtoSize::varint(encode_zigzag32(value));
|
||||
}
|
||||
|
||||
// Write tag (LENGTH_DELIMITED) + length + all zigzag-encoded values
|
||||
this->encode_field_raw(field_id, WIRE_TYPE_LENGTH_DELIMITED);
|
||||
this->encode_varint_raw(packed_size);
|
||||
for (int value : values) {
|
||||
this->encode_varint_raw(encode_zigzag32(value));
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of encode_message - must be after ProtoMessage is defined
|
||||
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) {
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||
@@ -833,9 +886,6 @@ class ProtoService {
|
||||
virtual bool is_authenticated() = 0;
|
||||
virtual bool is_connection_setup() = 0;
|
||||
virtual void on_fatal_error() = 0;
|
||||
#ifdef USE_API_PASSWORD
|
||||
virtual void on_unauthenticated_access() = 0;
|
||||
#endif
|
||||
virtual void on_no_setup_connection() = 0;
|
||||
/**
|
||||
* Create a buffer with a reserved size.
|
||||
@@ -873,20 +923,7 @@ class ProtoService {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool check_authenticated_() {
|
||||
#ifdef USE_API_PASSWORD
|
||||
if (!this->check_connection_setup_()) {
|
||||
return false;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return this->check_connection_setup_();
|
||||
#endif
|
||||
}
|
||||
inline bool check_authenticated_() { return this->check_connection_setup_(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -46,7 +46,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
|
||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||
ListEntitiesServicesResponse msg;
|
||||
msg.set_name(StringRef(this->name_));
|
||||
msg.name = StringRef(this->name_);
|
||||
msg.key = this->key_;
|
||||
msg.supports_response = this->supports_response_;
|
||||
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||
@@ -54,7 +54,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
for (size_t i = 0; i < sizeof...(Ts); i++) {
|
||||
auto &arg = msg.args.emplace_back();
|
||||
arg.type = arg_types[i];
|
||||
arg.set_name(StringRef(this->arg_names_[i]));
|
||||
arg.name = StringRef(this->arg_names_[i]);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
|
||||
|
||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||
ListEntitiesServicesResponse msg;
|
||||
msg.set_name(StringRef(this->name_));
|
||||
msg.name = StringRef(this->name_);
|
||||
msg.key = this->key_;
|
||||
msg.supports_response = enums::SUPPORTS_RESPONSE_NONE; // Dynamic services don't support responses yet
|
||||
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||
@@ -116,7 +116,7 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
|
||||
for (size_t i = 0; i < sizeof...(Ts); i++) {
|
||||
auto &arg = msg.args.emplace_back();
|
||||
arg.type = arg_types[i];
|
||||
arg.set_name(StringRef(this->arg_names_[i]));
|
||||
arg.name = StringRef(this->arg_names_[i]);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
@@ -255,7 +255,7 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
|
||||
bool return_response = std::get<1>(args);
|
||||
if (!return_response) {
|
||||
// Client doesn't want response data, just send success/error
|
||||
this->parent_->send_action_response(call_id, success, error_message);
|
||||
this->parent_->send_action_response(call_id, success, StringRef(error_message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -265,12 +265,12 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
|
||||
json::JsonBuilder builder;
|
||||
this->json_builder_(x..., builder.root());
|
||||
std::string json_str = builder.serialize();
|
||||
this->parent_->send_action_response(call_id, success, error_message,
|
||||
this->parent_->send_action_response(call_id, success, StringRef(error_message),
|
||||
reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
this->parent_->send_action_response(call_id, success, error_message);
|
||||
this->parent_->send_action_response(call_id, success, StringRef(error_message));
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -10,38 +10,37 @@ 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_calculation_grid_);
|
||||
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
|
||||
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
|
||||
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
||||
}
|
||||
|
||||
protected:
|
||||
static const int AMOUNT_OF_LEVELS = 6;
|
||||
static constexpr int NUM_LEVELS = 6;
|
||||
|
||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||
|
||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55},
|
||||
{56, 125}, {126, 225}, {226, INT_MAX}};
|
||||
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}};
|
||||
|
||||
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
||||
{255, 354}, {355, 424}, {425, INT_MAX}};
|
||||
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
||||
{255, 354}, {355, 424}, {425, INT_MAX}};
|
||||
|
||||
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||
int grid_index = get_grid_index_(value, array);
|
||||
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
int grid_index = get_grid_index(value, array);
|
||||
if (grid_index == -1) {
|
||||
return -1;
|
||||
}
|
||||
int aqi_lo = index_grid_[grid_index][0];
|
||||
int aqi_hi = index_grid_[grid_index][1];
|
||||
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];
|
||||
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||
for (int i = 0; i < AMOUNT_OF_LEVELS; i++) {
|
||||
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||
if (value >= array[i][0] && value <= array[i][1]) {
|
||||
return i;
|
||||
}
|
||||
|
||||
52
esphome/components/aqi/aqi_sensor.cpp
Normal file
52
esphome/components/aqi/aqi_sensor.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "aqi_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::aqi {
|
||||
|
||||
static const char *const TAG = "aqi";
|
||||
|
||||
void AQISensor::setup() {
|
||||
if (this->pm_2_5_sensor_ != nullptr) {
|
||||
this->pm_2_5_sensor_->add_on_state_callback([this](float value) {
|
||||
this->pm_2_5_value_ = value;
|
||||
// Defer calculation to avoid double-publishing if both sensors update in the same loop
|
||||
this->defer("update", [this]() { this->calculate_aqi_(); });
|
||||
});
|
||||
}
|
||||
if (this->pm_10_0_sensor_ != nullptr) {
|
||||
this->pm_10_0_sensor_->add_on_state_callback([this](float value) {
|
||||
this->pm_10_0_value_ = value;
|
||||
this->defer("update", [this]() { this->calculate_aqi_(); });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AQISensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AQI Sensor:");
|
||||
ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI");
|
||||
if (this->pm_2_5_sensor_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str());
|
||||
}
|
||||
if (this->pm_10_0_sensor_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " PM10 Sensor: '%s'", this->pm_10_0_sensor_->get_name().c_str());
|
||||
}
|
||||
LOG_SENSOR(" ", "AQI", this);
|
||||
}
|
||||
|
||||
void AQISensor::calculate_aqi_() {
|
||||
if (std::isnan(this->pm_2_5_value_) || std::isnan(this->pm_10_0_value_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
|
||||
if (calculator == nullptr) {
|
||||
ESP_LOGW(TAG, "Unknown AQI calculator type");
|
||||
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_));
|
||||
this->publish_state(aqi);
|
||||
}
|
||||
|
||||
} // namespace esphome::aqi
|
||||
31
esphome/components/aqi/aqi_sensor.h
Normal file
31
esphome/components/aqi/aqi_sensor.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "aqi_calculator_factory.h"
|
||||
|
||||
namespace esphome::aqi {
|
||||
|
||||
class AQISensor : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
|
||||
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
|
||||
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
|
||||
|
||||
protected:
|
||||
void calculate_aqi_();
|
||||
|
||||
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
||||
sensor::Sensor *pm_10_0_sensor_{nullptr};
|
||||
AQICalculatorType aqi_calc_type_{AQI_TYPE};
|
||||
AQICalculatorFactory aqi_calculator_factory_;
|
||||
|
||||
float pm_2_5_value_{NAN};
|
||||
float pm_10_0_value_{NAN};
|
||||
};
|
||||
|
||||
} // namespace esphome::aqi
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
namespace esphome::aqi {
|
||||
@@ -8,37 +7,37 @@ 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_calculation_grid_);
|
||||
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
|
||||
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
|
||||
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
||||
}
|
||||
|
||||
protected:
|
||||
static const int AMOUNT_OF_LEVELS = 5;
|
||||
static constexpr int NUM_LEVELS = 5;
|
||||
|
||||
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
|
||||
|
||||
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}};
|
||||
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}};
|
||||
|
||||
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
|
||||
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
|
||||
|
||||
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||
int grid_index = get_grid_index_(value, array);
|
||||
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
int grid_index = get_grid_index(value, array);
|
||||
if (grid_index == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int aqi_lo = index_grid_[grid_index][0];
|
||||
int aqi_hi = index_grid_[grid_index][1];
|
||||
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];
|
||||
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||
for (int i = 0; i < AMOUNT_OF_LEVELS; i++) {
|
||||
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||
if (value >= array[i][0] && value <= array[i][1]) {
|
||||
return i;
|
||||
}
|
||||
|
||||
51
esphome/components/aqi/sensor.py
Normal file
51
esphome/components/aqi/sensor.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_PM_2_5,
|
||||
CONF_PM_10_0,
|
||||
DEVICE_CLASS_AQI,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
|
||||
|
||||
CODEOWNERS = ["@jasstrong"]
|
||||
DEPENDENCIES = ["sensor"]
|
||||
|
||||
UNIT_INDEX = "index"
|
||||
|
||||
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
AQISensor,
|
||||
unit_of_measurement=UNIT_INDEX,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_AQI,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_PM_2_5): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_PM_10_0): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
|
||||
AQI_CALCULATION_TYPE, upper=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pm_2_5_sensor = await cg.get_variable(config[CONF_PM_2_5])
|
||||
cg.add(var.set_pm_2_5_sensor(pm_2_5_sensor))
|
||||
|
||||
pm_10_0_sensor = await cg.get_variable(config[CONF_PM_10_0])
|
||||
cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor))
|
||||
|
||||
cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE]))
|
||||
@@ -305,12 +305,14 @@ bool AS3935Component::calibrate_oscillator() {
|
||||
}
|
||||
|
||||
void AS3935Component::tune_antenna() {
|
||||
ESP_LOGI(TAG, "Starting antenna tuning");
|
||||
uint8_t div_ratio = this->read_div_ratio();
|
||||
uint8_t tune_val = this->read_capacitance();
|
||||
ESP_LOGI(TAG, "Division Ratio is set to: %d", div_ratio);
|
||||
ESP_LOGI(TAG, "Internal Capacitor is set to: %d", tune_val);
|
||||
ESP_LOGI(TAG, "Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio");
|
||||
ESP_LOGI(TAG,
|
||||
"Starting antenna tuning\n"
|
||||
"Division Ratio is set to: %d\n"
|
||||
"Internal Capacitor is set to: %d\n"
|
||||
"Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio",
|
||||
div_ratio, tune_val);
|
||||
this->display_oscillator(true, ANTFREQ);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,50 @@
|
||||
# Dummy integration to allow relying on AsyncTCP
|
||||
# Async TCP client support for all platforms
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RTL87XX,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["network"]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(
|
||||
[
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_LN882X,
|
||||
PLATFORM_RTL87XX,
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def AUTO_LOAD() -> list[str]:
|
||||
# Socket component needed for platforms using socket-based implementation
|
||||
# ESP32, ESP8266, RP2040, and LibreTiny use AsyncTCP libraries, others use sockets
|
||||
if (
|
||||
not CORE.is_esp32
|
||||
and not CORE.is_esp8266
|
||||
and not CORE.is_rp2040
|
||||
and not CORE.is_libretiny
|
||||
):
|
||||
return ["socket"]
|
||||
return []
|
||||
|
||||
|
||||
# Support all platforms - Arduino/ESP-IDF get libraries, other platforms use socket implementation
|
||||
CONFIG_SCHEMA = cv.Schema({})
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
|
||||
async def to_code(config):
|
||||
if CORE.is_esp32 or CORE.is_libretiny:
|
||||
if CORE.is_esp32:
|
||||
# https://github.com/ESP32Async/AsyncTCP
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
|
||||
add_idf_component(name="esp32async/asynctcp", ref="3.4.91")
|
||||
elif CORE.is_libretiny:
|
||||
# https://github.com/ESP32Async/AsyncTCP
|
||||
cg.add_library("ESP32Async/AsyncTCP", "3.4.5")
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/ESP32Async/ESPAsyncTCP
|
||||
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
|
||||
elif CORE.is_rp2040:
|
||||
# https://github.com/khoih-prog/AsyncTCP_RP2040W
|
||||
cg.add_library("khoih-prog/AsyncTCP_RP2040W", "1.2.0")
|
||||
# Other platforms (host, etc) use socket-based implementation
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
# Exclude socket implementation for platforms that use AsyncTCP libraries
|
||||
if CORE.is_esp32 or CORE.is_esp8266 or CORE.is_rp2040 or CORE.is_libretiny:
|
||||
return ["async_tcp_socket.cpp"]
|
||||
return []
|
||||
|
||||
16
esphome/components/async_tcp/async_tcp.h
Normal file
16
esphome/components/async_tcp/async_tcp.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
// Use AsyncTCP library for ESP32 (Arduino or ESP-IDF) and LibreTiny
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(USE_ESP8266)
|
||||
// Use ESPAsyncTCP library for ESP8266 (always Arduino)
|
||||
#include <ESPAsyncTCP.h>
|
||||
#elif defined(USE_RP2040)
|
||||
// Use AsyncTCP_RP2040W library for RP2040
|
||||
#include <AsyncTCP_RP2040W.h>
|
||||
#else
|
||||
// Use socket-based implementation for other platforms
|
||||
#include "async_tcp_socket.h"
|
||||
#endif
|
||||
162
esphome/components/async_tcp/async_tcp_socket.cpp
Normal file
162
esphome/components/async_tcp/async_tcp_socket.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "async_tcp_socket.h"
|
||||
|
||||
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
|
||||
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
|
||||
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cerrno>
|
||||
#include <sys/select.h>
|
||||
|
||||
namespace esphome::async_tcp {
|
||||
|
||||
static const char *const TAG = "async_tcp";
|
||||
|
||||
// Read buffer size matches TCP MSS (1500 MTU - 40 bytes IP/TCP headers).
|
||||
// This implementation only runs on ESP-IDF and host which have ample stack.
|
||||
static constexpr size_t READ_BUFFER_SIZE = 1460;
|
||||
|
||||
bool AsyncClient::connect(const char *host, uint16_t port) {
|
||||
if (connected_ || connecting_) {
|
||||
ESP_LOGW(TAG, "Already connected/connecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve address
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrlen = esphome::socket::set_sockaddr((struct sockaddr *) &addr, sizeof(addr), host, port);
|
||||
if (addrlen == 0) {
|
||||
ESP_LOGE(TAG, "Invalid address: %s", host);
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create socket with loop monitoring
|
||||
int family = ((struct sockaddr *) &addr)->sa_family;
|
||||
socket_ = esphome::socket::socket_loop_monitored(family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (!socket_) {
|
||||
ESP_LOGE(TAG, "Failed to create socket");
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, -1);
|
||||
return false;
|
||||
}
|
||||
|
||||
socket_->setblocking(false);
|
||||
|
||||
int err = socket_->connect((struct sockaddr *) &addr, addrlen);
|
||||
if (err == 0) {
|
||||
// Connection succeeded immediately (rare, but possible for localhost)
|
||||
connected_ = true;
|
||||
if (connect_cb_)
|
||||
connect_cb_(connect_arg_, this);
|
||||
return true;
|
||||
}
|
||||
if (errno != EINPROGRESS) {
|
||||
ESP_LOGE(TAG, "Connect failed: %d", errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
connecting_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncClient::close() {
|
||||
socket_.reset();
|
||||
bool was_connected = connected_;
|
||||
connected_ = false;
|
||||
connecting_ = false;
|
||||
if (was_connected && disconnect_cb_)
|
||||
disconnect_cb_(disconnect_arg_, this);
|
||||
}
|
||||
|
||||
size_t AsyncClient::write(const char *data, size_t len) {
|
||||
if (!socket_ || !connected_)
|
||||
return 0;
|
||||
|
||||
ssize_t sent = socket_->write(data, len);
|
||||
if (sent < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
ESP_LOGE(TAG, "Write error: %d", errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
void AsyncClient::loop() {
|
||||
if (!socket_)
|
||||
return;
|
||||
|
||||
if (connecting_) {
|
||||
// For connecting, we need to check writability, not readability
|
||||
// The Application's select() only monitors read FDs, so we do our own check here
|
||||
// For ESP platforms lwip_select() might be faster, but this code isn't used
|
||||
// on those platforms anyway. If it was, we'd fix the Application select()
|
||||
// to report writability instead of doing it this way.
|
||||
int fd = socket_->get_fd();
|
||||
if (fd < 0) {
|
||||
ESP_LOGW(TAG, "Invalid socket fd");
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
fd_set writefds;
|
||||
FD_ZERO(&writefds);
|
||||
FD_SET(fd, &writefds);
|
||||
|
||||
struct timeval tv = {0, 0};
|
||||
int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv);
|
||||
|
||||
if (ret > 0 && FD_ISSET(fd, &writefds)) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) {
|
||||
connecting_ = false;
|
||||
connected_ = true;
|
||||
if (connect_cb_)
|
||||
connect_cb_(connect_arg_, this);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Connection failed: %d", error);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, error);
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Select error: %d", errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
}
|
||||
} else if (connected_) {
|
||||
// For connected sockets, use the Application's select() results
|
||||
if (!socket_->ready())
|
||||
return;
|
||||
|
||||
uint8_t buf[READ_BUFFER_SIZE];
|
||||
ssize_t len = socket_->read(buf, READ_BUFFER_SIZE);
|
||||
|
||||
if (len == 0) {
|
||||
ESP_LOGI(TAG, "Connection closed by peer");
|
||||
close();
|
||||
} else if (len > 0) {
|
||||
if (data_cb_)
|
||||
data_cb_(data_arg_, this, buf, len);
|
||||
} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
ESP_LOGW(TAG, "Read error: %d", errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::async_tcp
|
||||
|
||||
#endif
|
||||
73
esphome/components/async_tcp/async_tcp_socket.h
Normal file
73
esphome/components/async_tcp/async_tcp_socket.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
|
||||
(defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
|
||||
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace esphome::async_tcp {
|
||||
|
||||
/// AsyncClient API for platforms using sockets (ESP-IDF, host, etc.)
|
||||
/// NOTE: This class is NOT thread-safe. All methods must be called from the main loop.
|
||||
class AsyncClient {
|
||||
public:
|
||||
using AcConnectHandler = std::function<void(void *, AsyncClient *)>;
|
||||
using AcDataHandler = std::function<void(void *, AsyncClient *, void *data, size_t len)>;
|
||||
using AcErrorHandler = std::function<void(void *, AsyncClient *, int8_t error)>;
|
||||
|
||||
AsyncClient() = default;
|
||||
~AsyncClient() = default;
|
||||
|
||||
[[nodiscard]] bool connect(const char *host, uint16_t port);
|
||||
void close();
|
||||
[[nodiscard]] bool connected() const { return connected_; }
|
||||
size_t write(const char *data, size_t len);
|
||||
|
||||
void onConnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
|
||||
connect_cb_ = std::move(cb);
|
||||
connect_arg_ = arg;
|
||||
}
|
||||
void onDisconnect(AcConnectHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
|
||||
disconnect_cb_ = std::move(cb);
|
||||
disconnect_arg_ = arg;
|
||||
}
|
||||
/// Set data callback. NOTE: data pointer is only valid during callback execution.
|
||||
void onData(AcDataHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
|
||||
data_cb_ = std::move(cb);
|
||||
data_arg_ = arg;
|
||||
}
|
||||
void onError(AcErrorHandler cb, void *arg = nullptr) { // NOLINT(readability-identifier-naming)
|
||||
error_cb_ = std::move(cb);
|
||||
error_arg_ = arg;
|
||||
}
|
||||
|
||||
// Must be called from loop()
|
||||
void loop();
|
||||
|
||||
private:
|
||||
std::unique_ptr<esphome::socket::Socket> socket_;
|
||||
|
||||
AcConnectHandler connect_cb_{nullptr};
|
||||
void *connect_arg_{nullptr};
|
||||
AcConnectHandler disconnect_cb_{nullptr};
|
||||
void *disconnect_arg_{nullptr};
|
||||
AcDataHandler data_cb_{nullptr};
|
||||
void *data_arg_{nullptr};
|
||||
AcErrorHandler error_cb_{nullptr};
|
||||
void *error_arg_{nullptr};
|
||||
|
||||
bool connected_{false};
|
||||
bool connecting_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::async_tcp
|
||||
|
||||
// Expose AsyncClient in global namespace to match library behavior
|
||||
using esphome::async_tcp::AsyncClient; // NOLINT(google-global-names-in-headers)
|
||||
#endif
|
||||
@@ -21,7 +21,9 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
||||
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
const char *addr_str = device.address_str_to(addr_buf);
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str);
|
||||
|
||||
bool success = false;
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
@@ -32,7 +34,7 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
||||
if (!(parse_message_(service_data.data, *res))) {
|
||||
continue;
|
||||
}
|
||||
if (!(report_results_(res, device.address_str()))) {
|
||||
if (!(report_results_(res, addr_str))) {
|
||||
continue;
|
||||
}
|
||||
if (res->temperature.has_value() && this->temperature_ != nullptr)
|
||||
@@ -103,13 +105,13 @@ bool ATCMiThermometer::parse_message_(const std::vector<uint8_t> &message, Parse
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const std::string &address) {
|
||||
bool ATCMiThermometer::report_results_(const optional<ParseResult> &result, const char *address) {
|
||||
if (!result.has_value()) {
|
||||
ESP_LOGVV(TAG, "report_results(): no results available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str());
|
||||
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address);
|
||||
|
||||
if (result->temperature.has_value()) {
|
||||
ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature);
|
||||
|
||||
@@ -41,7 +41,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
|
||||
|
||||
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
|
||||
bool report_results_(const optional<ParseResult> &result, const std::string &address);
|
||||
bool report_results_(const optional<ParseResult> &result, const char *address);
|
||||
};
|
||||
|
||||
} // namespace atc_mithermometer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "audio_reader.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "audio.h"
|
||||
#include "audio_transfer_buffer.h"
|
||||
|
||||
@@ -22,7 +22,8 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf));
|
||||
const auto &service_datas = device.get_service_datas();
|
||||
if (service_datas.size() != 1) {
|
||||
ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size());
|
||||
|
||||
@@ -193,8 +193,9 @@ bool BedJetHub::discover_characteristics_() {
|
||||
result = false;
|
||||
} else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
|
||||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
char uuid_buf[espbt::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_,
|
||||
descr->uuid.to_string().c_str());
|
||||
descr->uuid.to_str(uuid_buf));
|
||||
result = false;
|
||||
} else {
|
||||
this->config_descr_status_ = descr->handle;
|
||||
@@ -216,11 +217,14 @@ bool BedJetHub::discover_characteristics_() {
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "[%s] Discovered service characteristics: ", this->get_name().c_str());
|
||||
ESP_LOGI(TAG, " - Command char: 0x%x", this->char_handle_cmd_);
|
||||
ESP_LOGI(TAG, " - Status char: 0x%x", this->char_handle_status_);
|
||||
ESP_LOGI(TAG, " - config descriptor: 0x%x", this->config_descr_status_);
|
||||
ESP_LOGI(TAG, " - Name char: 0x%x", this->char_handle_name_);
|
||||
ESP_LOGI(TAG,
|
||||
"[%s] Discovered service characteristics:\n"
|
||||
" - Command char: 0x%x\n"
|
||||
" - Status char: 0x%x\n"
|
||||
" - config descriptor: 0x%x\n"
|
||||
" - Name char: 0x%x",
|
||||
this->get_name().c_str(), this->char_handle_cmd_, this->char_handle_status_, this->config_descr_status_,
|
||||
this->char_handle_name_);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ from logging import getLogger
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
from esphome.components.const import CONF_ON_STATE_CHANGE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -439,6 +439,7 @@ def validate_publish_initial_state(value):
|
||||
_BINARY_SENSOR_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMPONENT_SCHEMA)
|
||||
.extend(zigbee.BINARY_SENSOR_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
@@ -520,6 +521,7 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
|
||||
|
||||
_BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor"))
|
||||
_BINARY_SENSOR_SCHEMA.add_extra(zigbee.validate_binary_sensor)
|
||||
|
||||
|
||||
def binary_sensor_schema(
|
||||
@@ -621,6 +623,8 @@ async def setup_binary_sensor_core_(var, config):
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
|
||||
await zigbee.setup_binary_sensor(var, config)
|
||||
|
||||
|
||||
async def register_binary_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
||||
@@ -21,8 +21,10 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
// Start matching
|
||||
MultiClickTriggerEvent evt = this->timing_[0];
|
||||
if (evt.state == state) {
|
||||
ESP_LOGV(TAG, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length);
|
||||
ESP_LOGV(TAG, "Multi Click: Starting multi click action!");
|
||||
ESP_LOGV(TAG,
|
||||
"START min=%" PRIu32 " max=%" PRIu32 "\n"
|
||||
"Multi Click: Starting multi click action!",
|
||||
evt.min_length, evt.max_length);
|
||||
this->at_index_ = 1;
|
||||
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
|
||||
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// Maximum bytes to log in hex format for BLE writes (many logging buffers are 256 chars)
|
||||
static constexpr size_t BLE_WRITE_MAX_LOG_BYTES = 64;
|
||||
|
||||
namespace esphome::ble_client {
|
||||
|
||||
// placeholder class for static TAG .
|
||||
@@ -151,7 +155,10 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
|
||||
return false;
|
||||
}
|
||||
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char hex_buf[format_hex_pretty_size(BLE_WRITE_MAX_LOG_BYTES)];
|
||||
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty_to(hex_buf, data, len));
|
||||
#endif
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
|
||||
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
|
||||
@@ -179,8 +186,10 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
esph_log_w("ble_write_action", "Characteristic %s was not found in service %s",
|
||||
this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str());
|
||||
this->char_uuid_.to_str(char_buf), this->service_uuid_.to_str(service_buf));
|
||||
break;
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
@@ -192,11 +201,13 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
|
||||
esph_log_d(Automation::TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
|
||||
} else {
|
||||
esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str());
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
esph_log_e(Automation::TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_str(char_buf));
|
||||
break;
|
||||
}
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
esph_log_d(Automation::TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf),
|
||||
ble_client_->address_str());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@ static const char *const TAG = "ble_binary_output";
|
||||
|
||||
void BLEBinaryOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
this->service_uuid_.to_str(service_buf);
|
||||
this->char_uuid_.to_str(char_buf);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" MAC address : %s\n"
|
||||
" Service UUID : %s\n"
|
||||
" Characteristic UUID: %s",
|
||||
this->parent_->address_str(), this->service_uuid_.to_string().c_str(),
|
||||
this->char_uuid_.to_string().c_str());
|
||||
this->parent_->address_str(), service_buf, char_buf);
|
||||
LOG_BINARY_OUTPUT(this);
|
||||
}
|
||||
|
||||
@@ -24,8 +27,10 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_string().c_str(),
|
||||
this->service_uuid_.to_string().c_str());
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "Characteristic %s was not found in service %s", this->char_uuid_.to_str(char_buf),
|
||||
this->service_uuid_.to_str(service_buf));
|
||||
break;
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
@@ -37,20 +42,24 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
this->write_type_ = ESP_GATT_WRITE_TYPE_NO_RSP;
|
||||
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_string().c_str(),
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGE(TAG, "Characteristic %s does not allow writing with%s response", this->char_uuid_.to_str(char_buf),
|
||||
this->require_response_ ? "" : "out");
|
||||
break;
|
||||
}
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_str(char_buf),
|
||||
this->parent()->address_str());
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||
if (param->write.handle == this->char_handle_) {
|
||||
if (param->write.status != 0)
|
||||
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
|
||||
if (param->write.status != 0) {
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_str(char_buf), param->write.status);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -60,18 +69,19 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
}
|
||||
|
||||
void BLEBinaryOutput::write_state(bool state) {
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
|
||||
this->char_uuid_.to_string().c_str());
|
||||
this->char_uuid_.to_str(char_buf));
|
||||
return;
|
||||
}
|
||||
uint8_t state_as_uint = (uint8_t) state;
|
||||
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
|
||||
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_str(char_buf), state_as_uint);
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_,
|
||||
sizeof(state_as_uint), &state_as_uint, this->write_type_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ESP_GATT_OK)
|
||||
ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_string().c_str(), err);
|
||||
ESP_LOGW(TAG, "[%s] Write error, err=%d", this->char_uuid_.to_str(char_buf), err);
|
||||
}
|
||||
|
||||
} // namespace esphome::ble_client
|
||||
|
||||
@@ -18,14 +18,17 @@ void BLESensor::loop() {
|
||||
|
||||
void BLESensor::dump_config() {
|
||||
LOG_SENSOR("", "BLE Sensor", this);
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
char descr_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" MAC address : %s\n"
|
||||
" Service UUID : %s\n"
|
||||
" Characteristic UUID: %s\n"
|
||||
" Descriptor UUID : %s\n"
|
||||
" Notifications : %s",
|
||||
this->parent()->address_str(), this->service_uuid_.to_string().c_str(),
|
||||
this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_));
|
||||
this->parent()->address_str(), this->service_uuid_.to_str(service_buf),
|
||||
this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
@@ -51,8 +54,10 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
if (chr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
|
||||
this->char_uuid_.to_string().c_str());
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
|
||||
this->char_uuid_.to_str(char_buf));
|
||||
break;
|
||||
}
|
||||
this->handle = chr->handle;
|
||||
@@ -61,9 +66,12 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
if (descr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
char descr_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
|
||||
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
|
||||
this->descr_uuid_.to_string().c_str());
|
||||
this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf),
|
||||
this->descr_uuid_.to_str(descr_buf));
|
||||
break;
|
||||
}
|
||||
this->handle = descr->handle;
|
||||
@@ -109,7 +117,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_string().c_str());
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGD(TAG, "Register for notify on %s complete", this->char_uuid_.to_str(char_buf));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSe
|
||||
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
|
||||
param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
|
||||
this->trigger(std::string(reinterpret_cast<const char *>(param->notify.value), param->notify.value_len));
|
||||
}
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -11,8 +11,6 @@ namespace esphome::ble_client {
|
||||
|
||||
static const char *const TAG = "ble_text_sensor";
|
||||
|
||||
static const std::string EMPTY = "";
|
||||
|
||||
void BLETextSensor::loop() {
|
||||
// Parent BLEClientNode has a loop() method, but this component uses
|
||||
// polling via update() and BLE callbacks so loop isn't needed
|
||||
@@ -21,14 +19,17 @@ void BLETextSensor::loop() {
|
||||
|
||||
void BLETextSensor::dump_config() {
|
||||
LOG_TEXT_SENSOR("", "BLE Text Sensor", this);
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
char descr_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" MAC address : %s\n"
|
||||
" Service UUID : %s\n"
|
||||
" Characteristic UUID: %s\n"
|
||||
" Descriptor UUID : %s\n"
|
||||
" Notifications : %s",
|
||||
this->parent()->address_str(), this->service_uuid_.to_string().c_str(),
|
||||
this->char_uuid_.to_string().c_str(), this->descr_uuid_.to_string().c_str(), YESNO(this->notify_));
|
||||
this->parent()->address_str(), this->service_uuid_.to_str(service_buf),
|
||||
this->char_uuid_.to_str(char_buf), this->descr_uuid_.to_str(descr_buf), YESNO(this->notify_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
case ESP_GATTC_CLOSE_EVT: {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
this->publish_state("");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
@@ -52,9 +53,11 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
|
||||
this->char_uuid_.to_string().c_str());
|
||||
this->publish_state("");
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_str(service_buf),
|
||||
this->char_uuid_.to_str(char_buf));
|
||||
break;
|
||||
}
|
||||
this->handle = chr->handle;
|
||||
@@ -62,10 +65,13 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
auto *descr = chr->get_descriptor(this->descr_uuid_);
|
||||
if (descr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
this->publish_state("");
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
char char_buf[esp32_ble::UUID_STR_LEN];
|
||||
char descr_buf[esp32_ble::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
|
||||
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
|
||||
this->descr_uuid_.to_string().c_str());
|
||||
this->service_uuid_.to_str(service_buf), this->char_uuid_.to_str(char_buf),
|
||||
this->descr_uuid_.to_str(descr_buf));
|
||||
break;
|
||||
}
|
||||
this->handle = descr->handle;
|
||||
@@ -91,7 +97,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
|
||||
this->publish_state(reinterpret_cast<const char *>(param->read.value), param->read.value_len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -100,7 +106,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
|
||||
this->publish_state(reinterpret_cast<const char *>(param->notify.value), param->notify.value_len);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
@@ -113,11 +119,6 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
}
|
||||
|
||||
std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) {
|
||||
std::string text(value, value + value_len);
|
||||
return text;
|
||||
}
|
||||
|
||||
void BLETextSensor::update() {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
@@ -132,7 +133,7 @@ void BLETextSensor::update() {
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
this->publish_state("");
|
||||
ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, p
|
||||
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||
std::string parse_data(uint8_t *value, uint16_t value_len);
|
||||
uint16_t handle;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -103,8 +103,10 @@ void BLENUS::on_log(uint8_t level, const char *tag, const char *message, size_t
|
||||
#endif
|
||||
|
||||
void BLENUS::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ble nus:");
|
||||
ESP_LOGCONFIG(TAG, " log: %s", YESNO(this->expose_log_));
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"ble nus:\n"
|
||||
" log: %s",
|
||||
YESNO(this->expose_log_));
|
||||
uint32_t mtu = 0;
|
||||
bt_conn *conn = this->conn_.load();
|
||||
if (conn) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
@@ -15,17 +16,13 @@ namespace ble_scanner {
|
||||
class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
this->publish_state("{\"timestamp\":" + to_string(::time(nullptr)) +
|
||||
","
|
||||
"\"address\":\"" +
|
||||
device.address_str() +
|
||||
"\","
|
||||
"\"rssi\":" +
|
||||
to_string(device.get_rssi()) +
|
||||
","
|
||||
"\"name\":\"" +
|
||||
device.get_name() + "\"}");
|
||||
|
||||
// Format JSON using stack buffer to avoid heap allocations from string concatenation
|
||||
char buf[128];
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
snprintf(buf, sizeof(buf), "{\"timestamp\":%" PRId64 ",\"address\":\"%s\",\"rssi\":%d,\"name\":\"%s\"}",
|
||||
static_cast<int64_t>(::time(nullptr)), device.address_str_to(addr_buf), device.get_rssi(),
|
||||
device.get_name().c_str());
|
||||
this->publish_state(buf);
|
||||
return true;
|
||||
}
|
||||
void dump_config() override;
|
||||
|
||||
@@ -50,6 +50,7 @@ TYPES = [
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(cg.Component),
|
||||
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
|
||||
@@ -31,7 +31,11 @@ void BME68xBSEC2I2CComponent::dump_config() {
|
||||
BME68xBSEC2Component::dump_config();
|
||||
}
|
||||
|
||||
uint32_t BME68xBSEC2I2CComponent::get_hash() { return fnv1_hash("bme68x_bsec_state_" + to_string(this->address_)); }
|
||||
uint32_t BME68xBSEC2I2CComponent::get_hash() {
|
||||
char buf[22]; // "bme68x_bsec_state_" (18) + uint8_t max (3) + null
|
||||
snprintf(buf, sizeof(buf), "bme68x_bsec_state_%u", this->address_);
|
||||
return fnv1_hash(buf);
|
||||
}
|
||||
|
||||
int8_t BME68xBSEC2I2CComponent::read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr) {
|
||||
ESP_LOGVV(TAG, "read_bytes_wrapper: reg = %u", a_register);
|
||||
|
||||
@@ -22,13 +22,13 @@ void BP1658CJ::setup() {
|
||||
this->pwm_amounts_.resize(5, 0);
|
||||
}
|
||||
void BP1658CJ::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BP1658CJ:");
|
||||
LOG_PIN(" Data Pin: ", this->data_pin_);
|
||||
LOG_PIN(" Clock Pin: ", this->clock_pin_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"BP1658CJ:\n"
|
||||
" Color Channels Max Power: %u\n"
|
||||
" White Channels Max Power: %u",
|
||||
this->max_power_color_channels_, this->max_power_white_channels_);
|
||||
LOG_PIN(" Data Pin: ", this->data_pin_);
|
||||
LOG_PIN(" Clock Pin: ", this->clock_pin_);
|
||||
}
|
||||
|
||||
void BP1658CJ::loop() {
|
||||
|
||||
36
esphome/components/bthome_mithermometer/__init__.py
Normal file
36
esphome/components/bthome_mithermometer/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_ble_tracker
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_MAC_ADDRESS
|
||||
|
||||
CODEOWNERS = ["@nagyrobi"]
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
BLE_DEVICE_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA
|
||||
|
||||
bthome_mithermometer_ns = cg.esphome_ns.namespace("bthome_mithermometer")
|
||||
BTHomeMiThermometer = bthome_mithermometer_ns.class_(
|
||||
"BTHomeMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||
)
|
||||
|
||||
|
||||
def bthome_mithermometer_base_schema(extra_schema=None):
|
||||
if extra_schema is None:
|
||||
extra_schema = {}
|
||||
return (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(BTHomeMiThermometer),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}
|
||||
)
|
||||
.extend(BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(extra_schema)
|
||||
)
|
||||
|
||||
|
||||
async def setup_bthome_mithermometer(var, config):
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
300
esphome/components/bthome_mithermometer/bthome_ble.cpp
Normal file
300
esphome/components/bthome_mithermometer/bthome_ble.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
#include "bthome_ble.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace bthome_mithermometer {
|
||||
|
||||
static const char *const TAG = "bthome_mithermometer";
|
||||
|
||||
static const char *format_mac_address(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buffer, uint64_t address) {
|
||||
std::array<uint8_t, MAC_ADDRESS_SIZE> mac{};
|
||||
for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) {
|
||||
mac[i] = (address >> ((MAC_ADDRESS_SIZE - 1 - i) * 8)) & 0xFF;
|
||||
}
|
||||
|
||||
format_mac_addr_upper(mac.data(), buffer.data());
|
||||
return buffer.data();
|
||||
}
|
||||
|
||||
static bool get_bthome_value_length(uint8_t obj_type, size_t &value_length) {
|
||||
switch (obj_type) {
|
||||
case 0x00: // packet id
|
||||
case 0x01: // battery
|
||||
case 0x09: // count (uint8)
|
||||
case 0x0F: // generic boolean
|
||||
case 0x10: // power (bool)
|
||||
case 0x11: // opening
|
||||
case 0x15: // battery low
|
||||
case 0x16: // battery charging
|
||||
case 0x17: // carbon monoxide
|
||||
case 0x18: // cold
|
||||
case 0x19: // connectivity
|
||||
case 0x1A: // door
|
||||
case 0x1B: // garage door
|
||||
case 0x1C: // gas
|
||||
case 0x1D: // heat
|
||||
case 0x1E: // light
|
||||
case 0x1F: // lock
|
||||
case 0x20: // moisture
|
||||
case 0x21: // motion
|
||||
case 0x22: // moving
|
||||
case 0x23: // occupancy
|
||||
case 0x24: // plug
|
||||
case 0x25: // presence
|
||||
case 0x26: // problem
|
||||
case 0x27: // running
|
||||
case 0x28: // safety
|
||||
case 0x29: // smoke
|
||||
case 0x2A: // sound
|
||||
case 0x2B: // tamper
|
||||
case 0x2C: // vibration
|
||||
case 0x2D: // water leak
|
||||
case 0x2E: // humidity (uint8)
|
||||
case 0x2F: // moisture (uint8)
|
||||
case 0x46: // UV index
|
||||
case 0x57: // temperature (sint8)
|
||||
case 0x58: // temperature (0.35C step)
|
||||
case 0x59: // count (sint8)
|
||||
case 0x60: // channel
|
||||
value_length = 1;
|
||||
return true;
|
||||
case 0x02: // temperature (0.01C)
|
||||
case 0x03: // humidity
|
||||
case 0x06: // mass (kg)
|
||||
case 0x07: // mass (lb)
|
||||
case 0x08: // dewpoint
|
||||
case 0x0C: // voltage (mV)
|
||||
case 0x0D: // pm2.5
|
||||
case 0x0E: // pm10
|
||||
case 0x12: // CO2
|
||||
case 0x13: // TVOC
|
||||
case 0x14: // moisture
|
||||
case 0x3D: // count (uint16)
|
||||
case 0x3F: // rotation
|
||||
case 0x40: // distance (mm)
|
||||
case 0x41: // distance (m)
|
||||
case 0x43: // current (A)
|
||||
case 0x44: // speed
|
||||
case 0x45: // temperature (0.1C)
|
||||
case 0x47: // volume (L)
|
||||
case 0x48: // volume (mL)
|
||||
case 0x49: // volume flow rate
|
||||
case 0x4A: // voltage (0.1V)
|
||||
case 0x51: // acceleration
|
||||
case 0x52: // gyroscope
|
||||
case 0x56: // conductivity
|
||||
case 0x5A: // count (sint16)
|
||||
case 0x5D: // current (sint16)
|
||||
case 0x5E: // direction
|
||||
case 0x5F: // precipitation
|
||||
case 0x61: // rotational speed
|
||||
case 0xF0: // button event
|
||||
value_length = 2;
|
||||
return true;
|
||||
case 0x04: // pressure
|
||||
case 0x05: // illuminance
|
||||
case 0x0A: // energy
|
||||
case 0x0B: // power
|
||||
case 0x42: // duration
|
||||
case 0x4B: // gas (uint24)
|
||||
case 0xF2: // firmware version (uint24)
|
||||
value_length = 3;
|
||||
return true;
|
||||
case 0x3E: // count (uint32)
|
||||
case 0x4C: // gas (uint32)
|
||||
case 0x4D: // energy (uint32)
|
||||
case 0x4E: // volume (uint32)
|
||||
case 0x4F: // water (uint32)
|
||||
case 0x50: // timestamp
|
||||
case 0x55: // volume storage
|
||||
case 0x5B: // count (sint32)
|
||||
case 0x5C: // power (sint32)
|
||||
case 0x62: // speed (sint32)
|
||||
case 0x63: // acceleration (sint32)
|
||||
case 0xF1: // firmware version (uint32)
|
||||
value_length = 4;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BTHomeMiThermometer::dump_config() {
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG, "BTHome MiThermometer");
|
||||
ESP_LOGCONFIG(TAG, " MAC Address: %s", format_mac_address(addr_buf, this->address_));
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
|
||||
LOG_SENSOR(" ", "Signal Strength", this->signal_strength_);
|
||||
}
|
||||
|
||||
bool BTHomeMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
bool matched = false;
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
if (this->handle_service_data_(service_data, device)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
if (matched && this->signal_strength_ != nullptr) {
|
||||
this->signal_strength_->publish_state(device.get_rssi());
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
bool BTHomeMiThermometer::handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
|
||||
const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (!service_data.uuid.contains(0xD2, 0xFC)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &data = service_data.data;
|
||||
if (data.size() < 2) {
|
||||
ESP_LOGVV(TAG, "BTHome data too short: %zu", data.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t adv_info = data[0];
|
||||
const bool is_encrypted = adv_info & 0x01;
|
||||
const bool mac_included = adv_info & 0x02;
|
||||
const bool is_trigger_based = adv_info & 0x04;
|
||||
const uint8_t version = (adv_info >> 5) & 0x07;
|
||||
|
||||
if (version != 0x02) {
|
||||
ESP_LOGVV(TAG, "Unsupported BTHome version %u", version);
|
||||
return false;
|
||||
}
|
||||
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
if (is_encrypted) {
|
||||
ESP_LOGV(TAG, "Ignoring encrypted BTHome frame from %s", device.address_str_to(addr_buf));
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t payload_index = 1;
|
||||
uint64_t source_address = device.address_uint64();
|
||||
|
||||
if (mac_included) {
|
||||
if (data.size() < 7) {
|
||||
ESP_LOGVV(TAG, "BTHome payload missing MAC address");
|
||||
return false;
|
||||
}
|
||||
source_address = 0;
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
source_address = (source_address << 8) | data[1 + i];
|
||||
}
|
||||
payload_index = 7;
|
||||
}
|
||||
|
||||
if (source_address != this->address_) {
|
||||
ESP_LOGVV(TAG, "BTHome frame from unexpected device %s", format_mac_address(addr_buf, source_address));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payload_index >= data.size()) {
|
||||
ESP_LOGVV(TAG, "BTHome payload empty after header");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool reported = false;
|
||||
size_t offset = payload_index;
|
||||
uint8_t last_type = 0;
|
||||
|
||||
while (offset < data.size()) {
|
||||
const uint8_t obj_type = data[offset++];
|
||||
size_t value_length = 0;
|
||||
bool has_length_byte = obj_type == 0x53; // text objects include explicit length
|
||||
|
||||
if (has_length_byte) {
|
||||
if (offset >= data.size()) {
|
||||
break;
|
||||
}
|
||||
value_length = data[offset++];
|
||||
} else {
|
||||
if (!get_bthome_value_length(obj_type, value_length)) {
|
||||
ESP_LOGVV(TAG, "Unknown BTHome object 0x%02X", obj_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (value_length == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset + value_length > data.size()) {
|
||||
ESP_LOGVV(TAG, "BTHome object length exceeds payload");
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t *value = &data[offset];
|
||||
offset += value_length;
|
||||
|
||||
if (obj_type < last_type) {
|
||||
ESP_LOGVV(TAG, "BTHome objects not in ascending order");
|
||||
}
|
||||
last_type = obj_type;
|
||||
|
||||
switch (obj_type) {
|
||||
case 0x00: { // packet id
|
||||
const uint8_t packet_id = value[0];
|
||||
if (this->last_packet_id_.has_value() && *this->last_packet_id_ == packet_id) {
|
||||
return reported;
|
||||
}
|
||||
this->last_packet_id_ = packet_id;
|
||||
break;
|
||||
}
|
||||
case 0x01: { // battery percentage
|
||||
if (this->battery_level_ != nullptr) {
|
||||
this->battery_level_->publish_state(value[0]);
|
||||
reported = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0C: { // battery voltage (mV)
|
||||
if (this->battery_voltage_ != nullptr) {
|
||||
const uint16_t raw = encode_uint16(value[1], value[0]);
|
||||
this->battery_voltage_->publish_state(raw * 0.001f);
|
||||
reported = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x02: { // temperature
|
||||
if (this->temperature_ != nullptr) {
|
||||
const int16_t raw = encode_uint16(value[1], value[0]);
|
||||
this->temperature_->publish_state(raw * 0.01f);
|
||||
reported = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x03: { // humidity
|
||||
if (this->humidity_ != nullptr) {
|
||||
const uint16_t raw = encode_uint16(value[1], value[0]);
|
||||
this->humidity_->publish_state(raw * 0.01f);
|
||||
reported = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (reported) {
|
||||
ESP_LOGD(TAG, "BTHome data%sfrom %s", is_trigger_based ? " (triggered) " : " ", device.address_str_to(addr_buf));
|
||||
}
|
||||
|
||||
return reported;
|
||||
}
|
||||
|
||||
} // namespace bthome_mithermometer
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
44
esphome/components/bthome_mithermometer/bthome_ble.h
Normal file
44
esphome/components/bthome_mithermometer/bthome_ble.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace bthome_mithermometer {
|
||||
|
||||
class BTHomeMiThermometer : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
void set_address(uint64_t address) { this->address_ = address; }
|
||||
|
||||
void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { this->humidity_ = humidity; }
|
||||
void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; }
|
||||
void set_battery_voltage(sensor::Sensor *battery_voltage) { this->battery_voltage_ = battery_voltage; }
|
||||
void set_signal_strength(sensor::Sensor *signal_strength) { this->signal_strength_ = signal_strength; }
|
||||
|
||||
void dump_config() override;
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
|
||||
protected:
|
||||
bool handle_service_data_(const esp32_ble_tracker::ServiceData &service_data,
|
||||
const esp32_ble_tracker::ESPBTDevice &device);
|
||||
|
||||
uint64_t address_{0};
|
||||
optional<uint8_t> last_packet_id_{};
|
||||
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
sensor::Sensor *battery_level_{nullptr};
|
||||
sensor::Sensor *battery_voltage_{nullptr};
|
||||
sensor::Sensor *signal_strength_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace bthome_mithermometer
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
88
esphome/components/bthome_mithermometer/sensor.py
Normal file
88
esphome/components/bthome_mithermometer/sensor.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_SIGNAL_STRENGTH,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_DECIBEL_MILLIWATT,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
from . import bthome_mithermometer_base_schema, setup_bthome_mithermometer
|
||||
|
||||
CODEOWNERS = ["@nagyrobi"]
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
CONFIG_SCHEMA = bthome_mithermometer_base_schema(
|
||||
{
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon="mdi:battery-plus",
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await setup_bthome_mithermometer(var, config)
|
||||
|
||||
if temp_sens := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temp_sens)
|
||||
cg.add(var.set_temperature(sens))
|
||||
if humi_sens := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humi_sens)
|
||||
cg.add(var.set_humidity(sens))
|
||||
if batl_sens := config.get(CONF_BATTERY_LEVEL):
|
||||
sens = await sensor.new_sensor(batl_sens)
|
||||
cg.add(var.set_battery_level(sens))
|
||||
if batv_sens := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(batv_sens)
|
||||
cg.add(var.set_battery_voltage(sens))
|
||||
if sgnl_sens := config.get(CONF_SIGNAL_STRENGTH):
|
||||
sens = await sensor.new_sensor(sgnl_sens)
|
||||
cg.add(var.set_signal_strength(sens))
|
||||
@@ -63,14 +63,14 @@ void CAP1188Component::finish_setup_() {
|
||||
}
|
||||
|
||||
void CAP1188Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CAP1188:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"CAP1188:\n"
|
||||
" Product ID: 0x%x\n"
|
||||
" Manufacture ID: 0x%x\n"
|
||||
" Revision ID: 0x%x",
|
||||
this->cap1188_product_id_, this->cap1188_manufacture_id_, this->cap1188_revision_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AP,
|
||||
CONF_COMPRESSION,
|
||||
CONF_ID,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
@@ -43,6 +44,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||
web_server_base.WebServerBase
|
||||
),
|
||||
cv.Optional(CONF_COMPRESSION, default="br"): cv.one_of("br", "gzip"),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on(
|
||||
@@ -96,20 +98,26 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
cg.add_define("USE_CAPTIVE_PORTAL")
|
||||
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("ESP32 Async UDP", None)
|
||||
cg.add_library("DNSServer", None)
|
||||
cg.add_library("WiFi", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("DNSServer", None)
|
||||
if CORE.is_libretiny:
|
||||
cg.add_library("DNSServer", None)
|
||||
if config[CONF_COMPRESSION] == "gzip":
|
||||
cg.add_define("USE_CAPTIVE_PORTAL_GZIP")
|
||||
|
||||
# All platforms now use our custom DNS server implementations
|
||||
|
||||
|
||||
# Only compile the ESP-IDF DNS server when using ESP-IDF framework
|
||||
# Compile platform-specific DNS server implementations
|
||||
# ESP32 Arduino uses IDF components, so both use dns_server_esp32_idf.cpp
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
{
|
||||
"dns_server_esp32_idf.cpp": {PlatformFramework.ESP32_IDF},
|
||||
"dns_server_esp32_idf.cpp": {
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"dns_server_arduino.cpp": {
|
||||
PlatformFramework.ESP8266_ARDUINO,
|
||||
PlatformFramework.RP2040_ARDUINO,
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,87 +3,170 @@
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace captive_portal {
|
||||
namespace esphome::captive_portal {
|
||||
|
||||
#ifdef USE_CAPTIVE_PORTAL_GZIP
|
||||
const uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e,
|
||||
0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36,
|
||||
0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf,
|
||||
0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a,
|
||||
0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68,
|
||||
0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5,
|
||||
0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22,
|
||||
0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52,
|
||||
0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06,
|
||||
0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a,
|
||||
0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0,
|
||||
0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84,
|
||||
0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7,
|
||||
0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05,
|
||||
0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6,
|
||||
0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0,
|
||||
0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7,
|
||||
0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b,
|
||||
0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e,
|
||||
0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34,
|
||||
0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b,
|
||||
0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1,
|
||||
0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37,
|
||||
0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac,
|
||||
0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3,
|
||||
0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68,
|
||||
0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc,
|
||||
0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c,
|
||||
0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93,
|
||||
0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c,
|
||||
0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18,
|
||||
0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06,
|
||||
0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c,
|
||||
0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef,
|
||||
0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2,
|
||||
0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9,
|
||||
0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8,
|
||||
0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc,
|
||||
0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca,
|
||||
0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f,
|
||||
0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0,
|
||||
0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f,
|
||||
0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c,
|
||||
0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d,
|
||||
0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf,
|
||||
0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d,
|
||||
0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6,
|
||||
0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5,
|
||||
0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b,
|
||||
0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3,
|
||||
0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69,
|
||||
0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95,
|
||||
0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9,
|
||||
0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e,
|
||||
0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62,
|
||||
0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7,
|
||||
0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97,
|
||||
0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee,
|
||||
0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11,
|
||||
0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b,
|
||||
0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9,
|
||||
0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93,
|
||||
0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97,
|
||||
0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19,
|
||||
0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc,
|
||||
0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2,
|
||||
0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc,
|
||||
0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e,
|
||||
0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e,
|
||||
0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9,
|
||||
0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3,
|
||||
0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5,
|
||||
0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37,
|
||||
0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f,
|
||||
0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22,
|
||||
0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68,
|
||||
0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x95, 0x57, 0x6d, 0x6f, 0xdb, 0x36, 0x10, 0xfe, 0xde,
|
||||
0x5f, 0xc1, 0xb1, 0xdd, 0x2c, 0xad, 0x91, 0x64, 0x29, 0xb1, 0xdb, 0xda, 0x92, 0x8a, 0xae, 0xdb, 0xb0, 0x02, 0xeb,
|
||||
0x5a, 0x20, 0xd9, 0xf6, 0xa1, 0x28, 0x66, 0x5a, 0x3a, 0x59, 0x6c, 0x24, 0x4a, 0x23, 0xe9, 0xb7, 0x1a, 0xde, 0x6f,
|
||||
0xdf, 0x91, 0x92, 0x15, 0x27, 0x58, 0x87, 0x2d, 0x41, 0x6c, 0xbe, 0xdc, 0x3b, 0xef, 0x9e, 0xbb, 0xc4, 0x5f, 0xe5,
|
||||
0x4d, 0xa6, 0xf7, 0x2d, 0x90, 0x52, 0xd7, 0x55, 0x1a, 0x9b, 0x4f, 0x52, 0x31, 0xb1, 0x4a, 0x40, 0xe0, 0x0e, 0x58,
|
||||
0x9e, 0xc6, 0x35, 0x68, 0x46, 0xb2, 0x92, 0x49, 0x05, 0x3a, 0xf9, 0xf5, 0xe6, 0x47, 0xef, 0x79, 0x1a, 0x57, 0x5c,
|
||||
0xdc, 0x12, 0x09, 0x55, 0xc2, 0xb3, 0x46, 0x90, 0x52, 0x42, 0x91, 0xe4, 0x4c, 0xb3, 0x19, 0xaf, 0xd9, 0x0a, 0x7a,
|
||||
0x16, 0xc1, 0x6a, 0x48, 0x36, 0x1c, 0xb6, 0x6d, 0x23, 0x35, 0x41, 0x3a, 0x0d, 0x42, 0x27, 0x74, 0xcb, 0x73, 0x5d,
|
||||
0x26, 0x39, 0x6c, 0x78, 0x06, 0x9e, 0xdd, 0x5c, 0x70, 0xc1, 0x35, 0x67, 0x95, 0xa7, 0x32, 0x56, 0x41, 0x12, 0x5e,
|
||||
0xac, 0x15, 0x48, 0xbb, 0x61, 0x4b, 0xdc, 0x8b, 0x86, 0xa6, 0xb1, 0xca, 0x24, 0x6f, 0x35, 0x31, 0xa6, 0x26, 0x75,
|
||||
0x93, 0xaf, 0x2b, 0x48, 0x83, 0x80, 0x29, 0x34, 0x49, 0x05, 0x5c, 0xe4, 0xb0, 0xf3, 0x27, 0xe1, 0x34, 0x7a, 0x3e,
|
||||
0x99, 0xbe, 0xf0, 0x3f, 0xa9, 0x47, 0xe8, 0xd4, 0xba, 0x46, 0x6d, 0x7e, 0xd5, 0x64, 0x4c, 0xf3, 0x46, 0xf8, 0x0a,
|
||||
0x98, 0xcc, 0xca, 0x24, 0x49, 0xe8, 0x4b, 0xc5, 0x36, 0x40, 0xbf, 0xf9, 0xc6, 0x19, 0x88, 0x56, 0xa0, 0x7f, 0xa8,
|
||||
0xc0, 0x2c, 0xd5, 0x77, 0xfb, 0x1b, 0xb6, 0xfa, 0x05, 0x0d, 0x77, 0x28, 0x53, 0x3c, 0x07, 0xea, 0x7e, 0x18, 0x7f,
|
||||
0xf4, 0x95, 0xde, 0x57, 0xe0, 0xe7, 0x5c, 0xb5, 0x15, 0xdb, 0x27, 0x74, 0x89, 0x52, 0x6f, 0xa9, 0x3b, 0x2f, 0xd6,
|
||||
0x22, 0x33, 0xc2, 0x89, 0x74, 0xc0, 0x3d, 0x54, 0x80, 0xe6, 0x25, 0x6f, 0x99, 0x2e, 0xfd, 0x9a, 0xed, 0x9c, 0x6e,
|
||||
0xc1, 0x85, 0x13, 0x7d, 0xeb, 0xc0, 0xd3, 0x70, 0x3c, 0x76, 0x2f, 0xec, 0xc7, 0xd8, 0x0d, 0xf0, 0x7b, 0x2e, 0x41,
|
||||
0xaf, 0xa5, 0x20, 0x8d, 0xb3, 0x88, 0x5b, 0xa4, 0x24, 0x79, 0x42, 0xdf, 0x86, 0x11, 0x09, 0x5f, 0xf8, 0xd1, 0xe4,
|
||||
0x67, 0xff, 0x19, 0xb9, 0xc2, 0xef, 0xec, 0x99, 0x37, 0x21, 0xe1, 0x15, 0x7e, 0x44, 0x91, 0x3f, 0x21, 0xe3, 0xcf,
|
||||
0x94, 0x14, 0xbc, 0xaa, 0x12, 0x2a, 0x1a, 0x01, 0x94, 0x28, 0x2d, 0x9b, 0x5b, 0x48, 0x68, 0xb6, 0x96, 0x12, 0x6d,
|
||||
0x7f, 0xdd, 0x54, 0x8d, 0xa4, 0x41, 0xfa, 0xe8, 0x7f, 0x09, 0xd4, 0x92, 0x09, 0x55, 0x34, 0xb2, 0x4e, 0xa8, 0x8d,
|
||||
0xbe, 0xf3, 0xe4, 0xa0, 0x8f, 0xc4, 0x7c, 0xb8, 0x67, 0x97, 0x5e, 0x23, 0xf9, 0x8a, 0x8b, 0x84, 0x1a, 0x89, 0xcf,
|
||||
0x51, 0xc9, 0xc2, 0x3d, 0x0e, 0xde, 0x37, 0xc6, 0xfb, 0xde, 0x1f, 0xe5, 0x7c, 0x58, 0xc4, 0x6a, 0xb3, 0x22, 0xbb,
|
||||
0xba, 0x12, 0x2a, 0xa1, 0xa5, 0xd6, 0xed, 0x2c, 0x08, 0xb6, 0xdb, 0xad, 0xbf, 0xbd, 0xf4, 0x1b, 0xb9, 0x0a, 0xa2,
|
||||
0xf1, 0x78, 0x1c, 0x20, 0x05, 0x25, 0x5d, 0x22, 0xd0, 0xe8, 0x8a, 0x92, 0x12, 0xf8, 0xaa, 0xd4, 0x76, 0x9d, 0x3e,
|
||||
0x39, 0xc0, 0x31, 0x36, 0x14, 0xe9, 0xe2, 0xe3, 0x99, 0x16, 0x7e, 0xa6, 0x05, 0x5e, 0x36, 0x0e, 0x3d, 0xb9, 0x39,
|
||||
0xb2, 0x6e, 0x3e, 0x63, 0x11, 0x89, 0xc8, 0xd8, 0xfe, 0x46, 0x9e, 0x59, 0xf7, 0x3b, 0xef, 0xc1, 0x8e, 0x9c, 0xed,
|
||||
0xcc, 0xaa, 0x9e, 0x7a, 0x2f, 0x06, 0xde, 0xd0, 0x9c, 0x6c, 0xc2, 0xf1, 0xdd, 0x81, 0x61, 0xf8, 0x69, 0x7a, 0xbe,
|
||||
0xf7, 0xa2, 0xdf, 0xce, 0x09, 0x8c, 0xb6, 0x32, 0xfc, 0x6d, 0xca, 0x26, 0x64, 0xd2, 0x9f, 0x4c, 0x3c, 0xb3, 0x1e,
|
||||
0x76, 0x64, 0xb2, 0x41, 0x8a, 0xda, 0x9b, 0x7a, 0x13, 0x76, 0x49, 0x2e, 0x7b, 0x43, 0x70, 0x85, 0xc7, 0x53, 0x64,
|
||||
0x3c, 0x3b, 0xf3, 0x2e, 0x3f, 0x8f, 0x82, 0x94, 0xba, 0x33, 0x4a, 0xef, 0x3c, 0x57, 0xe7, 0x9e, 0xfb, 0x9f, 0x1a,
|
||||
0xcc, 0x29, 0x4a, 0x31, 0x32, 0xa0, 0xb3, 0xd2, 0xa1, 0x01, 0x16, 0x56, 0xc1, 0x57, 0x98, 0xf5, 0x8d, 0xa0, 0xae,
|
||||
0xaf, 0x4b, 0x10, 0xce, 0x89, 0xd5, 0x30, 0x82, 0xbd, 0x71, 0x1e, 0xde, 0x68, 0xf7, 0x30, 0xe4, 0xbf, 0xe6, 0x1a,
|
||||
0xcb, 0x4c, 0xfb, 0xa6, 0x60, 0x2f, 0x86, 0xd3, 0x65, 0x93, 0xef, 0xbf, 0x50, 0x1a, 0x65, 0xd8, 0xd5, 0x05, 0x17,
|
||||
0x02, 0xe4, 0x0d, 0xec, 0xf0, 0xe5, 0xde, 0xbe, 0x7a, 0x4d, 0x5e, 0xe5, 0xb9, 0x04, 0xa5, 0x66, 0x84, 0x3e, 0xd5,
|
||||
0x58, 0x03, 0xd9, 0x7f, 0x97, 0x15, 0xde, 0x93, 0xf5, 0x3b, 0xff, 0x91, 0x93, 0x5f, 0x40, 0x6f, 0x1b, 0x79, 0xdb,
|
||||
0x4b, 0x33, 0xa6, 0xcd, 0x4d, 0x85, 0x31, 0xb4, 0x93, 0xb5, 0xca, 0x57, 0x15, 0xc2, 0x87, 0x13, 0xba, 0xa8, 0xa7,
|
||||
0xbd, 0xf3, 0x4a, 0x9c, 0x02, 0xb5, 0x88, 0x73, 0xbe, 0x21, 0x59, 0x85, 0x08, 0x81, 0xe5, 0xd2, 0x89, 0xa2, 0xe4,
|
||||
0x11, 0xb1, 0x3f, 0x8d, 0xc8, 0x90, 0xfb, 0x36, 0xa1, 0xff, 0x80, 0x00, 0xdf, 0xed, 0xdf, 0xe4, 0xce, 0x48, 0x61,
|
||||
0xed, 0x8f, 0x5c, 0x7f, 0xc3, 0xaa, 0x35, 0x90, 0x84, 0xe8, 0x92, 0xab, 0x3b, 0x03, 0xe7, 0x5f, 0x64, 0x6b, 0xd5,
|
||||
0x2d, 0x72, 0x15, 0x78, 0xad, 0x1c, 0x97, 0xa6, 0xbd, 0xba, 0x98, 0x75, 0x00, 0x49, 0x1f, 0xd3, 0x07, 0x16, 0x79,
|
||||
0x15, 0x14, 0x7a, 0xa0, 0x23, 0x58, 0x76, 0xd2, 0x11, 0xbe, 0x44, 0xed, 0xee, 0x71, 0x38, 0x8c, 0x55, 0xcb, 0xc4,
|
||||
0x43, 0x46, 0x63, 0xa0, 0x29, 0x15, 0x04, 0x36, 0x5c, 0x99, 0x7a, 0x41, 0xa2, 0x41, 0x61, 0xc0, 0x4e, 0xcb, 0x27,
|
||||
0x07, 0x8e, 0x12, 0x0d, 0x5e, 0x0d, 0x12, 0xe3, 0x00, 0x43, 0x93, 0x2e, 0x8e, 0xee, 0x9c, 0x17, 0x77, 0x18, 0xf8,
|
||||
0xe7, 0x1a, 0xe4, 0xfe, 0x1a, 0x2a, 0xc8, 0x74, 0x23, 0x1d, 0xfa, 0x18, 0x15, 0x61, 0x36, 0x59, 0x9f, 0x7f, 0xba,
|
||||
0x79, 0xfb, 0x73, 0xa2, 0x1c, 0xe6, 0x5e, 0x7c, 0x89, 0xda, 0x74, 0x83, 0x0f, 0xd8, 0x0d, 0xfe, 0x4a, 0x46, 0xa6,
|
||||
0x1f, 0x8c, 0x3e, 0x22, 0xab, 0x75, 0x79, 0x71, 0xd7, 0x14, 0x4c, 0x45, 0x3f, 0x45, 0x54, 0xb8, 0x30, 0x4e, 0x7a,
|
||||
0xd3, 0x89, 0x7b, 0x5c, 0x5c, 0x68, 0x7f, 0x0b, 0xcb, 0x3f, 0x10, 0xec, 0x37, 0x20, 0x3b, 0x00, 0x15, 0xc9, 0xa0,
|
||||
0x23, 0x93, 0xc0, 0x34, 0xf4, 0xf1, 0x75, 0x28, 0x1a, 0x8d, 0x90, 0x2b, 0xce, 0x4c, 0x5a, 0xc4, 0xe5, 0x65, 0xfa,
|
||||
0xbd, 0xed, 0x23, 0xe4, 0x35, 0x76, 0x17, 0xd9, 0x54, 0x71, 0x80, 0x47, 0x43, 0xbc, 0x83, 0x97, 0x77, 0xe2, 0x93,
|
||||
0x10, 0xe1, 0x8c, 0x49, 0x7c, 0xb1, 0x84, 0xfe, 0xb1, 0xc4, 0x0e, 0x87, 0xe9, 0x30, 0xe4, 0xc1, 0x16, 0x3b, 0x48,
|
||||
0xb3, 0xf5, 0x9b, 0x16, 0x2b, 0x64, 0x74, 0x9f, 0x6b, 0x74, 0x31, 0xea, 0xc8, 0x47, 0xee, 0x09, 0xb3, 0x0b, 0x56,
|
||||
0x29, 0x98, 0xd3, 0xf4, 0x1d, 0x92, 0x93, 0xdf, 0x61, 0x49, 0xae, 0x2d, 0xad, 0x89, 0x7a, 0xdc, 0x12, 0xdb, 0x2b,
|
||||
0x12, 0x5a, 0xa0, 0x41, 0x9e, 0xe2, 0x9f, 0x61, 0x16, 0x46, 0xed, 0x6e, 0x9e, 0x19, 0x70, 0x9e, 0x3d, 0x9e, 0x4e,
|
||||
0xa7, 0xc8, 0xf8, 0xa6, 0xb0, 0x19, 0x45, 0xf2, 0x06, 0x94, 0x18, 0x69, 0x62, 0xf4, 0x5e, 0x90, 0x9a, 0x89, 0x35,
|
||||
0xab, 0xaa, 0x3d, 0x59, 0xca, 0x66, 0xab, 0x80, 0xe8, 0x06, 0x5f, 0xaf, 0xb7, 0x6c, 0x68, 0x63, 0x1d, 0x0e, 0x1f,
|
||||
0xef, 0xdb, 0x18, 0x07, 0x2d, 0xf6, 0x6a, 0x99, 0xc6, 0x4b, 0x99, 0x2e, 0xfe, 0x7b, 0xd1, 0x45, 0x1f, 0xfd, 0x25,
|
||||
0x20, 0xba, 0x03, 0x96, 0xcd, 0xf1, 0xe8, 0x62, 0x42, 0x60, 0x1a, 0xd9, 0x36, 0x8b, 0xed, 0xd6, 0x78, 0x91, 0x7e,
|
||||
0x7b, 0x58, 0x36, 0x3b, 0xe3, 0x05, 0x17, 0xab, 0x19, 0x17, 0x25, 0x48, 0xae, 0x8f, 0xf8, 0x10, 0xd8, 0xb0, 0xdb,
|
||||
0xb5, 0x3e, 0xb4, 0x2c, 0xcf, 0xcd, 0xcd, 0x04, 0x1d, 0x3c, 0xf3, 0x17, 0xea, 0x63, 0x77, 0x6f, 0x01, 0x7e, 0xf6,
|
||||
0x62, 0xf2, 0xf5, 0xd1, 0x98, 0x72, 0xd0, 0x58, 0x3b, 0x1e, 0xab, 0xf8, 0x4a, 0xcc, 0x32, 0xb4, 0x07, 0x64, 0xc7,
|
||||
0x54, 0xb0, 0x9a, 0x57, 0xfb, 0x99, 0xc2, 0x56, 0xe3, 0xa1, 0x47, 0xbc, 0x38, 0x2e, 0xd7, 0x5a, 0x37, 0x02, 0x75,
|
||||
0xcb, 0x1c, 0xe4, 0x6c, 0x3c, 0xef, 0x16, 0x9e, 0x64, 0x39, 0x5f, 0xab, 0x99, 0x7f, 0x29, 0xa1, 0x9e, 0x2f, 0x59,
|
||||
0x76, 0xbb, 0x92, 0xcd, 0x5a, 0xe4, 0x5e, 0x1f, 0xdb, 0xb0, 0x60, 0x97, 0x90, 0x9d, 0x22, 0x5d, 0x14, 0xc5, 0x1c,
|
||||
0xd3, 0x12, 0xbc, 0xae, 0xb5, 0xcc, 0x22, 0xff, 0xca, 0xb0, 0x9d, 0x99, 0xe9, 0x47, 0xe6, 0xa0, 0xb3, 0x11, 0x3b,
|
||||
0xf2, 0xd7, 0xf3, 0x93, 0x3b, 0xe3, 0x39, 0x76, 0x54, 0x85, 0x42, 0x5a, 0x84, 0x5a, 0x34, 0xf3, 0x58, 0x33, 0x2e,
|
||||
0xce, 0xad, 0x37, 0x55, 0x3b, 0xef, 0xa7, 0x01, 0x0c, 0x8b, 0x55, 0x63, 0x67, 0x82, 0x39, 0xf6, 0xfb, 0x6e, 0xa4,
|
||||
0x99, 0x45, 0xd3, 0x71, 0xbb, 0x3b, 0xfa, 0x7d, 0xbd, 0x1e, 0x4e, 0xd4, 0x45, 0x05, 0xbb, 0xf9, 0xa7, 0xb5, 0xd2,
|
||||
0xbc, 0xd8, 0x7b, 0xfd, 0x48, 0x34, 0xc3, 0xda, 0xc5, 0x51, 0x68, 0x89, 0xa4, 0x00, 0x62, 0x6e, 0x75, 0x78, 0x5c,
|
||||
0x43, 0xad, 0xfa, 0x38, 0x0d, 0x62, 0x2c, 0x5e, 0xdc, 0x97, 0xf5, 0x6f, 0xd4, 0x06, 0x1a, 0x0e, 0x35, 0x26, 0x3d,
|
||||
0x5a, 0xb5, 0x6c, 0x30, 0xa6, 0xf5, 0xcc, 0x7b, 0x86, 0x6f, 0xd5, 0x1f, 0x19, 0x61, 0xe8, 0x39, 0x9a, 0x69, 0x47,
|
||||
0x9d, 0x53, 0xbc, 0xc3, 0x76, 0x47, 0x54, 0x53, 0xf1, 0xbc, 0xa7, 0xb3, 0x24, 0x64, 0x3c, 0x84, 0x27, 0xc4, 0xe7,
|
||||
0x26, 0xe6, 0xec, 0x14, 0xea, 0xab, 0xe2, 0x39, 0x0b, 0xc7, 0xff, 0xf0, 0x22, 0x79, 0x51, 0x44, 0xcb, 0x62, 0x88,
|
||||
0x94, 0x99, 0x5a, 0x0c, 0x52, 0xd9, 0xd4, 0xc2, 0x4a, 0xb5, 0xc3, 0xa5, 0xc9, 0x0c, 0x9c, 0x17, 0x31, 0xc2, 0x98,
|
||||
0xc2, 0x21, 0xe1, 0x79, 0x82, 0x8d, 0x23, 0x3d, 0xeb, 0x26, 0x48, 0x18, 0x9e, 0xae, 0x70, 0x75, 0xaf, 0x37, 0x74,
|
||||
0x77, 0xd6, 0xfa, 0x14, 0x11, 0x40, 0x20, 0x1c, 0xa1, 0x85, 0xa6, 0x72, 0xfa, 0x10, 0xf8, 0xbe, 0x6f, 0x8a, 0xe2,
|
||||
0x7d, 0x05, 0x0c, 0x2b, 0x6a, 0xcb, 0xb8, 0xf6, 0xb1, 0x4c, 0x2d, 0x7d, 0x07, 0xab, 0x28, 0x14, 0x49, 0xd3, 0x1e,
|
||||
0x3f, 0x0d, 0xa0, 0x58, 0x05, 0xd7, 0xa0, 0x8d, 0x24, 0xd5, 0xe1, 0x89, 0x99, 0x80, 0x08, 0xb3, 0x1d, 0x26, 0x09,
|
||||
0xb6, 0xbc, 0xe0, 0x66, 0x82, 0x4c, 0x63, 0x9b, 0xe4, 0x46, 0x82, 0x89, 0x73, 0x37, 0xed, 0xda, 0x55, 0x05, 0x62,
|
||||
0x85, 0x83, 0xcd, 0x65, 0x44, 0xd0, 0xed, 0x0c, 0xca, 0xa6, 0xc2, 0xb0, 0x26, 0xd7, 0xd7, 0x6f, 0xbe, 0xb7, 0x15,
|
||||
0x7a, 0xc7, 0x87, 0xed, 0xa2, 0x63, 0x33, 0x8b, 0x9e, 0x6b, 0x7a, 0xd5, 0xcd, 0xb8, 0x2d, 0xc2, 0x3d, 0x7a, 0x90,
|
||||
0xdf, 0x93, 0xf1, 0xbe, 0x3f, 0xec, 0xe4, 0xd8, 0x3f, 0x5b, 0x2a, 0xe9, 0x35, 0x1a, 0x14, 0x07, 0xfd, 0x26, 0x0e,
|
||||
0x8c, 0xc1, 0xdd, 0x7d, 0x8f, 0x0a, 0x18, 0xbf, 0xf4, 0xdd, 0xcd, 0x2b, 0xf2, 0x6b, 0x8b, 0x80, 0x0c, 0x5d, 0xd8,
|
||||
0xac, 0x57, 0x38, 0xa8, 0x97, 0x4d, 0x9e, 0xbc, 0x7f, 0x77, 0x7d, 0x33, 0x78, 0xb8, 0xb6, 0x44, 0x04, 0x44, 0xd6,
|
||||
0x4d, 0xdb, 0xeb, 0x4a, 0xf3, 0x96, 0x49, 0x6d, 0xc5, 0x7a, 0x06, 0xd1, 0x4f, 0x3e, 0xd8, 0x7b, 0x1c, 0x49, 0xa1,
|
||||
0x73, 0xa3, 0x63, 0x4c, 0xc9, 0xc9, 0xaa, 0x93, 0xb6, 0x07, 0x76, 0x05, 0xdd, 0x6b, 0x07, 0xdd, 0xd3, 0x07, 0xf6,
|
||||
0x1f, 0x8f, 0xbf, 0x01, 0x7d, 0x9a, 0x47, 0xb5, 0x88, 0x0c, 0x00, 0x00};
|
||||
|
||||
} // namespace captive_portal
|
||||
} // namespace esphome
|
||||
#else // Brotli (default, smaller)
|
||||
const uint8_t INDEX_BR[] PROGMEM = {
|
||||
0x1b, 0x87, 0x0c, 0x00, 0x64, 0x5a, 0xd3, 0xfa, 0xe7, 0xf3, 0x62, 0x48, 0x83, 0x8d, 0xa0, 0x60, 0x49, 0x51, 0xb8,
|
||||
0x52, 0x9b, 0x44, 0x71, 0xc3, 0xe5, 0xc4, 0x8c, 0xb8, 0xd5, 0xe1, 0xfc, 0x6a, 0x2d, 0xdf, 0x92, 0x04, 0x76, 0x51,
|
||||
0xb1, 0x51, 0xa9, 0x08, 0x05, 0xd6, 0xe5, 0x75, 0xf7, 0x74, 0x08, 0x41, 0xa5, 0x6a, 0xe6, 0xf6, 0xc4, 0xde, 0x86,
|
||||
0x59, 0x38, 0x40, 0x45, 0xf0, 0xad, 0x23, 0xf9, 0xfd, 0x21, 0x5b, 0x7f, 0x3f, 0x73, 0xc1, 0x4a, 0x60, 0x0f, 0x75,
|
||||
0xea, 0x45, 0x28, 0x87, 0x33, 0x98, 0xa0, 0x4d, 0x5b, 0xc9, 0x2e, 0x9e, 0x4b, 0x1c, 0x78, 0xa7, 0x0e, 0x7e, 0x39,
|
||||
0x76, 0x04, 0x75, 0x08, 0xb1, 0xb7, 0x5b, 0xa4, 0x5a, 0xb2, 0x2b, 0x4d, 0x84, 0x1c, 0xf8, 0x60, 0x78, 0xc7, 0x2a,
|
||||
0x1c, 0x73, 0x9e, 0x53, 0x90, 0xb2, 0xa0, 0xf6, 0x3c, 0xb5, 0x9b, 0x05, 0x0b, 0xea, 0x47, 0x5f, 0xa8, 0xd0, 0xb8,
|
||||
0xb0, 0x1d, 0x94, 0x63, 0x43, 0x68, 0xc1, 0x1c, 0x57, 0xaa, 0x55, 0x86, 0xe1, 0x44, 0x17, 0x89, 0xf9, 0x27, 0x29,
|
||||
0xcb, 0xf8, 0x26, 0xcd, 0xb6, 0xf4, 0xaf, 0x9b, 0xd4, 0x9f, 0x5b, 0x6c, 0x58, 0x08, 0x01, 0xef, 0x4c, 0x9e, 0x7e,
|
||||
0xb8, 0x58, 0xa0, 0x88, 0x61, 0xa8, 0xf2, 0x62, 0xd5, 0x78, 0x24, 0xfc, 0x21, 0x8e, 0xa9, 0x86, 0xda, 0xba, 0x03,
|
||||
0xe2, 0xbc, 0x75, 0x07, 0x7b, 0xa4, 0xf1, 0xf8, 0x7a, 0x71, 0x00, 0x83, 0xd8, 0xb9, 0x6e, 0x6f, 0xab, 0xae, 0x91,
|
||||
0xe3, 0x7d, 0x63, 0x11, 0x3f, 0x43, 0x7a, 0xc5, 0xa2, 0x08, 0x07, 0xff, 0x34, 0x45, 0x38, 0x7c, 0x33, 0x52, 0xd8,
|
||||
0xdd, 0xa1, 0xb2, 0xd0, 0xed, 0xbf, 0xae, 0x05, 0x7c, 0xc5, 0x38, 0x60, 0x5b, 0xca, 0xd3, 0x97, 0x74, 0x0d, 0x92,
|
||||
0xd3, 0xd4, 0x6a, 0x4d, 0x52, 0xc0, 0x92, 0xbb, 0x64, 0xce, 0x69, 0x73, 0x74, 0x0b, 0x2f, 0x0b, 0x47, 0x6c, 0x01,
|
||||
0x6f, 0x31, 0x70, 0x43, 0xdf, 0x1d, 0xb5, 0x80, 0x1c, 0x40, 0x97, 0x06, 0xc5, 0xa9, 0x85, 0x72, 0x92, 0xa8, 0x5e,
|
||||
0x82, 0x8c, 0x34, 0x05, 0xa6, 0xa1, 0xd9, 0x38, 0x78, 0x70, 0xef, 0xc7, 0x11, 0x4a, 0xc9, 0xcd, 0x5b, 0x06, 0xa9,
|
||||
0x2e, 0xdc, 0xc0, 0x50, 0x96, 0xd8, 0xb7, 0x40, 0x4c, 0x27, 0x75, 0x7a, 0xd4, 0xa1, 0x1f, 0x65, 0xe1, 0x2e, 0xcf,
|
||||
0x2f, 0x79, 0x80, 0x3a, 0x01, 0x7b, 0x7b, 0x08, 0x77, 0xcd, 0x55, 0x2c, 0xb2, 0xd0, 0xda, 0x45, 0x61, 0x2c, 0x95,
|
||||
0xca, 0xf5, 0x3c, 0x91, 0xf7, 0x1c, 0x09, 0x94, 0xb3, 0x51, 0xfb, 0x62, 0xbf, 0x2c, 0x7f, 0xe5, 0x94, 0xdb, 0x8c,
|
||||
0xeb, 0x3b, 0x1d, 0x82, 0x55, 0x8d, 0x5d, 0x76, 0xae, 0x15, 0x07, 0xbf, 0x63, 0xa3, 0x6a, 0x4e, 0xdc, 0xf2, 0x92,
|
||||
0x1f, 0xc9, 0x0d, 0xa2, 0xc1, 0x40, 0x9b, 0x91, 0x2d, 0x7d, 0x31, 0xf3, 0xdd, 0x77, 0xc9, 0xa2, 0xc7, 0x68, 0xb2,
|
||||
0xe1, 0x69, 0xf6, 0x78, 0x80, 0xf0, 0xcf, 0x91, 0x90, 0x63, 0xe3, 0x81, 0x7d, 0xfe, 0x4a, 0x4f, 0x41, 0xda, 0x0c,
|
||||
0x52, 0xe2, 0xd6, 0x97, 0x26, 0x90, 0x5e, 0xc6, 0x6a, 0x6c, 0x49, 0x16, 0x94, 0xa1, 0x62, 0x10, 0x67, 0x46, 0x24,
|
||||
0x06, 0x71, 0x61, 0x46, 0xec, 0x7c, 0xff, 0x0e, 0x89, 0x6f, 0x97, 0xa1, 0x84, 0x78, 0x07, 0x61, 0xed, 0xf3, 0x38,
|
||||
0x5c, 0x1c, 0x20, 0x1f, 0x42, 0x88, 0x7d, 0xa3, 0x87, 0xea, 0x80, 0x60, 0x78, 0xe4, 0x92, 0xd8, 0x46, 0x64, 0x31,
|
||||
0xbe, 0x9c, 0x42, 0x0c, 0x39, 0x88, 0x4b, 0x74, 0x68, 0xbe, 0x28, 0x6a, 0x50, 0x19, 0x20, 0x75, 0xd1, 0x2c, 0x06,
|
||||
0x2a, 0xb4, 0x01, 0x81, 0x12, 0x09, 0x43, 0x3c, 0x6a, 0x0f, 0xec, 0x55, 0x0f, 0xac, 0x17, 0xd7, 0x2b, 0x52, 0xdb,
|
||||
0xab, 0x7b, 0x0f, 0xc6, 0xe3, 0x3d, 0xa5, 0x1d, 0x80, 0xab, 0x81, 0xb6, 0xaa, 0x4a, 0x2f, 0x2f, 0xeb, 0xfb, 0x62,
|
||||
0x1e, 0x9b, 0xa1, 0x55, 0xf4, 0xc2, 0xc5, 0x2b, 0xce, 0xa5, 0x6c, 0xa6, 0x12, 0x03, 0x55, 0x27, 0x77, 0x11, 0x9b,
|
||||
0x5c, 0xc2, 0x30, 0x6d, 0xd5, 0xa9, 0x66, 0xe5, 0xb6, 0xb1, 0x22, 0x4a, 0x4d, 0x27, 0x87, 0x40, 0xd7, 0x02, 0x26,
|
||||
0xe0, 0x97, 0xea, 0xd6, 0xb0, 0x40, 0xd8, 0x5c, 0xe7, 0x4c, 0xbd, 0xc4, 0xd4, 0xa6, 0xa6, 0xbe, 0x54, 0x0b, 0xb9,
|
||||
0xbc, 0x9c, 0xb4, 0xf1, 0xe4, 0x8e, 0x8f, 0x3a, 0x3a, 0xab, 0x16, 0xca, 0x2c, 0xbd, 0x30, 0x87, 0x94, 0xaa, 0x8c,
|
||||
0xcc, 0x6b, 0x90, 0x5f, 0xdf, 0xce, 0xc6, 0x1e, 0x59, 0xda, 0x3b, 0x67, 0xb0, 0x0f, 0x70, 0x95, 0xb6, 0x21, 0x46,
|
||||
0x21, 0x21, 0x2e, 0xd5, 0x96, 0xba, 0xca, 0x3c, 0x5e, 0x10, 0x3a, 0x6c, 0xb2, 0xde, 0xa4, 0x1a, 0x6d, 0xa0, 0xb3,
|
||||
0xe4, 0xce, 0x80, 0x8b, 0x9a, 0xd2, 0x23, 0x01, 0xad, 0x0e, 0x75, 0x97, 0xdc, 0x97, 0x66, 0xef, 0x7a, 0x04, 0xa7,
|
||||
0x56, 0x0f, 0x50, 0x91, 0xd3, 0x2d, 0x09, 0x87, 0x14, 0x0e, 0x28, 0xd0, 0x23, 0x44, 0xff, 0x68, 0xfe, 0x2f, 0x96,
|
||||
0x2f, 0x55, 0xcb, 0x5f, 0xe4, 0x54, 0x6d, 0x5f, 0xd9, 0x31, 0x40, 0xaa, 0x87, 0x57, 0xd7, 0xed, 0xbf, 0xa0, 0xa2,
|
||||
0x1f, 0x93, 0x2c, 0xc5, 0xbe, 0x0c, 0x06, 0x7a, 0xa5, 0xf7, 0x7f, 0x7a, 0x2e, 0x56, 0xa2, 0xcb, 0xb2, 0xc0, 0xe7,
|
||||
0xf2, 0xda, 0x5c, 0x42, 0x9c, 0xdb, 0x62, 0x15, 0x97, 0xd0, 0x87, 0xd7, 0x9d, 0x93, 0xee, 0x99, 0x20, 0x81, 0xd5,
|
||||
0x6b, 0xfa, 0xee, 0x64, 0xb1, 0x85, 0x60, 0x1d, 0x9e, 0xf2, 0x82, 0x73, 0x6c, 0x5c, 0x21, 0x28, 0xdb, 0x96, 0xc1,
|
||||
0x12, 0x43, 0x6e, 0x86, 0xf3, 0xfe, 0x14, 0xf3, 0xd2, 0x0b, 0xbf, 0xc4, 0x8f, 0x03, 0x1f, 0xd0, 0xf2, 0x5b, 0x15,
|
||||
0xa7, 0x8e, 0x18, 0x08, 0x43, 0x80, 0x61, 0x53, 0x96, 0x65, 0xc4, 0x35, 0xcf, 0x1a, 0xae, 0x92, 0x05, 0x4b, 0x18,
|
||||
0x94, 0xcf, 0xd7, 0x63, 0x38, 0x1f, 0x6c, 0xd0, 0x66, 0xb3, 0x71, 0x49, 0x1d, 0xeb, 0xe2, 0x00, 0x5f, 0x4e, 0xc0,
|
||||
0xc4, 0xee, 0xe2, 0xd0, 0x4b, 0x94, 0xb6, 0x4a, 0xa5, 0xe0, 0x45, 0x71, 0xc6, 0x28, 0x57, 0x21, 0x8b, 0xbd, 0xc7,
|
||||
0x1e, 0x68, 0x50, 0x4d, 0xb2, 0xc8, 0xb3, 0x71, 0xdf, 0x5d, 0x0f, 0x18, 0x7b, 0xbe, 0xdb, 0xa3, 0xe9, 0x88, 0xaf,
|
||||
0xcd, 0x65, 0x80, 0x2e, 0x14, 0x5b, 0x02, 0x8a, 0xd9, 0x75, 0x6e, 0xd5, 0xad, 0x8f, 0xc3, 0xf0, 0xf3, 0xb0, 0x6d,
|
||||
0x3a, 0xdf, 0x1a, 0x91, 0x07, 0xe6, 0x70, 0x30, 0x88, 0x85, 0x4d, 0x4b, 0x3d, 0xfa, 0x29, 0x1b, 0x21, 0x6b, 0x6d,
|
||||
0x94, 0x3b, 0xfe, 0x03, 0xe9, 0x55, 0x6d, 0x2e, 0xdc, 0x8e, 0xc6, 0xbd, 0x6e, 0x73, 0x08, 0x73, 0x0d, 0xd6, 0xa8,
|
||||
0x58, 0x57, 0xa3, 0x03, 0x23, 0x72, 0x84, 0xd3, 0xcc, 0x69, 0x42, 0x6a, 0x17, 0xb5, 0x9a, 0x72, 0x4f, 0x1b, 0x35,
|
||||
0x2b, 0xc4, 0x0f, 0xca, 0x67, 0x8d, 0x16, 0x7b, 0xb4, 0x15, 0x38, 0x02, 0x2a, 0x6f, 0x00, 0xd5, 0xd6, 0x1a, 0x0b,
|
||||
0xf8, 0x8b, 0x39, 0xd6, 0xae, 0xf3, 0x54, 0x66, 0xcb, 0x47, 0x0a, 0x73, 0x80, 0xcb, 0x65, 0xb9, 0x89, 0x52, 0x46,
|
||||
0xae, 0x86, 0x21, 0xc2, 0x6d, 0x65, 0x04, 0x59, 0x96, 0xe4, 0xf9, 0x94, 0x10, 0x1c, 0x28, 0x6d, 0x45, 0x49, 0xa9,
|
||||
0xce, 0x70, 0xd3, 0x0b, 0x9f, 0x02, 0xea, 0x21, 0x60, 0x44, 0x7b, 0xc5, 0x04, 0xae, 0x3b, 0xa8, 0xb0, 0x98, 0x15,
|
||||
0x95, 0xe0, 0x8e, 0x69, 0x73, 0x7b, 0xcf, 0xb0, 0x12, 0xb7, 0x74, 0x67, 0x08, 0x68, 0x13, 0x61, 0xbe, 0x01, 0xfc,
|
||||
0x4d, 0x73, 0xdf, 0x28, 0x69, 0x36, 0x8a, 0x45, 0x53, 0xe5, 0xaa, 0xba, 0x69, 0xf8, 0xbe, 0xc9, 0x31, 0xbe, 0xda,
|
||||
0x1e, 0xfe, 0x02, 0xa9, 0x0e, 0xcb, 0xa2, 0x55, 0x06, 0x72, 0x3e, 0xce, 0x80, 0xa9, 0x45, 0xab, 0x2a, 0xd9, 0xa3,
|
||||
0x90, 0x95, 0x23, 0xc1, 0x05, 0x2e, 0xb7, 0x7f, 0xe0, 0x70, 0xaa, 0x81, 0x4f, 0x8e, 0xbb, 0x80, 0xf4, 0x48, 0x33,
|
||||
0x34, 0x53, 0x4a, 0x05, 0x43, 0x90, 0x34, 0x60, 0x7f, 0x2b, 0x2d, 0x42, 0x1a, 0x6b, 0x70, 0x6e, 0xbf, 0xd5, 0x83,
|
||||
0x34, 0x96, 0x6a, 0x0f, 0x71, 0x26, 0x66, 0x2d, 0x7a, 0xd2, 0x29, 0xde, 0x0b, 0x7d, 0x86, 0xc3, 0x2b, 0xd3, 0x18,
|
||||
0x14, 0xe9, 0x97, 0x63, 0x75, 0xd1, 0xd6, 0x84, 0x78, 0xd9, 0xc5, 0x08, 0xfc, 0x10, 0x11, 0xf3, 0xb5, 0x74, 0xba,
|
||||
0x3f, 0x7c, 0x78, 0xf6, 0x50, 0x4a, 0x6d, 0xda, 0xd6, 0x93, 0x3b, 0x3e, 0xc2, 0x21, 0xef, 0xb5, 0x59, 0x82, 0x73,
|
||||
0x0d, 0xc4, 0x77, 0xd3, 0x31, 0xeb, 0xfd, 0x23, 0x2d, 0xfe, 0xcb, 0xe2, 0x52, 0x2c, 0x47, 0x1c, 0xa8, 0x79, 0x26,
|
||||
0x56, 0x0c, 0xc5, 0x11, 0xcd, 0x00, 0x73, 0xb2, 0xf5, 0xcd, 0xc7, 0x7b, 0xe7, 0xfe, 0x9d, 0x9b, 0xce, 0x5d, 0x47,
|
||||
0x08, 0xe6, 0xa1, 0xd6, 0x92, 0xe7, 0x11, 0xb6, 0x68, 0x5b, 0x95, 0x86, 0x2c, 0x2d, 0x18, 0x21, 0x94, 0x74, 0x31,
|
||||
0xd1, 0xea, 0xae, 0x5d, 0x08, 0xd3, 0x87, 0xab, 0xc2, 0xc4, 0xf5, 0x12, 0x94, 0x8c, 0x72, 0xf4, 0xf8, 0x2a, 0x0d,
|
||||
0xa7, 0xb8, 0x6b, 0x20, 0x02};
|
||||
|
||||
// Backwards compatibility alias
|
||||
#define INDEX_GZ INDEX_BR
|
||||
|
||||
#endif // USE_CAPTIVE_PORTAL_GZIP
|
||||
|
||||
} // namespace esphome::captive_portal
|
||||
|
||||
@@ -43,17 +43,28 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
|
||||
scan.get_with_auth());
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_WEBSERVER
|
||||
stream->print(ESPHOME_F("],\"web_server\":true}"));
|
||||
#else
|
||||
stream->print(ESPHOME_F("]}"));
|
||||
#endif
|
||||
request->send(stream);
|
||||
}
|
||||
void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
|
||||
std::string ssid = request->arg("ssid").c_str(); // NOLINT(readability-redundant-string-cstr)
|
||||
std::string psk = request->arg("psk").c_str(); // NOLINT(readability-redundant-string-cstr)
|
||||
ESP_LOGI(TAG, "Requested WiFi Settings Change:");
|
||||
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
|
||||
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
||||
ESP_LOGI(TAG,
|
||||
"Requested WiFi Settings Change:\n"
|
||||
" SSID='%s'\n"
|
||||
" Password=" LOG_SECRET("'%s'"),
|
||||
ssid.c_str(), psk.c_str());
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266 is single-threaded, call directly
|
||||
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
|
||||
#else
|
||||
// Defer save to main loop thread to avoid NVS operations from HTTP thread
|
||||
this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); });
|
||||
#endif
|
||||
request->redirect(ESPHOME_F("/?save"));
|
||||
}
|
||||
|
||||
@@ -69,16 +80,9 @@ void CaptivePortal::start() {
|
||||
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
// Create DNS server instance for ESP-IDF
|
||||
// Create DNS server instance with domain allowlisting support
|
||||
this->dns_server_ = make_unique<DNSServer>();
|
||||
this->dns_server_->start(ip);
|
||||
#endif
|
||||
#ifdef USE_ARDUINO
|
||||
this->dns_server_ = make_unique<DNSServer>();
|
||||
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
this->dns_server_->start(53, ESPHOME_F("*"), ip);
|
||||
#endif
|
||||
|
||||
this->initialized_ = true;
|
||||
this->active_ = true;
|
||||
@@ -106,7 +110,11 @@ void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
|
||||
#else
|
||||
auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
|
||||
#endif
|
||||
#ifdef USE_CAPTIVE_PORTAL_GZIP
|
||||
response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
|
||||
#else
|
||||
response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("br"));
|
||||
#endif
|
||||
req->send(response);
|
||||
}
|
||||
|
||||
@@ -117,6 +125,10 @@ float CaptivePortal::get_setup_priority() const {
|
||||
}
|
||||
void CaptivePortal::dump_config() { ESP_LOGCONFIG(TAG, "Captive Portal:"); }
|
||||
|
||||
bool CaptivePortal::canHandle(AsyncWebServerRequest *request) const {
|
||||
return this->active_ && request->method() == HTTP_GET;
|
||||
}
|
||||
|
||||
CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace captive_portal
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_CAPTIVE_PORTAL
|
||||
#include <memory>
|
||||
#ifdef USE_ARDUINO
|
||||
#include <DNSServer.h>
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
#if defined(USE_ESP32)
|
||||
#include "dns_server_esp32_idf.h"
|
||||
#elif defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
#include "dns_server_arduino.h"
|
||||
#endif
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -23,16 +22,9 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override {
|
||||
#ifdef USE_ARDUINO
|
||||
if (this->dns_server_ != nullptr) {
|
||||
this->dns_server_->processNextRequest();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
if (this->dns_server_ != nullptr) {
|
||||
this->dns_server_->process_next_request();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
float get_setup_priority() const override;
|
||||
void start();
|
||||
@@ -47,12 +39,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
}
|
||||
}
|
||||
|
||||
bool canHandle(AsyncWebServerRequest *request) const override {
|
||||
// Handle all GET requests when captive portal is active
|
||||
// This allows us to respond with the portal page for any URL,
|
||||
// triggering OS captive portal detection
|
||||
return this->active_ && request->method() == HTTP_GET;
|
||||
}
|
||||
bool canHandle(AsyncWebServerRequest *request) const override;
|
||||
|
||||
void handle_config(AsyncWebServerRequest *request);
|
||||
|
||||
@@ -64,7 +51,7 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
web_server_base::WebServerBase *base_;
|
||||
bool initialized_{false};
|
||||
bool active_{false};
|
||||
#if defined(USE_ARDUINO) || defined(USE_ESP_IDF)
|
||||
#if defined(USE_ARDUINO) || defined(USE_ESP32)
|
||||
std::unique_ptr<DNSServer> dns_server_{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
130
esphome/components/captive_portal/dns_server_arduino.cpp
Normal file
130
esphome/components/captive_portal/dns_server_arduino.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "dns_server_arduino.h"
|
||||
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
|
||||
#include "dns_server_common.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <lwip/def.h>
|
||||
|
||||
namespace esphome::captive_portal {
|
||||
|
||||
static const char *const TAG = "captive_portal.dns";
|
||||
|
||||
void DNSServer::start(const network::IPAddress &ip) {
|
||||
this->server_ip_ = ip;
|
||||
ESP_LOGV(TAG, "Starting DNS server");
|
||||
|
||||
this->udp_ = make_unique<WiFiUDP>();
|
||||
if (!this->udp_->begin(DNS_PORT)) {
|
||||
ESP_LOGE(TAG, "Failed to start UDP on port %d", DNS_PORT);
|
||||
this->udp_ = nullptr;
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Bound to port %d", DNS_PORT);
|
||||
}
|
||||
|
||||
void DNSServer::stop() {
|
||||
if (this->udp_ != nullptr) {
|
||||
this->udp_->stop();
|
||||
this->udp_ = nullptr;
|
||||
}
|
||||
ESP_LOGV(TAG, "Stopped");
|
||||
}
|
||||
|
||||
void DNSServer::process_next_request() {
|
||||
if (this->udp_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
int packet_size = this->udp_->parsePacket();
|
||||
if (packet_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet_size > static_cast<int>(sizeof(this->buffer_))) {
|
||||
// Packet too large, skip it
|
||||
while (this->udp_->available()) {
|
||||
this->udp_->read();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int len = this->udp_->read(this->buffer_, sizeof(this->buffer_));
|
||||
if (len < static_cast<int>(sizeof(DNSHeader) + 1)) {
|
||||
ESP_LOGV(TAG, "Request too short: %d", len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse DNS header
|
||||
DNSHeader *header = (DNSHeader *) this->buffer_;
|
||||
uint16_t flags = ntohs(header->flags);
|
||||
uint16_t qd_count = ntohs(header->qd_count);
|
||||
|
||||
// Check if it's a standard query
|
||||
if ((flags & DNS_QR_FLAG) || (flags & DNS_OPCODE_MASK) || qd_count != 1) {
|
||||
ESP_LOGV(TAG, "Not a standard query: flags=0x%04X, qd_count=%d", flags, qd_count);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse domain name
|
||||
uint8_t *ptr = this->buffer_ + sizeof(DNSHeader);
|
||||
uint8_t *end = this->buffer_ + len;
|
||||
char domain[128];
|
||||
|
||||
ptr = parse_dns_domain(ptr, end, domain, sizeof(domain));
|
||||
if (ptr == nullptr) {
|
||||
return; // Invalid domain name
|
||||
}
|
||||
|
||||
// Check allowlist and send REFUSED if needed
|
||||
if (is_allowlisted_domain(domain)) {
|
||||
ESP_LOGV(TAG, "Allowlisted domain, sending REFUSED: %s", domain);
|
||||
build_dns_refused_header(header);
|
||||
this->udp_->beginPacket(this->udp_->remoteIP(), this->udp_->remotePort());
|
||||
this->udp_->write(this->buffer_, len);
|
||||
this->udp_->endPacket();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Redirecting DNS query for: %s", domain);
|
||||
|
||||
// Check we have room for the question
|
||||
if (ptr + sizeof(DNSQuestion) > end) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse DNS question
|
||||
DNSQuestion *question = (DNSQuestion *) ptr;
|
||||
uint16_t qtype = ntohs(question->type);
|
||||
uint16_t qclass = ntohs(question->dns_class);
|
||||
|
||||
if (qtype != DNS_QTYPE_A || qclass != DNS_QCLASS_IN) {
|
||||
ESP_LOGV(TAG, "Not an A query: type=0x%04X, class=0x%04X", qtype, qclass);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build DNS response
|
||||
build_dns_response_header(header);
|
||||
|
||||
// Add answer section after the question
|
||||
size_t question_end_offset = (ptr + sizeof(DNSQuestion)) - this->buffer_;
|
||||
if (build_dns_answer(this->buffer_, sizeof(this->buffer_), question_end_offset,
|
||||
static_cast<uint32_t>(this->server_ip_)) == nullptr) {
|
||||
ESP_LOGW(TAG, "Response too large");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t response_len = question_end_offset + sizeof(DNSAnswer);
|
||||
|
||||
// Send response
|
||||
this->udp_->beginPacket(this->udp_->remoteIP(), this->udp_->remotePort());
|
||||
this->udp_->write(this->buffer_, response_len);
|
||||
if (!this->udp_->endPacket()) {
|
||||
ESP_LOGV(TAG, "Send failed");
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Sent %d bytes", response_len);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::captive_portal
|
||||
|
||||
#endif // USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
|
||||
26
esphome/components/captive_portal/dns_server_arduino.h
Normal file
26
esphome/components/captive_portal/dns_server_arduino.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
|
||||
#include <memory>
|
||||
#include "dns_server_common.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
namespace esphome::captive_portal {
|
||||
|
||||
class DNSServer {
|
||||
public:
|
||||
void start(const network::IPAddress &ip);
|
||||
void stop();
|
||||
void process_next_request();
|
||||
|
||||
protected:
|
||||
std::unique_ptr<WiFiUDP> udp_{nullptr};
|
||||
network::IPAddress server_ip_;
|
||||
uint8_t buffer_[DNS_BUFFER_SIZE];
|
||||
};
|
||||
|
||||
} // namespace esphome::captive_portal
|
||||
|
||||
#endif // USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
|
||||
133
esphome/components/captive_portal/dns_server_common.h
Normal file
133
esphome/components/captive_portal/dns_server_common.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <lwip/def.h>
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::captive_portal {
|
||||
|
||||
// DNS constants
|
||||
static constexpr uint16_t DNS_PORT = 53;
|
||||
static constexpr uint16_t DNS_QR_FLAG = 1 << 15;
|
||||
static constexpr uint16_t DNS_OPCODE_MASK = 0x7800;
|
||||
static constexpr uint16_t DNS_QTYPE_A = 0x0001;
|
||||
static constexpr uint16_t DNS_QCLASS_IN = 0x0001;
|
||||
static constexpr uint16_t DNS_ANSWER_TTL = 300;
|
||||
static constexpr size_t DNS_BUFFER_SIZE = 192;
|
||||
|
||||
// DNS Header structure
|
||||
struct DNSHeader {
|
||||
uint16_t id;
|
||||
uint16_t flags;
|
||||
uint16_t qd_count;
|
||||
uint16_t an_count;
|
||||
uint16_t ns_count;
|
||||
uint16_t ar_count;
|
||||
} __attribute__((packed));
|
||||
|
||||
// DNS Question structure
|
||||
struct DNSQuestion {
|
||||
uint16_t type;
|
||||
uint16_t dns_class;
|
||||
} __attribute__((packed));
|
||||
|
||||
// DNS Answer structure
|
||||
struct DNSAnswer {
|
||||
uint16_t ptr_offset;
|
||||
uint16_t type;
|
||||
uint16_t dns_class;
|
||||
uint32_t ttl;
|
||||
uint16_t addr_len;
|
||||
uint32_t ip_addr;
|
||||
} __attribute__((packed));
|
||||
|
||||
/// Check if domain is allowlisted (web_server CDN domains)
|
||||
/// Returns true if the domain should NOT be redirected
|
||||
inline bool is_allowlisted_domain([[maybe_unused]] const char *domain) {
|
||||
#ifdef USE_WEBSERVER
|
||||
#ifdef WEBSERVER_CDN_DOMAIN_0
|
||||
if (ESPHOME_strcasecmp_P(domain, ESPHOME_F(WEBSERVER_CDN_DOMAIN_0)) == 0)
|
||||
return true;
|
||||
#endif
|
||||
#ifdef WEBSERVER_CDN_DOMAIN_1
|
||||
if (ESPHOME_strcasecmp_P(domain, ESPHOME_F(WEBSERVER_CDN_DOMAIN_1)) == 0)
|
||||
return true;
|
||||
#endif
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Parse DNS domain name from packet into string buffer
|
||||
/// Returns pointer past the domain name (after null terminator), or nullptr on error
|
||||
inline uint8_t *parse_dns_domain(uint8_t *ptr, const uint8_t *end, char *domain, size_t domain_size) {
|
||||
size_t domain_len = 0;
|
||||
|
||||
while (ptr < end && *ptr != 0) {
|
||||
uint8_t label_len = *ptr;
|
||||
if (label_len > 63) { // Check for invalid label length
|
||||
return nullptr;
|
||||
}
|
||||
// Check if we have room for this label plus the length byte
|
||||
if (ptr + label_len + 1 > end) {
|
||||
return nullptr; // Would overflow
|
||||
}
|
||||
// Add dot separator if not first label
|
||||
if (domain_len > 0 && domain_len < domain_size - 1) {
|
||||
domain[domain_len++] = '.';
|
||||
}
|
||||
// Copy label to domain string
|
||||
for (uint8_t i = 0; i < label_len && domain_len < domain_size - 1; i++) {
|
||||
domain[domain_len++] = ptr[1 + i];
|
||||
}
|
||||
ptr += label_len + 1;
|
||||
}
|
||||
domain[domain_len] = '\0';
|
||||
|
||||
// Check if we reached a proper null terminator
|
||||
if (ptr >= end || *ptr != 0) {
|
||||
return nullptr; // Name not terminated or truncated
|
||||
}
|
||||
return ptr + 1; // Skip the null terminator
|
||||
}
|
||||
|
||||
/// Build DNS response header for A record query
|
||||
/// Modifies header in place, returns true if successful
|
||||
inline void build_dns_response_header(DNSHeader *header) {
|
||||
header->flags = htons(DNS_QR_FLAG | 0x8000); // Response + Authoritative
|
||||
header->an_count = htons(1); // One answer
|
||||
}
|
||||
|
||||
/// Build DNS REFUSED response header
|
||||
/// Used when domain is allowlisted to trigger fallback to other DNS
|
||||
inline void build_dns_refused_header(DNSHeader *header) {
|
||||
header->flags = htons(DNS_QR_FLAG | 0x8005); // Response + REFUSED
|
||||
header->an_count = 0;
|
||||
header->ns_count = 0;
|
||||
header->ar_count = 0;
|
||||
}
|
||||
|
||||
/// Build DNS answer section
|
||||
/// Returns the answer pointer, or nullptr if buffer too small
|
||||
inline DNSAnswer *build_dns_answer(uint8_t *buffer, size_t buffer_size, size_t question_end_offset, uint32_t ip_addr) {
|
||||
size_t answer_offset = question_end_offset;
|
||||
|
||||
// Check if we have room for the answer
|
||||
if (answer_offset + sizeof(DNSAnswer) > buffer_size) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DNSAnswer *answer = reinterpret_cast<DNSAnswer *>(buffer + answer_offset);
|
||||
|
||||
// Pointer to name in question (offset from start of packet)
|
||||
answer->ptr_offset = htons(0xC000 | sizeof(DNSHeader));
|
||||
answer->type = htons(DNS_QTYPE_A);
|
||||
answer->dns_class = htons(DNS_QCLASS_IN);
|
||||
answer->ttl = htonl(DNS_ANSWER_TTL);
|
||||
answer->addr_len = htons(4);
|
||||
answer->ip_addr = ip_addr;
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
} // namespace esphome::captive_portal
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "dns_server_esp32_idf.h"
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "dns_server_common.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include <lwip/sockets.h>
|
||||
#include <lwip/inet.h>
|
||||
@@ -11,43 +11,12 @@ namespace esphome::captive_portal {
|
||||
|
||||
static const char *const TAG = "captive_portal.dns";
|
||||
|
||||
// DNS constants
|
||||
static constexpr uint16_t DNS_PORT = 53;
|
||||
static constexpr uint16_t DNS_QR_FLAG = 1 << 15;
|
||||
static constexpr uint16_t DNS_OPCODE_MASK = 0x7800;
|
||||
static constexpr uint16_t DNS_QTYPE_A = 0x0001;
|
||||
static constexpr uint16_t DNS_QCLASS_IN = 0x0001;
|
||||
static constexpr uint16_t DNS_ANSWER_TTL = 300;
|
||||
|
||||
// DNS Header structure
|
||||
struct DNSHeader {
|
||||
uint16_t id;
|
||||
uint16_t flags;
|
||||
uint16_t qd_count;
|
||||
uint16_t an_count;
|
||||
uint16_t ns_count;
|
||||
uint16_t ar_count;
|
||||
} __attribute__((packed));
|
||||
|
||||
// DNS Question structure
|
||||
struct DNSQuestion {
|
||||
uint16_t type;
|
||||
uint16_t dns_class;
|
||||
} __attribute__((packed));
|
||||
|
||||
// DNS Answer structure
|
||||
struct DNSAnswer {
|
||||
uint16_t ptr_offset;
|
||||
uint16_t type;
|
||||
uint16_t dns_class;
|
||||
uint32_t ttl;
|
||||
uint16_t addr_len;
|
||||
uint32_t ip_addr;
|
||||
} __attribute__((packed));
|
||||
|
||||
void DNSServer::start(const network::IPAddress &ip) {
|
||||
this->server_ip_ = ip;
|
||||
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str().c_str());
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Starting DNS server on %s", ip.str_to(ip_buf));
|
||||
#endif
|
||||
|
||||
// Create loop-monitored UDP socket
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);
|
||||
@@ -123,27 +92,28 @@ void DNSServer::process_next_request() {
|
||||
return; // Not a standard query
|
||||
}
|
||||
|
||||
// Parse domain name (we don't actually care about it - redirect everything)
|
||||
// Parse domain name
|
||||
uint8_t *ptr = this->buffer_ + sizeof(DNSHeader);
|
||||
uint8_t *end = this->buffer_ + len;
|
||||
char domain[128];
|
||||
|
||||
while (ptr < end && *ptr != 0) {
|
||||
uint8_t label_len = *ptr;
|
||||
if (label_len > 63) { // Check for invalid label length
|
||||
return;
|
||||
}
|
||||
// Check if we have room for this label plus the length byte
|
||||
if (ptr + label_len + 1 > end) {
|
||||
return; // Would overflow
|
||||
}
|
||||
ptr += label_len + 1;
|
||||
ptr = parse_dns_domain(ptr, end, domain, sizeof(domain));
|
||||
if (ptr == nullptr) {
|
||||
return; // Invalid domain name
|
||||
}
|
||||
|
||||
// Check if we reached a proper null terminator
|
||||
if (ptr >= end || *ptr != 0) {
|
||||
return; // Name not terminated or truncated
|
||||
// Check allowlist and send REFUSED if needed
|
||||
if (is_allowlisted_domain(domain)) {
|
||||
ESP_LOGV(TAG, "Allowlisted domain, sending REFUSED: %s", domain);
|
||||
build_dns_refused_header(header);
|
||||
ssize_t sent = this->socket_->sendto(this->buffer_, len, 0, (struct sockaddr *) &client_addr, client_addr_len);
|
||||
if (sent < 0) {
|
||||
ESP_LOGV(TAG, "Send REFUSED failed: %d", errno);
|
||||
}
|
||||
return;
|
||||
}
|
||||
ptr++; // Skip the null terminator
|
||||
|
||||
ESP_LOGV(TAG, "Redirecting DNS query for: %s", domain);
|
||||
|
||||
// Check we have room for the question
|
||||
if (ptr + sizeof(DNSQuestion) > end) {
|
||||
@@ -161,34 +131,18 @@ void DNSServer::process_next_request() {
|
||||
return; // Not an A query
|
||||
}
|
||||
|
||||
// Build DNS response by modifying the request in-place
|
||||
header->flags = htons(DNS_QR_FLAG | 0x8000); // Response + Authoritative
|
||||
header->an_count = htons(1); // One answer
|
||||
// Build DNS response
|
||||
build_dns_response_header(header);
|
||||
|
||||
// Add answer section after the question
|
||||
size_t question_len = (ptr + sizeof(DNSQuestion)) - this->buffer_ - sizeof(DNSHeader);
|
||||
size_t answer_offset = sizeof(DNSHeader) + question_len;
|
||||
|
||||
// Check if we have room for the answer
|
||||
if (answer_offset + sizeof(DNSAnswer) > sizeof(this->buffer_)) {
|
||||
size_t question_end_offset = (ptr + sizeof(DNSQuestion)) - this->buffer_;
|
||||
ip4_addr_t addr = this->server_ip_;
|
||||
if (build_dns_answer(this->buffer_, sizeof(this->buffer_), question_end_offset, addr.addr) == nullptr) {
|
||||
ESP_LOGW(TAG, "Response too large");
|
||||
return;
|
||||
}
|
||||
|
||||
DNSAnswer *answer = (DNSAnswer *) (this->buffer_ + answer_offset);
|
||||
|
||||
// Pointer to name in question (offset from start of packet)
|
||||
answer->ptr_offset = htons(0xC000 | sizeof(DNSHeader));
|
||||
answer->type = htons(DNS_QTYPE_A);
|
||||
answer->dns_class = htons(DNS_QCLASS_IN);
|
||||
answer->ttl = htonl(DNS_ANSWER_TTL);
|
||||
answer->addr_len = htons(4);
|
||||
|
||||
// Get the raw IP address
|
||||
ip4_addr_t addr = this->server_ip_;
|
||||
answer->ip_addr = addr.addr;
|
||||
|
||||
size_t response_len = answer_offset + sizeof(DNSAnswer);
|
||||
size_t response_len = question_end_offset + sizeof(DNSAnswer);
|
||||
|
||||
// Send response
|
||||
ssize_t sent =
|
||||
@@ -202,4 +156,4 @@ void DNSServer::process_next_request() {
|
||||
|
||||
} // namespace esphome::captive_portal
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <memory>
|
||||
#include "dns_server_common.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
@@ -15,8 +16,6 @@ class DNSServer {
|
||||
void process_next_request();
|
||||
|
||||
protected:
|
||||
static constexpr size_t DNS_BUFFER_SIZE = 192;
|
||||
|
||||
std::unique_ptr<socket::Socket> socket_{nullptr};
|
||||
network::IPAddress server_ip_;
|
||||
uint8_t buffer_[DNS_BUFFER_SIZE];
|
||||
@@ -24,4 +23,4 @@ class DNSServer {
|
||||
|
||||
} // namespace esphome::captive_portal
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -264,6 +264,7 @@ async def to_code(config):
|
||||
var.get_packet_trigger(),
|
||||
[
|
||||
(cg.std_vector.template(cg.uint8), "x"),
|
||||
(cg.float_, "freq_offset"),
|
||||
(cg.float_, "rssi"),
|
||||
(cg.uint8, "lqi"),
|
||||
],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user