mirror of
https://github.com/esphome/esphome.git
synced 2026-01-13 13:37:39 -07:00
Compare commits
605 Commits
memory_api
...
wifi_less_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
384ed0bd59 | ||
|
|
206793d4ab | ||
|
|
5e99dd14ae | ||
|
|
a6097f4a0f | ||
|
|
f243e609a5 | ||
|
|
be0bf1e5b9 | ||
|
|
a275f37135 | ||
|
|
e9f2d75aab | ||
|
|
34067f8b15 | ||
|
|
47ae027026 | ||
|
|
cfe9e6204b | ||
|
|
547aa59c18 | ||
|
|
5b9c7d1322 | ||
|
|
d0ba608ffa | ||
|
|
c91f56171b | ||
|
|
15ad89f66d | ||
|
|
8f0de69e9f | ||
|
|
37de782e3e | ||
|
|
a5574bbabe | ||
|
|
1bea4df45e | ||
|
|
57829ddd76 | ||
|
|
99722fb04f | ||
|
|
faa4cf7483 | ||
|
|
16e96dfbc0 | ||
|
|
062195be95 | ||
|
|
b2133c75f1 | ||
|
|
655a746e0d | ||
|
|
a2ea545e10 | ||
|
|
6fe5d14b3f | ||
|
|
f446860166 | ||
|
|
02e8603051 | ||
|
|
3fe4e18dc4 | ||
|
|
b221673ba7 | ||
|
|
e711cd0e41 | ||
|
|
307489cd59 | ||
|
|
e7c0d13500 | ||
|
|
bdc087148a | ||
|
|
5a2e0612a8 | ||
|
|
f1fecd22e3 | ||
|
|
0919017d49 | ||
|
|
963f594c9e | ||
|
|
4f70663658 | ||
|
|
3f20a54240 | ||
|
|
e9e301c835 | ||
|
|
8c90477387 | ||
|
|
a394fe8ad2 | ||
|
|
d642e9d85e | ||
|
|
fa05018b2c | ||
|
|
63d7ab0d40 | ||
|
|
51f95c7f9a | ||
|
|
2ac67b59e8 | ||
|
|
0767df02d9 | ||
|
|
984822388d | ||
|
|
cc49ec82bf | ||
|
|
cc18092e7a | ||
|
|
825d12553e | ||
|
|
0bd82b19b3 | ||
|
|
460792e180 | ||
|
|
5411008c49 | ||
|
|
9e13f6ac4c | ||
|
|
b8cb6fedb3 | ||
|
|
68f36ae736 | ||
|
|
6cbe3e306b | ||
|
|
cae7163741 | ||
|
|
10aee92762 | ||
|
|
736a1bb019 | ||
|
|
ca652b2065 | ||
|
|
7608b8ee84 | ||
|
|
d490594609 | ||
|
|
8715a60b7a | ||
|
|
dd99c565ca | ||
|
|
20df6a7f9a | ||
|
|
3e4631baa9 | ||
|
|
6af34f1e2a | ||
|
|
0ba15b51c6 | ||
|
|
8004602ef2 | ||
|
|
a3ec57eaf4 | ||
|
|
98460ac828 | ||
|
|
2b10408e28 | ||
|
|
33d1efe27c | ||
|
|
0e9aaf1a8b | ||
|
|
7f4fad74c2 | ||
|
|
8b72c3c0ef | ||
|
|
958a35e262 | ||
|
|
8505a4dfaf | ||
|
|
071e42d4e7 | ||
|
|
38beb613c2 | ||
|
|
058c637b59 | ||
|
|
0c566c6f00 | ||
|
|
ba73289b28 | ||
|
|
99f7e9aeb7 | ||
|
|
ebb6babb3d | ||
|
|
0922f240e0 | ||
|
|
c8fb694dcb | ||
|
|
6054685dae | ||
|
|
61ec3508ed | ||
|
|
086ec770ea | ||
|
|
b055f5b4bf | ||
|
|
726db746c8 | ||
|
|
1922455fa7 | ||
|
|
dc943d7e7a | ||
|
|
ffefa8929e | ||
|
|
89ef523990 | ||
|
|
0ec741c425 | ||
|
|
c265436b07 | ||
|
|
04a75cf200 | ||
|
|
83598d6798 | ||
|
|
fa39b6bebd | ||
|
|
1beec0ecf1 | ||
|
|
3ef4e0bc47 | ||
|
|
bda2db9184 | ||
|
|
3009da14f1 | ||
|
|
d334d0d458 | ||
|
|
25b340cbbf | ||
|
|
fa2bc21d3d | ||
|
|
83d65cff5d | ||
|
|
9205cb3d67 | ||
|
|
f9a4a8a82e | ||
|
|
2d6b9b3888 | ||
|
|
da8e23f968 | ||
|
|
4bec2dc75c | ||
|
|
6d5ab00385 | ||
|
|
3e1db740ea | ||
|
|
e13f48b348 | ||
|
|
9f2d2eed8c | ||
|
|
b6b871cb73 | ||
|
|
452246e1c5 | ||
|
|
7d5342bca5 | ||
|
|
7944fe6993 | ||
|
|
b4c92dd8cb | ||
|
|
1b31253287 | ||
|
|
6ae3d9c769 | ||
|
|
af0d4d2c2c | ||
|
|
f238f93312 | ||
|
|
bdbe72b7f1 | ||
|
|
c8b531ac06 | ||
|
|
689e1d6ca0 | ||
|
|
7b82b3b584 | ||
|
|
36c0553cf7 | ||
|
|
d3b3358527 | ||
|
|
b950d65a7b | ||
|
|
b2a6e6e078 | ||
|
|
972cf01763 | ||
|
|
029df4ff3d | ||
|
|
21ba0e5a2b | ||
|
|
8bb57f90de | ||
|
|
145d09c8dd | ||
|
|
918bc4b74f | ||
|
|
5373393714 | ||
|
|
21f6fefd98 | ||
|
|
efc30ed198 | ||
|
|
63005eaa06 | ||
|
|
08c0f65f30 | ||
|
|
cd45fe0c3a | ||
|
|
676fbf6161 | ||
|
|
fb009f47f1 | ||
|
|
84b5d9b21c | ||
|
|
b272966d19 | ||
|
|
b0c99ec1ce | ||
|
|
218c8e4d75 | ||
|
|
b191461929 | ||
|
|
6383fe4598 | ||
|
|
265ad9d264 | ||
|
|
1bdbc4cb85 | ||
|
|
1756fc31b0 | ||
|
|
03db8e4f54 | ||
|
|
74b075d3cf | ||
|
|
ce86f01cba | ||
|
|
bee5847464 | ||
|
|
4ffbdd9a3a | ||
|
|
c5ac62676c | ||
|
|
63b8fa004c | ||
|
|
66b46ea81e | ||
|
|
fc019bf3e3 | ||
|
|
da872dcbf3 | ||
|
|
c7006f8c33 | ||
|
|
52eb08f48f | ||
|
|
fe84aba2ee | ||
|
|
0d993691d4 | ||
|
|
f17a0000aa | ||
|
|
8db6ff4039 | ||
|
|
ff808618da | ||
|
|
57baf7ac7b | ||
|
|
da8c4cd654 | ||
|
|
39926909af | ||
|
|
637e032528 | ||
|
|
d89eaf5bf6 | ||
|
|
9aff4a15a3 | ||
|
|
dbf494fd98 | ||
|
|
219cf26d98 | ||
|
|
bf617c3279 | ||
|
|
c70eab931e | ||
|
|
a799ac6488 | ||
|
|
5a36cea5ec | ||
|
|
60756db06d | ||
|
|
2113858f89 | ||
|
|
14ea235939 | ||
|
|
26f1be40dc | ||
|
|
0467fdbb61 | ||
|
|
c1463a569c | ||
|
|
6dd41a14c4 | ||
|
|
9855d86616 | ||
|
|
ffe459e666 | ||
|
|
1f15023d30 | ||
|
|
e89fe9b945 | ||
|
|
f1362cd9fe | ||
|
|
bf554a58ef | ||
|
|
9d01357e8a | ||
|
|
d6cf80f95a | ||
|
|
8d14c1ceb1 | ||
|
|
9ca2b0adb7 | ||
|
|
945d5890b5 | ||
|
|
2b53976b0f | ||
|
|
589942f52c | ||
|
|
a9c294bc03 | ||
|
|
a0d1a10d17 | ||
|
|
496c09b333 | ||
|
|
1dfb808590 | ||
|
|
c0ab783ba2 | ||
|
|
6b7115f40e | ||
|
|
42610d5a6f | ||
|
|
ad17c8524c | ||
|
|
1a60ce8cc6 | ||
|
|
644e806afd | ||
|
|
de3e72af04 | ||
|
|
43046b2c55 | ||
|
|
808f1ac078 | ||
|
|
c22eff24d8 | ||
|
|
f470cf5c87 | ||
|
|
6c2d255230 | ||
|
|
832d232814 | ||
|
|
6efb167b65 | ||
|
|
4d99632a61 | ||
|
|
6cb66559bc | ||
|
|
6f3bfc2060 | ||
|
|
e812d8683a | ||
|
|
cc9f42cc9a | ||
|
|
9a54cc12ba | ||
|
|
40eb898814 | ||
|
|
64269334ce | ||
|
|
121375ff39 | ||
|
|
48cdf9e036 | ||
|
|
bed779b1c9 | ||
|
|
a31bef5390 | ||
|
|
2d792ddf40 | ||
|
|
7365885464 | ||
|
|
df5193ff73 | ||
|
|
2d6103f0d0 | ||
|
|
4036671583 | ||
|
|
4a1db67566 | ||
|
|
0814419d61 | ||
|
|
c799ce05f7 | ||
|
|
8088f09902 | ||
|
|
0cff5326bc | ||
|
|
60d66365ad | ||
|
|
f727edab58 | ||
|
|
a0647cbe71 | ||
|
|
0543d65969 | ||
|
|
4bfe09768c | ||
|
|
4d0a54d9f0 | ||
|
|
4739e9cabe | ||
|
|
4c4c6fb257 | ||
|
|
3e313014e1 | ||
|
|
68b7fbdecf | ||
|
|
e7ea17fcba | ||
|
|
be6c1e4ec0 | ||
|
|
730bf206de | ||
|
|
4eea8082cd | ||
|
|
78899831cf | ||
|
|
6cac302c01 | ||
|
|
a76461cf5f | ||
|
|
6a9a6554e1 | ||
|
|
6904f0f3c4 | ||
|
|
7eca8905ea | ||
|
|
01224f25f7 | ||
|
|
cd6240541b | ||
|
|
94fcd18306 | ||
|
|
da472f3f79 | ||
|
|
c9fccdff25 | ||
|
|
ada6c42f3f | ||
|
|
988b888c63 | ||
|
|
940afdbb12 | ||
|
|
81e91c2a8f | ||
|
|
ebc3d28ade | ||
|
|
3fff2f2f0a | ||
|
|
04eb64f361 | ||
|
|
4464e464b6 | ||
|
|
25cebedcfc | ||
|
|
20a12ca02a | ||
|
|
b2a43a3a69 | ||
|
|
98ed679b19 | ||
|
|
097617cb4e | ||
|
|
59b38d79b4 | ||
|
|
26c16f4ca2 | ||
|
|
940e619481 | ||
|
|
043204baa6 | ||
|
|
92157c89bc | ||
|
|
25f83384a4 | ||
|
|
eaca81c3ab | ||
|
|
93e38f2608 | ||
|
|
3a888326d8 | ||
|
|
f0d0ea60a7 | ||
|
|
7ca11764ab | ||
|
|
3e38a5e630 | ||
|
|
636be92c97 | ||
|
|
195b1c6323 | ||
|
|
7e08092012 | ||
|
|
86119c3439 | ||
|
|
cf404c34d0 | ||
|
|
f962497db1 | ||
|
|
ce1c6721b1 | ||
|
|
a7f82f5201 | ||
|
|
38afc5149a | ||
|
|
7ae3a11d6b | ||
|
|
7a5a768c04 | ||
|
|
37e2a114db | ||
|
|
b5950d41b5 | ||
|
|
1c50c2b672 | ||
|
|
3a69cb9c13 | ||
|
|
5547f9f5d6 | ||
|
|
12d8e2ada2 | ||
|
|
455091a03f | ||
|
|
23ee8bdcaf | ||
|
|
41fd1762e9 | ||
|
|
cd93468225 | ||
|
|
2cf6ed2af7 | ||
|
|
b47b7d43fd | ||
|
|
663a4304e0 | ||
|
|
bef017fd3e | ||
|
|
6707ac6a0f | ||
|
|
ec55dcbe41 | ||
|
|
655f493eaa | ||
|
|
50f0f7595b | ||
|
|
ca47bad90a | ||
|
|
4f821a6d76 | ||
|
|
426305836d | ||
|
|
c6b33586c1 | ||
|
|
1b5af7d21d | ||
|
|
a97f637cb9 | ||
|
|
0185541000 | ||
|
|
9de7df7b5b | ||
|
|
ce18b4c3ca | ||
|
|
60b266e73d | ||
|
|
4c8a2fe3d4 | ||
|
|
190ebecd7b | ||
|
|
71b6fc4baa | ||
|
|
df2936f686 | ||
|
|
1fb89e5223 | ||
|
|
8c185254ef | ||
|
|
14ecb906a1 | ||
|
|
2b337aa306 | ||
|
|
4ddaff4027 | ||
|
|
91c504061b | ||
|
|
dc8f7abce2 | ||
|
|
3d673ac55e | ||
|
|
b02696edc0 | ||
|
|
0688abdbd8 | ||
|
|
f9720026d0 | ||
|
|
745f5b8dd2 | ||
|
|
d7b04a3d18 | ||
|
|
01d8775005 | ||
|
|
0e71fa97a7 | ||
|
|
7701371bb4 | ||
|
|
42e061c9ae | ||
|
|
94763ebdab | ||
|
|
f32bb618ac | ||
|
|
0707f383a6 | ||
|
|
e91c6a79ea | ||
|
|
63fc8b4e5a | ||
|
|
ab73ed76b8 | ||
|
|
bf6a03d1cf | ||
|
|
9928ab09cf | ||
|
|
eb2392b33a | ||
|
|
7e36112687 | ||
|
|
56c1691d72 | ||
|
|
a065990ab9 | ||
|
|
084f517a20 | ||
|
|
77d914e67f | ||
|
|
1122ec354f | ||
|
|
431183eebc | ||
|
|
08beaf8750 | ||
|
|
f2f30337a0 | ||
|
|
18814f12dc | ||
|
|
9cd888cef6 | ||
|
|
9727c7135c | ||
|
|
93621d85b0 | ||
|
|
046ea922e8 | ||
|
|
71f2331bc8 | ||
|
|
fab4efb469 | ||
|
|
efc5672567 | ||
|
|
0ea5f2fd81 | ||
|
|
8358ef0096 | ||
|
|
f8c9cf8fd9 | ||
|
|
fa3d998c3d | ||
|
|
8429684fce | ||
|
|
a30052e7c0 | ||
|
|
175250deb0 | ||
|
|
12734ba258 | ||
|
|
da67c47a76 | ||
|
|
38167c268f | ||
|
|
7298db0a7e | ||
|
|
6eb8095480 | ||
|
|
5e630e9255 | ||
|
|
864aaeec01 | ||
|
|
3679a81220 | ||
|
|
9c88e44300 | ||
|
|
4d6a93f92d | ||
|
|
7216120bfd | ||
|
|
1897551b28 | ||
|
|
dfae9e5750 | ||
|
|
ead60bc5c4 | ||
|
|
87f88b8a9a | ||
|
|
ba79a8e6c8 | ||
|
|
e8a3a8380d | ||
|
|
305a58cb84 | ||
|
|
f231fc856b | ||
|
|
7fe8e53f82 | ||
|
|
8cf0ee38a3 | ||
|
|
69fa5020d2 | ||
|
|
f5592595bc | ||
|
|
4a58ab6310 | ||
|
|
a5ff374540 | ||
|
|
ffbbf37fc2 | ||
|
|
4c926cca60 | ||
|
|
57634b612a | ||
|
|
8dff7ee746 | ||
|
|
803bb742c9 | ||
|
|
682acf81b2 | ||
|
|
d2b5398fad | ||
|
|
839139df36 | ||
|
|
24d7e9dd23 | ||
|
|
1214bb6bad | ||
|
|
68edb5c9a0 | ||
|
|
d911ae94fe | ||
|
|
487b66d92a | ||
|
|
fc4869e2f9 | ||
|
|
109e6c9719 | ||
|
|
3c42e534b3 | ||
|
|
b956c7798b | ||
|
|
f8c0cd9ff6 | ||
|
|
e27c693051 | ||
|
|
f6f1961e0e | ||
|
|
ab0ca3006a | ||
|
|
af1e0e6489 | ||
|
|
260ffba2a5 | ||
|
|
dab6b54e97 | ||
|
|
fd32139d89 | ||
|
|
245e7682c1 | ||
|
|
2e899dd010 | ||
|
|
93bf1c8452 | ||
|
|
0a63c50e1e | ||
|
|
61cbd07e1d | ||
|
|
87a125f303 | ||
|
|
e57e1f5094 | ||
|
|
09e9b58eb6 | ||
|
|
c451fbd697 | ||
|
|
853372a814 | ||
|
|
db91ac9c75 | ||
|
|
d83fd263b0 | ||
|
|
415a1dd75a | ||
|
|
ed4f90db8f | ||
|
|
9dc06f04b1 | ||
|
|
fe315a4cf8 | ||
|
|
b9d59f5a00 | ||
|
|
efc53ddd77 | ||
|
|
0bc81633bf | ||
|
|
cf20e0d772 | ||
|
|
bc5444cbe1 | ||
|
|
450962850a | ||
|
|
2dbaedbda2 | ||
|
|
5eab42441e | ||
|
|
0f22b23d9a | ||
|
|
b27229d9b9 | ||
|
|
6a5dc6e7f5 | ||
|
|
ee5a3088b9 | ||
|
|
6c166c904c | ||
|
|
995cf8be9c | ||
|
|
6b088caf5d | ||
|
|
3e6a65e7dc | ||
|
|
3a101d8886 | ||
|
|
fa0f07bfe9 | ||
|
|
fffa16e4d8 | ||
|
|
734710d22a | ||
|
|
3a1be6822e | ||
|
|
c85b1b8609 | ||
|
|
2e9ddd967c | ||
|
|
078afe9656 | ||
|
|
46574fcbec | ||
|
|
359f45400f | ||
|
|
4da95ccd7e | ||
|
|
c69d58273a | ||
|
|
ffce80f96c | ||
|
|
fa5b14fad4 | ||
|
|
45f413de0e | ||
|
|
24e490ef26 | ||
|
|
d9296a907d | ||
|
|
0a979cf26a | ||
|
|
712da5c2ae | ||
|
|
09573b5e5f | ||
|
|
11c9e974ac | ||
|
|
3786c84bbe | ||
|
|
c8b48df8f2 | ||
|
|
1de743d85e | ||
|
|
f22396a097 | ||
|
|
8cdee86334 | ||
|
|
7801420eca | ||
|
|
cee532a1e3 | ||
|
|
8524b894d6 | ||
|
|
4928862622 | ||
|
|
6939b67e47 | ||
|
|
0b32add874 | ||
|
|
616dae5bf9 | ||
|
|
3a5e708c13 | ||
|
|
96e418a8ca | ||
|
|
780a407b10 | ||
|
|
bd539fa34f | ||
|
|
8ce2cc564f | ||
|
|
2696297428 | ||
|
|
7eff3217aa | ||
|
|
cfc0d8bdfc | ||
|
|
af04eaaba0 | ||
|
|
786d7266f5 | ||
|
|
ede64a9f47 | ||
|
|
c4d9ed7b70 | ||
|
|
f20f3e0525 | ||
|
|
2bfa1aacd8 | ||
|
|
1b551b0897 | ||
|
|
7e1bd289b3 | ||
|
|
cf373edd81 | ||
|
|
0d7fbb79b3 | ||
|
|
c87771184e | ||
|
|
1185abadc1 | ||
|
|
6f6c65509d | ||
|
|
f50ffb2b92 | ||
|
|
4892bfb6e4 | ||
|
|
586e82bfa5 | ||
|
|
512a7df007 | ||
|
|
0e60aefdec | ||
|
|
1ebfd5b4eb | ||
|
|
4bde4dbdc8 | ||
|
|
841d9664d3 | ||
|
|
16107ad788 | ||
|
|
8299656375 | ||
|
|
184ac0c1e7 | ||
|
|
6198618044 | ||
|
|
ba0f559856 | ||
|
|
0539c5d4d2 | ||
|
|
bb35ed5f53 | ||
|
|
de500450d9 | ||
|
|
b4a54f2df1 | ||
|
|
cf8708b888 | ||
|
|
15494c0d28 | ||
|
|
4bf810fcd1 | ||
|
|
959a2e8ddd | ||
|
|
4b937b5228 | ||
|
|
0c7c1d3c57 | ||
|
|
d31be6ed9d | ||
|
|
dce5face4e | ||
|
|
6d91f1cd77 | ||
|
|
67937aeda4 | ||
|
|
1543f56f70 | ||
|
|
e0ce66e011 | ||
|
|
15d2d3ff96 | ||
|
|
d016302e36 | ||
|
|
fe798dff81 | ||
|
|
75b8279361 | ||
|
|
17db6bee3c | ||
|
|
e7c8892d6d | ||
|
|
12e0d6bdcc | ||
|
|
d8c52297ab | ||
|
|
6fce0a6104 | ||
|
|
eda0a391ca | ||
|
|
94fefb1405 | ||
|
|
da96ffb923 | ||
|
|
d7451257a5 | ||
|
|
b10a87c1b0 | ||
|
|
f2505ce453 | ||
|
|
32797fbe00 | ||
|
|
ec7143d835 | ||
|
|
d77f9c96b9 | ||
|
|
4329794924 | ||
|
|
99b0b974ad | ||
|
|
9dafafa07c | ||
|
|
200cd8cace | ||
|
|
b8c00e6452 | ||
|
|
1d081fd510 | ||
|
|
a86095d865 | ||
|
|
e728e8ed0c | ||
|
|
80fda97c60 | ||
|
|
7d3afe5de0 | ||
|
|
b5703523f9 | ||
|
|
07d784b0bf | ||
|
|
25805da008 | ||
|
|
ccebe613e2 | ||
|
|
295b317809 | ||
|
|
58fddeb74f | ||
|
|
54ed6154eb | ||
|
|
0b1ea8f2ca | ||
|
|
478f12f75e | ||
|
|
cfdb5a82e2 | ||
|
|
edc320fef8 | ||
|
|
2d3ccab0b3 | ||
|
|
fdd560b165 | ||
|
|
6b810b340a | ||
|
|
3cd14fa39d |
@@ -1 +1 @@
|
||||
766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07
|
||||
4268ab0b5150f79ab1c317e8f3834c8bb0b4c8122da4f6b1fd67c49d0f2098c9
|
||||
|
||||
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Set TAG
|
||||
run: |
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
|
||||
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -99,7 +99,7 @@ jobs:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -91,6 +91,10 @@ venv-*/
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# nix
|
||||
/default.nix
|
||||
/shell.nix
|
||||
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.pio
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.9
|
||||
rev: v0.14.10
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -42,6 +42,7 @@ esphome/components/animation/* @syndlex
|
||||
esphome/components/anova/* @buxtronix
|
||||
esphome/components/apds9306/* @aodrenah
|
||||
esphome/components/api/* @esphome/core
|
||||
esphome/components/aqi/* @freekode @jasstrong @ximex
|
||||
esphome/components/as5600/* @ammmze
|
||||
esphome/components/as5600/sensor/* @ammmze
|
||||
esphome/components/as7341/* @mrgnr
|
||||
@@ -215,6 +216,7 @@ esphome/components/hlk_fm22x/* @OnFreund
|
||||
esphome/components/hlw8032/* @rici4kubicek
|
||||
esphome/components/hm3301/* @freekode
|
||||
esphome/components/hmac_md5/* @dwmw2
|
||||
esphome/components/hmac_sha256/* @dwmw2
|
||||
esphome/components/homeassistant/* @esphome/core @OttoWinter
|
||||
esphome/components/homeassistant/number/* @landonr
|
||||
esphome/components/homeassistant/switch/* @Links2004
|
||||
@@ -517,6 +519,7 @@ esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/tuya/text_sensor/* @dentra
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/uart/button/* @ssieb
|
||||
esphome/components/uart/event/* @eoasmxd
|
||||
esphome/components/uart/packet_transport/* @clydebarrow
|
||||
esphome/components/udp/* @clydebarrow
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
@@ -535,6 +538,7 @@ esphome/components/version/* @esphome/core
|
||||
esphome/components/voice_assistant/* @jesserockz @kahrendt
|
||||
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||
esphome/components/watchdog/* @oarcher
|
||||
esphome/components/water_heater/* @dhoeben
|
||||
esphome/components/waveshare_epaper/* @clydebarrow
|
||||
esphome/components/web_server/ota/* @esphome/core
|
||||
esphome/components/web_server_base/* @esphome/core
|
||||
|
||||
@@ -518,10 +518,49 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
rc = platformio_api.run_compile(config, CORE.verbose)
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
# Check if firmware was rebuilt and emit build_info + create manifest
|
||||
_check_and_emit_build_info()
|
||||
|
||||
idedata = platformio_api.get_idedata(config)
|
||||
return 0 if idedata is not None else 1
|
||||
|
||||
|
||||
def _check_and_emit_build_info() -> None:
|
||||
"""Check if firmware was rebuilt and emit build_info."""
|
||||
import json
|
||||
|
||||
firmware_path = CORE.firmware_bin
|
||||
build_info_json_path = CORE.relative_build_path("build_info.json")
|
||||
|
||||
# Check if both files exist
|
||||
if not firmware_path.exists() or not build_info_json_path.exists():
|
||||
return
|
||||
|
||||
# Check if firmware is newer than build_info (indicating a relink occurred)
|
||||
if firmware_path.stat().st_mtime <= build_info_json_path.stat().st_mtime:
|
||||
return
|
||||
|
||||
# Read build_info from JSON
|
||||
try:
|
||||
with open(build_info_json_path, encoding="utf-8") as f:
|
||||
build_info = json.load(f)
|
||||
except (OSError, json.JSONDecodeError) as e:
|
||||
_LOGGER.debug("Failed to read build_info: %s", e)
|
||||
return
|
||||
|
||||
config_hash = build_info.get("config_hash")
|
||||
build_time_str = build_info.get("build_time_str")
|
||||
|
||||
if config_hash is None or build_time_str is None:
|
||||
return
|
||||
|
||||
# Emit build_info with human-readable time
|
||||
_LOGGER.info(
|
||||
"Build Info: config_hash=0x%08x build_time_str=%s", config_hash, build_time_str
|
||||
)
|
||||
|
||||
|
||||
def upload_using_esptool(
|
||||
config: ConfigType, port: str, file: str, speed: int
|
||||
) -> str | int:
|
||||
|
||||
@@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7880),
|
||||
cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All(
|
||||
cv.frequency, cv.Range(min=45.0, max=66.0)
|
||||
cv.frequency, cv.float_range(min=45.0, max=66.0)
|
||||
),
|
||||
cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema,
|
||||
|
||||
@@ -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
|
||||
@@ -132,14 +129,13 @@ class AlarmControlPanel : public EntityBase {
|
||||
// the call control function
|
||||
virtual void control(const AlarmControlPanelCall &call) = 0;
|
||||
// state callback - triggers check get_state() for specific state
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
// clear callback - fires when leaving TRIGGERED state
|
||||
CallbackManager<void()> cleared_callback_{};
|
||||
LazyCallbackManager<void()> cleared_callback_{};
|
||||
// chime callback
|
||||
CallbackManager<void()> chime_callback_{};
|
||||
LazyCallbackManager<void()> chime_callback_{};
|
||||
// ready callback
|
||||
CallbackManager<void()> ready_callback_{};
|
||||
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
|
||||
|
||||
@@ -102,7 +102,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;
|
||||
}
|
||||
@@ -139,7 +139,7 @@ message AuthenticationRequest {
|
||||
option (ifdef) = "USE_API_PASSWORD";
|
||||
|
||||
// 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.
|
||||
@@ -579,7 +579,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"];
|
||||
}
|
||||
|
||||
@@ -796,7 +796,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 ====================
|
||||
@@ -841,7 +841,7 @@ message GetTimeResponse {
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 epoch_seconds = 1;
|
||||
string timezone = 2 [(pointer_to_buffer) = true];
|
||||
string timezone = 2;
|
||||
}
|
||||
|
||||
// ==================== USER-DEFINES SERVICES ====================
|
||||
@@ -1101,6 +1101,85 @@ message ClimateCommandRequest {
|
||||
uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
// ==================== WATER_HEATER ====================
|
||||
enum WaterHeaterMode {
|
||||
WATER_HEATER_MODE_OFF = 0;
|
||||
WATER_HEATER_MODE_ECO = 1;
|
||||
WATER_HEATER_MODE_ELECTRIC = 2;
|
||||
WATER_HEATER_MODE_PERFORMANCE = 3;
|
||||
WATER_HEATER_MODE_HIGH_DEMAND = 4;
|
||||
WATER_HEATER_MODE_HEAT_PUMP = 5;
|
||||
WATER_HEATER_MODE_GAS = 6;
|
||||
}
|
||||
|
||||
message ListEntitiesWaterHeaterResponse {
|
||||
option (id) = 132;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 5;
|
||||
EntityCategory entity_category = 6;
|
||||
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
|
||||
float min_temperature = 8;
|
||||
float max_temperature = 9;
|
||||
float target_temperature_step = 10;
|
||||
repeated WaterHeaterMode supported_modes = 11 [(container_pointer_no_template) = "water_heater::WaterHeaterModeMask"];
|
||||
// Bitmask of WaterHeaterFeature flags
|
||||
uint32 supported_features = 12;
|
||||
}
|
||||
|
||||
message WaterHeaterStateResponse {
|
||||
option (id) = 133;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
float current_temperature = 2;
|
||||
float target_temperature = 3;
|
||||
WaterHeaterMode mode = 4;
|
||||
uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"];
|
||||
// Bitmask of current state flags (bit 0 = away, bit 1 = on)
|
||||
uint32 state = 6;
|
||||
float target_temperature_low = 7;
|
||||
float target_temperature_high = 8;
|
||||
}
|
||||
|
||||
// Bitmask for WaterHeaterCommandRequest.has_fields
|
||||
enum WaterHeaterCommandHasField {
|
||||
WATER_HEATER_COMMAND_HAS_NONE = 0;
|
||||
WATER_HEATER_COMMAND_HAS_MODE = 1;
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2;
|
||||
WATER_HEATER_COMMAND_HAS_STATE = 4;
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8;
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16;
|
||||
}
|
||||
|
||||
message WaterHeaterCommandRequest {
|
||||
option (id) = 134;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
// Bitmask of which fields are set (see WaterHeaterCommandHasField)
|
||||
uint32 has_fields = 2;
|
||||
WaterHeaterMode mode = 3;
|
||||
float target_temperature = 4;
|
||||
uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"];
|
||||
// State flags bitmask (bit 0 = away, bit 1 = on)
|
||||
uint32 state = 6;
|
||||
float target_temperature_low = 7;
|
||||
float target_temperature_high = 8;
|
||||
}
|
||||
|
||||
// ==================== NUMBER ====================
|
||||
enum NumberMode {
|
||||
NUMBER_MODE_AUTO = 0;
|
||||
@@ -1195,7 +1274,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"];
|
||||
}
|
||||
|
||||
@@ -1213,7 +1292,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;
|
||||
@@ -1613,7 +1692,7 @@ message BluetoothGATTWriteRequest {
|
||||
uint32 handle = 2;
|
||||
bool response = 3;
|
||||
|
||||
bytes data = 4 [(pointer_to_buffer) = true];
|
||||
bytes data = 4;
|
||||
}
|
||||
|
||||
message BluetoothGATTReadDescriptorRequest {
|
||||
@@ -1633,7 +1712,7 @@ message BluetoothGATTWriteDescriptorRequest {
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3 [(pointer_to_buffer) = true];
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTNotifyRequest {
|
||||
@@ -1858,7 +1937,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cinttypes>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
@@ -42,6 +43,9 @@
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
#include "esphome/components/zwave_proxy/zwave_proxy.h"
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
#include "esphome/components/water_heater/water_heater.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -93,8 +97,7 @@ static const int CAMERA_STOP_STREAM = 5000;
|
||||
return;
|
||||
#endif // USE_DEVICES
|
||||
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) : parent_(parent) {
|
||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||
auto &noise_ctx = parent->get_noise_ctx();
|
||||
if (noise_ctx.has_psk()) {
|
||||
@@ -128,11 +131,14 @@ 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
|
||||
char peername[socket::PEERNAME_MAX_LEN];
|
||||
this->helper_->getpeername_to(peername);
|
||||
this->client_info_.name = peername;
|
||||
}
|
||||
|
||||
APIConnection::~APIConnection() {
|
||||
this->destroy_active_iterator_();
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
|
||||
@@ -145,6 +151,32 @@ APIConnection::~APIConnection() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIConnection::destroy_active_iterator_() {
|
||||
switch (this->active_iterator_) {
|
||||
case ActiveIterator::LIST_ENTITIES:
|
||||
this->iterator_storage_.list_entities.~ListEntitiesIterator();
|
||||
break;
|
||||
case ActiveIterator::INITIAL_STATE:
|
||||
this->iterator_storage_.initial_state.~InitialStateIterator();
|
||||
break;
|
||||
case ActiveIterator::NONE:
|
||||
break;
|
||||
}
|
||||
this->active_iterator_ = ActiveIterator::NONE;
|
||||
}
|
||||
|
||||
void APIConnection::begin_iterator_(ActiveIterator type) {
|
||||
this->destroy_active_iterator_();
|
||||
this->active_iterator_ = type;
|
||||
if (type == ActiveIterator::LIST_ENTITIES) {
|
||||
new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this);
|
||||
this->iterator_storage_.list_entities.begin();
|
||||
} else {
|
||||
new (&this->iterator_storage_.initial_state) InitialStateIterator(this);
|
||||
this->iterator_storage_.initial_state.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->flags_.next_close) {
|
||||
// requested a disconnect
|
||||
@@ -187,31 +219,42 @@ void APIConnection::loop() {
|
||||
this->process_batch_();
|
||||
}
|
||||
|
||||
if (!this->list_entities_iterator_.completed()) {
|
||||
this->process_iterator_batch_(this->list_entities_iterator_);
|
||||
} else if (!this->initial_state_iterator_.completed()) {
|
||||
this->process_iterator_batch_(this->initial_state_iterator_);
|
||||
|
||||
// If we've completed initial states, process any remaining and clear the flag
|
||||
if (this->initial_state_iterator_.completed()) {
|
||||
// Process any remaining batched messages immediately
|
||||
if (!this->deferred_batch_.empty()) {
|
||||
this->process_batch_();
|
||||
switch (this->active_iterator_) {
|
||||
case ActiveIterator::LIST_ENTITIES:
|
||||
if (this->iterator_storage_.list_entities.completed()) {
|
||||
this->destroy_active_iterator_();
|
||||
if (this->flags_.state_subscription) {
|
||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||
}
|
||||
} else {
|
||||
this->process_iterator_batch_(this->iterator_storage_.list_entities);
|
||||
}
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
// Release excess memory from buffers that grew during initial sync
|
||||
this->deferred_batch_.release_buffer();
|
||||
this->helper_->release_buffers();
|
||||
}
|
||||
break;
|
||||
case ActiveIterator::INITIAL_STATE:
|
||||
if (this->iterator_storage_.initial_state.completed()) {
|
||||
this->destroy_active_iterator_();
|
||||
// Process any remaining batched messages immediately
|
||||
if (!this->deferred_batch_.empty()) {
|
||||
this->process_batch_();
|
||||
}
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
// Release excess memory from buffers that grew during initial sync
|
||||
this->deferred_batch_.release_buffer();
|
||||
this->helper_->release_buffers();
|
||||
} else {
|
||||
this->process_iterator_batch_(this->iterator_storage_.initial_state);
|
||||
}
|
||||
break;
|
||||
case ActiveIterator::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->flags_.sent_ping) {
|
||||
// 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
|
||||
@@ -228,40 +271,24 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
|
||||
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
||||
bool done = this->image_reader_->available() == to_send;
|
||||
|
||||
CameraImageResponse msg;
|
||||
msg.key = camera::Camera::instance()->get_object_id_hash();
|
||||
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
|
||||
msg.done = done;
|
||||
#ifdef USE_DEVICES
|
||||
msg.device_id = camera::Camera::instance()->get_device_id();
|
||||
#endif
|
||||
|
||||
if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
|
||||
this->image_reader_->consume_data(to_send);
|
||||
if (done) {
|
||||
this->image_reader_->return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
if (state_subs_at_ >= 0) {
|
||||
this->process_state_subscriptions_();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
// Process camera last - state updates are higher priority
|
||||
// (missing a frame is fine, missing a state update is not)
|
||||
this->try_send_camera_image_();
|
||||
#endif
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -447,7 +474,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(msg.preset_mode);
|
||||
call.set_preset_mode(msg.preset_mode.c_str(), msg.preset_mode.size());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
@@ -533,7 +560,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
|
||||
@@ -712,11 +739,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(msg.custom_fan_mode);
|
||||
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(msg.custom_preset);
|
||||
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();
|
||||
@@ -905,7 +932,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
|
||||
@@ -1057,6 +1084,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void APIConnection::try_send_camera_image_() {
|
||||
if (!this->image_reader_)
|
||||
return;
|
||||
|
||||
// Send as many chunks as possible without blocking
|
||||
while (this->image_reader_->available()) {
|
||||
if (!this->helper_->can_write_without_blocking())
|
||||
return;
|
||||
|
||||
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
||||
bool done = this->image_reader_->available() == to_send;
|
||||
|
||||
CameraImageResponse msg;
|
||||
msg.key = camera::Camera::instance()->get_object_id_hash();
|
||||
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
|
||||
msg.done = done;
|
||||
#ifdef USE_DEVICES
|
||||
msg.device_id = camera::Camera::instance()->get_device_id();
|
||||
#endif
|
||||
|
||||
if (!this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
|
||||
return; // Send failed, try again later
|
||||
}
|
||||
this->image_reader_->consume_data(to_send);
|
||||
if (done) {
|
||||
this->image_reader_->return_image();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
|
||||
if (!this->flags_.state_subscription)
|
||||
return;
|
||||
@@ -1064,8 +1121,11 @@ void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image)
|
||||
return;
|
||||
if (this->image_reader_->available())
|
||||
return;
|
||||
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE))
|
||||
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) {
|
||||
this->image_reader_->set_image(std::move(image));
|
||||
// Try to send immediately to reduce latency
|
||||
this->try_send_camera_image_();
|
||||
}
|
||||
}
|
||||
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -1094,9 +1154,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
|
||||
}
|
||||
@@ -1306,6 +1365,57 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
|
||||
return this->send_message_smart_(water_heater, &APIConnection::try_send_water_heater_state,
|
||||
WaterHeaterStateResponse::MESSAGE_TYPE, WaterHeaterStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
|
||||
WaterHeaterStateResponse resp;
|
||||
resp.mode = static_cast<enums::WaterHeaterMode>(wh->get_mode());
|
||||
resp.current_temperature = wh->get_current_temperature();
|
||||
resp.target_temperature = wh->get_target_temperature();
|
||||
resp.target_temperature_low = wh->get_target_temperature_low();
|
||||
resp.target_temperature_high = wh->get_target_temperature_high();
|
||||
resp.state = wh->get_state();
|
||||
resp.key = wh->get_object_id_hash();
|
||||
|
||||
return encode_message_to_buffer(resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
|
||||
ListEntitiesWaterHeaterResponse msg;
|
||||
auto traits = wh->get_traits();
|
||||
msg.min_temperature = traits.get_min_temperature();
|
||||
msg.max_temperature = traits.get_max_temperature();
|
||||
msg.target_temperature_step = traits.get_target_temperature_step();
|
||||
msg.supported_modes = &traits.get_supported_modes();
|
||||
msg.supported_features = traits.get_feature_flags();
|
||||
return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
|
||||
void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
|
||||
ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
|
||||
call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE)
|
||||
call.set_target_temperature(msg.target_temperature);
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW)
|
||||
call.set_target_temperature_low(msg.target_temperature_low);
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH)
|
||||
call.set_target_temperature_high(msg.target_temperature_high);
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE) {
|
||||
call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0);
|
||||
call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0);
|
||||
}
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, const char *event_type) {
|
||||
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||
@@ -1395,9 +1505,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);
|
||||
// Trigger expects std::string, get fresh peername from socket
|
||||
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->helper_->getpeername());
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
@@ -1412,12 +1523,13 @@ 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();
|
||||
this->client_info_.name.assign(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;
|
||||
char peername[socket::PEERNAME_MAX_LEN];
|
||||
this->helper_->getpeername_to(peername);
|
||||
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_);
|
||||
peername, this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
@@ -1440,7 +1552,7 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
||||
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);
|
||||
resp.invalid_password = !this->parent_->check_password(msg.password.byte(), msg.password.size());
|
||||
if (!resp.invalid_password) {
|
||||
this->complete_authentication_();
|
||||
}
|
||||
@@ -1472,7 +1584,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
|
||||
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||
|
||||
resp.set_compilation_time(App.get_compilation_time_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));
|
||||
|
||||
// Manufacturer string - define once, handle ESP8266 PROGMEM separately
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
@@ -1579,15 +1694,30 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
// Compare entity_id and attribute with message fields
|
||||
bool entity_match = (strcmp(it.entity_id, msg.entity_id.c_str()) == 0);
|
||||
bool attribute_match = (it.attribute != nullptr && strcmp(it.attribute, msg.attribute.c_str()) == 0) ||
|
||||
(it.attribute == nullptr && msg.attribute.empty());
|
||||
// Skip if entity_id is empty (invalid message)
|
||||
if (msg.entity_id.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity_match && attribute_match) {
|
||||
it.callback(msg.state);
|
||||
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.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.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
|
||||
std::string state(!msg.state.empty() ? msg.state.c_str() : "", msg.state.size());
|
||||
it.callback(state);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1622,20 +1752,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.set_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.set_error_message(error_message);
|
||||
resp.response_data = response_data;
|
||||
resp.response_data_len = response_data_len;
|
||||
this->send_message(resp, ExecuteServiceResponse::MESSAGE_TYPE);
|
||||
@@ -1663,13 +1793,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
|
||||
resp.success = false;
|
||||
|
||||
psk_t psk{};
|
||||
if (msg.key.empty()) {
|
||||
if (msg.key_len == 0) {
|
||||
if (this->parent_->clear_noise_psk(true)) {
|
||||
resp.success = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to clear encryption key");
|
||||
}
|
||||
} else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) {
|
||||
} else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
|
||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||
@@ -1721,12 +1851,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
#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());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no authentication"));
|
||||
}
|
||||
#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();
|
||||
@@ -1974,9 +2104,18 @@ void APIConnection::process_state_subscriptions_() {
|
||||
}
|
||||
#endif // USE_API_HOMEASSISTANT_STATES
|
||||
|
||||
void APIConnection::log_client_(int level, const LogString *message) {
|
||||
char peername[socket::PEERNAME_MAX_LEN];
|
||||
this->helper_->getpeername_to(peername);
|
||||
esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->client_info_.name.c_str(), 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(),
|
||||
LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
|
||||
char peername[socket::PEERNAME_MAX_LEN];
|
||||
this->helper_->getpeername_to(peername);
|
||||
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), peername, LOG_STR_ARG(message),
|
||||
LOG_STR_ARG(api_error_to_logstr(err)), errno);
|
||||
}
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -17,8 +17,9 @@ namespace esphome::api {
|
||||
|
||||
// Client information structure
|
||||
struct ClientInfo {
|
||||
std::string name; // Client name from Hello message
|
||||
std::string peername; // IP:port from socket
|
||||
std::string name; // Client name from Hello message
|
||||
// Note: peername (IP address) is not stored here to save memory.
|
||||
// Use helper_->getpeername_to() or helper_->getpeername() when needed.
|
||||
};
|
||||
|
||||
// Keepalive timeout in milliseconds
|
||||
@@ -176,6 +177,11 @@ class APIConnection final : public APIServerConnection {
|
||||
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool send_water_heater_state(water_heater::WaterHeater *water_heater);
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void send_event(event::Event *event, const char *event_type);
|
||||
#endif
|
||||
@@ -203,10 +209,14 @@ class APIConnection final : public APIServerConnection {
|
||||
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;
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
|
||||
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
||||
this->flags_.state_subscription = true;
|
||||
this->initial_state_iterator_.begin();
|
||||
// Start initial state iterator only if no iterator is active
|
||||
// If list_entities is running, we'll start initial_state when it completes
|
||||
if (this->active_iterator_ == ActiveIterator::NONE) {
|
||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||
}
|
||||
}
|
||||
void subscribe_logs(const SubscribeLogsRequest &msg) override {
|
||||
this->flags_.log_subscription = msg.level;
|
||||
@@ -224,9 +234,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
|
||||
@@ -281,12 +291,21 @@ class APIConnection final : public APIServerConnection {
|
||||
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; }
|
||||
/// Get peer name (IP address) into a stack buffer - avoids heap allocation
|
||||
size_t get_peername_to(std::span<char, socket::PEERNAME_MAX_LEN> buf) const {
|
||||
return this->helper_->getpeername_to(buf);
|
||||
}
|
||||
/// Get peer name as std::string - use sparingly, allocates on heap
|
||||
std::string get_peername() const { return this->helper_->getpeername(); }
|
||||
|
||||
protected:
|
||||
// Helper function to handle authentication completion
|
||||
void complete_authentication_();
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void try_send_camera_image_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void process_state_subscriptions_();
|
||||
#endif
|
||||
@@ -310,17 +329,10 @@ 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();
|
||||
// Try to use static reference first to avoid allocation
|
||||
StringRef static_ref = entity->get_object_id_ref_for_api_();
|
||||
// Store dynamic string outside the if-else to maintain lifetime
|
||||
std::string object_id;
|
||||
if (!static_ref.empty()) {
|
||||
msg.set_object_id(static_ref);
|
||||
} else {
|
||||
// Dynamic case - need to allocate
|
||||
object_id = entity->get_object_id();
|
||||
msg.set_object_id(StringRef(object_id));
|
||||
}
|
||||
// Get object_id with zero heap allocation
|
||||
// Static case returns direct reference, dynamic case uses buffer
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
msg.set_object_id(entity->get_object_id_to(object_id_buf));
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.set_name(entity->get_name());
|
||||
@@ -456,6 +468,12 @@ class APIConnection final : public APIServerConnection {
|
||||
static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single);
|
||||
@@ -490,10 +508,22 @@ class APIConnection final : public APIServerConnection {
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
APIServer *parent_;
|
||||
|
||||
// Group 2: Larger objects (must be 4-byte aligned)
|
||||
// These contain vectors/pointers internally, so putting them early ensures good alignment
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
// Group 2: Iterator union (saves ~16 bytes vs separate iterators)
|
||||
// These iterators are never active simultaneously - list_entities runs to completion
|
||||
// before initial_state begins, so we use a union with explicit construction/destruction.
|
||||
enum class ActiveIterator : uint8_t { NONE, LIST_ENTITIES, INITIAL_STATE };
|
||||
|
||||
union IteratorUnion {
|
||||
ListEntitiesIterator list_entities;
|
||||
InitialStateIterator initial_state;
|
||||
// Constructor/destructor do nothing - use placement new/explicit destructor
|
||||
IteratorUnion() {}
|
||||
~IteratorUnion() {}
|
||||
} iterator_storage_;
|
||||
|
||||
// Helper methods for iterator lifecycle management
|
||||
void destroy_active_iterator_();
|
||||
void begin_iterator_(ActiveIterator type);
|
||||
#ifdef USE_CAMERA
|
||||
std::unique_ptr<camera::CameraImageReader> image_reader_;
|
||||
#endif
|
||||
@@ -608,7 +638,9 @@ class APIConnection final : public APIServerConnection {
|
||||
// 2-byte types immediately after flags_ (no padding between them)
|
||||
uint16_t client_api_version_major_{0};
|
||||
uint16_t client_api_version_minor_{0};
|
||||
// Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary
|
||||
// 1-byte type to fill padding
|
||||
ActiveIterator active_iterator_{ActiveIterator::NONE};
|
||||
// Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary
|
||||
|
||||
uint32_t get_batch_delay_ms_() const;
|
||||
// Message will use 8 more bytes than the minimum size, and typical
|
||||
@@ -726,6 +758,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
|
||||
|
||||
@@ -13,8 +13,16 @@ namespace esphome::api {
|
||||
|
||||
static const char *const TAG = "api.frame_helper";
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
do { \
|
||||
char peername__[socket::PEERNAME_MAX_LEN]; \
|
||||
this->socket_->getpeername_to(peername__); \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), peername__, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
#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())
|
||||
|
||||
@@ -91,6 +91,7 @@ class APIFrameHelper {
|
||||
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); }
|
||||
size_t getpeername_to(std::span<char, socket::PEERNAME_MAX_LEN> buf) { return socket_->getpeername_to(buf); }
|
||||
APIError close() {
|
||||
state_ = State::CLOSED;
|
||||
int err = this->socket_->close();
|
||||
|
||||
@@ -24,8 +24,16 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||
#endif
|
||||
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
do { \
|
||||
char peername__[socket::PEERNAME_MAX_LEN]; \
|
||||
this->socket_->getpeername_to(peername__); \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), peername__, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
#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())
|
||||
|
||||
@@ -18,8 +18,16 @@ namespace esphome::api {
|
||||
|
||||
static const char *const TAG = "api.plaintext";
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
do { \
|
||||
char peername__[socket::PEERNAME_MAX_LEN]; \
|
||||
this->socket_->getpeername_to(peername__); \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), peername__, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
#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())
|
||||
|
||||
@@ -23,9 +23,7 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->client_info = value.data();
|
||||
this->client_info_len = value.size();
|
||||
this->client_info = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -49,9 +47,7 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
|
||||
bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->password = value.data();
|
||||
this->password_len = value.size();
|
||||
this->password = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -124,12 +120,12 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
for (const auto &it : this->devices) {
|
||||
buffer.encode_message(20, it, true);
|
||||
buffer.encode_message(20, it);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
for (const auto &it : this->areas) {
|
||||
buffer.encode_message(21, it, true);
|
||||
buffer.encode_message(21, it);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
@@ -447,9 +443,10 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 13:
|
||||
this->preset_mode = value.as_string();
|
||||
case 13: {
|
||||
this->preset_mode = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -612,9 +609,7 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
bool LightCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 19: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->effect = value.data();
|
||||
this->effect_len = value.size();
|
||||
this->effect = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -855,9 +850,11 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const {
|
||||
#ifdef USE_API_NOISE
|
||||
bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->key = value.as_string();
|
||||
case 1: {
|
||||
this->key = value.data();
|
||||
this->key_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -878,13 +875,13 @@ void HomeassistantServiceMap::calculate_size(ProtoSize &size) const {
|
||||
void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->service_ref_);
|
||||
for (auto &it : this->data) {
|
||||
buffer.encode_message(2, it, true);
|
||||
buffer.encode_message(2, it);
|
||||
}
|
||||
for (auto &it : this->data_template) {
|
||||
buffer.encode_message(3, it, true);
|
||||
buffer.encode_message(3, it);
|
||||
}
|
||||
for (auto &it : this->variables) {
|
||||
buffer.encode_message(4, it, true);
|
||||
buffer.encode_message(4, it);
|
||||
}
|
||||
buffer.encode_bool(5, this->is_event);
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
@@ -930,12 +927,12 @@ bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
||||
}
|
||||
bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3:
|
||||
this->error_message = value.as_string();
|
||||
case 3: {
|
||||
this->error_message = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
case 4: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->response_data = value.data();
|
||||
this->response_data_len = value.size();
|
||||
break;
|
||||
@@ -960,15 +957,18 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const
|
||||
}
|
||||
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->entity_id = value.as_string();
|
||||
case 1: {
|
||||
this->entity_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 2:
|
||||
this->state = value.as_string();
|
||||
}
|
||||
case 2: {
|
||||
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 3:
|
||||
this->attribute = value.as_string();
|
||||
}
|
||||
case 3: {
|
||||
this->attribute = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -978,9 +978,7 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->timezone = value.data();
|
||||
this->timezone_len = value.size();
|
||||
this->timezone = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -1011,7 +1009,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->name_ref_);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
for (auto &it : this->args) {
|
||||
buffer.encode_message(3, it, true);
|
||||
buffer.encode_message(3, it);
|
||||
}
|
||||
buffer.encode_uint32(4, static_cast<uint32_t>(this->supports_response));
|
||||
}
|
||||
@@ -1045,9 +1043,10 @@ bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
}
|
||||
bool ExecuteServiceArgument::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4:
|
||||
this->string_ = value.as_string();
|
||||
case 4: {
|
||||
this->string_ = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
case 9:
|
||||
this->string_array.push_back(value.as_string());
|
||||
break;
|
||||
@@ -1138,7 +1137,7 @@ void ExecuteServiceResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_bool(1, this->success);
|
||||
size.add_length(1, this->error_message_ref_.size());
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
size.add_length(4, this->response_data_len);
|
||||
size.add_length(1, this->response_data_len);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -1392,12 +1391,14 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
}
|
||||
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 17:
|
||||
this->custom_fan_mode = value.as_string();
|
||||
case 17: {
|
||||
this->custom_fan_mode = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 21:
|
||||
this->custom_preset = value.as_string();
|
||||
}
|
||||
case 21: {
|
||||
this->custom_preset = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1426,6 +1427,114 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id_ref_);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name_ref_);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(4, this->icon_ref_);
|
||||
#endif
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
buffer.encode_uint32(6, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(7, this->device_id);
|
||||
#endif
|
||||
buffer.encode_float(8, this->min_temperature);
|
||||
buffer.encode_float(9, this->max_temperature);
|
||||
buffer.encode_float(10, this->target_temperature_step);
|
||||
for (const auto &it : *this->supported_modes) {
|
||||
buffer.encode_uint32(11, static_cast<uint32_t>(it), true);
|
||||
}
|
||||
buffer.encode_uint32(12, this->supported_features);
|
||||
}
|
||||
void ListEntitiesWaterHeaterResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->object_id_ref_.size());
|
||||
size.add_fixed32(1, this->key);
|
||||
size.add_length(1, this->name_ref_.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size.add_length(1, this->icon_ref_.size());
|
||||
#endif
|
||||
size.add_bool(1, this->disabled_by_default);
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
size.add_float(1, this->min_temperature);
|
||||
size.add_float(1, this->max_temperature);
|
||||
size.add_float(1, this->target_temperature_step);
|
||||
if (!this->supported_modes->empty()) {
|
||||
for (const auto &it : *this->supported_modes) {
|
||||
size.add_uint32_force(1, static_cast<uint32_t>(it));
|
||||
}
|
||||
}
|
||||
size.add_uint32(1, this->supported_features);
|
||||
}
|
||||
void WaterHeaterStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->current_temperature);
|
||||
buffer.encode_float(3, this->target_temperature);
|
||||
buffer.encode_uint32(4, static_cast<uint32_t>(this->mode));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(5, this->device_id);
|
||||
#endif
|
||||
buffer.encode_uint32(6, this->state);
|
||||
buffer.encode_float(7, this->target_temperature_low);
|
||||
buffer.encode_float(8, this->target_temperature_high);
|
||||
}
|
||||
void WaterHeaterStateResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_fixed32(1, this->key);
|
||||
size.add_float(1, this->current_temperature);
|
||||
size.add_float(1, this->target_temperature);
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->mode));
|
||||
#ifdef USE_DEVICES
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
size.add_uint32(1, this->state);
|
||||
size.add_float(1, this->target_temperature_low);
|
||||
size.add_float(1, this->target_temperature_high);
|
||||
}
|
||||
bool WaterHeaterCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
this->has_fields = value.as_uint32();
|
||||
break;
|
||||
case 3:
|
||||
this->mode = static_cast<enums::WaterHeaterMode>(value.as_uint32());
|
||||
break;
|
||||
#ifdef USE_DEVICES
|
||||
case 5:
|
||||
this->device_id = value.as_uint32();
|
||||
break;
|
||||
#endif
|
||||
case 6:
|
||||
this->state = value.as_uint32();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->key = value.as_fixed32();
|
||||
break;
|
||||
case 4:
|
||||
this->target_temperature = value.as_float();
|
||||
break;
|
||||
case 7:
|
||||
this->target_temperature_low = value.as_float();
|
||||
break;
|
||||
case 8:
|
||||
this->target_temperature_high = value.as_float();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id_ref_);
|
||||
@@ -1573,9 +1682,7 @@ bool SelectCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
bool SelectCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->state = value.data();
|
||||
this->state_len = value.size();
|
||||
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -1603,8 +1710,8 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon_ref_);
|
||||
#endif
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
for (auto &it : this->tones) {
|
||||
buffer.encode_string(7, it, true);
|
||||
for (const char *it : *this->tones) {
|
||||
buffer.encode_string(7, it, strlen(it), true);
|
||||
}
|
||||
buffer.encode_bool(8, this->supports_duration);
|
||||
buffer.encode_bool(9, this->supports_volume);
|
||||
@@ -1621,9 +1728,9 @@ void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->icon_ref_.size());
|
||||
#endif
|
||||
size.add_bool(1, this->disabled_by_default);
|
||||
if (!this->tones.empty()) {
|
||||
for (const auto &it : this->tones) {
|
||||
size.add_length_force(1, it.size());
|
||||
if (!this->tones->empty()) {
|
||||
for (const char *it : *this->tones) {
|
||||
size.add_length_force(1, strlen(it));
|
||||
}
|
||||
}
|
||||
size.add_bool(1, this->supports_duration);
|
||||
@@ -1679,9 +1786,10 @@ bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 5:
|
||||
this->tone = value.as_string();
|
||||
case 5: {
|
||||
this->tone = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1770,9 +1878,10 @@ bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4:
|
||||
this->code = value.as_string();
|
||||
case 4: {
|
||||
this->code = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1867,7 +1976,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
|
||||
buffer.encode_bool(8, this->supports_pause);
|
||||
for (auto &it : this->supported_formats) {
|
||||
buffer.encode_message(9, it, true);
|
||||
buffer.encode_message(9, it);
|
||||
}
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
@@ -1940,9 +2049,10 @@ bool MediaPlayerCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
}
|
||||
bool MediaPlayerCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 7:
|
||||
this->media_url = value.as_string();
|
||||
case 7: {
|
||||
this->media_url = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1987,7 +2097,7 @@ void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||
buffer.encode_message(1, this->advertisements[i], true);
|
||||
buffer.encode_message(1, this->advertisements[i]);
|
||||
}
|
||||
}
|
||||
void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const {
|
||||
@@ -2060,7 +2170,7 @@ void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_uint32(3, this->properties);
|
||||
for (auto &it : this->descriptors) {
|
||||
buffer.encode_message(4, it, true);
|
||||
buffer.encode_message(4, it);
|
||||
}
|
||||
buffer.encode_uint32(5, this->short_uuid);
|
||||
}
|
||||
@@ -2081,7 +2191,7 @@ void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
for (auto &it : this->characteristics) {
|
||||
buffer.encode_message(3, it, true);
|
||||
buffer.encode_message(3, it);
|
||||
}
|
||||
buffer.encode_uint32(4, this->short_uuid);
|
||||
}
|
||||
@@ -2097,7 +2207,7 @@ void BluetoothGATTService::calculate_size(ProtoSize &size) const {
|
||||
void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
for (auto &it : this->services) {
|
||||
buffer.encode_message(2, it, true);
|
||||
buffer.encode_message(2, it);
|
||||
}
|
||||
}
|
||||
void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const {
|
||||
@@ -2150,7 +2260,6 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->data = value.data();
|
||||
this->data_len = value.size();
|
||||
break;
|
||||
@@ -2189,7 +2298,6 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto
|
||||
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->data = value.data();
|
||||
this->data_len = value.size();
|
||||
break;
|
||||
@@ -2373,12 +2481,14 @@ bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
}
|
||||
bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->name = value.as_string();
|
||||
case 1: {
|
||||
this->name = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 2:
|
||||
this->value = value.as_string();
|
||||
}
|
||||
case 2: {
|
||||
this->value = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2417,20 +2527,22 @@ bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->data = value.as_string();
|
||||
case 1: {
|
||||
this->data = value.data();
|
||||
this->data_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bytes(1, this->data_ptr_, this->data_len_);
|
||||
buffer.encode_bytes(1, this->data, this->data_len);
|
||||
buffer.encode_bool(2, this->end);
|
||||
}
|
||||
void VoiceAssistantAudio::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->data_len_);
|
||||
size.add_length(1, this->data_len);
|
||||
size.add_bool(1, this->end);
|
||||
}
|
||||
bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
@@ -2454,12 +2566,14 @@ bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVar
|
||||
}
|
||||
bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
this->timer_id = value.as_string();
|
||||
case 2: {
|
||||
this->timer_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 3:
|
||||
this->name = value.as_string();
|
||||
}
|
||||
case 3: {
|
||||
this->name = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2477,15 +2591,18 @@ bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt
|
||||
}
|
||||
bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->media_id = value.as_string();
|
||||
case 1: {
|
||||
this->media_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 2:
|
||||
this->text = value.as_string();
|
||||
}
|
||||
case 2: {
|
||||
this->text = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 3:
|
||||
this->preannounce_media_id = value.as_string();
|
||||
}
|
||||
case 3: {
|
||||
this->preannounce_media_id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2521,24 +2638,29 @@ bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarIn
|
||||
}
|
||||
bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->id = value.as_string();
|
||||
case 1: {
|
||||
this->id = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 2:
|
||||
this->wake_word = value.as_string();
|
||||
}
|
||||
case 2: {
|
||||
this->wake_word = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
this->trained_languages.push_back(value.as_string());
|
||||
break;
|
||||
case 4:
|
||||
this->model_type = value.as_string();
|
||||
case 4: {
|
||||
this->model_type = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 6:
|
||||
this->model_hash = value.as_string();
|
||||
}
|
||||
case 6: {
|
||||
this->model_hash = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
case 7:
|
||||
this->url = value.as_string();
|
||||
}
|
||||
case 7: {
|
||||
this->url = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2557,7 +2679,7 @@ bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoL
|
||||
}
|
||||
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->available_wake_words) {
|
||||
buffer.encode_message(1, it, true);
|
||||
buffer.encode_message(1, it);
|
||||
}
|
||||
for (const auto &it : *this->active_wake_words) {
|
||||
buffer.encode_string(2, it, true);
|
||||
@@ -2648,9 +2770,10 @@ bool AlarmControlPanelCommandRequest::decode_varint(uint32_t field_id, ProtoVarI
|
||||
}
|
||||
bool AlarmControlPanelCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3:
|
||||
this->code = value.as_string();
|
||||
case 3: {
|
||||
this->code = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2732,9 +2855,10 @@ bool TextCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
this->state = value.as_string();
|
||||
case 2: {
|
||||
this->state = StringRef(reinterpret_cast<const char *>(value.data()), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -3202,7 +3326,6 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->data = value.data();
|
||||
this->data_len = value.size();
|
||||
break;
|
||||
@@ -3227,7 +3350,6 @@ bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->data = value.data();
|
||||
this->data_len = value.size();
|
||||
break;
|
||||
@@ -3243,7 +3365,7 @@ void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->type));
|
||||
size.add_length(2, this->data_len);
|
||||
size.add_length(1, this->data_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -129,6 +129,25 @@ enum ClimatePreset : uint32_t {
|
||||
CLIMATE_PRESET_ACTIVITY = 7,
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
enum WaterHeaterMode : uint32_t {
|
||||
WATER_HEATER_MODE_OFF = 0,
|
||||
WATER_HEATER_MODE_ECO = 1,
|
||||
WATER_HEATER_MODE_ELECTRIC = 2,
|
||||
WATER_HEATER_MODE_PERFORMANCE = 3,
|
||||
WATER_HEATER_MODE_HIGH_DEMAND = 4,
|
||||
WATER_HEATER_MODE_HEAT_PUMP = 5,
|
||||
WATER_HEATER_MODE_GAS = 6,
|
||||
};
|
||||
#endif
|
||||
enum WaterHeaterCommandHasField : uint32_t {
|
||||
WATER_HEATER_COMMAND_HAS_NONE = 0,
|
||||
WATER_HEATER_COMMAND_HAS_MODE = 1,
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2,
|
||||
WATER_HEATER_COMMAND_HAS_STATE = 4,
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8,
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16,
|
||||
};
|
||||
#ifdef USE_NUMBER
|
||||
enum NumberMode : uint32_t {
|
||||
NUMBER_MODE_AUTO = 0,
|
||||
@@ -338,12 +357,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
|
||||
@@ -379,12 +397,11 @@ class HelloResponse final : public ProtoMessage {
|
||||
class AuthenticationRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 3;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 19;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 9;
|
||||
#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};
|
||||
StringRef password{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -778,7 +795,7 @@ class FanCommandRequest final : public CommandProtoMessage {
|
||||
bool has_speed_level{false};
|
||||
int32_t speed_level{0};
|
||||
bool has_preset_mode{false};
|
||||
std::string preset_mode{};
|
||||
StringRef preset_mode{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -840,7 +857,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
|
||||
@@ -869,8 +886,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
|
||||
@@ -1057,7 +1073,8 @@ class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "noise_encryption_set_key_request"; }
|
||||
#endif
|
||||
std::string key{};
|
||||
const uint8_t *key{nullptr};
|
||||
uint16_t key_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1144,13 +1161,13 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
class HomeassistantActionResponse final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 130;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 34;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 24;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "homeassistant_action_response"; }
|
||||
#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};
|
||||
@@ -1205,9 +1222,9 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "home_assistant_state_response"; }
|
||||
#endif
|
||||
std::string entity_id{};
|
||||
std::string state{};
|
||||
std::string attribute{};
|
||||
StringRef entity_id{};
|
||||
StringRef state{};
|
||||
StringRef attribute{};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1232,13 +1249,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
|
||||
@@ -1286,7 +1302,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{};
|
||||
@@ -1492,11 +1508,11 @@ class ClimateCommandRequest final : public CommandProtoMessage {
|
||||
bool has_swing_mode{false};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
bool has_custom_fan_mode{false};
|
||||
std::string custom_fan_mode{};
|
||||
StringRef custom_fan_mode{};
|
||||
bool has_preset{false};
|
||||
enums::ClimatePreset preset{};
|
||||
bool has_custom_preset{false};
|
||||
std::string custom_preset{};
|
||||
StringRef custom_preset{};
|
||||
bool has_target_humidity{false};
|
||||
float target_humidity{0.0f};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1509,6 +1525,70 @@ class ClimateCommandRequest final : public CommandProtoMessage {
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 132;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 63;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_water_heater_response"; }
|
||||
#endif
|
||||
float min_temperature{0.0f};
|
||||
float max_temperature{0.0f};
|
||||
float target_temperature_step{0.0f};
|
||||
const water_heater::WaterHeaterModeMask *supported_modes{};
|
||||
uint32_t supported_features{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class WaterHeaterStateResponse final : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 133;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 35;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "water_heater_state_response"; }
|
||||
#endif
|
||||
float current_temperature{0.0f};
|
||||
float target_temperature{0.0f};
|
||||
enums::WaterHeaterMode mode{};
|
||||
uint32_t state{0};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
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:
|
||||
};
|
||||
class WaterHeaterCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 134;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 34;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "water_heater_command_request"; }
|
||||
#endif
|
||||
uint32_t has_fields{0};
|
||||
enums::WaterHeaterMode mode{};
|
||||
float target_temperature{0.0f};
|
||||
uint32_t state{0};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
@@ -1605,12 +1685,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
|
||||
@@ -1629,7 +1708,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;
|
||||
@@ -1666,7 +1745,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};
|
||||
@@ -1727,7 +1806,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
|
||||
@@ -1837,7 +1916,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
|
||||
@@ -2067,7 +2146,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
|
||||
class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 75;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 29;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 19;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "bluetooth_gatt_write_request"; }
|
||||
#endif
|
||||
@@ -2103,7 +2182,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
|
||||
class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 77;
|
||||
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 "bluetooth_gatt_write_descriptor_request"; }
|
||||
#endif
|
||||
@@ -2413,8 +2492,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
|
||||
@@ -2442,17 +2521,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;
|
||||
@@ -2472,8 +2546,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};
|
||||
@@ -2492,9 +2566,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;
|
||||
@@ -2537,13 +2611,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
|
||||
@@ -2644,7 +2718,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
|
||||
@@ -2701,7 +2775,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
|
||||
|
||||
@@ -348,6 +348,47 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
template<> const char *proto_enum_to_string<enums::WaterHeaterMode>(enums::WaterHeaterMode value) {
|
||||
switch (value) {
|
||||
case enums::WATER_HEATER_MODE_OFF:
|
||||
return "WATER_HEATER_MODE_OFF";
|
||||
case enums::WATER_HEATER_MODE_ECO:
|
||||
return "WATER_HEATER_MODE_ECO";
|
||||
case enums::WATER_HEATER_MODE_ELECTRIC:
|
||||
return "WATER_HEATER_MODE_ELECTRIC";
|
||||
case enums::WATER_HEATER_MODE_PERFORMANCE:
|
||||
return "WATER_HEATER_MODE_PERFORMANCE";
|
||||
case enums::WATER_HEATER_MODE_HIGH_DEMAND:
|
||||
return "WATER_HEATER_MODE_HIGH_DEMAND";
|
||||
case enums::WATER_HEATER_MODE_HEAT_PUMP:
|
||||
return "WATER_HEATER_MODE_HEAT_PUMP";
|
||||
case enums::WATER_HEATER_MODE_GAS:
|
||||
return "WATER_HEATER_MODE_GAS";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
template<>
|
||||
const char *proto_enum_to_string<enums::WaterHeaterCommandHasField>(enums::WaterHeaterCommandHasField value) {
|
||||
switch (value) {
|
||||
case enums::WATER_HEATER_COMMAND_HAS_NONE:
|
||||
return "WATER_HEATER_COMMAND_HAS_NONE";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_MODE:
|
||||
return "WATER_HEATER_COMMAND_HAS_MODE";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE:
|
||||
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_STATE:
|
||||
return "WATER_HEATER_COMMAND_HAS_STATE";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW:
|
||||
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH:
|
||||
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#ifdef USE_NUMBER
|
||||
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
|
||||
switch (value) {
|
||||
@@ -695,7 +736,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("'").append(this->client_info.c_str(), this->client_info.size()).append("'");
|
||||
out.append("\n");
|
||||
dump_field(out, "api_version_major", this->api_version_major);
|
||||
dump_field(out, "api_version_minor", this->api_version_minor);
|
||||
@@ -711,7 +752,7 @@ void HelloResponse::dump_to(std::string &out) const {
|
||||
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("'").append(this->password.c_str(), this->password.size()).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
void AuthenticationResponse::dump_to(std::string &out) const {
|
||||
@@ -923,7 +964,9 @@ 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);
|
||||
dump_field(out, "preset_mode", this->preset_mode);
|
||||
out.append(" preset_mode: ");
|
||||
out.append("'").append(this->preset_mode.c_str(), this->preset_mode.size()).append("'");
|
||||
out.append("\n");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1000,7 +1043,7 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
||||
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("'").append(this->effect.c_str(), this->effect.size()).append("'");
|
||||
out.append("\n");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
@@ -1113,7 +1156,7 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
|
||||
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest");
|
||||
out.append(" key: ");
|
||||
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->key.data()), this->key.size()));
|
||||
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); }
|
||||
@@ -1162,7 +1205,9 @@ void HomeassistantActionResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HomeassistantActionResponse");
|
||||
dump_field(out, "call_id", this->call_id);
|
||||
dump_field(out, "success", this->success);
|
||||
dump_field(out, "error_message", this->error_message);
|
||||
out.append(" error_message: ");
|
||||
out.append("'").append(this->error_message.c_str(), this->error_message.size()).append("'");
|
||||
out.append("\n");
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
out.append(" response_data: ");
|
||||
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
||||
@@ -1182,9 +1227,15 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
}
|
||||
void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HomeAssistantStateResponse");
|
||||
dump_field(out, "entity_id", this->entity_id);
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "attribute", this->attribute);
|
||||
out.append(" entity_id: ");
|
||||
out.append("'").append(this->entity_id.c_str(), this->entity_id.size()).append("'");
|
||||
out.append("\n");
|
||||
out.append(" state: ");
|
||||
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
|
||||
out.append("\n");
|
||||
out.append(" attribute: ");
|
||||
out.append("'").append(this->attribute.c_str(), this->attribute.size()).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
#endif
|
||||
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
|
||||
@@ -1192,7 +1243,7 @@ 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("'").append(this->timezone.c_str(), this->timezone.size()).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
@@ -1217,7 +1268,9 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
|
||||
dump_field(out, "bool_", this->bool_);
|
||||
dump_field(out, "legacy_int", this->legacy_int);
|
||||
dump_field(out, "float_", this->float_);
|
||||
dump_field(out, "string_", this->string_);
|
||||
out.append(" string_: ");
|
||||
out.append("'").append(this->string_.c_str(), this->string_.size()).append("'");
|
||||
out.append("\n");
|
||||
dump_field(out, "int_", this->int_);
|
||||
for (const auto it : this->bool_array) {
|
||||
dump_field(out, "bool_array", static_cast<bool>(it), 4);
|
||||
@@ -1374,11 +1427,15 @@ 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);
|
||||
dump_field(out, "custom_fan_mode", this->custom_fan_mode);
|
||||
out.append(" custom_fan_mode: ");
|
||||
out.append("'").append(this->custom_fan_mode.c_str(), this->custom_fan_mode.size()).append("'");
|
||||
out.append("\n");
|
||||
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);
|
||||
dump_field(out, "custom_preset", this->custom_preset);
|
||||
out.append(" custom_preset: ");
|
||||
out.append("'").append(this->custom_preset.c_str(), this->custom_preset.size()).append("'");
|
||||
out.append("\n");
|
||||
dump_field(out, "has_target_humidity", this->has_target_humidity);
|
||||
dump_field(out, "target_humidity", this->target_humidity);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1386,6 +1443,55 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#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, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "min_temperature", this->min_temperature);
|
||||
dump_field(out, "max_temperature", this->max_temperature);
|
||||
dump_field(out, "target_temperature_step", this->target_temperature_step);
|
||||
for (const auto &it : *this->supported_modes) {
|
||||
dump_field(out, "supported_modes", static_cast<enums::WaterHeaterMode>(it), 4);
|
||||
}
|
||||
dump_field(out, "supported_features", this->supported_features);
|
||||
}
|
||||
void WaterHeaterStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "WaterHeaterStateResponse");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "current_temperature", this->current_temperature);
|
||||
dump_field(out, "target_temperature", this->target_temperature);
|
||||
dump_field(out, "mode", static_cast<enums::WaterHeaterMode>(this->mode));
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "target_temperature_low", this->target_temperature_low);
|
||||
dump_field(out, "target_temperature_high", this->target_temperature_high);
|
||||
}
|
||||
void WaterHeaterCommandRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "WaterHeaterCommandRequest");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "has_fields", this->has_fields);
|
||||
dump_field(out, "mode", static_cast<enums::WaterHeaterMode>(this->mode));
|
||||
dump_field(out, "target_temperature", this->target_temperature);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "target_temperature_low", this->target_temperature_low);
|
||||
dump_field(out, "target_temperature_high", this->target_temperature_high);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesNumberResponse");
|
||||
@@ -1456,7 +1562,7 @@ 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("'").append(this->state.c_str(), this->state.size()).append("'");
|
||||
out.append("\n");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
@@ -1473,7 +1579,7 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
#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);
|
||||
@@ -1497,7 +1603,9 @@ void SirenCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "has_state", this->has_state);
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "has_tone", this->has_tone);
|
||||
dump_field(out, "tone", this->tone);
|
||||
out.append(" tone: ");
|
||||
out.append("'").append(this->tone.c_str(), this->tone.size()).append("'");
|
||||
out.append("\n");
|
||||
dump_field(out, "has_duration", this->has_duration);
|
||||
dump_field(out, "duration", this->duration);
|
||||
dump_field(out, "has_volume", this->has_volume);
|
||||
@@ -1539,7 +1647,9 @@ void LockCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "command", static_cast<enums::LockCommand>(this->command));
|
||||
dump_field(out, "has_code", this->has_code);
|
||||
dump_field(out, "code", this->code);
|
||||
out.append(" code: ");
|
||||
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
|
||||
out.append("\n");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1617,7 +1727,9 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "has_volume", this->has_volume);
|
||||
dump_field(out, "volume", this->volume);
|
||||
dump_field(out, "has_media_url", this->has_media_url);
|
||||
dump_field(out, "media_url", this->media_url);
|
||||
out.append(" media_url: ");
|
||||
out.append("'").append(this->media_url.c_str(), this->media_url.size()).append("'");
|
||||
out.append("\n");
|
||||
dump_field(out, "has_announcement", this->has_announcement);
|
||||
dump_field(out, "announcement", this->announcement);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1847,8 +1959,12 @@ void VoiceAssistantResponse::dump_to(std::string &out) const {
|
||||
}
|
||||
void VoiceAssistantEventData::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantEventData");
|
||||
dump_field(out, "name", this->name);
|
||||
dump_field(out, "value", this->value);
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
|
||||
out.append("\n");
|
||||
out.append(" value: ");
|
||||
out.append("'").append(this->value.c_str(), this->value.size()).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
void VoiceAssistantEventResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantEventResponse");
|
||||
@@ -1862,28 +1978,34 @@ 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);
|
||||
}
|
||||
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantTimerEventResponse");
|
||||
dump_field(out, "event_type", static_cast<enums::VoiceAssistantTimerEvent>(this->event_type));
|
||||
dump_field(out, "timer_id", this->timer_id);
|
||||
dump_field(out, "name", this->name);
|
||||
out.append(" timer_id: ");
|
||||
out.append("'").append(this->timer_id.c_str(), this->timer_id.size()).append("'");
|
||||
out.append("\n");
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name.c_str(), this->name.size()).append("'");
|
||||
out.append("\n");
|
||||
dump_field(out, "total_seconds", this->total_seconds);
|
||||
dump_field(out, "seconds_left", this->seconds_left);
|
||||
dump_field(out, "is_active", this->is_active);
|
||||
}
|
||||
void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantAnnounceRequest");
|
||||
dump_field(out, "media_id", this->media_id);
|
||||
dump_field(out, "text", this->text);
|
||||
dump_field(out, "preannounce_media_id", this->preannounce_media_id);
|
||||
out.append(" media_id: ");
|
||||
out.append("'").append(this->media_id.c_str(), this->media_id.size()).append("'");
|
||||
out.append("\n");
|
||||
out.append(" text: ");
|
||||
out.append("'").append(this->text.c_str(), this->text.size()).append("'");
|
||||
out.append("\n");
|
||||
out.append(" preannounce_media_id: ");
|
||||
out.append("'").append(this->preannounce_media_id.c_str(), this->preannounce_media_id.size()).append("'");
|
||||
out.append("\n");
|
||||
dump_field(out, "start_conversation", this->start_conversation);
|
||||
}
|
||||
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
|
||||
@@ -1897,15 +2019,25 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const {
|
||||
}
|
||||
void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord");
|
||||
dump_field(out, "id", this->id);
|
||||
dump_field(out, "wake_word", this->wake_word);
|
||||
out.append(" id: ");
|
||||
out.append("'").append(this->id.c_str(), this->id.size()).append("'");
|
||||
out.append("\n");
|
||||
out.append(" wake_word: ");
|
||||
out.append("'").append(this->wake_word.c_str(), this->wake_word.size()).append("'");
|
||||
out.append("\n");
|
||||
for (const auto &it : this->trained_languages) {
|
||||
dump_field(out, "trained_languages", it, 4);
|
||||
}
|
||||
dump_field(out, "model_type", this->model_type);
|
||||
out.append(" model_type: ");
|
||||
out.append("'").append(this->model_type.c_str(), this->model_type.size()).append("'");
|
||||
out.append("\n");
|
||||
dump_field(out, "model_size", this->model_size);
|
||||
dump_field(out, "model_hash", this->model_hash);
|
||||
dump_field(out, "url", this->url);
|
||||
out.append(" model_hash: ");
|
||||
out.append("'").append(this->model_hash.c_str(), this->model_hash.size()).append("'");
|
||||
out.append("\n");
|
||||
out.append(" url: ");
|
||||
out.append("'").append(this->url.c_str(), this->url.size()).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest");
|
||||
@@ -1964,7 +2096,9 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "AlarmControlPanelCommandRequest");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "command", static_cast<enums::AlarmControlPanelStateCommand>(this->command));
|
||||
dump_field(out, "code", this->code);
|
||||
out.append(" code: ");
|
||||
out.append("'").append(this->code.c_str(), this->code.size()).append("'");
|
||||
out.append("\n");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -2001,7 +2135,9 @@ void TextStateResponse::dump_to(std::string &out) const {
|
||||
void TextCommandRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "TextCommandRequest");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "state", this->state);
|
||||
out.append(" state: ");
|
||||
out.append("'").append(this->state.c_str(), this->state.size()).append("'");
|
||||
out.append("\n");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "esphome/components/climate/climate_traits.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
#include "esphome/components/water_heater/water_heater.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
#include "esphome/components/light/light_traits.h"
|
||||
#endif
|
||||
|
||||
@@ -621,6 +621,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
this->on_homeassistant_action_response(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
case WaterHeaterCommandRequest::MESSAGE_TYPE: {
|
||||
WaterHeaterCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_water_heater_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_water_heater_command_request(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -91,6 +91,10 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
virtual void on_number_command_request(const NumberCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
@@ -125,15 +125,18 @@ void APIServer::loop() {
|
||||
if (!sock)
|
||||
break;
|
||||
|
||||
char peername[socket::PEERNAME_MAX_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
|
||||
}
|
||||
@@ -185,7 +187,8 @@ 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);
|
||||
// Trigger expects std::string, get fresh peername from socket
|
||||
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->get_peername());
|
||||
#endif
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
this->unregister_active_action_calls_for_connection(client.get());
|
||||
@@ -335,6 +338,10 @@ API_DISPATCH_UPDATE(valve::Valve, valve)
|
||||
API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
// Event is a special case - unlike other entities with simple state fields,
|
||||
// events store their state in a member accessed via obj->get_last_event_type()
|
||||
@@ -390,7 +397,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);
|
||||
@@ -402,7 +409,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) {
|
||||
@@ -421,7 +428,7 @@ 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) {
|
||||
std::function<void(const std::string &)> 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)
|
||||
@@ -430,7 +437,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(const std::string &)> 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));
|
||||
@@ -450,23 +457,23 @@ 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(const std::string &)> 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(const std::string &)> f) {
|
||||
this->add_state_subscription_(entity_id, attribute, std::move(f), true);
|
||||
}
|
||||
|
||||
// Existing std::string overload (for custom_api_device.h - heap allocation)
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
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(std::string)> f) {
|
||||
std::function<void(const std::string &)> f) {
|
||||
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
|
||||
}
|
||||
|
||||
@@ -674,7 +681,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);
|
||||
@@ -684,7 +691,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) {
|
||||
|
||||
@@ -133,6 +133,9 @@ class APIServer : public Component,
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
void on_water_heater_update(water_heater::WaterHeater *obj) override;
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||
|
||||
@@ -140,10 +143,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
|
||||
@@ -162,9 +165,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
|
||||
@@ -192,7 +195,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(const std::string &)> callback;
|
||||
bool once;
|
||||
|
||||
// Dynamic storage for external components using std::string API (custom_api_device.h)
|
||||
@@ -202,14 +205,16 @@ 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(const std::string &)> f);
|
||||
void get_home_assistant_state(const char *entity_id, const char *attribute,
|
||||
std::function<void(const std::string &)> f);
|
||||
|
||||
// Existing std::string overload (for custom_api_device.h - heap allocation)
|
||||
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f);
|
||||
std::function<void(const std::string &)> f);
|
||||
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f);
|
||||
std::function<void(const std::string &)> f);
|
||||
|
||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||
#endif
|
||||
@@ -233,10 +238,10 @@ 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(const std::string &)> f,
|
||||
bool once);
|
||||
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;
|
||||
|
||||
@@ -122,7 +122,7 @@ class CustomAPIDevice {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string state) {
|
||||
* void on_state_changed(const std::string &state) {
|
||||
* // State of sensor.weather_forecast is `state`
|
||||
* }
|
||||
* ```
|
||||
@@ -133,7 +133,7 @@ class CustomAPIDevice {
|
||||
* @param attribute The entity state attribute to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||
void subscribe_homeassistant_state(void (T::*callback)(const 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);
|
||||
@@ -148,7 +148,7 @@ 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, const std::string &state) {
|
||||
* // State of `entity_id` is `state`
|
||||
* }
|
||||
* ```
|
||||
@@ -159,14 +159,14 @@ class CustomAPIDevice {
|
||||
* @param attribute The entity state attribute to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, const 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);
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||
void subscribe_homeassistant_state(void (T::*callback)(const std::string &), 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 "
|
||||
@@ -174,8 +174,8 @@ class CustomAPIDevice {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, const std::string &),
|
||||
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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -73,6 +73,9 @@ LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMedia
|
||||
LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel,
|
||||
ListEntitiesAlarmControlPanelResponse)
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWaterHeaterResponse)
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
|
||||
#endif
|
||||
|
||||
@@ -82,6 +82,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool on_water_heater(water_heater::WaterHeater *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *entity) override;
|
||||
#endif
|
||||
|
||||
@@ -334,7 +334,7 @@ class ProtoWriteBuffer {
|
||||
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
this->encode_uint64(field_id, encode_zigzag64(value), force);
|
||||
}
|
||||
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
|
||||
void encode_message(uint32_t field_id, const ProtoMessage &value);
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
protected:
|
||||
@@ -795,7 +795,7 @@ class ProtoSize {
|
||||
};
|
||||
|
||||
// Implementation of encode_message - must be after ProtoMessage is defined
|
||||
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
|
||||
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) {
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||
|
||||
// Calculate the message size first
|
||||
|
||||
@@ -60,6 +60,9 @@ INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer)
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel)
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
INITIAL_STATE_HANDLER(water_heater, water_heater::WaterHeater)
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
INITIAL_STATE_HANDLER(update, update::UpdateEntity)
|
||||
#endif
|
||||
|
||||
@@ -76,6 +76,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool on_water_heater(water_heater::WaterHeater *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override { return true; };
|
||||
#endif
|
||||
|
||||
14
esphome/components/aqi/__init__.py
Normal file
14
esphome/components/aqi/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@jasstrong", "@ximex", "@freekode"]
|
||||
|
||||
aqi_ns = cg.esphome_ns.namespace("aqi")
|
||||
AQICalculatorType = aqi_ns.enum("AQICalculatorType")
|
||||
|
||||
CONF_AQI = "aqi"
|
||||
CONF_CALCULATION_TYPE = "calculation_type"
|
||||
|
||||
AQI_CALCULATION_TYPE = {
|
||||
"CAQI": AQICalculatorType.CAQI_TYPE,
|
||||
"AQI": AQICalculatorType.AQI_TYPE,
|
||||
}
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace hm3301 {
|
||||
namespace esphome::aqi {
|
||||
|
||||
class AbstractAQICalculator {
|
||||
public:
|
||||
virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
|
||||
};
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
} // namespace esphome::aqi
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <climits>
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
|
||||
|
||||
namespace esphome {
|
||||
namespace hm3301 {
|
||||
namespace esphome::aqi {
|
||||
|
||||
class AQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
@@ -28,6 +29,9 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
|
||||
int calculate_index_(uint16_t value, int array[AMOUNT_OF_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 conc_lo = array[grid_index][0];
|
||||
@@ -46,5 +50,4 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
} // namespace esphome::aqi
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "caqi_calculator.h"
|
||||
#include "aqi_calculator.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hm3301 {
|
||||
namespace esphome::aqi {
|
||||
|
||||
enum AQICalculatorType { CAQI_TYPE = 0, AQI_TYPE = 1 };
|
||||
|
||||
@@ -12,18 +11,17 @@ class AQICalculatorFactory {
|
||||
public:
|
||||
AbstractAQICalculator *get_calculator(AQICalculatorType type) {
|
||||
if (type == 0) {
|
||||
return caqi_calculator_;
|
||||
return &this->caqi_calculator_;
|
||||
} else if (type == 1) {
|
||||
return aqi_calculator_;
|
||||
return &this->aqi_calculator_;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
CAQICalculator *caqi_calculator_ = new CAQICalculator();
|
||||
AQICalculator *aqi_calculator_ = new AQICalculator();
|
||||
CAQICalculator caqi_calculator_;
|
||||
AQICalculator aqi_calculator_;
|
||||
};
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
} // namespace esphome::aqi
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hm3301 {
|
||||
namespace esphome::aqi {
|
||||
|
||||
class CAQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
@@ -48,5 +47,4 @@ class CAQICalculator : public AbstractAQICalculator {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
} // namespace esphome::aqi
|
||||
@@ -1,12 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import ble_client, climate
|
||||
from esphome.components import climate
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_HEAT_MODE,
|
||||
CONF_RECEIVE_TIMEOUT,
|
||||
CONF_TEMPERATURE_SOURCE,
|
||||
CONF_TIME_ID,
|
||||
)
|
||||
from esphome.const import CONF_HEAT_MODE, CONF_TEMPERATURE_SOURCE
|
||||
|
||||
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
|
||||
|
||||
@@ -38,22 +33,6 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(
|
||||
# TODO: remove compat layer.
|
||||
{
|
||||
cv.Optional(ble_client.CONF_BLE_CLIENT_ID): cv.invalid(
|
||||
"The 'ble_client_id' option has been removed. Please migrate "
|
||||
"to the new `bedjet_id` option in the `bedjet` component.\n"
|
||||
"See https://esphome.io/components/climate/bedjet/"
|
||||
),
|
||||
cv.Optional(CONF_TIME_ID): cv.invalid(
|
||||
"The 'time_id' option has been moved to the `bedjet` component."
|
||||
),
|
||||
cv.Optional(CONF_RECEIVE_TIMEOUT): cv.invalid(
|
||||
"The 'receive_timeout' option has been moved to the `bedjet` component."
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(BEDJET_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
@@ -20,16 +20,6 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional("resolution"): cv.invalid(
|
||||
"The 'resolution' option has been removed. The optimal value is now dynamically calculated."
|
||||
),
|
||||
cv.Optional("measurement_duration"): cv.invalid(
|
||||
"The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated."
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x23))
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
|
||||
|
||||
AUTO_LOAD = ["bme68x_bsec2"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c")
|
||||
BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_(
|
||||
|
||||
@@ -41,7 +41,7 @@ class Button : public EntityBase, public EntityBase_DeviceClass {
|
||||
*/
|
||||
virtual void press_action() = 0;
|
||||
|
||||
CallbackManager<void()> press_callback_{};
|
||||
LazyCallbackManager<void()> press_callback_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::button
|
||||
|
||||
@@ -25,7 +25,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def AUTO_LOAD() -> list[str]:
|
||||
auto_load = ["web_server_base", "ota.web_server"]
|
||||
if CORE.using_esp_idf:
|
||||
if CORE.is_esp32:
|
||||
auto_load.append("socket")
|
||||
return auto_load
|
||||
|
||||
|
||||
@@ -65,12 +65,6 @@ void CaptivePortal::start() {
|
||||
this->base_->init();
|
||||
if (!this->initialized_) {
|
||||
this->base_->add_handler(this);
|
||||
#ifdef USE_ESP32
|
||||
// Enable LRU socket purging to handle captive portal detection probe bursts
|
||||
// OS captive portal detection makes many simultaneous HTTP requests which can
|
||||
// exhaust sockets. LRU purging automatically closes oldest idle connections.
|
||||
this->base_->get_server()->set_lru_purge_enable(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
|
||||
@@ -40,10 +40,6 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
||||
void end() {
|
||||
this->active_ = false;
|
||||
this->disable_loop(); // Stop processing DNS requests
|
||||
#ifdef USE_ESP32
|
||||
// Disable LRU socket purging now that captive portal is done
|
||||
this->base_->get_server()->set_lru_purge_enable(false);
|
||||
#endif
|
||||
this->base_->deinit();
|
||||
if (this->dns_server_ != nullptr) {
|
||||
this->dns_server_->stop();
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
from esphome import automation
|
||||
from esphome import automation, pins
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import spi
|
||||
from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME
|
||||
from esphome.const import (
|
||||
CONF_CHANNEL,
|
||||
CONF_DATA,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_WAIT_TIME,
|
||||
)
|
||||
from esphome.core import ID
|
||||
|
||||
CODEOWNERS = ["@lygris", "@gabest11"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
@@ -29,7 +37,6 @@ CONF_MANCHESTER = "manchester"
|
||||
CONF_NUM_PREAMBLE = "num_preamble"
|
||||
CONF_SYNC1 = "sync1"
|
||||
CONF_SYNC0 = "sync0"
|
||||
CONF_PKTLEN = "pktlen"
|
||||
CONF_MAGN_TARGET = "magn_target"
|
||||
CONF_MAX_LNA_GAIN = "max_lna_gain"
|
||||
CONF_MAX_DVGA_GAIN = "max_dvga_gain"
|
||||
@@ -41,6 +48,12 @@ CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook"
|
||||
CONF_FREEZE = "freeze"
|
||||
CONF_HYST_LEVEL = "hyst_level"
|
||||
|
||||
# Packet mode config keys
|
||||
CONF_PACKET_MODE = "packet_mode"
|
||||
CONF_PACKET_LENGTH = "packet_length"
|
||||
CONF_WHITENING = "whitening"
|
||||
CONF_GDO0_PIN = "gdo0_pin"
|
||||
|
||||
# Enums
|
||||
SyncMode = ns.enum("SyncMode", True)
|
||||
SYNC_MODE = {
|
||||
@@ -147,45 +160,89 @@ HYST_LEVEL = {
|
||||
"High": HystLevel.HYST_LEVEL_HIGH,
|
||||
}
|
||||
|
||||
# Config key -> Validator mapping
|
||||
# Optional settings to generate setter calls for
|
||||
CONFIG_MAP = {
|
||||
CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0),
|
||||
CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False),
|
||||
CONF_DC_BLOCKING_FILTER: cv.boolean,
|
||||
CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)),
|
||||
CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)),
|
||||
CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)),
|
||||
CONF_CHANNEL: cv.uint8_t,
|
||||
CONF_CHANNEL_SPACING: cv.All(cv.frequency, cv.float_range(min=25000, max=405000)),
|
||||
CONF_FSK_DEVIATION: cv.All(cv.frequency, cv.float_range(min=1500, max=381000)),
|
||||
CONF_MSK_DEVIATION: cv.int_range(min=1, max=8),
|
||||
CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000),
|
||||
CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False),
|
||||
CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean,
|
||||
CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False),
|
||||
CONF_MANCHESTER: cv.boolean,
|
||||
CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7),
|
||||
CONF_SYNC1: cv.hex_uint8_t,
|
||||
CONF_SYNC0: cv.hex_uint8_t,
|
||||
CONF_PKTLEN: cv.uint8_t,
|
||||
CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False),
|
||||
CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False),
|
||||
CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False),
|
||||
CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7),
|
||||
CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False),
|
||||
CONF_LNA_PRIORITY: cv.boolean,
|
||||
CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False),
|
||||
CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False),
|
||||
CONF_FREEZE: cv.enum(FREEZE, upper=False),
|
||||
CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False),
|
||||
CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False),
|
||||
cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0),
|
||||
cv.Optional(CONF_RX_ATTENUATION, default="0dB"): cv.enum(
|
||||
RX_ATTENUATION, upper=False
|
||||
),
|
||||
cv.Optional(CONF_DC_BLOCKING_FILTER, default=True): cv.boolean,
|
||||
cv.Optional(CONF_FREQUENCY, default="433.92MHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)
|
||||
),
|
||||
cv.Optional(CONF_IF_FREQUENCY, default="153kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=25000, max=788000)
|
||||
),
|
||||
cv.Optional(CONF_FILTER_BANDWIDTH, default="203kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=58000, max=812000)
|
||||
),
|
||||
cv.Optional(CONF_CHANNEL, default=0): cv.uint8_t,
|
||||
cv.Optional(CONF_CHANNEL_SPACING, default="200kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=25000, max=405000)
|
||||
),
|
||||
cv.Optional(CONF_FSK_DEVIATION): cv.All(
|
||||
cv.frequency, cv.float_range(min=1500, max=381000)
|
||||
),
|
||||
cv.Optional(CONF_MSK_DEVIATION): cv.int_range(min=1, max=8),
|
||||
cv.Optional(CONF_SYMBOL_RATE, default=5000): cv.float_range(min=600, max=500000),
|
||||
cv.Optional(CONF_SYNC_MODE, default="16/16"): cv.enum(SYNC_MODE, upper=False),
|
||||
cv.Optional(CONF_CARRIER_SENSE_ABOVE_THRESHOLD, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MODULATION_TYPE, default="ASK/OOK"): cv.enum(
|
||||
MODULATION, upper=False
|
||||
),
|
||||
cv.Optional(CONF_MANCHESTER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_NUM_PREAMBLE, default=2): cv.int_range(min=0, max=7),
|
||||
cv.Optional(CONF_SYNC1, default=0xD3): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_SYNC0, default=0x91): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_MAGN_TARGET, default="42dB"): cv.enum(MAGN_TARGET, upper=False),
|
||||
cv.Optional(CONF_MAX_LNA_GAIN, default="Default"): cv.enum(
|
||||
MAX_LNA_GAIN, upper=False
|
||||
),
|
||||
cv.Optional(CONF_MAX_DVGA_GAIN, default="-3"): cv.enum(MAX_DVGA_GAIN, upper=False),
|
||||
cv.Optional(CONF_CARRIER_SENSE_ABS_THR): cv.int_range(min=-8, max=7),
|
||||
cv.Optional(CONF_CARRIER_SENSE_REL_THR): cv.enum(
|
||||
CARRIER_SENSE_REL_THR, upper=False
|
||||
),
|
||||
cv.Optional(CONF_LNA_PRIORITY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_FILTER_LENGTH_FSK_MSK): cv.enum(
|
||||
FILTER_LENGTH_FSK_MSK, upper=False
|
||||
),
|
||||
cv.Optional(CONF_FILTER_LENGTH_ASK_OOK): cv.enum(
|
||||
FILTER_LENGTH_ASK_OOK, upper=False
|
||||
),
|
||||
cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False),
|
||||
cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False),
|
||||
cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False),
|
||||
cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t,
|
||||
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_WHITENING, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)})
|
||||
.extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()})
|
||||
|
||||
def _validate_packet_mode(config):
|
||||
if config.get(CONF_PACKET_MODE, False):
|
||||
if CONF_GDO0_PIN not in config:
|
||||
raise cv.Invalid("gdo0_pin is required when packet_mode is enabled")
|
||||
if CONF_PACKET_LENGTH not in config:
|
||||
raise cv.Invalid("packet_length is required when packet_mode is enabled")
|
||||
if config[CONF_PACKET_LENGTH] > 64:
|
||||
raise cv.Invalid("packet_length must be <= 64 (FIFO size)")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CC1101Component),
|
||||
cv.Optional(CONF_GDO0_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
|
||||
}
|
||||
)
|
||||
.extend(CONFIG_MAP)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(spi.spi_device_schema(cs_pin_required=True))
|
||||
.extend(spi.spi_device_schema(cs_pin_required=True)),
|
||||
_validate_packet_mode,
|
||||
)
|
||||
|
||||
|
||||
@@ -194,16 +251,34 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
for key in CONFIG_MAP:
|
||||
for opt in CONFIG_MAP:
|
||||
key = opt.schema
|
||||
if key in config:
|
||||
cg.add(getattr(var, f"set_{key}")(config[key]))
|
||||
|
||||
if CONF_GDO0_PIN in config:
|
||||
gdo0_pin = await cg.gpio_pin_expression(config[CONF_GDO0_PIN])
|
||||
cg.add(var.set_gdo0_pin(gdo0_pin))
|
||||
if CONF_ON_PACKET in config:
|
||||
await automation.build_automation(
|
||||
var.get_packet_trigger(),
|
||||
[
|
||||
(cg.std_vector.template(cg.uint8), "x"),
|
||||
(cg.float_, "rssi"),
|
||||
(cg.uint8, "lqi"),
|
||||
],
|
||||
config[CONF_ON_PACKET],
|
||||
)
|
||||
|
||||
|
||||
# Actions
|
||||
BeginTxAction = ns.class_("BeginTxAction", automation.Action)
|
||||
BeginRxAction = ns.class_("BeginRxAction", automation.Action)
|
||||
ResetAction = ns.class_("ResetAction", automation.Action)
|
||||
SetIdleAction = ns.class_("SetIdleAction", automation.Action)
|
||||
SendPacketAction = ns.class_(
|
||||
"SendPacketAction", automation.Action, cg.Parented.template(CC1101Component)
|
||||
)
|
||||
|
||||
CC1101_ACTION_SCHEMA = cv.Schema(
|
||||
maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)})
|
||||
@@ -218,3 +293,42 @@ async def cc1101_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
if isinstance(value, str):
|
||||
return value.encode("utf-8")
|
||||
if isinstance(value, list):
|
||||
return cv.Schema([cv.hex_uint8_t])(value)
|
||||
raise cv.Invalid(
|
||||
"data must either be a string wrapped in quotes or a list of bytes"
|
||||
)
|
||||
|
||||
|
||||
SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(CC1101Component),
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
},
|
||||
key=CONF_DATA,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"cc1101.send_packet", SendPacketAction, SEND_PACKET_ACTION_SCHEMA
|
||||
)
|
||||
async def send_packet_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
data = config[CONF_DATA]
|
||||
if isinstance(data, bytes):
|
||||
data = list(data)
|
||||
if cg.is_template(data):
|
||||
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_data_template(templ))
|
||||
else:
|
||||
# Generate static array in flash to avoid RAM copy
|
||||
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
|
||||
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
|
||||
cg.add(var.set_data_static(arr, len(data)))
|
||||
return var
|
||||
|
||||
@@ -98,25 +98,8 @@ CC1101Component::CC1101Component() {
|
||||
this->state_.LENGTH_CONFIG = 2;
|
||||
this->state_.FS_AUTOCAL = 1;
|
||||
|
||||
// Default Settings
|
||||
this->set_frequency(433920);
|
||||
this->set_if_frequency(153);
|
||||
this->set_filter_bandwidth(203);
|
||||
this->set_channel(0);
|
||||
this->set_channel_spacing(200);
|
||||
this->set_symbol_rate(5000);
|
||||
this->set_sync_mode(SyncMode::SYNC_MODE_NONE);
|
||||
this->set_carrier_sense_above_threshold(true);
|
||||
this->set_modulation_type(Modulation::MODULATION_ASK_OOK);
|
||||
this->set_magn_target(MagnTarget::MAGN_TARGET_42DB);
|
||||
this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT);
|
||||
this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3);
|
||||
this->set_lna_priority(false);
|
||||
this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES);
|
||||
|
||||
// CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence)
|
||||
memset(this->pa_table_, 0, sizeof(this->pa_table_));
|
||||
this->set_output_power(10.0f);
|
||||
}
|
||||
|
||||
void CC1101Component::setup() {
|
||||
@@ -143,6 +126,11 @@ void CC1101Component::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup GDO0 pin if configured
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->gdo0_pin_->setup();
|
||||
}
|
||||
|
||||
this->initialized_ = true;
|
||||
|
||||
for (uint8_t i = 0; i <= static_cast<uint8_t>(Register::TEST0); i++) {
|
||||
@@ -151,8 +139,70 @@ void CC1101Component::setup() {
|
||||
}
|
||||
this->write_(static_cast<Register>(i));
|
||||
}
|
||||
this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_));
|
||||
this->set_output_power(this->output_power_requested_);
|
||||
this->strobe_(Command::RX);
|
||||
|
||||
// Defer pin mode setup until after all components have completed setup()
|
||||
// This handles the case where remote_transmitter runs after CC1101 and changes pin mode
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->defer([this]() { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); });
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::loop() {
|
||||
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO) || this->gdo0_pin_ == nullptr ||
|
||||
!this->gdo0_pin_->digital_read()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read state
|
||||
this->read_(Register::RXBYTES);
|
||||
uint8_t rx_bytes = this->state_.NUM_RXBYTES;
|
||||
bool overflow = this->state_.RXFIFO_OVERFLOW;
|
||||
if (overflow || rx_bytes == 0) {
|
||||
ESP_LOGW(TAG, "RX FIFO overflow, flushing");
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FRX);
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read packet
|
||||
uint8_t payload_length, expected_rx;
|
||||
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
|
||||
this->read_(Register::FIFO, &payload_length, 1);
|
||||
expected_rx = payload_length + 1;
|
||||
} else {
|
||||
payload_length = this->state_.PKTLEN;
|
||||
expected_rx = payload_length;
|
||||
}
|
||||
if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) {
|
||||
ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length);
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FRX);
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
return;
|
||||
}
|
||||
this->packet_.resize(payload_length);
|
||||
this->read_(Register::FIFO, this->packet_.data(), payload_length);
|
||||
|
||||
// Read status from registers (more reliable than FIFO status bytes due to timing issues)
|
||||
this->read_(Register::RSSI);
|
||||
this->read_(Register::LQI);
|
||||
float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET;
|
||||
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
|
||||
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
|
||||
if (this->state_.CRC_EN == 0 || crc_ok) {
|
||||
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
|
||||
}
|
||||
|
||||
// Return to rx
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FRX);
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
}
|
||||
|
||||
void CC1101Component::dump_config() {
|
||||
@@ -177,9 +227,12 @@ void CC1101Component::dump_config() {
|
||||
}
|
||||
|
||||
void CC1101Component::begin_tx() {
|
||||
// Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX
|
||||
// Ensure Packet Format is 3 (Async Serial)
|
||||
this->write_(Register::PKTCTRL0, 0x32);
|
||||
ESP_LOGV(TAG, "Beginning TX sequence");
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
}
|
||||
this->strobe_(Command::TX);
|
||||
if (!this->wait_for_state_(State::TX, 50)) {
|
||||
ESP_LOGW(TAG, "Timed out waiting for TX state!");
|
||||
@@ -188,6 +241,9 @@ void CC1101Component::begin_tx() {
|
||||
|
||||
void CC1101Component::begin_rx() {
|
||||
ESP_LOGV(TAG, "Beginning RX sequence");
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
}
|
||||
this->strobe_(Command::RX);
|
||||
}
|
||||
|
||||
@@ -201,20 +257,6 @@ void CC1101Component::set_idle() {
|
||||
this->enter_idle_();
|
||||
}
|
||||
|
||||
void CC1101Component::set_gdo0_config(uint8_t value) {
|
||||
this->state_.GDO0_CFG = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::IOCFG0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_gdo2_config(uint8_t value) {
|
||||
this->state_.GDO2_CFG = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::IOCFG2);
|
||||
}
|
||||
}
|
||||
|
||||
bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) {
|
||||
uint32_t start = millis();
|
||||
while (millis() - start < timeout_ms) {
|
||||
@@ -282,6 +324,33 @@ void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) {
|
||||
this->disable();
|
||||
}
|
||||
|
||||
CC1101Error CC1101Component::transmit_packet(const std::vector<uint8_t> &packet) {
|
||||
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO)) {
|
||||
return CC1101Error::PARAMS;
|
||||
}
|
||||
|
||||
// Write packet
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FTX);
|
||||
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
|
||||
this->write_(Register::FIFO, static_cast<uint8_t>(packet.size()));
|
||||
}
|
||||
this->write_(Register::FIFO, packet.data(), packet.size());
|
||||
this->strobe_(Command::TX);
|
||||
if (!this->wait_for_state_(State::IDLE, 1000)) {
|
||||
ESP_LOGW(TAG, "TX timeout");
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
return CC1101Error::TIMEOUT;
|
||||
}
|
||||
|
||||
// Return to rx
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
return CC1101Error::NONE;
|
||||
}
|
||||
|
||||
// Setters
|
||||
void CC1101Component::set_output_power(float value) {
|
||||
this->output_power_requested_ = value;
|
||||
@@ -428,6 +497,7 @@ void CC1101Component::set_modulation_type(Modulation value) {
|
||||
this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0;
|
||||
if (this->initialized_) {
|
||||
this->enter_idle_();
|
||||
this->set_output_power(this->output_power_requested_);
|
||||
this->write_(Register::MDMCFG2);
|
||||
this->write_(Register::FREND0);
|
||||
this->strobe_(Command::RX);
|
||||
@@ -462,13 +532,6 @@ void CC1101Component::set_sync0(uint8_t value) {
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_pktlen(uint8_t value) {
|
||||
this->state_.PKTLEN = value;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTLEN);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_magn_target(MagnTarget value) {
|
||||
this->state_.MAGN_TARGET = static_cast<uint8_t>(value);
|
||||
if (this->initialized_) {
|
||||
@@ -546,4 +609,53 @@ void CC1101Component::set_hyst_level(HystLevel value) {
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_packet_mode(bool value) {
|
||||
this->state_.PKT_FORMAT =
|
||||
static_cast<uint8_t>(value ? PacketFormat::PACKET_FORMAT_FIFO : PacketFormat::PACKET_FORMAT_ASYNC_SERIAL);
|
||||
if (value) {
|
||||
// Configure GDO0 for FIFO status (asserts on RX FIFO threshold or end of packet)
|
||||
this->state_.GDO0_CFG = 0x01;
|
||||
// Set max RX FIFO threshold to ensure we only trigger on end-of-packet
|
||||
this->state_.FIFO_THR = 15;
|
||||
// Don't append status bytes to FIFO - we read from registers instead
|
||||
this->state_.APPEND_STATUS = 0;
|
||||
} else {
|
||||
// Configure GDO0 for serial data (async serial mode)
|
||||
this->state_.GDO0_CFG = 0x0D;
|
||||
}
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTCTRL0);
|
||||
this->write_(Register::PKTCTRL1);
|
||||
this->write_(Register::IOCFG0);
|
||||
this->write_(Register::FIFOTHR);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_packet_length(uint8_t value) {
|
||||
if (value == 0) {
|
||||
this->state_.LENGTH_CONFIG = static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE);
|
||||
} else {
|
||||
this->state_.LENGTH_CONFIG = static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_FIXED);
|
||||
this->state_.PKTLEN = value;
|
||||
}
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTCTRL0);
|
||||
this->write_(Register::PKTLEN);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_crc_enable(bool value) {
|
||||
this->state_.CRC_EN = value ? 1 : 0;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTCTRL0);
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::set_whitening(bool value) {
|
||||
this->state_.WHITE_DATA = value ? 1 : 0;
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTCTRL0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::cc1101
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "cc1101defs.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::cc1101 {
|
||||
|
||||
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW };
|
||||
|
||||
class CC1101Component : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
||||
@@ -15,6 +18,7 @@ class CC1101Component : public Component,
|
||||
CC1101Component();
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
// Actions
|
||||
@@ -24,8 +28,7 @@ class CC1101Component : public Component,
|
||||
void set_idle();
|
||||
|
||||
// GDO Pin Configuration
|
||||
void set_gdo0_config(uint8_t value);
|
||||
void set_gdo2_config(uint8_t value);
|
||||
void set_gdo0_pin(InternalGPIOPin *pin) { this->gdo0_pin_ = pin; }
|
||||
|
||||
// Configuration Setters
|
||||
void set_output_power(float value);
|
||||
@@ -48,7 +51,6 @@ class CC1101Component : public Component,
|
||||
void set_num_preamble(uint8_t value);
|
||||
void set_sync1(uint8_t value);
|
||||
void set_sync0(uint8_t value);
|
||||
void set_pktlen(uint8_t value);
|
||||
|
||||
// AGC settings
|
||||
void set_magn_target(MagnTarget value);
|
||||
@@ -63,6 +65,16 @@ class CC1101Component : public Component,
|
||||
void set_wait_time(WaitTime value);
|
||||
void set_hyst_level(HystLevel value);
|
||||
|
||||
// Packet mode settings
|
||||
void set_packet_mode(bool value);
|
||||
void set_packet_length(uint8_t value);
|
||||
void set_crc_enable(bool value);
|
||||
void set_whitening(bool value);
|
||||
|
||||
// Packet mode operations
|
||||
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
|
||||
Trigger<std::vector<uint8_t>, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
|
||||
|
||||
protected:
|
||||
uint16_t chip_id_{0};
|
||||
bool initialized_{false};
|
||||
@@ -73,6 +85,13 @@ class CC1101Component : public Component,
|
||||
|
||||
CC1101State state_;
|
||||
|
||||
// GDO pin for packet reception
|
||||
InternalGPIOPin *gdo0_pin_{nullptr};
|
||||
|
||||
// Packet handling
|
||||
Trigger<std::vector<uint8_t>, float, uint8_t> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, uint8_t>()};
|
||||
std::vector<uint8_t> packet_;
|
||||
|
||||
// Low-level Helpers
|
||||
uint8_t strobe_(Command cmd);
|
||||
void write_(Register reg);
|
||||
@@ -107,4 +126,28 @@ template<typename... Ts> class SetIdleAction : public Action<Ts...>, public Pare
|
||||
void play(const Ts &...x) override { this->parent_->set_idle(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SendPacketAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) { this->data_func_ = func; }
|
||||
void set_data_static(const uint8_t *data, size_t len) {
|
||||
this->data_static_ = data;
|
||||
this->data_static_len_ = len;
|
||||
}
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
if (this->data_func_) {
|
||||
auto data = this->data_func_(x...);
|
||||
this->parent_->transmit_packet(data);
|
||||
} else if (this->data_static_ != nullptr) {
|
||||
std::vector<uint8_t> data(this->data_static_, this->data_static_ + this->data_static_len_);
|
||||
this->parent_->transmit_packet(data);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
const uint8_t *data_static_{nullptr};
|
||||
size_t data_static_len_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::cc1101
|
||||
|
||||
@@ -6,6 +6,12 @@ namespace esphome::cc1101 {
|
||||
|
||||
static constexpr float XTAL_FREQUENCY = 26000000;
|
||||
|
||||
static constexpr float RSSI_OFFSET = 74.0f;
|
||||
static constexpr float RSSI_STEP = 0.5f;
|
||||
|
||||
static constexpr uint8_t STATUS_CRC_OK_MASK = 0x80;
|
||||
static constexpr uint8_t STATUS_LQI_MASK = 0x7F;
|
||||
|
||||
static constexpr uint8_t BUS_BURST = 0x40;
|
||||
static constexpr uint8_t BUS_READ = 0x80;
|
||||
static constexpr uint8_t BUS_WRITE = 0x00;
|
||||
@@ -134,6 +140,10 @@ enum class SyncMode : uint8_t {
|
||||
SYNC_MODE_15_16,
|
||||
SYNC_MODE_16_16,
|
||||
SYNC_MODE_30_32,
|
||||
SYNC_MODE_NONE_CS,
|
||||
SYNC_MODE_15_16_CS,
|
||||
SYNC_MODE_16_16_CS,
|
||||
SYNC_MODE_30_32_CS,
|
||||
};
|
||||
|
||||
enum class Modulation : uint8_t {
|
||||
@@ -218,6 +228,19 @@ enum class HystLevel : uint8_t {
|
||||
HYST_LEVEL_HIGH,
|
||||
};
|
||||
|
||||
enum class PacketFormat : uint8_t {
|
||||
PACKET_FORMAT_FIFO,
|
||||
PACKET_FORMAT_SYNC_SERIAL,
|
||||
PACKET_FORMAT_RANDOM_TX,
|
||||
PACKET_FORMAT_ASYNC_SERIAL,
|
||||
};
|
||||
|
||||
enum class LengthConfig : uint8_t {
|
||||
LENGTH_CONFIG_FIXED,
|
||||
LENGTH_CONFIG_VARIABLE,
|
||||
LENGTH_CONFIG_INFINITE,
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) CC1101State {
|
||||
// Byte array accessors for bulk SPI transfers
|
||||
uint8_t *regs() { return reinterpret_cast<uint8_t *>(this); }
|
||||
|
||||
@@ -117,9 +117,7 @@ CONF_MIN_HUMIDITY = "min_humidity"
|
||||
CONF_MAX_HUMIDITY = "max_humidity"
|
||||
CONF_TARGET_HUMIDITY = "target_humidity"
|
||||
|
||||
visual_temperature = cv.float_with_unit(
|
||||
"visual_temperature", "(°C|° C|°|C|°K|° K|K|°F|° F|F)?"
|
||||
)
|
||||
visual_temperature = cv.float_with_unit("visual_temperature", "(°|(° ?)?[CKF])?")
|
||||
|
||||
|
||||
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include <strings.h>
|
||||
|
||||
namespace esphome::climate {
|
||||
|
||||
@@ -190,24 +191,30 @@ ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) {
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) {
|
||||
return this->set_fan_mode(custom_fan_mode, strlen(custom_fan_mode));
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||
return this->set_fan_mode(fan_mode.data(), fan_mode.size());
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode, size_t len) {
|
||||
// Check if it's a standard enum mode first
|
||||
for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) {
|
||||
if (str_equals_case_insensitive(custom_fan_mode, mode_entry.str)) {
|
||||
if (strncasecmp(custom_fan_mode, mode_entry.str, len) == 0 && mode_entry.str[len] == '\0') {
|
||||
return this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value));
|
||||
}
|
||||
}
|
||||
// Find the matching pointer from parent climate device
|
||||
if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode)) {
|
||||
if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode, len)) {
|
||||
this->custom_fan_mode_ = mode_ptr;
|
||||
this->fan_mode_.reset();
|
||||
return *this;
|
||||
}
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), custom_fan_mode);
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %.*s", this->parent_->get_name().c_str(), (int) len, custom_fan_mode);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { return this->set_fan_mode(fan_mode.c_str()); }
|
||||
|
||||
ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
|
||||
if (fan_mode.has_value()) {
|
||||
this->set_fan_mode(fan_mode.value());
|
||||
@@ -222,24 +229,30 @@ ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_preset(const char *custom_preset) {
|
||||
return this->set_preset(custom_preset, strlen(custom_preset));
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_preset(const std::string &preset) {
|
||||
return this->set_preset(preset.data(), preset.size());
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_preset(const char *custom_preset, size_t len) {
|
||||
// Check if it's a standard enum preset first
|
||||
for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) {
|
||||
if (str_equals_case_insensitive(custom_preset, preset_entry.str)) {
|
||||
if (strncasecmp(custom_preset, preset_entry.str, len) == 0 && preset_entry.str[len] == '\0') {
|
||||
return this->set_preset(static_cast<ClimatePreset>(preset_entry.value));
|
||||
}
|
||||
}
|
||||
// Find the matching pointer from parent climate device
|
||||
if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset)) {
|
||||
if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset, len)) {
|
||||
this->custom_preset_ = preset_ptr;
|
||||
this->preset_.reset();
|
||||
return *this;
|
||||
}
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), custom_preset);
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized preset %.*s", this->parent_->get_name().c_str(), (int) len, custom_preset);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ClimateCall &ClimateCall::set_preset(const std::string &preset) { return this->set_preset(preset.c_str()); }
|
||||
|
||||
ClimateCall &ClimateCall::set_preset(optional<std::string> preset) {
|
||||
if (preset.has_value()) {
|
||||
this->set_preset(preset.value());
|
||||
@@ -688,11 +701,19 @@ bool Climate::set_custom_preset_(const char *preset) {
|
||||
void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; }
|
||||
|
||||
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) {
|
||||
return this->get_traits().find_custom_fan_mode_(custom_fan_mode);
|
||||
return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) {
|
||||
return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len);
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_preset_(const char *custom_preset) {
|
||||
return this->get_traits().find_custom_preset_(custom_preset);
|
||||
return this->find_custom_preset_(custom_preset, strlen(custom_preset));
|
||||
}
|
||||
|
||||
const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) {
|
||||
return this->get_traits().find_custom_preset_(custom_preset, len);
|
||||
}
|
||||
|
||||
void Climate::dump_traits_(const char *tag) {
|
||||
|
||||
@@ -78,6 +78,8 @@ class ClimateCall {
|
||||
ClimateCall &set_fan_mode(optional<std::string> fan_mode);
|
||||
/// Set the custom fan mode of the climate device.
|
||||
ClimateCall &set_fan_mode(const char *custom_fan_mode);
|
||||
/// Set the custom fan mode of the climate device (zero-copy API path).
|
||||
ClimateCall &set_fan_mode(const char *custom_fan_mode, size_t len);
|
||||
/// Set the swing mode of the climate device.
|
||||
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
|
||||
/// Set the swing mode of the climate device.
|
||||
@@ -94,6 +96,8 @@ class ClimateCall {
|
||||
ClimateCall &set_preset(optional<std::string> preset);
|
||||
/// Set the custom preset of the climate device.
|
||||
ClimateCall &set_preset(const char *custom_preset);
|
||||
/// Set the custom preset of the climate device (zero-copy API path).
|
||||
ClimateCall &set_preset(const char *custom_preset, size_t len);
|
||||
|
||||
void perform();
|
||||
|
||||
@@ -290,9 +294,11 @@ class Climate : public EntityBase {
|
||||
|
||||
/// Find and return the matching custom fan mode pointer from traits, or nullptr if not found.
|
||||
const char *find_custom_fan_mode_(const char *custom_fan_mode);
|
||||
const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len);
|
||||
|
||||
/// Find and return the matching custom preset pointer from traits, or nullptr if not found.
|
||||
const char *find_custom_preset_(const char *custom_preset);
|
||||
const char *find_custom_preset_(const char *custom_preset, size_t len);
|
||||
|
||||
/** Get the default traits of this climate device.
|
||||
*
|
||||
@@ -320,8 +326,8 @@ class Climate : public EntityBase {
|
||||
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
CallbackManager<void(Climate &)> state_callback_{};
|
||||
CallbackManager<void(ClimateCall &)> control_callback_{};
|
||||
LazyCallbackManager<void(Climate &)> state_callback_{};
|
||||
LazyCallbackManager<void(ClimateCall &)> control_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
|
||||
float visual_min_temperature_override_{NAN};
|
||||
|
||||
@@ -20,18 +20,22 @@ using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimateP
|
||||
|
||||
// Lightweight linear search for small vectors (1-20 items) of const char* pointers
|
||||
// Avoids std::find template overhead
|
||||
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) {
|
||||
inline bool vector_contains(const std::vector<const char *> &vec, const char *value, size_t len) {
|
||||
for (const char *item : vec) {
|
||||
if (strcmp(item, value) == 0)
|
||||
if (strncmp(item, value, len) == 0 && item[len] == '\0')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) {
|
||||
return vector_contains(vec, value, strlen(value));
|
||||
}
|
||||
|
||||
// Find and return matching pointer from vector, or nullptr if not found
|
||||
inline const char *vector_find(const std::vector<const char *> &vec, const char *value) {
|
||||
inline const char *vector_find(const std::vector<const char *> &vec, const char *value, size_t len) {
|
||||
for (const char *item : vec) {
|
||||
if (strcmp(item, value) == 0)
|
||||
if (strncmp(item, value, len) == 0 && item[len] == '\0')
|
||||
return item;
|
||||
}
|
||||
return nullptr;
|
||||
@@ -257,13 +261,19 @@ class ClimateTraits {
|
||||
/// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found
|
||||
/// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead
|
||||
const char *find_custom_fan_mode_(const char *custom_fan_mode) const {
|
||||
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode);
|
||||
return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
|
||||
}
|
||||
const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const {
|
||||
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len);
|
||||
}
|
||||
|
||||
/// Find and return the matching custom preset pointer from supported presets, or nullptr if not found
|
||||
/// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead
|
||||
const char *find_custom_preset_(const char *custom_preset) const {
|
||||
return vector_find(this->supported_custom_presets_, custom_preset);
|
||||
return this->find_custom_preset_(custom_preset, strlen(custom_preset));
|
||||
}
|
||||
const char *find_custom_preset_(const char *custom_preset, size_t len) const {
|
||||
return vector_find(this->supported_custom_presets_, custom_preset, len);
|
||||
}
|
||||
|
||||
uint32_t feature_flags_{0};
|
||||
|
||||
@@ -7,9 +7,11 @@ BYTE_ORDER_LITTLE = "little_endian"
|
||||
BYTE_ORDER_BIG = "big_endian"
|
||||
|
||||
CONF_COLOR_DEPTH = "color_depth"
|
||||
CONF_CRC_ENABLE = "crc_enable"
|
||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
||||
CONF_ON_PACKET = "on_packet"
|
||||
CONF_ON_RECEIVE = "on_receive"
|
||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||
CONF_REQUEST_HEADERS = "request_headers"
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace copy {
|
||||
static const char *const TAG = "copy.select";
|
||||
|
||||
void CopySelect::setup() {
|
||||
source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); });
|
||||
source_->add_on_state_callback([this](size_t index) { this->publish_state(index); });
|
||||
|
||||
traits.set_options(source_->traits.get_options());
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
|
||||
|
||||
optional<CoverRestoreState> restore_state_();
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ class DateTimeBase : public EntityBase {
|
||||
#endif
|
||||
|
||||
protected:
|
||||
CallbackManager<void()> state_callback_;
|
||||
LazyCallbackManager<void()> state_callback_;
|
||||
|
||||
#ifdef USE_TIME
|
||||
time::RealTimeClock *rtc_;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from esphome import automation, pins
|
||||
from esphome import automation, core, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, time
|
||||
from esphome.components.esp32 import (
|
||||
@@ -23,16 +23,20 @@ from esphome.const import (
|
||||
CONF_MINUTE,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_PIN,
|
||||
CONF_PINS,
|
||||
CONF_RUN_DURATION,
|
||||
CONF_SECOND,
|
||||
CONF_SLEEP_DURATION,
|
||||
CONF_TIME_ID,
|
||||
CONF_WAKEUP_PIN,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
WAKEUP_PINS = {
|
||||
VARIANT_ESP32: [
|
||||
@@ -113,7 +117,7 @@ WAKEUP_PINS = {
|
||||
}
|
||||
|
||||
|
||||
def validate_pin_number(value):
|
||||
def validate_pin_number_esp32(value: ConfigType) -> ConfigType:
|
||||
valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32])
|
||||
if value[CONF_NUMBER] not in valid_pins:
|
||||
raise cv.Invalid(
|
||||
@@ -122,6 +126,51 @@ def validate_pin_number(value):
|
||||
return value
|
||||
|
||||
|
||||
def validate_pin_number(value: ConfigType) -> ConfigType:
|
||||
if not CORE.is_esp32:
|
||||
return value
|
||||
return validate_pin_number_esp32(value)
|
||||
|
||||
|
||||
def validate_wakeup_pin(
|
||||
value: ConfigType | list[ConfigType],
|
||||
) -> list[ConfigType]:
|
||||
if not isinstance(value, list):
|
||||
processed_pins: list[ConfigType] = [{CONF_PIN: value}]
|
||||
else:
|
||||
processed_pins = list(value)
|
||||
|
||||
for i, pin_config in enumerate(processed_pins):
|
||||
# now validate each item
|
||||
validated_pin = WAKEUP_PIN_SCHEMA(pin_config)
|
||||
validate_pin_number(validated_pin[CONF_PIN])
|
||||
processed_pins[i] = validated_pin
|
||||
|
||||
return processed_pins
|
||||
|
||||
|
||||
def validate_config(config: ConfigType) -> ConfigType:
|
||||
# right now only BK72XX supports the list format for wakeup pins
|
||||
if CORE.is_bk72xx:
|
||||
if CONF_WAKEUP_PIN_MODE in config:
|
||||
wakeup_pins = config.get(CONF_WAKEUP_PIN, [])
|
||||
if len(wakeup_pins) > 1:
|
||||
raise cv.Invalid(
|
||||
"You need to remove the global wakeup_pin_mode and define it per pin"
|
||||
)
|
||||
if wakeup_pins:
|
||||
wakeup_pins[0][CONF_WAKEUP_PIN_MODE] = config.pop(CONF_WAKEUP_PIN_MODE)
|
||||
elif (
|
||||
isinstance(config.get(CONF_WAKEUP_PIN), list)
|
||||
and len(config[CONF_WAKEUP_PIN]) > 1
|
||||
):
|
||||
raise cv.Invalid(
|
||||
"Your platform does not support providing multiple entries in wakeup_pin"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def _validate_ex1_wakeup_mode(value):
|
||||
if value == "ALL_LOW":
|
||||
esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value)
|
||||
@@ -141,6 +190,15 @@ def _validate_ex1_wakeup_mode(value):
|
||||
return value
|
||||
|
||||
|
||||
def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
|
||||
if not CORE.is_bk72xx:
|
||||
return value
|
||||
max_duration = core.TimePeriod(hours=36)
|
||||
if value > max_duration:
|
||||
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
|
||||
return value
|
||||
|
||||
|
||||
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
|
||||
DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component)
|
||||
EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action)
|
||||
@@ -186,6 +244,13 @@ WAKEUP_CAUSES_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
WAKEUP_PIN_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.enum(WAKEUP_PIN_MODES, upper=True),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -194,14 +259,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA),
|
||||
cv.positive_time_period_milliseconds,
|
||||
),
|
||||
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_WAKEUP_PIN): cv.All(
|
||||
cv.only_on_esp32,
|
||||
pins.internal_gpio_input_pin_schema,
|
||||
validate_pin_number,
|
||||
cv.Optional(CONF_SLEEP_DURATION): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
_validate_sleep_duration,
|
||||
),
|
||||
cv.Optional(CONF_WAKEUP_PIN): validate_wakeup_pin,
|
||||
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All(
|
||||
cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]),
|
||||
cv.enum(WAKEUP_PIN_MODES),
|
||||
upper=True,
|
||||
),
|
||||
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
|
||||
cv.only_on_esp32,
|
||||
@@ -212,7 +278,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PINS): cv.ensure_list(
|
||||
pins.internal_gpio_input_pin_schema, validate_pin_number
|
||||
pins.internal_gpio_input_pin_schema,
|
||||
validate_pin_number_esp32,
|
||||
),
|
||||
cv.Required(CONF_MODE): cv.All(
|
||||
cv.enum(EXT1_WAKEUP_MODES, upper=True),
|
||||
@@ -238,7 +305,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
|
||||
validate_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -249,8 +317,21 @@ async def to_code(config):
|
||||
if CONF_SLEEP_DURATION in config:
|
||||
cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION]))
|
||||
if CONF_WAKEUP_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_WAKEUP_PIN])
|
||||
cg.add(var.set_wakeup_pin(pin))
|
||||
pins_as_list = config.get(CONF_WAKEUP_PIN, [])
|
||||
if CORE.is_bk72xx:
|
||||
cg.add(var.init_wakeup_pins_(len(pins_as_list)))
|
||||
for item in pins_as_list:
|
||||
cg.add(
|
||||
var.add_wakeup_pin(
|
||||
await cg.gpio_pin_expression(item[CONF_PIN]),
|
||||
item.get(
|
||||
CONF_WAKEUP_PIN_MODE, WakeupPinMode.WAKEUP_PIN_MODE_IGNORE
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
pin = await cg.gpio_pin_expression(pins_as_list[0][CONF_PIN])
|
||||
cg.add(var.set_wakeup_pin(pin))
|
||||
if CONF_WAKEUP_PIN_MODE in config:
|
||||
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
|
||||
if CONF_RUN_DURATION in config:
|
||||
@@ -305,7 +386,10 @@ DEEP_SLEEP_ENTER_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
_validate_sleep_duration,
|
||||
)
|
||||
),
|
||||
# Only on ESP32 due to how long the RTC on ESP8266 can stay asleep
|
||||
cv.Exclusive(CONF_UNTIL, "time"): cv.All(
|
||||
@@ -363,5 +447,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"deep_sleep_bk72xx.cpp": {PlatformFramework.BK72XX_ARDUINO},
|
||||
}
|
||||
)
|
||||
|
||||
64
esphome/components/deep_sleep/deep_sleep_bk72xx.cpp
Normal file
64
esphome/components/deep_sleep/deep_sleep_bk72xx.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifdef USE_BK72XX
|
||||
|
||||
#include "deep_sleep_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep.bk72xx";
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
|
||||
|
||||
void DeepSleepComponent::dump_config_platform_() {
|
||||
for (const WakeUpPinItem &item : this->wakeup_pins_) {
|
||||
LOG_PIN(" Wakeup Pin: ", item.wakeup_pin);
|
||||
}
|
||||
}
|
||||
|
||||
bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pinItem) const {
|
||||
return (pinItem.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pinItem.wakeup_pin != nullptr &&
|
||||
!this->sleep_duration_.has_value() && (pinItem.wakeup_level == get_real_pin_state_(*pinItem.wakeup_pin)));
|
||||
}
|
||||
|
||||
bool DeepSleepComponent::prepare_to_sleep_() {
|
||||
if (wakeup_pins_.size() > 0) {
|
||||
for (WakeUpPinItem &item : this->wakeup_pins_) {
|
||||
if (pin_prevents_sleep_(item)) {
|
||||
// Defer deep sleep until inactive
|
||||
if (!this->next_enter_deep_sleep_) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGV(TAG, "Waiting for pin to switch state to enter deep sleep...");
|
||||
}
|
||||
this->next_enter_deep_sleep_ = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeepSleepComponent::deep_sleep_() {
|
||||
for (WakeUpPinItem &item : this->wakeup_pins_) {
|
||||
if (item.wakeup_pin_mode == WAKEUP_PIN_MODE_INVERT_WAKEUP) {
|
||||
if (item.wakeup_level == get_real_pin_state_(*item.wakeup_pin)) {
|
||||
item.wakeup_level = !item.wakeup_level;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Wake-up on P%u %s (%d)", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
|
||||
static_cast<int32_t>(item.wakeup_pin_mode));
|
||||
}
|
||||
|
||||
if (this->sleep_duration_.has_value())
|
||||
lt_deep_sleep_config_timer((*this->sleep_duration_ / 1000) & 0xFFFFFFFF);
|
||||
|
||||
for (WakeUpPinItem &item : this->wakeup_pins_) {
|
||||
lt_deep_sleep_config_gpio(1 << item.wakeup_pin->get_pin(), item.wakeup_level);
|
||||
lt_deep_sleep_keep_floating_gpio(1 << item.wakeup_pin->get_pin(), true);
|
||||
}
|
||||
|
||||
lt_deep_sleep_enter();
|
||||
}
|
||||
|
||||
} // namespace esphome::deep_sleep
|
||||
|
||||
#endif // USE_BK72XX
|
||||
@@ -19,7 +19,7 @@
|
||||
namespace esphome {
|
||||
namespace deep_sleep {
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_BK72XX)
|
||||
|
||||
/** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32
|
||||
* and the scenario occurs that the wakeup pin is already in the wakeup state.
|
||||
@@ -33,7 +33,17 @@ enum WakeupPinMode {
|
||||
*/
|
||||
WAKEUP_PIN_MODE_INVERT_WAKEUP,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(USE_BK72XX)
|
||||
struct WakeUpPinItem {
|
||||
InternalGPIOPin *wakeup_pin;
|
||||
WakeupPinMode wakeup_pin_mode;
|
||||
bool wakeup_level;
|
||||
};
|
||||
#endif // USE_BK72XX
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
struct Ext1Wakeup {
|
||||
uint64_t mask;
|
||||
@@ -75,6 +85,13 @@ class DeepSleepComponent : public Component {
|
||||
void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
|
||||
#endif // USE_ESP32
|
||||
|
||||
#if defined(USE_BK72XX)
|
||||
void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); }
|
||||
void add_wakeup_pin(InternalGPIOPin *wakeup_pin, WakeupPinMode wakeup_pin_mode) {
|
||||
this->wakeup_pins_.emplace_back(WakeUpPinItem{wakeup_pin, wakeup_pin_mode, !wakeup_pin->is_inverted()});
|
||||
}
|
||||
#endif // USE_BK72XX
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
|
||||
@@ -114,7 +131,17 @@ class DeepSleepComponent : public Component {
|
||||
bool prepare_to_sleep_();
|
||||
void deep_sleep_();
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const;
|
||||
bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); }
|
||||
#endif // USE_BK72XX
|
||||
|
||||
optional<uint64_t> sleep_duration_;
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
FixedVector<WakeUpPinItem> wakeup_pins_;
|
||||
#endif // USE_BK72XX
|
||||
|
||||
#ifdef USE_ESP32
|
||||
InternalGPIOPin *wakeup_pin_;
|
||||
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
||||
@@ -124,8 +151,10 @@ class DeepSleepComponent : public Component {
|
||||
#endif
|
||||
|
||||
optional<bool> touch_wakeup_;
|
||||
|
||||
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
|
||||
#endif // USE_ESP32
|
||||
|
||||
optional<uint32_t> run_duration_;
|
||||
bool next_enter_deep_sleep_{false};
|
||||
bool prevent_{false};
|
||||
|
||||
@@ -8,17 +8,20 @@ namespace dht {
|
||||
static const char *const TAG = "dht";
|
||||
|
||||
void DHT::setup() {
|
||||
this->pin_->digital_write(true);
|
||||
this->pin_->setup();
|
||||
this->pin_->digital_write(true);
|
||||
this->t_pin_->digital_write(true);
|
||||
this->t_pin_->setup();
|
||||
#ifdef USE_ESP32
|
||||
this->t_pin_->pin_mode(this->t_pin_->get_flags() | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN);
|
||||
#endif
|
||||
this->t_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void DHT::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "DHT:");
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
LOG_PIN(" Pin: ", this->t_pin_);
|
||||
ESP_LOGCONFIG(TAG, " %sModel: %s", this->is_auto_detect_ ? "Auto-detected " : "",
|
||||
this->model_ == DHT_MODEL_DHT11 ? "DHT11" : "DHT22 or equivalent");
|
||||
ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->pin_->get_flags() & gpio::FLAG_PULLUP));
|
||||
ESP_LOGCONFIG(TAG, " Internal pull-up: %s", ONOFF(this->t_pin_->get_flags() & gpio::FLAG_PULLUP));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
@@ -72,21 +75,15 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
int8_t i = 0;
|
||||
uint8_t data[5] = {0, 0, 0, 0, 0};
|
||||
|
||||
this->pin_->digital_write(false);
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
#ifndef USE_ESP32
|
||||
this->pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
#endif
|
||||
this->pin_.digital_write(false);
|
||||
|
||||
if (this->model_ == DHT_MODEL_DHT11) {
|
||||
delayMicroseconds(18000);
|
||||
} else if (this->model_ == DHT_MODEL_SI7021) {
|
||||
#ifdef USE_ESP8266
|
||||
delayMicroseconds(500);
|
||||
this->pin_->digital_write(true);
|
||||
delayMicroseconds(40);
|
||||
#else
|
||||
delayMicroseconds(400);
|
||||
this->pin_->digital_write(true);
|
||||
#endif
|
||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||
delayMicroseconds(2000);
|
||||
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
|
||||
@@ -94,7 +91,12 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
} else {
|
||||
delayMicroseconds(800);
|
||||
}
|
||||
this->pin_->pin_mode(this->pin_->get_flags());
|
||||
|
||||
#ifdef USE_ESP32
|
||||
this->pin_.digital_write(true);
|
||||
#else
|
||||
this->pin_.pin_mode(this->t_pin_->get_flags());
|
||||
#endif
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
@@ -110,7 +112,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
uint32_t start_time = micros();
|
||||
|
||||
// Wait for rising edge
|
||||
while (!this->pin_->digital_read()) {
|
||||
while (!this->pin_.digital_read()) {
|
||||
if (micros() - start_time > 90) {
|
||||
if (i < 0) {
|
||||
error_code = 1; // line didn't clear
|
||||
@@ -127,7 +129,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
uint32_t end_time = start_time;
|
||||
|
||||
// Wait for falling edge
|
||||
while (this->pin_->digital_read()) {
|
||||
while (this->pin_.digital_read()) {
|
||||
end_time = micros();
|
||||
if (end_time - start_time > 90) {
|
||||
if (i < 0) {
|
||||
|
||||
@@ -38,7 +38,10 @@ class DHT : public PollingComponent {
|
||||
*/
|
||||
void set_dht_model(DHTModel model);
|
||||
|
||||
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
|
||||
void set_pin(InternalGPIOPin *pin) {
|
||||
this->t_pin_ = pin;
|
||||
this->pin_ = pin->to_isr();
|
||||
}
|
||||
void set_model(DHTModel model) { model_ = model; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
@@ -54,7 +57,8 @@ class DHT : public PollingComponent {
|
||||
protected:
|
||||
bool read_sensor_(float *temperature, float *humidity, bool report_errors);
|
||||
|
||||
InternalGPIOPin *pin_;
|
||||
InternalGPIOPin *t_pin_;
|
||||
ISRInternalGPIOPin pin_;
|
||||
DHTModel model_{DHT_MODEL_AUTO_DETECT};
|
||||
bool is_auto_detect_{false};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
|
||||
@@ -63,11 +63,13 @@ def validate_auto_clear(value):
|
||||
return cv.boolean(value)
|
||||
|
||||
|
||||
BASIC_DISPLAY_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
def basic_display_schema(default_update_interval: str = "1s") -> cv.Schema:
|
||||
"""Create a basic display schema with configurable default update interval."""
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
).extend(cv.polling_component_schema(default_update_interval))
|
||||
|
||||
|
||||
def _validate_test_card(config):
|
||||
@@ -81,34 +83,41 @@ def _validate_test_card(config):
|
||||
return config
|
||||
|
||||
|
||||
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ROTATION): validate_rotation,
|
||||
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
|
||||
cv.ensure_list(
|
||||
def full_display_schema(default_update_interval: str = "1s") -> cv.Schema:
|
||||
"""Create a full display schema with configurable default update interval."""
|
||||
schema = basic_display_schema(default_update_interval).extend(
|
||||
{
|
||||
cv.Optional(CONF_ROTATION): validate_rotation,
|
||||
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
|
||||
cv.ensure_list(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DisplayPage),
|
||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
),
|
||||
cv.Length(min=1),
|
||||
),
|
||||
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DisplayPage),
|
||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayOnPageChangeTrigger
|
||||
),
|
||||
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
|
||||
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
|
||||
}
|
||||
),
|
||||
cv.Length(min=1),
|
||||
),
|
||||
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayOnPageChangeTrigger
|
||||
),
|
||||
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
|
||||
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
|
||||
): validate_auto_clear,
|
||||
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
||||
}
|
||||
)
|
||||
FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card)
|
||||
cv.Optional(
|
||||
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
|
||||
): validate_auto_clear,
|
||||
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
||||
}
|
||||
)
|
||||
schema.add_extra(_validate_test_card)
|
||||
return schema
|
||||
|
||||
|
||||
BASIC_DISPLAY_SCHEMA = basic_display_schema("1s")
|
||||
FULL_DISPLAY_SCHEMA = full_display_schema("1s")
|
||||
|
||||
|
||||
async def setup_display_core_(var, config):
|
||||
|
||||
@@ -54,6 +54,7 @@ bool MenuItemSelect::select_next() {
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_next(true).perform();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -65,6 +66,7 @@ bool MenuItemSelect::select_prev() {
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_previous(true).perform();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from esphome.const import (
|
||||
CONF_TRANSFORM,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
CONF_WIDTH,
|
||||
SCHEDULER_DONT_RUN,
|
||||
)
|
||||
from esphome.cpp_generator import RawExpression
|
||||
from esphome.final_validate import full_config
|
||||
@@ -72,12 +73,10 @@ TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL]]
|
||||
class_name = epaper_spi_ns.class_(model.class_name, EPaperBase)
|
||||
minimum_update_interval = update_interval(
|
||||
model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s")
|
||||
)
|
||||
cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required
|
||||
return (
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
display.full_display_schema("60s")
|
||||
.extend(
|
||||
spi.spi_device_schema(
|
||||
cs_pin_required=False,
|
||||
default_mode="MODE0",
|
||||
@@ -94,9 +93,6 @@ def model_schema(config):
|
||||
{
|
||||
cv.Optional(CONF_ROTATION, default=0): validate_rotation,
|
||||
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
|
||||
cv.Optional(CONF_UPDATE_INTERVAL, default=cv.UNDEFINED): cv.All(
|
||||
update_interval, cv.Range(min=minimum_update_interval)
|
||||
),
|
||||
cv.Optional(CONF_TRANSFORM): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||
@@ -150,15 +146,22 @@ def _final_validate(config):
|
||||
global_config = full_config.get()
|
||||
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
|
||||
|
||||
if CONF_LAMBDA not in config and CONF_PAGES not in config:
|
||||
if LVGL_DOMAIN in global_config:
|
||||
if CONF_UPDATE_INTERVAL not in config:
|
||||
config[CONF_UPDATE_INTERVAL] = update_interval("never")
|
||||
else:
|
||||
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
elif CONF_UPDATE_INTERVAL not in config:
|
||||
config[CONF_UPDATE_INTERVAL] = update_interval("1min")
|
||||
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||
if (
|
||||
CONF_LAMBDA not in config
|
||||
and CONF_PAGES not in config
|
||||
and LVGL_DOMAIN not in global_config
|
||||
):
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
|
||||
interval = config[CONF_UPDATE_INTERVAL]
|
||||
if interval != SCHEDULER_DONT_RUN:
|
||||
model = MODELS[config[CONF_MODEL]]
|
||||
minimum = update_interval(model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s"))
|
||||
if interval < minimum:
|
||||
raise cv.Invalid(
|
||||
f"update_interval must be at least {minimum} for {model.name}, got {interval}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import itertools
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from esphome import yaml_util
|
||||
import esphome.codegen as cg
|
||||
@@ -12,6 +13,7 @@ from esphome.const import (
|
||||
CONF_ADVANCED,
|
||||
CONF_BOARD,
|
||||
CONF_COMPONENTS,
|
||||
CONF_DISABLED,
|
||||
CONF_ESPHOME,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_IGNORE_EFUSE_CUSTOM_MAC,
|
||||
@@ -23,6 +25,7 @@ from esphome.const import (
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
CONF_REF,
|
||||
CONF_REFRESH,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_SOURCE,
|
||||
CONF_TYPE,
|
||||
CONF_VARIANT,
|
||||
@@ -80,6 +83,7 @@ CONF_ASSERTION_LEVEL = "assertion_level"
|
||||
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
|
||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
|
||||
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
||||
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
|
||||
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
||||
CONF_RELEASE = "release"
|
||||
|
||||
@@ -117,8 +121,8 @@ ARDUINO_ALLOWED_VARIANTS = [
|
||||
]
|
||||
|
||||
|
||||
def get_cpu_frequencies(*frequencies):
|
||||
return [str(x) + "MHZ" for x in frequencies]
|
||||
def get_cpu_frequencies(*frequencies: int) -> list[str]:
|
||||
return [f"{frequency}MHZ" for frequency in frequencies]
|
||||
|
||||
|
||||
CPU_FREQUENCIES = {
|
||||
@@ -135,7 +139,7 @@ CPU_FREQUENCIES = {
|
||||
}
|
||||
|
||||
# Make sure not missed here if a new variant added.
|
||||
assert all(v in CPU_FREQUENCIES for v in VARIANTS)
|
||||
assert all(variant in CPU_FREQUENCIES for variant in VARIANTS)
|
||||
|
||||
FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values()))
|
||||
|
||||
@@ -249,10 +253,10 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
|
||||
def add_idf_component(
|
||||
*,
|
||||
name: str,
|
||||
repo: str = None,
|
||||
ref: str = None,
|
||||
path: str = None,
|
||||
refresh: TimePeriod = None,
|
||||
repo: str | None = None,
|
||||
ref: str | None = None,
|
||||
path: str | None = None,
|
||||
refresh: TimePeriod | None = None,
|
||||
components: list[str] | None = None,
|
||||
submodules: list[str] | None = None,
|
||||
):
|
||||
@@ -333,7 +337,7 @@ def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
|
||||
|
||||
|
||||
def _is_framework_url(source: str) -> str:
|
||||
def _is_framework_url(source: str) -> bool:
|
||||
# platformio accepts many URL schemes for framework repositories and archives including http, https, git, file, and symlink
|
||||
import urllib.parse
|
||||
|
||||
@@ -570,6 +574,13 @@ def final_validate(config):
|
||||
path=[CONF_FLASH_SIZE],
|
||||
)
|
||||
)
|
||||
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
|
||||
safe_mode_config = full_config.get(CONF_SAFE_MODE)
|
||||
if safe_mode_config is None or safe_mode_config.get(CONF_DISABLED, False):
|
||||
_LOGGER.warning(
|
||||
"OTA rollback requires safe_mode, disabling rollback support"
|
||||
)
|
||||
advanced[CONF_ENABLE_OTA_ROLLBACK] = False
|
||||
if errs:
|
||||
raise cv.MultipleInvalid(errs)
|
||||
|
||||
@@ -630,10 +641,13 @@ def enable_ringbuf_in_iram() -> None:
|
||||
|
||||
def _parse_idf_component(value: str) -> ConfigType:
|
||||
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
|
||||
if "^" not in value:
|
||||
raise cv.Invalid(f"Invalid IDF component shorthand '{value}'")
|
||||
name, ref = value.split("^", 1)
|
||||
return {CONF_NAME: name, CONF_REF: ref}
|
||||
# Match operator followed by version-like string (digit or *)
|
||||
if match := re.search(r"(~=|>=|<=|==|!=|>|<|\^|~)(\d|\*)", value):
|
||||
return {CONF_NAME: value[: match.start()], CONF_REF: value[match.start() :]}
|
||||
raise cv.Invalid(
|
||||
f"Invalid IDF component shorthand '{value}'. "
|
||||
f"Expected format: 'owner/component<op>version' where <op> is one of: ^, ~, ~=, ==, !=, >=, >, <=, <"
|
||||
)
|
||||
|
||||
|
||||
def _validate_idf_component(config: ConfigType) -> ConfigType:
|
||||
@@ -701,6 +715,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
|
||||
min=8192, max=32768
|
||||
),
|
||||
cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
@@ -981,29 +996,14 @@ async def to_code(config):
|
||||
cg.add_platformio_option("framework", "arduino, espidf")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
||||
cg.add_platformio_option(
|
||||
"board_build.embed_txtfiles",
|
||||
[
|
||||
"managed_components/espressif__esp_insights/server_certs/https_server.crt",
|
||||
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt",
|
||||
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt",
|
||||
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt",
|
||||
],
|
||||
)
|
||||
cg.add_define(
|
||||
"USE_ARDUINO_VERSION_CODE",
|
||||
cg.RawExpression(
|
||||
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
|
||||
),
|
||||
)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ARDUINO_LOOP_STACK_SIZE",
|
||||
conf[CONF_ADVANCED][CONF_LOOP_TASK_STACK_SIZE],
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True)
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
|
||||
|
||||
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
|
||||
if get_esp32_variant() == VARIANT_ESP32S2:
|
||||
@@ -1178,6 +1178,11 @@ async def to_code(config):
|
||||
"CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True
|
||||
)
|
||||
|
||||
# Enable OTA rollback support
|
||||
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
|
||||
add_idf_sdkconfig_option("CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE", True)
|
||||
cg.add_define("USE_OTA_ROLLBACK")
|
||||
|
||||
cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE])
|
||||
|
||||
cg.add_define(
|
||||
@@ -1207,7 +1212,7 @@ APP_PARTITION_SIZES = {
|
||||
}
|
||||
|
||||
|
||||
def get_arduino_partition_csv(flash_size):
|
||||
def get_arduino_partition_csv(flash_size: str):
|
||||
app_partition_size = APP_PARTITION_SIZES[flash_size]
|
||||
eeprom_partition_size = 0x1000 # 4 KB
|
||||
spiffs_partition_size = 0xF000 # 60 KB
|
||||
@@ -1227,7 +1232,7 @@ spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size:
|
||||
"""
|
||||
|
||||
|
||||
def get_idf_partition_csv(flash_size):
|
||||
def get_idf_partition_csv(flash_size: str):
|
||||
app_partition_size = APP_PARTITION_SIZES[flash_size]
|
||||
|
||||
return f"""\
|
||||
|
||||
@@ -4,25 +4,20 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_clk_tree.h>
|
||||
#include <esp_cpu.h>
|
||||
#include <esp_idf_version.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_timer.h>
|
||||
#include <soc/rtc.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <hal/cpu_hal.h>
|
||||
void setup(); // NOLINT(readability-redundant-declaration)
|
||||
void loop(); // NOLINT(readability-redundant-declaration)
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Esp.h>
|
||||
#else
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
#include <esp_clk_tree.h>
|
||||
#endif
|
||||
void setup();
|
||||
void loop();
|
||||
#endif
|
||||
// Weak stub for initArduino - overridden when the Arduino component is present
|
||||
extern "C" __attribute__((weak)) void initArduino() {}
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -41,29 +36,13 @@ void arch_restart() {
|
||||
|
||||
void arch_init() {
|
||||
// Enable the task watchdog only on the loop task (from which we're currently running)
|
||||
#if defined(USE_ESP_IDF)
|
||||
esp_task_wdt_add(nullptr);
|
||||
// Idle task watchdog is disabled on ESP-IDF
|
||||
#elif defined(USE_ARDUINO)
|
||||
enableLoopWDT();
|
||||
// Disable idle task watchdog on the core we're using (Arduino pins the task to a core)
|
||||
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0
|
||||
disableCore0WDT();
|
||||
#endif
|
||||
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1
|
||||
disableCore1WDT();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// If the bootloader was compiled with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE the current
|
||||
// partition will get rolled back unless it is marked as valid.
|
||||
esp_ota_img_states_t state;
|
||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||
if (esp_ota_get_state_partition(running, &state) == ESP_OK) {
|
||||
if (state == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
}
|
||||
}
|
||||
// Handle OTA rollback: mark partition valid immediately unless USE_OTA_ROLLBACK is enabled,
|
||||
// in which case safe_mode will mark it valid after confirming successful boot.
|
||||
#ifndef USE_OTA_ROLLBACK
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
#endif
|
||||
}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
|
||||
|
||||
@@ -71,21 +50,10 @@ uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
|
||||
uint32_t arch_get_cpu_freq_hz() {
|
||||
uint32_t freq = 0;
|
||||
#ifdef USE_ESP_IDF
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
|
||||
#else
|
||||
rtc_cpu_freq_config_t config;
|
||||
rtc_clk_cpu_freq_get_config(&config);
|
||||
freq = config.freq_mhz * 1000000U;
|
||||
#endif
|
||||
#elif defined(USE_ARDUINO)
|
||||
freq = ESP.getCpuFreqMHz() * 1000000;
|
||||
#endif
|
||||
return freq;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
void loop_task(void *pv_params) {
|
||||
@@ -96,6 +64,7 @@ void loop_task(void *pv_params) {
|
||||
}
|
||||
|
||||
extern "C" void app_main() {
|
||||
initArduino();
|
||||
esp32::setup_preferences();
|
||||
#if CONFIG_FREERTOS_UNICORE
|
||||
xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle);
|
||||
@@ -103,11 +72,6 @@ extern "C" void app_main() {
|
||||
xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1);
|
||||
#endif
|
||||
}
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
extern "C" void init() { esp32::setup_preferences(); }
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import json # noqa: E402
|
||||
import os # noqa: E402
|
||||
import pathlib # noqa: E402
|
||||
import shutil # noqa: E402
|
||||
from glob import glob # noqa: E402
|
||||
|
||||
|
||||
def merge_factory_bin(source, target, env):
|
||||
@@ -126,3 +127,14 @@ def esp32_copy_ota_bin(source, target, env):
|
||||
# Run merge first, then ota copy second
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821
|
||||
|
||||
# Find server certificates in managed components and generate .S files.
|
||||
# Workaround for PlatformIO not processing target_add_binary_data() from managed component CMakeLists.
|
||||
project_dir = env.subst("$PROJECT_DIR")
|
||||
managed_components = os.path.join(project_dir, "managed_components")
|
||||
if os.path.isdir(managed_components):
|
||||
for cert_file in glob(os.path.join(managed_components, "**/server_certs/*.crt"), recursive=True):
|
||||
try:
|
||||
env.FileToAsm(cert_file, FILE_TYPE="TEXT")
|
||||
except Exception as e:
|
||||
print(f"Error processing {os.path.basename(cert_file)}: {e}")
|
||||
|
||||
@@ -4,26 +4,28 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include <nvs_flash.h>
|
||||
#include <cstring>
|
||||
#include <cinttypes>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
static const char *const TAG = "esp32.preferences";
|
||||
|
||||
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
|
||||
static constexpr size_t KEY_BUFFER_SIZE = 12;
|
||||
|
||||
struct NVSData {
|
||||
std::string key;
|
||||
uint32_t key;
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
size_t len;
|
||||
|
||||
void set_data(const uint8_t *src, size_t size) {
|
||||
data = std::make_unique<uint8_t[]>(size);
|
||||
memcpy(data.get(), src, size);
|
||||
len = size;
|
||||
this->data = std::make_unique<uint8_t[]>(size);
|
||||
memcpy(this->data.get(), src, size);
|
||||
this->len = size;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -31,27 +33,27 @@ static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-n
|
||||
|
||||
class ESP32PreferenceBackend : public ESPPreferenceBackend {
|
||||
public:
|
||||
std::string key;
|
||||
uint32_t key;
|
||||
uint32_t nvs_handle;
|
||||
bool save(const uint8_t *data, size_t len) override {
|
||||
// try find in pending saves and update that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == key) {
|
||||
if (obj.key == this->key) {
|
||||
obj.set_data(data, len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
NVSData save{};
|
||||
save.key = key;
|
||||
save.key = this->key;
|
||||
save.set_data(data, len);
|
||||
s_pending_save.emplace_back(std::move(save));
|
||||
ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len);
|
||||
ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
|
||||
return true;
|
||||
}
|
||||
bool load(uint8_t *data, size_t len) override {
|
||||
// try find in pending saves and load from that
|
||||
for (auto &obj : s_pending_save) {
|
||||
if (obj.key == key) {
|
||||
if (obj.key == this->key) {
|
||||
if (obj.len != len) {
|
||||
// size mismatch
|
||||
return false;
|
||||
@@ -61,22 +63,24 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
|
||||
}
|
||||
}
|
||||
|
||||
char key_str[KEY_BUFFER_SIZE];
|
||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
|
||||
size_t actual_len;
|
||||
esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len);
|
||||
esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err));
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
if (actual_len != len) {
|
||||
ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
|
||||
return false;
|
||||
}
|
||||
err = nvs_get_blob(nvs_handle, key.c_str(), data, &len);
|
||||
err = nvs_get_blob(this->nvs_handle, key_str, data, &len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err));
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
|
||||
return false;
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key.c_str(), len);
|
||||
ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -103,14 +107,12 @@ class ESP32Preferences : public ESPPreferences {
|
||||
}
|
||||
}
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
|
||||
return make_preference(length, type);
|
||||
return this->make_preference(length, type);
|
||||
}
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
|
||||
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
pref->nvs_handle = nvs_handle;
|
||||
|
||||
uint32_t keyval = type;
|
||||
pref->key = str_sprintf("%" PRIu32, keyval);
|
||||
pref->nvs_handle = this->nvs_handle;
|
||||
pref->key = type;
|
||||
|
||||
return ESPPreferenceObject(pref);
|
||||
}
|
||||
@@ -123,17 +125,19 @@ class ESP32Preferences : public ESPPreferences {
|
||||
// goal try write all pending saves even if one fails
|
||||
int cached = 0, written = 0, failed = 0;
|
||||
esp_err_t last_err = ESP_OK;
|
||||
std::string last_key{};
|
||||
uint32_t last_key = 0;
|
||||
|
||||
// go through vector from back to front (makes erase easier/more efficient)
|
||||
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
|
||||
const auto &save = s_pending_save[i];
|
||||
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str());
|
||||
if (is_changed(nvs_handle, save)) {
|
||||
esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.get(), save.len);
|
||||
ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len);
|
||||
char key_str[KEY_BUFFER_SIZE];
|
||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
|
||||
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
|
||||
if (this->is_changed_(this->nvs_handle, save, key_str)) {
|
||||
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len);
|
||||
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", save.key.c_str(), save.len, esp_err_to_name(err));
|
||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.len, esp_err_to_name(err));
|
||||
failed++;
|
||||
last_err = err;
|
||||
last_key = save.key;
|
||||
@@ -141,7 +145,7 @@ class ESP32Preferences : public ESPPreferences {
|
||||
}
|
||||
written++;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "NVS data not changed skipping %s len=%zu", save.key.c_str(), save.len);
|
||||
ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.len);
|
||||
cached++;
|
||||
}
|
||||
s_pending_save.erase(s_pending_save.begin() + i);
|
||||
@@ -149,12 +153,12 @@ class ESP32Preferences : public ESPPreferences {
|
||||
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
|
||||
failed);
|
||||
if (failed > 0) {
|
||||
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%s", failed, esp_err_to_name(last_err),
|
||||
last_key.c_str());
|
||||
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err),
|
||||
last_key);
|
||||
}
|
||||
|
||||
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
|
||||
esp_err_t err = nvs_commit(nvs_handle);
|
||||
esp_err_t err = nvs_commit(this->nvs_handle);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
@@ -162,11 +166,13 @@ class ESP32Preferences : public ESPPreferences {
|
||||
|
||||
return failed == 0;
|
||||
}
|
||||
bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
|
||||
|
||||
protected:
|
||||
bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) {
|
||||
size_t actual_len;
|
||||
esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len);
|
||||
esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err));
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
|
||||
return true;
|
||||
}
|
||||
// Check size first before allocating memory
|
||||
@@ -174,9 +180,9 @@ class ESP32Preferences : public ESPPreferences {
|
||||
return true;
|
||||
}
|
||||
auto stored_data = std::make_unique<uint8_t[]>(actual_len);
|
||||
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len);
|
||||
err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
|
||||
return true;
|
||||
}
|
||||
return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0;
|
||||
|
||||
@@ -260,8 +260,11 @@ bool ESP32BLE::ble_setup_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// BLE device names are limited to 20 characters
|
||||
// Buffer: 20 chars + null terminator
|
||||
constexpr size_t ble_name_max_len = 21;
|
||||
char name_buffer[ble_name_max_len];
|
||||
const char *device_name;
|
||||
std::string name_with_suffix;
|
||||
|
||||
if (this->name_ != nullptr) {
|
||||
if (App.is_name_add_mac_suffix_enabled()) {
|
||||
@@ -272,23 +275,28 @@ bool ESP32BLE::ble_setup_() {
|
||||
char mac_addr[mac_address_len];
|
||||
get_mac_address_into_buffer(mac_addr);
|
||||
const char *mac_suffix_ptr = mac_addr + mac_address_suffix_len;
|
||||
name_with_suffix =
|
||||
make_name_with_suffix(this->name_, strlen(this->name_), '-', mac_suffix_ptr, mac_address_suffix_len);
|
||||
device_name = name_with_suffix.c_str();
|
||||
make_name_with_suffix_to(name_buffer, sizeof(name_buffer), this->name_, strlen(this->name_), '-', mac_suffix_ptr,
|
||||
mac_address_suffix_len);
|
||||
device_name = name_buffer;
|
||||
} else {
|
||||
device_name = this->name_;
|
||||
}
|
||||
} else {
|
||||
name_with_suffix = App.get_name();
|
||||
if (name_with_suffix.length() > 20) {
|
||||
const std::string &app_name = App.get_name();
|
||||
size_t name_len = app_name.length();
|
||||
if (name_len > 20) {
|
||||
if (App.is_name_add_mac_suffix_enabled()) {
|
||||
// Keep first 13 chars and last 7 chars (MAC suffix), remove middle
|
||||
name_with_suffix.erase(13, name_with_suffix.length() - 20);
|
||||
memcpy(name_buffer, app_name.c_str(), 13);
|
||||
memcpy(name_buffer + 13, app_name.c_str() + name_len - 7, 7);
|
||||
} else {
|
||||
name_with_suffix.resize(20);
|
||||
memcpy(name_buffer, app_name.c_str(), 20);
|
||||
}
|
||||
name_buffer[20] = '\0';
|
||||
} else {
|
||||
memcpy(name_buffer, app_name.c_str(), name_len + 1); // Include null terminator
|
||||
}
|
||||
device_name = name_with_suffix.c_str();
|
||||
device_name = name_buffer;
|
||||
}
|
||||
|
||||
err = esp_ble_gap_set_device_name(device_name);
|
||||
@@ -312,13 +320,21 @@ bool ESP32BLE::ble_setup_() {
|
||||
bool ESP32BLE::ble_dismantle_() {
|
||||
esp_err_t err = esp_bluedroid_disable();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err);
|
||||
return false;
|
||||
// ESP_ERR_INVALID_STATE means Bluedroid is already disabled, which is fine
|
||||
if (err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Already disabled");
|
||||
}
|
||||
err = esp_bluedroid_deinit();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err);
|
||||
return false;
|
||||
// ESP_ERR_INVALID_STATE means Bluedroid is already deinitialized, which is fine
|
||||
if (err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Already deinitialized");
|
||||
}
|
||||
|
||||
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
|
||||
|
||||
@@ -213,17 +213,23 @@ extern ESP32BLE *global_ble;
|
||||
|
||||
template<typename... Ts> class BLEEnabledCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(const Ts &...x) override { return global_ble->is_active(); }
|
||||
bool check(const Ts &...x) override { return global_ble != nullptr && global_ble->is_active(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEEnableAction : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_ble->enable(); }
|
||||
void play(const Ts &...x) override {
|
||||
if (global_ble != nullptr)
|
||||
global_ble->enable();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEDisableAction : public Action<Ts...> {
|
||||
public:
|
||||
void play(const Ts &...x) override { global_ble->disable(); }
|
||||
void play(const Ts &...x) override {
|
||||
if (global_ble != nullptr)
|
||||
global_ble->disable();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::esp32_ble
|
||||
|
||||
@@ -143,9 +143,8 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
||||
return this->as_128bit() == uuid.as_128bit();
|
||||
}
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
char buf[40]; // Enough for 128-bit UUID with dashes
|
||||
char *pos = buf;
|
||||
void ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
|
||||
char *pos = output.data();
|
||||
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
@@ -156,7 +155,7 @@ std::string ESPBTUUID::to_string() const {
|
||||
*pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid16 >> 4) & 0x0F);
|
||||
*pos++ = format_hex_pretty_char(this->uuid_.uuid.uuid16 & 0x0F);
|
||||
*pos = '\0';
|
||||
return std::string(buf);
|
||||
return;
|
||||
|
||||
case ESP_UUID_LEN_32:
|
||||
*pos++ = '0';
|
||||
@@ -165,7 +164,7 @@ std::string ESPBTUUID::to_string() const {
|
||||
*pos++ = format_hex_pretty_char((this->uuid_.uuid.uuid32 >> shift) & 0x0F);
|
||||
}
|
||||
*pos = '\0';
|
||||
return std::string(buf);
|
||||
return;
|
||||
|
||||
default:
|
||||
case ESP_UUID_LEN_128:
|
||||
@@ -179,9 +178,13 @@ std::string ESPBTUUID::to_string() const {
|
||||
}
|
||||
}
|
||||
*pos = '\0';
|
||||
return std::string(buf);
|
||||
return;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
char buf[UUID_STR_LEN];
|
||||
this->to_str(buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
} // namespace esphome::esp32_ble
|
||||
|
||||
@@ -7,11 +7,15 @@
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_UUID
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <esp_bt_defs.h>
|
||||
|
||||
namespace esphome::esp32_ble {
|
||||
|
||||
/// Buffer size for UUID string: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\0"
|
||||
static constexpr size_t UUID_STR_LEN = 37;
|
||||
|
||||
class ESPBTUUID {
|
||||
public:
|
||||
ESPBTUUID();
|
||||
@@ -37,6 +41,7 @@ class ESPBTUUID {
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
std::string to_string() const;
|
||||
void to_str(std::span<char, UUID_STR_LEN> output) const;
|
||||
|
||||
protected:
|
||||
esp_bt_uuid_t uuid_;
|
||||
|
||||
@@ -50,8 +50,12 @@ void BLECharacteristic::parse_descriptors() {
|
||||
desc->handle = result.handle;
|
||||
desc->characteristic = this;
|
||||
this->descriptors.push_back(desc);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char uuid_buf[espbt::UUID_STR_LEN];
|
||||
desc->uuid.to_str(uuid_buf);
|
||||
ESP_LOGV(TAG, "[%d] [%s] descriptor %s, handle 0x%x", this->service->client->get_connection_index(),
|
||||
this->service->client->address_str(), desc->uuid.to_string().c_str(), desc->handle);
|
||||
this->service->client->address_str(), uuid_buf, desc->handle);
|
||||
#endif
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,12 +411,15 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
|
||||
} else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
for (auto &svc : this->services_) {
|
||||
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_,
|
||||
svc->uuid.to_string().c_str());
|
||||
char uuid_buf[espbt::UUID_STR_LEN];
|
||||
svc->uuid.to_str(uuid_buf);
|
||||
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_, uuid_buf);
|
||||
ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, this->address_str_,
|
||||
svc->start_handle, svc->end_handle);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_);
|
||||
|
||||
@@ -64,9 +64,12 @@ void BLEService::parse_characteristics() {
|
||||
characteristic->handle = result.char_handle;
|
||||
characteristic->service = this;
|
||||
this->characteristics.push_back(characteristic);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char uuid_buf[espbt::UUID_STR_LEN];
|
||||
characteristic->uuid.to_str(uuid_buf);
|
||||
ESP_LOGV(TAG, "[%d] [%s] characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(),
|
||||
this->client->address_str(), characteristic->uuid.to_string().c_str(), characteristic->handle,
|
||||
characteristic->properties);
|
||||
this->client->address_str(), uuid_buf, characteristic->handle, characteristic->properties);
|
||||
#endif
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,11 @@ void BLECharacteristic::do_create(BLEService *service) {
|
||||
esp_attr_control_t control;
|
||||
control.auto_rsp = ESP_GATT_RSP_BY_APP;
|
||||
|
||||
ESP_LOGV(TAG, "Creating characteristic - %s", this->uuid_.to_string().c_str());
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
this->uuid_.to_str(uuid_buf);
|
||||
ESP_LOGV(TAG, "Creating characteristic - %s", uuid_buf);
|
||||
#endif
|
||||
|
||||
esp_bt_uuid_t uuid = this->uuid_.get_uuid();
|
||||
esp_err_t err = esp_ble_gatts_add_char(service->get_handle(), &uuid, static_cast<esp_gatt_perm_t>(this->permissions_),
|
||||
|
||||
@@ -34,7 +34,11 @@ void BLEDescriptor::do_create(BLECharacteristic *characteristic) {
|
||||
esp_attr_control_t control;
|
||||
control.auto_rsp = ESP_GATT_AUTO_RSP;
|
||||
|
||||
ESP_LOGV(TAG, "Creating descriptor - %s", this->uuid_.to_string().c_str());
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
this->uuid_.to_str(uuid_buf);
|
||||
ESP_LOGV(TAG, "Creating descriptor - %s", uuid_buf);
|
||||
#endif
|
||||
esp_bt_uuid_t uuid = this->uuid_.get_uuid();
|
||||
esp_err_t err = esp_ble_gatts_add_char_descr(this->characteristic_->get_service()->get_handle(), &uuid,
|
||||
this->permissions_, &this->value_, &control);
|
||||
|
||||
@@ -106,7 +106,11 @@ void BLEServer::restart_advertising_() {
|
||||
}
|
||||
|
||||
BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles) {
|
||||
ESP_LOGV(TAG, "Creating BLE service - %s", uuid.to_string().c_str());
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
uuid.to_str(uuid_buf);
|
||||
ESP_LOGV(TAG, "Creating BLE service - %s", uuid_buf);
|
||||
#endif
|
||||
// Calculate the inst_id for the service
|
||||
uint8_t inst_id = 0;
|
||||
for (; inst_id < 0xFF; inst_id++) {
|
||||
@@ -115,7 +119,9 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n
|
||||
}
|
||||
}
|
||||
if (inst_id == 0xFF) {
|
||||
ESP_LOGW(TAG, "Could not create BLE service %s, too many instances", uuid.to_string().c_str());
|
||||
char warn_uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
uuid.to_str(warn_uuid_buf);
|
||||
ESP_LOGW(TAG, "Could not create BLE service %s, too many instances", warn_uuid_buf);
|
||||
return nullptr;
|
||||
}
|
||||
BLEService *service = // NOLINT(cppcoreguidelines-owning-memory)
|
||||
@@ -128,7 +134,11 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n
|
||||
}
|
||||
|
||||
void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) {
|
||||
ESP_LOGV(TAG, "Removing BLE service - %s %d", uuid.to_string().c_str(), inst_id);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
uuid.to_str(uuid_buf);
|
||||
ESP_LOGV(TAG, "Removing BLE service - %s %d", uuid_buf, inst_id);
|
||||
#endif
|
||||
for (auto it = this->services_.begin(); it != this->services_.end(); ++it) {
|
||||
if (it->uuid == uuid && it->inst_id == inst_id) {
|
||||
it->service->do_delete();
|
||||
@@ -137,7 +147,9 @@ void BLEServer::remove_service(ESPBTUUID uuid, uint8_t inst_id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "BLE service %s %d does not exist", uuid.to_string().c_str(), inst_id);
|
||||
char warn_uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
uuid.to_str(warn_uuid_buf);
|
||||
ESP_LOGW(TAG, "BLE service %s %d does not exist", warn_uuid_buf, inst_id);
|
||||
}
|
||||
|
||||
BLEService *BLEServer::get_service(ESPBTUUID uuid, uint8_t inst_id) {
|
||||
|
||||
@@ -188,7 +188,10 @@ void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_();
|
||||
|
||||
void ESP32BLETracker::stop_scan_() {
|
||||
if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
|
||||
ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_));
|
||||
// If scanner is already idle, there's nothing to stop - this is not an error
|
||||
if (this->scanner_state_ != ScannerState::IDLE) {
|
||||
ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Reset timeout state machine when stopping scan
|
||||
@@ -438,24 +441,31 @@ void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
|
||||
ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_);
|
||||
}
|
||||
for (auto &uuid : this->service_uuids_) {
|
||||
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
uuid.to_str(uuid_buf);
|
||||
ESP_LOGVV(TAG, " Service UUID: %s", uuid_buf);
|
||||
}
|
||||
for (auto &data : this->manufacturer_datas_) {
|
||||
auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data);
|
||||
if (ibeacon.has_value()) {
|
||||
ESP_LOGVV(TAG, " Manufacturer iBeacon:");
|
||||
ESP_LOGVV(TAG, " UUID: %s", ibeacon.value().get_uuid().to_string().c_str());
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
ibeacon.value().get_uuid().to_str(uuid_buf);
|
||||
ESP_LOGVV(TAG, " UUID: %s", uuid_buf);
|
||||
ESP_LOGVV(TAG, " Major: %u", ibeacon.value().get_major());
|
||||
ESP_LOGVV(TAG, " Minor: %u", ibeacon.value().get_minor());
|
||||
ESP_LOGVV(TAG, " TXPower: %d", ibeacon.value().get_signal_power());
|
||||
} else {
|
||||
ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(),
|
||||
format_hex_pretty(data.data).c_str());
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
data.uuid.to_str(uuid_buf);
|
||||
ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", uuid_buf, format_hex_pretty(data.data).c_str());
|
||||
}
|
||||
}
|
||||
for (auto &data : this->service_datas_) {
|
||||
ESP_LOGVV(TAG, " Service data:");
|
||||
ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
data.uuid.to_str(uuid_buf);
|
||||
ESP_LOGVV(TAG, " UUID: %s", uuid_buf);
|
||||
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import logging
|
||||
|
||||
from esphome import automation, pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
from esphome.components import i2c, socket
|
||||
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
|
||||
from esphome.components.psram import DOMAIN as psram_domain
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -27,7 +27,7 @@ import esphome.final_validate as fv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AUTO_LOAD = ["camera"]
|
||||
AUTO_LOAD = ["camera", "socket"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
||||
@@ -186,7 +186,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=8e6, max=20e6)
|
||||
cv.frequency, cv.float_range(min=8e6, max=20e6)
|
||||
),
|
||||
}
|
||||
),
|
||||
@@ -324,6 +324,7 @@ SETTERS = {
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_CAMERA")
|
||||
socket.require_wake_loop_threadsafe()
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await setup_entity(var, config, "camera")
|
||||
await cg.register_component(var, config)
|
||||
@@ -352,6 +353,8 @@ async def to_code(config):
|
||||
cg.add_define("USE_CAMERA")
|
||||
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
|
||||
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True)
|
||||
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False)
|
||||
|
||||
for conf in config.get(CONF_ON_STREAM_START, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace esphome {
|
||||
namespace esp32_camera {
|
||||
|
||||
static const char *const TAG = "esp32_camera";
|
||||
static constexpr size_t FRAMEBUFFER_TASK_STACK_SIZE = 1792;
|
||||
#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE
|
||||
static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000;
|
||||
#endif
|
||||
|
||||
/* ---------------- public API (derivated) ---------------- */
|
||||
void ESP32Camera::setup() {
|
||||
@@ -39,12 +43,12 @@ void ESP32Camera::setup() {
|
||||
this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
|
||||
this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
|
||||
xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
|
||||
"framebuffer_task", // name
|
||||
1024, // stack size
|
||||
this, // task pv params
|
||||
1, // priority
|
||||
nullptr, // handle
|
||||
1 // core
|
||||
"framebuffer_task", // name
|
||||
FRAMEBUFFER_TASK_STACK_SIZE, // stack size
|
||||
this, // task pv params
|
||||
1, // priority
|
||||
nullptr, // handle
|
||||
1 // core
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,6 +168,19 @@ void ESP32Camera::dump_config() {
|
||||
}
|
||||
|
||||
void ESP32Camera::loop() {
|
||||
// Fast path: skip all work when truly idle
|
||||
// (no current image, no pending requests, and not time for idle request yet)
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (!this->current_image_ && !this->has_requested_image_()) {
|
||||
// Only check idle interval when we're otherwise idle
|
||||
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
||||
this->last_idle_request_ = now;
|
||||
this->request_image(camera::IDLE);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check if we can return the image
|
||||
if (this->can_return_image_()) {
|
||||
// return image
|
||||
@@ -172,13 +189,6 @@ void ESP32Camera::loop() {
|
||||
this->current_image_.reset();
|
||||
}
|
||||
|
||||
// request idle image every idle_update_interval
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
||||
this->last_idle_request_ = now;
|
||||
this->request_image(camera::IDLE);
|
||||
}
|
||||
|
||||
// Check if we should fetch a new image
|
||||
if (!this->has_requested_image_())
|
||||
return;
|
||||
@@ -204,7 +214,20 @@ void ESP32Camera::loop() {
|
||||
}
|
||||
this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
|
||||
|
||||
ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
ESP_LOGV(TAG, "Got Image: len=%u", fb->len);
|
||||
#else
|
||||
// Initialize log time on first frame to ensure accurate interval measurement
|
||||
if (this->frame_count_ == 0) {
|
||||
this->last_log_time_ = now;
|
||||
}
|
||||
this->frame_count_++;
|
||||
if (now - this->last_log_time_ >= FRAME_LOG_INTERVAL_MS) {
|
||||
ESP_LOGD(TAG, "Received %u images in last %us", this->frame_count_, FRAME_LOG_INTERVAL_MS / 1000);
|
||||
this->last_log_time_ = now;
|
||||
this->frame_count_ = 0;
|
||||
}
|
||||
#endif
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->on_camera_image(this->current_image_);
|
||||
}
|
||||
@@ -405,6 +428,10 @@ void ESP32Camera::framebuffer_task(void *pv) {
|
||||
while (true) {
|
||||
camera_fb_t *framebuffer = esp_camera_fb_get();
|
||||
xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
|
||||
// Only wake the main loop if there's a pending request to consume the frame
|
||||
if (that->has_requested_image_()) {
|
||||
App.wake_loop_threadsafe();
|
||||
}
|
||||
// return is no-op for config with 1 fb
|
||||
xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
|
||||
esp_camera_fb_return(framebuffer);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <atomic>
|
||||
#include <esp_camera.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
@@ -205,14 +206,18 @@ class ESP32Camera : public camera::Camera {
|
||||
|
||||
esp_err_t init_error_{ESP_OK};
|
||||
std::shared_ptr<ESP32CameraImage> current_image_;
|
||||
uint8_t single_requesters_{0};
|
||||
uint8_t stream_requesters_{0};
|
||||
std::atomic<uint8_t> single_requesters_{0};
|
||||
std::atomic<uint8_t> stream_requesters_{0};
|
||||
QueueHandle_t framebuffer_get_queue_;
|
||||
QueueHandle_t framebuffer_return_queue_;
|
||||
std::vector<camera::CameraListener *> listeners_;
|
||||
|
||||
uint32_t last_idle_request_{0};
|
||||
uint32_t last_update_{0};
|
||||
#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE
|
||||
uint32_t last_log_time_{0};
|
||||
uint16_t frame_count_{0};
|
||||
#endif
|
||||
#ifdef USE_I2C
|
||||
i2c::InternalI2CBus *i2c_bus_{nullptr};
|
||||
#endif // USE_I2C
|
||||
|
||||
@@ -191,7 +191,8 @@ async def to_code(config):
|
||||
cg.add_define(ThreadModel.SINGLE)
|
||||
|
||||
cg.add_platformio_option(
|
||||
"extra_scripts", ["pre:testing_mode.py", "post:post_build.py"]
|
||||
"extra_scripts",
|
||||
["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"],
|
||||
)
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
@@ -278,3 +279,8 @@ def copy_files():
|
||||
testing_mode_file,
|
||||
CORE.relative_build_path("testing_mode.py"),
|
||||
)
|
||||
exclude_updater_file = dir / "exclude_updater.py.script"
|
||||
copy_file_if_changed(
|
||||
exclude_updater_file,
|
||||
CORE.relative_build_path("exclude_updater.py"),
|
||||
)
|
||||
|
||||
21
esphome/components/esp8266/exclude_updater.py.script
Normal file
21
esphome/components/esp8266/exclude_updater.py.script
Normal file
@@ -0,0 +1,21 @@
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
import os
|
||||
|
||||
# Filter out Updater.cpp from the Arduino core build
|
||||
# This saves 228 bytes of .bss by not instantiating the global Update object
|
||||
# ESPHome uses its own native OTA backend instead
|
||||
|
||||
|
||||
def filter_updater_from_core(env, node):
|
||||
"""Filter callback to exclude Updater.cpp from framework build."""
|
||||
path = node.get_path()
|
||||
if path.endswith("Updater.cpp"):
|
||||
print(f"ESPHome: Excluding {os.path.basename(path)} from build (using native OTA backend)")
|
||||
return None
|
||||
return node
|
||||
|
||||
|
||||
# Apply the filter to framework sources
|
||||
env.AddBuildMiddleware(filter_updater_from_core, "**/cores/esp8266/Updater.cpp")
|
||||
@@ -16,7 +16,7 @@ def valid_pwm_pin(value):
|
||||
esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm")
|
||||
ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component)
|
||||
SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action)
|
||||
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
|
||||
validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6))
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#endif
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_libretiny.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
|
||||
@@ -80,6 +80,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
std::string password_;
|
||||
std::unique_ptr<uint8_t[]> auth_buf_;
|
||||
#endif // USE_OTA_PASSWORD
|
||||
|
||||
std::unique_ptr<socket::Socket> server_;
|
||||
@@ -93,7 +94,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
uint8_t handshake_buf_pos_{0};
|
||||
uint8_t ota_features_{0};
|
||||
#ifdef USE_OTA_PASSWORD
|
||||
std::unique_ptr<uint8_t[]> auth_buf_;
|
||||
uint8_t auth_buf_pos_{0};
|
||||
uint8_t auth_type_{0}; // Store auth type to know which hasher to use
|
||||
#endif // USE_OTA_PASSWORD
|
||||
|
||||
@@ -13,7 +13,7 @@ static const char *const TAG = "espnow.transport";
|
||||
bool ESPNowTransport::should_send() { return this->parent_ != nullptr && !this->parent_->is_failed(); }
|
||||
|
||||
void ESPNowTransport::setup() {
|
||||
packet_transport::PacketTransport::setup();
|
||||
PacketTransport::setup();
|
||||
|
||||
if (this->parent_ == nullptr) {
|
||||
ESP_LOGE(TAG, "ESPNow component not set");
|
||||
@@ -26,15 +26,10 @@ void ESPNowTransport::setup() {
|
||||
this->peer_address_[2], this->peer_address_[3], this->peer_address_[4], this->peer_address_[5]);
|
||||
|
||||
// Register received handler
|
||||
this->parent_->register_received_handler(static_cast<ESPNowReceivedPacketHandler *>(this));
|
||||
this->parent_->register_received_handler(this);
|
||||
|
||||
// Register broadcasted handler
|
||||
this->parent_->register_broadcasted_handler(static_cast<ESPNowBroadcastedHandler *>(this));
|
||||
}
|
||||
|
||||
void ESPNowTransport::update() {
|
||||
packet_transport::PacketTransport::update();
|
||||
this->updated_ = true;
|
||||
this->parent_->register_broadcasted_handler(this);
|
||||
}
|
||||
|
||||
void ESPNowTransport::send_packet(const std::vector<uint8_t> &buf) const {
|
||||
|
||||
@@ -18,7 +18,6 @@ class ESPNowTransport : public packet_transport::PacketTransport,
|
||||
public ESPNowBroadcastedHandler {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void set_peer_address(peer_address_t address) {
|
||||
|
||||
@@ -220,10 +220,6 @@ BASE_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA,
|
||||
cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional("enable_mdns"): cv.invalid(
|
||||
"This option has been removed. Please use the [disabled] option under the "
|
||||
"new mdns component instead."
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -434,10 +430,13 @@ def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
|
||||
# Check all used pins against RMII reserved pins
|
||||
for pin_list in pins.PIN_SCHEMA_REGISTRY.pins_used.values():
|
||||
for pin_path, _, pin_config in pin_list:
|
||||
for pin_path, pin_device, pin_config in pin_list:
|
||||
pin_num = pin_config.get(CONF_NUMBER)
|
||||
if pin_num not in rmii_pins:
|
||||
continue
|
||||
# Skip if pin is not directly on ESP, but at some expander (device set to something else than 'None')
|
||||
if pin_device is not None:
|
||||
continue
|
||||
# Found a conflict - show helpful error message
|
||||
pin_function = rmii_pins[pin_num]
|
||||
component_path = ".".join(str(p) for p in pin_path)
|
||||
|
||||
@@ -644,6 +644,12 @@ void EthernetComponent::dump_connect_params_() {
|
||||
dns_ip2 = dns_getserver(1);
|
||||
}
|
||||
|
||||
// Use stack buffers for IP address formatting to avoid heap allocations
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" IP Address: %s\n"
|
||||
" Hostname: '%s'\n"
|
||||
@@ -651,9 +657,9 @@ void EthernetComponent::dump_connect_params_() {
|
||||
" Gateway: %s\n"
|
||||
" DNS1: %s\n"
|
||||
" DNS2: %s",
|
||||
network::IPAddress(&ip.ip).str().c_str(), App.get_name().c_str(),
|
||||
network::IPAddress(&ip.netmask).str().c_str(), network::IPAddress(&ip.gw).str().c_str(),
|
||||
network::IPAddress(dns_ip1).str().c_str(), network::IPAddress(dns_ip2).str().c_str());
|
||||
network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(),
|
||||
network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf),
|
||||
network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf));
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
@@ -665,12 +671,13 @@ void EthernetComponent::dump_connect_params_() {
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" MAC Address: %s\n"
|
||||
" Is Full Duplex: %s\n"
|
||||
" Link Speed: %u",
|
||||
this->get_eth_mac_address_pretty().c_str(), YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL),
|
||||
this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
|
||||
this->get_eth_mac_address_pretty_into_buffer(mac_buf),
|
||||
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
|
||||
}
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
@@ -711,11 +718,16 @@ void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||
}
|
||||
|
||||
std::string EthernetComponent::get_eth_mac_address_pretty() {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
return std::string(this->get_eth_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
|
||||
const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
|
||||
std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
|
||||
uint8_t mac[6];
|
||||
get_eth_mac_address_raw(mac);
|
||||
char buf[18];
|
||||
format_mac_addr_upper(mac, buf);
|
||||
return std::string(buf);
|
||||
format_mac_addr_upper(mac, buf.data());
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
@@ -93,6 +94,7 @@ class EthernetComponent : public Component {
|
||||
void set_use_address(const char *use_address);
|
||||
void get_eth_mac_address_raw(uint8_t *mac);
|
||||
std::string get_eth_mac_address_pretty();
|
||||
const char *get_eth_mac_address_pretty_into_buffer(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf);
|
||||
eth_duplex_t get_duplex_mode();
|
||||
eth_speed_t get_link_speed();
|
||||
bool powerdown();
|
||||
|
||||
@@ -50,7 +50,7 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
|
||||
protected:
|
||||
CallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
LazyCallbackManager<void(const std::string &event_type)> event_callback_;
|
||||
FixedVector<const char *> types_;
|
||||
|
||||
private:
|
||||
|
||||
@@ -50,7 +50,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(): cv.declare_id(FactoryResetComponent),
|
||||
cv.Optional(CONF_MAX_DELAY, default="10s"): cv.All(
|
||||
cv.positive_time_period_seconds,
|
||||
cv.Range(min=cv.TimePeriod(milliseconds=1000)),
|
||||
cv.Range(
|
||||
min=cv.TimePeriod(seconds=1), max=cv.TimePeriod(seconds=65535)
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_ON_INCREMENT): validate_automation(
|
||||
@@ -82,7 +84,7 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
reset_count,
|
||||
config[CONF_MAX_DELAY].total_milliseconds,
|
||||
config[CONF_MAX_DELAY].total_seconds,
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
for conf in config.get(CONF_ON_INCREMENT, []):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user