mirror of
https://github.com/esphome/esphome.git
synced 2025-11-07 10:31:49 +00:00
Compare commits
804 Commits
copilot/fi
...
2025.10.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a29dec6d9 | ||
|
|
63b113d823 | ||
|
|
0381644605 | ||
|
|
48a557b005 | ||
|
|
780ece73ff | ||
|
|
d7fcf8d57b | ||
|
|
82a3ca575f | ||
|
|
5913da5a89 | ||
|
|
8c13105ce1 | ||
|
|
c3fd07f8bc | ||
|
|
d02ed41eb4 | ||
|
|
07504c8208 | ||
|
|
b666b8e261 | ||
|
|
8627b56e36 | ||
|
|
69df07ddcf | ||
|
|
13cfa30c67 | ||
|
|
da1959ab5d | ||
|
|
2b42903e9c | ||
|
|
742c9cbb53 | ||
|
|
e4bc465a3d | ||
|
|
5cec0941f8 | ||
|
|
72a7aeb430 | ||
|
|
53e6b28092 | ||
|
|
7f3c7bb5c6 | ||
|
|
c02c0b2a96 | ||
|
|
5f5092e29f | ||
|
|
2864bf1674 | ||
|
|
132e949927 | ||
|
|
8fa44e471d | ||
|
|
ccedcfb600 | ||
|
|
8b0ec0afe3 | ||
|
|
dca29ed89b | ||
|
|
728726e29e | ||
|
|
79f4ca20b8 | ||
|
|
3eca72e0b8 | ||
|
|
22c0f55cef | ||
|
|
fd8ecc9608 | ||
|
|
ac96a59d58 | ||
|
|
dceed992d8 | ||
|
|
b0c66c1c09 | ||
|
|
8f04a5b944 | ||
|
|
e6c21df30b | ||
|
|
842cb9033a | ||
|
|
a2cb415dfa | ||
|
|
1fac193535 | ||
|
|
34632f78cf | ||
|
|
b93c60e85a | ||
|
|
60dc055509 | ||
|
|
9ad462d8c6 | ||
|
|
f1af9d978c | ||
|
|
785df05631 | ||
|
|
93266ad08f | ||
|
|
2fac813f18 | ||
|
|
a62c7a03dd | ||
|
|
ec63247ae0 | ||
|
|
0fe6e7169c | ||
|
|
a0f4de1bfb | ||
|
|
a541549d23 | ||
|
|
b74715fe14 | ||
|
|
5aff20a624 | ||
|
|
7682b4e9a3 | ||
|
|
6eabf709c6 | ||
|
|
6209d4b493 | ||
|
|
f10c361454 | ||
|
|
27456c1370 | ||
|
|
1aeefbe547 | ||
|
|
3f3bce7ef4 | ||
|
|
0acc58d5a1 | ||
|
|
0b4ef0fea2 | ||
|
|
a067bdb769 | ||
|
|
301e7a7ac5 | ||
|
|
ac566b7fd6 | ||
|
|
fddb8b35f2 | ||
|
|
27e1095cd7 | ||
|
|
fa4541a4f3 | ||
|
|
24dcc1843e | ||
|
|
f670d775ac | ||
|
|
59a31adac2 | ||
|
|
a3c0acc7c9 | ||
|
|
ad2c5b96a9 | ||
|
|
9adc3bd943 | ||
|
|
ad296a7d74 | ||
|
|
fdd422c42a | ||
|
|
3d82301c3d | ||
|
|
2fa49be17d | ||
|
|
75867842ea | ||
|
|
cba85c0925 | ||
|
|
42d1269aaf | ||
|
|
f4df17673b | ||
|
|
e340397b41 | ||
|
|
abeadc7830 | ||
|
|
8d4b347e5c | ||
|
|
a7f556c25f | ||
|
|
3f4250fcd7 | ||
|
|
b532e04ae4 | ||
|
|
697cab45dd | ||
|
|
a88182c8e3 | ||
|
|
8cfb6578d1 | ||
|
|
eb16d322cd | ||
|
|
22e06ba063 | ||
|
|
7147479f90 | ||
|
|
e55df1babc | ||
|
|
4c8fc5f4e6 | ||
|
|
646508006c | ||
|
|
9384f0683b | ||
|
|
5e7f5bf890 | ||
|
|
2a8796437d | ||
|
|
1635767aa2 | ||
|
|
192856e8d1 | ||
|
|
71be5a5f65 | ||
|
|
f86b83cda5 | ||
|
|
74c055745f | ||
|
|
3edcdc7d80 | ||
|
|
94fea68e3e | ||
|
|
6880f9fc5c | ||
|
|
26ebac8cb8 | ||
|
|
5cf0046601 | ||
|
|
c68017ddb4 | ||
|
|
cfd241ff29 | ||
|
|
f757a19e82 | ||
|
|
e8854e0659 | ||
|
|
a3622d878d | ||
|
|
da2089c8be | ||
|
|
118663f9e2 | ||
|
|
4a99987bfe | ||
|
|
d164c06f01 | ||
|
|
972987acdf | ||
|
|
eea2b6b81b | ||
|
|
f62e06104e | ||
|
|
f26e71bae6 | ||
|
|
c6e4a7911c | ||
|
|
e2c5eeef97 | ||
|
|
7ea51b1865 | ||
|
|
aa1afbd152 | ||
|
|
20d9ae699c | ||
|
|
c0fb0ae06f | ||
|
|
9b6d62cd69 | ||
|
|
5932a4bd0e | ||
|
|
84c3cf5f17 | ||
|
|
120a445abf | ||
|
|
41c073a451 | ||
|
|
0fd71ca211 | ||
|
|
19439199cc | ||
|
|
39d5cbc74a | ||
|
|
722c5a94f2 | ||
|
|
7b48fc292f | ||
|
|
6c7d92e726 | ||
|
|
b1859c50bd | ||
|
|
3f9924eac2 | ||
|
|
874db20b7d | ||
|
|
2eea674c04 | ||
|
|
0137954f2b | ||
|
|
0a40a30e4a | ||
|
|
d43b844e06 | ||
|
|
2596b6096f | ||
|
|
6f8e82aeb6 | ||
|
|
ca0e738799 | ||
|
|
14a23101f2 | ||
|
|
2b389bb8f2 | ||
|
|
89c3340ef6 | ||
|
|
ba0532cda7 | ||
|
|
5419b8bddb | ||
|
|
624868bb05 | ||
|
|
f2aa5a754c | ||
|
|
638c6cc14e | ||
|
|
8137d7600a | ||
|
|
08afc3030a | ||
|
|
1deb79a24b | ||
|
|
de21c61b6a | ||
|
|
db1aa82350 | ||
|
|
fe4799b300 | ||
|
|
93e18e850e | ||
|
|
5cef75dbe1 | ||
|
|
4194a940ae | ||
|
|
59c0ffb98b | ||
|
|
29658b79bc | ||
|
|
158a59aa83 | ||
|
|
c95180504a | ||
|
|
848ba6b717 | ||
|
|
922f4b6352 | ||
|
|
fd3c05b42e | ||
|
|
ab1f8326ee | ||
|
|
2a915e4efd | ||
|
|
f5e85a424f | ||
|
|
c69603d916 | ||
|
|
d75b7708a5 | ||
|
|
b023453e81 | ||
|
|
a5ba6237cb | ||
|
|
0e623055df | ||
|
|
6018f5f5d1 | ||
|
|
96868aa754 | ||
|
|
83d86c8c59 | ||
|
|
7703cabb7b | ||
|
|
300f1de11c | ||
|
|
3b73738d9f | ||
|
|
b176d1f890 | ||
|
|
2aaafd6ebb | ||
|
|
a96c013eb1 | ||
|
|
054b215d8d | ||
|
|
e3e98e2568 | ||
|
|
29db576f79 | ||
|
|
58166b3e71 | ||
|
|
345fc0b6ca | ||
|
|
127058e700 | ||
|
|
57f7a709cf | ||
|
|
f2a9e9265e | ||
|
|
1ecd26adb5 | ||
|
|
6d9fc672d5 | ||
|
|
b9361b0868 | ||
|
|
0246a8eb1d | ||
|
|
a56d044d98 | ||
|
|
f6253d52b4 | ||
|
|
77dff52183 | ||
|
|
4b86f31b66 | ||
|
|
78655968df | ||
|
|
ab79e596b5 | ||
|
|
ef73ae2116 | ||
|
|
0111f725ff | ||
|
|
34b4cb46f6 | ||
|
|
a2f833d665 | ||
|
|
a7042687c1 | ||
|
|
0d2d18c198 | ||
|
|
3f03e8c423 | ||
|
|
9dd6be4061 | ||
|
|
2bf79a607f | ||
|
|
61a11547ca | ||
|
|
abf522bbb9 | ||
|
|
25fc16163b | ||
|
|
55593628ef | ||
|
|
1f90d89731 | ||
|
|
1560b8b8e2 | ||
|
|
b26776fad4 | ||
|
|
875ada86b0 | ||
|
|
195d1be4a9 | ||
|
|
2b12ff5874 | ||
|
|
250b94d113 | ||
|
|
28199c1cf8 | ||
|
|
eeb3ccaef7 | ||
|
|
460eb219ba | ||
|
|
cef9cf49bf | ||
|
|
28f09f9ed1 | ||
|
|
3eb502b328 | ||
|
|
7af77d0f82 | ||
|
|
1c229947a8 | ||
|
|
74f09a2b59 | ||
|
|
549626bee2 | ||
|
|
65a1d2b2ff | ||
|
|
f7ed127182 | ||
|
|
44767c32cf | ||
|
|
0cc03dfe32 | ||
|
|
1922b7b3ed | ||
|
|
f22143f090 | ||
|
|
be92903a6f | ||
|
|
538941b3fd | ||
|
|
ce8ac8b89d | ||
|
|
6d0f134ff1 | ||
|
|
11ccf0e591 | ||
|
|
adfacdf1b7 | ||
|
|
f8226cd481 | ||
|
|
63326cbd6d | ||
|
|
d0d7abb542 | ||
|
|
cd7922faaf | ||
|
|
365e3afa9b | ||
|
|
e9c2e211ef | ||
|
|
afda9500bf | ||
|
|
bc7fc8df18 | ||
|
|
2f8a4d0caa | ||
|
|
a7ee7b962e | ||
|
|
3cb2a4569c | ||
|
|
3b20969171 | ||
|
|
3b40172073 | ||
|
|
2e220fcca2 | ||
|
|
56e8af79c3 | ||
|
|
25e9ec1782 | ||
|
|
1771c852af | ||
|
|
8714a45a5c | ||
|
|
5e94460608 | ||
|
|
d302c0c600 | ||
|
|
5c943d7c13 | ||
|
|
7629903afb | ||
|
|
68eb4091b8 | ||
|
|
5062e7a0e1 | ||
|
|
30bb640c89 | ||
|
|
fbb48c504f | ||
|
|
440b0b5574 | ||
|
|
c64d385fa6 | ||
|
|
0432a10543 | ||
|
|
4729bc87fa | ||
|
|
e3b64103cc | ||
|
|
ebdcb3e4d9 | ||
|
|
971522574d | ||
|
|
73e939dbbc | ||
|
|
a96798ef98 | ||
|
|
923e7049f1 | ||
|
|
26df542036 | ||
|
|
1ccec6950a | ||
|
|
b3a122de3c | ||
|
|
9ea3643b74 | ||
|
|
de617c85c7 | ||
|
|
e47f4ef602 | ||
|
|
9c201afe76 | ||
|
|
2bb64a189d | ||
|
|
9853a2e6ab | ||
|
|
961be7fd12 | ||
|
|
a5a21f47d1 | ||
|
|
a06cd84974 | ||
|
|
e3703b43c1 | ||
|
|
f6dc25c0ce | ||
|
|
fad0ec7793 | ||
|
|
a302cec993 | ||
|
|
6781da45cb | ||
|
|
37d526f003 | ||
|
|
d74cfefeef | ||
|
|
1ffb9d972a | ||
|
|
4e5339801b | ||
|
|
b8cee477fe | ||
|
|
ff2df278d6 | ||
|
|
429e989b69 | ||
|
|
28541bdb1c | ||
|
|
11c595bb09 | ||
|
|
fd888eaa68 | ||
|
|
3a233b2fd0 | ||
|
|
d2df232706 | ||
|
|
404e679e66 | ||
|
|
4426bf6029 | ||
|
|
8d401ad05a | ||
|
|
e542816f7d | ||
|
|
12cadf0a04 | ||
|
|
adc3d3127d | ||
|
|
61ab682099 | ||
|
|
27fa18dcec | ||
|
|
22989592f0 | ||
|
|
1f4b10f523 | ||
|
|
cbaf8d309b | ||
|
|
c05b7cca5e | ||
|
|
6ac395da6d | ||
|
|
54616ae1b4 | ||
|
|
e33dcda907 | ||
|
|
04c1b90e57 | ||
|
|
ddb8fedef7 | ||
|
|
04f4f79cb4 | ||
|
|
8890071360 | ||
|
|
4b3a997a8e | ||
|
|
660223e269 | ||
|
|
6d1de2106e | ||
|
|
90e33306f1 | ||
|
|
f3ac21b3b4 | ||
|
|
4859fe67eb | ||
|
|
a723673dcc | ||
|
|
612fb4cc3c | ||
|
|
5fac67d195 | ||
|
|
d671862e9a | ||
|
|
2a4ab6a811 | ||
|
|
459ef7f262 | ||
|
|
bd9dc43e59 | ||
|
|
971de64494 | ||
|
|
926fdcbecd | ||
|
|
6b147312cd | ||
|
|
2d9152d9b9 | ||
|
|
24f9550ce5 | ||
|
|
3427aaab8c | ||
|
|
4e17d14acc | ||
|
|
1750f02ef3 | ||
|
|
ae158179bd | ||
|
|
c601494779 | ||
|
|
646f4e66be | ||
|
|
5b5e5c213c | ||
|
|
46235684b1 | ||
|
|
5b702a1efa | ||
|
|
56e9fd2e38 | ||
|
|
65f15a706f | ||
|
|
eee64cc3a6 | ||
|
|
f43fb3c3a3 | ||
|
|
79b0025fe6 | ||
|
|
c6a039a72f | ||
|
|
6f1fa094c2 | ||
|
|
1d5a3b647d | ||
|
|
af3e1788d1 | ||
|
|
b946cb160d | ||
|
|
e0241e9dcd | ||
|
|
1accc409f6 | ||
|
|
f756de276b | ||
|
|
ac07a00141 | ||
|
|
7ae11de2e4 | ||
|
|
bb6be9c939 | ||
|
|
9c85a7eff3 | ||
|
|
10a665b864 | ||
|
|
35dce3c80d | ||
|
|
7e6b11ce84 | ||
|
|
adcba4fd9a | ||
|
|
d3592c451b | ||
|
|
24eb33a1c0 | ||
|
|
cf1fef8cfb | ||
|
|
28bba0666c | ||
|
|
4390fd80a3 | ||
|
|
4813c5134e | ||
|
|
bbef0e173e | ||
|
|
3240e19a7c | ||
|
|
ac0cd946f0 | ||
|
|
61bac6c6e6 | ||
|
|
5fd64c5c89 | ||
|
|
625f108183 | ||
|
|
c45efe8f40 | ||
|
|
fe1371f4dc | ||
|
|
e3f8a36eaa | ||
|
|
41f0d1c622 | ||
|
|
6469bb168d | ||
|
|
7a869a33f0 | ||
|
|
af0da3f897 | ||
|
|
32e4eb26ad | ||
|
|
10aae33979 | ||
|
|
56e85b3ef9 | ||
|
|
55dd12c66b | ||
|
|
9dd17b464d | ||
|
|
2401f81be3 | ||
|
|
52a7e26c6d | ||
|
|
d729dc20a8 | ||
|
|
d3b7a9687b | ||
|
|
9d7fc11108 | ||
|
|
7969627d3e | ||
|
|
82d2e367d4 | ||
|
|
972aa691e4 | ||
|
|
ac61b8f893 | ||
|
|
d9f625e5c8 | ||
|
|
e218f16f0f | ||
|
|
422d209786 | ||
|
|
e972e1f8c2 | ||
|
|
cfb90b7b18 | ||
|
|
8976ea2436 | ||
|
|
01ff09064d | ||
|
|
39212f0d7f | ||
|
|
8993f4e6b4 | ||
|
|
7adad0ee49 | ||
|
|
dd8815ec9d | ||
|
|
59e62a1f44 | ||
|
|
f5f84fe825 | ||
|
|
90c2fdd565 | ||
|
|
f6d69231e8 | ||
|
|
5cc0e21bc7 | ||
|
|
703b592793 | ||
|
|
75c9430d91 | ||
|
|
e5bba00deb | ||
|
|
8d90f13e97 | ||
|
|
666e33e70b | ||
|
|
7eaaa4e426 | ||
|
|
166ad942ef | ||
|
|
0ff08bbc09 | ||
|
|
6e2bcabbc9 | ||
|
|
afa191ae41 | ||
|
|
93da52c4d2 | ||
|
|
0cc0979674 | ||
|
|
629f1e94f1 | ||
|
|
8c28f346c7 | ||
|
|
3cf36e2f94 | ||
|
|
1ac07c96b1 | ||
|
|
91228c82e6 | ||
|
|
28d16728d3 | ||
|
|
f24a182ba2 | ||
|
|
0065fe1516 | ||
|
|
148fa698cc | ||
|
|
b25506b045 | ||
|
|
0c737fc4df | ||
|
|
a8b8507ffc | ||
|
|
c33bb3a8a9 | ||
|
|
4d09932320 | ||
|
|
e018b15641 | ||
|
|
3fd469cfe8 | ||
|
|
1359142106 | ||
|
|
487ba4dad0 | ||
|
|
694c590eb6 | ||
|
|
b74463c3e6 | ||
|
|
98e8a0c201 | ||
|
|
91b2f75d04 | ||
|
|
f1806046a9 | ||
|
|
5b283d6d38 | ||
|
|
1340665ac7 | ||
|
|
1510db277c | ||
|
|
a49669ee58 | ||
|
|
09b40b882e | ||
|
|
0069163d31 | ||
|
|
86c2af4882 | ||
|
|
b4b795dcaf | ||
|
|
b8ed7ec145 | ||
|
|
365a427b57 | ||
|
|
e327ae8c95 | ||
|
|
4c2f356b35 | ||
|
|
e55bce83e3 | ||
|
|
ba2433197e | ||
|
|
c471bdb446 | ||
|
|
cbac9caa52 | ||
|
|
edf7094662 | ||
|
|
25489b6009 | ||
|
|
dc45a613f3 | ||
|
|
e0617e01e0 | ||
|
|
c7ee727af4 | ||
|
|
c5b2a9e24b | ||
|
|
101d553df9 | ||
|
|
8fb6420b1c | ||
|
|
c03d978b46 | ||
|
|
2d3cdf60ba | ||
|
|
a29fef166b | ||
|
|
9fe94f1201 | ||
|
|
1b8978a89a | ||
|
|
6f188d1284 | ||
|
|
a1a336783e | ||
|
|
c55bc93f70 | ||
|
|
de998f2f39 | ||
|
|
950299e52b | ||
|
|
23c6650902 | ||
|
|
5759692627 | ||
|
|
0ab65c225e | ||
|
|
8aeb6d3ba2 | ||
|
|
c3359edb33 | ||
|
|
4d681ffe3d | ||
|
|
68628a85b1 | ||
|
|
086f1982fa | ||
|
|
5ba1c32242 | ||
|
|
d2b23ba3a7 | ||
|
|
83fbd77c4a | ||
|
|
1a054299d4 | ||
|
|
e3fb9c2a78 | ||
|
|
d1276dc6df | ||
|
|
f286bc57f3 | ||
|
|
ed48282d09 | ||
|
|
2ddd8c72d6 | ||
|
|
d0b4bc48e4 | ||
|
|
77dbe77117 | ||
|
|
6daeffcefd | ||
|
|
6d834c019d | ||
|
|
905e2906fe | ||
|
|
a25b544c3b | ||
|
|
da21174c6d | ||
|
|
e29f0ee7f8 | ||
|
|
983b3cb879 | ||
|
|
fd568d9af3 | ||
|
|
ca72286386 | ||
|
|
dea68bebd8 | ||
|
|
ef98f67b41 | ||
|
|
6a92b691a0 | ||
|
|
bc960cf6d2 | ||
|
|
461ce69296 | ||
|
|
6a20e6f9ad | ||
|
|
cde00a1f4c | ||
|
|
5dc691874b | ||
|
|
c526ab9a3f | ||
|
|
07875a8b1e | ||
|
|
ba4789970c | ||
|
|
015977cfdf | ||
|
|
e513c0f004 | ||
|
|
a11970aee0 | ||
|
|
4ab37b069b | ||
|
|
b6bb6699d1 | ||
|
|
078eaff9a8 | ||
|
|
a7786b75a0 | ||
|
|
d4c11dac8c | ||
|
|
2f2f2f7d15 | ||
|
|
a92a08c2de | ||
|
|
75595b08be | ||
|
|
3c7aba0681 | ||
|
|
e5d1c30797 | ||
|
|
c171d13c8c | ||
|
|
65d63de9b6 | ||
|
|
9e712e4127 | ||
|
|
9007621fd7 | ||
|
|
c01a26607e | ||
|
|
f6ca70970f | ||
|
|
4dc11f05a7 | ||
|
|
5e508f7461 | ||
|
|
2aceb56606 | ||
|
|
d071a074ef | ||
|
|
7a459c8c20 | ||
|
|
aebd21958a | ||
|
|
c542db8bfe | ||
|
|
d9dcfe66ec | ||
|
|
8517c2e903 | ||
|
|
684384892a | ||
|
|
d560831d79 | ||
|
|
fcc3c8e1b6 | ||
|
|
959ffde60e | ||
|
|
07715dd50f | ||
|
|
03836ee2d2 | ||
|
|
50408d9abb | ||
|
|
0de7259428 | ||
|
|
d054709c2d | ||
|
|
da16887915 | ||
|
|
6da8ec8d55 | ||
|
|
d2752b38c9 | ||
|
|
6004367ee2 | ||
|
|
ecfeb8e4d3 | ||
|
|
456c31262d | ||
|
|
9f02575287 | ||
|
|
07bca6103f | ||
|
|
a58c3950bc | ||
|
|
8fe582309e | ||
|
|
b41a61c76e | ||
|
|
61a5023888 | ||
|
|
4396bc0d1a | ||
|
|
acfce581fa | ||
|
|
88303f39fa | ||
|
|
ca19959d7c | ||
|
|
9737b35579 | ||
|
|
be9c20c357 | ||
|
|
12ba4b142e | ||
|
|
c096c6934d | ||
|
|
17f787fc36 | ||
|
|
5cd9a86dcb | ||
|
|
83fe4b4ff3 | ||
|
|
94accd5abe | ||
|
|
3ca0015284 | ||
|
|
33eddb6035 | ||
|
|
72c58ae36d | ||
|
|
35411d199f | ||
|
|
d45944a9e2 | ||
|
|
86f306ba9e | ||
|
|
1b3b2f6e6f | ||
|
|
2adb993242 | ||
|
|
3ff5b4773b | ||
|
|
2cbf4f30f9 | ||
|
|
56b6dd31f1 | ||
|
|
fc1b49e87d | ||
|
|
0089619518 | ||
|
|
5a6db28f1d | ||
|
|
6819bbd8f8 | ||
|
|
634f687c3e | ||
|
|
e2a9b85924 | ||
|
|
4ccc6aee09 | ||
|
|
0eab908b0e | ||
|
|
3964f9794b | ||
|
|
a45137434b | ||
|
|
9b1ebdb6da | ||
|
|
5a1533bea9 | ||
|
|
0b50ef227b | ||
|
|
0e31bc1a67 | ||
|
|
8e67df8059 | ||
|
|
e1a0949ddb | ||
|
|
c5b2c8d971 | ||
|
|
a8775ba60b | ||
|
|
104906ca11 | ||
|
|
ad5f6f0cfe | ||
|
|
8356f7fcd3 | ||
|
|
225de226b0 | ||
|
|
2aaf951357 | ||
|
|
82718e62e7 | ||
|
|
fd07e1d979 | ||
|
|
4dab9c4400 | ||
|
|
7e23d865e6 | ||
|
|
8f118232e4 | ||
|
|
23554cda06 | ||
|
|
064385eac6 | ||
|
|
6502ed70de | ||
|
|
bb894c3e32 | ||
|
|
c5858b7032 | ||
|
|
99f57ecb73 | ||
|
|
cc6c892678 | ||
|
|
07a98d2525 | ||
|
|
e80f616366 | ||
|
|
46be877594 | ||
|
|
ac8b48a53c | ||
|
|
7fdbd8528a | ||
|
|
80970f972b | ||
|
|
3c7865cd6f | ||
|
|
3a6a66537c | ||
|
|
7118bea031 | ||
|
|
44bd8e5b54 | ||
|
|
efaeb91803 | ||
|
|
761c6c6685 | ||
|
|
1f55486896 | ||
|
|
6818439109 | ||
|
|
0a77423073 | ||
|
|
c29f8d0187 | ||
|
|
2a3f80a82c | ||
|
|
75f3adcd95 | ||
|
|
daf8ec36ab | ||
|
|
6c5632a0b3 | ||
|
|
abecc0e8d8 | ||
|
|
af9ecf3429 | ||
|
|
5fa84439c2 | ||
|
|
5d18afcd99 | ||
|
|
117cffd2b0 | ||
|
|
8ea1a3ed64 | ||
|
|
4f29b3c7aa | ||
|
|
3325592d67 | ||
|
|
0a3ee7d84e | ||
|
|
882237120e | ||
|
|
71efaf097b | ||
|
|
bd60dbb746 | ||
|
|
6b5e43ca72 | ||
|
|
8d61b1e8df | ||
|
|
9c897993bb | ||
|
|
93f9475105 | ||
|
|
95cd224e3e | ||
|
|
b7afeafda9 | ||
|
|
7922462bcf | ||
|
|
46d433775b | ||
|
|
7c4a54de90 | ||
|
|
c3f1596498 | ||
|
|
0d1949a61b | ||
|
|
6a8722f33e | ||
|
|
fff66072d4 | ||
|
|
1c2e1ab3e5 | ||
|
|
68ddd98f5f | ||
|
|
0dda3faed5 | ||
|
|
40c0c36179 | ||
|
|
6b7ced1970 | ||
|
|
ed2b76050b | ||
|
|
113813617d | ||
|
|
c3a209d3f4 | ||
|
|
7ffdaa1f06 | ||
|
|
3a857950bf | ||
|
|
0256e0005e | ||
|
|
c65af68e63 | ||
|
|
ef2121a215 | ||
|
|
bb40b7702d | ||
|
|
6c48f3d719 | ||
|
|
ff52869b4c | ||
|
|
82b7c1224c | ||
|
|
c14c4fb658 | ||
|
|
42aee53dde | ||
|
|
9aa21956c8 | ||
|
|
4c2874a32b | ||
|
|
45b88f2da9 | ||
|
|
8f53961496 | ||
|
|
5cf0e4d9dd | ||
|
|
b70983ed09 | ||
|
|
ffa89eb2d3 | ||
|
|
8b67d6dfec | ||
|
|
581b4ef5a1 | ||
|
|
da02f970d4 | ||
|
|
2fc0a11596 | ||
|
|
5a8f722316 | ||
|
|
279f56141e | ||
|
|
6bfe281d18 | ||
|
|
a1371aea37 | ||
|
|
d5c9c10b3b | ||
|
|
cef39e7c59 | ||
|
|
2b9e1ce315 | ||
|
|
ff9ddb9d68 | ||
|
|
676c51ffa0 | ||
|
|
7e4d09dbd8 | ||
|
|
58504662d8 | ||
|
|
83b69519dd | ||
|
|
d4d1a96f9b | ||
|
|
76fd104fb6 | ||
|
|
c4d1b1317a | ||
|
|
14bc83342f | ||
|
|
a1461c5293 | ||
|
|
73b2db8af5 | ||
|
|
a7a119f576 | ||
|
|
1ba76f5f2e | ||
|
|
37a9ad6a0d | ||
|
|
c0a62c0be1 | ||
|
|
bfb14e1cf9 | ||
|
|
1415e02e40 | ||
|
|
81f907e994 | ||
|
|
61008bc8a9 | ||
|
|
6d66ddd68d | ||
|
|
fc180251be | ||
|
|
ee1d4f27ef | ||
|
|
325ec0a0ae | ||
|
|
6071f4b02c | ||
|
|
083ac8ce8e | ||
|
|
4ceda31f32 | ||
|
|
5021cc6d5f | ||
|
|
2b3e546203 | ||
|
|
1642d34d29 | ||
|
|
8ceb1b9d60 | ||
|
|
d872c8a999 | ||
|
|
99125c045f | ||
|
|
860a5ef5c0 | ||
|
|
b01f03cc24 | ||
|
|
cfb22e33c9 | ||
|
|
96bbb58f34 | ||
|
|
3edd746c6c | ||
|
|
c308e03e92 | ||
|
|
bd2b3b9da5 | ||
|
|
d443a97dd8 | ||
|
|
58a088e06b | ||
|
|
49a46883ed | ||
|
|
bc03538e25 | ||
|
|
969034b61a | ||
|
|
06eb1b6014 | ||
|
|
589d00f17f | ||
|
|
68c0aa4d6d | ||
|
|
2fddb061e1 | ||
|
|
c85eb448e4 | ||
|
|
396c02c6de | ||
|
|
52c4509208 | ||
|
|
d29cae9c3b | ||
|
|
532e3e370f | ||
|
|
da573a217d | ||
|
|
a9b27d1966 | ||
|
|
0aa3c9685e | ||
|
|
93b28447ee | ||
|
|
52634dac2a | ||
|
|
64c94c1440 | ||
|
|
f7bf1ef52c | ||
|
|
fa8c5e880c | ||
|
|
27ba90ea95 | ||
|
|
469246b8d8 | ||
|
|
50f15735dc | ||
|
|
83d9c02a1b | ||
|
|
701e6099aa | ||
|
|
d59476d0e1 | ||
|
|
fbbb791b0d |
@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
|
|
||||||
## 2. Core Technologies & Stack
|
## 2. Core Technologies & Stack
|
||||||
|
|
||||||
* **Languages:** Python (>=3.10), C++ (gnu++20)
|
* **Languages:** Python (>=3.11), C++ (gnu++20)
|
||||||
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
||||||
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
||||||
* **Configuration:** YAML.
|
* **Configuration:** YAML.
|
||||||
@@ -38,7 +38,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
|
5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
|
||||||
|
|
||||||
* **Platform Support:**
|
* **Platform Support:**
|
||||||
1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (S2, S3, C3, etc.) and both IDF and Arduino frameworks.
|
1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (Original, C2, C3, C5, C6, H2, P4, S2, S3) with ESP-IDF framework. Arduino framework supports only a subset of the variants (Original, C3, S2, S3).
|
||||||
2. **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
|
2. **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
|
||||||
3. **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
|
3. **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
|
||||||
4. **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
|
4. **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
|
||||||
@@ -150,7 +150,8 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
* **Configuration Validation:**
|
* **Configuration Validation:**
|
||||||
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
|
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
|
||||||
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
|
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
|
||||||
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `cv.only_with_arduino`.
|
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `esp32.only_on_variant(...)`, `cv.only_on_esp32`, `cv.only_on_esp8266`, `cv.only_on_rp2040`.
|
||||||
|
* **Framework-Specific:** `cv.only_with_framework(...)`, `cv.only_with_arduino`, `cv.only_with_esp_idf`.
|
||||||
* **Schema Extensions:**
|
* **Schema Extensions:**
|
||||||
```python
|
```python
|
||||||
CONFIG_SCHEMA = cv.Schema({ ... })
|
CONFIG_SCHEMA = cv.Schema({ ... })
|
||||||
@@ -185,6 +186,11 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
└── components/[component]/ # Component-specific tests
|
└── components/[component]/ # Component-specific tests
|
||||||
```
|
```
|
||||||
Run them using `script/test_build_components`. Use `-c <component>` to test specific components and `-t <target>` for specific platforms.
|
Run them using `script/test_build_components`. Use `-c <component>` to test specific components and `-t <target>` for specific platforms.
|
||||||
|
* **Testing All Components Together:** To verify that all components can be tested together without ID conflicts or configuration issues, use:
|
||||||
|
```bash
|
||||||
|
./script/test_component_grouping.py -e config --all
|
||||||
|
```
|
||||||
|
This tests all components in a single build to catch conflicts that might not appear when testing components individually. Use `-e config` for fast configuration validation, or `-e compile` for full compilation testing.
|
||||||
* **Debugging and Troubleshooting:**
|
* **Debugging and Troubleshooting:**
|
||||||
* **Debug Tools:**
|
* **Debug Tools:**
|
||||||
- `esphome config <file>.yaml` to validate configuration.
|
- `esphome config <file>.yaml` to validate configuration.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273
|
049d60eed541730efaa4c0dc5d337b4287bf29b6daa350b5dfc1f23915f1c52f
|
||||||
|
|||||||
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
@@ -47,7 +47,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and push to ghcr by digest
|
- name: Build and push to ghcr by digest
|
||||||
id: build-ghcr
|
id: build-ghcr
|
||||||
uses: docker/build-push-action@v6.18.0
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
@@ -73,7 +73,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and push to dockerhub by digest
|
- name: Build and push to dockerhub by digest
|
||||||
id: build-dockerhub
|
id: build-dockerhub
|
||||||
uses: docker/build-push-action@v6.18.0
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
|
|||||||
4
.github/actions/restore-python/action.yml
vendored
4
.github/actions/restore-python/action.yml
vendored
@@ -17,12 +17,12 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Set up Python ${{ inputs.python-version }}
|
- name: Set up Python ${{ inputs.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.2.3
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
|||||||
36
.github/workflows/auto-label-pr.yml
vendored
36
.github/workflows/auto-label-pr.yml
vendored
@@ -22,17 +22,17 @@ jobs:
|
|||||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@v2
|
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Auto Label PR
|
- name: Auto Label PR
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
@@ -105,7 +105,9 @@ jobs:
|
|||||||
|
|
||||||
// Calculate data from PR files
|
// Calculate data from PR files
|
||||||
const changedFiles = prFiles.map(file => file.filename);
|
const changedFiles = prFiles.map(file => file.filename);
|
||||||
const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||||
|
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||||
|
const totalChanges = totalAdditions + totalDeletions;
|
||||||
|
|
||||||
console.log('Current labels:', currentLabels.join(', '));
|
console.log('Current labels:', currentLabels.join(', '));
|
||||||
console.log('Changed files:', changedFiles.length);
|
console.log('Changed files:', changedFiles.length);
|
||||||
@@ -231,16 +233,21 @@ jobs:
|
|||||||
// Strategy: PR size detection
|
// Strategy: PR size detection
|
||||||
async function detectPRSize() {
|
async function detectPRSize() {
|
||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
const testChanges = prFiles
|
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
|
||||||
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
|
||||||
|
|
||||||
const nonTestChanges = totalChanges - testChanges;
|
|
||||||
|
|
||||||
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
||||||
labels.add('small-pr');
|
labels.add('small-pr');
|
||||||
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testAdditions = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||||
|
const testDeletions = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||||
|
|
||||||
|
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||||
|
|
||||||
// Don't add too-big if mega-pr label is already present
|
// Don't add too-big if mega-pr label is already present
|
||||||
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
||||||
labels.add('too-big');
|
labels.add('too-big');
|
||||||
@@ -375,7 +382,7 @@ jobs:
|
|||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
|
|
||||||
// Check for missing tests
|
// Check for missing tests
|
||||||
if ((allLabels.has('new-component') || allLabels.has('new-platform')) && !allLabels.has('has-tests')) {
|
if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
|
||||||
labels.add('needs-tests');
|
labels.add('needs-tests');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,10 +419,13 @@ jobs:
|
|||||||
|
|
||||||
// Too big message
|
// Too big message
|
||||||
if (finalLabels.includes('too-big')) {
|
if (finalLabels.includes('too-big')) {
|
||||||
const testChanges = prFiles
|
const testAdditions = prFiles
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||||
const nonTestChanges = totalChanges - testChanges;
|
const testDeletions = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||||
|
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||||
|
|
||||||
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||||
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
||||||
|
|||||||
10
.github/workflows/ci-api-proto.yml
vendored
10
.github/workflows/ci-api-proto.yml
vendored
@@ -21,9 +21,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- if: failure()
|
- if: failure()
|
||||||
name: Review PR
|
name: Review PR
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
await github.rest.pulls.createReview({
|
await github.rest.pulls.createReview({
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
run: git diff
|
run: git diff
|
||||||
- if: failure()
|
- if: failure()
|
||||||
name: Archive artifacts
|
name: Archive artifacts
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: generated-proto-files
|
name: generated-proto-files
|
||||||
path: |
|
path: |
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
esphome/components/api/api_pb2_service.*
|
esphome/components/api/api_pb2_service.*
|
||||||
- if: success()
|
- if: success()
|
||||||
name: Dismiss review
|
name: Dismiss review
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
let reviews = await github.rest.pulls.listReviews({
|
let reviews = await github.rest.pulls.listReviews({
|
||||||
|
|||||||
9
.github/workflows/ci-clang-tidy-hash.yml
vendored
9
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
- ".clang-tidy"
|
- ".clang-tidy"
|
||||||
- "platformio.ini"
|
- "platformio.ini"
|
||||||
- "requirements_dev.txt"
|
- "requirements_dev.txt"
|
||||||
|
- "sdkconfig.defaults"
|
||||||
- ".clang-tidy.hash"
|
- ".clang-tidy.hash"
|
||||||
- "script/clang_tidy_hash.py"
|
- "script/clang_tidy_hash.py"
|
||||||
- ".github/workflows/ci-clang-tidy-hash.yml"
|
- ".github/workflows/ci-clang-tidy-hash.yml"
|
||||||
@@ -20,10 +21,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- if: failure()
|
- if: failure()
|
||||||
name: Request changes
|
name: Request changes
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
await github.rest.pulls.createReview({
|
await github.rest.pulls.createReview({
|
||||||
@@ -54,7 +55,7 @@ jobs:
|
|||||||
|
|
||||||
- if: success()
|
- if: success()
|
||||||
name: Dismiss review
|
name: Dismiss review
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
let reviews = await github.rest.pulls.listReviews({
|
let reviews = await github.rest.pulls.listReviews({
|
||||||
|
|||||||
6
.github/workflows/ci-docker.yml
vendored
6
.github/workflows/ci-docker.yml
vendored
@@ -43,13 +43,13 @@ jobs:
|
|||||||
- "docker"
|
- "docker"
|
||||||
# - "lint"
|
# - "lint"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Set TAG
|
- name: Set TAG
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
143
.github/workflows/ci.yml
vendored
143
.github/workflows/ci.yml
vendored
@@ -36,18 +36,18 @@ jobs:
|
|||||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Generate cache-key
|
- name: Generate cache-key
|
||||||
id: cache-key
|
id: cache-key
|
||||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -91,7 +91,7 @@ jobs:
|
|||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
@@ -105,6 +105,7 @@ jobs:
|
|||||||
script/ci-custom.py
|
script/ci-custom.py
|
||||||
script/build_codeowners.py --check
|
script/build_codeowners.py --check
|
||||||
script/build_language_schema.py --check
|
script/build_language_schema.py --check
|
||||||
|
script/generate-esp32-boards.py --check
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
name: Run pytest
|
name: Run pytest
|
||||||
@@ -136,7 +137,7 @@ jobs:
|
|||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
id: restore-python
|
id: restore-python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
@@ -156,12 +157,12 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v5.4.3
|
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Save Python virtual environment cache
|
- name: Save Python virtual environment cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache/save@v4.2.3
|
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -176,10 +177,11 @@ jobs:
|
|||||||
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
||||||
python-linters: ${{ steps.determine.outputs.python-linters }}
|
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||||
changed-components: ${{ steps.determine.outputs.changed-components }}
|
changed-components: ${{ steps.determine.outputs.changed-components }}
|
||||||
|
changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
|
||||||
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
# Fetch enough history to find the merge base
|
# Fetch enough history to find the merge base
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -203,6 +205,7 @@ jobs:
|
|||||||
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
||||||
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||||
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
||||||
|
echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||||
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
integration-tests:
|
integration-tests:
|
||||||
@@ -214,15 +217,15 @@ jobs:
|
|||||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python 3.13
|
- name: Set up Python 3.13
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -287,7 +290,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
# Need history for HEAD~1 to work for checking changed files
|
# Need history for HEAD~1 to work for checking changed files
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -300,14 +303,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@v4.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@v4.2.3
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -366,31 +369,32 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 2
|
max-parallel: 2
|
||||||
matrix:
|
matrix:
|
||||||
file: ${{ fromJson(needs.determine-jobs.outputs.changed-components) }}
|
file: ${{ fromJson(needs.determine-jobs.outputs.changed-components-with-tests) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Cache apt packages
|
||||||
run: |
|
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
|
||||||
sudo apt-get update
|
with:
|
||||||
sudo apt-get install libsdl2-dev
|
packages: libsdl2-dev
|
||||||
|
version: 1.0
|
||||||
|
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: test_build_components -e config -c ${{ matrix.file }}
|
- name: Validate config for ${{ matrix.file }}
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
./script/test_build_components -e config -c ${{ matrix.file }}
|
python3 script/test_build_components.py -e config -c ${{ matrix.file }}
|
||||||
- name: test_build_components -e compile -c ${{ matrix.file }}
|
- name: Compile config for ${{ matrix.file }}
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
./script/test_build_components -e compile -c ${{ matrix.file }}
|
python3 script/test_build_components.py -e compile -c ${{ matrix.file }}
|
||||||
|
|
||||||
test-build-components-splitter:
|
test-build-components-splitter:
|
||||||
name: Split components for testing into 20 groups maximum
|
name: Split components for intelligent grouping (40 weighted per batch)
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
@@ -400,15 +404,27 @@ jobs:
|
|||||||
matrix: ${{ steps.split.outputs.components }}
|
matrix: ${{ steps.split.outputs.components }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Split components into 20 groups
|
- name: Restore Python
|
||||||
|
uses: ./.github/actions/restore-python
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
|
- name: Split components intelligently based on bus configurations
|
||||||
id: split
|
id: split
|
||||||
run: |
|
run: |
|
||||||
components=$(echo '${{ needs.determine-jobs.outputs.changed-components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
|
. venv/bin/activate
|
||||||
echo "components=$components" >> $GITHUB_OUTPUT
|
|
||||||
|
# Use intelligent splitter that groups components with same bus configs
|
||||||
|
components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}'
|
||||||
|
|
||||||
|
echo "Splitting components intelligently..."
|
||||||
|
output=$(python3 script/split_components_for_ci.py --components "$components" --batch-size 40 --output github)
|
||||||
|
|
||||||
|
echo "$output" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
test-build-components-split:
|
test-build-components-split:
|
||||||
name: Test split components
|
name: Test components batch (${{ matrix.components }})
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
@@ -417,39 +433,62 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
|
if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 4
|
max-parallel: ${{ (github.base_ref == 'beta' || github.base_ref == 'release') && 8 || 4 }}
|
||||||
matrix:
|
matrix:
|
||||||
components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
|
components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Show disk space
|
||||||
|
run: |
|
||||||
|
echo "Available disk space:"
|
||||||
|
df -h
|
||||||
|
|
||||||
- name: List components
|
- name: List components
|
||||||
run: echo ${{ matrix.components }}
|
run: echo ${{ matrix.components }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Cache apt packages
|
||||||
run: |
|
uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
|
||||||
sudo apt-get update
|
with:
|
||||||
sudo apt-get install libsdl2-dev
|
packages: libsdl2-dev
|
||||||
|
version: 1.0
|
||||||
|
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Validate config
|
- name: Validate and compile components with intelligent grouping
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
for component in ${{ matrix.components }}; do
|
# Use /mnt for build files (70GB available vs ~29GB on /)
|
||||||
./script/test_build_components -e config -c $component
|
# Bind mount PlatformIO directory to /mnt (tools, packages, build cache all go there)
|
||||||
done
|
sudo mkdir -p /mnt/platformio
|
||||||
- name: Compile config
|
sudo chown $USER:$USER /mnt/platformio
|
||||||
run: |
|
mkdir -p ~/.platformio
|
||||||
. venv/bin/activate
|
sudo mount --bind /mnt/platformio ~/.platformio
|
||||||
mkdir build_cache
|
|
||||||
export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache
|
# Bind mount test build directory to /mnt
|
||||||
for component in ${{ matrix.components }}; do
|
sudo mkdir -p /mnt/test_build_components_build
|
||||||
./script/test_build_components -e compile -c $component
|
sudo chown $USER:$USER /mnt/test_build_components_build
|
||||||
done
|
mkdir -p tests/test_build_components/build
|
||||||
|
sudo mount --bind /mnt/test_build_components_build tests/test_build_components/build
|
||||||
|
|
||||||
|
# Convert space-separated components to comma-separated for Python script
|
||||||
|
components_csv=$(echo "${{ matrix.components }}" | tr ' ' ',')
|
||||||
|
|
||||||
|
echo "Testing components: $components_csv"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Run config validation with grouping
|
||||||
|
python3 script/test_build_components.py -e config -c "$components_csv" -f
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Config validation passed! Starting compilation..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Run compilation with grouping
|
||||||
|
python3 script/test_build_components.py -e compile -c "$components_csv" -f
|
||||||
|
|
||||||
pre-commit-ci-lite:
|
pre-commit-ci-lite:
|
||||||
name: pre-commit.ci lite
|
name: pre-commit.ci lite
|
||||||
@@ -459,16 +498,16 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
|
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- uses: pre-commit/action@v3.0.1
|
- uses: esphome/action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache
|
||||||
env:
|
env:
|
||||||
SKIP: pylint,clang-tidy-hash
|
SKIP: pylint,clang-tidy-hash
|
||||||
- uses: pre-commit-ci/lite-action@v1.1.0
|
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
ci-status:
|
ci-status:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Request reviews from component codeowners
|
- name: Request reviews from component codeowners
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const owner = context.repo.owner;
|
const owner = context.repo.owner;
|
||||||
|
|||||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -54,11 +54,11 @@ jobs:
|
|||||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
@@ -86,6 +86,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
2
.github/workflows/external-component-bot.yml
vendored
2
.github/workflows/external-component-bot.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Add external component comment
|
- name: Add external component comment
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
2
.github/workflows/issue-codeowner-notify.yml
vendored
2
.github/workflows/issue-codeowner-notify.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Notify codeowners for component issues
|
- name: Notify codeowners for component issues
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const owner = context.repo.owner;
|
const owner = context.repo.owner;
|
||||||
|
|||||||
24
.github/workflows/needs-docs.yml
vendored
24
.github/workflows/needs-docs.yml
vendored
@@ -1,24 +0,0 @@
|
|||||||
name: Needs Docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [labeled, unlabeled]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
name: Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check for needs-docs label
|
|
||||||
uses: actions/github-script@v7.0.1
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number
|
|
||||||
});
|
|
||||||
const needsDocs = labels.find(label => label.name === 'needs-docs');
|
|
||||||
if (needsDocs) {
|
|
||||||
core.setFailed('Pull request needs docs');
|
|
||||||
}
|
|
||||||
34
.github/workflows/release.yml
vendored
34
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Get tag
|
- name: Get tag
|
||||||
id: tag
|
id: tag
|
||||||
# yamllint disable rule:line-length
|
# yamllint disable rule:line-length
|
||||||
@@ -60,9 +60,9 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
pip3 install build
|
pip3 install build
|
||||||
python3 -m build
|
python3 -m build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: pypa/gh-action-pypi-publish@v1.12.4
|
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||||
with:
|
with:
|
||||||
skip-existing: true
|
skip-existing: true
|
||||||
|
|
||||||
@@ -92,22 +92,22 @@ jobs:
|
|||||||
os: "ubuntu-24.04-arm"
|
os: "ubuntu-24.04-arm"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
uses: docker/login-action@v3.4.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
uses: docker/login-action@v3.4.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -138,7 +138,7 @@ jobs:
|
|||||||
# version: ${{ needs.init.outputs.tag }}
|
# version: ${{ needs.init.outputs.tag }}
|
||||||
|
|
||||||
- name: Upload digests
|
- name: Upload digests
|
||||||
uses: actions/upload-artifact@v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: digests-${{ matrix.platform.arch }}
|
name: digests-${{ matrix.platform.arch }}
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
@@ -168,27 +168,27 @@ jobs:
|
|||||||
- ghcr
|
- ghcr
|
||||||
- dockerhub
|
- dockerhub
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
uses: docker/login-action@v3.4.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
if: matrix.registry == 'ghcr'
|
if: matrix.registry == 'ghcr'
|
||||||
uses: docker/login-action@v3.4.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -220,7 +220,7 @@ jobs:
|
|||||||
- deploy-manifest
|
- deploy-manifest
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Workflow
|
- name: Trigger Workflow
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
@@ -246,7 +246,7 @@ jobs:
|
|||||||
environment: ${{ needs.init.outputs.deploy_env }}
|
environment: ${{ needs.init.outputs.deploy_env }}
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Workflow
|
- name: Trigger Workflow
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
|
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
52
.github/workflows/stale.yml
vendored
52
.github/workflows/stale.yml
vendored
@@ -15,36 +15,52 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
|
if: github.repository_owner == 'esphome'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9.1.0
|
- name: Stale
|
||||||
|
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||||
with:
|
with:
|
||||||
|
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
operations-per-run: 150
|
||||||
|
|
||||||
|
# The 90 day stale policy for PRs
|
||||||
|
# - PRs
|
||||||
|
# - No PRs marked as "not-stale"
|
||||||
|
# - No Issues (see below)
|
||||||
days-before-pr-stale: 90
|
days-before-pr-stale: 90
|
||||||
days-before-pr-close: 7
|
days-before-pr-close: 7
|
||||||
days-before-issue-stale: -1
|
|
||||||
days-before-issue-close: -1
|
|
||||||
remove-stale-when-updated: true
|
|
||||||
stale-pr-label: "stale"
|
stale-pr-label: "stale"
|
||||||
exempt-pr-labels: "not-stale"
|
exempt-pr-labels: "not-stale"
|
||||||
stale-pr-message: >
|
stale-pr-message: >
|
||||||
There hasn't been any activity on this pull request recently. This
|
There hasn't been any activity on this pull request recently. This
|
||||||
pull request has been automatically marked as stale because of that
|
pull request has been automatically marked as stale because of that
|
||||||
and will be closed if no further activity occurs within 7 days.
|
and will be closed if no further activity occurs within 7 days.
|
||||||
Thank you for your contributions.
|
|
||||||
|
|
||||||
# Use stale to automatically close issues with a
|
If you are the author of this PR, please leave a comment if you want
|
||||||
# reference to the issue tracker
|
to keep it open. Also, please rebase your PR onto the latest dev
|
||||||
close-issues:
|
branch to ensure that it's up to date with the latest changes.
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
Thank you for your contribution!
|
||||||
- uses: actions/stale@v9.1.0
|
|
||||||
with:
|
# The 90 day stale policy for Issues
|
||||||
days-before-pr-stale: -1
|
# - Issues
|
||||||
days-before-pr-close: -1
|
# - No Issues marked as "not-stale"
|
||||||
days-before-issue-stale: 1
|
# - No PRs (see above)
|
||||||
days-before-issue-close: 1
|
days-before-issue-stale: 90
|
||||||
remove-stale-when-updated: true
|
days-before-issue-close: 7
|
||||||
stale-issue-label: "stale"
|
stale-issue-label: "stale"
|
||||||
exempt-issue-labels: "not-stale"
|
exempt-issue-labels: "not-stale"
|
||||||
stale-issue-message: >
|
stale-issue-message: >
|
||||||
https://github.com/esphome/esphome/issues/430
|
There hasn't been any activity on this issue recently. Due to the
|
||||||
|
high number of incoming GitHub notifications, we have to clean some
|
||||||
|
of the old issues, as many of them have already been resolved with
|
||||||
|
the latest updates.
|
||||||
|
|
||||||
|
Please make sure to update to the latest ESPHome version and
|
||||||
|
check if that solves the issue. Let us know if that works for you by
|
||||||
|
adding a comment 👍
|
||||||
|
|
||||||
|
This issue has now been marked as stale and will be closed if no
|
||||||
|
further activity occurs. Thank you for your contributions.
|
||||||
|
|||||||
30
.github/workflows/status-check-labels.yml
vendored
Normal file
30
.github/workflows/status-check-labels.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Status check labels
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled, unlabeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check ${{ matrix.label }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
label:
|
||||||
|
- needs-docs
|
||||||
|
- merge-after-release
|
||||||
|
steps:
|
||||||
|
- name: Check for ${{ matrix.label }} label
|
||||||
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number
|
||||||
|
});
|
||||||
|
const hasLabel = labels.find(label => label.name === '${{ matrix.label }}');
|
||||||
|
if (hasLabel) {
|
||||||
|
core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}');
|
||||||
|
}
|
||||||
13
.github/workflows/sync-device-classes.yml
vendored
13
.github/workflows/sync-device-classes.yml
vendored
@@ -13,16 +13,16 @@ jobs:
|
|||||||
if: github.repository == 'esphome/esphome'
|
if: github.repository == 'esphome/esphome'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Checkout Home Assistant
|
- name: Checkout Home Assistant
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
repository: home-assistant/core
|
repository: home-assistant/core
|
||||||
path: lib/home-assistant
|
path: lib/home-assistant
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.13
|
python-version: 3.13
|
||||||
|
|
||||||
@@ -30,13 +30,18 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -e lib/home-assistant
|
pip install -e lib/home-assistant
|
||||||
|
pip install -r requirements_test.txt pre-commit
|
||||||
|
|
||||||
- name: Sync
|
- name: Sync
|
||||||
run: |
|
run: |
|
||||||
python ./script/sync-device_class.py
|
python ./script/sync-device_class.py
|
||||||
|
|
||||||
|
- name: Run pre-commit hooks
|
||||||
|
run: |
|
||||||
|
python script/run-in-env.py pre-commit run --all-files
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: peter-evans/create-pull-request@v7.0.8
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||||
with:
|
with:
|
||||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||||
committer: esphomebot <esphome@openhomefoundation.org>
|
committer: esphomebot <esphome@openhomefoundation.org>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ ci:
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.12.7
|
rev: v0.14.0
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|||||||
61
CODEOWNERS
61
CODEOWNERS
@@ -40,11 +40,11 @@ esphome/components/analog_threshold/* @ianchi
|
|||||||
esphome/components/animation/* @syndlex
|
esphome/components/animation/* @syndlex
|
||||||
esphome/components/anova/* @buxtronix
|
esphome/components/anova/* @buxtronix
|
||||||
esphome/components/apds9306/* @aodrenah
|
esphome/components/apds9306/* @aodrenah
|
||||||
esphome/components/api/* @OttoWinter
|
esphome/components/api/* @esphome/core
|
||||||
esphome/components/as5600/* @ammmze
|
esphome/components/as5600/* @ammmze
|
||||||
esphome/components/as5600/sensor/* @ammmze
|
esphome/components/as5600/sensor/* @ammmze
|
||||||
esphome/components/as7341/* @mrgnr
|
esphome/components/as7341/* @mrgnr
|
||||||
esphome/components/async_tcp/* @OttoWinter
|
esphome/components/async_tcp/* @esphome/core
|
||||||
esphome/components/at581x/* @X-Ryl669
|
esphome/components/at581x/* @X-Ryl669
|
||||||
esphome/components/atc_mithermometer/* @ahpohl
|
esphome/components/atc_mithermometer/* @ahpohl
|
||||||
esphome/components/atm90e26/* @danieltwagner
|
esphome/components/atm90e26/* @danieltwagner
|
||||||
@@ -66,10 +66,10 @@ esphome/components/binary_sensor/* @esphome/core
|
|||||||
esphome/components/bk72xx/* @kuba2k2
|
esphome/components/bk72xx/* @kuba2k2
|
||||||
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
|
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
|
||||||
esphome/components/bl0939/* @ziceva
|
esphome/components/bl0939/* @ziceva
|
||||||
esphome/components/bl0940/* @tobias-
|
esphome/components/bl0940/* @dan-s-github @tobias-
|
||||||
esphome/components/bl0942/* @dbuezas @dwmw2
|
esphome/components/bl0942/* @dbuezas @dwmw2
|
||||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||||
esphome/components/bluetooth_proxy/* @jesserockz
|
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
||||||
esphome/components/bme280_base/* @esphome/core
|
esphome/components/bme280_base/* @esphome/core
|
||||||
esphome/components/bme280_spi/* @apbodrov
|
esphome/components/bme280_spi/* @apbodrov
|
||||||
esphome/components/bme680_bsec/* @trvrnrth
|
esphome/components/bme680_bsec/* @trvrnrth
|
||||||
@@ -88,10 +88,11 @@ esphome/components/bp1658cj/* @Cossid
|
|||||||
esphome/components/bp5758d/* @Cossid
|
esphome/components/bp5758d/* @Cossid
|
||||||
esphome/components/button/* @esphome/core
|
esphome/components/button/* @esphome/core
|
||||||
esphome/components/bytebuffer/* @clydebarrow
|
esphome/components/bytebuffer/* @clydebarrow
|
||||||
esphome/components/camera/* @DT-art1 @bdraco
|
esphome/components/camera/* @bdraco @DT-art1
|
||||||
|
esphome/components/camera_encoder/* @DT-art1
|
||||||
esphome/components/canbus/* @danielschramm @mvturnho
|
esphome/components/canbus/* @danielschramm @mvturnho
|
||||||
esphome/components/cap1188/* @mreditor97
|
esphome/components/cap1188/* @mreditor97
|
||||||
esphome/components/captive_portal/* @OttoWinter
|
esphome/components/captive_portal/* @esphome/core
|
||||||
esphome/components/ccs811/* @habbie
|
esphome/components/ccs811/* @habbie
|
||||||
esphome/components/cd74hc4067/* @asoehlke
|
esphome/components/cd74hc4067/* @asoehlke
|
||||||
esphome/components/ch422g/* @clydebarrow @jesterret
|
esphome/components/ch422g/* @clydebarrow @jesterret
|
||||||
@@ -118,7 +119,7 @@ esphome/components/dallas_temp/* @ssieb
|
|||||||
esphome/components/daly_bms/* @s1lvi0
|
esphome/components/daly_bms/* @s1lvi0
|
||||||
esphome/components/dashboard_import/* @esphome/core
|
esphome/components/dashboard_import/* @esphome/core
|
||||||
esphome/components/datetime/* @jesserockz @rfdarter
|
esphome/components/datetime/* @jesserockz @rfdarter
|
||||||
esphome/components/debug/* @OttoWinter
|
esphome/components/debug/* @esphome/core
|
||||||
esphome/components/delonghi/* @grob6000
|
esphome/components/delonghi/* @grob6000
|
||||||
esphome/components/dfplayer/* @glmnet
|
esphome/components/dfplayer/* @glmnet
|
||||||
esphome/components/dfrobot_sen0395/* @niklasweber
|
esphome/components/dfrobot_sen0395/* @niklasweber
|
||||||
@@ -138,15 +139,17 @@ esphome/components/ens160_base/* @latonita @vincentscode
|
|||||||
esphome/components/ens160_i2c/* @latonita
|
esphome/components/ens160_i2c/* @latonita
|
||||||
esphome/components/ens160_spi/* @latonita
|
esphome/components/ens160_spi/* @latonita
|
||||||
esphome/components/ens210/* @itn3rd77
|
esphome/components/ens210/* @itn3rd77
|
||||||
|
esphome/components/epaper_spi/* @esphome/core
|
||||||
esphome/components/es7210/* @kahrendt
|
esphome/components/es7210/* @kahrendt
|
||||||
esphome/components/es7243e/* @kbx81
|
esphome/components/es7243e/* @kbx81
|
||||||
esphome/components/es8156/* @kbx81
|
esphome/components/es8156/* @kbx81
|
||||||
esphome/components/es8311/* @kahrendt @kroimon
|
esphome/components/es8311/* @kahrendt @kroimon
|
||||||
esphome/components/es8388/* @P4uLT
|
esphome/components/es8388/* @P4uLT
|
||||||
esphome/components/esp32/* @esphome/core
|
esphome/components/esp32/* @esphome/core
|
||||||
esphome/components/esp32_ble/* @Rapsssito @jesserockz
|
esphome/components/esp32_ble/* @bdraco @jesserockz @Rapsssito
|
||||||
esphome/components/esp32_ble_client/* @jesserockz
|
esphome/components/esp32_ble_client/* @bdraco @jesserockz
|
||||||
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
|
esphome/components/esp32_ble_server/* @clydebarrow @jesserockz @Rapsssito
|
||||||
|
esphome/components/esp32_ble_tracker/* @bdraco
|
||||||
esphome/components/esp32_camera_web_server/* @ayufan
|
esphome/components/esp32_camera_web_server/* @ayufan
|
||||||
esphome/components/esp32_can/* @Sympatron
|
esphome/components/esp32_can/* @Sympatron
|
||||||
esphome/components/esp32_hosted/* @swoboda1337
|
esphome/components/esp32_hosted/* @swoboda1337
|
||||||
@@ -158,14 +161,13 @@ esphome/components/esp_ldo/* @clydebarrow
|
|||||||
esphome/components/espnow/* @jesserockz
|
esphome/components/espnow/* @jesserockz
|
||||||
esphome/components/ethernet_info/* @gtjadsonsantos
|
esphome/components/ethernet_info/* @gtjadsonsantos
|
||||||
esphome/components/event/* @nohat
|
esphome/components/event/* @nohat
|
||||||
esphome/components/event_emitter/* @Rapsssito
|
|
||||||
esphome/components/exposure_notifications/* @OttoWinter
|
esphome/components/exposure_notifications/* @OttoWinter
|
||||||
esphome/components/ezo/* @ssieb
|
esphome/components/ezo/* @ssieb
|
||||||
esphome/components/ezo_pmp/* @carlos-sarmiento
|
esphome/components/ezo_pmp/* @carlos-sarmiento
|
||||||
esphome/components/factory_reset/* @anatoly-savchenkov
|
esphome/components/factory_reset/* @anatoly-savchenkov
|
||||||
esphome/components/fastled_base/* @OttoWinter
|
esphome/components/fastled_base/* @OttoWinter
|
||||||
esphome/components/feedback/* @ianchi
|
esphome/components/feedback/* @ianchi
|
||||||
esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh
|
esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
|
||||||
esphome/components/font/* @clydebarrow @esphome/core
|
esphome/components/font/* @clydebarrow @esphome/core
|
||||||
esphome/components/fs3000/* @kahrendt
|
esphome/components/fs3000/* @kahrendt
|
||||||
esphome/components/ft5x06/* @clydebarrow
|
esphome/components/ft5x06/* @clydebarrow
|
||||||
@@ -201,7 +203,7 @@ esphome/components/heatpumpir/* @rob-deutsch
|
|||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||||
esphome/components/hm3301/* @freekode
|
esphome/components/hm3301/* @freekode
|
||||||
esphome/components/hmac_md5/* @dwmw2
|
esphome/components/hmac_md5/* @dwmw2
|
||||||
esphome/components/homeassistant/* @OttoWinter @esphome/core
|
esphome/components/homeassistant/* @esphome/core @OttoWinter
|
||||||
esphome/components/homeassistant/number/* @landonr
|
esphome/components/homeassistant/number/* @landonr
|
||||||
esphome/components/homeassistant/switch/* @Links2004
|
esphome/components/homeassistant/switch/* @Links2004
|
||||||
esphome/components/honeywell_hih_i2c/* @Benichou34
|
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||||
@@ -226,18 +228,18 @@ esphome/components/iaqcore/* @yozik04
|
|||||||
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
|
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
|
||||||
esphome/components/improv_base/* @esphome/core
|
esphome/components/improv_base/* @esphome/core
|
||||||
esphome/components/improv_serial/* @esphome/core
|
esphome/components/improv_serial/* @esphome/core
|
||||||
esphome/components/ina226/* @Sergio303 @latonita
|
esphome/components/ina226/* @latonita @Sergio303
|
||||||
esphome/components/ina260/* @mreditor97
|
esphome/components/ina260/* @mreditor97
|
||||||
esphome/components/ina2xx_base/* @latonita
|
esphome/components/ina2xx_base/* @latonita
|
||||||
esphome/components/ina2xx_i2c/* @latonita
|
esphome/components/ina2xx_i2c/* @latonita
|
||||||
esphome/components/ina2xx_spi/* @latonita
|
esphome/components/ina2xx_spi/* @latonita
|
||||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||||
esphome/components/inkplate6/* @jesserockz
|
esphome/components/inkplate/* @jesserockz @JosipKuci
|
||||||
esphome/components/integration/* @OttoWinter
|
esphome/components/integration/* @OttoWinter
|
||||||
esphome/components/internal_temperature/* @Mat931
|
esphome/components/internal_temperature/* @Mat931
|
||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
esphome/components/jsn_sr04t/* @Mafus1
|
esphome/components/jsn_sr04t/* @Mafus1
|
||||||
esphome/components/json/* @OttoWinter
|
esphome/components/json/* @esphome/core
|
||||||
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
||||||
esphome/components/key_collector/* @ssieb
|
esphome/components/key_collector/* @ssieb
|
||||||
esphome/components/key_provider/* @ssieb
|
esphome/components/key_provider/* @ssieb
|
||||||
@@ -245,6 +247,7 @@ esphome/components/kuntze/* @ssieb
|
|||||||
esphome/components/lc709203f/* @ilikecake
|
esphome/components/lc709203f/* @ilikecake
|
||||||
esphome/components/lcd_menu/* @numo68
|
esphome/components/lcd_menu/* @numo68
|
||||||
esphome/components/ld2410/* @regevbr @sebcaps
|
esphome/components/ld2410/* @regevbr @sebcaps
|
||||||
|
esphome/components/ld2412/* @Rihan9
|
||||||
esphome/components/ld2420/* @descipher
|
esphome/components/ld2420/* @descipher
|
||||||
esphome/components/ld2450/* @hareeshmu
|
esphome/components/ld2450/* @hareeshmu
|
||||||
esphome/components/ld24xx/* @kbx81
|
esphome/components/ld24xx/* @kbx81
|
||||||
@@ -254,6 +257,7 @@ esphome/components/libretiny_pwm/* @kuba2k2
|
|||||||
esphome/components/light/* @esphome/core
|
esphome/components/light/* @esphome/core
|
||||||
esphome/components/lightwaverf/* @max246
|
esphome/components/lightwaverf/* @max246
|
||||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||||
|
esphome/components/lm75b/* @beormund
|
||||||
esphome/components/ln882x/* @lamauny
|
esphome/components/ln882x/* @lamauny
|
||||||
esphome/components/lock/* @esphome/core
|
esphome/components/lock/* @esphome/core
|
||||||
esphome/components/logger/* @esphome/core
|
esphome/components/logger/* @esphome/core
|
||||||
@@ -274,8 +278,8 @@ esphome/components/max7219digit/* @rspaargaren
|
|||||||
esphome/components/max9611/* @mckaymatthew
|
esphome/components/max9611/* @mckaymatthew
|
||||||
esphome/components/mcp23008/* @jesserockz
|
esphome/components/mcp23008/* @jesserockz
|
||||||
esphome/components/mcp23017/* @jesserockz
|
esphome/components/mcp23017/* @jesserockz
|
||||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
esphome/components/mcp23s08/* @jesserockz @SenexCrenshaw
|
||||||
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
|
esphome/components/mcp23s17/* @jesserockz @SenexCrenshaw
|
||||||
esphome/components/mcp23x08_base/* @jesserockz
|
esphome/components/mcp23x08_base/* @jesserockz
|
||||||
esphome/components/mcp23x17_base/* @jesserockz
|
esphome/components/mcp23x17_base/* @jesserockz
|
||||||
esphome/components/mcp23xxx_base/* @jesserockz
|
esphome/components/mcp23xxx_base/* @jesserockz
|
||||||
@@ -296,6 +300,7 @@ esphome/components/mics_4514/* @jesserockz
|
|||||||
esphome/components/midea/* @dudanov
|
esphome/components/midea/* @dudanov
|
||||||
esphome/components/midea_ir/* @dudanov
|
esphome/components/midea_ir/* @dudanov
|
||||||
esphome/components/mipi_dsi/* @clydebarrow
|
esphome/components/mipi_dsi/* @clydebarrow
|
||||||
|
esphome/components/mipi_rgb/* @clydebarrow
|
||||||
esphome/components/mipi_spi/* @clydebarrow
|
esphome/components/mipi_spi/* @clydebarrow
|
||||||
esphome/components/mitsubishi/* @RubyBailey
|
esphome/components/mitsubishi/* @RubyBailey
|
||||||
esphome/components/mixer/speaker/* @kahrendt
|
esphome/components/mixer/speaker/* @kahrendt
|
||||||
@@ -339,7 +344,7 @@ esphome/components/ota/* @esphome/core
|
|||||||
esphome/components/output/* @esphome/core
|
esphome/components/output/* @esphome/core
|
||||||
esphome/components/packet_transport/* @clydebarrow
|
esphome/components/packet_transport/* @clydebarrow
|
||||||
esphome/components/pca6416a/* @Mat931
|
esphome/components/pca6416a/* @Mat931
|
||||||
esphome/components/pca9554/* @clydebarrow @hwstar
|
esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
|
||||||
esphome/components/pcf85063/* @brogon
|
esphome/components/pcf85063/* @brogon
|
||||||
esphome/components/pcf8563/* @KoenBreeman
|
esphome/components/pcf8563/* @KoenBreeman
|
||||||
esphome/components/pi4ioe5v6408/* @jesserockz
|
esphome/components/pi4ioe5v6408/* @jesserockz
|
||||||
@@ -350,9 +355,9 @@ esphome/components/pm2005/* @andrewjswan
|
|||||||
esphome/components/pmsa003i/* @sjtrny
|
esphome/components/pmsa003i/* @sjtrny
|
||||||
esphome/components/pmsx003/* @ximex
|
esphome/components/pmsx003/* @ximex
|
||||||
esphome/components/pmwcs3/* @SeByDocKy
|
esphome/components/pmwcs3/* @SeByDocKy
|
||||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
esphome/components/pn532/* @jesserockz @OttoWinter
|
||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
esphome/components/pn532_i2c/* @jesserockz @OttoWinter
|
||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
esphome/components/pn532_spi/* @jesserockz @OttoWinter
|
||||||
esphome/components/pn7150/* @jesserockz @kbx81
|
esphome/components/pn7150/* @jesserockz @kbx81
|
||||||
esphome/components/pn7150_i2c/* @jesserockz @kbx81
|
esphome/components/pn7150_i2c/* @jesserockz @kbx81
|
||||||
esphome/components/pn7160/* @jesserockz @kbx81
|
esphome/components/pn7160/* @jesserockz @kbx81
|
||||||
@@ -361,7 +366,7 @@ esphome/components/pn7160_spi/* @jesserockz @kbx81
|
|||||||
esphome/components/power_supply/* @esphome/core
|
esphome/components/power_supply/* @esphome/core
|
||||||
esphome/components/preferences/* @esphome/core
|
esphome/components/preferences/* @esphome/core
|
||||||
esphome/components/psram/* @esphome/core
|
esphome/components/psram/* @esphome/core
|
||||||
esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
|
esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
|
||||||
esphome/components/pvvx_mithermometer/* @pasiz
|
esphome/components/pvvx_mithermometer/* @pasiz
|
||||||
esphome/components/pylontech/* @functionpointer
|
esphome/components/pylontech/* @functionpointer
|
||||||
esphome/components/qmp6988/* @andrewpc
|
esphome/components/qmp6988/* @andrewpc
|
||||||
@@ -402,7 +407,8 @@ esphome/components/sensirion_common/* @martgras
|
|||||||
esphome/components/sensor/* @esphome/core
|
esphome/components/sensor/* @esphome/core
|
||||||
esphome/components/sfa30/* @ghsensdev
|
esphome/components/sfa30/* @ghsensdev
|
||||||
esphome/components/sgp40/* @SenexCrenshaw
|
esphome/components/sgp40/* @SenexCrenshaw
|
||||||
esphome/components/sgp4x/* @SenexCrenshaw @martgras
|
esphome/components/sgp4x/* @martgras @SenexCrenshaw
|
||||||
|
esphome/components/sha256/* @esphome/core
|
||||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
||||||
esphome/components/sht3xd/* @mrtoy-me
|
esphome/components/sht3xd/* @mrtoy-me
|
||||||
esphome/components/sht4x/* @sjtrny
|
esphome/components/sht4x/* @sjtrny
|
||||||
@@ -424,6 +430,7 @@ esphome/components/speaker/media_player/* @kahrendt @synesthesiam
|
|||||||
esphome/components/spi/* @clydebarrow @esphome/core
|
esphome/components/spi/* @clydebarrow @esphome/core
|
||||||
esphome/components/spi_device/* @clydebarrow
|
esphome/components/spi_device/* @clydebarrow
|
||||||
esphome/components/spi_led_strip/* @clydebarrow
|
esphome/components/spi_led_strip/* @clydebarrow
|
||||||
|
esphome/components/split_buffer/* @jesserockz
|
||||||
esphome/components/sprinkler/* @kbx81
|
esphome/components/sprinkler/* @kbx81
|
||||||
esphome/components/sps30/* @martgras
|
esphome/components/sps30/* @martgras
|
||||||
esphome/components/ssd1322_base/* @kbx81
|
esphome/components/ssd1322_base/* @kbx81
|
||||||
@@ -467,7 +474,7 @@ esphome/components/template/event/* @nohat
|
|||||||
esphome/components/template/fan/* @ssieb
|
esphome/components/template/fan/* @ssieb
|
||||||
esphome/components/text/* @mauritskorse
|
esphome/components/text/* @mauritskorse
|
||||||
esphome/components/thermostat/* @kbx81
|
esphome/components/thermostat/* @kbx81
|
||||||
esphome/components/time/* @OttoWinter
|
esphome/components/time/* @esphome/core
|
||||||
esphome/components/tlc5947/* @rnauber
|
esphome/components/tlc5947/* @rnauber
|
||||||
esphome/components/tlc5971/* @IJIJI
|
esphome/components/tlc5971/* @IJIJI
|
||||||
esphome/components/tm1621/* @Philippe12
|
esphome/components/tm1621/* @Philippe12
|
||||||
@@ -511,7 +518,7 @@ esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
|||||||
esphome/components/watchdog/* @oarcher
|
esphome/components/watchdog/* @oarcher
|
||||||
esphome/components/waveshare_epaper/* @clydebarrow
|
esphome/components/waveshare_epaper/* @clydebarrow
|
||||||
esphome/components/web_server/ota/* @esphome/core
|
esphome/components/web_server/ota/* @esphome/core
|
||||||
esphome/components/web_server_base/* @OttoWinter
|
esphome/components/web_server_base/* @esphome/core
|
||||||
esphome/components/web_server_idf/* @dentra
|
esphome/components/web_server_idf/* @dentra
|
||||||
esphome/components/weikai/* @DrCoolZic
|
esphome/components/weikai/* @DrCoolZic
|
||||||
esphome/components/weikai_i2c/* @DrCoolZic
|
esphome/components/weikai_i2c/* @DrCoolZic
|
||||||
@@ -529,6 +536,7 @@ esphome/components/wk2204_spi/* @DrCoolZic
|
|||||||
esphome/components/wk2212_i2c/* @DrCoolZic
|
esphome/components/wk2212_i2c/* @DrCoolZic
|
||||||
esphome/components/wk2212_spi/* @DrCoolZic
|
esphome/components/wk2212_spi/* @DrCoolZic
|
||||||
esphome/components/wl_134/* @hobbypunk90
|
esphome/components/wl_134/* @hobbypunk90
|
||||||
|
esphome/components/wts01/* @alepee
|
||||||
esphome/components/x9c/* @EtienneMD
|
esphome/components/x9c/* @EtienneMD
|
||||||
esphome/components/xgzp68xx/* @gcormier
|
esphome/components/xgzp68xx/* @gcormier
|
||||||
esphome/components/xiaomi_hhccjcy10/* @fariouche
|
esphome/components/xiaomi_hhccjcy10/* @fariouche
|
||||||
@@ -544,3 +552,4 @@ esphome/components/xxtea/* @clydebarrow
|
|||||||
esphome/components/zephyr/* @tomaszduda23
|
esphome/components/zephyr/* @tomaszduda23
|
||||||
esphome/components/zhlt01/* @cfeenstra1024
|
esphome/components/zhlt01/* @cfeenstra1024
|
||||||
esphome/components/zio_ultrasonic/* @kahrendt
|
esphome/components/zio_ultrasonic/* @kahrendt
|
||||||
|
esphome/components/zwave_proxy/* @kbx81
|
||||||
|
|||||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.8.0-dev
|
PROJECT_NUMBER = 2025.10.0
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def main():
|
|||||||
def run_command(*cmd, ignore_error: bool = False):
|
def run_command(*cmd, ignore_error: bool = False):
|
||||||
print(f"$ {shlex.join(list(cmd))}")
|
print(f"$ {shlex.join(list(cmd))}")
|
||||||
if not args.dry_run:
|
if not args.dry_run:
|
||||||
rc = subprocess.call(list(cmd))
|
rc = subprocess.call(list(cmd), close_fds=False)
|
||||||
if rc != 0 and not ignore_error:
|
if rc != 0 and not ignore_error:
|
||||||
print("Command failed")
|
print("Command failed")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -6,17 +6,23 @@ import getpass
|
|||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
import argcomplete
|
import argcomplete
|
||||||
|
|
||||||
|
# Note: Do not import modules from esphome.components here, as this would
|
||||||
|
# cause them to be loaded before external components are processed, resulting
|
||||||
|
# in the built-in version being used instead of the external component one.
|
||||||
from esphome import const, writer, yaml_util
|
from esphome import const, writer, yaml_util
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.config import iter_component_configs, read_config, strip_default_ids
|
from esphome.config import iter_component_configs, read_config, strip_default_ids
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
ALLOWED_NAME_CHARS,
|
ALLOWED_NAME_CHARS,
|
||||||
|
CONF_API,
|
||||||
CONF_BAUD_RATE,
|
CONF_BAUD_RATE,
|
||||||
CONF_BROKER,
|
CONF_BROKER,
|
||||||
CONF_DEASSERT_RTS_DTR,
|
CONF_DEASSERT_RTS_DTR,
|
||||||
@@ -42,8 +48,10 @@ from esphome.const import (
|
|||||||
SECRETS_FILES,
|
SECRETS_FILES,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, EsphomeError, coroutine
|
from esphome.core import CORE, EsphomeError, coroutine
|
||||||
|
from esphome.enum import StrEnum
|
||||||
from esphome.helpers import get_bool_env, indent, is_ip_address
|
from esphome.helpers import get_bool_env, indent, is_ip_address
|
||||||
from esphome.log import AnsiFore, color, setup_log
|
from esphome.log import AnsiFore, color, setup_log
|
||||||
|
from esphome.types import ConfigType
|
||||||
from esphome.util import (
|
from esphome.util import (
|
||||||
get_serial_ports,
|
get_serial_ports,
|
||||||
list_yaml_files,
|
list_yaml_files,
|
||||||
@@ -55,6 +63,23 @@ from esphome.util import (
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ArgsProtocol(Protocol):
|
||||||
|
device: list[str] | None
|
||||||
|
reset: bool
|
||||||
|
username: str | None
|
||||||
|
password: str | None
|
||||||
|
client_id: str | None
|
||||||
|
topic: str | None
|
||||||
|
file: str | None
|
||||||
|
no_logs: bool
|
||||||
|
only_generate: bool
|
||||||
|
show_secrets: bool
|
||||||
|
dashboard: bool
|
||||||
|
configuration: str
|
||||||
|
name: str
|
||||||
|
upload_speed: str | None
|
||||||
|
|
||||||
|
|
||||||
def choose_prompt(options, purpose: str = None):
|
def choose_prompt(options, purpose: str = None):
|
||||||
if not options:
|
if not options:
|
||||||
raise EsphomeError(
|
raise EsphomeError(
|
||||||
@@ -87,51 +112,187 @@ def choose_prompt(options, purpose: str = None):
|
|||||||
return options[opt - 1][1]
|
return options[opt - 1][1]
|
||||||
|
|
||||||
|
|
||||||
|
class Purpose(StrEnum):
|
||||||
|
UPLOADING = "uploading"
|
||||||
|
LOGGING = "logging"
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_with_cache(address: str, purpose: Purpose) -> list[str]:
|
||||||
|
"""Resolve an address using cache if available, otherwise return the address itself."""
|
||||||
|
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(address)):
|
||||||
|
_LOGGER.debug("Using cached addresses for %s: %s", purpose.value, cached)
|
||||||
|
return cached
|
||||||
|
return [address]
|
||||||
|
|
||||||
|
|
||||||
def choose_upload_log_host(
|
def choose_upload_log_host(
|
||||||
default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
|
default: list[str] | str | None,
|
||||||
):
|
check_default: str | None,
|
||||||
|
purpose: Purpose,
|
||||||
|
) -> list[str]:
|
||||||
|
# Convert to list for uniform handling
|
||||||
|
defaults = [default] if isinstance(default, str) else default or []
|
||||||
|
|
||||||
|
# If devices specified, resolve them
|
||||||
|
if defaults:
|
||||||
|
resolved: list[str] = []
|
||||||
|
for device in defaults:
|
||||||
|
if device == "SERIAL":
|
||||||
|
serial_ports = get_serial_ports()
|
||||||
|
if not serial_ports:
|
||||||
|
_LOGGER.warning("No serial ports found, skipping SERIAL device")
|
||||||
|
continue
|
||||||
|
options = [
|
||||||
|
(f"{port.path} ({port.description})", port.path)
|
||||||
|
for port in serial_ports
|
||||||
|
]
|
||||||
|
resolved.append(choose_prompt(options, purpose=purpose))
|
||||||
|
elif device == "OTA":
|
||||||
|
# ensure IP adresses are used first
|
||||||
|
if is_ip_address(CORE.address) and (
|
||||||
|
(purpose == Purpose.LOGGING and has_api())
|
||||||
|
or (purpose == Purpose.UPLOADING and has_ota())
|
||||||
|
):
|
||||||
|
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
||||||
|
|
||||||
|
if purpose == Purpose.LOGGING:
|
||||||
|
if has_api() and has_mqtt_ip_lookup():
|
||||||
|
resolved.append("MQTTIP")
|
||||||
|
|
||||||
|
if has_mqtt_logging():
|
||||||
|
resolved.append("MQTT")
|
||||||
|
|
||||||
|
if has_api() and has_non_ip_address():
|
||||||
|
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
||||||
|
|
||||||
|
elif purpose == Purpose.UPLOADING:
|
||||||
|
if has_ota() and has_mqtt_ip_lookup():
|
||||||
|
resolved.append("MQTTIP")
|
||||||
|
|
||||||
|
if has_ota() and has_non_ip_address():
|
||||||
|
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
||||||
|
else:
|
||||||
|
resolved.append(device)
|
||||||
|
if not resolved:
|
||||||
|
_LOGGER.error("All specified devices: %s could not be resolved.", defaults)
|
||||||
|
return resolved
|
||||||
|
|
||||||
|
# No devices specified, show interactive chooser
|
||||||
options = [
|
options = [
|
||||||
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
|
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
|
||||||
]
|
]
|
||||||
if default == "SERIAL":
|
|
||||||
return choose_prompt(options, purpose=purpose)
|
if purpose == Purpose.LOGGING:
|
||||||
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
if has_mqtt_logging():
|
||||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
mqtt_config = CORE.config[CONF_MQTT]
|
||||||
if default == "OTA":
|
|
||||||
return CORE.address
|
|
||||||
if (
|
|
||||||
show_mqtt
|
|
||||||
and (mqtt_config := CORE.config.get(CONF_MQTT))
|
|
||||||
and mqtt_logging_enabled(mqtt_config)
|
|
||||||
):
|
|
||||||
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
|
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
|
||||||
if default == "OTA":
|
|
||||||
return "MQTT"
|
if has_api():
|
||||||
if default is not None:
|
if has_resolvable_address():
|
||||||
return default
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||||
|
if has_mqtt_ip_lookup():
|
||||||
|
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
|
||||||
|
|
||||||
|
elif purpose == Purpose.UPLOADING and has_ota():
|
||||||
|
if has_resolvable_address():
|
||||||
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||||
|
if has_mqtt_ip_lookup():
|
||||||
|
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
|
||||||
|
|
||||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||||
return check_default
|
return [check_default]
|
||||||
return choose_prompt(options, purpose=purpose)
|
return [choose_prompt(options, purpose=purpose)]
|
||||||
|
|
||||||
|
|
||||||
def mqtt_logging_enabled(mqtt_config):
|
def has_mqtt_logging() -> bool:
|
||||||
|
"""Check if MQTT logging is available."""
|
||||||
|
if CONF_MQTT not in CORE.config:
|
||||||
|
return False
|
||||||
|
|
||||||
|
mqtt_config = CORE.config[CONF_MQTT]
|
||||||
|
|
||||||
|
# enabled by default
|
||||||
|
if CONF_LOG_TOPIC not in mqtt_config:
|
||||||
|
return True
|
||||||
|
|
||||||
log_topic = mqtt_config[CONF_LOG_TOPIC]
|
log_topic = mqtt_config[CONF_LOG_TOPIC]
|
||||||
if log_topic is None:
|
if log_topic is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if CONF_TOPIC not in log_topic:
|
if CONF_TOPIC not in log_topic:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return log_topic.get(CONF_LEVEL, None) != "NONE"
|
return log_topic.get(CONF_LEVEL, None) != "NONE"
|
||||||
|
|
||||||
|
|
||||||
def get_port_type(port):
|
def has_mqtt() -> bool:
|
||||||
|
"""Check if MQTT is available."""
|
||||||
|
return CONF_MQTT in CORE.config
|
||||||
|
|
||||||
|
|
||||||
|
def has_api() -> bool:
|
||||||
|
"""Check if API is available."""
|
||||||
|
return CONF_API in CORE.config
|
||||||
|
|
||||||
|
|
||||||
|
def has_ota() -> bool:
|
||||||
|
"""Check if OTA is available."""
|
||||||
|
return CONF_OTA in CORE.config
|
||||||
|
|
||||||
|
|
||||||
|
def has_mqtt_ip_lookup() -> bool:
|
||||||
|
"""Check if MQTT is available and IP lookup is supported."""
|
||||||
|
from esphome.components.mqtt import CONF_DISCOVER_IP
|
||||||
|
|
||||||
|
if CONF_MQTT not in CORE.config:
|
||||||
|
return False
|
||||||
|
# Default Enabled
|
||||||
|
if CONF_DISCOVER_IP not in CORE.config[CONF_MQTT]:
|
||||||
|
return True
|
||||||
|
return CORE.config[CONF_MQTT][CONF_DISCOVER_IP]
|
||||||
|
|
||||||
|
|
||||||
|
def has_mdns() -> bool:
|
||||||
|
"""Check if MDNS is available."""
|
||||||
|
return CONF_MDNS not in CORE.config or not CORE.config[CONF_MDNS][CONF_DISABLED]
|
||||||
|
|
||||||
|
|
||||||
|
def has_non_ip_address() -> bool:
|
||||||
|
"""Check if CORE.address is set and is not an IP address."""
|
||||||
|
return CORE.address is not None and not is_ip_address(CORE.address)
|
||||||
|
|
||||||
|
|
||||||
|
def has_ip_address() -> bool:
|
||||||
|
"""Check if CORE.address is a valid IP address."""
|
||||||
|
return CORE.address is not None and is_ip_address(CORE.address)
|
||||||
|
|
||||||
|
|
||||||
|
def has_resolvable_address() -> bool:
|
||||||
|
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
|
||||||
|
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
|
||||||
|
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
|
||||||
|
return CORE.address is not None
|
||||||
|
|
||||||
|
|
||||||
|
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
|
||||||
|
from esphome import mqtt
|
||||||
|
|
||||||
|
return mqtt.get_esphome_device_ip(config, username, password, client_id)
|
||||||
|
|
||||||
|
|
||||||
|
_PORT_TO_PORT_TYPE = {
|
||||||
|
"MQTT": "MQTT",
|
||||||
|
"MQTTIP": "MQTTIP",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_port_type(port: str) -> str:
|
||||||
if port.startswith("/") or port.startswith("COM"):
|
if port.startswith("/") or port.startswith("COM"):
|
||||||
return "SERIAL"
|
return "SERIAL"
|
||||||
if port == "MQTT":
|
return _PORT_TO_PORT_TYPE.get(port, "NETWORK")
|
||||||
return "MQTT"
|
|
||||||
return "NETWORK"
|
|
||||||
|
|
||||||
|
|
||||||
def run_miniterm(config, port, args):
|
def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||||
from aioesphomeapi import LogParser
|
from aioesphomeapi import LogParser
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
@@ -173,7 +334,9 @@ def run_miniterm(config, port, args):
|
|||||||
.replace(b"\n", b"")
|
.replace(b"\n", b"")
|
||||||
.decode("utf8", "backslashreplace")
|
.decode("utf8", "backslashreplace")
|
||||||
)
|
)
|
||||||
time_str = datetime.now().time().strftime("[%H:%M:%S]")
|
time_ = datetime.now()
|
||||||
|
nanoseconds = time_.microsecond // 1000
|
||||||
|
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
|
||||||
safe_print(parser.parse_line(line, time_str))
|
safe_print(parser.parse_line(line, time_str))
|
||||||
|
|
||||||
backtrace_state = platformio_api.process_stacktrace(
|
backtrace_state = platformio_api.process_stacktrace(
|
||||||
@@ -208,7 +371,7 @@ def wrap_to_code(name, comp):
|
|||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
def write_cpp(config):
|
def write_cpp(config: ConfigType) -> int:
|
||||||
if not get_bool_env(ENV_NOGITIGNORE):
|
if not get_bool_env(ENV_NOGITIGNORE):
|
||||||
writer.write_gitignore()
|
writer.write_gitignore()
|
||||||
|
|
||||||
@@ -216,7 +379,7 @@ def write_cpp(config):
|
|||||||
return write_cpp_file()
|
return write_cpp_file()
|
||||||
|
|
||||||
|
|
||||||
def generate_cpp_contents(config):
|
def generate_cpp_contents(config: ConfigType) -> None:
|
||||||
_LOGGER.info("Generating C++ source...")
|
_LOGGER.info("Generating C++ source...")
|
||||||
|
|
||||||
for name, component, conf in iter_component_configs(CORE.config):
|
for name, component, conf in iter_component_configs(CORE.config):
|
||||||
@@ -227,7 +390,7 @@ def generate_cpp_contents(config):
|
|||||||
CORE.flush_tasks()
|
CORE.flush_tasks()
|
||||||
|
|
||||||
|
|
||||||
def write_cpp_file():
|
def write_cpp_file() -> int:
|
||||||
code_s = indent(CORE.cpp_main_section)
|
code_s = indent(CORE.cpp_main_section)
|
||||||
writer.write_cpp(code_s)
|
writer.write_cpp(code_s)
|
||||||
|
|
||||||
@@ -238,7 +401,7 @@ def write_cpp_file():
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def compile_program(args, config):
|
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
_LOGGER.info("Compiling app...")
|
_LOGGER.info("Compiling app...")
|
||||||
@@ -249,7 +412,9 @@ def compile_program(args, config):
|
|||||||
return 0 if idedata is not None else 1
|
return 0 if idedata is not None else 1
|
||||||
|
|
||||||
|
|
||||||
def upload_using_esptool(config, port, file, speed):
|
def upload_using_esptool(
|
||||||
|
config: ConfigType, port: str, file: str, speed: int
|
||||||
|
) -> str | int:
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
first_baudrate = speed or config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
first_baudrate = speed or config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||||
@@ -294,7 +459,7 @@ def upload_using_esptool(config, port, file, speed):
|
|||||||
"detect",
|
"detect",
|
||||||
]
|
]
|
||||||
for img in flash_images:
|
for img in flash_images:
|
||||||
cmd += [img.offset, img.path]
|
cmd += [img.offset, str(img.path)]
|
||||||
|
|
||||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||||
import esptool
|
import esptool
|
||||||
@@ -314,7 +479,7 @@ def upload_using_esptool(config, port, file, speed):
|
|||||||
return run_esptool(115200)
|
return run_esptool(115200)
|
||||||
|
|
||||||
|
|
||||||
def upload_using_platformio(config, port):
|
def upload_using_platformio(config: ConfigType, port: str):
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
|
|
||||||
upload_args = ["-t", "upload", "-t", "nobuild"]
|
upload_args = ["-t", "upload", "-t", "nobuild"]
|
||||||
@@ -323,7 +488,7 @@ def upload_using_platformio(config, port):
|
|||||||
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
|
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
|
||||||
|
|
||||||
|
|
||||||
def check_permissions(port):
|
def check_permissions(port: str):
|
||||||
if os.name == "posix" and get_port_type(port) == "SERIAL":
|
if os.name == "posix" and get_port_type(port) == "SERIAL":
|
||||||
# Check if we can open selected serial port
|
# Check if we can open selected serial port
|
||||||
if not os.access(port, os.F_OK):
|
if not os.access(port, os.F_OK):
|
||||||
@@ -341,27 +506,29 @@ def check_permissions(port):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def upload_program(config, args, host):
|
def upload_program(
|
||||||
|
config: ConfigType, args: ArgsProtocol, devices: list[str]
|
||||||
|
) -> tuple[int, str | None]:
|
||||||
|
host = devices[0]
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||||
if getattr(module, "upload_program")(config, args, host):
|
if getattr(module, "upload_program")(config, args, host):
|
||||||
return 0
|
return 0, host
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if get_port_type(host) == "SERIAL":
|
if get_port_type(host) == "SERIAL":
|
||||||
check_permissions(host)
|
check_permissions(host)
|
||||||
|
|
||||||
|
exit_code = 1
|
||||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||||
file = getattr(args, "file", None)
|
file = getattr(args, "file", None)
|
||||||
return upload_using_esptool(config, host, file, args.upload_speed)
|
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
|
||||||
|
elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
|
||||||
|
exit_code = upload_using_platformio(config, host)
|
||||||
|
# else: Unknown target platform, exit_code remains 1
|
||||||
|
|
||||||
if CORE.target_platform in (PLATFORM_RP2040):
|
return exit_code, host if exit_code == 0 else None
|
||||||
return upload_using_platformio(config, args.device)
|
|
||||||
|
|
||||||
if CORE.is_libretiny:
|
|
||||||
return upload_using_platformio(config, host)
|
|
||||||
|
|
||||||
return 1 # Unknown target platform
|
|
||||||
|
|
||||||
ota_conf = {}
|
ota_conf = {}
|
||||||
for ota_item in config.get(CONF_OTA, []):
|
for ota_item in config.get(CONF_OTA, []):
|
||||||
@@ -378,45 +545,57 @@ def upload_program(config, args, host):
|
|||||||
|
|
||||||
remote_port = int(ota_conf[CONF_PORT])
|
remote_port = int(ota_conf[CONF_PORT])
|
||||||
password = ota_conf.get(CONF_PASSWORD, "")
|
password = ota_conf.get(CONF_PASSWORD, "")
|
||||||
|
|
||||||
if (
|
|
||||||
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
|
||||||
and (not args.device or args.device in ("MQTT", "OTA"))
|
|
||||||
and (
|
|
||||||
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
|
|
||||||
or get_port_type(host) == "MQTT"
|
|
||||||
)
|
|
||||||
):
|
|
||||||
from esphome import mqtt
|
|
||||||
|
|
||||||
host = mqtt.get_esphome_device_ip(
|
|
||||||
config, args.username, args.password, args.client_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if getattr(args, "file", None) is not None:
|
if getattr(args, "file", None) is not None:
|
||||||
return espota2.run_ota(host, remote_port, password, args.file)
|
binary = Path(args.file)
|
||||||
|
else:
|
||||||
|
binary = CORE.firmware_bin
|
||||||
|
|
||||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
# MQTT address resolution
|
||||||
|
if get_port_type(host) in ("MQTT", "MQTTIP"):
|
||||||
|
devices = mqtt_get_ip(config, args.username, args.password, args.client_id)
|
||||||
|
|
||||||
|
return espota2.run_ota(devices, remote_port, password, binary)
|
||||||
|
|
||||||
|
|
||||||
def show_logs(config, args, port):
|
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
|
||||||
|
try:
|
||||||
|
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||||
|
if getattr(module, "show_logs")(config, args, devices):
|
||||||
|
return 0
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
if "logger" not in config:
|
if "logger" not in config:
|
||||||
raise EsphomeError("Logger is not configured!")
|
raise EsphomeError("Logger is not configured!")
|
||||||
|
|
||||||
|
port = devices[0]
|
||||||
|
|
||||||
if get_port_type(port) == "SERIAL":
|
if get_port_type(port) == "SERIAL":
|
||||||
check_permissions(port)
|
check_permissions(port)
|
||||||
return run_miniterm(config, port, args)
|
return run_miniterm(config, port, args)
|
||||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
|
||||||
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
|
|
||||||
from esphome import mqtt
|
|
||||||
|
|
||||||
port = mqtt.get_esphome_device_ip(
|
port_type = get_port_type(port)
|
||||||
|
|
||||||
|
# Check if we should use API for logging
|
||||||
|
if has_api():
|
||||||
|
addresses_to_use: list[str] | None = None
|
||||||
|
|
||||||
|
if port_type == "NETWORK":
|
||||||
|
# Network addresses (IPs, mDNS names, or regular DNS hostnames) can be used
|
||||||
|
# The resolve_ip_address() function in helpers.py handles all types
|
||||||
|
addresses_to_use = devices
|
||||||
|
elif port_type in ("MQTT", "MQTTIP") and has_mqtt_ip_lookup():
|
||||||
|
# Use MQTT IP lookup for MQTT/MQTTIP types
|
||||||
|
addresses_to_use = mqtt_get_ip(
|
||||||
config, args.username, args.password, args.client_id
|
config, args.username, args.password, args.client_id
|
||||||
)[0]
|
)
|
||||||
|
|
||||||
|
if addresses_to_use is not None:
|
||||||
from esphome.components.api.client import run_logs
|
from esphome.components.api.client import run_logs
|
||||||
|
|
||||||
return run_logs(config, port)
|
return run_logs(config, addresses_to_use)
|
||||||
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
|
||||||
|
if port_type in ("NETWORK", "MQTT") and has_mqtt_logging():
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.show_logs(
|
return mqtt.show_logs(
|
||||||
@@ -426,7 +605,7 @@ def show_logs(config, args, port):
|
|||||||
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||||
|
|
||||||
|
|
||||||
def clean_mqtt(config, args):
|
def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.clear_topic(
|
return mqtt.clear_topic(
|
||||||
@@ -434,13 +613,13 @@ def clean_mqtt(config, args):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def command_wizard(args):
|
def command_wizard(args: ArgsProtocol) -> int | None:
|
||||||
from esphome import wizard
|
from esphome import wizard
|
||||||
|
|
||||||
return wizard.wizard(args.configuration)
|
return wizard.wizard(Path(args.configuration))
|
||||||
|
|
||||||
|
|
||||||
def command_config(args, config):
|
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
if not CORE.verbose:
|
if not CORE.verbose:
|
||||||
config = strip_default_ids(config)
|
config = strip_default_ids(config)
|
||||||
output = yaml_util.dump(config, args.show_secrets)
|
output = yaml_util.dump(config, args.show_secrets)
|
||||||
@@ -455,7 +634,7 @@ def command_config(args, config):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_vscode(args):
|
def command_vscode(args: ArgsProtocol) -> int | None:
|
||||||
from esphome import vscode
|
from esphome import vscode
|
||||||
|
|
||||||
logging.disable(logging.INFO)
|
logging.disable(logging.INFO)
|
||||||
@@ -463,7 +642,7 @@ def command_vscode(args):
|
|||||||
vscode.read_config(args)
|
vscode.read_config(args)
|
||||||
|
|
||||||
|
|
||||||
def command_compile(args, config):
|
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
exit_code = write_cpp(config)
|
exit_code = write_cpp(config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
@@ -477,23 +656,23 @@ def command_compile(args, config):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_upload(args, config):
|
def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
port = choose_upload_log_host(
|
# Get devices, resolving special identifiers like OTA
|
||||||
|
devices = choose_upload_log_host(
|
||||||
default=args.device,
|
default=args.device,
|
||||||
check_default=None,
|
check_default=None,
|
||||||
show_ota=True,
|
purpose=Purpose.UPLOADING,
|
||||||
show_mqtt=False,
|
|
||||||
show_api=False,
|
|
||||||
purpose="uploading",
|
|
||||||
)
|
)
|
||||||
exit_code = upload_program(config, args, port)
|
|
||||||
if exit_code != 0:
|
exit_code, _ = upload_program(config, args, devices)
|
||||||
return exit_code
|
if exit_code == 0:
|
||||||
_LOGGER.info("Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
return 0
|
else:
|
||||||
|
_LOGGER.warning("Failed to upload to %s", devices)
|
||||||
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
def command_discover(args, config):
|
def command_discover(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
if "mqtt" in config:
|
if "mqtt" in config:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
@@ -502,19 +681,17 @@ def command_discover(args, config):
|
|||||||
raise EsphomeError("No discover method configured (mqtt)")
|
raise EsphomeError("No discover method configured (mqtt)")
|
||||||
|
|
||||||
|
|
||||||
def command_logs(args, config):
|
def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
port = choose_upload_log_host(
|
# Get devices, resolving special identifiers like OTA
|
||||||
|
devices = choose_upload_log_host(
|
||||||
default=args.device,
|
default=args.device,
|
||||||
check_default=None,
|
check_default=None,
|
||||||
show_ota=False,
|
purpose=Purpose.LOGGING,
|
||||||
show_mqtt=True,
|
|
||||||
show_api=True,
|
|
||||||
purpose="logging",
|
|
||||||
)
|
)
|
||||||
return show_logs(config, args, port)
|
return show_logs(config, args, devices)
|
||||||
|
|
||||||
|
|
||||||
def command_run(args, config):
|
def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
exit_code = write_cpp(config)
|
exit_code = write_cpp(config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
@@ -531,47 +708,58 @@ def command_run(args, config):
|
|||||||
program_path = idedata.raw["prog_path"]
|
program_path = idedata.raw["prog_path"]
|
||||||
return run_external_process(program_path)
|
return run_external_process(program_path)
|
||||||
|
|
||||||
port = choose_upload_log_host(
|
# Get devices, resolving special identifiers like OTA
|
||||||
|
devices = choose_upload_log_host(
|
||||||
default=args.device,
|
default=args.device,
|
||||||
check_default=None,
|
check_default=None,
|
||||||
show_ota=True,
|
purpose=Purpose.UPLOADING,
|
||||||
show_mqtt=False,
|
|
||||||
show_api=True,
|
|
||||||
purpose="uploading",
|
|
||||||
)
|
)
|
||||||
exit_code = upload_program(config, args, port)
|
|
||||||
if exit_code != 0:
|
exit_code, successful_device = upload_program(config, args, devices)
|
||||||
return exit_code
|
if exit_code == 0:
|
||||||
_LOGGER.info("Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("Failed to upload to %s", devices)
|
||||||
|
return exit_code
|
||||||
|
|
||||||
if args.no_logs:
|
if args.no_logs:
|
||||||
return 0
|
return 0
|
||||||
port = choose_upload_log_host(
|
|
||||||
default=args.device,
|
# For logs, prefer the device we successfully uploaded to
|
||||||
check_default=port,
|
devices = choose_upload_log_host(
|
||||||
show_ota=False,
|
default=successful_device,
|
||||||
show_mqtt=True,
|
check_default=successful_device,
|
||||||
show_api=True,
|
purpose=Purpose.LOGGING,
|
||||||
purpose="logging",
|
|
||||||
)
|
)
|
||||||
return show_logs(config, args, port)
|
return show_logs(config, args, devices)
|
||||||
|
|
||||||
|
|
||||||
def command_clean_mqtt(args, config):
|
def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
return clean_mqtt(config, args)
|
return clean_mqtt(config, args)
|
||||||
|
|
||||||
|
|
||||||
def command_mqtt_fingerprint(args, config):
|
def command_clean_all(args: ArgsProtocol) -> int | None:
|
||||||
|
try:
|
||||||
|
writer.clean_all(args.configuration)
|
||||||
|
except OSError as err:
|
||||||
|
_LOGGER.error("Error cleaning all files: %s", err)
|
||||||
|
return 1
|
||||||
|
_LOGGER.info("Done!")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.get_fingerprint(config)
|
return mqtt.get_fingerprint(config)
|
||||||
|
|
||||||
|
|
||||||
def command_version(args):
|
def command_version(args: ArgsProtocol) -> int | None:
|
||||||
safe_print(f"Version: {const.__version__}")
|
safe_print(f"Version: {const.__version__}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_clean(args, config):
|
def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
try:
|
try:
|
||||||
writer.clean_build()
|
writer.clean_build()
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
@@ -581,13 +769,13 @@ def command_clean(args, config):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_dashboard(args):
|
def command_dashboard(args: ArgsProtocol) -> int | None:
|
||||||
from esphome.dashboard import dashboard
|
from esphome.dashboard import dashboard
|
||||||
|
|
||||||
return dashboard.start_dashboard(args)
|
return dashboard.start_dashboard(args)
|
||||||
|
|
||||||
|
|
||||||
def command_update_all(args):
|
def command_update_all(args: ArgsProtocol) -> int | None:
|
||||||
import click
|
import click
|
||||||
|
|
||||||
success = {}
|
success = {}
|
||||||
@@ -601,7 +789,7 @@ def command_update_all(args):
|
|||||||
safe_print(f"{half_line}{middle_text}{half_line}")
|
safe_print(f"{half_line}{middle_text}{half_line}")
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
safe_print(f"Updating {color(AnsiFore.CYAN, f)}")
|
safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
|
||||||
safe_print("-" * twidth)
|
safe_print("-" * twidth)
|
||||||
safe_print()
|
safe_print()
|
||||||
if CORE.dashboard:
|
if CORE.dashboard:
|
||||||
@@ -613,10 +801,10 @@ def command_update_all(args):
|
|||||||
"esphome", "run", f, "--no-logs", "--device", "OTA"
|
"esphome", "run", f, "--no-logs", "--device", "OTA"
|
||||||
)
|
)
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
|
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
|
||||||
success[f] = True
|
success[f] = True
|
||||||
else:
|
else:
|
||||||
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
|
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {str(f)}")
|
||||||
success[f] = False
|
success[f] = False
|
||||||
|
|
||||||
safe_print()
|
safe_print()
|
||||||
@@ -627,14 +815,14 @@ def command_update_all(args):
|
|||||||
failed = 0
|
failed = 0
|
||||||
for f in files:
|
for f in files:
|
||||||
if success[f]:
|
if success[f]:
|
||||||
safe_print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
||||||
else:
|
else:
|
||||||
safe_print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
|
safe_print(f" - {str(f)}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
|
||||||
failed += 1
|
failed += 1
|
||||||
return failed
|
return failed
|
||||||
|
|
||||||
|
|
||||||
def command_idedata(args, config):
|
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from esphome import platformio_api
|
from esphome import platformio_api
|
||||||
@@ -650,8 +838,9 @@ def command_idedata(args, config):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_rename(args, config):
|
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
for c in args.name:
|
new_name = args.name
|
||||||
|
for c in new_name:
|
||||||
if c not in ALLOWED_NAME_CHARS:
|
if c not in ALLOWED_NAME_CHARS:
|
||||||
print(
|
print(
|
||||||
color(
|
color(
|
||||||
@@ -662,8 +851,7 @@ def command_rename(args, config):
|
|||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
# Load existing yaml file
|
# Load existing yaml file
|
||||||
with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file:
|
raw_contents = CORE.config_path.read_text(encoding="utf-8")
|
||||||
raw_contents = raw_file.read()
|
|
||||||
|
|
||||||
yaml = yaml_util.load_yaml(CORE.config_path)
|
yaml = yaml_util.load_yaml(CORE.config_path)
|
||||||
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
|
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
|
||||||
@@ -678,7 +866,7 @@ def command_rename(args, config):
|
|||||||
if match is None:
|
if match is None:
|
||||||
new_raw = re.sub(
|
new_raw = re.sub(
|
||||||
rf"name:\s+[\"']?{old_name}[\"']?",
|
rf"name:\s+[\"']?{old_name}[\"']?",
|
||||||
f'name: "{args.name}"',
|
f'name: "{new_name}"',
|
||||||
raw_contents,
|
raw_contents,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -698,29 +886,28 @@ def command_rename(args, config):
|
|||||||
|
|
||||||
new_raw = re.sub(
|
new_raw = re.sub(
|
||||||
rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
|
rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
|
||||||
f'\\1: "{args.name}"',
|
f'\\1: "{new_name}"',
|
||||||
raw_contents,
|
raw_contents,
|
||||||
flags=re.MULTILINE,
|
flags=re.MULTILINE,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
|
new_path: Path = CORE.config_dir / (new_name + ".yaml")
|
||||||
print(
|
print(
|
||||||
f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
|
f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
|
||||||
)
|
)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
with open(new_path, mode="w", encoding="utf-8") as new_file:
|
new_path.write_text(new_raw, encoding="utf-8")
|
||||||
new_file.write(new_raw)
|
|
||||||
|
|
||||||
rc = run_external_process("esphome", "config", new_path)
|
rc = run_external_process("esphome", "config", str(new_path))
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
|
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
|
||||||
os.remove(new_path)
|
new_path.unlink()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
cli_args = [
|
cli_args = [
|
||||||
"run",
|
"run",
|
||||||
new_path,
|
str(new_path),
|
||||||
"--no-logs",
|
"--no-logs",
|
||||||
"--device",
|
"--device",
|
||||||
CORE.address,
|
CORE.address,
|
||||||
@@ -734,11 +921,11 @@ def command_rename(args, config):
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
rc = 1
|
rc = 1
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
os.remove(new_path)
|
new_path.unlink()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if CORE.config_path != new_path:
|
if CORE.config_path != new_path:
|
||||||
os.remove(CORE.config_path)
|
CORE.config_path.unlink()
|
||||||
|
|
||||||
print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
|
print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
|
||||||
print()
|
print()
|
||||||
@@ -751,6 +938,7 @@ PRE_CONFIG_ACTIONS = {
|
|||||||
"dashboard": command_dashboard,
|
"dashboard": command_dashboard,
|
||||||
"vscode": command_vscode,
|
"vscode": command_vscode,
|
||||||
"update-all": command_update_all,
|
"update-all": command_update_all,
|
||||||
|
"clean-all": command_clean_all,
|
||||||
}
|
}
|
||||||
|
|
||||||
POST_CONFIG_ACTIONS = {
|
POST_CONFIG_ACTIONS = {
|
||||||
@@ -759,14 +947,20 @@ POST_CONFIG_ACTIONS = {
|
|||||||
"upload": command_upload,
|
"upload": command_upload,
|
||||||
"logs": command_logs,
|
"logs": command_logs,
|
||||||
"run": command_run,
|
"run": command_run,
|
||||||
|
"clean": command_clean,
|
||||||
"clean-mqtt": command_clean_mqtt,
|
"clean-mqtt": command_clean_mqtt,
|
||||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||||
"clean": command_clean,
|
|
||||||
"idedata": command_idedata,
|
"idedata": command_idedata,
|
||||||
"rename": command_rename,
|
"rename": command_rename,
|
||||||
"discover": command_discover,
|
"discover": command_discover,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SIMPLE_CONFIG_ACTIONS = [
|
||||||
|
"clean",
|
||||||
|
"clean-mqtt",
|
||||||
|
"config",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv):
|
def parse_args(argv):
|
||||||
options_parser = argparse.ArgumentParser(add_help=False)
|
options_parser = argparse.ArgumentParser(add_help=False)
|
||||||
@@ -799,6 +993,24 @@ def parse_args(argv):
|
|||||||
help="Add a substitution",
|
help="Add a substitution",
|
||||||
metavar=("key", "value"),
|
metavar=("key", "value"),
|
||||||
)
|
)
|
||||||
|
options_parser.add_argument(
|
||||||
|
"--mdns-address-cache",
|
||||||
|
help="mDNS address cache mapping in format 'hostname=ip1,ip2'",
|
||||||
|
action="append",
|
||||||
|
default=[],
|
||||||
|
)
|
||||||
|
options_parser.add_argument(
|
||||||
|
"--dns-address-cache",
|
||||||
|
help="DNS address cache mapping in format 'hostname=ip1,ip2'",
|
||||||
|
action="append",
|
||||||
|
default=[],
|
||||||
|
)
|
||||||
|
options_parser.add_argument(
|
||||||
|
"--testing-mode",
|
||||||
|
help="Enable testing mode (disables validation checks for grouped component testing)",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description=f"ESPHome {const.__version__}", parents=[options_parser]
|
description=f"ESPHome {const.__version__}", parents=[options_parser]
|
||||||
@@ -854,7 +1066,8 @@ def parse_args(argv):
|
|||||||
)
|
)
|
||||||
parser_upload.add_argument(
|
parser_upload.add_argument(
|
||||||
"--device",
|
"--device",
|
||||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
action="append",
|
||||||
|
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||||
)
|
)
|
||||||
parser_upload.add_argument(
|
parser_upload.add_argument(
|
||||||
"--upload_speed",
|
"--upload_speed",
|
||||||
@@ -876,7 +1089,8 @@ def parse_args(argv):
|
|||||||
)
|
)
|
||||||
parser_logs.add_argument(
|
parser_logs.add_argument(
|
||||||
"--device",
|
"--device",
|
||||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
action="append",
|
||||||
|
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||||
)
|
)
|
||||||
parser_logs.add_argument(
|
parser_logs.add_argument(
|
||||||
"--reset",
|
"--reset",
|
||||||
@@ -905,7 +1119,8 @@ def parse_args(argv):
|
|||||||
)
|
)
|
||||||
parser_run.add_argument(
|
parser_run.add_argument(
|
||||||
"--device",
|
"--device",
|
||||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
action="append",
|
||||||
|
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||||
)
|
)
|
||||||
parser_run.add_argument(
|
parser_run.add_argument(
|
||||||
"--upload_speed",
|
"--upload_speed",
|
||||||
@@ -953,6 +1168,13 @@ def parse_args(argv):
|
|||||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser_clean_all = subparsers.add_parser(
|
||||||
|
"clean-all", help="Clean all build and platform files."
|
||||||
|
)
|
||||||
|
parser_clean_all.add_argument(
|
||||||
|
"configuration", help="Your YAML configuration directory.", nargs="*"
|
||||||
|
)
|
||||||
|
|
||||||
parser_dashboard = subparsers.add_parser(
|
parser_dashboard = subparsers.add_parser(
|
||||||
"dashboard", help="Create a simple web server for a dashboard."
|
"dashboard", help="Create a simple web server for a dashboard."
|
||||||
)
|
)
|
||||||
@@ -999,7 +1221,7 @@ def parse_args(argv):
|
|||||||
|
|
||||||
parser_update = subparsers.add_parser("update-all")
|
parser_update = subparsers.add_parser("update-all")
|
||||||
parser_update.add_argument(
|
parser_update.add_argument(
|
||||||
"configuration", help="Your YAML configuration file directories.", nargs="+"
|
"configuration", help="Your YAML configuration file or directory.", nargs="+"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser_idedata = subparsers.add_parser("idedata")
|
parser_idedata = subparsers.add_parser("idedata")
|
||||||
@@ -1032,13 +1254,27 @@ def parse_args(argv):
|
|||||||
arguments = argv[1:]
|
arguments = argv[1:]
|
||||||
|
|
||||||
argcomplete.autocomplete(parser)
|
argcomplete.autocomplete(parser)
|
||||||
|
|
||||||
|
if len(arguments) > 0 and arguments[0] in SIMPLE_CONFIG_ACTIONS:
|
||||||
|
args, unknown_args = parser.parse_known_args(arguments)
|
||||||
|
if unknown_args:
|
||||||
|
_LOGGER.warning("Ignored unrecognized arguments: %s", unknown_args)
|
||||||
|
return args
|
||||||
|
|
||||||
return parser.parse_args(arguments)
|
return parser.parse_args(arguments)
|
||||||
|
|
||||||
|
|
||||||
def run_esphome(argv):
|
def run_esphome(argv):
|
||||||
|
from esphome.address_cache import AddressCache
|
||||||
|
|
||||||
args = parse_args(argv)
|
args = parse_args(argv)
|
||||||
CORE.dashboard = args.dashboard
|
CORE.dashboard = args.dashboard
|
||||||
|
CORE.testing_mode = args.testing_mode
|
||||||
|
|
||||||
|
# Create address cache from command-line arguments
|
||||||
|
CORE.address_cache = AddressCache.from_cli_args(
|
||||||
|
args.mdns_address_cache, args.dns_address_cache
|
||||||
|
)
|
||||||
# Override log level if verbose is set
|
# Override log level if verbose is set
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
args.log_level = "DEBUG"
|
args.log_level = "DEBUG"
|
||||||
@@ -1061,14 +1297,20 @@ def run_esphome(argv):
|
|||||||
_LOGGER.info("ESPHome %s", const.__version__)
|
_LOGGER.info("ESPHome %s", const.__version__)
|
||||||
|
|
||||||
for conf_path in args.configuration:
|
for conf_path in args.configuration:
|
||||||
if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
|
conf_path = Path(conf_path)
|
||||||
|
if any(conf_path.name == x for x in SECRETS_FILES):
|
||||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
CORE.config_path = conf_path
|
CORE.config_path = conf_path
|
||||||
CORE.dashboard = args.dashboard
|
CORE.dashboard = args.dashboard
|
||||||
|
|
||||||
config = read_config(dict(args.substitution) if args.substitution else {})
|
# For logs command, skip updating external components
|
||||||
|
skip_external = args.command == "logs"
|
||||||
|
config = read_config(
|
||||||
|
dict(args.substitution) if args.substitution else {},
|
||||||
|
skip_external_update=skip_external,
|
||||||
|
)
|
||||||
if config is None:
|
if config is None:
|
||||||
return 2
|
return 2
|
||||||
CORE.config = config
|
CORE.config = config
|
||||||
|
|||||||
142
esphome/address_cache.py
Normal file
142
esphome/address_cache.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
"""Address cache for DNS and mDNS lookups."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_hostname(hostname: str) -> str:
|
||||||
|
"""Normalize hostname for cache lookups.
|
||||||
|
|
||||||
|
Removes trailing dots and converts to lowercase.
|
||||||
|
"""
|
||||||
|
return hostname.rstrip(".").lower()
|
||||||
|
|
||||||
|
|
||||||
|
class AddressCache:
|
||||||
|
"""Cache for DNS and mDNS address lookups.
|
||||||
|
|
||||||
|
This cache stores pre-resolved addresses from command-line arguments
|
||||||
|
to avoid slow DNS/mDNS lookups during builds.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
mdns_cache: dict[str, list[str]] | None = None,
|
||||||
|
dns_cache: dict[str, list[str]] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the address cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mdns_cache: Pre-populated mDNS addresses (hostname -> IPs)
|
||||||
|
dns_cache: Pre-populated DNS addresses (hostname -> IPs)
|
||||||
|
"""
|
||||||
|
self.mdns_cache = mdns_cache or {}
|
||||||
|
self.dns_cache = dns_cache or {}
|
||||||
|
|
||||||
|
def _get_cached_addresses(
|
||||||
|
self, hostname: str, cache: dict[str, list[str]], cache_type: str
|
||||||
|
) -> list[str] | None:
|
||||||
|
"""Get cached addresses from a specific cache.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: The hostname to look up
|
||||||
|
cache: The cache dictionary to check
|
||||||
|
cache_type: Type of cache for logging ("mDNS" or "DNS")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of IP addresses if found in cache, None otherwise
|
||||||
|
"""
|
||||||
|
normalized = normalize_hostname(hostname)
|
||||||
|
if addresses := cache.get(normalized):
|
||||||
|
_LOGGER.debug("Using %s cache for %s: %s", cache_type, hostname, addresses)
|
||||||
|
return addresses
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_mdns_addresses(self, hostname: str) -> list[str] | None:
|
||||||
|
"""Get cached mDNS addresses for a hostname.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: The hostname to look up (should end with .local)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of IP addresses if found in cache, None otherwise
|
||||||
|
"""
|
||||||
|
return self._get_cached_addresses(hostname, self.mdns_cache, "mDNS")
|
||||||
|
|
||||||
|
def get_dns_addresses(self, hostname: str) -> list[str] | None:
|
||||||
|
"""Get cached DNS addresses for a hostname.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: The hostname to look up
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of IP addresses if found in cache, None otherwise
|
||||||
|
"""
|
||||||
|
return self._get_cached_addresses(hostname, self.dns_cache, "DNS")
|
||||||
|
|
||||||
|
def get_addresses(self, hostname: str) -> list[str] | None:
|
||||||
|
"""Get cached addresses for a hostname.
|
||||||
|
|
||||||
|
Checks mDNS cache for .local domains, DNS cache otherwise.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: The hostname to look up
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of IP addresses if found in cache, None otherwise
|
||||||
|
"""
|
||||||
|
normalized = normalize_hostname(hostname)
|
||||||
|
if normalized.endswith(".local"):
|
||||||
|
return self.get_mdns_addresses(hostname)
|
||||||
|
return self.get_dns_addresses(hostname)
|
||||||
|
|
||||||
|
def has_cache(self) -> bool:
|
||||||
|
"""Check if any cache entries exist."""
|
||||||
|
return bool(self.mdns_cache or self.dns_cache)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_cli_args(
|
||||||
|
cls, mdns_args: Iterable[str], dns_args: Iterable[str]
|
||||||
|
) -> AddressCache:
|
||||||
|
"""Create cache from command-line arguments.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mdns_args: List of mDNS cache entries like ['host=ip1,ip2']
|
||||||
|
dns_args: List of DNS cache entries like ['host=ip1,ip2']
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Configured AddressCache instance
|
||||||
|
"""
|
||||||
|
mdns_cache = cls._parse_cache_args(mdns_args)
|
||||||
|
dns_cache = cls._parse_cache_args(dns_args)
|
||||||
|
return cls(mdns_cache=mdns_cache, dns_cache=dns_cache)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_cache_args(cache_args: Iterable[str]) -> dict[str, list[str]]:
|
||||||
|
"""Parse cache arguments into a dictionary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cache_args: List of cache mappings like ['host1=ip1,ip2', 'host2=ip3']
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping normalized hostnames to list of IP addresses
|
||||||
|
"""
|
||||||
|
cache: dict[str, list[str]] = {}
|
||||||
|
for arg in cache_args:
|
||||||
|
if "=" not in arg:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Invalid cache format: %s (expected 'hostname=ip1,ip2')", arg
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
hostname, ips = arg.split("=", 1)
|
||||||
|
# Normalize hostname for consistent lookups
|
||||||
|
normalized = normalize_hostname(hostname)
|
||||||
|
cache[normalized] = [ip.strip() for ip in ips.split(",")]
|
||||||
|
return cache
|
||||||
@@ -15,7 +15,10 @@ from esphome.const import (
|
|||||||
CONF_TYPE_ID,
|
CONF_TYPE_ID,
|
||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
)
|
)
|
||||||
|
from esphome.core import ID
|
||||||
|
from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
|
||||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||||
|
from esphome.types import ConfigType
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
|
|
||||||
|
|
||||||
@@ -49,11 +52,11 @@ def maybe_conf(conf, *validators):
|
|||||||
return validate
|
return validate
|
||||||
|
|
||||||
|
|
||||||
def register_action(name, action_type, schema):
|
def register_action(name: str, action_type: MockObjClass, schema: cv.Schema):
|
||||||
return ACTION_REGISTRY.register(name, action_type, schema)
|
return ACTION_REGISTRY.register(name, action_type, schema)
|
||||||
|
|
||||||
|
|
||||||
def register_condition(name, condition_type, schema):
|
def register_condition(name: str, condition_type: MockObjClass, schema: cv.Schema):
|
||||||
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
||||||
|
|
||||||
|
|
||||||
@@ -164,43 +167,78 @@ XorCondition = cg.esphome_ns.class_("XorCondition", Condition)
|
|||||||
|
|
||||||
|
|
||||||
@register_condition("and", AndCondition, validate_condition_list)
|
@register_condition("and", AndCondition, validate_condition_list)
|
||||||
async def and_condition_to_code(config, condition_id, template_arg, args):
|
async def and_condition_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
condition_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
conditions = await build_condition_list(config, template_arg, args)
|
conditions = await build_condition_list(config, template_arg, args)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||||
|
|
||||||
|
|
||||||
@register_condition("or", OrCondition, validate_condition_list)
|
@register_condition("or", OrCondition, validate_condition_list)
|
||||||
async def or_condition_to_code(config, condition_id, template_arg, args):
|
async def or_condition_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
condition_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
conditions = await build_condition_list(config, template_arg, args)
|
conditions = await build_condition_list(config, template_arg, args)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||||
|
|
||||||
|
|
||||||
@register_condition("all", AndCondition, validate_condition_list)
|
@register_condition("all", AndCondition, validate_condition_list)
|
||||||
async def all_condition_to_code(config, condition_id, template_arg, args):
|
async def all_condition_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
condition_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
conditions = await build_condition_list(config, template_arg, args)
|
conditions = await build_condition_list(config, template_arg, args)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||||
|
|
||||||
|
|
||||||
@register_condition("any", OrCondition, validate_condition_list)
|
@register_condition("any", OrCondition, validate_condition_list)
|
||||||
async def any_condition_to_code(config, condition_id, template_arg, args):
|
async def any_condition_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
condition_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
conditions = await build_condition_list(config, template_arg, args)
|
conditions = await build_condition_list(config, template_arg, args)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||||
|
|
||||||
|
|
||||||
@register_condition("not", NotCondition, validate_potentially_and_condition)
|
@register_condition("not", NotCondition, validate_potentially_and_condition)
|
||||||
async def not_condition_to_code(config, condition_id, template_arg, args):
|
async def not_condition_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
condition_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
condition = await build_condition(config, template_arg, args)
|
condition = await build_condition(config, template_arg, args)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, condition)
|
return cg.new_Pvariable(condition_id, template_arg, condition)
|
||||||
|
|
||||||
|
|
||||||
@register_condition("xor", XorCondition, validate_condition_list)
|
@register_condition("xor", XorCondition, validate_condition_list)
|
||||||
async def xor_condition_to_code(config, condition_id, template_arg, args):
|
async def xor_condition_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
condition_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
conditions = await build_condition_list(config, template_arg, args)
|
conditions = await build_condition_list(config, template_arg, args)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||||
|
|
||||||
|
|
||||||
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
||||||
async def lambda_condition_to_code(config, condition_id, template_arg, args):
|
async def lambda_condition_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
condition_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, lambda_)
|
return cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||||
|
|
||||||
@@ -217,7 +255,12 @@ async def lambda_condition_to_code(config, condition_id, template_arg, args):
|
|||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
)
|
)
|
||||||
async def for_condition_to_code(config, condition_id, template_arg, args):
|
async def for_condition_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
condition_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
condition = await build_condition(
|
condition = await build_condition(
|
||||||
config[CONF_CONDITION], cg.TemplateArguments(), []
|
config[CONF_CONDITION], cg.TemplateArguments(), []
|
||||||
)
|
)
|
||||||
@@ -231,7 +274,12 @@ async def for_condition_to_code(config, condition_id, template_arg, args):
|
|||||||
@register_action(
|
@register_action(
|
||||||
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
||||||
)
|
)
|
||||||
async def delay_action_to_code(config, action_id, template_arg, args):
|
async def delay_action_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
await cg.register_component(var, {})
|
await cg.register_component(var, {})
|
||||||
template_ = await cg.templatable(config, args, cg.uint32)
|
template_ = await cg.templatable(config, args, cg.uint32)
|
||||||
@@ -256,10 +304,15 @@ async def delay_action_to_code(config, action_id, template_arg, args):
|
|||||||
cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
|
cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def if_action_to_code(config, action_id, template_arg, args):
|
async def if_action_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
|
cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
|
||||||
conditions = await build_condition(config[cond_conf], template_arg, args)
|
condition = await build_condition(config[cond_conf], template_arg, args)
|
||||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
var = cg.new_Pvariable(action_id, template_arg, condition)
|
||||||
if CONF_THEN in config:
|
if CONF_THEN in config:
|
||||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||||
cg.add(var.add_then(actions))
|
cg.add(var.add_then(actions))
|
||||||
@@ -279,9 +332,14 @@ async def if_action_to_code(config, action_id, template_arg, args):
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def while_action_to_code(config, action_id, template_arg, args):
|
async def while_action_to_code(
|
||||||
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
|
config: ConfigType,
|
||||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
|
condition = await build_condition(config[CONF_CONDITION], template_arg, args)
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, condition)
|
||||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||||
cg.add(var.add_then(actions))
|
cg.add(var.add_then(actions))
|
||||||
return var
|
return var
|
||||||
@@ -297,7 +355,12 @@ async def while_action_to_code(config, action_id, template_arg, args):
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def repeat_action_to_code(config, action_id, template_arg, args):
|
async def repeat_action_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
|
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
|
||||||
cg.add(var.set_count(count_template))
|
cg.add(var.set_count(count_template))
|
||||||
@@ -320,9 +383,14 @@ _validate_wait_until = cv.maybe_simple_value(
|
|||||||
|
|
||||||
|
|
||||||
@register_action("wait_until", WaitUntilAction, _validate_wait_until)
|
@register_action("wait_until", WaitUntilAction, _validate_wait_until)
|
||||||
async def wait_until_action_to_code(config, action_id, template_arg, args):
|
async def wait_until_action_to_code(
|
||||||
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
|
config: ConfigType,
|
||||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
|
condition = await build_condition(config[CONF_CONDITION], template_arg, args)
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, condition)
|
||||||
if CONF_TIMEOUT in config:
|
if CONF_TIMEOUT in config:
|
||||||
template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
|
template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
|
||||||
cg.add(var.set_timeout_value(template_))
|
cg.add(var.set_timeout_value(template_))
|
||||||
@@ -331,7 +399,12 @@ async def wait_until_action_to_code(config, action_id, template_arg, args):
|
|||||||
|
|
||||||
|
|
||||||
@register_action("lambda", LambdaAction, cv.lambda_)
|
@register_action("lambda", LambdaAction, cv.lambda_)
|
||||||
async def lambda_action_to_code(config, action_id, template_arg, args):
|
async def lambda_action_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
||||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||||
|
|
||||||
@@ -345,7 +418,12 @@ async def lambda_action_to_code(config, action_id, template_arg, args):
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def component_update_action_to_code(config, action_id, template_arg, args):
|
async def component_update_action_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
comp = await cg.get_variable(config[CONF_ID])
|
comp = await cg.get_variable(config[CONF_ID])
|
||||||
return cg.new_Pvariable(action_id, template_arg, comp)
|
return cg.new_Pvariable(action_id, template_arg, comp)
|
||||||
|
|
||||||
@@ -359,7 +437,12 @@ async def component_update_action_to_code(config, action_id, template_arg, args)
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def component_suspend_action_to_code(config, action_id, template_arg, args):
|
async def component_suspend_action_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
comp = await cg.get_variable(config[CONF_ID])
|
comp = await cg.get_variable(config[CONF_ID])
|
||||||
return cg.new_Pvariable(action_id, template_arg, comp)
|
return cg.new_Pvariable(action_id, template_arg, comp)
|
||||||
|
|
||||||
@@ -376,7 +459,12 @@ async def component_suspend_action_to_code(config, action_id, template_arg, args
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def component_resume_action_to_code(config, action_id, template_arg, args):
|
async def component_resume_action_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
) -> MockObj:
|
||||||
comp = await cg.get_variable(config[CONF_ID])
|
comp = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, comp)
|
var = cg.new_Pvariable(action_id, template_arg, comp)
|
||||||
if CONF_UPDATE_INTERVAL in config:
|
if CONF_UPDATE_INTERVAL in config:
|
||||||
@@ -385,7 +473,9 @@ async def component_resume_action_to_code(config, action_id, template_arg, args)
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
async def build_action(full_config, template_arg, args):
|
async def build_action(
|
||||||
|
full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
|
||||||
|
) -> MockObj:
|
||||||
registry_entry, config = cg.extract_registry_entry_config(
|
registry_entry, config = cg.extract_registry_entry_config(
|
||||||
ACTION_REGISTRY, full_config
|
ACTION_REGISTRY, full_config
|
||||||
)
|
)
|
||||||
@@ -394,15 +484,19 @@ async def build_action(full_config, template_arg, args):
|
|||||||
return await builder(config, action_id, template_arg, args)
|
return await builder(config, action_id, template_arg, args)
|
||||||
|
|
||||||
|
|
||||||
async def build_action_list(config, templ, arg_type):
|
async def build_action_list(
|
||||||
actions = []
|
config: list[ConfigType], templ: cg.TemplateArguments, arg_type: TemplateArgsType
|
||||||
|
) -> list[MockObj]:
|
||||||
|
actions: list[MockObj] = []
|
||||||
for conf in config:
|
for conf in config:
|
||||||
action = await build_action(conf, templ, arg_type)
|
action = await build_action(conf, templ, arg_type)
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
|
|
||||||
async def build_condition(full_config, template_arg, args):
|
async def build_condition(
|
||||||
|
full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
|
||||||
|
) -> MockObj:
|
||||||
registry_entry, config = cg.extract_registry_entry_config(
|
registry_entry, config = cg.extract_registry_entry_config(
|
||||||
CONDITION_REGISTRY, full_config
|
CONDITION_REGISTRY, full_config
|
||||||
)
|
)
|
||||||
@@ -411,15 +505,19 @@ async def build_condition(full_config, template_arg, args):
|
|||||||
return await builder(config, action_id, template_arg, args)
|
return await builder(config, action_id, template_arg, args)
|
||||||
|
|
||||||
|
|
||||||
async def build_condition_list(config, templ, args):
|
async def build_condition_list(
|
||||||
conditions = []
|
config: ConfigType, templ: cg.TemplateArguments, args: TemplateArgsType
|
||||||
|
) -> list[MockObj]:
|
||||||
|
conditions: list[MockObj] = []
|
||||||
for conf in config:
|
for conf in config:
|
||||||
condition = await build_condition(conf, templ, args)
|
condition = await build_condition(conf, templ, args)
|
||||||
conditions.append(condition)
|
conditions.append(condition)
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
|
|
||||||
async def build_automation(trigger, args, config):
|
async def build_automation(
|
||||||
|
trigger: MockObj, args: TemplateArgsType, config: ConfigType
|
||||||
|
) -> MockObj:
|
||||||
arg_types = [arg[0] for arg in args]
|
arg_types = [arg[0] for arg in args]
|
||||||
templ = cg.TemplateArguments(*arg_types)
|
templ = cg.TemplateArguments(*arg_types)
|
||||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
|
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from esphome.const import __version__
|
from esphome.const import __version__
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.helpers import mkdir_p, read_file, write_file_if_changed
|
from esphome.helpers import mkdir_p, read_file, write_file_if_changed
|
||||||
@@ -63,7 +61,7 @@ def write_ini(content):
|
|||||||
update_storage_json()
|
update_storage_json()
|
||||||
path = CORE.relative_build_path("platformio.ini")
|
path = CORE.relative_build_path("platformio.ini")
|
||||||
|
|
||||||
if os.path.isfile(path):
|
if path.is_file():
|
||||||
text = read_file(path)
|
text = read_file(path)
|
||||||
content_format = find_begin_end(
|
content_format = find_begin_end(
|
||||||
text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END
|
text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
|||||||
ArrayInitializer,
|
ArrayInitializer,
|
||||||
Expression,
|
Expression,
|
||||||
LineComment,
|
LineComment,
|
||||||
|
LogStringLiteral,
|
||||||
MockObj,
|
MockObj,
|
||||||
MockObjClass,
|
MockObjClass,
|
||||||
Pvariable,
|
Pvariable,
|
||||||
|
|||||||
@@ -61,11 +61,10 @@ void AbsoluteHumidityComponent::loop() {
|
|||||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||||
}
|
}
|
||||||
if (no_humidity) {
|
if (no_humidity) {
|
||||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
ESP_LOGW(TAG, "No valid state from humidity sensor!");
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
|
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
this->status_set_warning();
|
this->status_set_warning(LOG_STR("Unable to calculate absolute humidity."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +86,8 @@ void AbsoluteHumidityComponent::loop() {
|
|||||||
es = es_wobus(temperature_c);
|
es = es_wobus(temperature_c);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
|
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
this->status_set_error();
|
this->status_set_error("Invalid saturation vapor pressure equation selection!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from esphome.const import (
|
|||||||
CONF_EQUATION,
|
CONF_EQUATION,
|
||||||
CONF_HUMIDITY,
|
CONF_HUMIDITY,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
ICON_WATER,
|
DEVICE_CLASS_ABSOLUTE_HUMIDITY,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_GRAMS_PER_CUBIC_METER,
|
UNIT_GRAMS_PER_CUBIC_METER,
|
||||||
)
|
)
|
||||||
@@ -27,8 +27,8 @@ EQUATION = {
|
|||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
sensor.sensor_schema(
|
sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
|
unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
|
||||||
icon=ICON_WATER,
|
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_ABSOLUTE_HUMIDITY,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
)
|
)
|
||||||
.extend(
|
.extend(
|
||||||
|
|||||||
@@ -11,15 +11,8 @@ from esphome.components.esp32.const import (
|
|||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
from esphome.config_helpers import filter_source_files_from_platform
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
|
||||||
CONF_ANALOG,
|
|
||||||
CONF_INPUT,
|
|
||||||
CONF_NUMBER,
|
|
||||||
PLATFORM_ESP8266,
|
|
||||||
PlatformFramework,
|
|
||||||
)
|
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
@@ -273,21 +266,3 @@ def validate_adc_pin(value):
|
|||||||
)(value)
|
)(value)
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
|
||||||
{
|
|
||||||
"adc_sensor_esp32.cpp": {
|
|
||||||
PlatformFramework.ESP32_ARDUINO,
|
|
||||||
PlatformFramework.ESP32_IDF,
|
|
||||||
},
|
|
||||||
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
|
||||||
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
|
||||||
"adc_sensor_libretiny.cpp": {
|
|
||||||
PlatformFramework.BK72XX_ARDUINO,
|
|
||||||
PlatformFramework.RTL87XX_ARDUINO,
|
|
||||||
PlatformFramework.LN882X_ARDUINO,
|
|
||||||
},
|
|
||||||
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -241,6 +241,8 @@ float ADCSensor::sample_autorange_() {
|
|||||||
cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
|
cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
|
||||||
|
|
||||||
err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
|
||||||
|
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
|
||||||
#else
|
#else
|
||||||
adc_cali_line_fitting_config_t cali_config = {
|
adc_cali_line_fitting_config_t cali_config = {
|
||||||
.unit_id = this->adc_unit_,
|
.unit_id = this->adc_unit_,
|
||||||
@@ -251,10 +253,14 @@ float ADCSensor::sample_autorange_() {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
|
||||||
|
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int raw;
|
int raw;
|
||||||
err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
|
err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: Raw ADC read %s, value=%d (err=%d)", atten,
|
||||||
|
(err == ESP_OK) ? "SUCCESS" : "FAILED", raw, err);
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
|
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
|
||||||
@@ -275,8 +281,10 @@ float ADCSensor::sample_autorange_() {
|
|||||||
err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
|
err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
|
||||||
if (err == ESP_OK) {
|
if (err == ESP_OK) {
|
||||||
voltage = voltage_mv / 1000.0f;
|
voltage = voltage_mv / 1000.0f;
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: CALIBRATED - raw=%d -> %dmV -> %.6fV", atten, raw, voltage_mv, voltage);
|
||||||
} else {
|
} else {
|
||||||
voltage = raw * 3.3f / 4095.0f;
|
voltage = raw * 3.3f / 4095.0f;
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: UNCALIBRATED FALLBACK - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
|
||||||
}
|
}
|
||||||
// Clean up calibration handle
|
// Clean up calibration handle
|
||||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||||
@@ -287,6 +295,7 @@ float ADCSensor::sample_autorange_() {
|
|||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
voltage = raw * 3.3f / 4095.0f;
|
voltage = raw * 3.3f / 4095.0f;
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: NO CALIBRATION - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {raw, voltage};
|
return {raw, voltage};
|
||||||
@@ -324,18 +333,32 @@ float ADCSensor::sample_autorange_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int adc_half = 2048;
|
const int adc_half = 2048;
|
||||||
uint32_t c12 = std::min(raw12, adc_half);
|
const uint32_t c12 = std::min(raw12, adc_half);
|
||||||
uint32_t c6 = adc_half - std::abs(raw6 - adc_half);
|
|
||||||
uint32_t c2 = adc_half - std::abs(raw2 - adc_half);
|
const int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
|
||||||
uint32_t c0 = std::min(4095 - raw0, adc_half);
|
const uint32_t c6 = (c6_signed > 0) ? c6_signed : 0; // Clamp to prevent underflow
|
||||||
uint32_t csum = c12 + c6 + c2 + c0;
|
|
||||||
|
const int32_t c2_signed = adc_half - std::abs(raw2 - adc_half);
|
||||||
|
const uint32_t c2 = (c2_signed > 0) ? c2_signed : 0; // Clamp to prevent underflow
|
||||||
|
|
||||||
|
const uint32_t c0 = std::min(4095 - raw0, adc_half);
|
||||||
|
const uint32_t csum = c12 + c6 + c2 + c0;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Autorange summary:");
|
||||||
|
ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
|
||||||
|
ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
|
||||||
|
ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
|
||||||
|
|
||||||
if (csum == 0) {
|
if (csum == 0) {
|
||||||
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
|
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
|
||||||
return NAN;
|
return NAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
|
const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
|
||||||
|
ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
|
||||||
|
c0, csum, final_result);
|
||||||
|
|
||||||
|
return final_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace adc
|
} // namespace adc
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from esphome.components.zephyr import (
|
|||||||
zephyr_add_prj_conf,
|
zephyr_add_prj_conf,
|
||||||
zephyr_add_user,
|
zephyr_add_user,
|
||||||
)
|
)
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ATTENUATION,
|
CONF_ATTENUATION,
|
||||||
@@ -20,6 +21,7 @@ from esphome.const import (
|
|||||||
PLATFORM_NRF52,
|
PLATFORM_NRF52,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
@@ -174,3 +176,21 @@ async def to_code(config):
|
|||||||
}};
|
}};
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"adc_sensor_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||||
|
"adc_sensor_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ void ADE7880::update() {
|
|||||||
if (this->channel_a_ != nullptr) {
|
if (this->channel_a_ != nullptr) {
|
||||||
auto *chan = this->channel_a_;
|
auto *chan = this->channel_a_;
|
||||||
this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; });
|
this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; });
|
||||||
this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; });
|
this->update_sensor_from_s24zp_register16_(chan->voltage, AVRMS, [](float val) { return val / 10000.0f; });
|
||||||
this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; });
|
this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; });
|
||||||
this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; });
|
this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; });
|
||||||
this->update_sensor_from_s16_register16_(chan->power_factor, APF,
|
this->update_sensor_from_s16_register16_(chan->power_factor, APF,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from esphome.const import (
|
|||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
UNIT_WATT_HOURS,
|
UNIT_WATT_HOURS,
|
||||||
)
|
)
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
@@ -51,6 +52,20 @@ CONF_POWER_GAIN = "power_gain"
|
|||||||
|
|
||||||
CONF_NEUTRAL = "neutral"
|
CONF_NEUTRAL = "neutral"
|
||||||
|
|
||||||
|
# Tuple of power channel phases
|
||||||
|
POWER_PHASES = (CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C)
|
||||||
|
|
||||||
|
# Tuple of sensor types that can be configured for power channels
|
||||||
|
POWER_SENSOR_TYPES = (
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
CONF_ACTIVE_POWER,
|
||||||
|
CONF_APPARENT_POWER,
|
||||||
|
CONF_POWER_FACTOR,
|
||||||
|
CONF_FORWARD_ACTIVE_ENERGY,
|
||||||
|
CONF_REVERSE_ACTIVE_ENERGY,
|
||||||
|
)
|
||||||
|
|
||||||
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
|
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(NeutralChannel),
|
cv.GenerateID(): cv.declare_id(NeutralChannel),
|
||||||
@@ -150,7 +165,64 @@ POWER_CHANNEL_SCHEMA = cv.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
|
||||||
|
def prefix_sensor_name(
|
||||||
|
sensor_conf: ConfigType,
|
||||||
|
channel_name: str,
|
||||||
|
channel_config: ConfigType,
|
||||||
|
sensor_type: str,
|
||||||
|
) -> None:
|
||||||
|
"""Helper to prefix sensor name with channel name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sensor_conf: The sensor configuration (dict or string)
|
||||||
|
channel_name: The channel name to prefix with
|
||||||
|
channel_config: The channel configuration to update
|
||||||
|
sensor_type: The sensor type key in the channel config
|
||||||
|
"""
|
||||||
|
if isinstance(sensor_conf, dict) and CONF_NAME in sensor_conf:
|
||||||
|
sensor_name = sensor_conf[CONF_NAME]
|
||||||
|
if sensor_name and not sensor_name.startswith(channel_name):
|
||||||
|
sensor_conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||||
|
elif isinstance(sensor_conf, str):
|
||||||
|
# Simple value case - convert to dict with prefixed name
|
||||||
|
channel_config[sensor_type] = {CONF_NAME: f"{channel_name} {sensor_conf}"}
|
||||||
|
|
||||||
|
|
||||||
|
def process_channel_sensors(
|
||||||
|
config: ConfigType, channel_key: str, sensor_types: tuple
|
||||||
|
) -> None:
|
||||||
|
"""Process sensors for a channel and prefix their names.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: The main configuration
|
||||||
|
channel_key: The channel key (e.g., CONF_PHASE_A, CONF_NEUTRAL)
|
||||||
|
sensor_types: Tuple of sensor types to process for this channel
|
||||||
|
"""
|
||||||
|
if not (channel_config := config.get(channel_key)) or not (
|
||||||
|
channel_name := channel_config.get(CONF_NAME)
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
for sensor_type in sensor_types:
|
||||||
|
if sensor_conf := channel_config.get(sensor_type):
|
||||||
|
prefix_sensor_name(sensor_conf, channel_name, channel_config, sensor_type)
|
||||||
|
|
||||||
|
|
||||||
|
def preprocess_channels(config: ConfigType) -> ConfigType:
|
||||||
|
"""Preprocess channel configurations to add channel name prefix to sensor names."""
|
||||||
|
# Process power channels
|
||||||
|
for channel in POWER_PHASES:
|
||||||
|
process_channel_sensors(config, channel, POWER_SENSOR_TYPES)
|
||||||
|
|
||||||
|
# Process neutral channel
|
||||||
|
process_channel_sensors(config, CONF_NEUTRAL, (CONF_CURRENT,))
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
preprocess_channels,
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ADE7880),
|
cv.GenerateID(): cv.declare_id(ADE7880),
|
||||||
@@ -167,7 +239,7 @@ CONFIG_SCHEMA = (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.polling_component_schema("60s"))
|
.extend(cv.polling_component_schema("60s"))
|
||||||
.extend(i2c.i2c_device_schema(0x38))
|
.extend(i2c.i2c_device_schema(0x38)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -188,15 +260,7 @@ async def neutral_channel(config):
|
|||||||
async def power_channel(config):
|
async def power_channel(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
|
||||||
for sensor_type in [
|
for sensor_type in POWER_SENSOR_TYPES:
|
||||||
CONF_CURRENT,
|
|
||||||
CONF_VOLTAGE,
|
|
||||||
CONF_ACTIVE_POWER,
|
|
||||||
CONF_APPARENT_POWER,
|
|
||||||
CONF_POWER_FACTOR,
|
|
||||||
CONF_FORWARD_ACTIVE_ENERGY,
|
|
||||||
CONF_REVERSE_ACTIVE_ENERGY,
|
|
||||||
]:
|
|
||||||
if conf := config.get(sensor_type):
|
if conf := config.get(sensor_type):
|
||||||
sens = await sensor.new_sensor(conf)
|
sens = await sensor.new_sensor(conf)
|
||||||
cg.add(getattr(var, f"set_{sensor_type}")(sens))
|
cg.add(getattr(var, f"set_{sensor_type}")(sens))
|
||||||
@@ -216,44 +280,6 @@ async def power_channel(config):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
def final_validate(config):
|
|
||||||
for channel in [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]:
|
|
||||||
if channel := config.get(channel):
|
|
||||||
channel_name = channel.get(CONF_NAME)
|
|
||||||
|
|
||||||
for sensor_type in [
|
|
||||||
CONF_CURRENT,
|
|
||||||
CONF_VOLTAGE,
|
|
||||||
CONF_ACTIVE_POWER,
|
|
||||||
CONF_APPARENT_POWER,
|
|
||||||
CONF_POWER_FACTOR,
|
|
||||||
CONF_FORWARD_ACTIVE_ENERGY,
|
|
||||||
CONF_REVERSE_ACTIVE_ENERGY,
|
|
||||||
]:
|
|
||||||
if conf := channel.get(sensor_type):
|
|
||||||
sensor_name = conf.get(CONF_NAME)
|
|
||||||
if (
|
|
||||||
sensor_name
|
|
||||||
and channel_name
|
|
||||||
and not sensor_name.startswith(channel_name)
|
|
||||||
):
|
|
||||||
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
|
||||||
|
|
||||||
if channel := config.get(CONF_NEUTRAL):
|
|
||||||
channel_name = channel.get(CONF_NAME)
|
|
||||||
if conf := channel.get(CONF_CURRENT):
|
|
||||||
sensor_name = conf.get(CONF_NAME)
|
|
||||||
if (
|
|
||||||
sensor_name
|
|
||||||
and channel_name
|
|
||||||
and not sensor_name.startswith(channel_name)
|
|
||||||
):
|
|
||||||
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
|
||||||
|
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = final_validate
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
|
|||||||
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
||||||
uint8_t rev_newaddress = ~newaddress;
|
uint8_t rev_newaddress = ~newaddress;
|
||||||
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
|
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
|
||||||
data[4] = calc_crc8_(data, 4);
|
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
||||||
if (!this->write_bytes(REG_ADDRESS, data)) {
|
if (!this->write_bytes(REG_ADDRESS, data)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
@@ -109,7 +109,7 @@ bool AGS10Component::set_zero_point_with_current_resistance() { return this->set
|
|||||||
|
|
||||||
bool AGS10Component::set_zero_point_with(uint16_t value) {
|
bool AGS10Component::set_zero_point_with(uint16_t value) {
|
||||||
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
|
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
|
||||||
data[4] = calc_crc8_(data, 4);
|
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
||||||
if (!this->write_bytes(REG_CALIBRATION, data)) {
|
if (!this->write_bytes(REG_CALIBRATION, data)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
@@ -184,7 +184,7 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
|||||||
auto res = *data;
|
auto res = *data;
|
||||||
auto crc_byte = res[len];
|
auto crc_byte = res[len];
|
||||||
|
|
||||||
if (crc_byte != calc_crc8_(res, len)) {
|
if (crc_byte != crc8(res.data(), len, 0xFF, 0x31, true)) {
|
||||||
this->error_code_ = CRC_CHECK_FAILED;
|
this->error_code_ = CRC_CHECK_FAILED;
|
||||||
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
||||||
return optional<std::array<uint8_t, N>>();
|
return optional<std::array<uint8_t, N>>();
|
||||||
@@ -192,20 +192,5 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> dat, uint8_t num) {
|
|
||||||
uint8_t i, byte1, crc = 0xFF;
|
|
||||||
for (byte1 = 0; byte1 < num; byte1++) {
|
|
||||||
crc ^= (dat[byte1]);
|
|
||||||
for (i = 0; i < 8; i++) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x31;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
} // namespace ags10
|
} // namespace ags10
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ags10 {
|
namespace ags10 {
|
||||||
@@ -99,16 +99,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
|||||||
* Read, checks and returns data from the sensor.
|
* Read, checks and returns data from the sensor.
|
||||||
*/
|
*/
|
||||||
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates CRC8 value.
|
|
||||||
*
|
|
||||||
* CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1)
|
|
||||||
*
|
|
||||||
* @param[in] dat the data buffer
|
|
||||||
* @param num number of bytes in the buffer
|
|
||||||
*/
|
|
||||||
template<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ void AHT10Component::read_data_() {
|
|||||||
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
|
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
|
||||||
}
|
}
|
||||||
if (this->read(data, 6) != i2c::ERROR_OK) {
|
if (this->read(data, 6) != i2c::ERROR_OK) {
|
||||||
this->status_set_warning("Read failed, will retry");
|
this->status_set_warning(LOG_STR("Read failed, will retry"));
|
||||||
this->restart_read_();
|
this->restart_read_();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ void AHT10Component::read_data_() {
|
|||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "Invalid humidity, retrying");
|
ESP_LOGD(TAG, "Invalid humidity, retrying");
|
||||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||||
}
|
}
|
||||||
this->restart_read_();
|
this->restart_read_();
|
||||||
return;
|
return;
|
||||||
@@ -144,7 +144,7 @@ void AHT10Component::update() {
|
|||||||
return;
|
return;
|
||||||
this->start_time_ = millis();
|
this->start_time_ = millis();
|
||||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->restart_read_();
|
this->restart_read_();
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
await esp32_ble_tracker.register_ble_device(var, config)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from esphome.const import (
|
|||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
@@ -345,6 +345,6 @@ async def alarm_control_panel_is_armed_to_code(
|
|||||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(alarm_control_panel_ns.using)
|
cg.add_global(alarm_control_panel_ns.using)
|
||||||
|
|||||||
@@ -29,22 +29,6 @@ namespace am2315c {
|
|||||||
|
|
||||||
static const char *const TAG = "am2315c";
|
static const char *const TAG = "am2315c";
|
||||||
|
|
||||||
uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) {
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
while (len--) {
|
|
||||||
crc ^= *data++;
|
|
||||||
for (uint8_t i = 0; i < 8; i++) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc <<= 1;
|
|
||||||
crc ^= 0x31;
|
|
||||||
} else {
|
|
||||||
crc <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AM2315C::reset_register_(uint8_t reg) {
|
bool AM2315C::reset_register_(uint8_t reg) {
|
||||||
// code based on demo code sent by www.aosong.com
|
// code based on demo code sent by www.aosong.com
|
||||||
// no further documentation.
|
// no further documentation.
|
||||||
@@ -86,7 +70,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
|
|||||||
humidity = raw * 9.5367431640625e-5;
|
humidity = raw * 9.5367431640625e-5;
|
||||||
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||||
temperature = raw * 1.9073486328125e-4 - 50;
|
temperature = raw * 1.9073486328125e-4 - 50;
|
||||||
return this->crc8_(data, 6) == data[6];
|
return crc8(data, 6, 0xFF, 0x31, true) == data[6];
|
||||||
}
|
}
|
||||||
|
|
||||||
void AM2315C::setup() {
|
void AM2315C::setup() {
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace am2315c {
|
namespace am2315c {
|
||||||
@@ -39,7 +39,6 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
|
|||||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint8_t crc8_(uint8_t *data, uint8_t len);
|
|
||||||
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
||||||
bool reset_register_(uint8_t reg);
|
bool reset_register_(uint8_t reg);
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ uint32_t Animation::get_animation_frame_count() const { return this->animation_f
|
|||||||
int Animation::get_current_frame() const { return this->current_frame_; }
|
int Animation::get_current_frame() const { return this->current_frame_; }
|
||||||
void Animation::next_frame() {
|
void Animation::next_frame() {
|
||||||
this->current_frame_++;
|
this->current_frame_++;
|
||||||
if (loop_count_ && this->current_frame_ == loop_end_frame_ &&
|
if (loop_count_ && static_cast<uint32_t>(this->current_frame_) == loop_end_frame_ &&
|
||||||
(this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
|
(this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
|
||||||
this->current_frame_ = loop_start_frame_;
|
this->current_frame_ = loop_start_frame_;
|
||||||
this->loop_current_iteration_++;
|
this->loop_current_iteration_++;
|
||||||
}
|
}
|
||||||
if (this->current_frame_ >= animation_frame_count_) {
|
if (static_cast<uint32_t>(this->current_frame_) >= animation_frame_count_) {
|
||||||
this->loop_current_iteration_ = 1;
|
this->loop_current_iteration_ = 1;
|
||||||
this->current_frame_ = 0;
|
this->current_frame_ = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import base64
|
import base64
|
||||||
|
import logging
|
||||||
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import Condition
|
from esphome.automation import Condition
|
||||||
@@ -8,34 +9,59 @@ import esphome.config_validation as cv
|
|||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ACTION,
|
CONF_ACTION,
|
||||||
CONF_ACTIONS,
|
CONF_ACTIONS,
|
||||||
|
CONF_CAPTURE_RESPONSE,
|
||||||
CONF_DATA,
|
CONF_DATA,
|
||||||
CONF_DATA_TEMPLATE,
|
CONF_DATA_TEMPLATE,
|
||||||
CONF_EVENT,
|
CONF_EVENT,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_KEY,
|
CONF_KEY,
|
||||||
|
CONF_MAX_CONNECTIONS,
|
||||||
CONF_ON_CLIENT_CONNECTED,
|
CONF_ON_CLIENT_CONNECTED,
|
||||||
CONF_ON_CLIENT_DISCONNECTED,
|
CONF_ON_CLIENT_DISCONNECTED,
|
||||||
|
CONF_ON_ERROR,
|
||||||
|
CONF_ON_SUCCESS,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_REBOOT_TIMEOUT,
|
CONF_REBOOT_TIMEOUT,
|
||||||
|
CONF_RESPONSE_TEMPLATE,
|
||||||
CONF_SERVICE,
|
CONF_SERVICE,
|
||||||
CONF_SERVICES,
|
CONF_SERVICES,
|
||||||
CONF_TAG,
|
CONF_TAG,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VARIABLES,
|
CONF_VARIABLES,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||||
|
from esphome.cpp_generator import TemplateArgsType
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "api"
|
DOMAIN = "api"
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
AUTO_LOAD = ["socket"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
CODEOWNERS = ["@OttoWinter"]
|
|
||||||
|
|
||||||
|
def AUTO_LOAD(config: ConfigType) -> list[str]:
|
||||||
|
"""Conditionally auto-load json only when capture_response is used."""
|
||||||
|
base = ["socket"]
|
||||||
|
|
||||||
|
# Check if any homeassistant.action/homeassistant.service has capture_response: true
|
||||||
|
# This flag is set during config validation in _validate_response_config
|
||||||
|
if not config or CORE.data.get(DOMAIN, {}).get(CONF_CAPTURE_RESPONSE, False):
|
||||||
|
return base + ["json"]
|
||||||
|
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
api_ns = cg.esphome_ns.namespace("api")
|
api_ns = cg.esphome_ns.namespace("api")
|
||||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||||
HomeAssistantServiceCallAction = api_ns.class_(
|
HomeAssistantServiceCallAction = api_ns.class_(
|
||||||
"HomeAssistantServiceCallAction", automation.Action
|
"HomeAssistantServiceCallAction", automation.Action
|
||||||
)
|
)
|
||||||
|
ActionResponse = api_ns.class_("ActionResponse")
|
||||||
|
HomeAssistantActionResponseTrigger = api_ns.class_(
|
||||||
|
"HomeAssistantActionResponseTrigger", automation.Trigger
|
||||||
|
)
|
||||||
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
||||||
|
|
||||||
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
||||||
@@ -55,6 +81,8 @@ CONF_BATCH_DELAY = "batch_delay"
|
|||||||
CONF_CUSTOM_SERVICES = "custom_services"
|
CONF_CUSTOM_SERVICES = "custom_services"
|
||||||
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
|
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
|
||||||
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
|
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
|
||||||
|
CONF_LISTEN_BACKLOG = "listen_backlog"
|
||||||
|
CONF_MAX_SEND_QUEUE = "max_send_queue"
|
||||||
|
|
||||||
|
|
||||||
def validate_encryption_key(value):
|
def validate_encryption_key(value):
|
||||||
@@ -101,6 +129,32 @@ def _encryption_schema(config):
|
|||||||
return ENCRYPTION_SCHEMA(config)
|
return ENCRYPTION_SCHEMA(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_api_config(config: ConfigType) -> ConfigType:
|
||||||
|
"""Validate API configuration with mutual exclusivity check and deprecation warning."""
|
||||||
|
# Check if both password and encryption are configured
|
||||||
|
has_password = CONF_PASSWORD in config and config[CONF_PASSWORD]
|
||||||
|
has_encryption = CONF_ENCRYPTION in config
|
||||||
|
|
||||||
|
if has_password and has_encryption:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"The 'password' and 'encryption' options are mutually exclusive. "
|
||||||
|
"The API client only supports one authentication method at a time. "
|
||||||
|
"Please remove one of them. "
|
||||||
|
"Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. "
|
||||||
|
"We strongly recommend using 'encryption' instead for better security."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Warn about password deprecation
|
||||||
|
if has_password:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. "
|
||||||
|
"Please migrate to the 'encryption' configuration. "
|
||||||
|
"See https://esphome.io/components/api.html#configuration-variables"
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -128,13 +182,50 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
|
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
|
# Connection limits to prevent memory exhaustion on resource-constrained devices
|
||||||
|
# Each connection uses ~500-1000 bytes of RAM plus system resources
|
||||||
|
# Platform defaults based on available RAM and network stack implementation:
|
||||||
|
cv.SplitDefault(
|
||||||
|
CONF_LISTEN_BACKLOG,
|
||||||
|
esp8266=1, # Limited RAM (~40KB free), LWIP raw sockets
|
||||||
|
esp32=4, # More RAM (520KB), BSD sockets
|
||||||
|
rp2040=1, # Limited RAM (264KB), LWIP raw sockets like ESP8266
|
||||||
|
bk72xx=4, # Moderate RAM, BSD-style sockets
|
||||||
|
rtl87xx=4, # Moderate RAM, BSD-style sockets
|
||||||
|
host=4, # Abundant resources
|
||||||
|
ln882x=4, # Moderate RAM
|
||||||
|
): cv.int_range(min=1, max=10),
|
||||||
|
cv.SplitDefault(
|
||||||
|
CONF_MAX_CONNECTIONS,
|
||||||
|
esp8266=4, # ~40KB free RAM, each connection uses ~500-1000 bytes
|
||||||
|
esp32=8, # 520KB RAM available
|
||||||
|
rp2040=4, # 264KB RAM but LWIP constraints
|
||||||
|
bk72xx=8, # Moderate RAM
|
||||||
|
rtl87xx=8, # Moderate RAM
|
||||||
|
host=8, # Abundant resources
|
||||||
|
ln882x=8, # Moderate RAM
|
||||||
|
): cv.int_range(min=1, max=20),
|
||||||
|
# Maximum queued send buffers per connection before dropping connection
|
||||||
|
# Each buffer uses ~8-12 bytes overhead plus actual message size
|
||||||
|
# Platform defaults based on available RAM and typical message rates:
|
||||||
|
cv.SplitDefault(
|
||||||
|
CONF_MAX_SEND_QUEUE,
|
||||||
|
esp8266=5, # Limited RAM, need to fail fast
|
||||||
|
esp32=8, # More RAM, can buffer more
|
||||||
|
rp2040=5, # Limited RAM
|
||||||
|
bk72xx=8, # Moderate RAM
|
||||||
|
rtl87xx=8, # Moderate RAM
|
||||||
|
host=16, # Abundant resources
|
||||||
|
ln882x=8, # Moderate RAM
|
||||||
|
): cv.int_range(min=1, max=64),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
|
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
|
||||||
|
_validate_api_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(40.0)
|
@coroutine_with_priority(CoroPriority.WEB)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
@@ -145,6 +236,11 @@ async def to_code(config):
|
|||||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||||
|
if CONF_LISTEN_BACKLOG in config:
|
||||||
|
cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG]))
|
||||||
|
if CONF_MAX_CONNECTIONS in config:
|
||||||
|
cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS]))
|
||||||
|
cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
|
||||||
|
|
||||||
# Set USE_API_SERVICES if any services are enabled
|
# Set USE_API_SERVICES if any services are enabled
|
||||||
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
||||||
@@ -193,6 +289,7 @@ async def to_code(config):
|
|||||||
if key := encryption_config.get(CONF_KEY):
|
if key := encryption_config.get(CONF_KEY):
|
||||||
decoded = base64.b64decode(key)
|
decoded = base64.b64decode(key)
|
||||||
cg.add(var.set_noise_psk(list(decoded)))
|
cg.add(var.set_noise_psk(list(decoded)))
|
||||||
|
cg.add_define("USE_API_NOISE_PSK_FROM_YAML")
|
||||||
else:
|
else:
|
||||||
# No key provided, but encryption desired
|
# No key provided, but encryption desired
|
||||||
# This will allow a plaintext client to provide a noise key,
|
# This will allow a plaintext client to provide a noise key,
|
||||||
@@ -212,6 +309,29 @@ async def to_code(config):
|
|||||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_response_config(config: ConfigType) -> ConfigType:
|
||||||
|
# Validate dependencies:
|
||||||
|
# - response_template requires capture_response: true
|
||||||
|
# - capture_response: true requires on_success
|
||||||
|
if CONF_RESPONSE_TEMPLATE in config and not config[CONF_CAPTURE_RESPONSE]:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"`{CONF_RESPONSE_TEMPLATE}` requires `{CONF_CAPTURE_RESPONSE}: true` to be set.",
|
||||||
|
path=[CONF_RESPONSE_TEMPLATE],
|
||||||
|
)
|
||||||
|
|
||||||
|
if config[CONF_CAPTURE_RESPONSE] and CONF_ON_SUCCESS not in config:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"`{CONF_CAPTURE_RESPONSE}: true` requires `{CONF_ON_SUCCESS}` to be set.",
|
||||||
|
path=[CONF_CAPTURE_RESPONSE],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Track if any action uses capture_response for AUTO_LOAD
|
||||||
|
if config[CONF_CAPTURE_RESPONSE]:
|
||||||
|
CORE.data.setdefault(DOMAIN, {})[CONF_CAPTURE_RESPONSE] = True
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -227,10 +347,15 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||||
{cv.string: cv.returning_lambda}
|
{cv.string: cv.returning_lambda}
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_RESPONSE_TEMPLATE): cv.templatable(cv.string),
|
||||||
|
cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_ON_SUCCESS): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
|
||||||
cv.rename_key(CONF_SERVICE, CONF_ACTION),
|
cv.rename_key(CONF_SERVICE, CONF_ACTION),
|
||||||
|
_validate_response_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -244,7 +369,12 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
|||||||
HomeAssistantServiceCallAction,
|
HomeAssistantServiceCallAction,
|
||||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
async def homeassistant_service_to_code(
|
||||||
|
config: ConfigType,
|
||||||
|
action_id: ID,
|
||||||
|
template_arg: cg.TemplateArguments,
|
||||||
|
args: TemplateArgsType,
|
||||||
|
):
|
||||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||||
serv = await cg.get_variable(config[CONF_ID])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||||
@@ -259,6 +389,40 @@ async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
|||||||
for key, value in config[CONF_VARIABLES].items():
|
for key, value in config[CONF_VARIABLES].items():
|
||||||
templ = await cg.templatable(value, args, None)
|
templ = await cg.templatable(value, args, None)
|
||||||
cg.add(var.add_variable(key, templ))
|
cg.add(var.add_variable(key, templ))
|
||||||
|
|
||||||
|
if on_error := config.get(CONF_ON_ERROR):
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS")
|
||||||
|
cg.add(var.set_wants_status())
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_error_trigger(),
|
||||||
|
[(cg.std_string, "error"), *args],
|
||||||
|
on_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
if on_success := config.get(CONF_ON_SUCCESS):
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
||||||
|
cg.add(var.set_wants_status())
|
||||||
|
if config[CONF_CAPTURE_RESPONSE]:
|
||||||
|
cg.add(var.set_wants_response())
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON")
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_success_trigger_with_response(),
|
||||||
|
[(cg.JsonObjectConst, "response"), *args],
|
||||||
|
on_success,
|
||||||
|
)
|
||||||
|
|
||||||
|
if response_template := config.get(CONF_RESPONSE_TEMPLATE):
|
||||||
|
templ = await cg.templatable(response_template, args, cg.std_string)
|
||||||
|
cg.add(var.set_response_template(templ))
|
||||||
|
|
||||||
|
else:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_success_trigger(),
|
||||||
|
args,
|
||||||
|
on_success,
|
||||||
|
)
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@@ -321,6 +485,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
|||||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||||
serv = await cg.get_variable(config[CONF_ID])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||||
cg.add(var.set_service("esphome.tag_scanned"))
|
cg.add(var.set_service("esphome.tag_scanned"))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ service APIConnection {
|
|||||||
option (needs_setup_connection) = false;
|
option (needs_setup_connection) = false;
|
||||||
option (needs_authentication) = false;
|
option (needs_authentication) = false;
|
||||||
}
|
}
|
||||||
rpc connect (ConnectRequest) returns (ConnectResponse) {
|
rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) {
|
||||||
option (needs_setup_connection) = false;
|
option (needs_setup_connection) = false;
|
||||||
option (needs_authentication) = false;
|
option (needs_authentication) = false;
|
||||||
}
|
}
|
||||||
@@ -27,9 +27,6 @@ service APIConnection {
|
|||||||
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
||||||
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
||||||
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
||||||
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
|
|
||||||
option (needs_authentication) = false;
|
|
||||||
}
|
|
||||||
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
||||||
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
|
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
|
||||||
|
|
||||||
@@ -69,6 +66,9 @@ service APIConnection {
|
|||||||
rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
|
rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
|
||||||
|
|
||||||
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
|
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
|
||||||
|
|
||||||
|
rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {}
|
||||||
|
rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ message HelloRequest {
|
|||||||
// For example "Home Assistant"
|
// For example "Home Assistant"
|
||||||
// Not strictly necessary to send but nice for debugging
|
// Not strictly necessary to send but nice for debugging
|
||||||
// purposes.
|
// purposes.
|
||||||
string client_info = 1;
|
string client_info = 1 [(pointer_to_buffer) = true];
|
||||||
uint32 api_version_major = 2;
|
uint32 api_version_major = 2;
|
||||||
uint32 api_version_minor = 3;
|
uint32 api_version_minor = 3;
|
||||||
}
|
}
|
||||||
@@ -132,21 +132,23 @@ message HelloResponse {
|
|||||||
|
|
||||||
// Message sent at the beginning of each connection to authenticate the client
|
// Message sent at the beginning of each connection to authenticate the client
|
||||||
// Can only be sent by the client and only at the beginning of the connection
|
// Can only be sent by the client and only at the beginning of the connection
|
||||||
message ConnectRequest {
|
message AuthenticationRequest {
|
||||||
option (id) = 3;
|
option (id) = 3;
|
||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (ifdef) = "USE_API_PASSWORD";
|
||||||
|
|
||||||
// The password to log in with
|
// The password to log in with
|
||||||
string password = 1;
|
string password = 1 [(pointer_to_buffer) = true];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||||
// Can only be sent by the server and only at the beginning of the connection
|
// Can only be sent by the server and only at the beginning of the connection
|
||||||
message ConnectResponse {
|
message AuthenticationResponse {
|
||||||
option (id) = 4;
|
option (id) = 4;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (ifdef) = "USE_API_PASSWORD";
|
||||||
|
|
||||||
bool invalid_password = 1;
|
bool invalid_password = 1;
|
||||||
}
|
}
|
||||||
@@ -255,6 +257,10 @@ message DeviceInfoResponse {
|
|||||||
|
|
||||||
// Top-level area info to phase out suggested_area
|
// Top-level area info to phase out suggested_area
|
||||||
AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
|
AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
|
||||||
|
|
||||||
|
// Indicates if Z-Wave proxy support is available and features supported
|
||||||
|
uint32 zwave_proxy_feature_flags = 23 [(field_ifdef) = "USE_ZWAVE_PROXY"];
|
||||||
|
uint32 zwave_home_id = 24 [(field_ifdef) = "USE_ZWAVE_PROXY"];
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListEntitiesRequest {
|
message ListEntitiesRequest {
|
||||||
@@ -763,7 +769,7 @@ message HomeassistantServiceMap {
|
|||||||
string value = 2 [(no_zero_copy) = true];
|
string value = 2 [(no_zero_copy) = true];
|
||||||
}
|
}
|
||||||
|
|
||||||
message HomeassistantServiceResponse {
|
message HomeassistantActionRequest {
|
||||||
option (id) = 35;
|
option (id) = 35;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
@@ -774,6 +780,22 @@ message HomeassistantServiceResponse {
|
|||||||
repeated HomeassistantServiceMap data_template = 3;
|
repeated HomeassistantServiceMap data_template = 3;
|
||||||
repeated HomeassistantServiceMap variables = 4;
|
repeated HomeassistantServiceMap variables = 4;
|
||||||
bool is_event = 5;
|
bool is_event = 5;
|
||||||
|
uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"];
|
||||||
|
bool wants_response = 7 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||||
|
string response_template = 8 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message sent by Home Assistant to ESPHome with service call response data
|
||||||
|
message HomeassistantActionResponse {
|
||||||
|
option (id) = 130;
|
||||||
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (no_delay) = true;
|
||||||
|
option (ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES";
|
||||||
|
|
||||||
|
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"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||||
@@ -809,15 +831,16 @@ message HomeAssistantStateResponse {
|
|||||||
// ==================== IMPORT TIME ====================
|
// ==================== IMPORT TIME ====================
|
||||||
message GetTimeRequest {
|
message GetTimeRequest {
|
||||||
option (id) = 36;
|
option (id) = 36;
|
||||||
option (source) = SOURCE_BOTH;
|
option (source) = SOURCE_SERVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetTimeResponse {
|
message GetTimeResponse {
|
||||||
option (id) = 37;
|
option (id) = 37;
|
||||||
option (source) = SOURCE_BOTH;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 epoch_seconds = 1;
|
fixed32 epoch_seconds = 1;
|
||||||
|
string timezone = 2 [(pointer_to_buffer) = true];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== USER-DEFINES SERVICES ====================
|
// ==================== USER-DEFINES SERVICES ====================
|
||||||
@@ -1438,11 +1461,11 @@ message BluetoothLERawAdvertisementsResponse {
|
|||||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
|
||||||
repeated BluetoothLERawAdvertisement advertisements = 1;
|
repeated BluetoothLERawAdvertisement advertisements = 1 [(fixed_array_with_length_define) = "BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE"];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BluetoothDeviceRequestType {
|
enum BluetoothDeviceRequestType {
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
|
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0 [deprecated = true]; // V1 removed, use V3 variants
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
|
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
|
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
||||||
@@ -1458,7 +1481,7 @@ message BluetoothDeviceRequest {
|
|||||||
|
|
||||||
uint64 address = 1;
|
uint64 address = 1;
|
||||||
BluetoothDeviceRequestType request_type = 2;
|
BluetoothDeviceRequestType request_type = 2;
|
||||||
bool has_address_type = 3;
|
bool has_address_type = 3; // Deprecated, should be removed in 2027.8 - https://github.com/esphome/esphome/pull/10318
|
||||||
uint32 address_type = 4;
|
uint32 address_type = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1564,7 +1587,7 @@ message BluetoothGATTWriteRequest {
|
|||||||
uint32 handle = 2;
|
uint32 handle = 2;
|
||||||
bool response = 3;
|
bool response = 3;
|
||||||
|
|
||||||
bytes data = 4;
|
bytes data = 4 [(pointer_to_buffer) = true];
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothGATTReadDescriptorRequest {
|
message BluetoothGATTReadDescriptorRequest {
|
||||||
@@ -1584,7 +1607,7 @@ message BluetoothGATTWriteDescriptorRequest {
|
|||||||
uint64 address = 1;
|
uint64 address = 1;
|
||||||
uint32 handle = 2;
|
uint32 handle = 2;
|
||||||
|
|
||||||
bytes data = 3;
|
bytes data = 3 [(pointer_to_buffer) = true];
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothGATTNotifyRequest {
|
message BluetoothGATTNotifyRequest {
|
||||||
@@ -1712,6 +1735,7 @@ message BluetoothScannerStateResponse {
|
|||||||
|
|
||||||
BluetoothScannerState state = 1;
|
BluetoothScannerState state = 1;
|
||||||
BluetoothScannerMode mode = 2;
|
BluetoothScannerMode mode = 2;
|
||||||
|
BluetoothScannerMode configured_mode = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothScannerSetModeRequest {
|
message BluetoothScannerSetModeRequest {
|
||||||
@@ -1857,10 +1881,22 @@ message VoiceAssistantWakeWord {
|
|||||||
repeated string trained_languages = 3;
|
repeated string trained_languages = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message VoiceAssistantExternalWakeWord {
|
||||||
|
string id = 1;
|
||||||
|
string wake_word = 2;
|
||||||
|
repeated string trained_languages = 3;
|
||||||
|
string model_type = 4;
|
||||||
|
uint32 model_size = 5;
|
||||||
|
string model_hash = 6;
|
||||||
|
string url = 7;
|
||||||
|
}
|
||||||
|
|
||||||
message VoiceAssistantConfigurationRequest {
|
message VoiceAssistantConfigurationRequest {
|
||||||
option (id) = 121;
|
option (id) = 121;
|
||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||||
|
|
||||||
|
repeated VoiceAssistantExternalWakeWord external_wake_words = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message VoiceAssistantConfigurationResponse {
|
message VoiceAssistantConfigurationResponse {
|
||||||
@@ -2275,3 +2311,28 @@ message UpdateCommandRequest {
|
|||||||
UpdateCommand command = 2;
|
UpdateCommand command = 2;
|
||||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Z-WAVE ====================
|
||||||
|
|
||||||
|
message ZWaveProxyFrame {
|
||||||
|
option (id) = 128;
|
||||||
|
option (source) = SOURCE_BOTH;
|
||||||
|
option (ifdef) = "USE_ZWAVE_PROXY";
|
||||||
|
option (no_delay) = true;
|
||||||
|
|
||||||
|
bytes data = 1 [(pointer_to_buffer) = true];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ZWaveProxyRequestType {
|
||||||
|
ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0;
|
||||||
|
ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1;
|
||||||
|
ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2;
|
||||||
|
}
|
||||||
|
message ZWaveProxyRequest {
|
||||||
|
option (id) = 129;
|
||||||
|
option (source) = SOURCE_BOTH;
|
||||||
|
option (ifdef) = "USE_ZWAVE_PROXY";
|
||||||
|
|
||||||
|
ZWaveProxyRequestType type = 1;
|
||||||
|
bytes data = 2 [(pointer_to_buffer) = true];
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <utility>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <utility>
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
@@ -30,6 +30,9 @@
|
|||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
#include "esphome/components/voice_assistant/voice_assistant.h"
|
#include "esphome/components/voice_assistant/voice_assistant.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
#include "esphome/components/zwave_proxy/zwave_proxy.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
@@ -42,6 +45,8 @@ static constexpr uint8_t MAX_PING_RETRIES = 60;
|
|||||||
static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
|
static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
|
||||||
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
|
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
|
||||||
|
|
||||||
|
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||||
|
|
||||||
static const char *const TAG = "api.connection";
|
static const char *const TAG = "api.connection";
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
static const int CAMERA_STOP_STREAM = 5000;
|
static const int CAMERA_STOP_STREAM = 5000;
|
||||||
@@ -111,8 +116,7 @@ void APIConnection::start() {
|
|||||||
|
|
||||||
APIError err = this->helper_->init();
|
APIError err = this->helper_->init();
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
|
||||||
this->log_warning_("Helper init failed", err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->client_info_.peername = helper_->getpeername();
|
this->client_info_.peername = helper_->getpeername();
|
||||||
@@ -142,8 +146,7 @@ void APIConnection::loop() {
|
|||||||
|
|
||||||
APIError err = this->helper_->loop();
|
APIError err = this->helper_->loop();
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
|
||||||
this->log_socket_operation_failed_(err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,17 +161,13 @@ void APIConnection::loop() {
|
|||||||
// No more data available
|
// No more data available
|
||||||
break;
|
break;
|
||||||
} else if (err != APIError::OK) {
|
} else if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
|
||||||
this->log_warning_("Reading failed", err);
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this->last_traffic_ = now;
|
this->last_traffic_ = now;
|
||||||
// read a packet
|
// read a packet
|
||||||
if (buffer.data_len > 0) {
|
this->read_message(buffer.data_len, buffer.type,
|
||||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr);
|
||||||
} else {
|
|
||||||
this->read_message(0, buffer.type, nullptr);
|
|
||||||
}
|
|
||||||
if (this->flags_.remove)
|
if (this->flags_.remove)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -200,7 +199,8 @@ void APIConnection::loop() {
|
|||||||
// Disconnect if not responded within 2.5*keepalive
|
// Disconnect if not responded within 2.5*keepalive
|
||||||
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
|
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
|
ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
|
||||||
|
this->client_info_.peername.c_str());
|
||||||
}
|
}
|
||||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
|
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
|
||||||
// Only send ping if we're not disconnecting
|
// Only send ping if we're not disconnecting
|
||||||
@@ -250,7 +250,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
|
|||||||
// remote initiated disconnect_client
|
// remote initiated disconnect_client
|
||||||
// don't close yet, we still need to send the disconnect response
|
// don't close yet, we still need to send the disconnect response
|
||||||
// close will happen on next loop
|
// close will happen on next loop
|
||||||
ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str());
|
ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||||
this->flags_.next_close = true;
|
this->flags_.next_close = true;
|
||||||
DisconnectResponse resp;
|
DisconnectResponse resp;
|
||||||
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
|
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
|
||||||
@@ -289,16 +289,26 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
|
|||||||
return 0; // Doesn't fit
|
return 0; // Doesn't fit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate buffer space - pass payload size, allocation functions add header/footer space
|
|
||||||
ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
|
|
||||||
: conn->allocate_batch_message_buffer(calculated_size);
|
|
||||||
|
|
||||||
// Get buffer size after allocation (which includes header padding)
|
// Get buffer size after allocation (which includes header padding)
|
||||||
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
|
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
|
||||||
size_t size_before_encode = shared_buf.size();
|
|
||||||
|
if (is_single || conn->flags_.batch_first_message) {
|
||||||
|
// Single message or first batch message
|
||||||
|
conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size);
|
||||||
|
if (conn->flags_.batch_first_message) {
|
||||||
|
conn->flags_.batch_first_message = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Batch message second or later
|
||||||
|
// Add padding for previous message footer + this message header
|
||||||
|
size_t current_size = shared_buf.size();
|
||||||
|
shared_buf.reserve(current_size + total_calculated_size);
|
||||||
|
shared_buf.resize(current_size + footer_size + header_padding);
|
||||||
|
}
|
||||||
|
|
||||||
// Encode directly into buffer
|
// Encode directly into buffer
|
||||||
msg.encode(buffer);
|
size_t size_before_encode = shared_buf.size();
|
||||||
|
msg.encode({&shared_buf});
|
||||||
|
|
||||||
// Calculate actual encoded size (not including header that was already added)
|
// Calculate actual encoded size (not including header that was already added)
|
||||||
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
||||||
@@ -455,9 +465,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
|||||||
resp.cold_white = values.get_cold_white();
|
resp.cold_white = values.get_cold_white();
|
||||||
resp.warm_white = values.get_warm_white();
|
resp.warm_white = values.get_warm_white();
|
||||||
if (light->supports_effects()) {
|
if (light->supports_effects()) {
|
||||||
// get_effect_name() returns temporary std::string - must store it
|
resp.set_effect(light->get_effect_name_ref());
|
||||||
std::string effect_name = light->get_effect_name();
|
|
||||||
resp.set_effect(StringRef(effect_name));
|
|
||||||
}
|
}
|
||||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
@@ -1062,17 +1070,23 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||||
if (homeassistant::global_homeassistant_time != nullptr)
|
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||||
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||||
|
#ifdef USE_TIME_TIMEZONE
|
||||||
|
if (value.timezone_len > 0) {
|
||||||
|
const std::string ¤t_tz = homeassistant::global_homeassistant_time->get_timezone();
|
||||||
|
// Compare without allocating a string
|
||||||
|
if (current_tz.length() != value.timezone_len ||
|
||||||
|
memcmp(current_tz.c_str(), value.timezone, value.timezone_len) != 0) {
|
||||||
|
homeassistant::global_homeassistant_time->set_timezone(
|
||||||
|
std::string(reinterpret_cast<const char *>(value.timezone), value.timezone_len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool APIConnection::send_get_time_response(const GetTimeRequest &msg) {
|
|
||||||
GetTimeResponse resp;
|
|
||||||
resp.epoch_seconds = ::time(nullptr);
|
|
||||||
return this->send_message(resp, GetTimeResponse::MESSAGE_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||||
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
|
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
|
||||||
@@ -1183,6 +1197,23 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
|
|||||||
resp_wake_word.trained_languages.push_back(lang);
|
resp_wake_word.trained_languages.push_back(lang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter external wake words
|
||||||
|
for (auto &wake_word : msg.external_wake_words) {
|
||||||
|
if (wake_word.model_type != "micro") {
|
||||||
|
// microWakeWord only
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.available_wake_words.emplace_back();
|
||||||
|
auto &resp_wake_word = resp.available_wake_words.back();
|
||||||
|
resp_wake_word.set_id(StringRef(wake_word.id));
|
||||||
|
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
|
||||||
|
for (const auto &lang : wake_word.trained_languages) {
|
||||||
|
resp_wake_word.trained_languages.push_back(lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp.active_wake_words = &config.active_wake_words;
|
resp.active_wake_words = &config.active_wake_words;
|
||||||
resp.max_active_wake_words = config.max_active_wake_words;
|
resp.max_active_wake_words = config.max_active_wake_words;
|
||||||
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
|
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
|
||||||
@@ -1193,7 +1224,16 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon
|
|||||||
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
|
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void APIConnection::zwave_proxy_frame(const ZWaveProxyFrame &msg) {
|
||||||
|
zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) {
|
||||||
|
zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
@@ -1340,7 +1380,7 @@ void APIConnection::complete_authentication_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
|
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
|
||||||
ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str());
|
ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||||
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
|
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
|
||||||
#endif
|
#endif
|
||||||
@@ -1349,10 +1389,15 @@ void APIConnection::complete_authentication_() {
|
|||||||
this->send_time_request();
|
this->send_time_request();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
if (zwave_proxy::global_zwave_proxy != nullptr) {
|
||||||
|
zwave_proxy::global_zwave_proxy->api_connection_authenticated(this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
||||||
this->client_info_.name = msg.client_info;
|
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_.peername = this->helper_->getpeername();
|
||||||
this->client_api_version_major_ = msg.api_version_major;
|
this->client_api_version_major_ = msg.api_version_major;
|
||||||
this->client_api_version_minor_ = msg.api_version_minor;
|
this->client_api_version_minor_ = msg.api_version_minor;
|
||||||
@@ -1362,9 +1407,8 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
|||||||
HelloResponse resp;
|
HelloResponse resp;
|
||||||
resp.api_version_major = 1;
|
resp.api_version_major = 1;
|
||||||
resp.api_version_minor = 12;
|
resp.api_version_minor = 12;
|
||||||
// Temporary string for concatenation - will be valid during send_message call
|
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
|
||||||
std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
resp.set_server_info(ESPHOME_VERSION_REF);
|
||||||
resp.set_server_info(StringRef(server_info));
|
|
||||||
resp.set_name(StringRef(App.get_name()));
|
resp.set_name(StringRef(App.get_name()));
|
||||||
|
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
@@ -1377,20 +1421,17 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
|||||||
|
|
||||||
return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
|
return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
bool APIConnection::send_connect_response(const ConnectRequest &msg) {
|
|
||||||
bool correct = true;
|
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
correct = this->parent_->check_password(msg.password);
|
bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
|
||||||
#endif
|
AuthenticationResponse resp;
|
||||||
|
|
||||||
ConnectResponse resp;
|
|
||||||
// bool invalid_password = 1;
|
// bool invalid_password = 1;
|
||||||
resp.invalid_password = !correct;
|
resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
|
||||||
if (correct) {
|
if (!resp.invalid_password) {
|
||||||
this->complete_authentication_();
|
this->complete_authentication_();
|
||||||
}
|
}
|
||||||
return this->send_message(resp, ConnectResponse::MESSAGE_TYPE);
|
return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
#endif // USE_API_PASSWORD
|
||||||
|
|
||||||
bool APIConnection::send_ping_response(const PingRequest &msg) {
|
bool APIConnection::send_ping_response(const PingRequest &msg) {
|
||||||
PingResponse resp;
|
PingResponse resp;
|
||||||
@@ -1411,13 +1452,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
|||||||
std::string mac_address = get_mac_address_pretty();
|
std::string mac_address = get_mac_address_pretty();
|
||||||
resp.set_mac_address(StringRef(mac_address));
|
resp.set_mac_address(StringRef(mac_address));
|
||||||
|
|
||||||
// Compile-time StringRef constants
|
|
||||||
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
|
||||||
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||||
|
|
||||||
// get_compilation_time() returns temporary std::string - must store it
|
resp.set_compilation_time(App.get_compilation_time_ref());
|
||||||
std::string compilation_time = App.get_compilation_time();
|
|
||||||
resp.set_compilation_time(StringRef(compilation_time));
|
|
||||||
|
|
||||||
// Compile-time StringRef constants for manufacturers
|
// Compile-time StringRef constants for manufacturers
|
||||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||||
@@ -1458,6 +1495,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
|||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
|
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
resp.zwave_proxy_feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags();
|
||||||
|
resp.zwave_home_id = zwave_proxy::global_zwave_proxy->get_home_id();
|
||||||
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
resp.api_encryption_supported = true;
|
resp.api_encryption_supported = true;
|
||||||
#endif
|
#endif
|
||||||
@@ -1508,6 +1549,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
void APIConnection::on_homeassistant_action_response(const HomeassistantActionResponse &msg) {
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
if (msg.response_data_len > 0) {
|
||||||
|
this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message, msg.response_data,
|
||||||
|
msg.response_data_len);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
|
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
|
||||||
NoiseEncryptionSetKeyResponse resp;
|
NoiseEncryptionSetKeyResponse resp;
|
||||||
@@ -1538,8 +1593,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
|||||||
delay(0);
|
delay(0);
|
||||||
APIError err = this->helper_->loop();
|
APIError err = this->helper_->loop();
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
|
||||||
this->log_socket_operation_failed_(err);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this->helper_->can_write_without_blocking())
|
if (this->helper_->can_write_without_blocking())
|
||||||
@@ -1558,8 +1612,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
|||||||
if (err == APIError::WOULD_BLOCK)
|
if (err == APIError::WOULD_BLOCK)
|
||||||
return false;
|
return false;
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
this->fatal_error_with_log_(LOG_STR("Packet write failed"), err);
|
||||||
this->log_warning_("Packet write failed", err);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Do not set last_traffic_ on send
|
// Do not set last_traffic_ on send
|
||||||
@@ -1568,12 +1621,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
|||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
void APIConnection::on_unauthenticated_access() {
|
void APIConnection::on_unauthenticated_access() {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str());
|
ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
void APIConnection::on_no_setup_connection() {
|
void APIConnection::on_no_setup_connection() {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str());
|
ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||||
}
|
}
|
||||||
void APIConnection::on_fatal_error() {
|
void APIConnection::on_fatal_error() {
|
||||||
this->helper_->close();
|
this->helper_->close();
|
||||||
@@ -1620,14 +1673,6 @@ bool APIConnection::schedule_batch_() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
|
|
||||||
|
|
||||||
ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
|
|
||||||
ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
|
|
||||||
this->flags_.batch_first_message = false;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void APIConnection::process_batch_() {
|
void APIConnection::process_batch_() {
|
||||||
// Ensure PacketInfo remains trivially destructible for our placement new approach
|
// Ensure PacketInfo remains trivially destructible for our placement new approach
|
||||||
static_assert(std::is_trivially_destructible<PacketInfo>::value,
|
static_assert(std::is_trivially_destructible<PacketInfo>::value,
|
||||||
@@ -1735,7 +1780,7 @@ void APIConnection::process_batch_() {
|
|||||||
}
|
}
|
||||||
remaining_size -= payload_size;
|
remaining_size -= payload_size;
|
||||||
// Calculate where the next message's header padding will start
|
// Calculate where the next message's header padding will start
|
||||||
// Current buffer size + footer space (that prepare_message_buffer will add for this message)
|
// Current buffer size + footer space for this message
|
||||||
current_offset = shared_buf.size() + footer_size;
|
current_offset = shared_buf.size() + footer_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1753,8 +1798,7 @@ void APIConnection::process_batch_() {
|
|||||||
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
|
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
|
||||||
std::span<const PacketInfo>(packet_info, packet_count));
|
std::span<const PacketInfo>(packet_info, packet_count));
|
||||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||||
on_fatal_error();
|
this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
|
||||||
this->log_warning_("Batch write failed", err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@@ -1832,11 +1876,10 @@ void APIConnection::process_state_subscriptions_() {
|
|||||||
}
|
}
|
||||||
#endif // USE_API_HOMEASSISTANT_STATES
|
#endif // USE_API_HOMEASSISTANT_STATES
|
||||||
|
|
||||||
void APIConnection::log_warning_(const char *message, APIError err) {
|
void APIConnection::log_warning_(const LogString *message, APIError err) {
|
||||||
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
|
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
@@ -19,14 +19,6 @@ namespace esphome::api {
|
|||||||
struct ClientInfo {
|
struct ClientInfo {
|
||||||
std::string name; // Client name from Hello message
|
std::string name; // Client name from Hello message
|
||||||
std::string peername; // IP:port from socket
|
std::string peername; // IP:port from socket
|
||||||
|
|
||||||
std::string get_combined_info() const {
|
|
||||||
if (name == peername) {
|
|
||||||
// Before Hello message, both are the same
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
return name + " (" + peername + ")";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keepalive timeout in milliseconds
|
// Keepalive timeout in milliseconds
|
||||||
@@ -44,7 +36,7 @@ static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HO
|
|||||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class APIConnection : public APIServerConnection {
|
class APIConnection final : public APIServerConnection {
|
||||||
public:
|
public:
|
||||||
friend class APIServer;
|
friend class APIServer;
|
||||||
friend class ListEntitiesIterator;
|
friend class ListEntitiesIterator;
|
||||||
@@ -132,12 +124,15 @@ class APIConnection : public APIServerConnection {
|
|||||||
#endif
|
#endif
|
||||||
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
|
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
void send_homeassistant_action(const HomeassistantActionRequest &call) {
|
||||||
if (!this->flags_.service_call_subscription)
|
if (!this->flags_.service_call_subscription)
|
||||||
return;
|
return;
|
||||||
this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE);
|
this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
#endif
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
|
||||||
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
@@ -171,6 +166,11 @@ class APIConnection : public APIServerConnection {
|
|||||||
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
|
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void zwave_proxy_frame(const ZWaveProxyFrame &msg) override;
|
||||||
|
void zwave_proxy_request(const ZWaveProxyRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
|
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
|
||||||
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
|
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
|
||||||
@@ -197,7 +197,9 @@ class APIConnection : public APIServerConnection {
|
|||||||
void on_get_time_response(const GetTimeResponse &value) override;
|
void on_get_time_response(const GetTimeResponse &value) override;
|
||||||
#endif
|
#endif
|
||||||
bool send_hello_response(const HelloRequest &msg) override;
|
bool send_hello_response(const HelloRequest &msg) override;
|
||||||
bool send_connect_response(const ConnectRequest &msg) override;
|
#ifdef USE_API_PASSWORD
|
||||||
|
bool send_authenticate_response(const AuthenticationRequest &msg) override;
|
||||||
|
#endif
|
||||||
bool send_disconnect_response(const DisconnectRequest &msg) override;
|
bool send_disconnect_response(const DisconnectRequest &msg) override;
|
||||||
bool send_ping_response(const PingRequest &msg) override;
|
bool send_ping_response(const PingRequest &msg) override;
|
||||||
bool send_device_info_response(const DeviceInfoRequest &msg) override;
|
bool send_device_info_response(const DeviceInfoRequest &msg) override;
|
||||||
@@ -219,7 +221,6 @@ class APIConnection : public APIServerConnection {
|
|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
|
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
bool send_get_time_response(const GetTimeRequest &msg) override;
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
@@ -252,54 +253,28 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
// Get header padding size - used for both reserve and insert
|
// Get header padding size - used for both reserve and insert
|
||||||
uint8_t header_padding = this->helper_->frame_header_padding();
|
uint8_t header_padding = this->helper_->frame_header_padding();
|
||||||
|
|
||||||
// Get shared buffer from parent server
|
// Get shared buffer from parent server
|
||||||
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
||||||
|
this->prepare_first_message_buffer(shared_buf, header_padding,
|
||||||
|
reserve_size + header_padding + this->helper_->frame_footer_size());
|
||||||
|
return {&shared_buf};
|
||||||
|
}
|
||||||
|
|
||||||
|
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
|
||||||
shared_buf.clear();
|
shared_buf.clear();
|
||||||
// Reserve space for header padding + message + footer
|
// Reserve space for header padding + message + footer
|
||||||
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
||||||
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
||||||
shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
|
shared_buf.reserve(total_size);
|
||||||
// Resize to add header padding so message encoding starts at the correct position
|
// Resize to add header padding so message encoding starts at the correct position
|
||||||
shared_buf.resize(header_padding);
|
shared_buf.resize(header_padding);
|
||||||
return {&shared_buf};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare buffer for next message in batch
|
|
||||||
ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
|
|
||||||
// Get reference to shared buffer (it maintains state between batch messages)
|
|
||||||
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
|
||||||
|
|
||||||
if (is_first_message) {
|
|
||||||
shared_buf.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t current_size = shared_buf.size();
|
|
||||||
|
|
||||||
// Calculate padding to add:
|
|
||||||
// - First message: just header padding
|
|
||||||
// - Subsequent messages: footer for previous message + header padding for this message
|
|
||||||
size_t padding_to_add = is_first_message
|
|
||||||
? this->helper_->frame_header_padding()
|
|
||||||
: this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
|
|
||||||
|
|
||||||
// Reserve space for padding + message
|
|
||||||
shared_buf.reserve(current_size + padding_to_add + message_size);
|
|
||||||
|
|
||||||
// Resize to add the padding bytes
|
|
||||||
shared_buf.resize(current_size + padding_to_add);
|
|
||||||
|
|
||||||
return {&shared_buf};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool try_to_clear_buffer(bool log_out_of_space);
|
bool try_to_clear_buffer(bool log_out_of_space);
|
||||||
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
|
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
|
||||||
|
|
||||||
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
|
const std::string &get_name() const { return this->client_info_.name; }
|
||||||
|
const std::string &get_peername() const { return this->client_info_.peername; }
|
||||||
// Buffer allocator methods for batch processing
|
|
||||||
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
|
|
||||||
ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Helper function to handle authentication completion
|
// Helper function to handle authentication completion
|
||||||
@@ -328,9 +303,17 @@ class APIConnection : public APIServerConnection {
|
|||||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||||
// Set common fields that are shared by all entity types
|
// Set common fields that are shared by all entity types
|
||||||
msg.key = entity->get_object_id_hash();
|
msg.key = entity->get_object_id_hash();
|
||||||
// IMPORTANT: get_object_id() may return a temporary std::string
|
// Try to use static reference first to avoid allocation
|
||||||
std::string object_id = entity->get_object_id();
|
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));
|
msg.set_object_id(StringRef(object_id));
|
||||||
|
}
|
||||||
|
|
||||||
if (entity->has_own_name()) {
|
if (entity->has_own_name()) {
|
||||||
msg.set_name(entity->get_name());
|
msg.set_name(entity->get_name());
|
||||||
@@ -751,9 +734,12 @@ class APIConnection : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to log API errors with errno
|
// Helper function to log API errors with errno
|
||||||
void log_warning_(const char *message, APIError err);
|
void log_warning_(const LogString *message, APIError err);
|
||||||
// Specific helper for duplicated error message
|
// Helper to handle fatal errors with logging
|
||||||
void log_socket_operation_failed_(APIError err);
|
inline void fatal_error_with_log_(const LogString *message, APIError err) {
|
||||||
|
this->on_fatal_error();
|
||||||
|
this->log_warning_(message, err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ namespace esphome::api {
|
|||||||
|
|
||||||
static const char *const TAG = "api.frame_helper";
|
static const char *const TAG = "api.frame_helper";
|
||||||
|
|
||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
|
#define HELPER_LOG(msg, ...) \
|
||||||
|
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||||
|
|
||||||
#ifdef HELPER_LOG_PACKETS
|
#ifdef HELPER_LOG_PACKETS
|
||||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
||||||
@@ -23,64 +24,64 @@ static const char *const TAG = "api.frame_helper";
|
|||||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *api_error_to_str(APIError err) {
|
const LogString *api_error_to_logstr(APIError err) {
|
||||||
// not using switch to ensure compiler doesn't try to build a big table out of it
|
// not using switch to ensure compiler doesn't try to build a big table out of it
|
||||||
if (err == APIError::OK) {
|
if (err == APIError::OK) {
|
||||||
return "OK";
|
return LOG_STR("OK");
|
||||||
} else if (err == APIError::WOULD_BLOCK) {
|
} else if (err == APIError::WOULD_BLOCK) {
|
||||||
return "WOULD_BLOCK";
|
return LOG_STR("WOULD_BLOCK");
|
||||||
} else if (err == APIError::BAD_INDICATOR) {
|
} else if (err == APIError::BAD_INDICATOR) {
|
||||||
return "BAD_INDICATOR";
|
return LOG_STR("BAD_INDICATOR");
|
||||||
} else if (err == APIError::BAD_DATA_PACKET) {
|
} else if (err == APIError::BAD_DATA_PACKET) {
|
||||||
return "BAD_DATA_PACKET";
|
return LOG_STR("BAD_DATA_PACKET");
|
||||||
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
||||||
return "TCP_NODELAY_FAILED";
|
return LOG_STR("TCP_NODELAY_FAILED");
|
||||||
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
||||||
return "TCP_NONBLOCKING_FAILED";
|
return LOG_STR("TCP_NONBLOCKING_FAILED");
|
||||||
} else if (err == APIError::CLOSE_FAILED) {
|
} else if (err == APIError::CLOSE_FAILED) {
|
||||||
return "CLOSE_FAILED";
|
return LOG_STR("CLOSE_FAILED");
|
||||||
} else if (err == APIError::SHUTDOWN_FAILED) {
|
} else if (err == APIError::SHUTDOWN_FAILED) {
|
||||||
return "SHUTDOWN_FAILED";
|
return LOG_STR("SHUTDOWN_FAILED");
|
||||||
} else if (err == APIError::BAD_STATE) {
|
} else if (err == APIError::BAD_STATE) {
|
||||||
return "BAD_STATE";
|
return LOG_STR("BAD_STATE");
|
||||||
} else if (err == APIError::BAD_ARG) {
|
} else if (err == APIError::BAD_ARG) {
|
||||||
return "BAD_ARG";
|
return LOG_STR("BAD_ARG");
|
||||||
} else if (err == APIError::SOCKET_READ_FAILED) {
|
} else if (err == APIError::SOCKET_READ_FAILED) {
|
||||||
return "SOCKET_READ_FAILED";
|
return LOG_STR("SOCKET_READ_FAILED");
|
||||||
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
||||||
return "SOCKET_WRITE_FAILED";
|
return LOG_STR("SOCKET_WRITE_FAILED");
|
||||||
} else if (err == APIError::OUT_OF_MEMORY) {
|
} else if (err == APIError::OUT_OF_MEMORY) {
|
||||||
return "OUT_OF_MEMORY";
|
return LOG_STR("OUT_OF_MEMORY");
|
||||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||||
return "CONNECTION_CLOSED";
|
return LOG_STR("CONNECTION_CLOSED");
|
||||||
}
|
}
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||||
return "BAD_HANDSHAKE_PACKET_LEN";
|
return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
||||||
return "HANDSHAKESTATE_READ_FAILED";
|
return LOG_STR("HANDSHAKESTATE_READ_FAILED");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
||||||
return "HANDSHAKESTATE_WRITE_FAILED";
|
return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
||||||
return "HANDSHAKESTATE_BAD_STATE";
|
return LOG_STR("HANDSHAKESTATE_BAD_STATE");
|
||||||
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
||||||
return "CIPHERSTATE_DECRYPT_FAILED";
|
return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
|
||||||
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
||||||
return "CIPHERSTATE_ENCRYPT_FAILED";
|
return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
||||||
return "HANDSHAKESTATE_SETUP_FAILED";
|
return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
||||||
return "HANDSHAKESTATE_SPLIT_FAILED";
|
return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
|
||||||
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
||||||
return "BAD_HANDSHAKE_ERROR_BYTE";
|
return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return "UNKNOWN";
|
return LOG_STR("UNKNOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation for loop - handles sending buffered data
|
// Default implementation for loop - handles sending buffered data
|
||||||
APIError APIFrameHelper::loop() {
|
APIError APIFrameHelper::loop() {
|
||||||
if (!this->tx_buf_.empty()) {
|
if (this->tx_buf_count_ > 0) {
|
||||||
APIError err = try_send_tx_buf_();
|
APIError err = try_send_tx_buf_();
|
||||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||||
return err;
|
return err;
|
||||||
@@ -102,9 +103,20 @@ APIError APIFrameHelper::handle_socket_write_error_() {
|
|||||||
// Helper method to buffer data from IOVs
|
// Helper method to buffer data from IOVs
|
||||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
|
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
|
||||||
uint16_t offset) {
|
uint16_t offset) {
|
||||||
SendBuffer buffer;
|
// Check if queue is full
|
||||||
buffer.size = total_write_len - offset;
|
if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) {
|
||||||
buffer.data = std::make_unique<uint8_t[]>(buffer.size);
|
HELPER_LOG("Send queue full (%u buffers), dropping connection", this->tx_buf_count_);
|
||||||
|
this->state_ = State::FAILED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t buffer_size = total_write_len - offset;
|
||||||
|
auto &buffer = this->tx_buf_[this->tx_buf_tail_];
|
||||||
|
buffer = std::make_unique<SendBuffer>(SendBuffer{
|
||||||
|
.data = std::make_unique<uint8_t[]>(buffer_size),
|
||||||
|
.size = buffer_size,
|
||||||
|
.offset = 0,
|
||||||
|
});
|
||||||
|
|
||||||
uint16_t to_skip = offset;
|
uint16_t to_skip = offset;
|
||||||
uint16_t write_pos = 0;
|
uint16_t write_pos = 0;
|
||||||
@@ -117,12 +129,15 @@ void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt,
|
|||||||
// Include this segment (partially or fully)
|
// Include this segment (partially or fully)
|
||||||
const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
|
const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
|
||||||
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
|
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
|
||||||
std::memcpy(buffer.data.get() + write_pos, src, len);
|
std::memcpy(buffer->data.get() + write_pos, src, len);
|
||||||
write_pos += len;
|
write_pos += len;
|
||||||
to_skip = 0;
|
to_skip = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->tx_buf_.push_back(std::move(buffer));
|
|
||||||
|
// Update circular buffer tracking
|
||||||
|
this->tx_buf_tail_ = (this->tx_buf_tail_ + 1) % API_MAX_SEND_QUEUE;
|
||||||
|
this->tx_buf_count_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method writes data to socket or buffers it
|
// This method writes data to socket or buffers it
|
||||||
@@ -140,7 +155,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Try to send any existing buffered data first if there is any
|
// Try to send any existing buffered data first if there is any
|
||||||
if (!this->tx_buf_.empty()) {
|
if (this->tx_buf_count_ > 0) {
|
||||||
APIError send_result = try_send_tx_buf_();
|
APIError send_result = try_send_tx_buf_();
|
||||||
// If real error occurred (not just WOULD_BLOCK), return it
|
// If real error occurred (not just WOULD_BLOCK), return it
|
||||||
if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
|
if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
|
||||||
@@ -149,14 +164,16 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
|
|||||||
|
|
||||||
// If there is still data in the buffer, we can't send, buffer
|
// If there is still data in the buffer, we can't send, buffer
|
||||||
// the new data and return
|
// the new data and return
|
||||||
if (!this->tx_buf_.empty()) {
|
if (this->tx_buf_count_ > 0) {
|
||||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
|
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
|
||||||
return APIError::OK; // Success, data buffered
|
return APIError::OK; // Success, data buffered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to send directly if no buffered data
|
// Try to send directly if no buffered data
|
||||||
ssize_t sent = this->socket_->writev(iov, iovcnt);
|
// Optimize for single iovec case (common for plaintext API)
|
||||||
|
ssize_t sent =
|
||||||
|
(iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
|
||||||
|
|
||||||
if (sent == -1) {
|
if (sent == -1) {
|
||||||
APIError err = this->handle_socket_write_error_();
|
APIError err = this->handle_socket_write_error_();
|
||||||
@@ -175,32 +192,31 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Common implementation for trying to send buffered data
|
// Common implementation for trying to send buffered data
|
||||||
// IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method
|
// IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method
|
||||||
APIError APIFrameHelper::try_send_tx_buf_() {
|
APIError APIFrameHelper::try_send_tx_buf_() {
|
||||||
// Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
|
// Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
|
||||||
bool tx_buf_empty = false;
|
while (this->tx_buf_count_ > 0) {
|
||||||
while (!tx_buf_empty) {
|
|
||||||
// Get the first buffer in the queue
|
// Get the first buffer in the queue
|
||||||
SendBuffer &front_buffer = this->tx_buf_.front();
|
SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get();
|
||||||
|
|
||||||
// Try to send the remaining data in this buffer
|
// Try to send the remaining data in this buffer
|
||||||
ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
|
ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining());
|
||||||
|
|
||||||
if (sent == -1) {
|
if (sent == -1) {
|
||||||
return this->handle_socket_write_error_();
|
return this->handle_socket_write_error_();
|
||||||
} else if (sent == 0) {
|
} else if (sent == 0) {
|
||||||
// Nothing sent but not an error
|
// Nothing sent but not an error
|
||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
} else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) {
|
} else if (static_cast<uint16_t>(sent) < front_buffer->remaining()) {
|
||||||
// Partially sent, update offset
|
// Partially sent, update offset
|
||||||
// Cast to ensure no overflow issues with uint16_t
|
// Cast to ensure no overflow issues with uint16_t
|
||||||
front_buffer.offset += static_cast<uint16_t>(sent);
|
front_buffer->offset += static_cast<uint16_t>(sent);
|
||||||
return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
|
return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
|
||||||
} else {
|
} else {
|
||||||
// Buffer completely sent, remove it from the queue
|
// Buffer completely sent, remove it from the queue
|
||||||
this->tx_buf_.pop_front();
|
this->tx_buf_[this->tx_buf_head_].reset();
|
||||||
// Update empty status for the loop condition
|
this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE;
|
||||||
tx_buf_empty = this->tx_buf_.empty();
|
this->tx_buf_count_--;
|
||||||
// Continue loop to try sending the next buffer
|
// Continue loop to try sending the next buffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <deque>
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -17,6 +18,17 @@ namespace esphome::api {
|
|||||||
// uncomment to log raw packets
|
// uncomment to log raw packets
|
||||||
//#define HELPER_LOG_PACKETS
|
//#define HELPER_LOG_PACKETS
|
||||||
|
|
||||||
|
// Maximum message size limits to prevent OOM on constrained devices
|
||||||
|
// Handshake messages are limited to a small size for security
|
||||||
|
static constexpr uint16_t MAX_HANDSHAKE_SIZE = 128;
|
||||||
|
|
||||||
|
// Data message limits vary by platform based on available memory
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
static constexpr uint16_t MAX_MESSAGE_SIZE = 8192; // 8 KiB for ESP8266
|
||||||
|
#else
|
||||||
|
static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and other platforms
|
||||||
|
#endif
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
struct ClientInfo;
|
struct ClientInfo;
|
||||||
|
|
||||||
@@ -66,7 +78,7 @@ enum class APIError : uint16_t {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *api_error_to_str(APIError err);
|
const LogString *api_error_to_logstr(APIError err);
|
||||||
|
|
||||||
class APIFrameHelper {
|
class APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
@@ -79,7 +91,7 @@ class APIFrameHelper {
|
|||||||
virtual APIError init() = 0;
|
virtual APIError init() = 0;
|
||||||
virtual APIError loop();
|
virtual APIError loop();
|
||||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||||
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
|
||||||
std::string getpeername() { return socket_->getpeername(); }
|
std::string getpeername() { return socket_->getpeername(); }
|
||||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
|
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
|
||||||
APIError close() {
|
APIError close() {
|
||||||
@@ -104,9 +116,9 @@ class APIFrameHelper {
|
|||||||
// The buffer contains all messages with appropriate padding before each
|
// The buffer contains all messages with appropriate padding before each
|
||||||
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
||||||
// Get the frame header padding required by this protocol
|
// Get the frame header padding required by this protocol
|
||||||
virtual uint8_t frame_header_padding() = 0;
|
uint8_t frame_header_padding() const { return frame_header_padding_; }
|
||||||
// Get the frame footer size required by this protocol
|
// Get the frame footer size required by this protocol
|
||||||
virtual uint8_t frame_footer_size() = 0;
|
uint8_t frame_footer_size() const { return frame_footer_size_; }
|
||||||
// Check if socket has data ready to read
|
// Check if socket has data ready to read
|
||||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||||
|
|
||||||
@@ -161,7 +173,7 @@ class APIFrameHelper {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Containers (size varies, but typically 12+ bytes on 32-bit)
|
// Containers (size varies, but typically 12+ bytes on 32-bit)
|
||||||
std::deque<SendBuffer> tx_buf_;
|
std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
|
||||||
std::vector<struct iovec> reusable_iovs_;
|
std::vector<struct iovec> reusable_iovs_;
|
||||||
std::vector<uint8_t> rx_buf_;
|
std::vector<uint8_t> rx_buf_;
|
||||||
|
|
||||||
@@ -174,7 +186,10 @@ class APIFrameHelper {
|
|||||||
State state_{State::INITIALIZE};
|
State state_{State::INITIALIZE};
|
||||||
uint8_t frame_header_padding_{0};
|
uint8_t frame_header_padding_{0};
|
||||||
uint8_t frame_footer_size_{0};
|
uint8_t frame_footer_size_{0};
|
||||||
// 5 bytes total, 3 bytes padding
|
uint8_t tx_buf_head_{0};
|
||||||
|
uint8_t tx_buf_tail_{0};
|
||||||
|
uint8_t tx_buf_count_{0};
|
||||||
|
// 8 bytes total, 0 bytes padding
|
||||||
|
|
||||||
// Common initialization for both plaintext and noise protocols
|
// Common initialization for both plaintext and noise protocols
|
||||||
APIError init_common_();
|
APIError init_common_();
|
||||||
|
|||||||
@@ -10,13 +10,22 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#include <pgmspace.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
static const char *const TAG = "api.noise";
|
static const char *const TAG = "api.noise";
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
|
||||||
|
#else
|
||||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||||
|
#endif
|
||||||
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||||
|
|
||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
|
#define HELPER_LOG(msg, ...) \
|
||||||
|
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||||
|
|
||||||
#ifdef HELPER_LOG_PACKETS
|
#ifdef HELPER_LOG_PACKETS
|
||||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
||||||
@@ -27,42 +36,42 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Convert a noise error code to a readable error
|
/// Convert a noise error code to a readable error
|
||||||
std::string noise_err_to_str(int err) {
|
const LogString *noise_err_to_logstr(int err) {
|
||||||
if (err == NOISE_ERROR_NO_MEMORY)
|
if (err == NOISE_ERROR_NO_MEMORY)
|
||||||
return "NO_MEMORY";
|
return LOG_STR("NO_MEMORY");
|
||||||
if (err == NOISE_ERROR_UNKNOWN_ID)
|
if (err == NOISE_ERROR_UNKNOWN_ID)
|
||||||
return "UNKNOWN_ID";
|
return LOG_STR("UNKNOWN_ID");
|
||||||
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
||||||
return "UNKNOWN_NAME";
|
return LOG_STR("UNKNOWN_NAME");
|
||||||
if (err == NOISE_ERROR_MAC_FAILURE)
|
if (err == NOISE_ERROR_MAC_FAILURE)
|
||||||
return "MAC_FAILURE";
|
return LOG_STR("MAC_FAILURE");
|
||||||
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
||||||
return "NOT_APPLICABLE";
|
return LOG_STR("NOT_APPLICABLE");
|
||||||
if (err == NOISE_ERROR_SYSTEM)
|
if (err == NOISE_ERROR_SYSTEM)
|
||||||
return "SYSTEM";
|
return LOG_STR("SYSTEM");
|
||||||
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
||||||
return "REMOTE_KEY_REQUIRED";
|
return LOG_STR("REMOTE_KEY_REQUIRED");
|
||||||
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
||||||
return "LOCAL_KEY_REQUIRED";
|
return LOG_STR("LOCAL_KEY_REQUIRED");
|
||||||
if (err == NOISE_ERROR_PSK_REQUIRED)
|
if (err == NOISE_ERROR_PSK_REQUIRED)
|
||||||
return "PSK_REQUIRED";
|
return LOG_STR("PSK_REQUIRED");
|
||||||
if (err == NOISE_ERROR_INVALID_LENGTH)
|
if (err == NOISE_ERROR_INVALID_LENGTH)
|
||||||
return "INVALID_LENGTH";
|
return LOG_STR("INVALID_LENGTH");
|
||||||
if (err == NOISE_ERROR_INVALID_PARAM)
|
if (err == NOISE_ERROR_INVALID_PARAM)
|
||||||
return "INVALID_PARAM";
|
return LOG_STR("INVALID_PARAM");
|
||||||
if (err == NOISE_ERROR_INVALID_STATE)
|
if (err == NOISE_ERROR_INVALID_STATE)
|
||||||
return "INVALID_STATE";
|
return LOG_STR("INVALID_STATE");
|
||||||
if (err == NOISE_ERROR_INVALID_NONCE)
|
if (err == NOISE_ERROR_INVALID_NONCE)
|
||||||
return "INVALID_NONCE";
|
return LOG_STR("INVALID_NONCE");
|
||||||
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
||||||
return "INVALID_PRIVATE_KEY";
|
return LOG_STR("INVALID_PRIVATE_KEY");
|
||||||
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
||||||
return "INVALID_PUBLIC_KEY";
|
return LOG_STR("INVALID_PUBLIC_KEY");
|
||||||
if (err == NOISE_ERROR_INVALID_FORMAT)
|
if (err == NOISE_ERROR_INVALID_FORMAT)
|
||||||
return "INVALID_FORMAT";
|
return LOG_STR("INVALID_FORMAT");
|
||||||
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
||||||
return "INVALID_SIGNATURE";
|
return LOG_STR("INVALID_SIGNATURE");
|
||||||
return to_string(err);
|
return LOG_STR("UNKNOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the frame helper, returns OK if successful.
|
/// Initialize the frame helper, returns OK if successful.
|
||||||
@@ -75,7 +84,11 @@ APIError APINoiseFrameHelper::init() {
|
|||||||
// init prologue
|
// init prologue
|
||||||
size_t old_size = prologue_.size();
|
size_t old_size = prologue_.size();
|
||||||
prologue_.resize(old_size + PROLOGUE_INIT_LEN);
|
prologue_.resize(old_size + PROLOGUE_INIT_LEN);
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||||
|
#else
|
||||||
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||||
|
#endif
|
||||||
|
|
||||||
state_ = State::CLIENT_HELLO;
|
state_ = State::CLIENT_HELLO;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
@@ -83,18 +96,18 @@ APIError APINoiseFrameHelper::init() {
|
|||||||
// Helper for handling handshake frame errors
|
// Helper for handling handshake frame errors
|
||||||
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
||||||
if (aerr == APIError::BAD_INDICATOR) {
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
send_explicit_handshake_reject_("Bad indicator byte");
|
send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
|
||||||
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||||
send_explicit_handshake_reject_("Bad handshake packet len");
|
send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
|
||||||
}
|
}
|
||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for handling noise library errors
|
// Helper for handling noise library errors
|
||||||
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
|
APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
|
HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
|
||||||
return api_err;
|
return api_err;
|
||||||
}
|
}
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
@@ -119,26 +132,16 @@ APIError APINoiseFrameHelper::loop() {
|
|||||||
return APIFrameHelper::loop();
|
return APIFrameHelper::loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
/** Read a packet into the rx_buf_.
|
||||||
*
|
*
|
||||||
* @param frame: The struct to hold the frame information in.
|
* @return APIError::OK if a full packet is in rx_buf_
|
||||||
* msg_start: points to the start of the payload - this pointer is only valid until the next
|
|
||||||
* try_receive_raw_ call
|
|
||||||
*
|
|
||||||
* @return 0 if a full packet is in rx_buf_
|
|
||||||
* @return -1 if error, check errno.
|
|
||||||
*
|
*
|
||||||
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
|
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
|
||||||
* errno ENOMEM: Not enough memory for reading packet.
|
* errno ENOMEM: Not enough memory for reading packet.
|
||||||
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||||
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
||||||
*/
|
*/
|
||||||
APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
APIError APINoiseFrameHelper::try_read_frame_() {
|
||||||
if (frame == nullptr) {
|
|
||||||
HELPER_LOG("Bad argument for try_read_frame_");
|
|
||||||
return APIError::BAD_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read header
|
// read header
|
||||||
if (rx_header_buf_len_ < 3) {
|
if (rx_header_buf_len_ < 3) {
|
||||||
// no header information yet
|
// no header information yet
|
||||||
@@ -165,16 +168,17 @@ APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
// read body
|
// read body
|
||||||
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
||||||
|
|
||||||
if (state_ != State::DATA && msg_size > 128) {
|
// Check against size limits to prevent OOM: MAX_HANDSHAKE_SIZE for handshake, MAX_MESSAGE_SIZE for data
|
||||||
// for handshake message only permit up to 128 bytes
|
uint16_t limit = (state_ == State::DATA) ? MAX_MESSAGE_SIZE : MAX_HANDSHAKE_SIZE;
|
||||||
|
if (msg_size > limit) {
|
||||||
state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad packet len for handshake: %d", msg_size);
|
HELPER_LOG("Bad packet: message size %u exceeds maximum %u", msg_size, limit);
|
||||||
return APIError::BAD_HANDSHAKE_PACKET_LEN;
|
return (state_ == State::DATA) ? APIError::BAD_DATA_PACKET : APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reserve space for body
|
// Reserve space for body
|
||||||
if (rx_buf_.size() != msg_size) {
|
if (this->rx_buf_.size() != msg_size) {
|
||||||
rx_buf_.resize(msg_size);
|
this->rx_buf_.resize(msg_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rx_buf_len_ < msg_size) {
|
if (rx_buf_len_ < msg_size) {
|
||||||
@@ -192,12 +196,12 @@ APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_PACKET_RECEIVED(rx_buf_);
|
LOG_PACKET_RECEIVED(this->rx_buf_);
|
||||||
*frame = std::move(rx_buf_);
|
|
||||||
// consume msg
|
// Clear state for next frame (rx_buf_ still contains data for caller)
|
||||||
rx_buf_ = {};
|
this->rx_buf_len_ = 0;
|
||||||
rx_buf_len_ = 0;
|
this->rx_header_buf_len_ = 0;
|
||||||
rx_header_buf_len_ = 0;
|
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,18 +223,17 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
if (state_ == State::CLIENT_HELLO) {
|
if (state_ == State::CLIENT_HELLO) {
|
||||||
// waiting for client hello
|
// waiting for client hello
|
||||||
std::vector<uint8_t> frame;
|
aerr = this->try_read_frame_();
|
||||||
aerr = try_read_frame_(&frame);
|
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return handle_handshake_frame_error_(aerr);
|
return handle_handshake_frame_error_(aerr);
|
||||||
}
|
}
|
||||||
// ignore contents, may be used in future for flags
|
// ignore contents, may be used in future for flags
|
||||||
// Resize for: existing prologue + 2 size bytes + frame data
|
// Resize for: existing prologue + 2 size bytes + frame data
|
||||||
size_t old_size = prologue_.size();
|
size_t old_size = this->prologue_.size();
|
||||||
prologue_.resize(old_size + 2 + frame.size());
|
this->prologue_.resize(old_size + 2 + this->rx_buf_.size());
|
||||||
prologue_[old_size] = (uint8_t) (frame.size() >> 8);
|
this->prologue_[old_size] = (uint8_t) (this->rx_buf_.size() >> 8);
|
||||||
prologue_[old_size + 1] = (uint8_t) frame.size();
|
this->prologue_[old_size + 1] = (uint8_t) this->rx_buf_.size();
|
||||||
std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size());
|
std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), this->rx_buf_.size());
|
||||||
|
|
||||||
state_ = State::SERVER_HELLO;
|
state_ = State::SERVER_HELLO;
|
||||||
}
|
}
|
||||||
@@ -272,29 +275,30 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
int action = noise_handshakestate_get_action(handshake_);
|
int action = noise_handshakestate_get_action(handshake_);
|
||||||
if (action == NOISE_ACTION_READ_MESSAGE) {
|
if (action == NOISE_ACTION_READ_MESSAGE) {
|
||||||
// waiting for handshake msg
|
// waiting for handshake msg
|
||||||
std::vector<uint8_t> frame;
|
aerr = this->try_read_frame_();
|
||||||
aerr = try_read_frame_(&frame);
|
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return handle_handshake_frame_error_(aerr);
|
return handle_handshake_frame_error_(aerr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame.empty()) {
|
if (this->rx_buf_.empty()) {
|
||||||
send_explicit_handshake_reject_("Empty handshake message");
|
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
} else if (frame[0] != 0x00) {
|
} else if (this->rx_buf_[0] != 0x00) {
|
||||||
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]);
|
||||||
send_explicit_handshake_reject_("Bad handshake error byte");
|
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
NoiseBuffer mbuf;
|
NoiseBuffer mbuf;
|
||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
|
noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1);
|
||||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
// Special handling for MAC failure
|
// Special handling for MAC failure
|
||||||
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
|
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
|
||||||
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
|
: LOG_STR("Handshake error"));
|
||||||
|
return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
|
||||||
|
APIError::HANDSHAKESTATE_READ_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
aerr = check_handshake_finished_();
|
aerr = check_handshake_finished_();
|
||||||
@@ -307,8 +311,8 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
||||||
|
|
||||||
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
||||||
APIError aerr_write =
|
APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
|
||||||
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
|
APIError::HANDSHAKESTATE_WRITE_FAILED);
|
||||||
if (aerr_write != APIError::OK)
|
if (aerr_write != APIError::OK)
|
||||||
return aerr_write;
|
return aerr_write;
|
||||||
buffer[0] = 0x00; // success
|
buffer[0] = 0x00; // success
|
||||||
@@ -331,15 +335,31 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
|
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
|
||||||
|
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||||
|
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
||||||
|
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
data.resize(reason.length() + 1);
|
data.resize(reason_len + 1);
|
||||||
|
data[0] = 0x01; // failure
|
||||||
|
|
||||||
|
// Copy error message from PROGMEM
|
||||||
|
if (reason_len > 0) {
|
||||||
|
memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Normal memory access
|
||||||
|
const char *reason_str = LOG_STR_ARG(reason);
|
||||||
|
size_t reason_len = strlen(reason_str);
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
data.resize(reason_len + 1);
|
||||||
data[0] = 0x01; // failure
|
data[0] = 0x01; // failure
|
||||||
|
|
||||||
// Copy error message in bulk
|
// Copy error message in bulk
|
||||||
if (!reason.empty()) {
|
if (reason_len > 0) {
|
||||||
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
std::memcpy(data.data() + 1, reason_str, reason_len);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// temporarily remove failed state
|
// temporarily remove failed state
|
||||||
auto orig_state = state_;
|
auto orig_state = state_;
|
||||||
@@ -348,34 +368,33 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
|
|||||||
state_ = orig_state;
|
state_ = orig_state;
|
||||||
}
|
}
|
||||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
int err;
|
APIError aerr = this->state_action_();
|
||||||
APIError aerr;
|
|
||||||
aerr = state_action_();
|
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state_ != State::DATA) {
|
if (this->state_ != State::DATA) {
|
||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> frame;
|
aerr = this->try_read_frame_();
|
||||||
aerr = try_read_frame_(&frame);
|
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
NoiseBuffer mbuf;
|
NoiseBuffer mbuf;
|
||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
noise_buffer_set_inout(mbuf, this->rx_buf_.data(), this->rx_buf_.size(), this->rx_buf_.size());
|
||||||
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
int err = noise_cipherstate_decrypt(this->recv_cipher_, &mbuf);
|
||||||
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
|
APIError decrypt_err =
|
||||||
if (decrypt_err != APIError::OK)
|
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||||
|
if (decrypt_err != APIError::OK) {
|
||||||
return decrypt_err;
|
return decrypt_err;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t msg_size = mbuf.size;
|
uint16_t msg_size = mbuf.size;
|
||||||
uint8_t *msg_data = frame.data();
|
uint8_t *msg_data = this->rx_buf_.data();
|
||||||
if (msg_size < 4) {
|
if (msg_size < 4) {
|
||||||
state_ = State::FAILED;
|
this->state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
@@ -383,12 +402,12 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
||||||
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||||
if (data_len > msg_size - 4) {
|
if (data_len > msg_size - 4) {
|
||||||
state_ = State::FAILED;
|
this->state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
|
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
|
||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer->container = std::move(frame);
|
buffer->container = std::move(this->rx_buf_);
|
||||||
buffer->data_offset = 4;
|
buffer->data_offset = 4;
|
||||||
buffer->data_len = data_len;
|
buffer->data_len = data_len;
|
||||||
buffer->type = type;
|
buffer->type = type;
|
||||||
@@ -450,7 +469,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
|||||||
4 + packet.payload_size + frame_footer_size_);
|
4 + packet.payload_size + frame_footer_size_);
|
||||||
|
|
||||||
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||||
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
APIError aerr =
|
||||||
|
handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
@@ -504,25 +524,27 @@ APIError APINoiseFrameHelper::init_handshake_() {
|
|||||||
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
||||||
|
|
||||||
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
||||||
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
APIError aerr =
|
||||||
|
handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
const auto &psk = ctx_->get_psk();
|
const auto &psk = ctx_->get_psk();
|
||||||
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
||||||
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
|
||||||
|
APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
||||||
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
// set_prologue copies it into handshakestate, so we can get rid of it now
|
// set_prologue copies it into handshakestate, so we can get rid of it now
|
||||||
prologue_ = {};
|
prologue_ = {};
|
||||||
|
|
||||||
err = noise_handshakestate_start(handshake_);
|
err = noise_handshakestate_start(handshake_);
|
||||||
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
@@ -540,7 +562,8 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
|||||||
return APIError::HANDSHAKESTATE_BAD_STATE;
|
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||||
}
|
}
|
||||||
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
||||||
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
APIError aerr =
|
||||||
|
handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
class APINoiseFrameHelper : public APIFrameHelper {
|
class APINoiseFrameHelper final : public APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
|
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
|
||||||
const ClientInfo *client_info)
|
const ClientInfo *client_info)
|
||||||
@@ -25,20 +25,16 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
// Get the frame header padding required by this protocol
|
|
||||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
|
||||||
// Get the frame footer size required by this protocol
|
|
||||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIError state_action_();
|
APIError state_action_();
|
||||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
APIError try_read_frame_();
|
||||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||||
APIError init_handshake_();
|
APIError init_handshake_();
|
||||||
APIError check_handshake_finished_();
|
APIError check_handshake_finished_();
|
||||||
void send_explicit_handshake_reject_(const std::string &reason);
|
void send_explicit_handshake_reject_(const LogString *reason);
|
||||||
APIError handle_handshake_frame_error_(APIError aerr);
|
APIError handle_handshake_frame_error_(APIError aerr);
|
||||||
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
|
APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err);
|
||||||
|
|
||||||
// Pointers first (4 bytes each)
|
// Pointers first (4 bytes each)
|
||||||
NoiseHandshakeState *handshake_{nullptr};
|
NoiseHandshakeState *handshake_{nullptr};
|
||||||
|
|||||||
@@ -10,11 +10,16 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#include <pgmspace.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
static const char *const TAG = "api.plaintext";
|
static const char *const TAG = "api.plaintext";
|
||||||
|
|
||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
|
#define HELPER_LOG(msg, ...) \
|
||||||
|
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||||
|
|
||||||
#ifdef HELPER_LOG_PACKETS
|
#ifdef HELPER_LOG_PACKETS
|
||||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
||||||
@@ -42,21 +47,13 @@ APIError APIPlaintextFrameHelper::loop() {
|
|||||||
return APIFrameHelper::loop();
|
return APIFrameHelper::loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
/** Read a packet into the rx_buf_.
|
||||||
*
|
|
||||||
* @param frame: The struct to hold the frame information in.
|
|
||||||
* msg: store the parsed frame in that struct
|
|
||||||
*
|
*
|
||||||
* @return See APIError
|
* @return See APIError
|
||||||
*
|
*
|
||||||
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||||
*/
|
*/
|
||||||
APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
APIError APIPlaintextFrameHelper::try_read_frame_() {
|
||||||
if (frame == nullptr) {
|
|
||||||
HELPER_LOG("Bad argument for try_read_frame_");
|
|
||||||
return APIError::BAD_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read header
|
// read header
|
||||||
while (!rx_header_parsed_) {
|
while (!rx_header_parsed_) {
|
||||||
// Now that we know when the socket is ready, we can read up to 3 bytes
|
// Now that we know when the socket is ready, we can read up to 3 bytes
|
||||||
@@ -118,10 +115,10 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
|
if (msg_size_varint->as_uint32() > MAX_MESSAGE_SIZE) {
|
||||||
state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
|
HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
|
||||||
std::numeric_limits<uint16_t>::max());
|
MAX_MESSAGE_SIZE);
|
||||||
return APIError::BAD_DATA_PACKET;
|
return APIError::BAD_DATA_PACKET;
|
||||||
}
|
}
|
||||||
rx_header_parsed_len_ = msg_size_varint->as_uint16();
|
rx_header_parsed_len_ = msg_size_varint->as_uint16();
|
||||||
@@ -145,9 +142,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
}
|
}
|
||||||
// header reading done
|
// header reading done
|
||||||
|
|
||||||
// reserve space for body
|
// Reserve space for body
|
||||||
if (rx_buf_.size() != rx_header_parsed_len_) {
|
if (this->rx_buf_.size() != this->rx_header_parsed_len_) {
|
||||||
rx_buf_.resize(rx_header_parsed_len_);
|
this->rx_buf_.resize(this->rx_header_parsed_len_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rx_buf_len_ < rx_header_parsed_len_) {
|
if (rx_buf_len_ < rx_header_parsed_len_) {
|
||||||
@@ -165,24 +162,22 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_PACKET_RECEIVED(rx_buf_);
|
LOG_PACKET_RECEIVED(this->rx_buf_);
|
||||||
*frame = std::move(rx_buf_);
|
|
||||||
// consume msg
|
// Clear state for next frame (rx_buf_ still contains data for caller)
|
||||||
rx_buf_ = {};
|
this->rx_buf_len_ = 0;
|
||||||
rx_buf_len_ = 0;
|
this->rx_header_buf_pos_ = 0;
|
||||||
rx_header_buf_pos_ = 0;
|
this->rx_header_parsed_ = false;
|
||||||
rx_header_parsed_ = false;
|
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|
||||||
APIError aerr;
|
|
||||||
|
|
||||||
if (state_ != State::DATA) {
|
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
|
if (this->state_ != State::DATA) {
|
||||||
return APIError::WOULD_BLOCK;
|
return APIError::WOULD_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> frame;
|
APIError aerr = this->try_read_frame_();
|
||||||
aerr = try_read_frame_(&frame);
|
|
||||||
if (aerr != APIError::OK) {
|
if (aerr != APIError::OK) {
|
||||||
if (aerr == APIError::BAD_INDICATOR) {
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
// Make sure to tell the remote that we don't
|
// Make sure to tell the remote that we don't
|
||||||
@@ -197,19 +192,28 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
// We must send at least 3 bytes to be read, so we add
|
// We must send at least 3 bytes to be read, so we add
|
||||||
// a message after the indicator byte to ensures its long
|
// a message after the indicator byte to ensures its long
|
||||||
// enough and can aid in debugging.
|
// enough and can aid in debugging.
|
||||||
const char msg[] = "\x00"
|
static constexpr uint8_t INDICATOR_MSG_SIZE = 19;
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
static const char MSG_PROGMEM[] PROGMEM = "\x00"
|
||||||
"Bad indicator byte";
|
"Bad indicator byte";
|
||||||
|
char msg[INDICATOR_MSG_SIZE];
|
||||||
|
memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
|
||||||
iov[0].iov_base = (void *) msg;
|
iov[0].iov_base = (void *) msg;
|
||||||
iov[0].iov_len = 19;
|
#else
|
||||||
this->write_raw_(iov, 1, 19);
|
static const char MSG[] = "\x00"
|
||||||
|
"Bad indicator byte";
|
||||||
|
iov[0].iov_base = (void *) MSG;
|
||||||
|
#endif
|
||||||
|
iov[0].iov_len = INDICATOR_MSG_SIZE;
|
||||||
|
this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
|
||||||
}
|
}
|
||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer->container = std::move(frame);
|
buffer->container = std::move(this->rx_buf_);
|
||||||
buffer->data_offset = 0;
|
buffer->data_offset = 0;
|
||||||
buffer->data_len = rx_header_parsed_len_;
|
buffer->data_len = this->rx_header_parsed_len_;
|
||||||
buffer->type = rx_header_parsed_type_;
|
buffer->type = this->rx_header_parsed_type_;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
||||||
: APIFrameHelper(std::move(socket), client_info) {
|
: APIFrameHelper(std::move(socket), client_info) {
|
||||||
@@ -22,12 +22,9 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
|||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
|
||||||
// Get the frame footer size required by this protocol
|
|
||||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
APIError try_read_frame_();
|
||||||
|
|
||||||
// Group 2-byte aligned types
|
// Group 2-byte aligned types
|
||||||
uint16_t rx_header_parsed_type_ = 0;
|
uint16_t rx_header_parsed_type_ = 0;
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ extend google.protobuf.FieldOptions {
|
|||||||
optional bool no_zero_copy = 50008 [default=false];
|
optional bool no_zero_copy = 50008 [default=false];
|
||||||
optional bool fixed_array_skip_zero = 50009 [default=false];
|
optional bool fixed_array_skip_zero = 50009 [default=false];
|
||||||
optional string fixed_array_size_define = 50010;
|
optional string fixed_array_size_define = 50010;
|
||||||
|
optional string fixed_array_with_length_define = 50011;
|
||||||
|
|
||||||
|
// pointer_to_buffer: Use pointer instead of array for fixed-size byte fields
|
||||||
|
// When set, the field will be declared as a pointer (const uint8_t *data)
|
||||||
|
// instead of an array (uint8_t data[N]). This allows zero-copy on decode
|
||||||
|
// by pointing directly to the protobuf buffer. The buffer must remain valid
|
||||||
|
// until the message is processed (which is guaranteed for stack-allocated messages).
|
||||||
|
optional bool pointer_to_buffer = 50012 [default=false];
|
||||||
|
|
||||||
// container_pointer: Zero-copy optimization for repeated fields.
|
// container_pointer: Zero-copy optimization for repeated fields.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||||||
}
|
}
|
||||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case 1:
|
case 1: {
|
||||||
this->client_info = value.as_string();
|
// Use raw data directly to avoid allocation
|
||||||
|
this->client_info = value.data();
|
||||||
|
this->client_info_len = value.size();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -42,18 +45,23 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
|
|||||||
size.add_length(1, this->server_info_ref_.size());
|
size.add_length(1, this->server_info_ref_.size());
|
||||||
size.add_length(1, this->name_ref_.size());
|
size.add_length(1, this->name_ref_.size());
|
||||||
}
|
}
|
||||||
bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
#ifdef USE_API_PASSWORD
|
||||||
|
bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case 1:
|
case 1: {
|
||||||
this->password = value.as_string();
|
// Use raw data directly to avoid allocation
|
||||||
|
this->password = value.data();
|
||||||
|
this->password_len = value.size();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
|
void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
|
||||||
void ConnectResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); }
|
void AuthenticationResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); }
|
||||||
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
|
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_uint32(1, this->area_id);
|
buffer.encode_uint32(1, this->area_id);
|
||||||
@@ -127,6 +135,12 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
buffer.encode_message(22, this->area);
|
buffer.encode_message(22, this->area);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
buffer.encode_uint32(23, this->zwave_proxy_feature_flags);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
buffer.encode_uint32(24, this->zwave_home_id);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
|
void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
@@ -179,6 +193,12 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
|
|||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
size.add_message_object(2, this->area);
|
size.add_message_object(2, this->area);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
size.add_uint32(2, this->zwave_proxy_feature_flags);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
size.add_uint32(2, this->zwave_home_id);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
@@ -852,7 +872,7 @@ void HomeassistantServiceMap::calculate_size(ProtoSize &size) const {
|
|||||||
size.add_length(1, this->key_ref_.size());
|
size.add_length(1, this->key_ref_.size());
|
||||||
size.add_length(1, this->value.size());
|
size.add_length(1, this->value.size());
|
||||||
}
|
}
|
||||||
void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
|
void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_string(1, this->service_ref_);
|
buffer.encode_string(1, this->service_ref_);
|
||||||
for (auto &it : this->data) {
|
for (auto &it : this->data) {
|
||||||
buffer.encode_message(2, it, true);
|
buffer.encode_message(2, it, true);
|
||||||
@@ -864,13 +884,64 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_message(4, it, true);
|
buffer.encode_message(4, it, true);
|
||||||
}
|
}
|
||||||
buffer.encode_bool(5, this->is_event);
|
buffer.encode_bool(5, this->is_event);
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
buffer.encode_uint32(6, this->call_id);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
buffer.encode_bool(7, this->wants_response);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
buffer.encode_string(8, this->response_template);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
void HomeassistantServiceResponse::calculate_size(ProtoSize &size) const {
|
void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
|
||||||
size.add_length(1, this->service_ref_.size());
|
size.add_length(1, this->service_ref_.size());
|
||||||
size.add_repeated_message(1, this->data);
|
size.add_repeated_message(1, this->data);
|
||||||
size.add_repeated_message(1, this->data_template);
|
size.add_repeated_message(1, this->data_template);
|
||||||
size.add_repeated_message(1, this->variables);
|
size.add_repeated_message(1, this->variables);
|
||||||
size.add_bool(1, this->is_event);
|
size.add_bool(1, this->is_event);
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
size.add_uint32(1, this->call_id);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
size.add_bool(1, this->wants_response);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
size.add_length(1, this->response_template.size());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1:
|
||||||
|
this->call_id = value.as_uint32();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this->success = value.as_bool();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 3:
|
||||||
|
this->error_message = value.as_string();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
@@ -901,6 +972,19 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
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();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -911,8 +995,6 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
|
|
||||||
void GetTimeResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->epoch_seconds); }
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
|
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_string(1, this->name_ref_);
|
buffer.encode_string(1, this->name_ref_);
|
||||||
@@ -1843,12 +1925,14 @@ void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const {
|
|||||||
size.add_length(1, this->data_len);
|
size.add_length(1, this->data_len);
|
||||||
}
|
}
|
||||||
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
|
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
for (auto &it : this->advertisements) {
|
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||||
buffer.encode_message(1, it, true);
|
buffer.encode_message(1, this->advertisements[i], true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const {
|
void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const {
|
||||||
size.add_repeated_message(1, this->advertisements);
|
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||||
|
size.add_message_object_force(1, this->advertisements[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
@@ -2004,9 +2088,12 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val
|
|||||||
}
|
}
|
||||||
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case 4:
|
case 4: {
|
||||||
this->data = value.as_string();
|
// Use raw data directly to avoid allocation
|
||||||
|
this->data = value.data();
|
||||||
|
this->data_len = value.size();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2040,9 +2127,12 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto
|
|||||||
}
|
}
|
||||||
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case 3:
|
case 3: {
|
||||||
this->data = value.as_string();
|
// Use raw data directly to avoid allocation
|
||||||
|
this->data = value.data();
|
||||||
|
this->data_len = value.size();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2151,10 +2241,12 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
|
|||||||
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
|
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
|
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
|
||||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
||||||
|
buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
|
||||||
}
|
}
|
||||||
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
|
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->state));
|
size.add_uint32(1, static_cast<uint32_t>(this->state));
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->mode));
|
size.add_uint32(1, static_cast<uint32_t>(this->mode));
|
||||||
|
size.add_uint32(1, static_cast<uint32_t>(this->configured_mode));
|
||||||
}
|
}
|
||||||
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
@@ -2356,6 +2448,52 @@ void VoiceAssistantWakeWord::calculate_size(ProtoSize &size) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 5:
|
||||||
|
this->model_size = value.as_uint32();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1:
|
||||||
|
this->id = value.as_string();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this->wake_word = value.as_string();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this->trained_languages.push_back(value.as_string());
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this->model_type = value.as_string();
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
this->model_hash = value.as_string();
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
this->url = value.as_string();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1:
|
||||||
|
this->external_wake_words.emplace_back();
|
||||||
|
value.decode_to_message(this->external_wake_words.back());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
|
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
for (auto &it : this->available_wake_words) {
|
for (auto &it : this->available_wake_words) {
|
||||||
buffer.encode_message(1, it, true);
|
buffer.encode_message(1, it, true);
|
||||||
@@ -2999,5 +3137,53 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void ZWaveProxyFrame::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(1, this->data, this->data_len); }
|
||||||
|
void ZWaveProxyFrame::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_len); }
|
||||||
|
bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 1:
|
||||||
|
this->type = static_cast<enums::ZWaveProxyRequestType>(value.as_uint32());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
|
||||||
|
buffer.encode_uint32(1, static_cast<uint32_t>(this->type));
|
||||||
|
buffer.encode_bytes(2, this->data, this->data_len);
|
||||||
|
}
|
||||||
|
void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
|
||||||
|
size.add_uint32(1, static_cast<uint32_t>(this->type));
|
||||||
|
size.add_length(2, this->data_len);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -655,10 +655,26 @@ template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums::ZWaveProxyRequestType value) {
|
||||||
|
switch (value) {
|
||||||
|
case enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE:
|
||||||
|
return "ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE";
|
||||||
|
case enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
|
||||||
|
return "ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE";
|
||||||
|
case enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE:
|
||||||
|
return "ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void HelloRequest::dump_to(std::string &out) const {
|
void HelloRequest::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "HelloRequest");
|
MessageDumpHelper helper(out, "HelloRequest");
|
||||||
dump_field(out, "client_info", this->client_info);
|
out.append(" client_info: ");
|
||||||
|
out.append(format_hex_pretty(this->client_info, this->client_info_len));
|
||||||
|
out.append("\n");
|
||||||
dump_field(out, "api_version_major", this->api_version_major);
|
dump_field(out, "api_version_major", this->api_version_major);
|
||||||
dump_field(out, "api_version_minor", this->api_version_minor);
|
dump_field(out, "api_version_minor", this->api_version_minor);
|
||||||
}
|
}
|
||||||
@@ -669,8 +685,18 @@ void HelloResponse::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "server_info", this->server_info_ref_);
|
dump_field(out, "server_info", this->server_info_ref_);
|
||||||
dump_field(out, "name", this->name_ref_);
|
dump_field(out, "name", this->name_ref_);
|
||||||
}
|
}
|
||||||
void ConnectRequest::dump_to(std::string &out) const { dump_field(out, "password", this->password); }
|
#ifdef USE_API_PASSWORD
|
||||||
void ConnectResponse::dump_to(std::string &out) const { dump_field(out, "invalid_password", this->invalid_password); }
|
void AuthenticationRequest::dump_to(std::string &out) const {
|
||||||
|
MessageDumpHelper helper(out, "AuthenticationRequest");
|
||||||
|
out.append(" password: ");
|
||||||
|
out.append(format_hex_pretty(this->password, this->password_len));
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
|
void AuthenticationResponse::dump_to(std::string &out) const {
|
||||||
|
MessageDumpHelper helper(out, "AuthenticationResponse");
|
||||||
|
dump_field(out, "invalid_password", this->invalid_password);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
|
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
|
||||||
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
|
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
|
||||||
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
|
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
|
||||||
@@ -749,6 +775,12 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
|||||||
this->area.dump_to(out);
|
this->area.dump_to(out);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
dump_field(out, "zwave_proxy_feature_flags", this->zwave_proxy_feature_flags);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
dump_field(out, "zwave_home_id", this->zwave_home_id);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
|
void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
|
||||||
void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
|
void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
|
||||||
@@ -1071,8 +1103,8 @@ void HomeassistantServiceMap::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "key", this->key_ref_);
|
dump_field(out, "key", this->key_ref_);
|
||||||
dump_field(out, "value", this->value);
|
dump_field(out, "value", this->value);
|
||||||
}
|
}
|
||||||
void HomeassistantServiceResponse::dump_to(std::string &out) const {
|
void HomeassistantActionRequest::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "HomeassistantServiceResponse");
|
MessageDumpHelper helper(out, "HomeassistantActionRequest");
|
||||||
dump_field(out, "service", this->service_ref_);
|
dump_field(out, "service", this->service_ref_);
|
||||||
for (const auto &it : this->data) {
|
for (const auto &it : this->data) {
|
||||||
out.append(" data: ");
|
out.append(" data: ");
|
||||||
@@ -1090,6 +1122,28 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const {
|
|||||||
out.append("\n");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
dump_field(out, "is_event", this->is_event);
|
dump_field(out, "is_event", this->is_event);
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
dump_field(out, "call_id", this->call_id);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
dump_field(out, "wants_response", this->wants_response);
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
dump_field(out, "response_template", this->response_template);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
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);
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
out.append(" response_data: ");
|
||||||
|
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
||||||
|
out.append("\n");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
@@ -1110,7 +1164,13 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
|
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
|
||||||
void GetTimeResponse::dump_to(std::string &out) const { dump_field(out, "epoch_seconds", this->epoch_seconds); }
|
void GetTimeResponse::dump_to(std::string &out) const {
|
||||||
|
MessageDumpHelper helper(out, "GetTimeResponse");
|
||||||
|
dump_field(out, "epoch_seconds", this->epoch_seconds);
|
||||||
|
out.append(" timezone: ");
|
||||||
|
out.append(format_hex_pretty(this->timezone, this->timezone_len));
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
|
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
|
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
|
||||||
@@ -1135,7 +1195,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "string_", this->string_);
|
dump_field(out, "string_", this->string_);
|
||||||
dump_field(out, "int_", this->int_);
|
dump_field(out, "int_", this->int_);
|
||||||
for (const auto it : this->bool_array) {
|
for (const auto it : this->bool_array) {
|
||||||
dump_field(out, "bool_array", it, 4);
|
dump_field(out, "bool_array", static_cast<bool>(it), 4);
|
||||||
}
|
}
|
||||||
for (const auto &it : this->int_array) {
|
for (const auto &it : this->int_array) {
|
||||||
dump_field(out, "int_array", it, 4);
|
dump_field(out, "int_array", it, 4);
|
||||||
@@ -1534,9 +1594,9 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
|
|||||||
}
|
}
|
||||||
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
|
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse");
|
MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse");
|
||||||
for (const auto &it : this->advertisements) {
|
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||||
out.append(" advertisements: ");
|
out.append(" advertisements: ");
|
||||||
it.dump_to(out);
|
this->advertisements[i].dump_to(out);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1622,7 +1682,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "handle", this->handle);
|
dump_field(out, "handle", this->handle);
|
||||||
dump_field(out, "response", this->response);
|
dump_field(out, "response", this->response);
|
||||||
out.append(" data: ");
|
out.append(" data: ");
|
||||||
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");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
|
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
|
||||||
@@ -1635,7 +1695,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "address", this->address);
|
dump_field(out, "address", this->address);
|
||||||
dump_field(out, "handle", this->handle);
|
dump_field(out, "handle", this->handle);
|
||||||
out.append(" data: ");
|
out.append(" data: ");
|
||||||
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");
|
out.append("\n");
|
||||||
}
|
}
|
||||||
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
|
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
|
||||||
@@ -1704,6 +1764,7 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
|
|||||||
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
|
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
|
||||||
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
|
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
|
||||||
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
|
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
|
||||||
|
dump_field(out, "configured_mode", static_cast<enums::BluetoothScannerMode>(this->configured_mode));
|
||||||
}
|
}
|
||||||
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
|
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
|
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
|
||||||
@@ -1787,8 +1848,25 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "trained_languages", it, 4);
|
dump_field(out, "trained_languages", it, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
for (const auto &it : this->trained_languages) {
|
||||||
|
dump_field(out, "trained_languages", it, 4);
|
||||||
|
}
|
||||||
|
dump_field(out, "model_type", this->model_type);
|
||||||
|
dump_field(out, "model_size", this->model_size);
|
||||||
|
dump_field(out, "model_hash", this->model_hash);
|
||||||
|
dump_field(out, "url", this->url);
|
||||||
|
}
|
||||||
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
|
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
|
||||||
out.append("VoiceAssistantConfigurationRequest {}");
|
MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest");
|
||||||
|
for (const auto &it : this->external_wake_words) {
|
||||||
|
out.append(" external_wake_words: ");
|
||||||
|
it.dump_to(out);
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
|
void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "VoiceAssistantConfigurationResponse");
|
MessageDumpHelper helper(out, "VoiceAssistantConfigurationResponse");
|
||||||
@@ -2097,6 +2175,21 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void ZWaveProxyFrame::dump_to(std::string &out) const {
|
||||||
|
MessageDumpHelper helper(out, "ZWaveProxyFrame");
|
||||||
|
out.append(" data: ");
|
||||||
|
out.append(format_hex_pretty(this->data, this->data_len));
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
|
void ZWaveProxyRequest::dump_to(std::string &out) const {
|
||||||
|
MessageDumpHelper helper(out, "ZWaveProxyRequest");
|
||||||
|
dump_field(out, "type", static_cast<enums::ZWaveProxyRequestType>(this->type));
|
||||||
|
out.append(" data: ");
|
||||||
|
out.append(format_hex_pretty(this->data, this->data_len));
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|
||||||
|
|||||||
@@ -24,15 +24,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_hello_request(msg);
|
this->on_hello_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ConnectRequest::MESSAGE_TYPE: {
|
#ifdef USE_API_PASSWORD
|
||||||
ConnectRequest msg;
|
case AuthenticationRequest::MESSAGE_TYPE: {
|
||||||
|
AuthenticationRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str());
|
||||||
#endif
|
#endif
|
||||||
this->on_connect_request(msg);
|
this->on_authentication_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
case DisconnectRequest::MESSAGE_TYPE: {
|
case DisconnectRequest::MESSAGE_TYPE: {
|
||||||
DisconnectRequest msg;
|
DisconnectRequest msg;
|
||||||
// Empty message: no decode needed
|
// Empty message: no decode needed
|
||||||
@@ -160,15 +162,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
case GetTimeRequest::MESSAGE_TYPE: {
|
|
||||||
GetTimeRequest msg;
|
|
||||||
// Empty message: no decode needed
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
|
|
||||||
#endif
|
|
||||||
this->on_get_time_request(msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GetTimeResponse::MESSAGE_TYPE: {
|
case GetTimeResponse::MESSAGE_TYPE: {
|
||||||
GetTimeResponse msg;
|
GetTimeResponse msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
@@ -555,7 +548,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: {
|
case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: {
|
||||||
VoiceAssistantConfigurationRequest msg;
|
VoiceAssistantConfigurationRequest msg;
|
||||||
// Empty message: no decode needed
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
|
||||||
#endif
|
#endif
|
||||||
@@ -595,6 +588,39 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_bluetooth_scanner_set_mode_request(msg);
|
this->on_bluetooth_scanner_set_mode_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
case ZWaveProxyFrame::MESSAGE_TYPE: {
|
||||||
|
ZWaveProxyFrame msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
this->on_z_wave_proxy_frame(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
case ZWaveProxyRequest::MESSAGE_TYPE: {
|
||||||
|
ZWaveProxyRequest msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
this->on_z_wave_proxy_request(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
case HomeassistantActionResponse::MESSAGE_TYPE: {
|
||||||
|
HomeassistantActionResponse msg;
|
||||||
|
msg.decode(msg_data, msg_size);
|
||||||
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
ESP_LOGVV(TAG, "on_homeassistant_action_response: %s", msg.dump().c_str());
|
||||||
|
#endif
|
||||||
|
this->on_homeassistant_action_response(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -606,11 +632,13 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) {
|
|||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
|
#ifdef USE_API_PASSWORD
|
||||||
if (!this->send_connect_response(msg)) {
|
void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) {
|
||||||
|
if (!this->send_authenticate_response(msg)) {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
||||||
if (!this->send_disconnect_response(msg)) {
|
if (!this->send_disconnect_response(msg)) {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
@@ -622,246 +650,139 @@ void APIServerConnection::on_ping_request(const PingRequest &msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
|
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
|
||||||
if (this->check_connection_setup_() && !this->send_device_info_response(msg)) {
|
if (!this->send_device_info_response(msg)) {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
|
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { this->list_entities(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->list_entities(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
|
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->subscribe_states(msg);
|
this->subscribe_states(msg);
|
||||||
}
|
|
||||||
}
|
|
||||||
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
|
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->subscribe_logs(msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->subscribe_logs(msg); }
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void APIServerConnection::on_subscribe_homeassistant_services_request(
|
void APIServerConnection::on_subscribe_homeassistant_services_request(
|
||||||
const SubscribeHomeassistantServicesRequest &msg) {
|
const SubscribeHomeassistantServicesRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->subscribe_homeassistant_services(msg);
|
this->subscribe_homeassistant_services(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
|
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->subscribe_home_assistant_states(msg);
|
this->subscribe_home_assistant_states(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
|
||||||
if (this->check_connection_setup_() && !this->send_get_time_response(msg)) {
|
|
||||||
this->on_fatal_error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
|
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->execute_service(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
|
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
|
||||||
if (this->check_authenticated_() && !this->send_noise_encryption_set_key_response(msg)) {
|
if (!this->send_noise_encryption_set_key_response(msg)) {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BUTTON
|
#ifdef USE_BUTTON
|
||||||
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
|
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { this->button_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->button_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
|
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { this->camera_image(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->camera_image(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
|
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { this->climate_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->climate_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
|
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { this->cover_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->cover_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
|
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { this->date_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->date_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
|
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->datetime_command(msg);
|
this->datetime_command(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
|
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { this->fan_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->fan_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
|
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { this->light_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->light_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
|
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { this->lock_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->lock_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
|
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->media_player_command(msg);
|
this->media_player_command(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) {
|
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { this->number_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->number_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
|
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { this->select_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->select_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SIREN
|
#ifdef USE_SIREN
|
||||||
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) {
|
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { this->siren_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->siren_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
|
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { this->switch_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->switch_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
|
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { this->text_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->text_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) {
|
void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { this->time_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->time_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
|
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { this->update_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->update_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VALVE
|
#ifdef USE_VALVE
|
||||||
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
|
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); }
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->valve_command(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->subscribe_bluetooth_le_advertisements(msg);
|
this->subscribe_bluetooth_le_advertisements(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->bluetooth_device_request(msg);
|
this->bluetooth_device_request(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
|
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->bluetooth_gatt_get_services(msg);
|
this->bluetooth_gatt_get_services(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
|
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->bluetooth_gatt_read(msg);
|
this->bluetooth_gatt_read(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
|
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->bluetooth_gatt_write(msg);
|
this->bluetooth_gatt_write(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
|
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->bluetooth_gatt_read_descriptor(msg);
|
this->bluetooth_gatt_read_descriptor(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
|
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->bluetooth_gatt_write_descriptor(msg);
|
this->bluetooth_gatt_write_descriptor(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
|
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->bluetooth_gatt_notify(msg);
|
this->bluetooth_gatt_notify(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
|
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
|
||||||
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
||||||
if (this->check_authenticated_() && !this->send_subscribe_bluetooth_connections_free_response(msg)) {
|
if (!this->send_subscribe_bluetooth_connections_free_response(msg)) {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -869,45 +790,68 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
|
|||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
|
void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
|
||||||
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
|
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->unsubscribe_bluetooth_le_advertisements(msg);
|
this->unsubscribe_bluetooth_le_advertisements(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
|
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->bluetooth_scanner_set_mode(msg);
|
this->bluetooth_scanner_set_mode(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
|
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->subscribe_voice_assistant(msg);
|
this->subscribe_voice_assistant(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
|
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
|
||||||
if (this->check_authenticated_() && !this->send_voice_assistant_get_configuration_response(msg)) {
|
if (!this->send_voice_assistant_get_configuration_response(msg)) {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
|
void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->voice_assistant_set_configuration(msg);
|
this->voice_assistant_set_configuration(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
|
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
|
||||||
this->alarm_control_panel_command(msg);
|
this->alarm_control_panel_command(msg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { this->zwave_proxy_frame(msg); }
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||||
|
// Check authentication/connection requirements for messages
|
||||||
|
switch (msg_type) {
|
||||||
|
case HelloRequest::MESSAGE_TYPE: // No setup required
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
|
case AuthenticationRequest::MESSAGE_TYPE: // No setup required
|
||||||
|
#endif
|
||||||
|
case DisconnectRequest::MESSAGE_TYPE: // No setup required
|
||||||
|
case PingRequest::MESSAGE_TYPE: // No setup required
|
||||||
|
break; // Skip all checks for these messages
|
||||||
|
case DeviceInfoRequest::MESSAGE_TYPE: // Connection setup only
|
||||||
|
if (!this->check_connection_setup_()) {
|
||||||
|
return; // Connection not setup
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// All other messages require authentication (which includes connection check)
|
||||||
|
if (!this->check_authenticated_()) {
|
||||||
|
return; // Authentication failed
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call base implementation to process the message
|
||||||
|
APIServerConnectionBase::read_message(msg_size, msg_type, msg_data);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
|
|
||||||
virtual void on_hello_request(const HelloRequest &value){};
|
virtual void on_hello_request(const HelloRequest &value){};
|
||||||
|
|
||||||
virtual void on_connect_request(const ConnectRequest &value){};
|
#ifdef USE_API_PASSWORD
|
||||||
|
virtual void on_authentication_request(const AuthenticationRequest &value){};
|
||||||
|
#endif
|
||||||
|
|
||||||
virtual void on_disconnect_request(const DisconnectRequest &value){};
|
virtual void on_disconnect_request(const DisconnectRequest &value){};
|
||||||
virtual void on_disconnect_response(const DisconnectResponse &value){};
|
virtual void on_disconnect_response(const DisconnectResponse &value){};
|
||||||
@@ -64,6 +66,9 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||||
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
@@ -71,7 +76,7 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||||
#endif
|
#endif
|
||||||
virtual void on_get_time_request(const GetTimeRequest &value){};
|
|
||||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||||
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
@@ -205,6 +210,12 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
|
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
virtual void on_update_command_request(const UpdateCommandRequest &value){};
|
virtual void on_update_command_request(const UpdateCommandRequest &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
protected:
|
protected:
|
||||||
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||||
@@ -213,7 +224,9 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
class APIServerConnection : public APIServerConnectionBase {
|
class APIServerConnection : public APIServerConnectionBase {
|
||||||
public:
|
public:
|
||||||
virtual bool send_hello_response(const HelloRequest &msg) = 0;
|
virtual bool send_hello_response(const HelloRequest &msg) = 0;
|
||||||
virtual bool send_connect_response(const ConnectRequest &msg) = 0;
|
#ifdef USE_API_PASSWORD
|
||||||
|
virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
|
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
|
||||||
virtual bool send_ping_response(const PingRequest &msg) = 0;
|
virtual bool send_ping_response(const PingRequest &msg) = 0;
|
||||||
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
|
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
|
||||||
@@ -226,7 +239,6 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
virtual bool send_get_time_response(const GetTimeRequest &msg) = 0;
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -332,10 +344,18 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
|
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
protected:
|
protected:
|
||||||
void on_hello_request(const HelloRequest &msg) override;
|
void on_hello_request(const HelloRequest &msg) override;
|
||||||
void on_connect_request(const ConnectRequest &msg) override;
|
#ifdef USE_API_PASSWORD
|
||||||
|
void on_authentication_request(const AuthenticationRequest &msg) override;
|
||||||
|
#endif
|
||||||
void on_disconnect_request(const DisconnectRequest &msg) override;
|
void on_disconnect_request(const DisconnectRequest &msg) override;
|
||||||
void on_ping_request(const PingRequest &msg) override;
|
void on_ping_request(const PingRequest &msg) override;
|
||||||
void on_device_info_request(const DeviceInfoRequest &msg) override;
|
void on_device_info_request(const DeviceInfoRequest &msg) override;
|
||||||
@@ -348,7 +368,6 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
@@ -455,6 +474,13 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
|
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|||||||
@@ -9,12 +9,16 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
#include "esphome/core/version.h"
|
#include "esphome/core/version.h"
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
#include "homeassistant_service.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
#include "esphome/components/logger/logger.h"
|
#include "esphome/components/logger/logger.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
@@ -37,12 +41,14 @@ void APIServer::setup() {
|
|||||||
|
|
||||||
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
|
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
|
||||||
|
|
||||||
|
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||||
|
// Only load saved PSK if not set from YAML
|
||||||
SavedNoisePsk noise_pref_saved{};
|
SavedNoisePsk noise_pref_saved{};
|
||||||
if (this->noise_pref_.load(&noise_pref_saved)) {
|
if (this->noise_pref_.load(&noise_pref_saved)) {
|
||||||
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
||||||
|
|
||||||
this->set_noise_psk(noise_pref_saved.psk);
|
this->set_noise_psk(noise_pref_saved.psk);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Schedule reboot if no clients connect within timeout
|
// Schedule reboot if no clients connect within timeout
|
||||||
@@ -85,7 +91,7 @@ void APIServer::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = this->socket_->listen(4);
|
err = this->socket_->listen(this->listen_backlog_);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
@@ -138,9 +144,19 @@ void APIServer::loop() {
|
|||||||
while (true) {
|
while (true) {
|
||||||
struct sockaddr_storage source_addr;
|
struct sockaddr_storage source_addr;
|
||||||
socklen_t addr_len = sizeof(source_addr);
|
socklen_t addr_len = sizeof(source_addr);
|
||||||
|
|
||||||
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
||||||
if (!sock)
|
if (!sock)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
// Immediately close - socket destructor will handle cleanup
|
||||||
|
sock.reset();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
||||||
|
|
||||||
auto *conn = new APIConnection(std::move(sock), this);
|
auto *conn = new APIConnection(std::move(sock), this);
|
||||||
@@ -165,7 +181,8 @@ void APIServer::loop() {
|
|||||||
// Network is down - disconnect all clients
|
// Network is down - disconnect all clients
|
||||||
for (auto &client : this->clients_) {
|
for (auto &client : this->clients_) {
|
||||||
client->on_fatal_error();
|
client->on_fatal_error();
|
||||||
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
|
ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
|
||||||
|
client->client_info_.peername.c_str());
|
||||||
}
|
}
|
||||||
// Continue to process and clean up the clients below
|
// Continue to process and clean up the clients below
|
||||||
}
|
}
|
||||||
@@ -204,8 +221,10 @@ void APIServer::loop() {
|
|||||||
void APIServer::dump_config() {
|
void APIServer::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
"Server:\n"
|
"Server:\n"
|
||||||
" Address: %s:%u",
|
" Address: %s:%u\n"
|
||||||
network::get_use_address().c_str(), this->port_);
|
" Listen backlog: %u\n"
|
||||||
|
" Max connections: %u",
|
||||||
|
network::get_use_address().c_str(), this->port_, this->listen_backlog_, this->max_connections_);
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
|
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
|
||||||
if (!this->noise_ctx_->has_psk()) {
|
if (!this->noise_ctx_->has_psk()) {
|
||||||
@@ -217,12 +236,12 @@ void APIServer::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
bool APIServer::check_password(const std::string &password) const {
|
bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
|
||||||
// depend only on input password length
|
// depend only on input password length
|
||||||
const char *a = this->password_.c_str();
|
const char *a = this->password_.c_str();
|
||||||
uint32_t len_a = this->password_.length();
|
uint32_t len_a = this->password_.length();
|
||||||
const char *b = password.c_str();
|
const char *b = reinterpret_cast<const char *>(password_data);
|
||||||
uint32_t len_b = password.length();
|
uint32_t len_b = password_len;
|
||||||
|
|
||||||
// disable optimization with volatile
|
// disable optimization with volatile
|
||||||
volatile uint32_t length = len_b;
|
volatile uint32_t length = len_b;
|
||||||
@@ -245,6 +264,7 @@ bool APIServer::check_password(const std::string &password) const {
|
|||||||
|
|
||||||
return result == 0;
|
return result == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||||
@@ -355,6 +375,15 @@ void APIServer::on_update(update::UpdateEntity *obj) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) {
|
||||||
|
// We could add code to manage a second subscription type, but, since this message type is
|
||||||
|
// very infrequent and small, we simply send it to all clients
|
||||||
|
for (auto &c : this->clients_)
|
||||||
|
c->send_message(msg, api::ZWaveProxyRequest::MESSAGE_TYPE);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
|
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
|
||||||
#endif
|
#endif
|
||||||
@@ -370,12 +399,43 @@ void APIServer::set_password(const std::string &password) { this->password_ = pa
|
|||||||
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
|
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call) {
|
||||||
for (auto &client : this->clients_) {
|
for (auto &client : this->clients_) {
|
||||||
client->send_homeassistant_service_call(call);
|
client->send_homeassistant_action(call);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
void APIServer::register_action_response_callback(uint32_t call_id, ActionResponseCallback callback) {
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
this->action_response_callbacks_.erase(it);
|
||||||
|
ActionResponse response(success, error_message);
|
||||||
|
callback(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &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) {
|
||||||
|
auto callback = std::move(it->callback);
|
||||||
|
this->action_response_callbacks_.erase(it);
|
||||||
|
ActionResponse response(success, error_message, response_data, response_data_len);
|
||||||
|
callback(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
@@ -409,6 +469,12 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
|
|||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||||
|
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||||
|
// When PSK is set from YAML, this function should never be called
|
||||||
|
// but if it is, reject the change
|
||||||
|
ESP_LOGW(TAG, "Key set in YAML");
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
auto &old_psk = this->noise_ctx_->get_psk();
|
auto &old_psk = this->noise_ctx_->get_psk();
|
||||||
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
|
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
|
||||||
ESP_LOGW(TAG, "New PSK matches old");
|
ESP_LOGW(TAG, "New PSK matches old");
|
||||||
@@ -437,6 +503,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
@@ -37,13 +38,15 @@ class APIServer : public Component, public Controller {
|
|||||||
void on_shutdown() override;
|
void on_shutdown() override;
|
||||||
bool teardown() override;
|
bool teardown() override;
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
bool check_password(const std::string &password) const;
|
bool check_password(const uint8_t *password_data, size_t password_len) const;
|
||||||
void set_password(const std::string &password);
|
void set_password(const std::string &password);
|
||||||
#endif
|
#endif
|
||||||
void set_port(uint16_t port);
|
void set_port(uint16_t port);
|
||||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||||
void set_batch_delay(uint16_t batch_delay);
|
void set_batch_delay(uint16_t batch_delay);
|
||||||
uint16_t get_batch_delay() const { return batch_delay_; }
|
uint16_t get_batch_delay() const { return batch_delay_; }
|
||||||
|
void set_listen_backlog(uint8_t listen_backlog) { this->listen_backlog_ = listen_backlog; }
|
||||||
|
void set_max_connections(uint8_t max_connections) { this->max_connections_ = max_connections; }
|
||||||
|
|
||||||
// Get reference to shared buffer for API connections
|
// Get reference to shared buffer for API connections
|
||||||
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
|
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
|
||||||
@@ -107,8 +110,19 @@ class APIServer : public Component, public Controller {
|
|||||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||||
#endif
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
// 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);
|
||||||
|
#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);
|
||||||
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||||
#endif
|
#endif
|
||||||
@@ -125,6 +139,9 @@ class APIServer : public Component, public Controller {
|
|||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
void on_update(update::UpdateEntity *obj) override;
|
void on_update(update::UpdateEntity *obj) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_ZWAVE_PROXY
|
||||||
|
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
|
||||||
|
#endif
|
||||||
|
|
||||||
bool is_connected() const;
|
bool is_connected() const;
|
||||||
|
|
||||||
@@ -181,12 +198,23 @@ class APIServer : public Component, public Controller {
|
|||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
std::vector<UserServiceDescriptor *> user_services_;
|
std::vector<UserServiceDescriptor *> user_services_;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
struct PendingActionResponse {
|
||||||
|
uint32_t call_id;
|
||||||
|
ActionResponseCallback callback;
|
||||||
|
};
|
||||||
|
std::vector<PendingActionResponse> action_response_callbacks_;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Group smaller types together
|
// Group smaller types together
|
||||||
uint16_t port_{6053};
|
uint16_t port_{6053};
|
||||||
uint16_t batch_delay_{100};
|
uint16_t batch_delay_{100};
|
||||||
|
// Connection limits - these defaults will be overridden by config values
|
||||||
|
// from cv.SplitDefault in __init__.py which sets platform-specific defaults
|
||||||
|
uint8_t listen_backlog_{4};
|
||||||
|
uint8_t max_connections_{8};
|
||||||
bool shutting_down_ = false;
|
bool shutting_down_ = false;
|
||||||
// 5 bytes used, 3 bytes padding
|
// 7 bytes used, 1 byte padding
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||||
"""Run the logs command in the event loop."""
|
"""Run the logs command in the event loop."""
|
||||||
conf = config["api"]
|
conf = config["api"]
|
||||||
name = config["esphome"]["name"]
|
name = config["esphome"]["name"]
|
||||||
@@ -39,13 +39,21 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
|||||||
noise_psk: str | None = None
|
noise_psk: str | None = None
|
||||||
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
|
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
|
||||||
noise_psk = key
|
noise_psk = key
|
||||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
|
||||||
|
if len(addresses) == 1:
|
||||||
|
_LOGGER.info("Starting log output from %s using esphome API", addresses[0])
|
||||||
|
else:
|
||||||
|
_LOGGER.info(
|
||||||
|
"Starting log output from %s using esphome API", " or ".join(addresses)
|
||||||
|
)
|
||||||
|
|
||||||
cli = APIClient(
|
cli = APIClient(
|
||||||
address,
|
addresses[0], # Primary address for compatibility
|
||||||
port,
|
port,
|
||||||
password,
|
password,
|
||||||
client_info=f"ESPHome Logs {__version__}",
|
client_info=f"ESPHome Logs {__version__}",
|
||||||
noise_psk=noise_psk,
|
noise_psk=noise_psk,
|
||||||
|
addresses=addresses, # Pass all addresses for automatic retry
|
||||||
)
|
)
|
||||||
dashboard = CORE.dashboard
|
dashboard = CORE.dashboard
|
||||||
|
|
||||||
@@ -54,9 +62,11 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
|||||||
time_ = datetime.now()
|
time_ = datetime.now()
|
||||||
message: bytes = msg.message
|
message: bytes = msg.message
|
||||||
text = message.decode("utf8", "backslashreplace")
|
text = message.decode("utf8", "backslashreplace")
|
||||||
for parsed_msg in parse_log_message(
|
nanoseconds = time_.microsecond // 1000
|
||||||
text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]"
|
timestamp = (
|
||||||
):
|
f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
|
||||||
|
)
|
||||||
|
for parsed_msg in parse_log_message(text, timestamp):
|
||||||
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
|
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
|
||||||
|
|
||||||
stop = await async_run(cli, on_log, name=name)
|
stop = await async_run(cli, on_log, name=name)
|
||||||
@@ -66,7 +76,7 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
|||||||
await stop()
|
await stop()
|
||||||
|
|
||||||
|
|
||||||
def run_logs(config: dict[str, Any], address: str) -> None:
|
def run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||||
"""Run the logs command."""
|
"""Run the logs command."""
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
with contextlib.suppress(KeyboardInterrupt):
|
||||||
asyncio.run(async_run_logs(config, address))
|
asyncio.run(async_run_logs(config, addresses))
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ class CustomAPIDevice {
|
|||||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
||||||
global_api_server->register_user_service(service);
|
global_api_server->register_user_service(service);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
template<typename T, typename... Ts>
|
||||||
|
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||||
|
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||||
|
static_assert(
|
||||||
|
sizeof(T) == 0,
|
||||||
|
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Register a custom native API service that will show up in Home Assistant.
|
/** Register a custom native API service that will show up in Home Assistant.
|
||||||
@@ -81,6 +89,12 @@ class CustomAPIDevice {
|
|||||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
||||||
global_api_server->register_user_service(service);
|
global_api_server->register_user_service(service);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||||
|
static_assert(
|
||||||
|
sizeof(T) == 0,
|
||||||
|
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
@@ -135,6 +149,22 @@ class CustomAPIDevice {
|
|||||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
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);
|
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,
|
||||||
|
const std::string &attribute = "") {
|
||||||
|
static_assert(sizeof(T) == 0,
|
||||||
|
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
||||||
|
"of your YAML configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||||
|
const std::string &attribute = "") {
|
||||||
|
static_assert(sizeof(T) == 0,
|
||||||
|
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
||||||
|
"of your YAML configuration");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
@@ -149,9 +179,9 @@ class CustomAPIDevice {
|
|||||||
* @param service_name The service to call.
|
* @param service_name The service to call.
|
||||||
*/
|
*/
|
||||||
void call_homeassistant_service(const std::string &service_name) {
|
void call_homeassistant_service(const std::string &service_name) {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantActionRequest resp;
|
||||||
resp.set_service(StringRef(service_name));
|
resp.set_service(StringRef(service_name));
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_action(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Call a Home Assistant service from ESPHome.
|
/** Call a Home Assistant service from ESPHome.
|
||||||
@@ -169,7 +199,7 @@ class CustomAPIDevice {
|
|||||||
* @param data The data for the service call, mapping from string to string.
|
* @param data The data for the service call, mapping from string to string.
|
||||||
*/
|
*/
|
||||||
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantActionRequest resp;
|
||||||
resp.set_service(StringRef(service_name));
|
resp.set_service(StringRef(service_name));
|
||||||
for (auto &it : data) {
|
for (auto &it : data) {
|
||||||
resp.data.emplace_back();
|
resp.data.emplace_back();
|
||||||
@@ -177,7 +207,7 @@ class CustomAPIDevice {
|
|||||||
kv.set_key(StringRef(it.first));
|
kv.set_key(StringRef(it.first));
|
||||||
kv.value = it.second;
|
kv.value = it.second;
|
||||||
}
|
}
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_action(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fire an ESPHome event in Home Assistant.
|
/** Fire an ESPHome event in Home Assistant.
|
||||||
@@ -191,10 +221,10 @@ class CustomAPIDevice {
|
|||||||
* @param event_name The event to fire.
|
* @param event_name The event to fire.
|
||||||
*/
|
*/
|
||||||
void fire_homeassistant_event(const std::string &event_name) {
|
void fire_homeassistant_event(const std::string &event_name) {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantActionRequest resp;
|
||||||
resp.set_service(StringRef(event_name));
|
resp.set_service(StringRef(event_name));
|
||||||
resp.is_event = true;
|
resp.is_event = true;
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_action(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fire an ESPHome event in Home Assistant.
|
/** Fire an ESPHome event in Home Assistant.
|
||||||
@@ -211,7 +241,7 @@ class CustomAPIDevice {
|
|||||||
* @param data The data for the event, mapping from string to string.
|
* @param data The data for the event, mapping from string to string.
|
||||||
*/
|
*/
|
||||||
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantActionRequest resp;
|
||||||
resp.set_service(StringRef(service_name));
|
resp.set_service(StringRef(service_name));
|
||||||
resp.is_event = true;
|
resp.is_event = true;
|
||||||
for (auto &it : data) {
|
for (auto &it : data) {
|
||||||
@@ -220,7 +250,29 @@ class CustomAPIDevice {
|
|||||||
kv.set_key(StringRef(it.first));
|
kv.set_key(StringRef(it.first));
|
||||||
kv.value = it.second;
|
kv.value = it.second;
|
||||||
}
|
}
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_action(resp);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
template<typename T = void> void call_homeassistant_service(const std::string &service_name) {
|
||||||
|
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
|
||||||
|
"section of your YAML configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = void>
|
||||||
|
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
|
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
|
||||||
|
"section of your YAML configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = void> void fire_homeassistant_event(const std::string &event_name) {
|
||||||
|
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
|
||||||
|
"section of your YAML configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = void>
|
||||||
|
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
|
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
|
||||||
|
"section of your YAML configuration");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,15 @@
|
|||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
#include "esphome/components/json/json_util.h"
|
||||||
|
#endif
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
@@ -44,9 +49,47 @@ template<typename... Ts> class TemplatableKeyValuePair {
|
|||||||
TemplatableStringValue<Ts...> value;
|
TemplatableStringValue<Ts...> value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
// Represents the response data from a Home Assistant action
|
||||||
|
class ActionResponse {
|
||||||
|
public:
|
||||||
|
ActionResponse(bool success, std::string error_message = "")
|
||||||
|
: success_(success), error_message_(std::move(error_message)) {}
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
ActionResponse(bool success, std::string error_message, const uint8_t *data, size_t data_len)
|
||||||
|
: success_(success), error_message_(std::move(error_message)) {
|
||||||
|
if (data == nullptr || data_len == 0)
|
||||||
|
return;
|
||||||
|
this->json_document_ = json::parse_json(data, data_len);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool is_success() const { return this->success_; }
|
||||||
|
const std::string &get_error_message() const { return this->error_message_; }
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
// Get data as parsed JSON object (const version returns read-only view)
|
||||||
|
JsonObjectConst get_json() const { return this->json_document_.as<JsonObjectConst>(); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool success_;
|
||||||
|
std::string error_message_;
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
JsonDocument json_document_;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Callback type for action responses
|
||||||
|
template<typename... Ts> using ActionResponseCallback = std::function<void(const ActionResponse &, Ts...)>;
|
||||||
|
#endif
|
||||||
|
|
||||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent) {
|
||||||
|
this->flags_.is_event = is_event;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T> void set_service(T service) { this->service_ = service; }
|
template<typename T> void set_service(T service) { this->service_ = service; }
|
||||||
|
|
||||||
@@ -61,11 +104,29 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
this->variables_.emplace_back(std::move(key), value);
|
this->variables_.emplace_back(std::move(key), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
template<typename T> void set_response_template(T response_template) {
|
||||||
|
this->response_template_ = response_template;
|
||||||
|
this->flags_.has_response_template = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_wants_status() { this->flags_.wants_status = true; }
|
||||||
|
void set_wants_response() { this->flags_.wants_response = true; }
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() const {
|
||||||
|
return this->success_trigger_with_response_;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
Trigger<Ts...> *get_success_trigger() const { return this->success_trigger_; }
|
||||||
|
Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
|
||||||
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantActionRequest resp;
|
||||||
std::string service_value = this->service_.value(x...);
|
std::string service_value = this->service_.value(x...);
|
||||||
resp.set_service(StringRef(service_value));
|
resp.set_service(StringRef(service_value));
|
||||||
resp.is_event = this->is_event_;
|
resp.is_event = this->flags_.is_event;
|
||||||
for (auto &it : this->data_) {
|
for (auto &it : this->data_) {
|
||||||
resp.data.emplace_back();
|
resp.data.emplace_back();
|
||||||
auto &kv = resp.data.back();
|
auto &kv = resp.data.back();
|
||||||
@@ -84,18 +145,74 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
kv.set_key(StringRef(it.key));
|
kv.set_key(StringRef(it.key));
|
||||||
kv.value = it.value.value(x...);
|
kv.value = it.value.value(x...);
|
||||||
}
|
}
|
||||||
this->parent_->send_homeassistant_service_call(resp);
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
if (this->flags_.wants_status) {
|
||||||
|
// Generate a unique call ID for this service call
|
||||||
|
static uint32_t call_id_counter = 1;
|
||||||
|
uint32_t call_id = call_id_counter++;
|
||||||
|
resp.call_id = call_id;
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
if (this->flags_.wants_response) {
|
||||||
|
resp.wants_response = true;
|
||||||
|
// Set response template if provided
|
||||||
|
if (this->flags_.has_response_template) {
|
||||||
|
std::string response_template_value = this->response_template_.value(x...);
|
||||||
|
resp.response_template = response_template_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto captured_args = std::make_tuple(x...);
|
||||||
|
this->parent_->register_action_response_callback(call_id, [this, captured_args](const ActionResponse &response) {
|
||||||
|
std::apply(
|
||||||
|
[this, &response](auto &&...args) {
|
||||||
|
if (response.is_success()) {
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
if (this->flags_.wants_response) {
|
||||||
|
this->success_trigger_with_response_->trigger(response.get_json(), args...);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
this->success_trigger_->trigger(args...);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->error_trigger_->trigger(response.get_error_message(), args...);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
captured_args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->parent_->send_homeassistant_action(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIServer *parent_;
|
APIServer *parent_;
|
||||||
bool is_event_;
|
|
||||||
TemplatableStringValue<Ts...> service_{};
|
TemplatableStringValue<Ts...> service_{};
|
||||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
TemplatableStringValue<Ts...> response_template_{""};
|
||||||
|
Trigger<JsonObjectConst, Ts...> *success_trigger_with_response_ = new Trigger<JsonObjectConst, Ts...>();
|
||||||
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||||
|
Trigger<Ts...> *success_trigger_ = new Trigger<Ts...>();
|
||||||
|
Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>();
|
||||||
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
|
|
||||||
|
struct Flags {
|
||||||
|
uint8_t is_event : 1;
|
||||||
|
uint8_t wants_status : 1;
|
||||||
|
uint8_t wants_response : 1;
|
||||||
|
uint8_t has_response_template : 1;
|
||||||
|
uint8_t reserved : 5;
|
||||||
|
} flags_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,74 +8,70 @@ namespace esphome::api {
|
|||||||
static const char *const TAG = "api.proto";
|
static const char *const TAG = "api.proto";
|
||||||
|
|
||||||
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||||
uint32_t i = 0;
|
const uint8_t *ptr = buffer;
|
||||||
bool error = false;
|
const uint8_t *end = buffer + length;
|
||||||
while (i < length) {
|
|
||||||
|
while (ptr < end) {
|
||||||
uint32_t consumed;
|
uint32_t consumed;
|
||||||
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
|
||||||
|
// Parse field header
|
||||||
|
auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
|
ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t field_type = (res->as_uint32()) & 0b111;
|
uint32_t tag = res->as_uint32();
|
||||||
uint32_t field_id = (res->as_uint32()) >> 3;
|
uint32_t field_type = tag & 0b111;
|
||||||
i += consumed;
|
uint32_t field_id = tag >> 3;
|
||||||
|
ptr += consumed;
|
||||||
|
|
||||||
switch (field_type) {
|
switch (field_type) {
|
||||||
case 0: { // VarInt
|
case 0: { // VarInt
|
||||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
|
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (!this->decode_varint(field_id, *res)) {
|
if (!this->decode_varint(field_id, *res)) {
|
||||||
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
|
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
|
||||||
}
|
}
|
||||||
i += consumed;
|
ptr += consumed;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2: { // Length-delimited
|
case 2: { // Length-delimited
|
||||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
|
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
uint32_t field_length = res->as_uint32();
|
uint32_t field_length = res->as_uint32();
|
||||||
i += consumed;
|
ptr += consumed;
|
||||||
if (field_length > length - i) {
|
if (ptr + field_length > end) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
|
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
|
if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
|
||||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
|
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
|
||||||
}
|
}
|
||||||
i += field_length;
|
ptr += field_length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 5: { // 32-bit
|
case 5: { // 32-bit
|
||||||
if (length - i < 4) {
|
if (ptr + 4 > end) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
|
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
|
uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
||||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
||||||
}
|
}
|
||||||
i += 4;
|
ptr += 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
|
ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,23 @@
|
|||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
|
// Helper functions for ZigZag encoding/decoding
|
||||||
|
inline constexpr uint32_t encode_zigzag32(int32_t value) {
|
||||||
|
return (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr uint64_t encode_zigzag64(int64_t value) {
|
||||||
|
return (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int32_t decode_zigzag32(uint32_t value) {
|
||||||
|
return (value & 1) ? static_cast<int32_t>(~(value >> 1)) : static_cast<int32_t>(value >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr int64_t decode_zigzag64(uint64_t value) {
|
||||||
|
return (value & 1) ? static_cast<int64_t>(~(value >> 1)) : static_cast<int64_t>(value >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* StringRef Ownership Model for API Protocol Messages
|
* StringRef Ownership Model for API Protocol Messages
|
||||||
* ===================================================
|
* ===================================================
|
||||||
@@ -87,33 +104,25 @@ class ProtoVarInt {
|
|||||||
return {}; // Incomplete or invalid varint
|
return {}; // Incomplete or invalid varint
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t as_uint16() const { return this->value_; }
|
constexpr uint16_t as_uint16() const { return this->value_; }
|
||||||
uint32_t as_uint32() const { return this->value_; }
|
constexpr uint32_t as_uint32() const { return this->value_; }
|
||||||
uint64_t as_uint64() const { return this->value_; }
|
constexpr uint64_t as_uint64() const { return this->value_; }
|
||||||
bool as_bool() const { return this->value_; }
|
constexpr bool as_bool() const { return this->value_; }
|
||||||
int32_t as_int32() const {
|
constexpr int32_t as_int32() const {
|
||||||
// Not ZigZag encoded
|
// Not ZigZag encoded
|
||||||
return static_cast<int32_t>(this->as_int64());
|
return static_cast<int32_t>(this->as_int64());
|
||||||
}
|
}
|
||||||
int64_t as_int64() const {
|
constexpr int64_t as_int64() const {
|
||||||
// Not ZigZag encoded
|
// Not ZigZag encoded
|
||||||
return static_cast<int64_t>(this->value_);
|
return static_cast<int64_t>(this->value_);
|
||||||
}
|
}
|
||||||
int32_t as_sint32() const {
|
constexpr int32_t as_sint32() const {
|
||||||
// with ZigZag encoding
|
// with ZigZag encoding
|
||||||
if (this->value_ & 1) {
|
return decode_zigzag32(static_cast<uint32_t>(this->value_));
|
||||||
return static_cast<int32_t>(~(this->value_ >> 1));
|
|
||||||
} else {
|
|
||||||
return static_cast<int32_t>(this->value_ >> 1);
|
|
||||||
}
|
}
|
||||||
}
|
constexpr int64_t as_sint64() const {
|
||||||
int64_t as_sint64() const {
|
|
||||||
// with ZigZag encoding
|
// with ZigZag encoding
|
||||||
if (this->value_ & 1) {
|
return decode_zigzag64(this->value_);
|
||||||
return static_cast<int64_t>(~(this->value_ >> 1));
|
|
||||||
} else {
|
|
||||||
return static_cast<int64_t>(this->value_ >> 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
||||||
@@ -173,6 +182,10 @@ class ProtoLengthDelimited {
|
|||||||
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
||||||
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
||||||
|
|
||||||
|
// Direct access to raw data without string allocation
|
||||||
|
const uint8_t *data() const { return this->value_; }
|
||||||
|
size_t size() const { return this->length_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode the length-delimited data into an existing ProtoDecodableMessage instance.
|
* Decode the length-delimited data into an existing ProtoDecodableMessage instance.
|
||||||
*
|
*
|
||||||
@@ -309,22 +322,10 @@ class ProtoWriteBuffer {
|
|||||||
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
||||||
}
|
}
|
||||||
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
||||||
uint32_t uvalue;
|
this->encode_uint32(field_id, encode_zigzag32(value), force);
|
||||||
if (value < 0) {
|
|
||||||
uvalue = ~(value << 1);
|
|
||||||
} else {
|
|
||||||
uvalue = value << 1;
|
|
||||||
}
|
|
||||||
this->encode_uint32(field_id, uvalue, force);
|
|
||||||
}
|
}
|
||||||
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
||||||
uint64_t uvalue;
|
this->encode_uint64(field_id, encode_zigzag64(value), force);
|
||||||
if (value < 0) {
|
|
||||||
uvalue = ~(value << 1);
|
|
||||||
} else {
|
|
||||||
uvalue = value << 1;
|
|
||||||
}
|
|
||||||
this->encode_uint64(field_id, uvalue, force);
|
|
||||||
}
|
}
|
||||||
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
|
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
|
||||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||||
@@ -395,7 +396,7 @@ class ProtoSize {
|
|||||||
* @param value The uint32_t value to calculate size for
|
* @param value The uint32_t value to calculate size for
|
||||||
* @return The number of bytes needed to encode the value
|
* @return The number of bytes needed to encode the value
|
||||||
*/
|
*/
|
||||||
static inline uint32_t varint(uint32_t value) {
|
static constexpr uint32_t varint(uint32_t value) {
|
||||||
// Optimized varint size calculation using leading zeros
|
// Optimized varint size calculation using leading zeros
|
||||||
// Each 7 bits requires one byte in the varint encoding
|
// Each 7 bits requires one byte in the varint encoding
|
||||||
if (value < 128)
|
if (value < 128)
|
||||||
@@ -419,7 +420,7 @@ class ProtoSize {
|
|||||||
* @param value The uint64_t value to calculate size for
|
* @param value The uint64_t value to calculate size for
|
||||||
* @return The number of bytes needed to encode the value
|
* @return The number of bytes needed to encode the value
|
||||||
*/
|
*/
|
||||||
static inline uint32_t varint(uint64_t value) {
|
static constexpr uint32_t varint(uint64_t value) {
|
||||||
// Handle common case of values fitting in uint32_t (vast majority of use cases)
|
// Handle common case of values fitting in uint32_t (vast majority of use cases)
|
||||||
if (value <= UINT32_MAX) {
|
if (value <= UINT32_MAX) {
|
||||||
return varint(static_cast<uint32_t>(value));
|
return varint(static_cast<uint32_t>(value));
|
||||||
@@ -450,7 +451,7 @@ class ProtoSize {
|
|||||||
* @param value The int32_t value to calculate size for
|
* @param value The int32_t value to calculate size for
|
||||||
* @return The number of bytes needed to encode the value
|
* @return The number of bytes needed to encode the value
|
||||||
*/
|
*/
|
||||||
static inline uint32_t varint(int32_t value) {
|
static constexpr uint32_t varint(int32_t value) {
|
||||||
// Negative values are sign-extended to 64 bits in protocol buffers,
|
// Negative values are sign-extended to 64 bits in protocol buffers,
|
||||||
// which always results in a 10-byte varint for negative int32
|
// which always results in a 10-byte varint for negative int32
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
@@ -466,7 +467,7 @@ class ProtoSize {
|
|||||||
* @param value The int64_t value to calculate size for
|
* @param value The int64_t value to calculate size for
|
||||||
* @return The number of bytes needed to encode the value
|
* @return The number of bytes needed to encode the value
|
||||||
*/
|
*/
|
||||||
static inline uint32_t varint(int64_t value) {
|
static constexpr uint32_t varint(int64_t value) {
|
||||||
// For int64_t, we convert to uint64_t and calculate the size
|
// For int64_t, we convert to uint64_t and calculate the size
|
||||||
// This works because the bit pattern determines the encoding size,
|
// This works because the bit pattern determines the encoding size,
|
||||||
// and we've handled negative int32 values as a special case above
|
// and we've handled negative int32 values as a special case above
|
||||||
@@ -480,7 +481,7 @@ class ProtoSize {
|
|||||||
* @param type The wire type value (from the WireType enum in the protobuf spec)
|
* @param type The wire type value (from the WireType enum in the protobuf spec)
|
||||||
* @return The number of bytes needed to encode the field ID and wire type
|
* @return The number of bytes needed to encode the field ID and wire type
|
||||||
*/
|
*/
|
||||||
static inline uint32_t field(uint32_t field_id, uint32_t type) {
|
static constexpr uint32_t field(uint32_t field_id, uint32_t type) {
|
||||||
uint32_t tag = (field_id << 3) | (type & 0b111);
|
uint32_t tag = (field_id << 3) | (type & 0b111);
|
||||||
return varint(tag);
|
return varint(tag);
|
||||||
}
|
}
|
||||||
@@ -607,9 +608,8 @@ class ProtoSize {
|
|||||||
*/
|
*/
|
||||||
inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
|
inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
|
||||||
// Always calculate size when force is true
|
// Always calculate size when force is true
|
||||||
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
|
// ZigZag encoding for sint32
|
||||||
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
total_size_ += field_id_size + varint(encode_zigzag32(value));
|
||||||
total_size_ += field_id_size + varint(zigzag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -831,7 +831,7 @@ class ProtoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authentication helper methods
|
// Authentication helper methods
|
||||||
bool check_connection_setup_() {
|
inline bool check_connection_setup_() {
|
||||||
if (!this->is_connection_setup()) {
|
if (!this->is_connection_setup()) {
|
||||||
this->on_no_setup_connection();
|
this->on_no_setup_connection();
|
||||||
return false;
|
return false;
|
||||||
@@ -839,7 +839,7 @@ class ProtoService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_authenticated_() {
|
inline bool check_authenticated_() {
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
if (!this->check_connection_setup_()) {
|
if (!this->check_connection_setup_()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
|||||||
msg.set_name(StringRef(this->name_));
|
msg.set_name(StringRef(this->name_));
|
||||||
msg.key = this->key_;
|
msg.key = this->key_;
|
||||||
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||||
for (int i = 0; i < sizeof...(Ts); i++) {
|
for (size_t i = 0; i < sizeof...(Ts); i++) {
|
||||||
msg.args.emplace_back();
|
msg.args.emplace_back();
|
||||||
auto &arg = msg.args.back();
|
auto &arg = msg.args.back();
|
||||||
arg.type = arg_types[i];
|
arg.type = arg_types[i];
|
||||||
@@ -55,7 +55,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void execute(Ts... x) = 0;
|
virtual void execute(Ts... x) = 0;
|
||||||
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...> type) {
|
template<int... S> void execute_(const std::vector<ExecuteServiceArgument> &args, seq<S...> type) {
|
||||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
|||||||
CONF_DIRECTION,
|
CONF_DIRECTION,
|
||||||
CONF_HYSTERESIS,
|
CONF_HYSTERESIS,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
CONF_POWER_MODE,
|
||||||
CONF_RANGE,
|
CONF_RANGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,7 +58,6 @@ FAST_FILTER = {
|
|||||||
CONF_RAW_ANGLE = "raw_angle"
|
CONF_RAW_ANGLE = "raw_angle"
|
||||||
CONF_RAW_POSITION = "raw_position"
|
CONF_RAW_POSITION = "raw_position"
|
||||||
CONF_WATCHDOG = "watchdog"
|
CONF_WATCHDOG = "watchdog"
|
||||||
CONF_POWER_MODE = "power_mode"
|
|
||||||
CONF_SLOW_FILTER = "slow_filter"
|
CONF_SLOW_FILTER = "slow_filter"
|
||||||
CONF_FAST_FILTER = "fast_filter"
|
CONF_FAST_FILTER = "fast_filter"
|
||||||
CONF_START_POSITION = "start_position"
|
CONF_START_POSITION = "start_position"
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingCompone
|
|||||||
CONF_RAW_ANGLE = "raw_angle"
|
CONF_RAW_ANGLE = "raw_angle"
|
||||||
CONF_RAW_POSITION = "raw_position"
|
CONF_RAW_POSITION = "raw_position"
|
||||||
CONF_WATCHDOG = "watchdog"
|
CONF_WATCHDOG = "watchdog"
|
||||||
CONF_POWER_MODE = "power_mode"
|
|
||||||
CONF_SLOW_FILTER = "slow_filter"
|
CONF_SLOW_FILTER = "slow_filter"
|
||||||
CONF_FAST_FILTER = "fast_filter"
|
CONF_FAST_FILTER = "fast_filter"
|
||||||
CONF_PWM_FREQUENCY = "pwm_frequency"
|
CONF_PWM_FREQUENCY = "pwm_frequency"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
|
|||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_CLEAR,
|
||||||
CONF_GAIN,
|
CONF_GAIN,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
DEVICE_CLASS_ILLUMINANCE,
|
DEVICE_CLASS_ILLUMINANCE,
|
||||||
@@ -29,7 +30,6 @@ CONF_F5 = "f5"
|
|||||||
CONF_F6 = "f6"
|
CONF_F6 = "f6"
|
||||||
CONF_F7 = "f7"
|
CONF_F7 = "f7"
|
||||||
CONF_F8 = "f8"
|
CONF_F8 = "f8"
|
||||||
CONF_CLEAR = "clear"
|
|
||||||
CONF_NIR = "nir"
|
CONF_NIR = "nir"
|
||||||
|
|
||||||
UNIT_COUNTS = "#"
|
UNIT_COUNTS = "#"
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ from esphome.const import (
|
|||||||
PLATFORM_LN882X,
|
PLATFORM_LN882X,
|
||||||
PLATFORM_RTL87XX,
|
PLATFORM_RTL87XX,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@OttoWinter"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema({}),
|
cv.Schema({}),
|
||||||
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(200.0)
|
@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
if CORE.is_esp32 or CORE.is_libretiny:
|
if CORE.is_esp32 or CORE.is_libretiny:
|
||||||
# https://github.com/ESP32Async/AsyncTCP
|
# https://github.com/ESP32Async/AsyncTCP
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_POWER_FACTOR,
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
|
DEVICE_CLASS_REACTIVE_POWER,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
ICON_CURRENT_AC,
|
ICON_CURRENT_AC,
|
||||||
ICON_LIGHTBULB,
|
ICON_LIGHTBULB,
|
||||||
@@ -78,6 +79,7 @@ CONFIG_SCHEMA = (
|
|||||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||||
icon=ICON_LIGHTBULB,
|
icon=ICON_LIGHTBULB,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_REACTIVE_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ void ATM90E32Component::update() {
|
|||||||
|
|
||||||
void ATM90E32Component::setup() {
|
void ATM90E32Component::setup() {
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
|
this->cs_summary_ = this->cs_->dump_summary();
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
|
|
||||||
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||||
uint16_t high_thresh = 0;
|
uint16_t high_thresh = 0;
|
||||||
@@ -130,7 +132,7 @@ void ATM90E32Component::setup() {
|
|||||||
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
||||||
}
|
}
|
||||||
|
|
||||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A, false); // Perform soft reset
|
||||||
delay(6); // Wait for the minimum 5ms + 1ms
|
delay(6); // Wait for the minimum 5ms + 1ms
|
||||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||||
if (!this->validate_spi_read_(0x55AA, "setup()")) {
|
if (!this->validate_spi_read_(0x55AA, "setup()")) {
|
||||||
@@ -156,16 +158,17 @@ void ATM90E32Component::setup() {
|
|||||||
|
|
||||||
if (this->enable_offset_calibration_) {
|
if (this->enable_offset_calibration_) {
|
||||||
// Initialize flash storage for offset calibrations
|
// Initialize flash storage for offset calibrations
|
||||||
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_->dump_summary());
|
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
|
||||||
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
||||||
this->restore_offset_calibrations_();
|
this->restore_offset_calibrations_();
|
||||||
|
|
||||||
// Initialize flash storage for power offset calibrations
|
// Initialize flash storage for power offset calibrations
|
||||||
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_->dump_summary());
|
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
|
||||||
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
||||||
this->restore_power_offset_calibrations_();
|
this->restore_power_offset_calibrations_();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Power & Voltage/Current offset calibration is disabled. Using config file values.");
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
|
||||||
|
cs);
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
this->write16_(this->voltage_offset_registers[phase],
|
this->write16_(this->voltage_offset_registers[phase],
|
||||||
static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
|
static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
|
||||||
@@ -180,21 +183,18 @@ void ATM90E32Component::setup() {
|
|||||||
|
|
||||||
if (this->enable_gain_calibration_) {
|
if (this->enable_gain_calibration_) {
|
||||||
// Initialize flash storage for gain calibration
|
// Initialize flash storage for gain calibration
|
||||||
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_->dump_summary());
|
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
|
||||||
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
||||||
this->restore_gain_calibrations_();
|
this->restore_gain_calibrations_();
|
||||||
|
|
||||||
if (this->using_saved_calibrations_) {
|
if (!this->using_saved_calibrations_) {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored gain calibration from memory.");
|
|
||||||
} else {
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration is disabled. Using config file values.");
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||||
@@ -213,6 +213,122 @@ void ATM90E32Component::setup() {
|
|||||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ATM90E32Component::log_calibration_status_() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
|
|
||||||
|
bool offset_mismatch = false;
|
||||||
|
bool power_mismatch = false;
|
||||||
|
bool gain_mismatch = false;
|
||||||
|
|
||||||
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
|
offset_mismatch |= this->offset_calibration_mismatch_[phase];
|
||||||
|
power_mismatch |= this->power_offset_calibration_mismatch_[phase];
|
||||||
|
gain_mismatch |= this->gain_calibration_mismatch_[phase];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset_mismatch) {
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"[CALIBRATION][%s] ===================== Offset mismatch: using flash values =====================", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||||
|
cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||||
|
cs);
|
||||||
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
|
||||||
|
this->config_offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].voltage_offset_,
|
||||||
|
this->config_offset_phase_[phase].current_offset_, this->offset_phase_[phase].current_offset_);
|
||||||
|
}
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||||
|
}
|
||||||
|
if (power_mismatch) {
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"[CALIBRATION][%s] ================= Power offset mismatch: using flash values =================", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||||
|
cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_active_power|offset_reactive_power|", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||||
|
cs);
|
||||||
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
|
||||||
|
this->config_power_offset_phase_[phase].active_power_offset,
|
||||||
|
this->power_offset_phase_[phase].active_power_offset,
|
||||||
|
this->config_power_offset_phase_[phase].reactive_power_offset,
|
||||||
|
this->power_offset_phase_[phase].reactive_power_offset);
|
||||||
|
}
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||||
|
}
|
||||||
|
if (gain_mismatch) {
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"[CALIBRATION][%s] ====================== Gain mismatch: using flash values =====================", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||||
|
cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||||
|
cs);
|
||||||
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6u | %6u | %6u | %6u |", cs, 'A' + phase,
|
||||||
|
this->config_gain_phase_[phase].voltage_gain, this->gain_phase_[phase].voltage_gain,
|
||||||
|
this->config_gain_phase_[phase].current_gain, this->gain_phase_[phase].current_gain);
|
||||||
|
}
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||||
|
}
|
||||||
|
if (!this->enable_offset_calibration_) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
|
||||||
|
cs);
|
||||||
|
} else if (this->restored_offset_calibration_ && !offset_mismatch) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ============== Restored offset calibration from memory ==============", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||||
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||||
|
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\\n", cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->restored_power_offset_calibration_ && !power_mismatch) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restored power offset calibration from memory ============", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||||
|
this->power_offset_phase_[phase].active_power_offset,
|
||||||
|
this->power_offset_phase_[phase].reactive_power_offset);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||||
|
}
|
||||||
|
if (!this->enable_gain_calibration_) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
||||||
|
} else if (this->restored_gain_calibration_ && !gain_mismatch) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restoring saved gain calibrations to registers ============", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
|
||||||
|
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\\n", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration loaded and verified successfully.\n", cs);
|
||||||
|
}
|
||||||
|
this->calibration_message_printed_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
void ATM90E32Component::dump_config() {
|
void ATM90E32Component::dump_config() {
|
||||||
ESP_LOGCONFIG("", "ATM90E32:");
|
ESP_LOGCONFIG("", "ATM90E32:");
|
||||||
LOG_PIN(" CS Pin: ", this->cs_);
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
@@ -255,6 +371,10 @@ void ATM90E32Component::dump_config() {
|
|||||||
LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
|
LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
|
||||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||||
|
if (this->restored_offset_calibration_ || this->restored_power_offset_calibration_ ||
|
||||||
|
this->restored_gain_calibration_ || !this->enable_offset_calibration_ || !this->enable_gain_calibration_) {
|
||||||
|
this->log_calibration_status_();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
|
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
|
||||||
@@ -263,19 +383,17 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
|
|||||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
||||||
// Default is 143FH (20ms, 63ms)
|
// Default is 143FH (20ms, 63ms)
|
||||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||||
|
this->enable();
|
||||||
|
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
|
||||||
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||||
uint8_t addrl = (a_register & 0xFF);
|
uint8_t addrl = (a_register & 0xFF);
|
||||||
uint8_t data[2];
|
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
|
||||||
uint16_t output;
|
this->transfer_array(data, 4);
|
||||||
this->enable();
|
uint16_t output = encode_uint16(data[2], data[3]);
|
||||||
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1ms is plenty
|
|
||||||
this->write_byte(addrh);
|
|
||||||
this->write_byte(addrl);
|
|
||||||
this->read_array(data, 2);
|
|
||||||
this->disable();
|
|
||||||
|
|
||||||
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
|
||||||
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
||||||
|
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
|
||||||
|
this->disable();
|
||||||
|
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,12 +410,18 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
|
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val, bool validate) {
|
||||||
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
|
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
|
||||||
|
uint8_t addrh = ((a_register >> 8) & 0x03);
|
||||||
|
uint8_t addrl = (a_register & 0xFF);
|
||||||
|
uint8_t data[4] = {addrh, addrl, uint8_t((val >> 8) & 0xFF), uint8_t(val & 0xFF)};
|
||||||
this->enable();
|
this->enable();
|
||||||
this->write_byte16(a_register);
|
delay_microseconds_safe(1); // ensure CS setup time
|
||||||
this->write_byte16(val);
|
this->write_array(data, 4);
|
||||||
|
delay_microseconds_safe(1); // allow clock to settle before raising CS
|
||||||
this->disable();
|
this->disable();
|
||||||
|
delay_microseconds_safe(1); // ensure minimum CS high time
|
||||||
|
if (validate)
|
||||||
this->validate_spi_read_(val, "write16()");
|
this->validate_spi_read_(val, "write16()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,8 +565,10 @@ float ATM90E32Component::get_chip_temperature_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_gain_calibrations() {
|
void ATM90E32Component::run_gain_calibrations() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
if (!this->enable_gain_calibration_) {
|
if (!this->enable_gain_calibration_) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Gain calibration is disabled! Enable it first with enable_gain_calibration: true");
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Gain calibration is disabled! Enable it first with enable_gain_calibration: true",
|
||||||
|
cs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,12 +580,14 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
|
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
|
||||||
this->get_reference_current(2)};
|
this->get_reference_current(2)};
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] ");
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] ========================= Gain Calibration =========================");
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ========================= Gain Calibration =========================", cs);
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(
|
||||||
"[CALIBRATION] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |");
|
TAG,
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
|
"[CALIBRATION][%s] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |",
|
||||||
|
cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
float measured_voltage = this->get_phase_voltage_avg_(phase);
|
float measured_voltage = this->get_phase_voltage_avg_(phase);
|
||||||
@@ -476,22 +604,22 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
|
|
||||||
// Voltage calibration
|
// Voltage calibration
|
||||||
if (ref_voltage <= 0.0f) {
|
if (ref_voltage <= 0.0f) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: reference voltage is 0.",
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: reference voltage is 0.", cs,
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else if (measured_voltage == 0.0f) {
|
} else if (measured_voltage == 0.0f) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: measured voltage is 0.",
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: measured voltage is 0.", cs,
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else {
|
} else {
|
||||||
uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
|
uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
|
||||||
if (new_voltage_gain == 0) {
|
if (new_voltage_gain == 0) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Voltage gain would be 0. Check reference and measured voltage.",
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Voltage gain would be 0. Check reference and measured voltage.", cs,
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else {
|
} else {
|
||||||
if (new_voltage_gain >= 65535) {
|
if (new_voltage_gain >= 65535) {
|
||||||
ESP_LOGW(
|
ESP_LOGW(TAG,
|
||||||
TAG,
|
"[CALIBRATION][%s] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage "
|
||||||
"[CALIBRATION] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage transformer.",
|
"transformer.",
|
||||||
phase_labels[phase]);
|
cs, phase_labels[phase]);
|
||||||
new_voltage_gain = 65535;
|
new_voltage_gain = 65535;
|
||||||
}
|
}
|
||||||
this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
|
this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
|
||||||
@@ -501,20 +629,20 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
|
|
||||||
// Current calibration
|
// Current calibration
|
||||||
if (ref_current == 0.0f) {
|
if (ref_current == 0.0f) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: reference current is 0.",
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: reference current is 0.", cs,
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else if (measured_current == 0.0f) {
|
} else if (measured_current == 0.0f) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: measured current is 0.",
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: measured current is 0.", cs,
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else {
|
} else {
|
||||||
uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
|
uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
|
||||||
if (new_current_gain == 0) {
|
if (new_current_gain == 0) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain would be 0. Check reference and measured current.",
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain would be 0. Check reference and measured current.", cs,
|
||||||
phase_labels[phase]);
|
phase_labels[phase]);
|
||||||
} else {
|
} else {
|
||||||
if (new_current_gain >= 65535) {
|
if (new_current_gain >= 65535) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
|
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
|
||||||
phase_labels[phase]);
|
cs, phase_labels[phase]);
|
||||||
new_current_gain = 65535;
|
new_current_gain = 65535;
|
||||||
}
|
}
|
||||||
this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
|
this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
|
||||||
@@ -523,13 +651,13 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Final row output
|
// Final row output
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |",
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |", cs,
|
||||||
'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
|
'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
|
||||||
did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
|
did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
|
||||||
did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
|
did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] =====================================================================\n");
|
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||||
|
|
||||||
this->save_gain_calibration_to_memory_();
|
this->save_gain_calibration_to_memory_();
|
||||||
this->write_gains_to_registers_();
|
this->write_gains_to_registers_();
|
||||||
@@ -537,54 +665,108 @@ void ATM90E32Component::run_gain_calibrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||||
|
global_preferences->sync();
|
||||||
if (success) {
|
if (success) {
|
||||||
this->using_saved_calibrations_ = true;
|
this->using_saved_calibrations_ = true;
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration saved to memory.");
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration saved to memory.", cs);
|
||||||
} else {
|
} else {
|
||||||
this->using_saved_calibrations_ = false;
|
this->using_saved_calibrations_ = false;
|
||||||
ESP_LOGE(TAG, "[CALIBRATION] Failed to save gain calibration to memory!");
|
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save gain calibration to memory!", cs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATM90E32Component::save_offset_calibration_to_memory_() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
|
bool success = this->offset_pref_.save(&this->offset_phase_);
|
||||||
|
global_preferences->sync();
|
||||||
|
if (success) {
|
||||||
|
this->using_saved_calibrations_ = true;
|
||||||
|
this->restored_offset_calibration_ = true;
|
||||||
|
for (bool &phase : this->offset_calibration_mismatch_)
|
||||||
|
phase = false;
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Offset calibration saved to memory.", cs);
|
||||||
|
} else {
|
||||||
|
this->using_saved_calibrations_ = false;
|
||||||
|
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save offset calibration to memory!", cs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ATM90E32Component::save_power_offset_calibration_to_memory_() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
|
bool success = this->power_offset_pref_.save(&this->power_offset_phase_);
|
||||||
|
global_preferences->sync();
|
||||||
|
if (success) {
|
||||||
|
this->using_saved_calibrations_ = true;
|
||||||
|
this->restored_power_offset_calibration_ = true;
|
||||||
|
for (bool &phase : this->power_offset_calibration_mismatch_)
|
||||||
|
phase = false;
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offset calibration saved to memory.", cs);
|
||||||
|
} else {
|
||||||
|
this->using_saved_calibrations_ = false;
|
||||||
|
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save power offset calibration to memory!", cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_offset_calibrations() {
|
void ATM90E32Component::run_offset_calibrations() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
if (!this->enable_offset_calibration_) {
|
if (!this->enable_offset_calibration_) {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] Offset calibration is disabled! Enable it first with enable_offset_calibration: true");
|
ESP_LOGW(TAG,
|
||||||
|
"[CALIBRATION][%s] Offset calibration is disabled! Enable it first with enable_offset_calibration: true",
|
||||||
|
cs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ======================== Offset Calibration ========================", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
int16_t voltage_offset = calibrate_offset(phase, true);
|
int16_t voltage_offset = calibrate_offset(phase, true);
|
||||||
int16_t current_offset = calibrate_offset(phase, false);
|
int16_t current_offset = calibrate_offset(phase, false);
|
||||||
|
|
||||||
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage: %d, offset_current: %d", 'A' + phase, voltage_offset,
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
|
||||||
current_offset);
|
current_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->offset_pref_.save(&this->offset_phase_); // Save to flash
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ==================================================================\n", cs);
|
||||||
|
|
||||||
|
this->save_offset_calibration_to_memory_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::run_power_offset_calibrations() {
|
void ATM90E32Component::run_power_offset_calibrations() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
if (!this->enable_offset_calibration_) {
|
if (!this->enable_offset_calibration_) {
|
||||||
ESP_LOGW(
|
ESP_LOGW(
|
||||||
TAG,
|
TAG,
|
||||||
"[CALIBRATION] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true");
|
"[CALIBRATION][%s] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true",
|
||||||
|
cs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ===================== Power Offset Calibration =====================", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
int16_t active_offset = calibrate_power_offset(phase, false);
|
int16_t active_offset = calibrate_power_offset(phase, false);
|
||||||
int16_t reactive_offset = calibrate_power_offset(phase, true);
|
int16_t reactive_offset = calibrate_power_offset(phase, true);
|
||||||
|
|
||||||
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
|
||||||
active_offset, reactive_offset);
|
reactive_offset);
|
||||||
}
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||||
|
|
||||||
this->power_offset_pref_.save(&this->power_offset_phase_); // Save to flash
|
this->save_power_offset_calibration_to_memory_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::write_gains_to_registers_() {
|
void ATM90E32Component::write_gains_to_registers_() {
|
||||||
@@ -631,102 +813,276 @@ void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_gain_calibrations_() {
|
void ATM90E32Component::restore_gain_calibrations_() {
|
||||||
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
|
const char *cs = this->cs_summary_.c_str();
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Restoring saved gain calibrations to registers:");
|
for (uint8_t i = 0; i < 3; ++i) {
|
||||||
|
this->config_gain_phase_[i].voltage_gain = this->phase_[i].voltage_gain_;
|
||||||
|
this->config_gain_phase_[i].current_gain = this->phase_[i].ct_gain_;
|
||||||
|
this->gain_phase_[i] = this->config_gain_phase_[i];
|
||||||
|
}
|
||||||
|
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
|
||||||
uint16_t v_gain = this->gain_phase_[phase].voltage_gain;
|
bool all_zero = true;
|
||||||
uint16_t i_gain = this->gain_phase_[phase].current_gain;
|
bool same_as_config = true;
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase, v_gain, i_gain);
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
|
const auto &cfg = this->config_gain_phase_[phase];
|
||||||
|
const auto &saved = this->gain_phase_[phase];
|
||||||
|
if (saved.voltage_gain != 0 || saved.current_gain != 0)
|
||||||
|
all_zero = false;
|
||||||
|
if (saved.voltage_gain != cfg.voltage_gain || saved.current_gain != cfg.current_gain)
|
||||||
|
same_as_config = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!all_zero && !same_as_config) {
|
||||||
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
|
bool mismatch = false;
|
||||||
|
if (this->has_config_voltage_gain_[phase] &&
|
||||||
|
this->gain_phase_[phase].voltage_gain != this->config_gain_phase_[phase].voltage_gain)
|
||||||
|
mismatch = true;
|
||||||
|
if (this->has_config_current_gain_[phase] &&
|
||||||
|
this->gain_phase_[phase].current_gain != this->config_gain_phase_[phase].current_gain)
|
||||||
|
mismatch = true;
|
||||||
|
if (mismatch)
|
||||||
|
this->gain_calibration_mismatch_[phase] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->write_gains_to_registers_();
|
this->write_gains_to_registers_();
|
||||||
|
|
||||||
if (this->verify_gain_writes_()) {
|
if (this->verify_gain_writes_()) {
|
||||||
this->using_saved_calibrations_ = true;
|
this->using_saved_calibrations_ = true;
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration loaded and verified successfully.");
|
this->restored_gain_calibration_ = true;
|
||||||
} else {
|
return;
|
||||||
this->using_saved_calibrations_ = false;
|
|
||||||
ESP_LOGE(TAG, "[CALIBRATION] Gain verification failed! Calibration may not be applied correctly.");
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this->using_saved_calibrations_ = false;
|
this->using_saved_calibrations_ = false;
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] No stored gain calibrations found. Using config file values.");
|
ESP_LOGE(TAG, "[CALIBRATION][%s] Gain verification failed! Calibration may not be applied correctly.", cs);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->using_saved_calibrations_ = false;
|
||||||
|
for (uint8_t i = 0; i < 3; ++i)
|
||||||
|
this->gain_phase_[i] = this->config_gain_phase_[i];
|
||||||
|
this->write_gains_to_registers_();
|
||||||
|
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored gain calibrations found. Using config file values.", cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_offset_calibrations_() {
|
void ATM90E32Component::restore_offset_calibrations_() {
|
||||||
if (this->offset_pref_.load(&this->offset_phase_)) {
|
const char *cs = this->cs_summary_.c_str();
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored offset calibration from memory.");
|
for (uint8_t i = 0; i < 3; ++i)
|
||||||
|
this->config_offset_phase_[i] = this->offset_phase_[i];
|
||||||
|
|
||||||
|
bool have_data = this->offset_pref_.load(&this->offset_phase_);
|
||||||
|
bool all_zero = true;
|
||||||
|
if (have_data) {
|
||||||
|
for (auto &phase : this->offset_phase_) {
|
||||||
|
if (phase.voltage_offset_ != 0 || phase.current_offset_ != 0) {
|
||||||
|
all_zero = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (have_data && !all_zero) {
|
||||||
|
this->restored_offset_calibration_ = true;
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
auto &offset = this->offset_phase_[phase];
|
auto &offset = this->offset_phase_[phase];
|
||||||
write_offsets_to_registers_(phase, offset.voltage_offset_, offset.current_offset_);
|
bool mismatch = false;
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage:: %d, offset_current: %d", 'A' + phase,
|
if (this->has_config_voltage_offset_[phase] &&
|
||||||
offset.voltage_offset_, offset.current_offset_);
|
offset.voltage_offset_ != this->config_offset_phase_[phase].voltage_offset_)
|
||||||
|
mismatch = true;
|
||||||
|
if (this->has_config_current_offset_[phase] &&
|
||||||
|
offset.current_offset_ != this->config_offset_phase_[phase].current_offset_)
|
||||||
|
mismatch = true;
|
||||||
|
if (mismatch)
|
||||||
|
this->offset_calibration_mismatch_[phase] = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] No stored offset calibrations found. Using default values.");
|
for (uint8_t phase = 0; phase < 3; phase++)
|
||||||
|
this->offset_phase_[phase] = this->config_offset_phase_[phase];
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored offset calibrations found. Using default values.", cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
|
write_offsets_to_registers_(phase, this->offset_phase_[phase].voltage_offset_,
|
||||||
|
this->offset_phase_[phase].current_offset_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::restore_power_offset_calibrations_() {
|
void ATM90E32Component::restore_power_offset_calibrations_() {
|
||||||
if (this->power_offset_pref_.load(&this->power_offset_phase_)) {
|
const char *cs = this->cs_summary_.c_str();
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored power offset calibration from memory.");
|
for (uint8_t i = 0; i < 3; ++i)
|
||||||
|
this->config_power_offset_phase_[i] = this->power_offset_phase_[i];
|
||||||
|
|
||||||
|
bool have_data = this->power_offset_pref_.load(&this->power_offset_phase_);
|
||||||
|
bool all_zero = true;
|
||||||
|
if (have_data) {
|
||||||
|
for (auto &phase : this->power_offset_phase_) {
|
||||||
|
if (phase.active_power_offset != 0 || phase.reactive_power_offset != 0) {
|
||||||
|
all_zero = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (have_data && !all_zero) {
|
||||||
|
this->restored_power_offset_calibration_ = true;
|
||||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
auto &offset = this->power_offset_phase_[phase];
|
auto &offset = this->power_offset_phase_[phase];
|
||||||
write_power_offsets_to_registers_(phase, offset.active_power_offset, offset.reactive_power_offset);
|
bool mismatch = false;
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
|
if (this->has_config_active_power_offset_[phase] &&
|
||||||
offset.active_power_offset, offset.reactive_power_offset);
|
offset.active_power_offset != this->config_power_offset_phase_[phase].active_power_offset)
|
||||||
|
mismatch = true;
|
||||||
|
if (this->has_config_reactive_power_offset_[phase] &&
|
||||||
|
offset.reactive_power_offset != this->config_power_offset_phase_[phase].reactive_power_offset)
|
||||||
|
mismatch = true;
|
||||||
|
if (mismatch)
|
||||||
|
this->power_offset_calibration_mismatch_[phase] = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "[CALIBRATION] No stored power offsets found. Using default values.");
|
for (uint8_t phase = 0; phase < 3; ++phase)
|
||||||
|
this->power_offset_phase_[phase] = this->config_power_offset_phase_[phase];
|
||||||
|
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored power offsets found. Using default values.", cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||||
|
write_power_offsets_to_registers_(phase, this->power_offset_phase_[phase].active_power_offset,
|
||||||
|
this->power_offset_phase_[phase].reactive_power_offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_gain_calibrations() {
|
void ATM90E32Component::clear_gain_calibrations() {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values");
|
const char *cs = this->cs_summary_.c_str();
|
||||||
|
if (!this->using_saved_calibrations_) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||||
for (int phase = 0; phase < 3; phase++) {
|
for (int phase = 0; phase < 3; phase++) {
|
||||||
gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
|
||||||
gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
|
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored gain calibrations and restoring config-defined values", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||||
|
|
||||||
|
for (int phase = 0; phase < 3; phase++) {
|
||||||
|
uint16_t voltage_gain = this->phase_[phase].voltage_gain_;
|
||||||
|
uint16_t current_gain = this->phase_[phase].ct_gain_;
|
||||||
|
|
||||||
|
this->config_gain_phase_[phase].voltage_gain = voltage_gain;
|
||||||
|
this->config_gain_phase_[phase].current_gain = current_gain;
|
||||||
|
this->gain_phase_[phase].voltage_gain = voltage_gain;
|
||||||
|
this->gain_phase_[phase].current_gain = current_gain;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase, voltage_gain, current_gain);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
||||||
|
|
||||||
|
GainCalibration zero_gains[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||||
|
bool success = this->gain_calibration_pref_.save(&zero_gains);
|
||||||
|
global_preferences->sync();
|
||||||
|
|
||||||
this->using_saved_calibrations_ = false;
|
this->using_saved_calibrations_ = false;
|
||||||
|
this->restored_gain_calibration_ = false;
|
||||||
|
for (bool &phase : this->gain_calibration_mismatch_)
|
||||||
|
phase = false;
|
||||||
|
|
||||||
if (success) {
|
if (!success) {
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibrations cleared. Config values restored:");
|
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear gain calibrations!", cs);
|
||||||
for (int phase = 0; phase < 3; phase++) {
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase,
|
|
||||||
gain_phase_[phase].voltage_gain, gain_phase_[phase].current_gain);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "[CALIBRATION] Failed to clear gain calibrations!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->write_gains_to_registers_(); // Apply them to the chip immediately
|
this->write_gains_to_registers_(); // Apply them to the chip immediately
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_offset_calibrations() {
|
void ATM90E32Component::clear_offset_calibrations() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
|
if (!this->restored_offset_calibration_) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
this->write_offsets_to_registers_(phase, 0, 0);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||||
|
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->offset_pref_.save(&this->offset_phase_); // Save cleared values to flash memory
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored offset calibrations and restoring config-defined values", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Offsets cleared.");
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
|
int16_t voltage_offset =
|
||||||
|
this->has_config_voltage_offset_[phase] ? this->config_offset_phase_[phase].voltage_offset_ : 0;
|
||||||
|
int16_t current_offset =
|
||||||
|
this->has_config_current_offset_[phase] ? this->config_offset_phase_[phase].current_offset_ : 0;
|
||||||
|
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
|
||||||
|
current_offset);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
||||||
|
|
||||||
|
OffsetCalibration zero_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||||
|
this->offset_pref_.save(&zero_offsets); // Clear stored values in flash
|
||||||
|
global_preferences->sync();
|
||||||
|
|
||||||
|
this->restored_offset_calibration_ = false;
|
||||||
|
for (bool &phase : this->offset_calibration_mismatch_)
|
||||||
|
phase = false;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Offsets cleared.", cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::clear_power_offset_calibrations() {
|
void ATM90E32Component::clear_power_offset_calibrations() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
|
if (!this->restored_power_offset_calibration_) {
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
this->write_power_offsets_to_registers_(phase, 0, 0);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||||
|
this->power_offset_phase_[phase].active_power_offset,
|
||||||
|
this->power_offset_phase_[phase].reactive_power_offset);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->power_offset_pref_.save(&this->power_offset_phase_);
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored power offsets and restoring config-defined values", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||||
|
|
||||||
ESP_LOGI(TAG, "[CALIBRATION] Power offsets cleared.");
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
|
int16_t active_offset =
|
||||||
|
this->has_config_active_power_offset_[phase] ? this->config_power_offset_phase_[phase].active_power_offset : 0;
|
||||||
|
int16_t reactive_offset = this->has_config_reactive_power_offset_[phase]
|
||||||
|
? this->config_power_offset_phase_[phase].reactive_power_offset
|
||||||
|
: 0;
|
||||||
|
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
|
||||||
|
reactive_offset);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||||
|
|
||||||
|
PowerOffsetCalibration zero_power_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||||
|
this->power_offset_pref_.save(&zero_power_offsets);
|
||||||
|
global_preferences->sync();
|
||||||
|
|
||||||
|
this->restored_power_offset_calibration_ = false;
|
||||||
|
for (bool &phase : this->power_offset_calibration_mismatch_)
|
||||||
|
phase = false;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offsets cleared.", cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
||||||
@@ -747,20 +1103,21 @@ int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
|||||||
|
|
||||||
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
|
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
|
||||||
const uint8_t num_reads = 5;
|
const uint8_t num_reads = 5;
|
||||||
uint64_t total_value = 0;
|
int64_t total_value = 0;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < num_reads; ++i) {
|
for (uint8_t i = 0; i < num_reads; ++i) {
|
||||||
uint32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
|
int32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
|
||||||
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
||||||
total_value += reading;
|
total_value += reading;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t average_value = total_value / num_reads;
|
int32_t average_value = total_value / num_reads;
|
||||||
const uint32_t power_offset = ~average_value + 1;
|
int32_t power_offset = -average_value;
|
||||||
return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
|
return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ATM90E32Component::verify_gain_writes_() {
|
bool ATM90E32Component::verify_gain_writes_() {
|
||||||
|
const char *cs = this->cs_summary_.c_str();
|
||||||
bool success = true;
|
bool success = true;
|
||||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||||
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
|
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
|
||||||
@@ -768,7 +1125,7 @@ bool ATM90E32Component::verify_gain_writes_() {
|
|||||||
|
|
||||||
if (read_voltage != this->gain_phase_[phase].voltage_gain ||
|
if (read_voltage != this->gain_phase_[phase].voltage_gain ||
|
||||||
read_current != this->gain_phase_[phase].current_gain) {
|
read_current != this->gain_phase_[phase].current_gain) {
|
||||||
ESP_LOGE(TAG, "[CALIBRATION] Mismatch detected for Phase %s!", phase_labels[phase]);
|
ESP_LOGE(TAG, "[CALIBRATION][%s] Mismatch detected for Phase %s!", cs, phase_labels[phase]);
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -791,15 +1148,15 @@ void ATM90E32Component::check_phase_status() {
|
|||||||
status += "Phase Loss; ";
|
status += "Phase Loss; ";
|
||||||
|
|
||||||
auto *sensor = this->phase_status_text_sensor_[phase];
|
auto *sensor = this->phase_status_text_sensor_[phase];
|
||||||
const char *phase_name = sensor ? sensor->get_name().c_str() : "Unknown Phase";
|
if (sensor == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!status.empty()) {
|
if (!status.empty()) {
|
||||||
status.pop_back(); // remove space
|
status.pop_back(); // remove space
|
||||||
status.pop_back(); // remove semicolon
|
status.pop_back(); // remove semicolon
|
||||||
ESP_LOGW(TAG, "%s: %s", phase_name, status.c_str());
|
ESP_LOGW(TAG, "%s: %s", sensor->get_name().c_str(), status.c_str());
|
||||||
if (sensor != nullptr)
|
|
||||||
sensor->publish_state(status);
|
sensor->publish_state(status);
|
||||||
} else {
|
} else {
|
||||||
if (sensor != nullptr)
|
|
||||||
sensor->publish_state("Okay");
|
sensor->publish_state("Okay");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -817,9 +1174,12 @@ void ATM90E32Component::check_freq_status() {
|
|||||||
} else {
|
} else {
|
||||||
freq_status = "Normal";
|
freq_status = "Normal";
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
|
|
||||||
|
|
||||||
if (this->freq_status_text_sensor_ != nullptr) {
|
if (this->freq_status_text_sensor_ != nullptr) {
|
||||||
|
if (freq_status == "Normal") {
|
||||||
|
ESP_LOGD(TAG, "Frequency status: %s", freq_status.c_str());
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
|
||||||
|
}
|
||||||
this->freq_status_text_sensor_->publish_state(freq_status);
|
this->freq_status_text_sensor_->publish_state(freq_status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,15 +61,29 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
this->phase_[phase].harmonic_active_power_sensor_ = obj;
|
this->phase_[phase].harmonic_active_power_sensor_ = obj;
|
||||||
}
|
}
|
||||||
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
|
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
|
||||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
|
void set_volt_gain(int phase, uint16_t gain) {
|
||||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
this->phase_[phase].voltage_gain_ = gain;
|
||||||
void set_voltage_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].voltage_offset_ = offset; }
|
this->has_config_voltage_gain_[phase] = true;
|
||||||
void set_current_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].current_offset_ = offset; }
|
}
|
||||||
|
void set_ct_gain(int phase, uint16_t gain) {
|
||||||
|
this->phase_[phase].ct_gain_ = gain;
|
||||||
|
this->has_config_current_gain_[phase] = true;
|
||||||
|
}
|
||||||
|
void set_voltage_offset(uint8_t phase, int16_t offset) {
|
||||||
|
this->offset_phase_[phase].voltage_offset_ = offset;
|
||||||
|
this->has_config_voltage_offset_[phase] = true;
|
||||||
|
}
|
||||||
|
void set_current_offset(uint8_t phase, int16_t offset) {
|
||||||
|
this->offset_phase_[phase].current_offset_ = offset;
|
||||||
|
this->has_config_current_offset_[phase] = true;
|
||||||
|
}
|
||||||
void set_active_power_offset(uint8_t phase, int16_t offset) {
|
void set_active_power_offset(uint8_t phase, int16_t offset) {
|
||||||
this->power_offset_phase_[phase].active_power_offset = offset;
|
this->power_offset_phase_[phase].active_power_offset = offset;
|
||||||
|
this->has_config_active_power_offset_[phase] = true;
|
||||||
}
|
}
|
||||||
void set_reactive_power_offset(uint8_t phase, int16_t offset) {
|
void set_reactive_power_offset(uint8_t phase, int16_t offset) {
|
||||||
this->power_offset_phase_[phase].reactive_power_offset = offset;
|
this->power_offset_phase_[phase].reactive_power_offset = offset;
|
||||||
|
this->has_config_reactive_power_offset_[phase] = true;
|
||||||
}
|
}
|
||||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||||
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
||||||
@@ -127,7 +141,7 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
#endif
|
#endif
|
||||||
uint16_t read16_(uint16_t a_register);
|
uint16_t read16_(uint16_t a_register);
|
||||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||||
void write16_(uint16_t a_register, uint16_t val);
|
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
|
||||||
float get_local_phase_voltage_(uint8_t phase);
|
float get_local_phase_voltage_(uint8_t phase);
|
||||||
float get_local_phase_current_(uint8_t phase);
|
float get_local_phase_current_(uint8_t phase);
|
||||||
float get_local_phase_active_power_(uint8_t phase);
|
float get_local_phase_active_power_(uint8_t phase);
|
||||||
@@ -159,12 +173,15 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
void restore_offset_calibrations_();
|
void restore_offset_calibrations_();
|
||||||
void restore_power_offset_calibrations_();
|
void restore_power_offset_calibrations_();
|
||||||
void restore_gain_calibrations_();
|
void restore_gain_calibrations_();
|
||||||
|
void save_offset_calibration_to_memory_();
|
||||||
void save_gain_calibration_to_memory_();
|
void save_gain_calibration_to_memory_();
|
||||||
|
void save_power_offset_calibration_to_memory_();
|
||||||
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
|
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
|
||||||
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
|
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
|
||||||
void write_gains_to_registers_();
|
void write_gains_to_registers_();
|
||||||
bool verify_gain_writes_();
|
bool verify_gain_writes_();
|
||||||
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
||||||
|
void log_calibration_status_();
|
||||||
|
|
||||||
struct ATM90E32Phase {
|
struct ATM90E32Phase {
|
||||||
uint16_t voltage_gain_{0};
|
uint16_t voltage_gain_{0};
|
||||||
@@ -204,19 +221,33 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
int16_t current_offset_{0};
|
int16_t current_offset_{0};
|
||||||
} offset_phase_[3];
|
} offset_phase_[3];
|
||||||
|
|
||||||
|
OffsetCalibration config_offset_phase_[3];
|
||||||
|
|
||||||
struct PowerOffsetCalibration {
|
struct PowerOffsetCalibration {
|
||||||
int16_t active_power_offset{0};
|
int16_t active_power_offset{0};
|
||||||
int16_t reactive_power_offset{0};
|
int16_t reactive_power_offset{0};
|
||||||
} power_offset_phase_[3];
|
} power_offset_phase_[3];
|
||||||
|
|
||||||
|
PowerOffsetCalibration config_power_offset_phase_[3];
|
||||||
|
|
||||||
struct GainCalibration {
|
struct GainCalibration {
|
||||||
uint16_t voltage_gain{1};
|
uint16_t voltage_gain{1};
|
||||||
uint16_t current_gain{1};
|
uint16_t current_gain{1};
|
||||||
} gain_phase_[3];
|
} gain_phase_[3];
|
||||||
|
|
||||||
|
GainCalibration config_gain_phase_[3];
|
||||||
|
|
||||||
|
bool has_config_voltage_offset_[3]{false, false, false};
|
||||||
|
bool has_config_current_offset_[3]{false, false, false};
|
||||||
|
bool has_config_active_power_offset_[3]{false, false, false};
|
||||||
|
bool has_config_reactive_power_offset_[3]{false, false, false};
|
||||||
|
bool has_config_voltage_gain_[3]{false, false, false};
|
||||||
|
bool has_config_current_gain_[3]{false, false, false};
|
||||||
|
|
||||||
ESPPreferenceObject offset_pref_;
|
ESPPreferenceObject offset_pref_;
|
||||||
ESPPreferenceObject power_offset_pref_;
|
ESPPreferenceObject power_offset_pref_;
|
||||||
ESPPreferenceObject gain_calibration_pref_;
|
ESPPreferenceObject gain_calibration_pref_;
|
||||||
|
std::string cs_summary_;
|
||||||
|
|
||||||
sensor::Sensor *freq_sensor_{nullptr};
|
sensor::Sensor *freq_sensor_{nullptr};
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
@@ -231,6 +262,13 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
bool peak_current_signed_{false};
|
bool peak_current_signed_{false};
|
||||||
bool enable_offset_calibration_{false};
|
bool enable_offset_calibration_{false};
|
||||||
bool enable_gain_calibration_{false};
|
bool enable_gain_calibration_{false};
|
||||||
|
bool restored_offset_calibration_{false};
|
||||||
|
bool restored_power_offset_calibration_{false};
|
||||||
|
bool restored_gain_calibration_{false};
|
||||||
|
bool calibration_message_printed_{false};
|
||||||
|
bool offset_calibration_mismatch_[3]{false, false, false};
|
||||||
|
bool power_offset_calibration_mismatch_[3]{false, false, false};
|
||||||
|
bool gain_calibration_mismatch_[3]{false, false, false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace atm90e32
|
} // namespace atm90e32
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ from esphome.const import (
|
|||||||
CONF_REACTIVE_POWER,
|
CONF_REACTIVE_POWER,
|
||||||
CONF_REVERSE_ACTIVE_ENERGY,
|
CONF_REVERSE_ACTIVE_ENERGY,
|
||||||
CONF_VOLTAGE,
|
CONF_VOLTAGE,
|
||||||
|
DEVICE_CLASS_APPARENT_POWER,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_POWER_FACTOR,
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
|
DEVICE_CLASS_REACTIVE_POWER,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
@@ -100,13 +102,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
|||||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||||
icon=ICON_LIGHTBULB,
|
icon=ICON_LIGHTBULB,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
device_class=DEVICE_CLASS_POWER,
|
device_class=DEVICE_CLASS_REACTIVE_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
|
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
device_class=DEVICE_CLASS_POWER,
|
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||||
|
|||||||
@@ -165,4 +165,4 @@ def final_validate_audio_schema(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_library("esphome/esp-audio-libs", "1.1.4")
|
cg.add_library("esphome/esp-audio-libs", "2.0.1")
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const char *audio_file_type_to_string(AudioFileType file_type) {
|
|||||||
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
|
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
|
||||||
size_t samples_to_scale) {
|
size_t samples_to_scale) {
|
||||||
// Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same.
|
// Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same.
|
||||||
for (int i = 0; i < samples_to_scale; i++) {
|
for (size_t i = 0; i < samples_to_scale; i++) {
|
||||||
int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor;
|
int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor;
|
||||||
output_buffer[i] = (int16_t) (acc >> 15);
|
output_buffer[i] = (int16_t) (acc >> 15);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,18 +229,18 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
|||||||
auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
|
auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
|
||||||
this->input_transfer_buffer_->available());
|
this->input_transfer_buffer_->available());
|
||||||
|
|
||||||
if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
if (result > esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
||||||
return FileDecoderState::POTENTIALLY_FAILED;
|
// Serrious error reading FLAC header, there is no recovery
|
||||||
}
|
|
||||||
|
|
||||||
if (result != esp_audio_libs::flac::FLAC_DECODER_SUCCESS) {
|
|
||||||
// Couldn't read FLAC header
|
|
||||||
return FileDecoderState::FAILED;
|
return FileDecoderState::FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
||||||
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
||||||
|
|
||||||
|
if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
||||||
|
return FileDecoderState::MORE_TO_PROCESS;
|
||||||
|
}
|
||||||
|
|
||||||
// Reallocate the output transfer buffer to the smallest necessary size
|
// Reallocate the output transfer buffer to the smallest necessary size
|
||||||
this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
|
this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
|
||||||
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
|
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
|
||||||
@@ -256,9 +256,9 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t output_samples = 0;
|
uint32_t output_samples = 0;
|
||||||
auto result = this->flac_decoder_->decode_frame(
|
auto result = this->flac_decoder_->decode_frame(this->input_transfer_buffer_->get_buffer_start(),
|
||||||
this->input_transfer_buffer_->get_buffer_start(), this->input_transfer_buffer_->available(),
|
this->input_transfer_buffer_->available(),
|
||||||
reinterpret_cast<int16_t *>(this->output_transfer_buffer_->get_buffer_end()), &output_samples);
|
this->output_transfer_buffer_->get_buffer_end(), &output_samples);
|
||||||
|
|
||||||
if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
|
if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
|
||||||
// Not an issue, just needs more data that we'll get next time.
|
// Not an issue, just needs more data that we'll get next time.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from esphome import automation
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_MIC_GAIN
|
from esphome.const import CONF_ID, CONF_MIC_GAIN
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@kbx81"]
|
CODEOWNERS = ["@kbx81"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -35,7 +35,7 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define("USE_AUDIO_ADC")
|
cg.add_define("USE_AUDIO_ADC")
|
||||||
cg.add_global(audio_adc_ns.using)
|
cg.add_global(audio_adc_ns.using)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from esphome.automation import maybe_simple_id
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_VOLUME
|
from esphome.const import CONF_ID, CONF_VOLUME
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@kbx81"]
|
CODEOWNERS = ["@kbx81"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define("USE_AUDIO_DAC")
|
cg.add_define("USE_AUDIO_DAC")
|
||||||
cg.add_global(audio_dac_ns.using)
|
cg.add_global(audio_dac_ns.using)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
|
|||||||
|
|
||||||
#define ERROR_CHECK(err) \
|
#define ERROR_CHECK(err) \
|
||||||
if ((err) != i2c::ERROR_OK) { \
|
if ((err) != i2c::ERROR_OK) { \
|
||||||
this->status_set_warning("Failed to communicate"); \
|
this->status_set_warning(LOG_STR("Failed to communicate")); \
|
||||||
return; \
|
return; \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
|
|||||||
i2c::ErrorCode err;
|
i2c::ErrorCode err;
|
||||||
uint8_t data[8]{};
|
uint8_t data[8]{};
|
||||||
|
|
||||||
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false);
|
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD));
|
||||||
ERROR_CHECK(err);
|
ERROR_CHECK(err);
|
||||||
err = this->read(data, sizeof(data));
|
err = this->read(data, sizeof(data));
|
||||||
ERROR_CHECK(err);
|
ERROR_CHECK(err);
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ void BedJetHub::dump_config() {
|
|||||||
" ble_client.app_id: %d\n"
|
" ble_client.app_id: %d\n"
|
||||||
" ble_client.conn_id: %d",
|
" ble_client.conn_id: %d",
|
||||||
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
|
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
|
||||||
LOG_UPDATE_INTERVAL(this)
|
LOG_UPDATE_INTERVAL(this);
|
||||||
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
|
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
|
||||||
for (auto *child : this->children_) {
|
for (auto *child : this->children_) {
|
||||||
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());
|
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_VIBRATION,
|
DEVICE_CLASS_VIBRATION,
|
||||||
DEVICE_CLASS_WINDOW,
|
DEVICE_CLASS_WINDOW,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
@@ -652,7 +652,7 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
|
|||||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(binary_sensor_ns.using)
|
cg.add_global(binary_sensor_ns.using)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,19 @@ namespace binary_sensor {
|
|||||||
|
|
||||||
static const char *const TAG = "binary_sensor";
|
static const char *const TAG = "binary_sensor";
|
||||||
|
|
||||||
|
// Function implementation of LOG_BINARY_SENSOR macro to reduce code size
|
||||||
|
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) {
|
||||||
|
if (obj == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||||
|
|
||||||
|
if (!obj->get_device_class_ref().empty()) {
|
||||||
|
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BinarySensor::publish_state(bool new_state) {
|
void BinarySensor::publish_state(bool new_state) {
|
||||||
if (this->filter_list_ == nullptr) {
|
if (this->filter_list_ == nullptr) {
|
||||||
this->send_state_internal(new_state);
|
this->send_state_internal(new_state);
|
||||||
|
|||||||
@@ -10,13 +10,10 @@ namespace esphome {
|
|||||||
|
|
||||||
namespace binary_sensor {
|
namespace binary_sensor {
|
||||||
|
|
||||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
|
class BinarySensor;
|
||||||
if ((obj) != nullptr) { \
|
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
|
||||||
if (!(obj)->get_device_class().empty()) { \
|
#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SUB_BINARY_SENSOR(name) \
|
#define SUB_BINARY_SENSOR(name) \
|
||||||
protected: \
|
protected: \
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ void BL0906::handle_actions_() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ActionCallbackFuncPtr ptr_func = nullptr;
|
ActionCallbackFuncPtr ptr_func = nullptr;
|
||||||
for (int i = 0; i < this->action_queue_.size(); i++) {
|
for (size_t i = 0; i < this->action_queue_.size(); i++) {
|
||||||
ptr_func = this->action_queue_[i];
|
ptr_func = this->action_queue_[i];
|
||||||
if (ptr_func) {
|
if (ptr_func) {
|
||||||
ESP_LOGI(TAG, "HandleActionCallback[%d]", i);
|
ESP_LOGI(TAG, "HandleActionCallback[%zu]", i);
|
||||||
(this->*ptr_func)();
|
(this->*ptr_func)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
CODEOWNERS = ["@tobias-"]
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
CODEOWNERS = ["@tobias-", "@dan-s-github"]
|
||||||
|
|
||||||
|
CONF_BL0940_ID = "bl0940_id"
|
||||||
|
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
||||||
|
|||||||
@@ -7,28 +7,26 @@ namespace bl0940 {
|
|||||||
|
|
||||||
static const char *const TAG = "bl0940";
|
static const char *const TAG = "bl0940";
|
||||||
|
|
||||||
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
|
|
||||||
static const uint8_t BL0940_FULL_PACKET = 0xAA;
|
static const uint8_t BL0940_FULL_PACKET = 0xAA;
|
||||||
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
|
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to en doc but 0x55 in cn doc
|
||||||
|
|
||||||
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
|
|
||||||
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
|
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
|
||||||
static const uint8_t BL0940_REG_MODE = 0x18;
|
static const uint8_t BL0940_REG_MODE = 0x18;
|
||||||
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
|
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
|
||||||
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
|
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
|
||||||
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
|
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
|
||||||
|
|
||||||
const uint8_t BL0940_INIT[5][6] = {
|
static const uint8_t BL0940_INIT[5][5] = {
|
||||||
// Reset to default
|
// Reset to default
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
{BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
||||||
// Enable User Operation Write
|
// Enable User Operation Write
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
{BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
||||||
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
|
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
{BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
||||||
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
|
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
{BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
||||||
// 0x181C = Half cycle, Fast RMS threshold 6172
|
// 0x181C = Half cycle, Fast RMS threshold 6172
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
{BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
||||||
|
|
||||||
void BL0940::loop() {
|
void BL0940::loop() {
|
||||||
DataPacket buffer;
|
DataPacket buffer;
|
||||||
@@ -36,8 +34,8 @@ void BL0940::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
|
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
|
||||||
if (validate_checksum(&buffer)) {
|
if (this->validate_checksum_(&buffer)) {
|
||||||
received_package_(&buffer);
|
this->received_package_(&buffer);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
|
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
|
||||||
@@ -46,35 +44,151 @@ void BL0940::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BL0940::validate_checksum(const DataPacket *data) {
|
bool BL0940::validate_checksum_(DataPacket *data) {
|
||||||
uint8_t checksum = BL0940_READ_COMMAND;
|
uint8_t checksum = this->read_command_;
|
||||||
// Whole package but checksum
|
// Whole package but checksum
|
||||||
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
|
uint8_t *raw = (uint8_t *) data;
|
||||||
checksum += data->raw[i];
|
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
|
||||||
|
checksum += raw[i];
|
||||||
}
|
}
|
||||||
checksum ^= 0xFF;
|
checksum ^= 0xFF;
|
||||||
if (checksum != data->checksum) {
|
if (checksum != data->checksum) {
|
||||||
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
||||||
}
|
}
|
||||||
return checksum == data->checksum;
|
return checksum == data->checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BL0940::update() {
|
void BL0940::update() {
|
||||||
this->flush();
|
this->flush();
|
||||||
this->write_byte(BL0940_READ_COMMAND);
|
this->write_byte(this->read_command_);
|
||||||
this->write_byte(BL0940_FULL_PACKET);
|
this->write_byte(BL0940_FULL_PACKET);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BL0940::setup() {
|
void BL0940::setup() {
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
// add calibration callbacks
|
||||||
|
if (this->voltage_calibration_number_ != nullptr) {
|
||||||
|
this->voltage_calibration_number_->add_on_state_callback(
|
||||||
|
[this](float state) { this->voltage_calibration_callback_(state); });
|
||||||
|
if (this->voltage_calibration_number_->has_state()) {
|
||||||
|
this->voltage_calibration_callback_(this->voltage_calibration_number_->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->current_calibration_number_ != nullptr) {
|
||||||
|
this->current_calibration_number_->add_on_state_callback(
|
||||||
|
[this](float state) { this->current_calibration_callback_(state); });
|
||||||
|
if (this->current_calibration_number_->has_state()) {
|
||||||
|
this->current_calibration_callback_(this->current_calibration_number_->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->power_calibration_number_ != nullptr) {
|
||||||
|
this->power_calibration_number_->add_on_state_callback(
|
||||||
|
[this](float state) { this->power_calibration_callback_(state); });
|
||||||
|
if (this->power_calibration_number_->has_state()) {
|
||||||
|
this->power_calibration_callback_(this->power_calibration_number_->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->energy_calibration_number_ != nullptr) {
|
||||||
|
this->energy_calibration_number_->add_on_state_callback(
|
||||||
|
[this](float state) { this->energy_calibration_callback_(state); });
|
||||||
|
if (this->energy_calibration_number_->has_state()) {
|
||||||
|
this->energy_calibration_callback_(this->energy_calibration_number_->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// calculate calibrated reference values
|
||||||
|
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
|
||||||
|
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
|
||||||
|
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
|
||||||
|
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
|
||||||
|
|
||||||
for (auto *i : BL0940_INIT) {
|
for (auto *i : BL0940_INIT) {
|
||||||
this->write_array(i, 6);
|
this->write_byte(this->write_command_), this->write_array(i, 5);
|
||||||
delay(1);
|
delay(1);
|
||||||
}
|
}
|
||||||
this->flush();
|
this->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
|
float BL0940::calculate_power_reference_() {
|
||||||
auto tb = (float) (temperature.h << 8 | temperature.l);
|
// calculate power reference based on voltage and current reference
|
||||||
|
return this->voltage_reference_cal_ * this->current_reference_cal_ * 4046 / 324004 / 79931;
|
||||||
|
}
|
||||||
|
|
||||||
|
float BL0940::calculate_energy_reference_() {
|
||||||
|
// formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
|
||||||
|
// or: power_reference_ * 3600000 / (1638.4 * 256)
|
||||||
|
return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
|
||||||
|
|
||||||
|
void BL0940::reset_calibration() {
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) {
|
||||||
|
this->current_calibration_number_->make_call().set_value(0).perform();
|
||||||
|
}
|
||||||
|
if (this->voltage_calibration_number_ != nullptr && this->voltage_cal_ != 1) {
|
||||||
|
this->voltage_calibration_number_->make_call().set_value(0).perform();
|
||||||
|
}
|
||||||
|
if (this->power_calibration_number_ != nullptr && this->power_cal_ != 1) {
|
||||||
|
this->power_calibration_number_->make_call().set_value(0).perform();
|
||||||
|
}
|
||||||
|
if (this->energy_calibration_number_ != nullptr && this->energy_cal_ != 1) {
|
||||||
|
this->energy_calibration_number_->make_call().set_value(0).perform();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ESP_LOGD(TAG, "external calibration values restored to initial state");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0940::current_calibration_callback_(float state) {
|
||||||
|
this->current_cal_ = this->calculate_calibration_value_(state);
|
||||||
|
ESP_LOGV(TAG, "update current calibration state: %f", this->current_cal_);
|
||||||
|
this->recalibrate_();
|
||||||
|
}
|
||||||
|
void BL0940::voltage_calibration_callback_(float state) {
|
||||||
|
this->voltage_cal_ = this->calculate_calibration_value_(state);
|
||||||
|
ESP_LOGV(TAG, "update voltage calibration state: %f", this->voltage_cal_);
|
||||||
|
this->recalibrate_();
|
||||||
|
}
|
||||||
|
void BL0940::power_calibration_callback_(float state) {
|
||||||
|
this->power_cal_ = this->calculate_calibration_value_(state);
|
||||||
|
ESP_LOGV(TAG, "update power calibration state: %f", this->power_cal_);
|
||||||
|
this->recalibrate_();
|
||||||
|
}
|
||||||
|
void BL0940::energy_calibration_callback_(float state) {
|
||||||
|
this->energy_cal_ = this->calculate_calibration_value_(state);
|
||||||
|
ESP_LOGV(TAG, "update energy calibration state: %f", this->energy_cal_);
|
||||||
|
this->recalibrate_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0940::recalibrate_() {
|
||||||
|
ESP_LOGV(TAG, "Recalibrating reference values");
|
||||||
|
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
|
||||||
|
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
|
||||||
|
|
||||||
|
if (this->voltage_cal_ != 1 || this->current_cal_ != 1) {
|
||||||
|
this->power_reference_ = this->calculate_power_reference_();
|
||||||
|
}
|
||||||
|
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
|
||||||
|
|
||||||
|
if (this->voltage_cal_ != 1 || this->current_cal_ != 1 || this->power_cal_ != 1) {
|
||||||
|
this->energy_reference_ = this->calculate_energy_reference_();
|
||||||
|
}
|
||||||
|
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG,
|
||||||
|
"Recalibrated reference values:\n"
|
||||||
|
"Voltage: %f\n, Current: %f\n, Power: %f\n, Energy: %f\n",
|
||||||
|
this->voltage_reference_cal_, this->current_reference_cal_, this->power_reference_cal_,
|
||||||
|
this->energy_reference_cal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) const {
|
||||||
|
auto tb = (float) temperature;
|
||||||
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
|
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
|
||||||
if (sensor != nullptr) {
|
if (sensor != nullptr) {
|
||||||
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
|
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
|
||||||
@@ -87,33 +201,40 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
|
|||||||
return converted_temp;
|
return converted_temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BL0940::received_package_(const DataPacket *data) const {
|
void BL0940::received_package_(DataPacket *data) {
|
||||||
// Bad header
|
// Bad header
|
||||||
if (data->frame_header != BL0940_PACKET_HEADER) {
|
if (data->frame_header != BL0940_PACKET_HEADER) {
|
||||||
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
|
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
|
// cf_cnt is only 24 bits, so track overflows
|
||||||
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
|
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
|
||||||
float watt = (float) to_int32_t(data->watt) / power_reference_;
|
cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
|
||||||
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
|
if (cf_cnt < this->prev_cf_cnt_) {
|
||||||
float total_energy_consumption = (float) cf_cnt / energy_reference_;
|
cf_cnt += 0x1000000;
|
||||||
|
}
|
||||||
|
this->prev_cf_cnt_ = cf_cnt;
|
||||||
|
|
||||||
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
|
float v_rms = (uint24_t) data->v_rms / this->voltage_reference_cal_;
|
||||||
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
|
float i_rms = (uint24_t) data->i_rms / this->current_reference_cal_;
|
||||||
|
float watt = (int24_t) data->watt / this->power_reference_cal_;
|
||||||
|
float total_energy_consumption = cf_cnt / this->energy_reference_cal_;
|
||||||
|
|
||||||
if (voltage_sensor_ != nullptr) {
|
float tps1 = update_temp_(this->internal_temperature_sensor_, data->tps1);
|
||||||
voltage_sensor_->publish_state(v_rms);
|
float tps2 = update_temp_(this->external_temperature_sensor_, data->tps2);
|
||||||
|
|
||||||
|
if (this->voltage_sensor_ != nullptr) {
|
||||||
|
this->voltage_sensor_->publish_state(v_rms);
|
||||||
}
|
}
|
||||||
if (current_sensor_ != nullptr) {
|
if (this->current_sensor_ != nullptr) {
|
||||||
current_sensor_->publish_state(i_rms);
|
this->current_sensor_->publish_state(i_rms);
|
||||||
}
|
}
|
||||||
if (power_sensor_ != nullptr) {
|
if (this->power_sensor_ != nullptr) {
|
||||||
power_sensor_->publish_state(watt);
|
this->power_sensor_->publish_state(watt);
|
||||||
}
|
}
|
||||||
if (energy_sensor_ != nullptr) {
|
if (this->energy_sensor_ != nullptr) {
|
||||||
energy_sensor_->publish_state(total_energy_consumption);
|
this->energy_sensor_->publish_state(total_energy_consumption);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
|
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
|
||||||
@@ -121,7 +242,27 @@ void BL0940::received_package_(const DataPacket *data) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
|
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
|
||||||
ESP_LOGCONFIG(TAG, "BL0940:");
|
ESP_LOGCONFIG(TAG,
|
||||||
|
"BL0940:\n"
|
||||||
|
" LEGACY MODE: %s\n"
|
||||||
|
" READ CMD: 0x%02X\n"
|
||||||
|
" WRITE CMD: 0x%02X\n"
|
||||||
|
" ------------------\n"
|
||||||
|
" Current reference: %f\n"
|
||||||
|
" Energy reference: %f\n"
|
||||||
|
" Power reference: %f\n"
|
||||||
|
" Voltage reference: %f\n",
|
||||||
|
TRUEFALSE(this->legacy_mode_enabled_), this->read_command_, this->write_command_,
|
||||||
|
this->current_reference_, this->energy_reference_, this->power_reference_, this->voltage_reference_);
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
|
"BL0940:\n"
|
||||||
|
" Current calibration: %f\n"
|
||||||
|
" Energy calibration: %f\n"
|
||||||
|
" Power calibration: %f\n"
|
||||||
|
" Voltage calibration: %f\n",
|
||||||
|
this->current_cal_, this->energy_cal_, this->power_cal_, this->voltage_cal_);
|
||||||
|
#endif
|
||||||
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
|
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
|
||||||
LOG_SENSOR("", "Current", this->current_sensor_);
|
LOG_SENSOR("", "Current", this->current_sensor_);
|
||||||
LOG_SENSOR("", "Power", this->power_sensor_);
|
LOG_SENSOR("", "Power", this->power_sensor_);
|
||||||
@@ -130,9 +271,5 @@ void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexit
|
|||||||
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
|
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
|
||||||
|
|
||||||
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
|
||||||
|
|
||||||
} // namespace bl0940
|
} // namespace bl0940
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,66 +1,48 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/core/datatypes.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_BUTTON
|
||||||
|
#include "esphome/components/button/button.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
#include "esphome/components/number/number.h"
|
||||||
|
#endif
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bl0940 {
|
namespace bl0940 {
|
||||||
|
|
||||||
static const float BL0940_PREF = 1430;
|
|
||||||
static const float BL0940_UREF = 33000;
|
|
||||||
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
|
|
||||||
|
|
||||||
// Measured to 297J per click according to power consumption of 5 minutes
|
|
||||||
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
|
|
||||||
static const float BL0940_EREF = 3.6e6 / 297;
|
|
||||||
|
|
||||||
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
|
||||||
uint8_t l;
|
|
||||||
uint8_t m;
|
|
||||||
uint8_t h;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
|
||||||
uint8_t l;
|
|
||||||
uint8_t h;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
|
||||||
uint8_t l;
|
|
||||||
uint8_t m;
|
|
||||||
int8_t h;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
// Caveat: All these values are big endian (low - middle - high)
|
// Caveat: All these values are big endian (low - middle - high)
|
||||||
|
struct DataPacket {
|
||||||
union DataPacket { // NOLINT(altera-struct-pack-align)
|
uint8_t frame_header; // Packet header (0x58 in EN docs, 0x55 in CN docs and Tasmota tests)
|
||||||
uint8_t raw[35];
|
uint24_le_t i_fast_rms; // Fast RMS current
|
||||||
struct {
|
uint24_le_t i_rms; // RMS current
|
||||||
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
|
uint24_t RESERVED0; // Reserved
|
||||||
ube24_t i_fast_rms; // 0x00
|
uint24_le_t v_rms; // RMS voltage
|
||||||
ube24_t i_rms; // 0x04
|
uint24_t RESERVED1; // Reserved
|
||||||
ube24_t RESERVED0; // reserved
|
int24_le_t watt; // Active power (can be negative for bidirectional measurement)
|
||||||
ube24_t v_rms; // 0x06
|
uint24_t RESERVED2; // Reserved
|
||||||
ube24_t RESERVED1; // reserved
|
uint24_le_t cf_cnt; // Energy pulse count
|
||||||
sbe24_t watt; // 0x08
|
uint24_t RESERVED3; // Reserved
|
||||||
ube24_t RESERVED2; // reserved
|
uint16_le_t tps1; // Internal temperature sensor 1
|
||||||
ube24_t cf_cnt; // 0x0A
|
uint8_t RESERVED4; // Reserved (should be 0x00)
|
||||||
ube24_t RESERVED3; // reserved
|
uint16_le_t tps2; // Internal temperature sensor 2
|
||||||
ube16_t tps1; // 0x0c
|
uint8_t RESERVED5; // Reserved (should be 0x00)
|
||||||
uint8_t RESERVED4; // value of 0x00
|
uint8_t checksum; // Packet checksum
|
||||||
ube16_t tps2; // 0x0c
|
|
||||||
uint8_t RESERVED5; // value of 0x00
|
|
||||||
uint8_t checksum; // checksum
|
|
||||||
};
|
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
class BL0940 : public PollingComponent, public uart::UARTDevice {
|
class BL0940 : public PollingComponent, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
|
// Sensor setters
|
||||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||||
|
|
||||||
|
// Temperature sensor setters
|
||||||
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
|
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
|
||||||
internal_temperature_sensor_ = internal_temperature_sensor;
|
internal_temperature_sensor_ = internal_temperature_sensor;
|
||||||
}
|
}
|
||||||
@@ -68,42 +50,105 @@ class BL0940 : public PollingComponent, public uart::UARTDevice {
|
|||||||
external_temperature_sensor_ = external_temperature_sensor;
|
external_temperature_sensor_ = external_temperature_sensor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() override;
|
// Configuration setters
|
||||||
|
void set_legacy_mode(bool enable) { this->legacy_mode_enabled_ = enable; }
|
||||||
|
void set_read_command(uint8_t read_command) { this->read_command_ = read_command; }
|
||||||
|
void set_write_command(uint8_t write_command) { this->write_command_ = write_command; }
|
||||||
|
|
||||||
|
// Reference value setters (used for calibration and conversion)
|
||||||
|
void set_current_reference(float current_ref) { this->current_reference_ = current_ref; }
|
||||||
|
void set_energy_reference(float energy_ref) { this->energy_reference_ = energy_ref; }
|
||||||
|
void set_power_reference(float power_ref) { this->power_reference_ = power_ref; }
|
||||||
|
void set_voltage_reference(float voltage_ref) { this->voltage_reference_ = voltage_ref; }
|
||||||
|
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
// Calibration number setters (for Home Assistant number entities)
|
||||||
|
void set_current_calibration_number(number::Number *num) { this->current_calibration_number_ = num; }
|
||||||
|
void set_voltage_calibration_number(number::Number *num) { this->voltage_calibration_number_ = num; }
|
||||||
|
void set_power_calibration_number(number::Number *num) { this->power_calibration_number_ = num; }
|
||||||
|
void set_energy_calibration_number(number::Number *num) { this->energy_calibration_number_ = num; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_BUTTON
|
||||||
|
// Resets all calibration values to defaults (can be triggered by a button)
|
||||||
|
void reset_calibration();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Core component methods
|
||||||
|
void loop() override;
|
||||||
void update() override;
|
void update() override;
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
sensor::Sensor *voltage_sensor_{nullptr};
|
// --- Sensor pointers ---
|
||||||
sensor::Sensor *current_sensor_{nullptr};
|
sensor::Sensor *voltage_sensor_{nullptr}; // Voltage sensor
|
||||||
// NB This may be negative as the circuits is seemingly able to measure
|
sensor::Sensor *current_sensor_{nullptr}; // Current sensor
|
||||||
// power in both directions
|
sensor::Sensor *power_sensor_{nullptr}; // Power sensor (can be negative for bidirectional)
|
||||||
sensor::Sensor *power_sensor_{nullptr};
|
sensor::Sensor *energy_sensor_{nullptr}; // Energy sensor
|
||||||
sensor::Sensor *energy_sensor_{nullptr};
|
sensor::Sensor *internal_temperature_sensor_{nullptr}; // Internal temperature sensor
|
||||||
sensor::Sensor *internal_temperature_sensor_{nullptr};
|
sensor::Sensor *external_temperature_sensor_{nullptr}; // External temperature sensor
|
||||||
sensor::Sensor *external_temperature_sensor_{nullptr};
|
|
||||||
|
|
||||||
// Max difference between two measurements of the temperature. Used to avoid noise.
|
#ifdef USE_NUMBER
|
||||||
float max_temperature_diff_{0};
|
// --- Calibration number entities (for dynamic calibration via HA UI) ---
|
||||||
// Divide by this to turn into Watt
|
number::Number *voltage_calibration_number_{nullptr};
|
||||||
float power_reference_ = BL0940_PREF;
|
number::Number *current_calibration_number_{nullptr};
|
||||||
// Divide by this to turn into Volt
|
number::Number *power_calibration_number_{nullptr};
|
||||||
float voltage_reference_ = BL0940_UREF;
|
number::Number *energy_calibration_number_{nullptr};
|
||||||
// Divide by this to turn into Ampere
|
#endif
|
||||||
float current_reference_ = BL0940_IREF;
|
|
||||||
// Divide by this to turn into kWh
|
|
||||||
float energy_reference_ = BL0940_EREF;
|
|
||||||
|
|
||||||
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
|
// --- Internal state ---
|
||||||
|
uint32_t prev_cf_cnt_ = 0; // Previous energy pulse count (for energy calculation)
|
||||||
|
float max_temperature_diff_{0}; // Max allowed temperature difference between two measurements (noise filter)
|
||||||
|
|
||||||
static uint32_t to_uint32_t(ube24_t input);
|
// --- Reference values for conversion ---
|
||||||
|
float power_reference_; // Divider for raw power to get Watts
|
||||||
|
float power_reference_cal_; // Calibrated power reference
|
||||||
|
float voltage_reference_; // Divider for raw voltage to get Volts
|
||||||
|
float voltage_reference_cal_; // Calibrated voltage reference
|
||||||
|
float current_reference_; // Divider for raw current to get Amperes
|
||||||
|
float current_reference_cal_; // Calibrated current reference
|
||||||
|
float energy_reference_; // Divider for raw energy to get kWh
|
||||||
|
float energy_reference_cal_; // Calibrated energy reference
|
||||||
|
|
||||||
static int32_t to_int32_t(sbe24_t input);
|
// --- Home Assistant calibration values (multipliers, default 1) ---
|
||||||
|
float current_cal_{1};
|
||||||
|
float voltage_cal_{1};
|
||||||
|
float power_cal_{1};
|
||||||
|
float energy_cal_{1};
|
||||||
|
|
||||||
static bool validate_checksum(const DataPacket *data);
|
// --- Protocol commands ---
|
||||||
|
uint8_t read_command_;
|
||||||
|
uint8_t write_command_;
|
||||||
|
|
||||||
void received_package_(const DataPacket *data) const;
|
// --- Mode flags ---
|
||||||
|
bool legacy_mode_enabled_ = true;
|
||||||
|
|
||||||
|
// --- Methods ---
|
||||||
|
// Converts packed temperature value to float and updates the sensor
|
||||||
|
float update_temp_(sensor::Sensor *sensor, uint16_le_t packed_temperature) const;
|
||||||
|
|
||||||
|
// Validates the checksum of a received data packet
|
||||||
|
bool validate_checksum_(DataPacket *data);
|
||||||
|
|
||||||
|
// Handles a received data packet
|
||||||
|
void received_package_(DataPacket *data);
|
||||||
|
|
||||||
|
// Calculates reference values for calibration and conversion
|
||||||
|
float calculate_energy_reference_();
|
||||||
|
float calculate_power_reference_();
|
||||||
|
float calculate_calibration_value_(float state);
|
||||||
|
|
||||||
|
// Calibration update callbacks (used with number entities)
|
||||||
|
void current_calibration_callback_(float state);
|
||||||
|
void voltage_calibration_callback_(float state);
|
||||||
|
void power_calibration_callback_(float state);
|
||||||
|
void energy_calibration_callback_(float state);
|
||||||
|
void reset_calibration_callback_();
|
||||||
|
|
||||||
|
// Recalculates all reference values after calibration changes
|
||||||
|
void recalibrate_();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bl0940
|
} // namespace bl0940
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
27
esphome/components/bl0940/button/__init__.py
Normal file
27
esphome/components/bl0940/button/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import button
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
|
||||||
|
|
||||||
|
from .. import CONF_BL0940_ID, bl0940_ns
|
||||||
|
from ..sensor import BL0940
|
||||||
|
|
||||||
|
CalibrationResetButton = bl0940_ns.class_(
|
||||||
|
"CalibrationResetButton", button.Button, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
button.button_schema(
|
||||||
|
CalibrationResetButton,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
icon=ICON_RESTART,
|
||||||
|
)
|
||||||
|
.extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await button.new_button(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await cg.register_parented(var, config[CONF_BL0940_ID])
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include "calibration_reset_button.h"
|
||||||
|
#include "../bl0940.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
static const char *const TAG = "bl0940.button.calibration_reset";
|
||||||
|
|
||||||
|
void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); }
|
||||||
|
|
||||||
|
void CalibrationResetButton::press_action() {
|
||||||
|
ESP_LOGI(TAG, "Resetting calibration defaults...");
|
||||||
|
this->parent_->reset_calibration();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
||||||
19
esphome/components/bl0940/button/calibration_reset_button.h
Normal file
19
esphome/components/bl0940/button/calibration_reset_button.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/button/button.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
class BL0940; // Forward declaration of BL0940 class
|
||||||
|
|
||||||
|
class CalibrationResetButton : public button::Button, public Component, public Parented<BL0940> {
|
||||||
|
public:
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void press_action() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
||||||
94
esphome/components/bl0940/number/__init__.py
Normal file
94
esphome/components/bl0940/number/__init__.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import number
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_MAX_VALUE,
|
||||||
|
CONF_MIN_VALUE,
|
||||||
|
CONF_MODE,
|
||||||
|
CONF_RESTORE_VALUE,
|
||||||
|
CONF_STEP,
|
||||||
|
ENTITY_CATEGORY_CONFIG,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .. import CONF_BL0940_ID, bl0940_ns
|
||||||
|
from ..sensor import BL0940
|
||||||
|
|
||||||
|
# Define calibration types
|
||||||
|
CONF_CURRENT_CALIBRATION = "current_calibration"
|
||||||
|
CONF_VOLTAGE_CALIBRATION = "voltage_calibration"
|
||||||
|
CONF_POWER_CALIBRATION = "power_calibration"
|
||||||
|
CONF_ENERGY_CALIBRATION = "energy_calibration"
|
||||||
|
|
||||||
|
BL0940Number = bl0940_ns.class_("BL0940Number")
|
||||||
|
|
||||||
|
CalibrationNumber = bl0940_ns.class_(
|
||||||
|
"CalibrationNumber", number.Number, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_min_max(config):
|
||||||
|
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
|
||||||
|
raise cv.Invalid("max_value must be greater than min_value")
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
CALIBRATION_SCHEMA = cv.All(
|
||||||
|
number.number_schema(
|
||||||
|
CalibrationNumber,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES),
|
||||||
|
cv.Optional(CONF_MAX_VALUE, default=10): cv.All(
|
||||||
|
cv.float_, cv.Range(max=50)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_MIN_VALUE, default=-10): cv.All(
|
||||||
|
cv.float_, cv.Range(min=-50)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
|
||||||
|
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
|
validate_min_max,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configuration schema for BL0940 numbers
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BL0940Number),
|
||||||
|
cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940),
|
||||||
|
cv.Optional(CONF_CURRENT_CALIBRATION): CALIBRATION_SCHEMA,
|
||||||
|
cv.Optional(CONF_VOLTAGE_CALIBRATION): CALIBRATION_SCHEMA,
|
||||||
|
cv.Optional(CONF_POWER_CALIBRATION): CALIBRATION_SCHEMA,
|
||||||
|
cv.Optional(CONF_ENERGY_CALIBRATION): CALIBRATION_SCHEMA,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
# Get the BL0940 component instance
|
||||||
|
bl0940 = await cg.get_variable(config[CONF_BL0940_ID])
|
||||||
|
|
||||||
|
# Process all calibration types
|
||||||
|
for cal_type, setter_method in [
|
||||||
|
(CONF_CURRENT_CALIBRATION, "set_current_calibration_number"),
|
||||||
|
(CONF_VOLTAGE_CALIBRATION, "set_voltage_calibration_number"),
|
||||||
|
(CONF_POWER_CALIBRATION, "set_power_calibration_number"),
|
||||||
|
(CONF_ENERGY_CALIBRATION, "set_energy_calibration_number"),
|
||||||
|
]:
|
||||||
|
if conf := config.get(cal_type):
|
||||||
|
var = await number.new_number(
|
||||||
|
conf,
|
||||||
|
min_value=conf.get(CONF_MIN_VALUE),
|
||||||
|
max_value=conf.get(CONF_MAX_VALUE),
|
||||||
|
step=conf.get(CONF_STEP),
|
||||||
|
)
|
||||||
|
await cg.register_component(var, conf)
|
||||||
|
|
||||||
|
if restore_value := config.get(CONF_RESTORE_VALUE):
|
||||||
|
cg.add(var.set_restore_value(restore_value))
|
||||||
|
cg.add(getattr(bl0940, setter_method)(var))
|
||||||
29
esphome/components/bl0940/number/calibration_number.cpp
Normal file
29
esphome/components/bl0940/number/calibration_number.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include "calibration_number.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
static const char *const TAG = "bl0940.number";
|
||||||
|
|
||||||
|
void CalibrationNumber::setup() {
|
||||||
|
float value = 0.0f;
|
||||||
|
if (this->restore_value_) {
|
||||||
|
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
|
||||||
|
if (!this->pref_.load(&value)) {
|
||||||
|
value = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->publish_state(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalibrationNumber::control(float value) {
|
||||||
|
this->publish_state(value);
|
||||||
|
if (this->restore_value_)
|
||||||
|
this->pref_.save(&value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalibrationNumber::dump_config() { LOG_NUMBER("", "Calibration Number", this); }
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
||||||
26
esphome/components/bl0940/number/calibration_number.h
Normal file
26
esphome/components/bl0940/number/calibration_number.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/number/number.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
class CalibrationNumber : public number::Number, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void control(float value) override;
|
||||||
|
bool restore_value_{true};
|
||||||
|
|
||||||
|
ESPPreferenceObject pref_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
||||||
@@ -8,6 +8,7 @@ from esphome.const import (
|
|||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INTERNAL_TEMPERATURE,
|
CONF_INTERNAL_TEMPERATURE,
|
||||||
CONF_POWER,
|
CONF_POWER,
|
||||||
|
CONF_REFERENCE_VOLTAGE,
|
||||||
CONF_VOLTAGE,
|
CONF_VOLTAGE,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
@@ -23,12 +24,133 @@ from esphome.const import (
|
|||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from . import bl0940_ns
|
||||||
|
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
|
|
||||||
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
|
||||||
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
|
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
|
||||||
|
|
||||||
|
CONF_LEGACY_MODE = "legacy_mode"
|
||||||
|
|
||||||
|
CONF_READ_COMMAND = "read_command"
|
||||||
|
CONF_WRITE_COMMAND = "write_command"
|
||||||
|
|
||||||
|
CONF_RESISTOR_SHUNT = "resistor_shunt"
|
||||||
|
CONF_RESISTOR_ONE = "resistor_one"
|
||||||
|
CONF_RESISTOR_TWO = "resistor_two"
|
||||||
|
|
||||||
|
CONF_CURRENT_REFERENCE = "current_reference"
|
||||||
|
CONF_ENERGY_REFERENCE = "energy_reference"
|
||||||
|
CONF_POWER_REFERENCE = "power_reference"
|
||||||
|
CONF_VOLTAGE_REFERENCE = "voltage_reference"
|
||||||
|
|
||||||
|
DEFAULT_BL0940_READ_COMMAND = 0x58
|
||||||
|
DEFAULT_BL0940_WRITE_COMMAND = 0xA1
|
||||||
|
|
||||||
|
# Values according to BL0940 application note:
|
||||||
|
# https://www.belling.com.cn/media/file_object/bel_product/BL0940/guide/BL0940_APPNote_TSSOP14_V1.04_EN.pdf
|
||||||
|
DEFAULT_BL0940_VREF = 1.218 # Vref = 1.218
|
||||||
|
DEFAULT_BL0940_RL = 1 # RL = 1 mΩ
|
||||||
|
DEFAULT_BL0940_R1 = 0.51 # R1 = 0.51 kΩ
|
||||||
|
DEFAULT_BL0940_R2 = 1950 # R2 = 5 x 390 kΩ -> 1950 kΩ
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# values from initial implementation
|
||||||
|
DEFAULT_BL0940_LEGACY_READ_COMMAND = 0x50
|
||||||
|
DEFAULT_BL0940_LEGACY_WRITE_COMMAND = 0xA0
|
||||||
|
|
||||||
|
DEFAULT_BL0940_LEGACY_UREF = 33000
|
||||||
|
DEFAULT_BL0940_LEGACY_IREF = 275000
|
||||||
|
DEFAULT_BL0940_LEGACY_PREF = 1430
|
||||||
|
# Measured to 297J per click according to power consumption of 5 minutes
|
||||||
|
# Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
|
||||||
|
DEFAULT_BL0940_LEGACY_EREF = 3.6e6 / 297
|
||||||
|
# ----------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# methods to calculate voltage and current reference values
|
||||||
|
def calculate_voltage_reference(vref, r_one, r_two):
|
||||||
|
# formula: 79931 / Vref * (R1 * 1000) / (R1 + R2)
|
||||||
|
return 79931 / vref * (r_one * 1000) / (r_one + r_two)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_current_reference(vref, r_shunt):
|
||||||
|
# formula: 324004 * RL / Vref
|
||||||
|
return 324004 * r_shunt / vref
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_power_reference(voltage_reference, current_reference):
|
||||||
|
# calculate power reference based on voltage and current reference
|
||||||
|
return voltage_reference * current_reference * 4046 / 324004 / 79931
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_energy_reference(power_reference):
|
||||||
|
# formula: power_reference * 3600000 / (1638.4 * 256)
|
||||||
|
return power_reference * 3600000 / (1638.4 * 256)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_legacy_mode(config):
|
||||||
|
# Only allow schematic calibration options if legacy_mode is False
|
||||||
|
if config.get(CONF_LEGACY_MODE, True):
|
||||||
|
forbidden = [
|
||||||
|
CONF_REFERENCE_VOLTAGE,
|
||||||
|
CONF_RESISTOR_SHUNT,
|
||||||
|
CONF_RESISTOR_ONE,
|
||||||
|
CONF_RESISTOR_TWO,
|
||||||
|
]
|
||||||
|
for key in forbidden:
|
||||||
|
if key in config:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Option '{key}' is only allowed when legacy_mode: false"
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def set_command_defaults(config):
|
||||||
|
# Set defaults for read_command and write_command based on legacy_mode
|
||||||
|
legacy = config.get(CONF_LEGACY_MODE, True)
|
||||||
|
if legacy:
|
||||||
|
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_LEGACY_READ_COMMAND)
|
||||||
|
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_LEGACY_WRITE_COMMAND)
|
||||||
|
else:
|
||||||
|
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_READ_COMMAND)
|
||||||
|
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_WRITE_COMMAND)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def set_reference_values(config):
|
||||||
|
# Set default reference values based on legacy_mode
|
||||||
|
if config.get(CONF_LEGACY_MODE, True):
|
||||||
|
config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
|
||||||
|
config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
|
||||||
|
config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
|
||||||
|
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
|
||||||
|
else:
|
||||||
|
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
|
||||||
|
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
|
||||||
|
r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2)
|
||||||
|
r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL)
|
||||||
|
|
||||||
|
config.setdefault(
|
||||||
|
CONF_VOLTAGE_REFERENCE, calculate_voltage_reference(vref, r_one, r_two)
|
||||||
|
)
|
||||||
|
config.setdefault(
|
||||||
|
CONF_CURRENT_REFERENCE, calculate_current_reference(vref, r_shunt)
|
||||||
|
)
|
||||||
|
config.setdefault(
|
||||||
|
CONF_POWER_REFERENCE,
|
||||||
|
calculate_power_reference(
|
||||||
|
config.get(CONF_VOLTAGE_REFERENCE), config.get(CONF_CURRENT_REFERENCE)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
config.setdefault(
|
||||||
|
CONF_ENERGY_REFERENCE,
|
||||||
|
calculate_energy_reference(config.get(CONF_POWER_REFERENCE)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -69,10 +191,24 @@ CONFIG_SCHEMA = (
|
|||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_LEGACY_MODE, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_READ_COMMAND): cv.hex_uint8_t,
|
||||||
|
cv.Optional(CONF_WRITE_COMMAND): cv.hex_uint8_t,
|
||||||
|
cv.Optional(CONF_REFERENCE_VOLTAGE): cv.float_,
|
||||||
|
cv.Optional(CONF_RESISTOR_SHUNT): cv.float_,
|
||||||
|
cv.Optional(CONF_RESISTOR_ONE): cv.float_,
|
||||||
|
cv.Optional(CONF_RESISTOR_TWO): cv.float_,
|
||||||
|
cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
|
||||||
|
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
|
||||||
|
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
|
||||||
|
cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.polling_component_schema("60s"))
|
.extend(cv.polling_component_schema("60s"))
|
||||||
.extend(uart.UART_DEVICE_SCHEMA)
|
.extend(uart.UART_DEVICE_SCHEMA)
|
||||||
|
.add_extra(validate_legacy_mode)
|
||||||
|
.add_extra(set_command_defaults)
|
||||||
|
.add_extra(set_reference_values)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -99,3 +235,16 @@ async def to_code(config):
|
|||||||
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
|
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(external_temperature_config)
|
sens = await sensor.new_sensor(external_temperature_config)
|
||||||
cg.add(var.set_external_temperature_sensor(sens))
|
cg.add(var.set_external_temperature_sensor(sens))
|
||||||
|
|
||||||
|
# enable legacy mode
|
||||||
|
cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE)))
|
||||||
|
|
||||||
|
# Set bl0940 commands after validator has determined which defaults to use if not set
|
||||||
|
cg.add(var.set_read_command(config.get(CONF_READ_COMMAND)))
|
||||||
|
cg.add(var.set_write_command(config.get(CONF_WRITE_COMMAND)))
|
||||||
|
|
||||||
|
# Set reference values after validator has set the values either from defaults or calculated
|
||||||
|
cg.add(var.set_current_reference(config.get(CONF_CURRENT_REFERENCE)))
|
||||||
|
cg.add(var.set_voltage_reference(config.get(CONF_VOLTAGE_REFERENCE)))
|
||||||
|
cg.add(var.set_power_reference(config.get(CONF_POWER_REFERENCE)))
|
||||||
|
cg.add(var.set_energy_reference(config.get(CONF_ENERGY_REFERENCE)))
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ void BL0942::loop() {
|
|||||||
if (!avail) {
|
if (!avail) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (avail < sizeof(buffer)) {
|
if (static_cast<size_t>(avail) < sizeof(buffer)) {
|
||||||
if (!this->rx_start_) {
|
if (!this->rx_start_) {
|
||||||
this->rx_start_ = millis();
|
this->rx_start_ = millis();
|
||||||
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
|
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
|
||||||
@@ -148,8 +148,8 @@ void BL0942::setup() {
|
|||||||
|
|
||||||
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
|
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
|
||||||
|
|
||||||
if (this->read_reg_(BL0942_REG_MODE) != mode)
|
if (static_cast<uint32_t>(this->read_reg_(BL0942_REG_MODE)) != mode)
|
||||||
this->status_set_warning("BL0942 setup failed!");
|
this->status_set_warning(LOG_STR("BL0942 setup failed!"));
|
||||||
|
|
||||||
this->flush();
|
this->flush();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA),
|
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA),
|
||||||
esp32_ble_tracker.consume_connection_slots(1, "ble_client"),
|
esp32_ble.consume_connection_slots(1, "ble_client"),
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_BLE_CLIENT_ID = "ble_client_id"
|
CONF_BLE_CLIENT_ID = "ble_client_id"
|
||||||
@@ -286,6 +286,7 @@ async def remove_bond_to_code(config, action_id, template_arg, args):
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
# Register the loggers this component needs
|
# Register the loggers this component needs
|
||||||
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
|
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
|
||||||
|
cg.add_define("USE_ESP32_BLE_UUID")
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
cg.add(
|
cg.add(
|
||||||
@@ -63,6 +63,6 @@ def to_code(config):
|
|||||||
)
|
)
|
||||||
cg.add(var.set_char_uuid128(uuid128))
|
cg.add(var.set_char_uuid128(uuid128))
|
||||||
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
||||||
yield output.register_output(var, config)
|
await output.register_output(var, config)
|
||||||
yield ble_client.register_ble_node(var, config)
|
await ble_client.register_ble_node(var, config)
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
|
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||||
@@ -7,7 +9,9 @@ from esphome.const import CONF_ACTIVE, CONF_ID
|
|||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
||||||
DEPENDENCIES = ["api", "esp32"]
|
DEPENDENCIES = ["api", "esp32"]
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz", "@bdraco"]
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_CONNECTION_SLOTS = "connection_slots"
|
CONF_CONNECTION_SLOTS = "connection_slots"
|
||||||
CONF_CACHE_SERVICES = "cache_services"
|
CONF_CACHE_SERVICES = "cache_services"
|
||||||
@@ -38,9 +42,8 @@ def validate_connections(config):
|
|||||||
)
|
)
|
||||||
elif config[CONF_ACTIVE]:
|
elif config[CONF_ACTIVE]:
|
||||||
connection_slots: int = config[CONF_CONNECTION_SLOTS]
|
connection_slots: int = config[CONF_CONNECTION_SLOTS]
|
||||||
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
esp32_ble.consume_connection_slots(connection_slots, "bluetooth_proxy")(config)
|
||||||
config
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
**config,
|
**config,
|
||||||
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
||||||
@@ -53,20 +56,18 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
||||||
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||||
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
|
cv.Optional(CONF_CACHE_SERVICES, default=True): cv.boolean,
|
||||||
cv.only_with_esp_idf, cv.boolean
|
|
||||||
),
|
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_CONNECTION_SLOTS,
|
CONF_CONNECTION_SLOTS,
|
||||||
default=DEFAULT_CONNECTION_SLOTS,
|
default=DEFAULT_CONNECTION_SLOTS,
|
||||||
): cv.All(
|
): cv.All(
|
||||||
cv.positive_int,
|
cv.positive_int,
|
||||||
cv.Range(min=1, max=esp32_ble_tracker.max_connections()),
|
cv.Range(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS),
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_CONNECTIONS): cv.All(
|
cv.Optional(CONF_CONNECTIONS): cv.All(
|
||||||
cv.ensure_list(CONNECTION_SCHEMA),
|
cv.ensure_list(CONNECTION_SCHEMA),
|
||||||
cv.Length(min=1, max=esp32_ble_tracker.max_connections()),
|
cv.Length(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -91,6 +92,12 @@ async def to_code(config):
|
|||||||
connection_count = len(config.get(CONF_CONNECTIONS, []))
|
connection_count = len(config.get(CONF_CONNECTIONS, []))
|
||||||
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
|
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
|
||||||
|
|
||||||
|
# Define batch size for BLE advertisements
|
||||||
|
# Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
||||||
|
# 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
|
||||||
|
# This achieves ~97% WiFi MTU utilization while staying under the limit
|
||||||
|
cg.add_define("BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE", 16)
|
||||||
|
|
||||||
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
||||||
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
|
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||||
await cg.register_component(connection_var, connection_conf)
|
await cg.register_component(connection_var, connection_conf)
|
||||||
|
|||||||
@@ -12,16 +12,30 @@ namespace esphome::bluetooth_proxy {
|
|||||||
|
|
||||||
static const char *const TAG = "bluetooth_proxy.connection";
|
static const char *const TAG = "bluetooth_proxy.connection";
|
||||||
|
|
||||||
|
// This function is allocation-free and directly packs UUIDs into the output array
|
||||||
|
// using precalculated constants for the Bluetooth base UUID
|
||||||
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
|
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
|
||||||
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
|
// Bluetooth base UUID: 00000000-0000-1000-8000-00805F9B34FB
|
||||||
out[0] = ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
// out[0] = bytes 8-15 (big-endian)
|
||||||
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
// - For 128-bit UUIDs: use bytes 8-15 as-is
|
||||||
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
// - For 16/32-bit UUIDs: insert into bytes 12-15, use 0x00001000 for bytes 8-11
|
||||||
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
|
out[0] = uuid_source.len == ESP_UUID_LEN_128
|
||||||
out[1] = ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
? (((uint64_t) uuid_source.uuid.uuid128[15] << 56) | ((uint64_t) uuid_source.uuid.uuid128[14] << 48) |
|
||||||
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
((uint64_t) uuid_source.uuid.uuid128[13] << 40) | ((uint64_t) uuid_source.uuid.uuid128[12] << 32) |
|
||||||
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
((uint64_t) uuid_source.uuid.uuid128[11] << 24) | ((uint64_t) uuid_source.uuid.uuid128[10] << 16) |
|
||||||
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
|
((uint64_t) uuid_source.uuid.uuid128[9] << 8) | ((uint64_t) uuid_source.uuid.uuid128[8]))
|
||||||
|
: (((uint64_t) (uuid_source.len == ESP_UUID_LEN_16 ? uuid_source.uuid.uuid16 : uuid_source.uuid.uuid32)
|
||||||
|
<< 32) |
|
||||||
|
0x00001000ULL); // Base UUID bytes 8-11
|
||||||
|
// out[1] = bytes 0-7 (big-endian)
|
||||||
|
// - For 128-bit UUIDs: use bytes 0-7 as-is
|
||||||
|
// - For 16/32-bit UUIDs: use precalculated base UUID constant
|
||||||
|
out[1] = uuid_source.len == ESP_UUID_LEN_128
|
||||||
|
? ((uint64_t) uuid_source.uuid.uuid128[7] << 56) | ((uint64_t) uuid_source.uuid.uuid128[6] << 48) |
|
||||||
|
((uint64_t) uuid_source.uuid.uuid128[5] << 40) | ((uint64_t) uuid_source.uuid.uuid128[4] << 32) |
|
||||||
|
((uint64_t) uuid_source.uuid.uuid128[3] << 24) | ((uint64_t) uuid_source.uuid.uuid128[2] << 16) |
|
||||||
|
((uint64_t) uuid_source.uuid.uuid128[1] << 8) | ((uint64_t) uuid_source.uuid.uuid128[0])
|
||||||
|
: 0x800000805F9B34FBULL; // Base UUID bytes 0-7: 80-00-00-80-5F-9B-34-FB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to fill UUID in the appropriate format based on client support and UUID type
|
// Helper to fill UUID in the appropriate format based on client support and UUID type
|
||||||
@@ -80,9 +94,11 @@ void BluetoothConnection::dump_config() {
|
|||||||
|
|
||||||
void BluetoothConnection::update_allocated_slot_(uint64_t find_value, uint64_t set_value) {
|
void BluetoothConnection::update_allocated_slot_(uint64_t find_value, uint64_t set_value) {
|
||||||
auto &allocated = this->proxy_->connections_free_response_.allocated;
|
auto &allocated = this->proxy_->connections_free_response_.allocated;
|
||||||
auto *it = std::find(allocated.begin(), allocated.end(), find_value);
|
for (auto &slot : allocated) {
|
||||||
if (it != allocated.end()) {
|
if (slot == find_value) {
|
||||||
*it = set_value;
|
slot = set_value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,13 +121,24 @@ void BluetoothConnection::set_address(uint64_t address) {
|
|||||||
void BluetoothConnection::loop() {
|
void BluetoothConnection::loop() {
|
||||||
BLEClientBase::loop();
|
BLEClientBase::loop();
|
||||||
|
|
||||||
// Early return if no active connection or not in service discovery phase
|
// Early return if no active connection
|
||||||
if (this->address_ == 0 || this->send_service_ < 0 || this->send_service_ > this->service_count_) {
|
if (this->address_ == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle service discovery
|
// Handle service discovery if in valid range
|
||||||
|
if (this->send_service_ >= 0 && this->send_service_ <= this->service_count_) {
|
||||||
this->send_service_for_discovery_();
|
this->send_service_for_discovery_();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should disable the loop
|
||||||
|
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
|
||||||
|
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
|
||||||
|
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
|
||||||
|
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||||
|
this->send_service_ == DONE_SENDING_SERVICES)) {
|
||||||
|
this->disable_loop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
||||||
@@ -125,7 +152,7 @@ void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
|||||||
// to detect incomplete service discovery rather than relying on us to
|
// to detect incomplete service discovery rather than relying on us to
|
||||||
// tell them about a partial list.
|
// tell them about a partial list.
|
||||||
this->set_address(0);
|
this->set_address(0);
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = INIT_SENDING_SERVICES;
|
||||||
this->proxy_->send_connections_free();
|
this->proxy_->send_connections_free();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,10 +160,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
if (this->send_service_ >= this->service_count_) {
|
if (this->send_service_ >= this->service_count_) {
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
this->proxy_->send_gatt_services_done(this->address_);
|
this->proxy_->send_gatt_services_done(this->address_);
|
||||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
|
||||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
|
||||||
this->release_services();
|
this->release_services();
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,8 +209,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
||||||
|
|
||||||
if (char_count_status != ESP_GATT_OK) {
|
if (char_count_status != ESP_GATT_OK) {
|
||||||
ESP_LOGE(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_,
|
this->log_connection_error_("esp_ble_gattc_get_attr_count", char_count_status);
|
||||||
this->address_str().c_str(), char_count_status);
|
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -220,8 +243,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (char_status != ESP_GATT_OK) {
|
if (char_status != ESP_GATT_OK) {
|
||||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
|
this->log_connection_error_("esp_ble_gattc_get_all_char", char_status);
|
||||||
this->address_str().c_str(), char_status);
|
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -244,8 +266,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
||||||
|
|
||||||
if (desc_count_status != ESP_GATT_OK) {
|
if (desc_count_status != ESP_GATT_OK) {
|
||||||
ESP_LOGE(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
|
this->log_connection_error_("esp_ble_gattc_get_attr_count", desc_count_status);
|
||||||
this->connection_index_, this->address_str().c_str(), char_result.char_handle, desc_count_status);
|
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -266,8 +287,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (desc_status != ESP_GATT_OK) {
|
if (desc_status != ESP_GATT_OK) {
|
||||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_,
|
this->log_connection_error_("esp_ble_gattc_get_all_descr", desc_status);
|
||||||
this->address_str().c_str(), desc_status);
|
|
||||||
this->send_service_ = DONE_SENDING_SERVICES;
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -321,6 +341,33 @@ void BluetoothConnection::send_service_for_discovery_() {
|
|||||||
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BluetoothConnection::log_connection_error_(const char *operation, esp_gatt_status_t status) {
|
||||||
|
ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str().c_str(), operation,
|
||||||
|
status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothConnection::log_connection_warning_(const char *operation, esp_err_t err) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str().c_str(), operation, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothConnection::log_gatt_not_connected_(const char *action, const char *type) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str().c_str(),
|
||||||
|
action, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothConnection::log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str().c_str(),
|
||||||
|
operation, handle, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t BluetoothConnection::check_and_log_error_(const char *operation, esp_err_t err) {
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
this->log_connection_warning_(operation, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
||||||
@@ -328,10 +375,19 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
case ESP_GATTC_DISCONNECT_EVT: {
|
||||||
this->reset_connection_(param->disconnect.reason);
|
// Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
|
||||||
|
// This prevents race condition where we mark slot as free before controller cleanup is complete
|
||||||
|
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
param->disconnect.reason);
|
||||||
|
// Send disconnection notification but don't free the slot yet
|
||||||
|
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_CLOSE_EVT: {
|
case ESP_GATTC_CLOSE_EVT: {
|
||||||
|
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
param->close.reason);
|
||||||
|
// Now the GATT connection is fully closed and controller resources are freed
|
||||||
|
// Safe to mark the connection slot as available
|
||||||
this->reset_connection_(param->close.reason);
|
this->reset_connection_(param->close.reason);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -361,8 +417,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
case ESP_GATTC_READ_DESCR_EVT:
|
case ESP_GATTC_READ_DESCR_EVT:
|
||||||
case ESP_GATTC_READ_CHAR_EVT: {
|
case ESP_GATTC_READ_CHAR_EVT: {
|
||||||
if (param->read.status != ESP_GATT_OK) {
|
if (param->read.status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
this->log_gatt_operation_error_("reading char/descriptor", param->read.handle, param->read.status);
|
||||||
this->address_str_.c_str(), param->read.handle, param->read.status);
|
|
||||||
this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
|
this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -376,8 +431,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||||
if (param->write.status != ESP_GATT_OK) {
|
if (param->write.status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
this->log_gatt_operation_error_("writing char/descriptor", param->write.handle, param->write.status);
|
||||||
this->address_str_.c_str(), param->write.handle, param->write.status);
|
|
||||||
this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
|
this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -389,8 +443,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
}
|
}
|
||||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||||
if (param->unreg_for_notify.status != ESP_GATT_OK) {
|
if (param->unreg_for_notify.status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
|
this->log_gatt_operation_error_("unregistering notifications", param->unreg_for_notify.handle,
|
||||||
this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
|
|
||||||
param->unreg_for_notify.status);
|
param->unreg_for_notify.status);
|
||||||
this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
|
this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
|
||||||
break;
|
break;
|
||||||
@@ -403,8 +456,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
}
|
}
|
||||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||||
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
|
this->log_gatt_operation_error_("registering notifications", param->reg_for_notify.handle,
|
||||||
this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
|
param->reg_for_notify.status);
|
||||||
this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
|
this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -450,8 +503,7 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
|
|||||||
|
|
||||||
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
|
this->log_gatt_not_connected_("read", "characteristic");
|
||||||
this->address_str_.c_str());
|
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,76 +511,59 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
|||||||
handle);
|
handle);
|
||||||
|
|
||||||
esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (err != ERR_OK) {
|
return this->check_and_log_error_("esp_ble_gattc_read_char", err);
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
|
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8_t *data, size_t length,
|
||||||
|
bool response) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_,
|
this->log_gatt_not_connected_("write", "characteristic");
|
||||||
this->address_str_.c_str());
|
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||||
handle);
|
handle);
|
||||||
|
|
||||||
|
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
|
||||||
|
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
|
||||||
|
// const_cast is safe here and was previously hidden by a C-style cast
|
||||||
esp_err_t err =
|
esp_err_t err =
|
||||||
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
|
||||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (err != ERR_OK) {
|
return this->check_and_log_error_("esp_ble_gattc_write_char", err);
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
|
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_,
|
this->log_gatt_not_connected_("read", "descriptor");
|
||||||
this->address_str_.c_str());
|
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||||
handle);
|
handle);
|
||||||
|
|
||||||
esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (err != ERR_OK) {
|
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
|
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_,
|
this->log_gatt_not_connected_("write", "descriptor");
|
||||||
this->address_str_.c_str());
|
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||||
handle);
|
handle);
|
||||||
|
|
||||||
|
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
|
||||||
|
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
|
||||||
|
// const_cast is safe here and was previously hidden by a C-style cast
|
||||||
esp_err_t err = esp_ble_gattc_write_char_descr(
|
esp_err_t err = esp_ble_gattc_write_char_descr(
|
||||||
this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
|
||||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (err != ERR_OK) {
|
return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
|
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
|
||||||
if (!this->connected()) {
|
if (!this->connected()) {
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_,
|
this->log_gatt_not_connected_("notify", "characteristic");
|
||||||
this->address_str_.c_str());
|
|
||||||
return ESP_GATT_NOT_CONNECTED;
|
return ESP_GATT_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,22 +571,13 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
|
|||||||
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
|
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
|
||||||
this->address_str_.c_str(), handle);
|
this->address_str_.c_str(), handle);
|
||||||
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
||||||
if (err != ESP_OK) {
|
return this->check_and_log_error_("esp_ble_gattc_register_for_notify", err);
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), err);
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
|
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
|
||||||
this->address_str_.c_str(), handle);
|
this->address_str_.c_str(), handle);
|
||||||
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
||||||
if (err != ESP_OK) {
|
return this->check_and_log_error_("esp_ble_gattc_unregister_for_notify", err);
|
||||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_,
|
|
||||||
this->address_str_.c_str(), err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
|
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user