mirror of
https://github.com/esphome/esphome.git
synced 2026-06-26 18:23:40 +00:00
Compare commits
536 Commits
2026.3.0
...
remove-set
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8938728b8 | ||
|
|
3fbf0f0c01 | ||
|
|
1436d034bf | ||
|
|
08c7b3afbd | ||
|
|
f36d78e09c | ||
|
|
be56be5201 | ||
|
|
bcc7b8f490 | ||
|
|
27c662e73f | ||
|
|
eefbb42be4 | ||
|
|
b5c4449a16 | ||
|
|
5cdbbd4887 | ||
|
|
bdce47e764 | ||
|
|
813b142b72 | ||
|
|
b7dabe236e | ||
|
|
2e3ea2152d | ||
|
|
ea609d3552 | ||
|
|
f33fd047ee | ||
|
|
cc88896280 | ||
|
|
fbfb5d401f | ||
|
|
212b3e1688 | ||
|
|
31a70ab299 | ||
|
|
8f2cf8b8a7 | ||
|
|
600ca01fd3 | ||
|
|
65051153ac | ||
|
|
514c0c8331 | ||
|
|
dc634b8c7b | ||
|
|
66a4acafd0 | ||
|
|
3bf45d8fe0 | ||
|
|
9cd7c5e700 | ||
|
|
d79cf1d718 | ||
|
|
3d8a3a91f2 | ||
|
|
3fd3dcc7e5 | ||
|
|
7b5a4b466a | ||
|
|
92642df419 | ||
|
|
f5f99071fb | ||
|
|
cb15e98765 | ||
|
|
2f2c7ac393 | ||
|
|
d9788aaefc | ||
|
|
f7b410fd0c | ||
|
|
e261b5de65 | ||
|
|
954227b203 | ||
|
|
4a23ba7d8a | ||
|
|
b71c406e70 | ||
|
|
15bcd62f22 | ||
|
|
23dcc5389d | ||
|
|
9dca7e0daf | ||
|
|
66b6d36a26 | ||
|
|
2064eef273 | ||
|
|
64e836f9c8 | ||
|
|
2cb987095d | ||
|
|
da6c4e20fe | ||
|
|
26b426bbff | ||
|
|
2449aa75af | ||
|
|
2c9a3051d6 | ||
|
|
9b97e95cf3 | ||
|
|
c64bc24960 | ||
|
|
ceb3cb2ae7 | ||
|
|
a3913b98ba | ||
|
|
ef65e47bc5 | ||
|
|
53b2a03c80 | ||
|
|
58df755d8b | ||
|
|
c5eb0eb984 | ||
|
|
f25fa71235 | ||
|
|
8561a8c495 | ||
|
|
8688ef7125 | ||
|
|
46ea61666e | ||
|
|
8969eb76e9 | ||
|
|
ffee4c22b3 | ||
|
|
ad3f6ae313 | ||
|
|
b579758c46 | ||
|
|
45e6d49d36 | ||
|
|
ddb188e8f0 | ||
|
|
1a86e88373 | ||
|
|
31574a427b | ||
|
|
1bc6a8d956 | ||
|
|
d420e7bc23 | ||
|
|
cd3c2ae77e | ||
|
|
95b0e60617 | ||
|
|
ffbbe5eab3 | ||
|
|
18168ad7fd | ||
|
|
17afbeb87b | ||
|
|
d51b047f63 | ||
|
|
508ec295a4 | ||
|
|
66754fa376 | ||
|
|
4da7f5ecc2 | ||
|
|
29419d9d97 | ||
|
|
3520ef7480 | ||
|
|
d6475eaeed | ||
|
|
a9aaf29d83 | ||
|
|
38fa8925da | ||
|
|
c2b8ea3361 | ||
|
|
584807b039 | ||
|
|
5da3253f4b | ||
|
|
2a97eca00b | ||
|
|
1f3fd60d29 | ||
|
|
8a802ca666 | ||
|
|
a91e6d92f6 | ||
|
|
d9adb078aa | ||
|
|
7a7c33fdb1 | ||
|
|
b6abfec82e | ||
|
|
47774fb644 | ||
|
|
34410e92b7 | ||
|
|
a99f051e19 | ||
|
|
f6c63c62e4 | ||
|
|
76d75850a3 | ||
|
|
68d9f657ad | ||
|
|
24b8a95340 | ||
|
|
d245b9f123 | ||
|
|
a2dee21e8e | ||
|
|
3016cd3636 | ||
|
|
7532e1f957 | ||
|
|
f0db0c1054 | ||
|
|
05c15f4241 | ||
|
|
951ad91cb2 | ||
|
|
53bd57f3c2 | ||
|
|
4b9467cd0c | ||
|
|
0a607b9c93 | ||
|
|
810c046cc6 | ||
|
|
5a8d6931a8 | ||
|
|
0d67f91fac | ||
|
|
f9d41bd36a | ||
|
|
39509265bc | ||
|
|
2f3c21c7c1 | ||
|
|
d77bf23c76 | ||
|
|
f5cd1e5e76 | ||
|
|
a73c67e476 | ||
|
|
a95f9f41fb | ||
|
|
6ffb5af60c | ||
|
|
a5416df615 | ||
|
|
985477f2cf | ||
|
|
a4a8fa3027 | ||
|
|
623408bbfe | ||
|
|
514df6c99a | ||
|
|
54283a2599 | ||
|
|
4493d2efb6 | ||
|
|
83b3187126 | ||
|
|
a2d452684a | ||
|
|
2e42547d32 | ||
|
|
dea8fdd906 | ||
|
|
b41634e19a | ||
|
|
b0f6a94df5 | ||
|
|
1e65165e48 | ||
|
|
73e939ffb5 | ||
|
|
2d9922496c | ||
|
|
6feb2d04df | ||
|
|
f2fa97bfda | ||
|
|
90dafa3fa4 | ||
|
|
e77cdb5971 | ||
|
|
90e6c0d7c7 | ||
|
|
240e53afce | ||
|
|
fa8a609bcc | ||
|
|
6aafb521c1 | ||
|
|
81f0aa1168 | ||
|
|
3152642571 | ||
|
|
1e2c410abf | ||
|
|
a008c27fcf | ||
|
|
1edf952dda | ||
|
|
d9ada4536c | ||
|
|
bf89a191f0 | ||
|
|
c2456409bd | ||
|
|
02e23eb386 | ||
|
|
6898284361 | ||
|
|
f3a31be6d0 | ||
|
|
9260401747 | ||
|
|
80028ea1ad | ||
|
|
8a6b009173 | ||
|
|
676ac9d8b8 | ||
|
|
29e263ad7d | ||
|
|
a075f63b59 | ||
|
|
ec60da893f | ||
|
|
d8fbce365a | ||
|
|
f6c5767a83 | ||
|
|
19615f2eae | ||
|
|
c42c6745b9 | ||
|
|
65d0a91fcc | ||
|
|
a22d47c719 | ||
|
|
010516aef2 | ||
|
|
a15389318f | ||
|
|
5d67868ac6 | ||
|
|
e0d8000007 | ||
|
|
b66ff374a2 | ||
|
|
6c981e83db | ||
|
|
2355fcb44e | ||
|
|
f5bbff0b05 | ||
|
|
c45c9da771 | ||
|
|
7a40759567 | ||
|
|
af5b98c635 | ||
|
|
690dc324c9 | ||
|
|
26e78c840c | ||
|
|
9c9ae190ee | ||
|
|
238adbe008 | ||
|
|
f457b995f7 | ||
|
|
b6aec4fa25 | ||
|
|
9fb5b6aa15 | ||
|
|
752fe30332 | ||
|
|
4ff85e2a1e | ||
|
|
13baf26050 | ||
|
|
8751f348c8 | ||
|
|
22bc47da23 | ||
|
|
55df21db51 | ||
|
|
3cd50f0495 | ||
|
|
b3390d40fb | ||
|
|
7eddf429ea | ||
|
|
793813790a | ||
|
|
fe2c4e47bf | ||
|
|
df4318505f | ||
|
|
69911c3db1 | ||
|
|
8ad8f89e50 | ||
|
|
a3d9854704 | ||
|
|
13d3968d9b | ||
|
|
382de7ca90 | ||
|
|
a0d0516b22 | ||
|
|
0fb31726f6 | ||
|
|
e6a73cab8f | ||
|
|
bf6000ef3d | ||
|
|
332118db56 | ||
|
|
6956bf7e53 | ||
|
|
11b829dda1 | ||
|
|
1e16b30380 | ||
|
|
4c1363b104 | ||
|
|
9da0c5bc85 | ||
|
|
4b0c711f77 | ||
|
|
9385f16128 | ||
|
|
36d2e58b11 | ||
|
|
03d6b36fe0 | ||
|
|
3b5b51b4f0 | ||
|
|
e8c5dfca3e | ||
|
|
5a984b54cf | ||
|
|
43879964bd | ||
|
|
5560c9eef7 | ||
|
|
f4097d5a95 | ||
|
|
225330413a | ||
|
|
e67b5a78d0 | ||
|
|
baf365404c | ||
|
|
0de2c758aa | ||
|
|
597bb18543 | ||
|
|
ebdf20adc0 | ||
|
|
7ecdf6db2e | ||
|
|
8a3b5a8def | ||
|
|
98d9fd76b3 | ||
|
|
6992219e34 | ||
|
|
fbe3e7d99c | ||
|
|
9cdc17566a | ||
|
|
cd05462e9f | ||
|
|
83d02c602a | ||
|
|
e85065b1c4 | ||
|
|
d0e705d948 | ||
|
|
2c06464f7b | ||
|
|
84727b1f71 | ||
|
|
aef987dccf | ||
|
|
b2b61bea6a | ||
|
|
30f66be1da | ||
|
|
6caa9ee227 | ||
|
|
9152f77cdd | ||
|
|
4d09eb2cec | ||
|
|
5cc4f6e85a | ||
|
|
6d16c57747 | ||
|
|
27f3a5f5f4 | ||
|
|
45c0e6ef7f | ||
|
|
593dbc9e67 | ||
|
|
daafa8faa3 | ||
|
|
320474b62d | ||
|
|
a3c483edf3 | ||
|
|
036be63f7b | ||
|
|
bbfe324dd6 | ||
|
|
de3292c828 | ||
|
|
67ab2e143c | ||
|
|
9abc112f76 | ||
|
|
b5880df93c | ||
|
|
2352c732de | ||
|
|
77264de3f6 | ||
|
|
42da281854 | ||
|
|
06cc5a29a7 | ||
|
|
98b4e1ea15 | ||
|
|
0bf6e1e839 | ||
|
|
3fe84eadef | ||
|
|
12eed0d384 | ||
|
|
28e8250b69 | ||
|
|
0297260a57 | ||
|
|
d4f7cb984c | ||
|
|
08187a01b1 | ||
|
|
daf3502e15 | ||
|
|
08cab43548 | ||
|
|
5cbe936256 | ||
|
|
729d3d4bc2 | ||
|
|
8af0991590 | ||
|
|
99d968f80a | ||
|
|
705d548435 | ||
|
|
2b6d63fd09 | ||
|
|
c917b8ce06 | ||
|
|
12b10d8b89 | ||
|
|
6a77b8b1f4 | ||
|
|
ba4be2a904 | ||
|
|
ca0523b86c | ||
|
|
5e68282519 | ||
|
|
a0d5525312 | ||
|
|
c48fd0738b | ||
|
|
8224da3460 | ||
|
|
dd82a91d8f | ||
|
|
86ec218f75 | ||
|
|
2a6ec597b4 | ||
|
|
8dd69207ea | ||
|
|
d203a46ef8 | ||
|
|
1920d8a887 | ||
|
|
95dea59382 | ||
|
|
f3cddcee21 | ||
|
|
21e384cafd | ||
|
|
32db055b98 | ||
|
|
2c87260046 | ||
|
|
51ccad8461 | ||
|
|
7f500c4b6e | ||
|
|
564d155cb6 | ||
|
|
edf5542559 | ||
|
|
51335e8830 | ||
|
|
391ffe34f8 | ||
|
|
12ead0408a | ||
|
|
2d39cc2540 | ||
|
|
a9a8f4cb3b | ||
|
|
8fa2e75afa | ||
|
|
0b01f9fc42 | ||
|
|
ed8c062d9f | ||
|
|
5e516e78e4 | ||
|
|
896b6ec8c9 | ||
|
|
9e7cdaf475 | ||
|
|
a3fd1d5d00 | ||
|
|
7257bed1e9 | ||
|
|
5a9977cf5c | ||
|
|
12b3aec567 | ||
|
|
d59c006ff9 | ||
|
|
02ada93ea5 | ||
|
|
6e87f8eb4e | ||
|
|
7df550f2a9 | ||
|
|
b02f0e3c5f | ||
|
|
5f9dccace0 | ||
|
|
151f71e033 | ||
|
|
7ac001e994 | ||
|
|
de177d2445 | ||
|
|
a9cb7143dc | ||
|
|
902258b56e | ||
|
|
c2a96ea293 | ||
|
|
37a3c3ab3a | ||
|
|
a8ed781f3e | ||
|
|
63f0d054b7 | ||
|
|
e7dcf54a77 | ||
|
|
1ba5504944 | ||
|
|
5637116378 | ||
|
|
cdc4ba6295 | ||
|
|
d1aa1881bb | ||
|
|
14107ec452 | ||
|
|
2ca6681896 | ||
|
|
40a65d36b4 | ||
|
|
16ec237ac6 | ||
|
|
0afcdbfe73 | ||
|
|
b9439036d4 | ||
|
|
cb23f9453f | ||
|
|
0858ecbb8e | ||
|
|
96da6dd075 | ||
|
|
2c31bdc6a2 | ||
|
|
0a3393bed3 | ||
|
|
c2c50ceea7 | ||
|
|
2341d510d3 | ||
|
|
9d6f2f71e8 | ||
|
|
e1334cf57f | ||
|
|
a1aff7cadf | ||
|
|
2271ac6470 | ||
|
|
8fe36cde23 | ||
|
|
fdd5956c1e | ||
|
|
403ba262c6 | ||
|
|
f8be27ce6d | ||
|
|
a50d70c8d3 | ||
|
|
4d86049c21 | ||
|
|
44037c4f9b | ||
|
|
5856d05701 | ||
|
|
a2a048e3bf | ||
|
|
9f4c773963 | ||
|
|
ef0eef8117 | ||
|
|
097e6eb41f | ||
|
|
73a49493a2 | ||
|
|
4a93d5b544 | ||
|
|
cc0655a904 | ||
|
|
a859cb3cce | ||
|
|
47909d5299 | ||
|
|
16667bf5be | ||
|
|
ef3afe3e21 | ||
|
|
3a47317fc8 | ||
|
|
89066e3e20 | ||
|
|
9a80c980cb | ||
|
|
c9e6c85e6a | ||
|
|
e88c9ba066 | ||
|
|
45be290392 | ||
|
|
3f28ab88ca | ||
|
|
1d07f37d62 | ||
|
|
16c5224341 | ||
|
|
e83372e2f3 | ||
|
|
2531fb1a02 | ||
|
|
3e845d387a | ||
|
|
b9e8da92c7 | ||
|
|
0c5f055d45 | ||
|
|
342020e1d3 | ||
|
|
62f9bc79c4 | ||
|
|
53bfb02a21 | ||
|
|
83484a8828 | ||
|
|
ece235218f | ||
|
|
f3409acfa8 | ||
|
|
77b7201eb8 | ||
|
|
6b91df8d75 | ||
|
|
1670f04a87 | ||
|
|
1adf05e2d5 | ||
|
|
a94bb74d04 | ||
|
|
c19c75220b | ||
|
|
97382ed814 | ||
|
|
5f06679d78 | ||
|
|
851e8b6c0d | ||
|
|
9a729608d5 | ||
|
|
53fa346ddc | ||
|
|
b3210de374 | ||
|
|
82ccc37ba1 | ||
|
|
3826e95506 | ||
|
|
b083491e74 | ||
|
|
73ca0ff106 | ||
|
|
bba11b3b1e | ||
|
|
2142bc1b76 | ||
|
|
f81e04b036 | ||
|
|
c3327d0b43 | ||
|
|
8577c26358 | ||
|
|
80730fd012 | ||
|
|
5ee3e94ca1 | ||
|
|
037f75e0ff | ||
|
|
c47f4fbc1c | ||
|
|
2f86e48a83 | ||
|
|
0bbba75757 | ||
|
|
9362d9745e | ||
|
|
c8f708c13c | ||
|
|
05590a3a21 | ||
|
|
cdf2867baf | ||
|
|
b142557979 | ||
|
|
db405c483e | ||
|
|
808c7b67b3 | ||
|
|
7131eafc09 | ||
|
|
7b4af76a61 | ||
|
|
2cd93daa5e | ||
|
|
f86bb2bdb0 | ||
|
|
414182fe6d | ||
|
|
2ee0df1da3 | ||
|
|
e1252e32d1 | ||
|
|
1183ef825b | ||
|
|
c09edb94c1 | ||
|
|
9948adc6a0 | ||
|
|
ccb467b219 | ||
|
|
1377776d21 | ||
|
|
29501ef4f8 | ||
|
|
d97c23b8e3 | ||
|
|
92d5e7b18c | ||
|
|
15ce4b3616 | ||
|
|
33f9ad9cee | ||
|
|
18a082de30 | ||
|
|
7f418d969e | ||
|
|
fe9f19d9ed | ||
|
|
d37f8876d7 | ||
|
|
d7c42bc9ec | ||
|
|
efc508a82b | ||
|
|
0edc0fd9c8 | ||
|
|
cc4c13930f | ||
|
|
234ca7c951 | ||
|
|
447c4669b1 | ||
|
|
27942f1973 | ||
|
|
158a119a5a | ||
|
|
b126f3af3b | ||
|
|
d4e1e32a30 | ||
|
|
271b423b22 | ||
|
|
417858f098 | ||
|
|
c52042e023 | ||
|
|
f12531e7e0 | ||
|
|
ca279110c9 | ||
|
|
f2968e0449 | ||
|
|
0043be6165 | ||
|
|
0716c9f722 | ||
|
|
fcf5637aa5 | ||
|
|
5e3c44d48f | ||
|
|
d6d3bbbad8 | ||
|
|
86b7933081 | ||
|
|
7cceb72cc3 | ||
|
|
56f7b3e61b | ||
|
|
22062d79a2 | ||
|
|
ab3b677113 | ||
|
|
cdb445f69d | ||
|
|
1eed1adfa0 | ||
|
|
a6c08576be | ||
|
|
f41aa8b18c | ||
|
|
6700347a48 | ||
|
|
b147830ef9 | ||
|
|
bd844fcd0a | ||
|
|
8936be628f | ||
|
|
5920fa97e4 | ||
|
|
326769e43c | ||
|
|
7524590bcf | ||
|
|
15ec46abfe | ||
|
|
920af91db6 | ||
|
|
a744261934 | ||
|
|
59c1368440 | ||
|
|
7e8e085a04 | ||
|
|
22b25724ae | ||
|
|
89719cf4b2 | ||
|
|
e15b19b223 | ||
|
|
2ca13972b9 | ||
|
|
7bb4e75459 | ||
|
|
fd8e510745 | ||
|
|
25c74c8f99 | ||
|
|
05d285ba86 | ||
|
|
186ca4e458 | ||
|
|
618312f0ee | ||
|
|
70d188202a | ||
|
|
4a21afe7ce | ||
|
|
fd1d016795 | ||
|
|
03c091adfc | ||
|
|
a3a88acfcf | ||
|
|
07f8ae6c82 | ||
|
|
25c30ac5bb | ||
|
|
a76767a0ab | ||
|
|
511d185772 | ||
|
|
c4c19c8a6c | ||
|
|
fe2d60ccec | ||
|
|
657890695f | ||
|
|
8a5f008aee | ||
|
|
f8a22b87b8 | ||
|
|
7f38d95424 | ||
|
|
bb7d96b954 | ||
|
|
8daa946afa | ||
|
|
ddc40f44fa | ||
|
|
409640c0ee | ||
|
|
822c9161c6 | ||
|
|
a060f175ad | ||
|
|
73f305ff9c | ||
|
|
b6ff7185e7 | ||
|
|
928f6f1866 | ||
|
|
e7c3277eeb |
@@ -124,6 +124,28 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Indentation:** Use spaces (two per indentation level), not tabs
|
||||
* **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;`
|
||||
* **Line length:** Wrap lines at no more than 120 characters
|
||||
* **Constructor parameters vs setters:** Component properties that are both **required** and **invariant**
|
||||
(never change after construction) should be constructor parameters rather than set via setter methods.
|
||||
This makes the dependency explicit and prevents use of the object in an incompletely-initialized state.
|
||||
In code generation, when calling `cg.new_Pvariable()` or the relevant helper function to create the component, pass these as arguments.
|
||||
```cpp
|
||||
// Good - required invariant dependency as constructor parameter
|
||||
class SourceTextSensor : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
explicit SourceTextSensor(text::Text *source) : source_(source) {}
|
||||
protected:
|
||||
text::Text *source_;
|
||||
};
|
||||
```
|
||||
```cpp
|
||||
// Bad - required invariant dependency as setter
|
||||
class SourceTextSensor : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
void set_source(text::Text *source) { this->source_ = source; }
|
||||
protected:
|
||||
text::Text *source_{nullptr};
|
||||
};
|
||||
```
|
||||
|
||||
* **Component Structure:**
|
||||
* **Standard Files:**
|
||||
@@ -217,6 +239,123 @@ This document provides essential context for AI models interacting with this pro
|
||||
var = await switch.new_switch(config)
|
||||
```
|
||||
|
||||
* **Automations (Triggers, Actions, Conditions):**
|
||||
|
||||
Automations have three building blocks: **Triggers** (fire when something happens), **Actions** (do something), and **Conditions** (check if something is true).
|
||||
|
||||
* **Triggers -- Callback method (preferred):**
|
||||
|
||||
Use `build_callback_automation()` for simple triggers. This eliminates the need for a C++ Trigger class by using a lightweight pointer-sized forwarder struct registered directly as a callback. No `CONF_TRIGGER_ID` in the schema.
|
||||
|
||||
**Python:**
|
||||
```python
|
||||
from esphome import automation
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(MyComponent),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [(bool, "x")], conf
|
||||
)
|
||||
```
|
||||
|
||||
`build_callback_automation` arguments: `parent`, `callback_method` (C++ method name), `args` (template args as `[(type, name)]` tuples), `config`, and optional `forwarder` (defaults to `TriggerForwarder<Ts...>`).
|
||||
|
||||
For boolean filtering (e.g. `on_press`/`on_release`), use built-in forwarders with `args=[]`:
|
||||
```python
|
||||
for conf_key, forwarder in (
|
||||
(CONF_ON_PRESS, automation.TriggerOnTrueForwarder),
|
||||
(CONF_ON_RELEASE, automation.TriggerOnFalseForwarder),
|
||||
):
|
||||
for conf in config.get(conf_key, []):
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [], conf, forwarder=forwarder
|
||||
)
|
||||
```
|
||||
|
||||
**C++ -- no trigger class needed.** The callback registration method must be templatized to accept both `std::function` and lightweight forwarder structs (which avoid heap allocation):
|
||||
```cpp
|
||||
class MyComponent : public Component {
|
||||
public:
|
||||
// Must be a template -- accepts both std::function and pointer-sized forwarder structs
|
||||
template<typename F> void add_on_state_callback(F &&callback) {
|
||||
this->state_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
protected:
|
||||
// Use CallbackManager when callbacks are always registered (e.g. core components)
|
||||
CallbackManager<void(bool)> state_callback_;
|
||||
// Use LazyCallbackManager when callbacks are often not registered -- saves 8 bytes
|
||||
// (nullptr vs empty std::vector) per instance when no callbacks are added
|
||||
// LazyCallbackManager<void(bool)> state_callback_;
|
||||
};
|
||||
```
|
||||
|
||||
* **Triggers -- Trigger class method:**
|
||||
|
||||
Use `build_automation()` with a `Trigger<Ts...>` subclass only when the forwarder needs **mutable state beyond a single `Automation*` pointer** (e.g. edge detection tracking previous state, timing logic).
|
||||
|
||||
**Python:**
|
||||
```python
|
||||
TurnOnTrigger = my_ns.class_("TurnOnTrigger", automation.Trigger.template())
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TurnOnTrigger)}
|
||||
),
|
||||
})
|
||||
|
||||
async def to_code(config):
|
||||
for conf in config.get(CONF_ON_TURN_ON, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
```
|
||||
|
||||
**C++:**
|
||||
```cpp
|
||||
class TurnOnTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit TurnOnTrigger(MyComponent *parent) : last_on_{false} {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (state && !this->last_on_)
|
||||
this->trigger();
|
||||
this->last_on_ = state;
|
||||
});
|
||||
}
|
||||
protected:
|
||||
bool last_on_;
|
||||
};
|
||||
```
|
||||
|
||||
* **Actions:**
|
||||
```cpp
|
||||
template<typename... Ts> class MyAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit MyAction(MyComponent *parent) : parent_(parent) {}
|
||||
void play(const Ts &...) override { this->parent_->do_something(); }
|
||||
protected:
|
||||
MyComponent *parent_;
|
||||
};
|
||||
```
|
||||
Register with `@automation.register_action("my_component.do_something", MyAction, schema, synchronous=True)`. Use `synchronous=True` for actions that run to completion inside `play()` without deferring. Use `synchronous=False` if the action may suspend/defer execution (e.g. `delay`, `wait_until`, `script.wait`) or store trigger arguments for later use.
|
||||
|
||||
* **Conditions:**
|
||||
```cpp
|
||||
template<typename... Ts> class MyCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit MyCondition(MyComponent *parent) : parent_(parent) {}
|
||||
bool check(const Ts &...) override { return this->parent_->is_active(); }
|
||||
protected:
|
||||
MyComponent *parent_;
|
||||
};
|
||||
```
|
||||
Register with `@automation.register_condition("my_component.is_active", MyCondition, schema)`.
|
||||
|
||||
* **Configuration Validation:**
|
||||
* **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)`.
|
||||
@@ -252,10 +391,39 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Component Tests:** YAML-based compilation tests are located in `tests/`. The structure is as follows:
|
||||
```
|
||||
tests/
|
||||
├── test_build_components/ # Base test configurations
|
||||
└── components/[component]/ # Component-specific tests
|
||||
├── test_build_components/
|
||||
│ └── common/ # Shared bus packages (uart, i2c, spi, etc.)
|
||||
│ ├── uart/ # UART at default baud rate
|
||||
│ ├── uart_115200/ # UART at 115200 baud
|
||||
│ ├── i2c/ # I2C bus
|
||||
│ └── spi/ # SPI bus
|
||||
└── components/[component]/
|
||||
├── common.yaml # Component-only config (no bus definitions)
|
||||
├── test.esp32-idf.yaml
|
||||
├── test.esp8266-ard.yaml
|
||||
└── test.rp2040-ard.yaml
|
||||
```
|
||||
Run them using `script/test_build_components`. Use `-c <component>` to test specific components and `-t <target>` for specific platforms.
|
||||
|
||||
* **Test Grouping with Packages:** Components that use shared bus packages can be grouped together in CI to reduce build count. **Never define buses (uart, i2c, spi, modbus) directly in test YAML files** — always use packages from `test_build_components/common/`:
|
||||
```yaml
|
||||
# test.esp32-idf.yaml — use packages for buses
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart_115200/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
```
|
||||
```yaml
|
||||
# common.yaml — component config only, NO bus definitions
|
||||
my_component:
|
||||
id: my_instance
|
||||
|
||||
sensor:
|
||||
- platform: my_component
|
||||
name: My Sensor
|
||||
```
|
||||
Components that define buses directly are flagged as "NEEDS MIGRATION" and cannot be grouped, increasing CI build time.
|
||||
|
||||
* **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
|
||||
@@ -395,6 +563,30 @@ This document provides essential context for AI models interacting with this pro
|
||||
|
||||
Note: Avoiding heap allocation after `setup()` is always required regardless of component type. The prioritization above is about the effort spent on container optimization (e.g., migrating from `std::vector` to `StaticVector`).
|
||||
|
||||
**Callback Managers:**
|
||||
|
||||
ESPHome provides two callback manager types in `esphome/core/helpers.h` for the observer pattern. Both support `std::function`, lambdas, and lightweight forwarder structs via their templatized `add()` method.
|
||||
|
||||
| Type | Idle overhead (32-bit) | When to use |
|
||||
|------|----------------------|-------------|
|
||||
| `CallbackManager<void(Ts...)>` | 12 bytes (empty `std::vector`) | Callbacks are always or almost always registered |
|
||||
| `LazyCallbackManager<void(Ts...)>` | 4 bytes (`nullptr`) | Callbacks are often not registered (common case) |
|
||||
|
||||
`LazyCallbackManager` is a drop-in replacement for `CallbackManager` that defers allocation until the first callback is added. Prefer it for entity-level callbacks where most instances have no subscribers.
|
||||
|
||||
**Important:** Registration methods that add to a callback manager **must always be templatized** to accept both `std::function` and pointer-sized forwarder structs (used by `build_callback_automation`). Never use `std::function` in the method signature:
|
||||
```cpp
|
||||
// Bad -- forces heap allocation for forwarder structs
|
||||
void add_on_state_callback(std::function<void(bool)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// Good -- accepts any callable without forcing std::function wrapping
|
||||
template<typename F> void add_on_state_callback(F &&callback) {
|
||||
this->state_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
```
|
||||
|
||||
* **State Management:** Use `CORE.data` for component state that needs to persist during configuration generation. Avoid module-level mutable globals.
|
||||
|
||||
**Bad Pattern (Module-Level Globals):**
|
||||
|
||||
@@ -1 +1 @@
|
||||
8e48e836c6fc196d3da000d46eb09db243b87fe33518a74e49c8e009d756074a
|
||||
f31f13994768b5b07e29624406c9b053bf4bb26e1623ac2bc1e9d4a9477502d6
|
||||
|
||||
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@@ -22,7 +22,7 @@ runs:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
||||
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
|
||||
4
.github/workflows/ci-clang-tidy-hash.yml
vendored
4
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
echo "You have modified clang-tidy configuration but have not updated the hash." | tee -a $GITHUB_STEP_SUMMARY
|
||||
echo "Please run 'script/clang_tidy_hash.py --update' and commit the changes." | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
- if: failure()
|
||||
- if: failure() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Request changes
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
body: 'You have modified clang-tidy configuration but have not updated the hash.\nPlease run `script/clang_tidy_hash.py --update` and commit the changes.'
|
||||
})
|
||||
|
||||
- if: success()
|
||||
- if: success() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
|
||||
90
.github/workflows/ci.yml
vendored
90
.github/workflows/ci.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -106,6 +106,7 @@ jobs:
|
||||
script/build_codeowners.py --check
|
||||
script/build_language_schema.py --check
|
||||
script/generate-esp32-boards.py --check
|
||||
script/generate-rp2040-boards.py --check
|
||||
|
||||
pytest:
|
||||
name: Run pytest
|
||||
@@ -153,12 +154,12 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Save Python virtual environment cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -170,6 +171,8 @@ jobs:
|
||||
- common
|
||||
outputs:
|
||||
integration-tests: ${{ steps.determine.outputs.integration-tests }}
|
||||
integration-tests-run-all: ${{ steps.determine.outputs.integration-tests-run-all }}
|
||||
integration-test-files: ${{ steps.determine.outputs.integration-test-files }}
|
||||
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
||||
clang-tidy-mode: ${{ steps.determine.outputs.clang-tidy-mode }}
|
||||
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||
@@ -182,6 +185,7 @@ jobs:
|
||||
cpp-unit-tests-run-all: ${{ steps.determine.outputs.cpp-unit-tests-run-all }}
|
||||
cpp-unit-tests-components: ${{ steps.determine.outputs.cpp-unit-tests-components }}
|
||||
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
||||
benchmarks: ${{ steps.determine.outputs.benchmarks }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -194,7 +198,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Restore components graph cache
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -210,6 +214,8 @@ jobs:
|
||||
|
||||
# Extract individual fields
|
||||
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
|
||||
echo "integration-tests-run-all=$(echo "$output" | jq -r '.integration_tests_run_all')" >> $GITHUB_OUTPUT
|
||||
echo "integration-test-files=$(echo "$output" | jq -c '.integration_test_files')" >> $GITHUB_OUTPUT
|
||||
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
||||
echo "clang-tidy-mode=$(echo "$output" | jq -r '.clang_tidy_mode')" >> $GITHUB_OUTPUT
|
||||
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||
@@ -222,9 +228,10 @@ jobs:
|
||||
echo "cpp-unit-tests-run-all=$(echo "$output" | jq -r '.cpp_unit_tests_run_all')" >> $GITHUB_OUTPUT
|
||||
echo "cpp-unit-tests-components=$(echo "$output" | jq -c '.cpp_unit_tests_components')" >> $GITHUB_OUTPUT
|
||||
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
||||
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
|
||||
- name: Save components graph cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -246,7 +253,7 @@ jobs:
|
||||
python-version: "3.13"
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -261,9 +268,20 @@ jobs:
|
||||
- name: Register matcher
|
||||
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
||||
- name: Run integration tests
|
||||
env:
|
||||
INTEGRATION_TEST_FILES: ${{ needs.determine-jobs.outputs.integration-test-files }}
|
||||
INTEGRATION_TESTS_RUN_ALL: ${{ needs.determine-jobs.outputs.integration-tests-run-all }}
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pytest -vv --no-cov --tb=native -n auto tests/integration/
|
||||
if [[ "$INTEGRATION_TESTS_RUN_ALL" == "true" ]]; then
|
||||
echo "Running all integration tests"
|
||||
pytest -vv --no-cov --tb=native -n auto tests/integration/
|
||||
else
|
||||
# Parse JSON array into bash array to avoid shell expansion issues
|
||||
mapfile -t test_files < <(echo "$INTEGRATION_TEST_FILES" | jq -r '.[]')
|
||||
echo "Running ${#test_files[@]} specific integration tests"
|
||||
pytest -vv --no-cov --tb=native -n auto "${test_files[@]}"
|
||||
fi
|
||||
|
||||
cpp-unit-tests:
|
||||
name: Run C++ unit tests
|
||||
@@ -292,6 +310,40 @@ jobs:
|
||||
script/cpp_unit_test.py $ARGS
|
||||
fi
|
||||
|
||||
benchmarks:
|
||||
name: Run CodSpeed benchmarks
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: >-
|
||||
(github.event_name == 'push' && github.ref_name == 'dev') ||
|
||||
(github.event_name == 'pull_request' && needs.determine-jobs.outputs.benchmarks == 'true')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Build benchmarks
|
||||
id: build
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
export BENCHMARK_LIB_CONFIG=$(python script/setup_codspeed_lib.py)
|
||||
# --build-only prints BUILD_BINARY=<path> to stdout
|
||||
BINARY=$(script/cpp_benchmark.py --all --build-only | grep '^BUILD_BINARY=' | tail -1 | cut -d= -f2-)
|
||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@d872884a306dd4853acf0f584f4b706cf0cc72a2 # v4
|
||||
with:
|
||||
run: ${{ steps.build.outputs.binary }}
|
||||
mode: simulation
|
||||
|
||||
clang-tidy-single:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -335,14 +387,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -414,14 +466,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
@@ -503,14 +555,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
@@ -765,7 +817,7 @@ jobs:
|
||||
- name: Restore cached memory analysis
|
||||
id: cache-memory-analysis
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -789,7 +841,7 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -830,7 +882,7 @@ jobs:
|
||||
|
||||
- name: Save memory analysis to cache
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -877,7 +929,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -945,13 +997,13 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Download target analysis JSON
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: memory-analysis-target
|
||||
path: ./memory-analysis
|
||||
continue-on-error: true
|
||||
- name: Download PR analysis JSON
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: memory-analysis-pr
|
||||
path: ./memory-analysis
|
||||
|
||||
@@ -10,6 +10,9 @@ name: Codeowner Approved Label
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches-ignore:
|
||||
- release
|
||||
- beta
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
@@ -13,6 +13,9 @@ on:
|
||||
# Needs to be pull_request_target to get write permissions
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
branches-ignore:
|
||||
- release
|
||||
- beta
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
12
.github/workflows/pr-title-check.yml
vendored
12
.github/workflows/pr-title-check.yml
vendored
@@ -65,6 +65,18 @@ jobs:
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for angle brackets not wrapped in backticks.
|
||||
// Astro docs MDX treats bare < as JSX component opening tags.
|
||||
const stripped = title.replace(/`[^`]*`/g, '');
|
||||
if (/[<>]/.test(stripped)) {
|
||||
core.setFailed(
|
||||
'PR title contains `<` or `>` not wrapped in backticks.\n' +
|
||||
'Astro docs MDX interprets bare `<` as JSX components.\n' +
|
||||
'Please wrap angle brackets with backticks, e.g.: [component] Add `<feature>` support'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check title starts with [tag] prefix
|
||||
const bracketPattern = /^\[\w+\]/;
|
||||
if (!bracketPattern.test(title)) {
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -171,7 +171,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: digests-*
|
||||
path: /tmp/digests
|
||||
@@ -221,7 +221,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
|
||||
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Install Home Assistant
|
||||
run: |
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.15.5
|
||||
rev: v0.15.8
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -92,6 +92,7 @@ esphome/components/bmp3xx_i2c/* @latonita
|
||||
esphome/components/bmp3xx_spi/* @latonita
|
||||
esphome/components/bmp581_base/* @danielkent-net @kahrendt
|
||||
esphome/components/bmp581_i2c/* @danielkent-net @kahrendt
|
||||
esphome/components/bmp581_spi/* @danielkent-net @kahrendt
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/bthome_mithermometer/* @nagyrobi
|
||||
@@ -216,6 +217,7 @@ esphome/components/hbridge/light/* @DotNetDann
|
||||
esphome/components/hbridge/switch/* @dwmw2
|
||||
esphome/components/hc8/* @omartijn
|
||||
esphome/components/hdc2010/* @optimusprimespace @ssieb
|
||||
esphome/components/hdc2080/* @G-Pereira @jesserockz
|
||||
esphome/components/hdc302x/* @joshuasing
|
||||
esphome/components/he60r/* @clydebarrow
|
||||
esphome/components/heatpumpir/* @rob-deutsch
|
||||
@@ -244,7 +246,6 @@ esphome/components/hyt271/* @Philippe12
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2c_device/* @gabest11
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/i2s_audio/media_player/* @jesserockz
|
||||
esphome/components/i2s_audio/microphone/* @jesserockz
|
||||
esphome/components/i2s_audio/speaker/* @jesserockz @kahrendt
|
||||
esphome/components/iaqcore/* @yozik04
|
||||
@@ -330,6 +331,7 @@ esphome/components/mipi_dsi/* @clydebarrow
|
||||
esphome/components/mipi_rgb/* @clydebarrow
|
||||
esphome/components/mipi_spi/* @clydebarrow
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mitsubishi_cn105/* @crnjan
|
||||
esphome/components/mixer/speaker/* @kahrendt
|
||||
esphome/components/mlx90393/* @functionpointer
|
||||
esphome/components/mlx90614/* @jesserockz
|
||||
@@ -458,6 +460,9 @@ esphome/components/sn74hc165/* @jesserockz
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/sound_level/* @kahrendt
|
||||
esphome/components/spa06_base/* @danielkent-net
|
||||
esphome/components/spa06_i2c/* @danielkent-net
|
||||
esphome/components/spa06_spi/* @danielkent-net
|
||||
esphome/components/speaker/* @jesserockz @kahrendt
|
||||
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
|
||||
esphome/components/speaker_source/* @kahrendt
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2026.3.0
|
||||
PROJECT_NUMBER = 2026.4.0-dev
|
||||
|
||||
# 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
|
||||
# ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/) [](https://codspeed.io/esphome/esphome)
|
||||
|
||||
<a href="https://esphome.io/">
|
||||
<picture>
|
||||
|
||||
@@ -1046,7 +1046,11 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
|
||||
):
|
||||
from esphome.components.api.client import run_logs
|
||||
|
||||
return run_logs(config, network_devices)
|
||||
return run_logs(
|
||||
config,
|
||||
network_devices,
|
||||
subscribe_states=not getattr(args, "no_states", False),
|
||||
)
|
||||
|
||||
if port_type in (PortType.NETWORK, PortType.MQTT) and has_mqtt_logging():
|
||||
from esphome import mqtt
|
||||
@@ -1664,6 +1668,11 @@ def parse_args(argv):
|
||||
help="Reset the device before starting serial logs.",
|
||||
default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
|
||||
)
|
||||
parser_logs.add_argument(
|
||||
"--no-states",
|
||||
action="store_true",
|
||||
help="Do not show entity state changes in log output.",
|
||||
)
|
||||
|
||||
parser_discover = subparsers.add_parser(
|
||||
"discover",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Memory usage analyzer for ESPHome compiled binaries."""
|
||||
|
||||
from collections import defaultdict
|
||||
from collections import Counter, defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
from pathlib import Path
|
||||
@@ -40,6 +40,15 @@ _READELF_SECTION_PATTERN = re.compile(
|
||||
r"\s*\[\s*\d+\]\s+([\.\w]+)\s+\w+\s+[\da-fA-F]+\s+[\da-fA-F]+\s+([\da-fA-F]+)"
|
||||
)
|
||||
|
||||
# Regex for extracting call targets from objdump disassembly
|
||||
# Matches direct call instructions across architectures:
|
||||
# Xtensa: call0/call4/call8/call12/callx0/callx4/callx8/callx12 <addr> <symbol>
|
||||
# ARM: bl/blx <addr> <symbol>
|
||||
# Captures the mangled symbol name inside angle brackets.
|
||||
_CALL_TARGET_PATTERN = re.compile(
|
||||
r"\t(?:call(?:0|4|8|12)|callx(?:0|4|8|12)|blx?)\s+[\da-fA-F]+ <([^>]+)>"
|
||||
)
|
||||
|
||||
# Component category prefixes
|
||||
_COMPONENT_PREFIX_ESPHOME = "[esphome]"
|
||||
_COMPONENT_PREFIX_EXTERNAL = "[external]"
|
||||
@@ -47,6 +56,10 @@ _COMPONENT_PREFIX_LIB = "[lib]"
|
||||
_COMPONENT_CORE = f"{_COMPONENT_PREFIX_ESPHOME}core"
|
||||
_COMPONENT_API = f"{_COMPONENT_PREFIX_ESPHOME}api"
|
||||
|
||||
# Placement new storage suffix (generated by codegen Pvariable)
|
||||
_PSTORAGE_SUFFIX = "__pstorage"
|
||||
|
||||
|
||||
# C++ namespace prefixes
|
||||
_NAMESPACE_ESPHOME = "esphome::"
|
||||
_NAMESPACE_STD = "std::"
|
||||
@@ -192,20 +205,27 @@ class MemoryAnalyzer:
|
||||
self._cswtch_symbols: list[tuple[str, int, str, str]] = []
|
||||
# Library symbol mapping: symbol_name -> library_name
|
||||
self._lib_symbol_map: dict[str, str] = {}
|
||||
# Source file symbol mapping: symbol_name -> component_name
|
||||
# Used for extern "C" and other symbols without C++ namespace
|
||||
self._source_symbol_map: dict[str, str] = {}
|
||||
# Library dir to name mapping: "lib641" -> "espsoftwareserial",
|
||||
# "espressif__mdns" -> "mdns"
|
||||
self._lib_hash_to_name: dict[str, str] = {}
|
||||
# Heuristic category to library redirect: "mdns_lib" -> "[lib]mdns"
|
||||
self._heuristic_to_lib: dict[str, str] = {}
|
||||
# Function call counts: mangled_name -> call_count
|
||||
self._function_call_counts: Counter[str] = Counter()
|
||||
|
||||
def analyze(self) -> dict[str, ComponentMemory]:
|
||||
"""Analyze the ELF file and return component memory usage."""
|
||||
self._parse_sections()
|
||||
self._parse_symbols()
|
||||
self._scan_libraries()
|
||||
self._scan_source_symbols()
|
||||
self._categorize_symbols()
|
||||
self._analyze_cswtch_symbols()
|
||||
self._analyze_sdk_libraries()
|
||||
self._analyze_function_calls()
|
||||
return dict(self.components)
|
||||
|
||||
def _parse_sections(self) -> None:
|
||||
@@ -316,6 +336,13 @@ class MemoryAnalyzer:
|
||||
# Demangle C++ names if needed
|
||||
demangled = self._demangle_symbol(symbol_name)
|
||||
|
||||
# Check for placement new storage symbols (generated by codegen)
|
||||
# Format: {component}__{id}__pstorage
|
||||
if demangled.endswith(_PSTORAGE_SUFFIX) and (
|
||||
component := self._match_pstorage_component(demangled)
|
||||
):
|
||||
return component
|
||||
|
||||
# Check for special component classes first (before namespace pattern)
|
||||
# This handles cases like esphome::ESPHomeOTAComponent which should map to ota
|
||||
if _NAMESPACE_ESPHOME in demangled:
|
||||
@@ -351,6 +378,11 @@ class MemoryAnalyzer:
|
||||
if lib_name := self._lib_symbol_map.get(symbol_name):
|
||||
return f"{_COMPONENT_PREFIX_LIB}{lib_name}"
|
||||
|
||||
# Check source file mapping (catches extern "C" functions in ESPHome sources)
|
||||
# Must be before heuristic patterns since source attribution is authoritative
|
||||
if component := self._source_symbol_map.get(symbol_name):
|
||||
return component
|
||||
|
||||
# Check against symbol patterns
|
||||
for component, patterns in SYMBOL_PATTERNS.items():
|
||||
if any(pattern in symbol_name for pattern in patterns):
|
||||
@@ -378,14 +410,33 @@ class MemoryAnalyzer:
|
||||
# Track uncategorized symbols for analysis
|
||||
return "other"
|
||||
|
||||
def _match_pstorage_component(self, symbol_name: str) -> str | None:
|
||||
"""Match a __pstorage symbol to its ESPHome component.
|
||||
|
||||
Symbol format: {component}__{id}__pstorage
|
||||
The component namespace is embedded by codegen before the double underscore.
|
||||
"""
|
||||
prefix = symbol_name[: -len(_PSTORAGE_SUFFIX)]
|
||||
# Extract component namespace before the first double underscore
|
||||
dunder_pos = prefix.find("__")
|
||||
if dunder_pos == -1:
|
||||
return None
|
||||
component_name = prefix[:dunder_pos]
|
||||
if component_name in get_esphome_components():
|
||||
return f"{_COMPONENT_PREFIX_ESPHOME}{component_name}"
|
||||
if component_name in self.external_components:
|
||||
return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}"
|
||||
return None
|
||||
|
||||
def _batch_demangle_symbols(self, symbols: list[str]) -> None:
|
||||
"""Batch demangle C++ symbol names for efficiency."""
|
||||
if not symbols:
|
||||
return
|
||||
|
||||
_LOGGER.info("Demangling %d symbols", len(symbols))
|
||||
self._demangle_cache = batch_demangle(symbols, objdump_path=self.objdump_path)
|
||||
_LOGGER.info("Successfully demangled %d symbols", len(self._demangle_cache))
|
||||
demangled = batch_demangle(symbols, objdump_path=self.objdump_path)
|
||||
self._demangle_cache.update(demangled)
|
||||
_LOGGER.info("Successfully demangled %d symbols", len(demangled))
|
||||
|
||||
def _demangle_symbol(self, symbol: str) -> str:
|
||||
"""Get demangled C++ symbol name from cache."""
|
||||
@@ -640,6 +691,7 @@ class MemoryAnalyzer:
|
||||
return None
|
||||
|
||||
symbol_map: dict[str, str] = {}
|
||||
source_symbol_map: dict[str, str] = {}
|
||||
current_symbol: str | None = None
|
||||
section_prefixes = (".text.", ".rodata.", ".data.", ".bss.", ".literal.")
|
||||
|
||||
@@ -675,9 +727,18 @@ class MemoryAnalyzer:
|
||||
if dir_key in source_path:
|
||||
symbol_map[current_symbol] = lib_name
|
||||
break
|
||||
else:
|
||||
# Map ESPHome source files to components for extern "C"
|
||||
# and other symbols without C++ namespace
|
||||
component = self._source_file_to_component(source_path)
|
||||
if component.startswith(
|
||||
(_COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL)
|
||||
):
|
||||
source_symbol_map[current_symbol] = component
|
||||
|
||||
current_symbol = None
|
||||
|
||||
self._source_symbol_map = source_symbol_map
|
||||
return symbol_map or None
|
||||
|
||||
def _scan_libraries(self) -> None:
|
||||
@@ -728,6 +789,112 @@ class MemoryAnalyzer:
|
||||
len(libraries),
|
||||
)
|
||||
|
||||
def _scan_source_symbols(self) -> None:
|
||||
"""Scan ESPHome source object files to map extern "C" symbols to components.
|
||||
|
||||
When no linker map file is available, this uses ``nm`` to scan ``.o`` files
|
||||
under ``src/esphome/`` and build a symbol-to-component mapping. This catches
|
||||
``extern "C"`` functions and other symbols that lack C++ namespace prefixes.
|
||||
|
||||
Skips scanning if ``_source_symbol_map`` was already populated by
|
||||
``_parse_map_file()``.
|
||||
"""
|
||||
if self._source_symbol_map or not self.nm_path:
|
||||
return
|
||||
|
||||
obj_dir = self._find_object_files_dir()
|
||||
if obj_dir is None:
|
||||
return
|
||||
|
||||
# Find ESPHome source object files
|
||||
esphome_src_dir = obj_dir / "src" / "esphome"
|
||||
if not esphome_src_dir.is_dir():
|
||||
return
|
||||
|
||||
obj_files = sorted(esphome_src_dir.rglob("*.o"))
|
||||
if not obj_files:
|
||||
return
|
||||
|
||||
# Run nm with --print-file-name to get file:symbol mapping
|
||||
result = run_tool(
|
||||
[self.nm_path, "--print-file-name", "-g", "--defined-only"]
|
||||
+ [str(f) for f in obj_files],
|
||||
)
|
||||
if result is None or result.returncode != 0:
|
||||
_LOGGER.debug("nm scan of source objects failed")
|
||||
return
|
||||
|
||||
self._source_symbol_map = self._parse_nm_source_output(result.stdout, obj_dir)
|
||||
if self._source_symbol_map:
|
||||
_LOGGER.info(
|
||||
"Built source symbol map from nm: %d symbols",
|
||||
len(self._source_symbol_map),
|
||||
)
|
||||
|
||||
def _parse_nm_source_output(self, output: str, base_dir: Path) -> dict[str, str]:
|
||||
"""Parse nm output to map non-namespaced symbols to ESPHome components.
|
||||
|
||||
Extracts global defined symbols from ESPHome source object files that
|
||||
don't use C++ namespacing (e.g. ``extern "C"`` functions).
|
||||
|
||||
Args:
|
||||
output: Raw stdout from ``nm --print-file-name -g --defined-only``
|
||||
or ``nm --print-file-name -S``.
|
||||
base_dir: Build directory for computing relative paths.
|
||||
|
||||
Returns:
|
||||
Dict mapping symbol names to component names.
|
||||
"""
|
||||
source_map: dict[str, str] = {}
|
||||
for line in output.splitlines():
|
||||
# Format: /path/to/file.o: addr type name
|
||||
# or: /path/to/file.o: addr size type name (with -S)
|
||||
colon_idx = line.rfind(".o:")
|
||||
if colon_idx == -1:
|
||||
continue
|
||||
|
||||
file_path = line[: colon_idx + 2]
|
||||
fields = line[colon_idx + 3 :].split()
|
||||
if len(fields) < 3:
|
||||
continue
|
||||
|
||||
# With -S flag, format is: addr size type name
|
||||
# Without -S flag: addr type name
|
||||
# type is a single char; size is hex digits
|
||||
# Detect by checking if fields[1] is a single uppercase letter (type)
|
||||
if len(fields[1]) == 1 and fields[1].isalpha():
|
||||
# addr type name
|
||||
sym_type = fields[1]
|
||||
symbol_name = fields[2]
|
||||
elif len(fields) >= 4:
|
||||
# addr size type name
|
||||
sym_type = fields[2]
|
||||
symbol_name = fields[3]
|
||||
else:
|
||||
continue
|
||||
|
||||
# Only global defined symbols (uppercase type)
|
||||
if not sym_type.isupper() or sym_type == "U":
|
||||
continue
|
||||
|
||||
# Skip symbols already in esphome:: namespace
|
||||
if symbol_name.startswith("_ZN7esphome"):
|
||||
continue
|
||||
|
||||
# Make path relative to base_dir for _source_file_to_component
|
||||
try:
|
||||
rel_path = str(Path(file_path).relative_to(base_dir))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
component = self._source_file_to_component(rel_path)
|
||||
if component.startswith(
|
||||
(_COMPONENT_PREFIX_ESPHOME, _COMPONENT_PREFIX_EXTERNAL)
|
||||
):
|
||||
source_map[symbol_name] = component
|
||||
|
||||
return source_map
|
||||
|
||||
def _find_object_files_dir(self) -> Path | None:
|
||||
"""Find the directory containing object files for this build.
|
||||
|
||||
@@ -1011,6 +1178,43 @@ class MemoryAnalyzer:
|
||||
total_size,
|
||||
)
|
||||
|
||||
def _analyze_function_calls(self) -> None:
|
||||
"""Count function call sites by parsing disassembly output.
|
||||
|
||||
Parses direct call instructions (call0/call8/bl/blx) from objdump -d
|
||||
to count how many times each function is called. This helps identify
|
||||
inlining candidates — frequently called small functions benefit most
|
||||
from inlining.
|
||||
"""
|
||||
result = run_tool(
|
||||
[self.objdump_path, "-d", str(self.elf_path)],
|
||||
timeout=60,
|
||||
)
|
||||
if result is None or result.returncode != 0:
|
||||
_LOGGER.debug("Failed to disassemble ELF for function call analysis")
|
||||
return
|
||||
|
||||
self._function_call_counts = Counter(
|
||||
match.group(1)
|
||||
for line in result.stdout.splitlines()
|
||||
if (match := _CALL_TARGET_PATTERN.search(line))
|
||||
)
|
||||
|
||||
# Demangle any call targets not already in the cache
|
||||
missing = [
|
||||
name
|
||||
for name in self._function_call_counts
|
||||
if name not in self._demangle_cache
|
||||
]
|
||||
if missing:
|
||||
self._batch_demangle_symbols(missing)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Function call analysis: %d unique targets, %d total calls",
|
||||
len(self._function_call_counts),
|
||||
sum(self._function_call_counts.values()),
|
||||
)
|
||||
|
||||
def get_unattributed_ram(self) -> tuple[int, int, int]:
|
||||
"""Get unattributed RAM sizes (SDK/framework overhead).
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from . import (
|
||||
_COMPONENT_PREFIX_ESPHOME,
|
||||
_COMPONENT_PREFIX_EXTERNAL,
|
||||
_COMPONENT_PREFIX_LIB,
|
||||
_PSTORAGE_SUFFIX,
|
||||
RAM_SECTIONS,
|
||||
MemoryAnalyzer,
|
||||
)
|
||||
@@ -23,6 +24,17 @@ if TYPE_CHECKING:
|
||||
from . import ComponentMemory
|
||||
|
||||
|
||||
def _format_pstorage_name(name: str) -> str:
|
||||
"""Format a __pstorage symbol as 'storage for {id}'."""
|
||||
if not name.endswith(_PSTORAGE_SUFFIX):
|
||||
return name
|
||||
prefix = name[: -len(_PSTORAGE_SUFFIX)]
|
||||
# Strip component namespace prefix: {component}__{id} -> {id}
|
||||
dunder_pos = prefix.find("__")
|
||||
var_id = prefix[dunder_pos + 2 :] if dunder_pos != -1 else prefix
|
||||
return f"storage for {var_id}"
|
||||
|
||||
|
||||
class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
"""Memory analyzer with CLI-specific report generation."""
|
||||
|
||||
@@ -148,11 +160,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
If section is one of the RAM sections (.data or .bss), a label like
|
||||
" [data]" or " [bss]" is appended. For non-RAM sections or when
|
||||
section is None, no section label is added.
|
||||
|
||||
Placement new storage symbols are formatted as "storage for {id}".
|
||||
"""
|
||||
display_name = _format_pstorage_name(demangled)
|
||||
section_label = ""
|
||||
if section in RAM_SECTIONS:
|
||||
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
|
||||
return f"{demangled} ({size:,} B){section_label}"
|
||||
return f"{display_name} ({size:,} B){section_label}"
|
||||
|
||||
def _add_top_symbols(self, lines: list[str]) -> None:
|
||||
"""Add a section showing the top largest symbols in the binary."""
|
||||
@@ -175,11 +190,13 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
for i, (_, demangled, size, section, component) in enumerate(top_symbols):
|
||||
# Format section label
|
||||
section_label = f"[{section[1:]}]" if section else ""
|
||||
# Truncate demangled name if too long
|
||||
# Format storage symbols readably
|
||||
display_name = _format_pstorage_name(demangled)
|
||||
# Truncate if too long
|
||||
demangled_display = (
|
||||
f"{demangled[:truncate_limit]}..."
|
||||
if len(demangled) > self.COL_TOP_SYMBOL_NAME
|
||||
else demangled
|
||||
f"{display_name[:truncate_limit]}..."
|
||||
if len(display_name) > self.COL_TOP_SYMBOL_NAME
|
||||
else display_name
|
||||
)
|
||||
lines.append(
|
||||
f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}"
|
||||
@@ -231,6 +248,110 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
lines.append(f" {size:>6,} B {sym_name}")
|
||||
lines.append("")
|
||||
|
||||
# Number of top called functions to show
|
||||
TOP_CALLS_LIMIT: int = 50
|
||||
# Number of inlining candidates to show
|
||||
INLINE_CANDIDATES_LIMIT: int = 25
|
||||
# Maximum function size in bytes to consider for inlining
|
||||
INLINE_SIZE_THRESHOLD: int = 16
|
||||
|
||||
def _build_symbol_sizes(self) -> dict[str, int]:
|
||||
"""Build a size lookup from all component symbols: mangled_name -> size."""
|
||||
return {
|
||||
symbol: size
|
||||
for symbols in self._component_symbols.values()
|
||||
for symbol, _, size, _ in symbols
|
||||
}
|
||||
|
||||
def _format_call_row(
|
||||
self, index: int, mangled: str, count: int, symbol_sizes: dict[str, int]
|
||||
) -> str:
|
||||
"""Format a single row for call frequency tables."""
|
||||
demangled = self._demangle_cache.get(mangled, mangled)
|
||||
if len(demangled) > 80:
|
||||
demangled = f"{demangled[:77]}..."
|
||||
size = symbol_sizes.get(mangled)
|
||||
size_str = f"{size:>5,} B" if size is not None else " ?"
|
||||
return f"{index:>3} {count:>5} {size_str} {demangled}"
|
||||
|
||||
def _add_call_table_header(self, lines: list[str]) -> None:
|
||||
"""Add the header row for call frequency tables."""
|
||||
lines.append(f"{'#':>3} {'Calls':>5} {'Size':>7} Function")
|
||||
lines.append(f"{'---':>3} {'-----':>5} {'-------':>7} {'-' * 60}")
|
||||
|
||||
def _add_function_call_analysis(self, lines: list[str]) -> None:
|
||||
"""Add function call frequency analysis section.
|
||||
|
||||
Shows the most frequently called functions by call site count.
|
||||
"""
|
||||
self._add_section_header(lines, "Top Called Functions")
|
||||
|
||||
symbol_sizes = self._build_symbol_sizes()
|
||||
|
||||
# Sort by call count descending
|
||||
sorted_calls = sorted(
|
||||
self._function_call_counts.items(), key=lambda x: x[1], reverse=True
|
||||
)
|
||||
|
||||
self._add_call_table_header(lines)
|
||||
|
||||
for i, (mangled, count) in enumerate(sorted_calls[: self.TOP_CALLS_LIMIT]):
|
||||
lines.append(self._format_call_row(i + 1, mangled, count, symbol_sizes))
|
||||
|
||||
total_calls = sum(self._function_call_counts.values())
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"Total: {len(self._function_call_counts)} unique targets, "
|
||||
f"{total_calls:,} call sites"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
def _add_inline_candidates(self, lines: list[str]) -> None:
|
||||
"""Add inlining candidates section.
|
||||
|
||||
Shows frequently called functions that are small enough to benefit
|
||||
from inlining (< 16 bytes). These are the best candidates for
|
||||
reducing call overhead.
|
||||
"""
|
||||
self._add_section_header(
|
||||
lines,
|
||||
f"Inlining Candidates (<{self.INLINE_SIZE_THRESHOLD} B, by call count)",
|
||||
)
|
||||
|
||||
symbol_sizes = self._build_symbol_sizes()
|
||||
|
||||
# Filter to small functions with known size, sort by call count
|
||||
candidates = sorted(
|
||||
(
|
||||
(mangled, count)
|
||||
for mangled, count in self._function_call_counts.items()
|
||||
if mangled in symbol_sizes
|
||||
and symbol_sizes[mangled] < self.INLINE_SIZE_THRESHOLD
|
||||
),
|
||||
key=lambda x: x[1],
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
if not candidates:
|
||||
lines.append("No candidates found.")
|
||||
lines.append("")
|
||||
return
|
||||
|
||||
self._add_call_table_header(lines)
|
||||
|
||||
for i, (mangled, count) in enumerate(
|
||||
candidates[: self.INLINE_CANDIDATES_LIMIT]
|
||||
):
|
||||
lines.append(self._format_call_row(i + 1, mangled, count, symbol_sizes))
|
||||
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"Showing top {min(len(candidates), self.INLINE_CANDIDATES_LIMIT)} "
|
||||
f"of {len(candidates)} functions under "
|
||||
f"{self.INLINE_SIZE_THRESHOLD} B"
|
||||
)
|
||||
lines.append("")
|
||||
|
||||
def generate_report(self, detailed: bool = False) -> str:
|
||||
"""Generate a formatted memory report."""
|
||||
components = sorted(
|
||||
@@ -469,15 +590,16 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
lines.append(f"Total size: {comp_mem.flash_total:,} B")
|
||||
lines.append("")
|
||||
|
||||
# Show all symbols above threshold for better visibility
|
||||
# Show symbols above threshold, always include storage symbols
|
||||
large_symbols = [
|
||||
(sym, dem, size, sec)
|
||||
for sym, dem, size, sec in sorted_symbols
|
||||
if size > self.SYMBOL_SIZE_THRESHOLD
|
||||
or dem.endswith(_PSTORAGE_SUFFIX)
|
||||
]
|
||||
|
||||
lines.append(
|
||||
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):"
|
||||
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B & storage ({len(large_symbols)} symbols):"
|
||||
)
|
||||
for i, (symbol, demangled, size, section) in enumerate(large_symbols):
|
||||
lines.append(
|
||||
@@ -500,7 +622,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
# Sort by size descending
|
||||
sorted_ram_syms = sorted(ram_syms, key=lambda x: x[2], reverse=True)
|
||||
large_ram_syms = [
|
||||
s for s in sorted_ram_syms if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD
|
||||
s
|
||||
for s in sorted_ram_syms
|
||||
if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD
|
||||
or s[1].endswith(_PSTORAGE_SUFFIX)
|
||||
]
|
||||
|
||||
lines.append(f"{name} ({mem.ram_total:,} B total RAM):")
|
||||
@@ -518,13 +643,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
for symbol, demangled, size, section in large_ram_syms[:10]:
|
||||
# Format section label consistently by stripping leading dot
|
||||
section_label = section.lstrip(".") if section else ""
|
||||
display_name = _format_pstorage_name(demangled)
|
||||
# Add ellipsis if name is truncated
|
||||
demangled_display = (
|
||||
f"{demangled[:70]}..." if len(demangled) > 70 else demangled
|
||||
)
|
||||
lines.append(
|
||||
f" {size:>6,} B [{section_label}] {demangled_display}"
|
||||
display_name = (
|
||||
f"{display_name[:70]}..."
|
||||
if len(display_name) > 70
|
||||
else display_name
|
||||
)
|
||||
lines.append(f" {size:>6,} B [{section_label}] {display_name}")
|
||||
if len(large_ram_syms) > 10:
|
||||
lines.append(f" ... and {len(large_ram_syms) - 10} more")
|
||||
lines.append("")
|
||||
@@ -533,6 +659,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
if self._cswtch_symbols:
|
||||
self._add_cswtch_analysis(lines)
|
||||
|
||||
# Function call frequency analysis
|
||||
if self._function_call_counts:
|
||||
self._add_function_call_analysis(lines)
|
||||
self._add_inline_candidates(lines)
|
||||
|
||||
lines.append(
|
||||
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
|
||||
)
|
||||
|
||||
@@ -408,7 +408,6 @@ SYMBOL_PATTERNS = {
|
||||
],
|
||||
"arduino_core": [
|
||||
"pinMode",
|
||||
"resetPins",
|
||||
"millis",
|
||||
"micros",
|
||||
"delay(", # More specific - Arduino delay function with parenthesis
|
||||
|
||||
@@ -137,6 +137,9 @@ UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||
SuspendComponentAction = cg.esphome_ns.class_("SuspendComponentAction", Action)
|
||||
ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
TriggerForwarder = cg.esphome_ns.class_("TriggerForwarder")
|
||||
TriggerOnTrueForwarder = cg.esphome_ns.class_("TriggerOnTrueForwarder")
|
||||
TriggerOnFalseForwarder = cg.esphome_ns.class_("TriggerOnFalseForwarder")
|
||||
|
||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
|
||||
@@ -247,7 +250,9 @@ async def and_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("or", OrCondition, validate_condition_list)
|
||||
@@ -258,7 +263,9 @@ async def or_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("all", AndCondition, validate_condition_list)
|
||||
@@ -269,7 +276,9 @@ async def all_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("any", OrCondition, validate_condition_list)
|
||||
@@ -280,7 +289,9 @@ async def any_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("not", NotCondition, validate_potentially_and_condition)
|
||||
@@ -302,7 +313,9 @@ async def xor_condition_to_code(
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
||||
@@ -413,13 +426,16 @@ async def if_action_to_code(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
has_else = CONF_ELSE in config
|
||||
# Prepend HasElse bool to template arguments: IfAction<HasElse, Ts...>
|
||||
if_template_arg = cg.TemplateArguments(has_else, *template_arg)
|
||||
cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
|
||||
condition = await build_condition(config[cond_conf], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, condition)
|
||||
var = cg.new_Pvariable(action_id, if_template_arg, condition)
|
||||
if CONF_THEN in config:
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
if CONF_ELSE in config:
|
||||
if has_else:
|
||||
actions = await build_action_list(config[CONF_ELSE], template_arg, args)
|
||||
cg.add(var.add_else(actions))
|
||||
return var
|
||||
@@ -658,3 +674,44 @@ async def build_automation(
|
||||
actions = await build_action_list(config[CONF_THEN], templ, args)
|
||||
cg.add(obj.add_actions(actions))
|
||||
return obj
|
||||
|
||||
|
||||
async def build_callback_automation(
|
||||
parent: MockObj,
|
||||
callback_method: str,
|
||||
args: TemplateArgsType,
|
||||
config: ConfigType,
|
||||
forwarder: MockObj | MockObjClass | None = None,
|
||||
) -> None:
|
||||
"""Build an Automation and register it as a callback on the parent.
|
||||
|
||||
Eliminates the need for a Trigger wrapper object by registering the
|
||||
automation's trigger() directly as a callback on the parent component.
|
||||
|
||||
Uses template forwarder structs so the compiler deduplicates the operator()
|
||||
body across all call sites with the same signature. The forwarder must be
|
||||
pointer-sized (single Automation* field) to fit inline in Callback::ctx_
|
||||
and avoid heap allocation.
|
||||
|
||||
:param parent: The component object (e.g., button, sensor).
|
||||
:param callback_method: Name of the callback method (e.g., "add_on_press_callback").
|
||||
:param args: Automation template args as list of (type, name) tuples.
|
||||
:param config: The automation config dict.
|
||||
:param forwarder: Optional forwarder type to use instead of the default
|
||||
TriggerForwarder<Ts...>. Pass any struct type whose aggregate init takes
|
||||
a single Automation pointer (e.g., TriggerOnTrueForwarder).
|
||||
"""
|
||||
arg_types = [arg[0] for arg in args]
|
||||
templ = cg.TemplateArguments(*arg_types)
|
||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ)
|
||||
actions = await build_action_list(config[CONF_THEN], templ, args)
|
||||
cg.add(obj.add_actions(actions))
|
||||
# Use template forwarder structs for deduplication. The compiler generates
|
||||
# one operator() per forwarder type; different automation pointers are just
|
||||
# data in the struct.
|
||||
if forwarder is None:
|
||||
forwarder = TriggerForwarder.template(templ)
|
||||
# RawExpression for aggregate init — both forwarder and obj are codegen
|
||||
# MockObjs (not user input), and there's no Expression type for positional
|
||||
# aggregate initialization (StructInitializer uses named fields).
|
||||
cg.add(getattr(parent, callback_method)(cg.RawExpression(f"{forwarder}{{{obj}}}")))
|
||||
|
||||
@@ -53,6 +53,13 @@ def get_project_cmakelists() -> str:
|
||||
variant = get_esp32_variant()
|
||||
idf_target = variant.lower().replace("-", "")
|
||||
|
||||
# Extract compile definitions from build flags (-DXXX -> XXX)
|
||||
compile_defs = [flag for flag in CORE.build_flags if flag.startswith("-D")]
|
||||
extra_compile_options = "\n".join(
|
||||
f'idf_build_set_property(COMPILE_OPTIONS "{compile_def}" APPEND)'
|
||||
for compile_def in compile_defs
|
||||
)
|
||||
|
||||
return f"""\
|
||||
# Auto-generated by ESPHome
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
@@ -61,6 +68,9 @@ set(IDF_TARGET {idf_target})
|
||||
set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
|
||||
|
||||
include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
||||
|
||||
{extra_compile_options}
|
||||
|
||||
project({CORE.name})
|
||||
"""
|
||||
|
||||
@@ -70,10 +80,6 @@ def get_component_cmakelists(minimal: bool = False) -> str:
|
||||
idf_requires = [] if minimal else (get_available_components() or [])
|
||||
requires_str = " ".join(idf_requires)
|
||||
|
||||
# Extract compile definitions from build flags (-DXXX -> XXX)
|
||||
compile_defs = [flag[2:] for flag in CORE.build_flags if flag.startswith("-D")]
|
||||
compile_defs_str = "\n ".join(sorted(compile_defs)) if compile_defs else ""
|
||||
|
||||
# Extract compile options (-W flags, excluding linker flags)
|
||||
compile_opts = [
|
||||
flag
|
||||
@@ -104,11 +110,6 @@ idf_component_register(
|
||||
# Apply C++ standard
|
||||
target_compile_features(${{COMPONENT_LIB}} PUBLIC cxx_std_20)
|
||||
|
||||
# ESPHome compile definitions
|
||||
target_compile_definitions(${{COMPONENT_LIB}} PUBLIC
|
||||
{compile_defs_str}
|
||||
)
|
||||
|
||||
# ESPHome compile options
|
||||
target_compile_options(${{COMPONENT_LIB}} PUBLIC
|
||||
{compile_opts_str}
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "absolute_humidity.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace absolute_humidity {
|
||||
namespace esphome::absolute_humidity {
|
||||
|
||||
static const char *const TAG = "absolute_humidity.sensor";
|
||||
static const char *const TAG{"absolute_humidity.sensor"};
|
||||
|
||||
void AbsoluteHumidityComponent::setup() {
|
||||
this->temperature_sensor_->add_on_state_callback([this](float state) {
|
||||
this->temperature_ = state;
|
||||
this->enable_loop();
|
||||
});
|
||||
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
|
||||
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
|
||||
// Get initial value
|
||||
if (this->temperature_sensor_->has_state()) {
|
||||
this->temperature_callback_(this->temperature_sensor_->get_state());
|
||||
this->temperature_ = this->temperature_sensor_->get_state();
|
||||
}
|
||||
|
||||
this->humidity_sensor_->add_on_state_callback([this](float state) {
|
||||
this->humidity_ = state;
|
||||
this->enable_loop();
|
||||
});
|
||||
ESP_LOGD(TAG, " Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str());
|
||||
this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); });
|
||||
// Get initial value
|
||||
if (this->humidity_sensor_->has_state()) {
|
||||
this->humidity_callback_(this->humidity_sensor_->get_state());
|
||||
this->humidity_ = this->humidity_sensor_->get_state();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,14 +53,12 @@ void AbsoluteHumidityComponent::dump_config() {
|
||||
}
|
||||
|
||||
void AbsoluteHumidityComponent::loop() {
|
||||
if (!this->next_update_) {
|
||||
return;
|
||||
}
|
||||
this->next_update_ = false;
|
||||
// Only run once
|
||||
this->disable_loop();
|
||||
|
||||
// Ensure we have source data
|
||||
const bool no_temperature = std::isnan(this->temperature_);
|
||||
const bool no_humidity = std::isnan(this->humidity_);
|
||||
const bool no_temperature{std::isnan(this->temperature_)};
|
||||
const bool no_humidity{std::isnan(this->humidity_)};
|
||||
if (no_temperature || no_humidity) {
|
||||
if (no_temperature) {
|
||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||
@@ -67,9 +72,9 @@ void AbsoluteHumidityComponent::loop() {
|
||||
}
|
||||
|
||||
// Convert to desired units
|
||||
const float temperature_c = this->temperature_;
|
||||
const float temperature_k = temperature_c + 273.15;
|
||||
const float hr = this->humidity_ / 100;
|
||||
const float temperature_c{this->temperature_};
|
||||
const float temperature_k{temperature_c + 273.15f};
|
||||
const float hr{this->humidity_ / 100.0f};
|
||||
|
||||
// Calculate saturation vapor pressure
|
||||
float es;
|
||||
@@ -90,7 +95,7 @@ void AbsoluteHumidityComponent::loop() {
|
||||
}
|
||||
|
||||
// Calculate absolute humidity
|
||||
const float absolute_humidity = vapor_density(es, hr, temperature_k);
|
||||
const float absolute_humidity{vapor_density(es, hr, temperature_k)};
|
||||
|
||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa, absolute humidity %f g/m³", es, absolute_humidity);
|
||||
|
||||
@@ -103,16 +108,16 @@ void AbsoluteHumidityComponent::loop() {
|
||||
// More accurate than Tetens in normal meteorologic conditions
|
||||
float AbsoluteHumidityComponent::es_buck(float temperature_c) {
|
||||
float a, b, c, d;
|
||||
if (temperature_c >= 0) {
|
||||
a = 0.61121;
|
||||
b = 18.678;
|
||||
c = 234.5;
|
||||
d = 257.14;
|
||||
if (temperature_c >= 0.0f) {
|
||||
a = 0.61121f;
|
||||
b = 18.678f;
|
||||
c = 234.5f;
|
||||
d = 257.14f;
|
||||
} else {
|
||||
a = 0.61115;
|
||||
b = 18.678;
|
||||
c = 233.7;
|
||||
d = 279.82;
|
||||
a = 0.61115f;
|
||||
b = 18.678f;
|
||||
c = 233.7f;
|
||||
d = 279.82f;
|
||||
}
|
||||
return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c)));
|
||||
}
|
||||
@@ -120,14 +125,14 @@ float AbsoluteHumidityComponent::es_buck(float temperature_c) {
|
||||
// Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation)
|
||||
float AbsoluteHumidityComponent::es_tetens(float temperature_c) {
|
||||
float a, b;
|
||||
if (temperature_c >= 0) {
|
||||
a = 17.27;
|
||||
b = 237.3;
|
||||
if (temperature_c >= 0.0f) {
|
||||
a = 17.27f;
|
||||
b = 237.3f;
|
||||
} else {
|
||||
a = 21.875;
|
||||
b = 265.5;
|
||||
a = 21.875f;
|
||||
b = 265.5f;
|
||||
}
|
||||
return 0.61078 * expf((a * temperature_c) / (temperature_c + b));
|
||||
return 0.61078f * expf((a * temperature_c) / (temperature_c + b));
|
||||
}
|
||||
|
||||
// Wobus equation
|
||||
@@ -146,18 +151,18 @@ float AbsoluteHumidityComponent::es_wobus(float t) {
|
||||
//
|
||||
// Baker, Schlatter 17-MAY-1982 Original version.
|
||||
|
||||
const float c0 = +0.99999683e00;
|
||||
const float c1 = -0.90826951e-02;
|
||||
const float c2 = +0.78736169e-04;
|
||||
const float c3 = -0.61117958e-06;
|
||||
const float c4 = +0.43884187e-08;
|
||||
const float c5 = -0.29883885e-10;
|
||||
const float c6 = +0.21874425e-12;
|
||||
const float c7 = -0.17892321e-14;
|
||||
const float c8 = +0.11112018e-16;
|
||||
const float c9 = -0.30994571e-19;
|
||||
const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))));
|
||||
return 0.61078 / pow(p, 8);
|
||||
constexpr float c0{+0.99999683e+00f};
|
||||
constexpr float c1{-0.90826951e-02f};
|
||||
constexpr float c2{+0.78736169e-04f};
|
||||
constexpr float c3{-0.61117958e-06f};
|
||||
constexpr float c4{+0.43884187e-08f};
|
||||
constexpr float c5{-0.29883885e-10f};
|
||||
constexpr float c6{+0.21874425e-12f};
|
||||
constexpr float c7{-0.17892321e-14f};
|
||||
constexpr float c8{+0.11112018e-16f};
|
||||
constexpr float c9{-0.30994571e-19f};
|
||||
const float p{c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))))};
|
||||
return 0.61078f / powf(p, 8.0f);
|
||||
}
|
||||
|
||||
// From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/
|
||||
@@ -168,11 +173,10 @@ float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) {
|
||||
// hr = relative humidity [0-1]
|
||||
// ta = absolute temperature (K)
|
||||
|
||||
const float ea = hr * es * 1000; // vapor pressure of the air (Pa)
|
||||
const float mw = 18.01528; // molar mass of water (g⋅mol⁻¹)
|
||||
const float r = 8.31446261815324; // molar gas constant (J⋅K⁻¹)
|
||||
const float ea{hr * es * 1000.0f}; // vapor pressure of the air (Pa)
|
||||
const float mw{18.01528f}; // molar mass of water (g⋅mol⁻¹)
|
||||
const float r{8.31446261815324f}; // molar gas constant (J⋅K⁻¹)
|
||||
return (ea * mw) / (r * ta);
|
||||
}
|
||||
|
||||
} // namespace absolute_humidity
|
||||
} // namespace esphome
|
||||
} // namespace esphome::absolute_humidity
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace absolute_humidity {
|
||||
namespace esphome::absolute_humidity {
|
||||
|
||||
/// Enum listing all implemented saturation vapor pressure equations.
|
||||
enum SaturationVaporPressureEquation {
|
||||
@@ -16,8 +15,6 @@ enum SaturationVaporPressureEquation {
|
||||
/// This class implements calculation of absolute humidity from temperature and relative humidity.
|
||||
class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
public:
|
||||
AbsoluteHumidityComponent() = default;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; }
|
||||
@@ -27,15 +24,6 @@ class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
void temperature_callback_(float state) {
|
||||
this->next_update_ = true;
|
||||
this->temperature_ = state;
|
||||
}
|
||||
void humidity_callback_(float state) {
|
||||
this->next_update_ = true;
|
||||
this->humidity_ = state;
|
||||
}
|
||||
|
||||
/** Buck equation for saturation vapor pressure in kPa.
|
||||
*
|
||||
* @param temperature_c Air temperature in °C.
|
||||
@@ -57,19 +45,15 @@ class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
* @param es Saturation vapor pressure in kPa.
|
||||
* @param hr Relative humidity 0 to 1.
|
||||
* @param ta Absolute temperature in K.
|
||||
* @param heater_duration The duration in ms that the heater should turn on for when measuring.
|
||||
*/
|
||||
static float vapor_density(float es, float hr, float ta);
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
|
||||
bool next_update_{false};
|
||||
|
||||
float temperature_{NAN};
|
||||
float humidity_{NAN};
|
||||
SaturationVaporPressureEquation equation_;
|
||||
};
|
||||
|
||||
} // namespace absolute_humidity
|
||||
} // namespace esphome
|
||||
} // namespace esphome::absolute_humidity
|
||||
|
||||
@@ -22,7 +22,8 @@ namespace adc {
|
||||
|
||||
#ifdef USE_ESP32
|
||||
// clang-format off
|
||||
#if (ESP_IDF_VERSION_MAJOR == 5 && \
|
||||
#if ESP_IDF_VERSION_MAJOR >= 6 || \
|
||||
(ESP_IDF_VERSION_MAJOR == 5 && \
|
||||
((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \
|
||||
(ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \
|
||||
(ESP_IDF_VERSION_MINOR >= 2)) \
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
@@ -346,7 +347,8 @@ float ADCSensor::sample_autorange_() {
|
||||
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);
|
||||
ESP_LOGVV(TAG, " Coefficients: c12=%" PRIu32 ", c6=%" PRIu32 ", c2=%" PRIu32 ", c0=%" PRIu32 ", sum=%" PRIu32, c12,
|
||||
c6, c2, c0, csum);
|
||||
|
||||
if (csum == 0) {
|
||||
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
|
||||
@@ -354,8 +356,10 @@ float ADCSensor::sample_autorange_() {
|
||||
}
|
||||
|
||||
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);
|
||||
ESP_LOGV(TAG,
|
||||
"Autorange final: (%.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 ")/%" PRIu32
|
||||
" = %.6fV",
|
||||
mv12, c12, mv6, c6, mv2, c2, mv0, c0, csum, final_result);
|
||||
|
||||
return final_result;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_STATE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
@@ -34,39 +33,9 @@ CONF_ON_READY = "on_ready"
|
||||
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
|
||||
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
|
||||
|
||||
StateTrigger = alarm_control_panel_ns.class_(
|
||||
"StateTrigger", automation.Trigger.template()
|
||||
)
|
||||
TriggeredTrigger = alarm_control_panel_ns.class_(
|
||||
"TriggeredTrigger", automation.Trigger.template()
|
||||
)
|
||||
ClearedTrigger = alarm_control_panel_ns.class_(
|
||||
"ClearedTrigger", automation.Trigger.template()
|
||||
)
|
||||
ArmingTrigger = alarm_control_panel_ns.class_(
|
||||
"ArmingTrigger", automation.Trigger.template()
|
||||
)
|
||||
PendingTrigger = alarm_control_panel_ns.class_(
|
||||
"PendingTrigger", automation.Trigger.template()
|
||||
)
|
||||
ArmedHomeTrigger = alarm_control_panel_ns.class_(
|
||||
"ArmedHomeTrigger", automation.Trigger.template()
|
||||
)
|
||||
ArmedNightTrigger = alarm_control_panel_ns.class_(
|
||||
"ArmedNightTrigger", automation.Trigger.template()
|
||||
)
|
||||
ArmedAwayTrigger = alarm_control_panel_ns.class_(
|
||||
"ArmedAwayTrigger", automation.Trigger.template()
|
||||
)
|
||||
DisarmedTrigger = alarm_control_panel_ns.class_(
|
||||
"DisarmedTrigger", automation.Trigger.template()
|
||||
)
|
||||
ChimeTrigger = alarm_control_panel_ns.class_(
|
||||
"ChimeTrigger", automation.Trigger.template()
|
||||
)
|
||||
ReadyTrigger = alarm_control_panel_ns.class_(
|
||||
"ReadyTrigger", automation.Trigger.template()
|
||||
)
|
||||
StateAnyForwarder = alarm_control_panel_ns.class_("StateAnyForwarder")
|
||||
StateEnterForwarder = alarm_control_panel_ns.class_("StateEnterForwarder")
|
||||
AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState")
|
||||
|
||||
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
|
||||
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
|
||||
@@ -89,61 +58,17 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTAlarmControlPanelComponent
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ARMING): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PENDING): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CHIME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_READY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ARMING): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_PENDING): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_DISARMED): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_CLEARED): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_CHIME): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_READY): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -189,38 +114,39 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
|
||||
@setup_entity("alarm_control_panel")
|
||||
async def setup_alarm_control_panel_core_(var, config):
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_TRIGGERED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_ARMING, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PENDING, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_ARMED_HOME, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_ARMED_NIGHT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_ARMED_AWAY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_DISARMED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [], conf, forwarder=StateAnyForwarder
|
||||
)
|
||||
_STATE_ENTER_MAP = {
|
||||
CONF_ON_TRIGGERED: AlarmControlPanelState.ACP_STATE_TRIGGERED,
|
||||
CONF_ON_ARMING: AlarmControlPanelState.ACP_STATE_ARMING,
|
||||
CONF_ON_PENDING: AlarmControlPanelState.ACP_STATE_PENDING,
|
||||
CONF_ON_ARMED_HOME: AlarmControlPanelState.ACP_STATE_ARMED_HOME,
|
||||
CONF_ON_ARMED_NIGHT: AlarmControlPanelState.ACP_STATE_ARMED_NIGHT,
|
||||
CONF_ON_ARMED_AWAY: AlarmControlPanelState.ACP_STATE_ARMED_AWAY,
|
||||
CONF_ON_DISARMED: AlarmControlPanelState.ACP_STATE_DISARMED,
|
||||
}
|
||||
for conf_key, state_enum in _STATE_ENTER_MAP.items():
|
||||
for conf in config.get(conf_key, []):
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_state_callback",
|
||||
[],
|
||||
conf,
|
||||
forwarder=StateEnterForwarder.template(state_enum),
|
||||
)
|
||||
for conf in config.get(CONF_ON_CLEARED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_cleared_callback", [], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_CHIME, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_chime_callback", [], conf
|
||||
)
|
||||
for conf in config.get(CONF_ON_READY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_ready_callback", [], conf
|
||||
)
|
||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||
await web_server.add_entity_config(var, web_server_config)
|
||||
if mqtt_id := config.get(CONF_MQTT_ID):
|
||||
|
||||
@@ -31,12 +31,12 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
|
||||
this->last_update_ = millis();
|
||||
if (state != this->current_state_) {
|
||||
auto prev_state = this->current_state_;
|
||||
ESP_LOGD(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(),
|
||||
ESP_LOGV(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(),
|
||||
LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
|
||||
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
|
||||
this->current_state_ = state;
|
||||
// Single state callback - triggers check get_state() for specific states
|
||||
this->state_callback_.call();
|
||||
// Single state callback - listeners receive the new state as an argument
|
||||
this->state_callback_.call(state);
|
||||
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_alarm_control_panel_update(this);
|
||||
#endif
|
||||
@@ -51,22 +51,6 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
|
||||
this->cleared_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void AlarmControlPanel::add_on_chime_callback(std::function<void()> &&callback) {
|
||||
this->chime_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void AlarmControlPanel::add_on_ready_callback(std::function<void()> &&callback) {
|
||||
this->ready_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void AlarmControlPanel::arm_with_code_(AlarmControlPanelCall &(AlarmControlPanelCall::*arm_method)(),
|
||||
const char *code) {
|
||||
auto call = this->make_call();
|
||||
|
||||
@@ -37,25 +37,24 @@ class AlarmControlPanel : public EntityBase {
|
||||
*
|
||||
* @param callback The callback function
|
||||
*/
|
||||
void add_on_state_callback(std::function<void()> &&callback);
|
||||
template<typename F> void add_on_state_callback(F &&callback) {
|
||||
this->state_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
/** Add a callback for when the state of the alarm_control_panel clears from triggered
|
||||
*
|
||||
* @param callback The callback function
|
||||
*/
|
||||
void add_on_cleared_callback(std::function<void()> &&callback);
|
||||
/** Add a callback for when the state of the alarm_control_panel clears from triggered. */
|
||||
template<typename F> void add_on_cleared_callback(F &&callback) {
|
||||
this->cleared_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
/** Add a callback for when a chime zone goes from closed to open
|
||||
*
|
||||
* @param callback The callback function
|
||||
*/
|
||||
void add_on_chime_callback(std::function<void()> &&callback);
|
||||
/** Add a callback for when a chime zone goes from closed to open. */
|
||||
template<typename F> void add_on_chime_callback(F &&callback) {
|
||||
this->chime_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
/** Add a callback for when a ready state changes
|
||||
*
|
||||
* @param callback The callback function
|
||||
*/
|
||||
void add_on_ready_callback(std::function<void()> &&callback);
|
||||
/** Add a callback for when a ready state changes. */
|
||||
template<typename F> void add_on_ready_callback(F &&callback) {
|
||||
this->ready_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
/** A numeric representation of the supported features as per HomeAssistant
|
||||
*
|
||||
@@ -146,8 +145,8 @@ class AlarmControlPanel : public EntityBase {
|
||||
uint32_t last_update_;
|
||||
// the call control function
|
||||
virtual void control(const AlarmControlPanelCall &call) = 0;
|
||||
// state callback - triggers check get_state() for specific state
|
||||
LazyCallbackManager<void()> state_callback_{};
|
||||
// state callback - passes the new state to listeners
|
||||
LazyCallbackManager<void(AlarmControlPanelState)> state_callback_{};
|
||||
// clear callback - fires when leaving TRIGGERED state
|
||||
LazyCallbackManager<void()> cleared_callback_{};
|
||||
// chime callback
|
||||
|
||||
@@ -5,60 +5,27 @@
|
||||
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
/// Trigger on any state change
|
||||
class StateTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
|
||||
alarm_control_panel->add_on_state_callback([this]() { this->trigger(); });
|
||||
/// Callback forwarder that triggers an Automation<> on any state change.
|
||||
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
|
||||
struct StateAnyForwarder {
|
||||
Automation<> *automation;
|
||||
void operator()(AlarmControlPanelState /*state*/) const { this->automation->trigger(); }
|
||||
};
|
||||
|
||||
/// Callback forwarder that triggers an Automation<> only when the alarm enters a specific state.
|
||||
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
|
||||
template<AlarmControlPanelState State> struct StateEnterForwarder {
|
||||
Automation<> *automation;
|
||||
void operator()(AlarmControlPanelState state) const {
|
||||
if (state == State)
|
||||
this->automation->trigger();
|
||||
}
|
||||
};
|
||||
|
||||
/// Template trigger that fires when entering a specific state
|
||||
template<AlarmControlPanelState State> class StateEnterTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {
|
||||
alarm_control_panel->add_on_state_callback([this]() {
|
||||
if (this->alarm_control_panel_->get_state() == State)
|
||||
this->trigger();
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
AlarmControlPanel *alarm_control_panel_;
|
||||
};
|
||||
|
||||
// Type aliases for state-specific triggers
|
||||
using TriggeredTrigger = StateEnterTrigger<ACP_STATE_TRIGGERED>;
|
||||
using ArmingTrigger = StateEnterTrigger<ACP_STATE_ARMING>;
|
||||
using PendingTrigger = StateEnterTrigger<ACP_STATE_PENDING>;
|
||||
using ArmedHomeTrigger = StateEnterTrigger<ACP_STATE_ARMED_HOME>;
|
||||
using ArmedNightTrigger = StateEnterTrigger<ACP_STATE_ARMED_NIGHT>;
|
||||
using ArmedAwayTrigger = StateEnterTrigger<ACP_STATE_ARMED_AWAY>;
|
||||
using DisarmedTrigger = StateEnterTrigger<ACP_STATE_DISARMED>;
|
||||
|
||||
/// Trigger when leaving TRIGGERED state (alarm cleared)
|
||||
class ClearedTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
|
||||
alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
/// Trigger on chime event (zone opened while disarmed)
|
||||
class ChimeTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) {
|
||||
alarm_control_panel->add_on_chime_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
/// Trigger on ready state change
|
||||
class ReadyTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) {
|
||||
alarm_control_panel->add_on_ready_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(StateAnyForwarder) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
|
||||
static_assert(sizeof(StateEnterForwarder<ACP_STATE_TRIGGERED>) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<ACP_STATE_TRIGGERED>>);
|
||||
|
||||
template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
|
||||
public:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
@@ -301,11 +301,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
# 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:
|
||||
# CONF_MAX_SEND_QUEUE defaults are power of 2 for efficient modulo
|
||||
cv.SplitDefault(
|
||||
CONF_MAX_SEND_QUEUE,
|
||||
esp8266=5, # Limited RAM, need to fail fast
|
||||
esp8266=4, # Limited RAM, need to fail fast
|
||||
esp32=8, # More RAM, can buffer more
|
||||
rp2040=5, # Limited RAM
|
||||
rp2040=8, # Moderate RAM
|
||||
bk72xx=8, # Moderate RAM
|
||||
nrf52=8, # Moderate RAM
|
||||
rtl87xx=8, # Moderate RAM
|
||||
@@ -454,6 +455,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.11")
|
||||
# Enable optimized memzero/memcmp in libsodium instead of volatile byte loops
|
||||
cg.add_build_flag("-DHAVE_WEAK_SYMBOLS=1")
|
||||
cg.add_build_flag("-DHAVE_INLINE_ASM=1")
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ message ListEntitiesBinarySensorResponse {
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -334,7 +334,7 @@ message BinarySensorStateResponse {
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
// If the binary sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -350,7 +350,7 @@ message ListEntitiesCoverResponse {
|
||||
option (ifdef) = "USE_COVER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -383,7 +383,7 @@ message CoverStateResponse {
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// legacy: state has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
// Deprecated in API version 1.1
|
||||
@@ -409,7 +409,7 @@ message CoverCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
|
||||
// legacy: command has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
@@ -434,7 +434,7 @@ message ListEntitiesFanResponse {
|
||||
option (ifdef) = "USE_FAN";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -466,7 +466,7 @@ message FanStateResponse {
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
// Deprecated in API version 1.6
|
||||
@@ -483,7 +483,7 @@ message FanCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
// Deprecated in API version 1.6
|
||||
@@ -522,7 +522,7 @@ message ListEntitiesLightResponse {
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -551,7 +551,7 @@ message LightStateResponse {
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
ColorMode color_mode = 11;
|
||||
@@ -573,7 +573,7 @@ message LightCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
@@ -627,7 +627,7 @@ message ListEntitiesSensorResponse {
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -651,7 +651,7 @@ message SensorStateResponse {
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float state = 2;
|
||||
// If the sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -667,7 +667,7 @@ message ListEntitiesSwitchResponse {
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -685,7 +685,7 @@ message SwitchStateResponse {
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -696,7 +696,7 @@ message SwitchCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -709,7 +709,7 @@ message ListEntitiesTextSensorResponse {
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -726,7 +726,7 @@ message TextSensorStateResponse {
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
// If the text sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -922,7 +922,7 @@ message ListEntitiesServicesResponse {
|
||||
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
|
||||
|
||||
string name = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
repeated ListEntitiesServicesArgument args = 3 [(fixed_vector) = true];
|
||||
SupportsResponseType supports_response = 4;
|
||||
}
|
||||
@@ -945,7 +945,7 @@ message ExecuteServiceRequest {
|
||||
option (no_delay) = true;
|
||||
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
repeated ExecuteServiceArgument args = 2 [(fixed_vector) = true];
|
||||
uint32 call_id = 3 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"];
|
||||
bool return_response = 4 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"];
|
||||
@@ -972,7 +972,7 @@ message ListEntitiesCameraResponse {
|
||||
option (ifdef) = "USE_CAMERA";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
bool disabled_by_default = 5;
|
||||
@@ -987,7 +987,7 @@ message CameraImageResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CAMERA";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bytes data = 2;
|
||||
bool done = 3;
|
||||
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
|
||||
@@ -1057,7 +1057,7 @@ message ListEntitiesClimateResponse {
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1095,7 +1095,7 @@ message ClimateStateResponse {
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
ClimateMode mode = 2;
|
||||
float current_temperature = 3;
|
||||
float target_temperature = 4;
|
||||
@@ -1121,7 +1121,7 @@ message ClimateCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_mode = 2;
|
||||
ClimateMode mode = 3;
|
||||
bool has_target_temperature = 4;
|
||||
@@ -1168,7 +1168,7 @@ message ListEntitiesWaterHeaterResponse {
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 5;
|
||||
@@ -1189,7 +1189,7 @@ message WaterHeaterStateResponse {
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float current_temperature = 2;
|
||||
float target_temperature = 3;
|
||||
WaterHeaterMode mode = 4;
|
||||
@@ -1219,7 +1219,7 @@ message WaterHeaterCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// Bitmask of which fields are set (see WaterHeaterCommandHasField)
|
||||
uint32 has_fields = 2;
|
||||
WaterHeaterMode mode = 3;
|
||||
@@ -1244,7 +1244,7 @@ message ListEntitiesNumberResponse {
|
||||
option (ifdef) = "USE_NUMBER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1266,7 +1266,7 @@ message NumberStateResponse {
|
||||
option (ifdef) = "USE_NUMBER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float state = 2;
|
||||
// If the number does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -1280,7 +1280,7 @@ message NumberCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -1293,7 +1293,7 @@ message ListEntitiesSelectResponse {
|
||||
option (ifdef) = "USE_SELECT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1310,7 +1310,7 @@ message SelectStateResponse {
|
||||
option (ifdef) = "USE_SELECT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
// If the select does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -1324,7 +1324,7 @@ message SelectCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -1337,7 +1337,7 @@ message ListEntitiesSirenResponse {
|
||||
option (ifdef) = "USE_SIREN";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1356,7 +1356,7 @@ message SirenStateResponse {
|
||||
option (ifdef) = "USE_SIREN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -1367,7 +1367,7 @@ message SirenCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_tone = 4;
|
||||
@@ -1400,7 +1400,7 @@ message ListEntitiesLockResponse {
|
||||
option (ifdef) = "USE_LOCK";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1422,7 +1422,7 @@ message LockStateResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
LockState state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -1432,7 +1432,7 @@ message LockCommandRequest {
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
LockCommand command = 2;
|
||||
|
||||
// Not yet implemented:
|
||||
@@ -1449,7 +1449,7 @@ message ListEntitiesButtonResponse {
|
||||
option (ifdef) = "USE_BUTTON";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1466,7 +1466,7 @@ message ButtonCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
uint32 device_id = 2 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
@@ -1516,7 +1516,7 @@ message ListEntitiesMediaPlayerResponse {
|
||||
option (ifdef) = "USE_MEDIA_PLAYER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -1538,7 +1538,7 @@ message MediaPlayerStateResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_MEDIA_PLAYER";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
MediaPlayerState state = 2;
|
||||
float volume = 3;
|
||||
bool muted = 4;
|
||||
@@ -1551,7 +1551,7 @@ message MediaPlayerCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
|
||||
bool has_command = 2;
|
||||
MediaPlayerCommand command = 3;
|
||||
@@ -2104,7 +2104,7 @@ message ListEntitiesAlarmControlPanelResponse {
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
@@ -2122,7 +2122,7 @@ message AlarmControlPanelStateResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
AlarmControlPanelState state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2133,7 +2133,7 @@ message AlarmControlPanelCommandRequest {
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
AlarmControlPanelStateCommand command = 2;
|
||||
string code = 3;
|
||||
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
|
||||
@@ -2151,7 +2151,7 @@ message ListEntitiesTextResponse {
|
||||
option (ifdef) = "USE_TEXT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
@@ -2171,7 +2171,7 @@ message TextStateResponse {
|
||||
option (ifdef) = "USE_TEXT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
// If the Text does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
@@ -2185,7 +2185,7 @@ message TextCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string state = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2199,7 +2199,7 @@ message ListEntitiesDateResponse {
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2215,7 +2215,7 @@ message DateStateResponse {
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// If the date does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 2;
|
||||
@@ -2231,7 +2231,7 @@ message DateCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
uint32 year = 2;
|
||||
uint32 month = 3;
|
||||
uint32 day = 4;
|
||||
@@ -2246,7 +2246,7 @@ message ListEntitiesTimeResponse {
|
||||
option (ifdef) = "USE_DATETIME_TIME";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2262,7 +2262,7 @@ message TimeStateResponse {
|
||||
option (ifdef) = "USE_DATETIME_TIME";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// If the time does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 2;
|
||||
@@ -2278,7 +2278,7 @@ message TimeCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
uint32 hour = 2;
|
||||
uint32 minute = 3;
|
||||
uint32 second = 4;
|
||||
@@ -2293,7 +2293,7 @@ message ListEntitiesEventResponse {
|
||||
option (ifdef) = "USE_EVENT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2311,7 +2311,7 @@ message EventResponse {
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_EVENT";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
string event_type = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2324,7 +2324,7 @@ message ListEntitiesValveResponse {
|
||||
option (ifdef) = "USE_VALVE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2351,7 +2351,7 @@ message ValveStateResponse {
|
||||
option (ifdef) = "USE_VALVE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
float position = 2;
|
||||
ValveOperation current_operation = 3;
|
||||
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
|
||||
@@ -2364,7 +2364,7 @@ message ValveCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool has_position = 2;
|
||||
float position = 3;
|
||||
bool stop = 4;
|
||||
@@ -2379,7 +2379,7 @@ message ListEntitiesDateTimeResponse {
|
||||
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2395,7 +2395,7 @@ message DateTimeStateResponse {
|
||||
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
// If the datetime does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 2;
|
||||
@@ -2409,7 +2409,7 @@ message DateTimeCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
fixed32 epoch_seconds = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2422,7 +2422,7 @@ message ListEntitiesUpdateResponse {
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
reserved 4; // Deprecated: was string unique_id
|
||||
|
||||
@@ -2439,7 +2439,7 @@ message UpdateStateResponse {
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
bool missing_state = 2;
|
||||
bool in_progress = 3;
|
||||
bool has_progress = 4;
|
||||
@@ -2463,7 +2463,7 @@ message UpdateCommandRequest {
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
fixed32 key = 1 [(force) = true];
|
||||
UpdateCommand command = 2;
|
||||
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
@@ -2505,13 +2505,14 @@ message ListEntitiesInfraredResponse {
|
||||
option (ifdef) = "USE_INFRARED";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3;
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 5;
|
||||
EntityCategory entity_category = 6;
|
||||
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
|
||||
uint32 capabilities = 8; // Bitfield of InfraredCapabilityFlags
|
||||
uint32 receiver_frequency = 9; // Demodulation frequency of the IR receiver in Hz (0 = unspecified)
|
||||
}
|
||||
|
||||
// Command to transmit infrared/RF data using raw timings
|
||||
@@ -2521,7 +2522,7 @@ message InfraredRFTransmitRawTimingsRequest {
|
||||
option (ifdef) = "USE_IR_RF";
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2; // Key identifying the transmitter instance
|
||||
fixed32 key = 2 [(force) = true]; // Key identifying the transmitter instance
|
||||
uint32 carrier_frequency = 3; // Carrier frequency in Hz
|
||||
uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.)
|
||||
repeated sint32 timings = 5 [packed = true, (packed_buffer) = true]; // Raw timings in microseconds (zigzag-encoded): positive = mark (LED/TX on), negative = space (LED/TX off)
|
||||
@@ -2535,7 +2536,7 @@ message InfraredRFReceiveEvent {
|
||||
option (no_delay) = true;
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2; // Key identifying the receiver instance
|
||||
fixed32 key = 2 [(force) = true]; // Key identifying the receiver instance
|
||||
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector<int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,12 @@ class APIBuffer {
|
||||
this->reserve(n);
|
||||
this->size_ = n; // no zero-fill
|
||||
}
|
||||
/// Reserve capacity for max(reserve_size, new_size) bytes, then set size to new_size.
|
||||
/// Single grow_ check regardless of argument order.
|
||||
inline void reserve_and_resize(size_t reserve_size, size_t new_size) ESPHOME_ALWAYS_INLINE {
|
||||
this->reserve(std::max(reserve_size, new_size));
|
||||
this->size_ = new_size;
|
||||
}
|
||||
uint8_t *data() { return this->data_.get(); }
|
||||
const uint8_t *data() const { return this->data_.get(); }
|
||||
size_t size() const { return this->size_; }
|
||||
|
||||
@@ -64,7 +64,11 @@ static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS *
|
||||
// A stalled handshake from a buggy client or network glitch holds a connection
|
||||
// slot, which can prevent legitimate clients from reconnecting. Also hardens
|
||||
// against the less likely case of intentional connection slot exhaustion.
|
||||
static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 15000;
|
||||
//
|
||||
// 60s is intentionally high: on ESP8266 with power_save_mode: LIGHT and weak
|
||||
// WiFi (-70 dBm+), TCP retransmissions push real-world handshake times to
|
||||
// 28-30s. See https://github.com/esphome/esphome/issues/14999
|
||||
static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 60000;
|
||||
|
||||
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||
|
||||
@@ -128,8 +132,6 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
|
||||
|
||||
void APIConnection::start() {
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
|
||||
@@ -230,7 +232,7 @@ void APIConnection::loop() {
|
||||
this->last_traffic_ = now;
|
||||
}
|
||||
// read a packet
|
||||
this->read_message(buffer.data_len, buffer.type, buffer.data);
|
||||
this->read_message_(buffer.data_len, buffer.type, buffer.data);
|
||||
if (this->flags_.remove)
|
||||
return;
|
||||
}
|
||||
@@ -1461,7 +1463,7 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent
|
||||
void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range (max %u)", msg.instance,
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range (max %" PRIu32 ")", msg.instance,
|
||||
static_cast<uint32_t>(proxies.size()));
|
||||
return;
|
||||
}
|
||||
@@ -1472,7 +1474,7 @@ void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigure
|
||||
void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
proxies[msg.instance]->write_from_client(msg.data, msg.data_len);
|
||||
@@ -1481,7 +1483,7 @@ void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest
|
||||
void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
proxies[msg.instance]->set_modem_pins(msg.line_states);
|
||||
@@ -1490,7 +1492,7 @@ void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetM
|
||||
void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
SerialProxyGetModemPinsResponse resp{};
|
||||
@@ -1502,7 +1504,7 @@ void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetM
|
||||
void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
switch (msg.type) {
|
||||
@@ -1515,16 +1517,16 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
|
||||
resp.instance = msg.instance;
|
||||
resp.type = enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH;
|
||||
switch (proxies[msg.instance]->flush_port()) {
|
||||
case uart::FlushResult::SUCCESS:
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_OK;
|
||||
break;
|
||||
case uart::FlushResult::ASSUMED_SUCCESS:
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS;
|
||||
break;
|
||||
case uart::FlushResult::TIMEOUT:
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_TIMEOUT;
|
||||
break;
|
||||
case uart::FlushResult::FAILED:
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
@@ -1532,7 +1534,7 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown serial proxy request type: %u", static_cast<uint32_t>(msg.type));
|
||||
ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(msg.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1545,6 +1547,7 @@ uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection
|
||||
auto *infrared = static_cast<infrared::Infrared *>(entity);
|
||||
ListEntitiesInfraredResponse msg;
|
||||
msg.capabilities = infrared->get_capability_flags();
|
||||
msg.receiver_frequency = infrared->get_traits().get_receiver_frequency_hz();
|
||||
return fill_and_encode_entity_info(infrared, msg, conn, remaining_size);
|
||||
}
|
||||
#endif
|
||||
@@ -2018,25 +2021,23 @@ uint16_t APIConnection::encode_to_buffer(uint32_t calculated_size, MessageEncode
|
||||
|
||||
auto &shared_buf = conn->parent_->get_shared_buffer_ref();
|
||||
|
||||
size_t to_add;
|
||||
if (conn->flags_.batch_first_message) {
|
||||
// First message - buffer already prepared by caller, just clear flag
|
||||
conn->flags_.batch_first_message = false;
|
||||
to_add = calculated_size;
|
||||
} 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);
|
||||
// Reserve for full message, resize to include footer gap + header padding + payload
|
||||
to_add = total_calculated_size;
|
||||
}
|
||||
|
||||
// Pre-resize buffer to include payload, then encode through raw pointer
|
||||
size_t write_start = shared_buf.size();
|
||||
shared_buf.resize(write_start + calculated_size);
|
||||
ProtoWriteBuffer buffer{&shared_buf, write_start};
|
||||
shared_buf.resize(shared_buf.size() + to_add);
|
||||
ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size};
|
||||
encode_fn(msg, buffer);
|
||||
|
||||
// Return total size (header + payload + footer)
|
||||
return static_cast<uint16_t>(header_padding + calculated_size + footer_size);
|
||||
return static_cast<uint16_t>(total_calculated_size);
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
|
||||
@@ -2068,37 +2069,9 @@ void APIConnection::on_fatal_error() {
|
||||
this->flags_.remove = true;
|
||||
}
|
||||
|
||||
void __attribute__((flatten)) APIConnection::DeferredBatch::push_item(const BatchItem &item) { items.push_back(item); }
|
||||
|
||||
void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
uint8_t aux_data_index) {
|
||||
// Check if we already have a message of this type for this entity
|
||||
// This provides deduplication per entity/message_type combination
|
||||
// O(n) but optimized for RAM and not performance.
|
||||
// Skip deduplication for events - they are edge-triggered, every occurrence matters
|
||||
#ifdef USE_EVENT
|
||||
if (message_type != EventResponse::MESSAGE_TYPE)
|
||||
#endif
|
||||
{
|
||||
for (const auto &item : items) {
|
||||
if (item.entity == entity && item.message_type == message_type)
|
||||
return; // Already queued
|
||||
}
|
||||
}
|
||||
// No existing item found (or event), add new one
|
||||
this->push_item({entity, message_type, estimated_size, aux_data_index});
|
||||
}
|
||||
|
||||
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
// Add high priority message and swap to front
|
||||
// This avoids expensive vector::insert which shifts all elements
|
||||
// Note: We only ever have one high-priority message at a time (ping OR disconnect)
|
||||
// If we're disconnecting, pings are blocked, so this simple swap is sufficient
|
||||
this->push_item({entity, message_type, estimated_size, AUX_DATA_UNUSED});
|
||||
if (items.size() > 1) {
|
||||
// Swap the new high-priority item to the front
|
||||
std::swap(items.front(), items.back());
|
||||
}
|
||||
bool APIConnection::schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
|
||||
bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
|
||||
@@ -44,16 +44,46 @@ static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= AP
|
||||
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
|
||||
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
|
||||
|
||||
#ifdef USE_BENCHMARK
|
||||
class APIConnection;
|
||||
void bench_enable_immediate_send(APIConnection *conn);
|
||||
void bench_clear_batch(APIConnection *conn);
|
||||
void bench_process_batch(APIConnection *conn);
|
||||
#endif
|
||||
|
||||
class APIConnection final : public APIServerConnectionBase {
|
||||
public:
|
||||
friend class APIServer;
|
||||
friend class ListEntitiesIterator;
|
||||
#ifdef USE_BENCHMARK
|
||||
friend void bench_enable_immediate_send(APIConnection *conn);
|
||||
friend void bench_clear_batch(APIConnection *conn);
|
||||
friend void bench_process_batch(APIConnection *conn);
|
||||
#endif
|
||||
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
|
||||
virtual ~APIConnection();
|
||||
~APIConnection();
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
|
||||
protected:
|
||||
// read_message_ is defined here (instead of in APIServerConnectionBase) so the
|
||||
// compiler can devirtualize and inline on_* handler calls within this final class.
|
||||
void read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data);
|
||||
|
||||
// Auth helpers defined here (not in ProtoService) so the compiler can
|
||||
// devirtualize is_connection_setup()/on_no_setup_connection() calls
|
||||
// within this final class.
|
||||
inline bool check_connection_setup_() {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
inline bool check_authenticated_() { return this->check_connection_setup_(); }
|
||||
|
||||
public:
|
||||
bool send_list_info_done() {
|
||||
return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE,
|
||||
ListEntitiesDoneResponse::ESTIMATED_SIZE);
|
||||
@@ -63,72 +93,72 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state(cover::Cover *cover);
|
||||
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
||||
void on_cover_command_request(const CoverCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state(fan::Fan *fan);
|
||||
void on_fan_command_request(const FanCommandRequest &msg) override;
|
||||
void on_fan_command_request(const FanCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state(light::LightState *light);
|
||||
void on_light_command_request(const LightCommandRequest &msg) override;
|
||||
void on_light_command_request(const LightCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state(sensor::Sensor *sensor);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state(switch_::Switch *a_switch);
|
||||
void on_switch_command_request(const SwitchCommandRequest &msg) override;
|
||||
void on_switch_command_request(const SwitchCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor);
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
void set_camera_state(std::shared_ptr<camera::CameraImage> image);
|
||||
void on_camera_image_request(const CameraImageRequest &msg) override;
|
||||
void on_camera_image_request(const CameraImageRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg) override;
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool send_number_state(number::Number *number);
|
||||
void on_number_command_request(const NumberCommandRequest &msg) override;
|
||||
void on_number_command_request(const NumberCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool send_date_state(datetime::DateEntity *date);
|
||||
void on_date_command_request(const DateCommandRequest &msg) override;
|
||||
void on_date_command_request(const DateCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool send_time_state(datetime::TimeEntity *time);
|
||||
void on_time_command_request(const TimeCommandRequest &msg) override;
|
||||
void on_time_command_request(const TimeCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool send_datetime_state(datetime::DateTimeEntity *datetime);
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_text_state(text::Text *text);
|
||||
void on_text_command_request(const TextCommandRequest &msg) override;
|
||||
void on_text_command_request(const TextCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_select_state(select::Select *select);
|
||||
void on_select_command_request(const SelectCommandRequest &msg) override;
|
||||
void on_select_command_request(const SelectCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
void on_button_command_request(const ButtonCommandRequest &msg) override;
|
||||
void on_button_command_request(const ButtonCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state(lock::Lock *a_lock);
|
||||
void on_lock_command_request(const LockCommandRequest &msg) override;
|
||||
void on_lock_command_request(const LockCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool send_valve_state(valve::Valve *valve);
|
||||
void on_valve_command_request(const ValveCommandRequest &msg) override;
|
||||
void on_valve_command_request(const ValveCommandRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool send_media_player_state(media_player::MediaPlayer *media_player);
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &msg);
|
||||
#endif
|
||||
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
@@ -138,23 +168,23 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
this->send_message(call);
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &msg);
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#endif // USE_API_HOMEASSISTANT_SERVICES
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request() override;
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg);
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request();
|
||||
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
|
||||
void on_subscribe_bluetooth_connections_free_request() override;
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
|
||||
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg) override;
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg);
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg);
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg);
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg);
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg);
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg);
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg);
|
||||
void on_subscribe_bluetooth_connections_free_request();
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg);
|
||||
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg);
|
||||
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
@@ -165,42 +195,42 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
|
||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
|
||||
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override;
|
||||
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg);
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &msg);
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg);
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &msg);
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg);
|
||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg);
|
||||
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg);
|
||||
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override;
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
|
||||
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg);
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
|
||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
|
||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool send_water_heater_state(water_heater::WaterHeater *water_heater);
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override;
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg);
|
||||
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) override;
|
||||
void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) override;
|
||||
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) override;
|
||||
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) override;
|
||||
void on_serial_proxy_request(const SerialProxyRequest &msg) override;
|
||||
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg);
|
||||
void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg);
|
||||
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg);
|
||||
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg);
|
||||
void on_serial_proxy_request(const SerialProxyRequest &msg);
|
||||
void send_serial_proxy_data(const SerialProxyDataReceived &msg);
|
||||
#endif
|
||||
|
||||
@@ -210,26 +240,26 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool send_update_state(update::UpdateEntity *update);
|
||||
void on_update_command_request(const UpdateCommandRequest &msg) override;
|
||||
void on_update_command_request(const UpdateCommandRequest &msg);
|
||||
#endif
|
||||
|
||||
void on_disconnect_response() override;
|
||||
void on_ping_response() override {
|
||||
void on_disconnect_response();
|
||||
void on_ping_response() {
|
||||
// we initiated ping
|
||||
this->flags_.sent_ping = false;
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void on_get_time_response(const GetTimeResponse &value) override;
|
||||
void on_get_time_response(const GetTimeResponse &value);
|
||||
#endif
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
void on_disconnect_request() override;
|
||||
void on_ping_request() override;
|
||||
void on_device_info_request() override;
|
||||
void on_list_entities_request() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
|
||||
void on_subscribe_states_request() override {
|
||||
void on_hello_request(const HelloRequest &msg);
|
||||
void on_disconnect_request();
|
||||
void on_ping_request();
|
||||
void on_device_info_request();
|
||||
void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
|
||||
void on_subscribe_states_request() {
|
||||
this->flags_.state_subscription = true;
|
||||
// Start initial state iterator only if no iterator is active
|
||||
// If list_entities is running, we'll start initial_state when it completes
|
||||
@@ -237,7 +267,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||
}
|
||||
}
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override {
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
|
||||
this->flags_.log_subscription = msg.level;
|
||||
if (msg.dump_config)
|
||||
App.schedule_dump_config();
|
||||
@@ -249,13 +279,13 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; }
|
||||
void on_subscribe_homeassistant_services_request() { this->flags_.service_call_subscription = true; }
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void on_subscribe_home_assistant_states_request() override;
|
||||
void on_subscribe_home_assistant_states_request();
|
||||
#endif
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
@@ -265,13 +295,13 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg);
|
||||
#endif
|
||||
|
||||
bool is_authenticated() override {
|
||||
bool is_authenticated() {
|
||||
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED;
|
||||
}
|
||||
bool is_connection_setup() override {
|
||||
bool is_connection_setup() {
|
||||
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED ||
|
||||
this->is_authenticated();
|
||||
}
|
||||
@@ -284,8 +314,8 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
(this->client_api_version_major_ == major && this->client_api_version_minor_ >= minor);
|
||||
}
|
||||
|
||||
void on_fatal_error() override;
|
||||
void on_no_setup_connection() override;
|
||||
void on_fatal_error();
|
||||
void on_no_setup_connection();
|
||||
|
||||
// Function pointer type for type-erased message encoding
|
||||
using MessageEncodeFn = void (*)(const void *, ProtoWriteBuffer &);
|
||||
@@ -305,9 +335,9 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
// Reserve space for header padding + message + footer
|
||||
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
||||
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
||||
shared_buf.reserve(total_size);
|
||||
// Resize to add header padding so message encoding starts at the correct position
|
||||
shared_buf.resize(header_padding);
|
||||
// Reserve full size but only set initial size to header padding
|
||||
// so message encoding starts at the correct position
|
||||
shared_buf.reserve_and_resize(total_size, header_padding);
|
||||
}
|
||||
|
||||
// Convenience overload - computes frame overhead internally
|
||||
@@ -324,7 +354,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
return true;
|
||||
return this->try_to_clear_buffer_slow_(log_out_of_space);
|
||||
}
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type);
|
||||
|
||||
const char *get_name() const { return this->helper_->get_client_name(); }
|
||||
/// Get peer name (IP address) into caller-provided buffer, returns buf for convenience
|
||||
@@ -614,11 +644,28 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
|
||||
// Add item to the batch (with deduplication)
|
||||
void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
uint8_t aux_data_index = AUX_DATA_UNUSED);
|
||||
uint8_t aux_data_index = AUX_DATA_UNUSED) {
|
||||
// Dedup: O(n) scan but optimized for RAM over performance
|
||||
// Skip deduplication for events - they are edge-triggered, every occurrence matters
|
||||
#ifdef USE_EVENT
|
||||
if (message_type != EventResponse::MESSAGE_TYPE)
|
||||
#endif
|
||||
{
|
||||
for (const auto &item : this->items) {
|
||||
if (item.entity == entity && item.message_type == message_type)
|
||||
return; // Already queued
|
||||
}
|
||||
}
|
||||
this->items.push_back({entity, message_type, estimated_size, aux_data_index});
|
||||
}
|
||||
// Add item to the front of the batch (for high priority messages like ping)
|
||||
void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size);
|
||||
// Single push_back site to avoid duplicate _M_realloc_insert instantiation
|
||||
void push_item(const BatchItem &item);
|
||||
void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
// Swap to front avoids expensive vector::insert which shifts all elements
|
||||
this->items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED});
|
||||
if (this->items.size() > 1) {
|
||||
std::swap(this->items.front(), this->items.back());
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all items
|
||||
void clear() {
|
||||
@@ -683,7 +730,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
ActiveIterator active_iterator_{ActiveIterator::NONE};
|
||||
// Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary
|
||||
|
||||
uint32_t get_batch_delay_ms_() const;
|
||||
uint32_t get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
|
||||
// Message will use 8 more bytes than the minimum size, and typical
|
||||
// MTU is 1500. Sometimes users will see as low as 1460 MTU.
|
||||
// If its IPv6 the header is 40 bytes, and if its IPv4
|
||||
@@ -750,10 +797,8 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
}
|
||||
|
||||
// Helper function to schedule a high priority message at the front of the batch
|
||||
bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
// Out-of-line: callers (on_shutdown, check_keepalive_) are cold paths
|
||||
bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size);
|
||||
|
||||
// Helper function to log client messages with name and peername
|
||||
void log_client_(int level, const LogString *message);
|
||||
|
||||
@@ -100,149 +100,61 @@ const LogString *api_error_to_logstr(APIError err) {
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
||||
// Default implementation for loop - handles sending buffered data
|
||||
APIError APIFrameHelper::loop() {
|
||||
if (this->tx_buf_count_ > 0) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
APIError APIFrameHelper::drain_overflow_and_handle_errors_() {
|
||||
if (this->overflow_buf_.try_drain(this->socket_.get()) == -1) {
|
||||
int err = errno;
|
||||
if (this->check_socket_write_err_(err) != APIError::WOULD_BLOCK) {
|
||||
HELPER_LOG("Socket write failed with errno %d", err);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
// Common socket write error handling
|
||||
APIError APIFrameHelper::handle_socket_write_error_() {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
HELPER_LOG("Socket write failed with errno %d", errno);
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
|
||||
uint16_t offset) {
|
||||
// Check if queue is full
|
||||
if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) {
|
||||
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 write_pos = 0;
|
||||
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_skip >= iov[i].iov_len) {
|
||||
// Skip this entire segment
|
||||
to_skip -= static_cast<uint16_t>(iov[i].iov_len);
|
||||
} else {
|
||||
// Include this segment (partially or fully)
|
||||
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;
|
||||
std::memcpy(buffer->data.get() + write_pos, src, len);
|
||||
write_pos += len;
|
||||
to_skip = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// Write data to socket, overflow to backlog buffer if LWIP TCP send buffer is full.
|
||||
// Returns OK if all data was sent or successfully queued.
|
||||
// Returns SOCKET_WRITE_FAILED on hard error (sets state to FAILED).
|
||||
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||
// Returns APIError::OK if successful (or would block, but data has been buffered)
|
||||
// Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED
|
||||
|
||||
if (iovcnt == 0)
|
||||
return APIError::OK; // Nothing to do, success
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
LOG_PACKET_SENDING(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Try to send any existing buffered data first if there is any
|
||||
if (this->tx_buf_count_ > 0) {
|
||||
APIError send_result = try_send_tx_buf_();
|
||||
// If real error occurred (not just WOULD_BLOCK), return it
|
||||
if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
|
||||
return send_result;
|
||||
}
|
||||
uint16_t skip = 0;
|
||||
|
||||
// If there is still data in the buffer, we can't send, buffer
|
||||
// the new data and return
|
||||
if (this->tx_buf_count_ > 0) {
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
|
||||
return APIError::OK; // Success, data buffered
|
||||
}
|
||||
// Drain any existing backlog first
|
||||
if (!this->overflow_buf_.empty()) [[unlikely]] {
|
||||
APIError err = this->drain_overflow_and_handle_errors_();
|
||||
if (err != APIError::OK)
|
||||
return err;
|
||||
}
|
||||
|
||||
// Try to send directly if no buffered data
|
||||
// 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 backlog is clear, try direct send
|
||||
if (this->overflow_buf_.empty()) [[likely]] {
|
||||
ssize_t sent =
|
||||
(iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
|
||||
|
||||
if (sent == -1) {
|
||||
APIError err = this->handle_socket_write_error_();
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// Socket would block, buffer the data
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
|
||||
return APIError::OK; // Success, data buffered
|
||||
}
|
||||
return err; // Socket write failed
|
||||
} else if (static_cast<uint16_t>(sent) < total_write_len) {
|
||||
// Partially sent, buffer the remaining data
|
||||
this->buffer_data_from_iov_(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent));
|
||||
}
|
||||
|
||||
return APIError::OK; // Success, all data sent or buffered
|
||||
}
|
||||
|
||||
// Common implementation for trying to send buffered data
|
||||
// IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method
|
||||
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
|
||||
while (this->tx_buf_count_ > 0) {
|
||||
// Get the first buffer in the queue
|
||||
SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get();
|
||||
|
||||
// Try to send the remaining data in this buffer
|
||||
ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining());
|
||||
|
||||
if (sent == -1) {
|
||||
return this->handle_socket_write_error_();
|
||||
} else if (sent == 0) {
|
||||
// Nothing sent but not an error
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (static_cast<uint16_t>(sent) < front_buffer->remaining()) {
|
||||
// Partially sent, update offset
|
||||
// Cast to ensure no overflow issues with uint16_t
|
||||
front_buffer->offset += static_cast<uint16_t>(sent);
|
||||
return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
|
||||
if (sent == -1) [[unlikely]] {
|
||||
int err = errno;
|
||||
if (this->check_socket_write_err_(err) != APIError::WOULD_BLOCK) {
|
||||
HELPER_LOG("Socket write failed with errno %d", err);
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
} else if (static_cast<uint16_t>(sent) >= total_write_len) [[likely]] {
|
||||
return APIError::OK;
|
||||
} else {
|
||||
// Buffer completely sent, remove it from the queue
|
||||
this->tx_buf_[this->tx_buf_head_].reset();
|
||||
this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE;
|
||||
this->tx_buf_count_--;
|
||||
// Continue loop to try sending the next buffer
|
||||
skip = static_cast<uint16_t>(sent);
|
||||
}
|
||||
}
|
||||
|
||||
return APIError::OK; // All buffers sent successfully
|
||||
// Queue unsent data into overflow buffer
|
||||
if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_len, skip)) {
|
||||
HELPER_LOG("Overflow buffer full, dropping connection");
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
const char *APIFrameHelper::get_peername_to(std::span<char, socket::SOCKADDR_STR_LEN> buf) const {
|
||||
@@ -278,11 +190,12 @@ APIError APIFrameHelper::init_common_() {
|
||||
|
||||
APIError APIFrameHelper::handle_socket_read_result_(ssize_t received) {
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
const int err = errno;
|
||||
if (err == EWOULDBLOCK || err == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
HELPER_LOG("Socket read failed with errno %d", err);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_API
|
||||
#include "esphome/components/api/api_buffer.h"
|
||||
#include "esphome/components/api/api_overflow_buffer.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "proto.h"
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -37,8 +39,6 @@ static constexpr uint16_t RX_BUF_NULL_TERMINATOR = 1;
|
||||
// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there)
|
||||
static constexpr size_t MAX_MESSAGES_PER_BATCH = 34;
|
||||
|
||||
class ProtoWriteBuffer;
|
||||
|
||||
// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars)
|
||||
static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32;
|
||||
|
||||
@@ -105,9 +105,9 @@ class APIFrameHelper {
|
||||
}
|
||||
virtual ~APIFrameHelper() = default;
|
||||
virtual APIError init() = 0;
|
||||
virtual APIError loop();
|
||||
virtual APIError loop() = 0;
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
|
||||
bool can_write_without_blocking() { return this->state_ == State::DATA && this->overflow_buf_.empty(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
|
||||
APIError close() {
|
||||
if (state_ == State::CLOSED)
|
||||
@@ -147,25 +147,28 @@ class APIFrameHelper {
|
||||
//
|
||||
void set_nodelay_for_message(bool is_log_message) {
|
||||
if (!is_log_message) {
|
||||
if (this->nodelay_state_ != NODELAY_ON) {
|
||||
if (this->nodelay_counter_) {
|
||||
this->set_nodelay_raw_(true);
|
||||
this->nodelay_state_ = NODELAY_ON;
|
||||
this->nodelay_counter_ = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Log messages: state transitions -1 -> 1 -> ... -> LOG_NAGLE_COUNT -> -1 (flush)
|
||||
if (this->nodelay_state_ == NODELAY_ON) {
|
||||
// Log message: enable Nagle on first, flush after LOG_NAGLE_COUNT
|
||||
if (!this->nodelay_counter_)
|
||||
this->set_nodelay_raw_(false);
|
||||
this->nodelay_state_ = 1;
|
||||
} else if (this->nodelay_state_ >= LOG_NAGLE_COUNT) {
|
||||
if (++this->nodelay_counter_ > LOG_NAGLE_COUNT) {
|
||||
this->set_nodelay_raw_(true);
|
||||
this->nodelay_state_ = NODELAY_ON;
|
||||
} else {
|
||||
this->nodelay_state_++;
|
||||
this->nodelay_counter_ = 0;
|
||||
}
|
||||
}
|
||||
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
// Resize buffer to include footer space if needed (e.g. Noise MAC)
|
||||
if (frame_footer_size_)
|
||||
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||
MessageInfo msg{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
|
||||
}
|
||||
// Write multiple protobuf messages in a single operation
|
||||
// messages contains (message_type, offset, length) for each message in the buffer
|
||||
// The buffer contains all messages with appropriate padding before each
|
||||
@@ -187,28 +190,23 @@ class APIFrameHelper {
|
||||
}
|
||||
|
||||
protected:
|
||||
// Buffer containing data to be sent
|
||||
struct SendBuffer {
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
uint16_t size{0}; // Total size of the buffer
|
||||
uint16_t offset{0}; // Current offset within the buffer
|
||||
|
||||
// Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
|
||||
uint16_t remaining() const { return size - offset; }
|
||||
const uint8_t *current_data() const { return data.get() + offset; }
|
||||
};
|
||||
// Drain backlogged overflow data to the socket and handle errors.
|
||||
// Called when overflow_buf_.empty() is false. Out-of-line to keep the
|
||||
// fast path (empty check) inline at call sites.
|
||||
// Returns OK for transient errors (WOULD_BLOCK), SOCKET_WRITE_FAILED for hard errors.
|
||||
APIError drain_overflow_and_handle_errors_();
|
||||
|
||||
// Common implementation for writing raw data to socket
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
|
||||
|
||||
// Try to send data from the tx buffer
|
||||
APIError try_send_tx_buf_();
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset);
|
||||
|
||||
// Common socket write error handling
|
||||
APIError handle_socket_write_error_();
|
||||
// Check if a socket write errno is a hard error (not WOULD_BLOCK/EAGAIN).
|
||||
// Returns WOULD_BLOCK for transient errors, SOCKET_WRITE_FAILED for hard errors.
|
||||
APIError check_socket_write_err_(int err) {
|
||||
if (err == EWOULDBLOCK || err == EAGAIN)
|
||||
return APIError::WOULD_BLOCK;
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED;
|
||||
}
|
||||
|
||||
// Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit)
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
@@ -243,8 +241,8 @@ class APIFrameHelper {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
// Containers (size varies, but typically 12+ bytes on 32-bit)
|
||||
std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
|
||||
// Backlog for unsent data when TCP send buffer is full (rarely used in production)
|
||||
APIOverflowBuffer overflow_buf_;
|
||||
APIBuffer rx_buf_;
|
||||
|
||||
// Client name buffer - stores name from Hello message or initial peername
|
||||
@@ -255,21 +253,17 @@ class APIFrameHelper {
|
||||
State state_{State::INITIALIZE};
|
||||
uint8_t frame_header_padding_{0};
|
||||
uint8_t frame_footer_size_{0};
|
||||
uint8_t tx_buf_head_{0};
|
||||
uint8_t tx_buf_tail_{0};
|
||||
uint8_t tx_buf_count_{0};
|
||||
// Nagle batching state for log messages. NODELAY_ON (-1) means NODELAY is enabled
|
||||
// (immediate send). Values 1..LOG_NAGLE_COUNT count log messages in the current Nagle batch.
|
||||
// After LOG_NAGLE_COUNT logs, we switch to NODELAY to flush and reset.
|
||||
// Nagle batching counter for log messages. 0 means NODELAY is enabled (immediate send).
|
||||
// Values 1..LOG_NAGLE_COUNT count log messages in the current Nagle batch.
|
||||
// After LOG_NAGLE_COUNT logs, we flush by re-enabling NODELAY and resetting to 0.
|
||||
// ESP8266 has the tightest TCP send buffer (2×MSS) and needs conservative batching.
|
||||
// ESP32 (4×MSS+), RP2040 (8×MSS), and LibreTiny (4×MSS) can coalesce more.
|
||||
static constexpr int8_t NODELAY_ON = -1;
|
||||
#ifdef USE_ESP8266
|
||||
static constexpr int8_t LOG_NAGLE_COUNT = 2;
|
||||
static constexpr uint8_t LOG_NAGLE_COUNT = 2;
|
||||
#else
|
||||
static constexpr int8_t LOG_NAGLE_COUNT = 3;
|
||||
static constexpr uint8_t LOG_NAGLE_COUNT = 3;
|
||||
#endif
|
||||
int8_t nodelay_state_{NODELAY_ON};
|
||||
uint8_t nodelay_counter_{0};
|
||||
|
||||
// Internal helper to set TCP_NODELAY socket option
|
||||
void set_nodelay_raw_(bool enable) {
|
||||
|
||||
@@ -153,8 +153,10 @@ APIError APINoiseFrameHelper::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// Use base class implementation for buffer sending
|
||||
return APIFrameHelper::loop();
|
||||
if (!this->overflow_buf_.empty()) [[unlikely]] {
|
||||
return this->drain_overflow_and_handle_errors_();
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_.
|
||||
@@ -450,14 +452,6 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
// Resize to include MAC space (required for Noise encryption)
|
||||
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||
MessageInfo msg{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) {
|
||||
APIError aerr = this->check_data_state_();
|
||||
if (aerr != APIError::OK)
|
||||
|
||||
@@ -22,7 +22,6 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -64,8 +64,10 @@ APIError APIPlaintextFrameHelper::loop() {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
// Use base class implementation for buffer sending
|
||||
return APIFrameHelper::loop();
|
||||
if (!this->overflow_buf_.empty()) [[unlikely]] {
|
||||
return this->drain_overflow_and_handle_errors_();
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_.
|
||||
@@ -235,11 +237,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = this->rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
MessageInfo msg{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
||||
return write_protobuf_messages(buffer, std::span<const MessageInfo>(&msg, 1));
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer,
|
||||
std::span<const MessageInfo> messages) {
|
||||
APIError aerr = this->check_data_state_();
|
||||
@@ -257,9 +254,11 @@ APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffe
|
||||
uint16_t total_write_len = 0;
|
||||
|
||||
for (const auto &msg : messages) {
|
||||
// Calculate varint sizes for header layout
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(msg.payload_size));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(msg.message_type));
|
||||
// Calculate varint sizes for header layout using inline ternary to avoid varint_slow call overhead
|
||||
uint8_t size_varint_len = msg.payload_size < ProtoSize::VARINT_THRESHOLD_1_BYTE
|
||||
? 1
|
||||
: (msg.payload_size < ProtoSize::VARINT_THRESHOLD_2_BYTE ? 2 : 3);
|
||||
uint8_t type_varint_len = msg.message_type < ProtoSize::VARINT_THRESHOLD_1_BYTE ? 1 : 2;
|
||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
// Calculate where to start writing the header
|
||||
@@ -281,8 +280,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffe
|
||||
//
|
||||
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
||||
// [0] - 0x00 indicator byte
|
||||
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
||||
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
||||
// [1-3] - Payload size varint (3 bytes, for sizes 16384-65535)
|
||||
// [4-5] - Message type varint (2 bytes, for types 128-16383)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// The message starts at offset + frame_header_padding_
|
||||
|
||||
@@ -19,7 +19,6 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
|
||||
|
||||
protected:
|
||||
|
||||
73
esphome/components/api/api_overflow_buffer.cpp
Normal file
73
esphome/components/api/api_overflow_buffer.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "api_overflow_buffer.h"
|
||||
#ifdef USE_API
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
APIOverflowBuffer::~APIOverflowBuffer() {
|
||||
for (auto *entry : this->queue_) {
|
||||
if (entry != nullptr)
|
||||
Entry::destroy(entry);
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t APIOverflowBuffer::try_drain(socket::Socket *socket) {
|
||||
while (this->count_ > 0) {
|
||||
Entry *front = this->queue_[this->head_];
|
||||
|
||||
ssize_t sent = socket->write(front->current_data(), front->remaining());
|
||||
|
||||
if (sent <= 0) {
|
||||
// -1 = error (caller checks errno for EWOULDBLOCK vs hard error)
|
||||
// 0 = nothing sent (treat as no progress)
|
||||
return sent;
|
||||
}
|
||||
|
||||
if (static_cast<uint16_t>(sent) < front->remaining()) {
|
||||
// Partially sent, update offset and stop
|
||||
front->offset += static_cast<uint16_t>(sent);
|
||||
return sent;
|
||||
}
|
||||
|
||||
// Entry fully sent — free it and advance
|
||||
Entry::destroy(front);
|
||||
this->queue_[this->head_] = nullptr;
|
||||
this->head_ = (this->head_ + 1) % API_MAX_SEND_QUEUE;
|
||||
this->count_--;
|
||||
}
|
||||
|
||||
return 0; // All drained
|
||||
}
|
||||
|
||||
bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip) {
|
||||
if (this->count_ >= API_MAX_SEND_QUEUE)
|
||||
return false;
|
||||
|
||||
uint16_t buffer_size = total_len - skip;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
auto *entry = new Entry{new uint8_t[buffer_size], buffer_size, 0};
|
||||
this->queue_[this->tail_] = entry;
|
||||
|
||||
uint16_t to_skip = skip;
|
||||
uint16_t write_pos = 0;
|
||||
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_skip >= iov[i].iov_len) {
|
||||
to_skip -= static_cast<uint16_t>(iov[i].iov_len);
|
||||
} else {
|
||||
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;
|
||||
std::memcpy(entry->data + write_pos, src, len);
|
||||
write_pos += len;
|
||||
to_skip = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this->tail_ = (this->tail_ + 1) % API_MAX_SEND_QUEUE;
|
||||
this->count_++;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
#endif // USE_API
|
||||
76
esphome/components/api/api_overflow_buffer.h
Normal file
76
esphome/components/api/api_overflow_buffer.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_API
|
||||
|
||||
#include "esphome/components/socket/headers.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
/// Circular queue of heap-allocated byte buffers used as a TCP send backlog.
|
||||
///
|
||||
/// Under normal operation this buffer is **never used** — data goes straight
|
||||
/// from the frame helper to the socket. It only fills when the LWIP TCP
|
||||
/// send buffer is full (slow client, congested network, heavy logging).
|
||||
/// The queue drains automatically on subsequent write/loop calls once the
|
||||
/// socket becomes writable again.
|
||||
///
|
||||
/// Capacity is compile-time-fixed via API_MAX_SEND_QUEUE (set from Python
|
||||
/// config). If the queue fills completely the connection is marked failed.
|
||||
class APIOverflowBuffer {
|
||||
public:
|
||||
/// A single heap-allocated send-backlog entry.
|
||||
/// Lifetime is manually managed — see destroy().
|
||||
struct Entry {
|
||||
uint8_t *data;
|
||||
uint16_t size; // Total size of the buffer
|
||||
uint16_t offset; // Current send offset within the buffer
|
||||
|
||||
uint16_t remaining() const { return this->size - this->offset; }
|
||||
const uint8_t *current_data() const { return this->data + this->offset; }
|
||||
|
||||
/// Free this entry and its data buffer.
|
||||
static ESPHOME_ALWAYS_INLINE void destroy(Entry *entry) {
|
||||
delete[] entry->data;
|
||||
delete entry; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
};
|
||||
|
||||
~APIOverflowBuffer();
|
||||
|
||||
/// True when no backlogged data is waiting.
|
||||
bool empty() const { return this->count_ == 0; }
|
||||
|
||||
/// True when the queue has no room for another entry.
|
||||
bool full() const { return this->count_ >= API_MAX_SEND_QUEUE; }
|
||||
|
||||
/// Number of entries currently queued.
|
||||
uint8_t count() const { return this->count_; }
|
||||
|
||||
/// Try to drain queued data to the socket.
|
||||
/// Returns bytes-written > 0 on success/partial, 0 if all drained or no progress,
|
||||
/// -1 on error (caller must check errno to distinguish EWOULDBLOCK from hard errors).
|
||||
/// Callers only need to act on -1; 0 and positive values both mean "no error".
|
||||
/// Frees entries as they are fully sent.
|
||||
ssize_t try_drain(socket::Socket *socket);
|
||||
|
||||
/// Enqueue unsent IOV data into the backlog.
|
||||
/// Copies iov data starting at byte offset `skip` into a new entry.
|
||||
/// Returns false if the queue is full (caller should fail the connection).
|
||||
bool enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip);
|
||||
|
||||
protected:
|
||||
std::array<Entry *, API_MAX_SEND_QUEUE> queue_{};
|
||||
uint8_t head_{0};
|
||||
uint8_t tail_{0};
|
||||
uint8_t count_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
#endif // USE_API
|
||||
@@ -208,7 +208,7 @@ uint32_t DeviceInfoResponse::calculate_size() const {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(5, this->device_class);
|
||||
buffer.encode_bool(6, this->is_status_binary_sensor);
|
||||
@@ -224,7 +224,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesBinarySensorResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_length(1, this->device_class.size());
|
||||
size += ProtoSize::calc_bool(1, this->is_status_binary_sensor);
|
||||
@@ -239,7 +239,7 @@ uint32_t ListEntitiesBinarySensorResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -248,7 +248,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t BinarySensorStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -260,7 +260,7 @@ uint32_t BinarySensorStateResponse::calculate_size() const {
|
||||
#ifdef USE_COVER
|
||||
void ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_bool(5, this->assumed_state);
|
||||
buffer.encode_bool(6, this->supports_position);
|
||||
@@ -279,7 +279,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesCoverResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_bool(1, this->assumed_state);
|
||||
size += ProtoSize::calc_bool(1, this->supports_position);
|
||||
@@ -297,7 +297,7 @@ uint32_t ListEntitiesCoverResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void CoverStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(3, this->position);
|
||||
buffer.encode_float(4, this->tilt);
|
||||
buffer.encode_uint32(5, static_cast<uint32_t>(this->current_operation));
|
||||
@@ -307,7 +307,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t CoverStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->position);
|
||||
size += ProtoSize::calc_float(1, this->tilt);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->current_operation));
|
||||
@@ -357,7 +357,7 @@ bool CoverCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_FAN
|
||||
void ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_bool(5, this->supports_oscillation);
|
||||
buffer.encode_bool(6, this->supports_speed);
|
||||
@@ -378,7 +378,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesFanResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_bool(1, this->supports_oscillation);
|
||||
size += ProtoSize::calc_bool(1, this->supports_speed);
|
||||
@@ -400,7 +400,7 @@ uint32_t ListEntitiesFanResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void FanStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_bool(3, this->oscillating);
|
||||
buffer.encode_uint32(5, static_cast<uint32_t>(this->direction));
|
||||
@@ -412,7 +412,7 @@ void FanStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t FanStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->oscillating);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->direction));
|
||||
@@ -487,7 +487,7 @@ bool FanCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_LIGHT
|
||||
void ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
for (const auto &it : *this->supported_color_modes) {
|
||||
buffer.encode_uint32(12, static_cast<uint32_t>(it), true);
|
||||
@@ -509,7 +509,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesLightResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
if (!this->supported_color_modes->empty()) {
|
||||
for (const auto &it : *this->supported_color_modes) {
|
||||
@@ -534,7 +534,7 @@ uint32_t ListEntitiesLightResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void LightStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_float(3, this->brightness);
|
||||
buffer.encode_uint32(11, static_cast<uint32_t>(this->color_mode));
|
||||
@@ -553,7 +553,7 @@ void LightStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t LightStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
size += ProtoSize::calc_float(1, this->brightness);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->color_mode));
|
||||
@@ -683,7 +683,7 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_SENSOR
|
||||
void ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -702,7 +702,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesSensorResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -720,7 +720,7 @@ uint32_t ListEntitiesSensorResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void SensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -729,7 +729,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t SensorStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -741,7 +741,7 @@ uint32_t SensorStateResponse::calculate_size() const {
|
||||
#ifdef USE_SWITCH
|
||||
void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -757,7 +757,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesSwitchResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -772,7 +772,7 @@ uint32_t ListEntitiesSwitchResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void SwitchStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -780,7 +780,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t SwitchStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -816,7 +816,7 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -831,7 +831,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesTextSensorResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -845,7 +845,7 @@ uint32_t ListEntitiesTextSensorResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void TextSensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -854,7 +854,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t TextSensorStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->state.size());
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1124,7 +1124,7 @@ uint32_t ListEntitiesServicesArgument::calculate_size() const {
|
||||
}
|
||||
void ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->name);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
for (auto &it : this->args) {
|
||||
buffer.encode_sub_message(3, it);
|
||||
}
|
||||
@@ -1133,7 +1133,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesServicesResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
if (!this->args.empty()) {
|
||||
for (const auto &it : this->args) {
|
||||
size += ProtoSize::calc_message_force(1, it.calculate_size());
|
||||
@@ -1269,7 +1269,7 @@ uint32_t ExecuteServiceResponse::calculate_size() const {
|
||||
#ifdef USE_CAMERA
|
||||
void ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
@@ -1283,7 +1283,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesCameraResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_bool(1, this->disabled_by_default);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
@@ -1296,7 +1296,7 @@ uint32_t ListEntitiesCameraResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void CameraImageResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bytes(2, this->data_ptr_, this->data_len_);
|
||||
buffer.encode_bool(3, this->done);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1305,7 +1305,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t CameraImageResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->data_len_);
|
||||
size += ProtoSize::calc_bool(1, this->done);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1330,7 +1330,7 @@ bool CameraImageRequest::decode_varint(uint32_t field_id, proto_varint_value_t v
|
||||
#ifdef USE_CLIMATE
|
||||
void ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_bool(5, this->supports_current_temperature);
|
||||
buffer.encode_bool(6, this->supports_two_point_target_temperature);
|
||||
@@ -1374,7 +1374,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesClimateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_bool(1, this->supports_current_temperature);
|
||||
size += ProtoSize::calc_bool(1, this->supports_two_point_target_temperature);
|
||||
@@ -1429,7 +1429,7 @@ uint32_t ListEntitiesClimateResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void ClimateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
||||
buffer.encode_float(3, this->current_temperature);
|
||||
buffer.encode_float(4, this->target_temperature);
|
||||
@@ -1449,7 +1449,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t ClimateStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->mode));
|
||||
size += ProtoSize::calc_float(1, this->current_temperature);
|
||||
size += ProtoSize::calc_float(1, this->target_temperature);
|
||||
@@ -1563,7 +1563,7 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_WATER_HEATER
|
||||
void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(4, this->icon);
|
||||
@@ -1584,7 +1584,7 @@ void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1606,7 +1606,7 @@ uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(2, this->current_temperature);
|
||||
buffer.encode_float(3, this->target_temperature);
|
||||
buffer.encode_uint32(4, static_cast<uint32_t>(this->mode));
|
||||
@@ -1619,7 +1619,7 @@ void WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t WaterHeaterStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->current_temperature);
|
||||
size += ProtoSize::calc_float(1, this->target_temperature);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->mode));
|
||||
@@ -1675,7 +1675,7 @@ bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value
|
||||
#ifdef USE_NUMBER
|
||||
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -1695,7 +1695,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesNumberResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1714,7 +1714,7 @@ uint32_t ListEntitiesNumberResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void NumberStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1723,7 +1723,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t NumberStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1760,7 +1760,7 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_SELECT
|
||||
void ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -1777,7 +1777,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesSelectResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1795,7 +1795,7 @@ uint32_t ListEntitiesSelectResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void SelectStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1804,7 +1804,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t SelectStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->state.size());
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1849,7 +1849,7 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_SIREN
|
||||
void ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -1868,7 +1868,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesSirenResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1888,7 +1888,7 @@ uint32_t ListEntitiesSirenResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void SirenStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -1896,7 +1896,7 @@ void SirenStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t SirenStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -1961,7 +1961,7 @@ bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_LOCK
|
||||
void ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -1979,7 +1979,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesLockResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -1996,7 +1996,7 @@ uint32_t ListEntitiesLockResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void LockStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -2004,7 +2004,7 @@ void LockStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t LockStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -2054,7 +2054,7 @@ bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_BUTTON
|
||||
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -2069,7 +2069,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesButtonResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -2124,7 +2124,7 @@ uint32_t MediaPlayerSupportedFormat::calculate_size() const {
|
||||
}
|
||||
void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -2143,7 +2143,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesMediaPlayerResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -2163,7 +2163,7 @@ uint32_t ListEntitiesMediaPlayerResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
buffer.encode_float(3, this->volume);
|
||||
buffer.encode_bool(4, this->muted);
|
||||
@@ -2173,7 +2173,7 @@ void MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t MediaPlayerStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
|
||||
size += ProtoSize::calc_float(1, this->volume);
|
||||
size += ProtoSize::calc_bool(1, this->muted);
|
||||
@@ -2249,10 +2249,14 @@ bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id,
|
||||
return true;
|
||||
}
|
||||
void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address, true);
|
||||
buffer.encode_sint32(2, this->rssi, true);
|
||||
buffer.write_raw_byte(8);
|
||||
buffer.encode_varint_raw_64(this->address);
|
||||
buffer.write_raw_byte(16);
|
||||
buffer.encode_varint_raw(encode_zigzag32(this->rssi));
|
||||
buffer.encode_uint32(3, this->address_type);
|
||||
buffer.encode_bytes(4, this->data, this->data_len, true);
|
||||
buffer.write_raw_byte(34);
|
||||
buffer.encode_varint_raw(this->data_len);
|
||||
buffer.encode_raw(this->data, this->data_len);
|
||||
}
|
||||
uint32_t BluetoothLERawAdvertisement::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
@@ -2942,7 +2946,7 @@ bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengt
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -2959,7 +2963,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer) con
|
||||
uint32_t ListEntitiesAlarmControlPanelResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -2975,7 +2979,7 @@ uint32_t ListEntitiesAlarmControlPanelResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -2983,7 +2987,7 @@ void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t AlarmControlPanelStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -3030,7 +3034,7 @@ bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit
|
||||
#ifdef USE_TEXT
|
||||
void ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3048,7 +3052,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesTextResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3065,7 +3069,7 @@ uint32_t ListEntitiesTextResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void TextStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3074,7 +3078,7 @@ void TextStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t TextStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->state.size());
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3119,7 +3123,7 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3133,7 +3137,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesDateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3146,7 +3150,7 @@ uint32_t ListEntitiesDateResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void DateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_uint32(3, this->year);
|
||||
buffer.encode_uint32(4, this->month);
|
||||
@@ -3157,7 +3161,7 @@ void DateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t DateStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
size += ProtoSize::calc_uint32(1, this->year);
|
||||
size += ProtoSize::calc_uint32(1, this->month);
|
||||
@@ -3202,7 +3206,7 @@ bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3216,7 +3220,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesTimeResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3229,7 +3233,7 @@ uint32_t ListEntitiesTimeResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void TimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_uint32(3, this->hour);
|
||||
buffer.encode_uint32(4, this->minute);
|
||||
@@ -3240,7 +3244,7 @@ void TimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t TimeStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
size += ProtoSize::calc_uint32(1, this->hour);
|
||||
size += ProtoSize::calc_uint32(1, this->minute);
|
||||
@@ -3285,7 +3289,7 @@ bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_EVENT
|
||||
void ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3303,7 +3307,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesEventResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3322,7 +3326,7 @@ uint32_t ListEntitiesEventResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void EventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_string(2, this->event_type);
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(3, this->device_id);
|
||||
@@ -3330,7 +3334,7 @@ void EventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t EventResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->event_type.size());
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
@@ -3341,7 +3345,7 @@ uint32_t EventResponse::calculate_size() const {
|
||||
#ifdef USE_VALVE
|
||||
void ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3359,7 +3363,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesValveResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3376,7 +3380,7 @@ uint32_t ListEntitiesValveResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void ValveStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_float(2, this->position);
|
||||
buffer.encode_uint32(3, static_cast<uint32_t>(this->current_operation));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3385,7 +3389,7 @@ void ValveStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t ValveStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_float(1, this->position);
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->current_operation));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3428,7 +3432,7 @@ bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3442,7 +3446,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesDateTimeResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3455,7 +3459,7 @@ uint32_t ListEntitiesDateTimeResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void DateTimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_fixed32(3, this->epoch_seconds);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3464,7 +3468,7 @@ void DateTimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t DateTimeStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
size += ProtoSize::calc_fixed32(1, this->epoch_seconds);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3501,7 +3505,7 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
#ifdef USE_UPDATE
|
||||
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(5, this->icon);
|
||||
@@ -3516,7 +3520,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
uint32_t ListEntitiesUpdateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3530,7 +3534,7 @@ uint32_t ListEntitiesUpdateResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
void UpdateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.write_tag_and_fixed32(13, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_bool(3, this->in_progress);
|
||||
buffer.encode_bool(4, this->has_progress);
|
||||
@@ -3546,7 +3550,7 @@ void UpdateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
}
|
||||
uint32_t UpdateStateResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
size += ProtoSize::calc_bool(1, this->in_progress);
|
||||
size += ProtoSize::calc_bool(1, this->has_progress);
|
||||
@@ -3642,7 +3646,7 @@ uint32_t ZWaveProxyRequest::calculate_size() const {
|
||||
#ifdef USE_INFRARED
|
||||
void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(4, this->icon);
|
||||
@@ -3653,11 +3657,12 @@ void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(7, this->device_id);
|
||||
#endif
|
||||
buffer.encode_uint32(8, this->capabilities);
|
||||
buffer.encode_uint32(9, this->receiver_frequency);
|
||||
}
|
||||
uint32_t ListEntitiesInfraredResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
@@ -3668,6 +3673,7 @@ uint32_t ListEntitiesInfraredResponse::calculate_size() const {
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
#endif
|
||||
size += ProtoSize::calc_uint32(1, this->capabilities);
|
||||
size += ProtoSize::calc_uint32(1, this->receiver_frequency);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
@@ -3717,7 +3723,7 @@ void InfraredRFReceiveEvent::encode(ProtoWriteBuffer &buffer) const {
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(1, this->device_id);
|
||||
#endif
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.write_tag_and_fixed32(21, this->key);
|
||||
for (const auto &it : *this->timings) {
|
||||
buffer.encode_sint32(3, it, true);
|
||||
}
|
||||
@@ -3727,7 +3733,7 @@ uint32_t InfraredRFReceiveEvent::calculate_size() const {
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
#endif
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += 5;
|
||||
if (!this->timings->empty()) {
|
||||
for (const auto &it : *this->timings) {
|
||||
size += ProtoSize::calc_sint32_force(1, it);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See script/api_protobuf/api_protobuf.py
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::api {
|
||||
@@ -8,8 +9,8 @@ namespace esphome::api {
|
||||
static const char *const TAG = "api.service";
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void APIServerConnectionBase::log_send_message_(const char *name, const char *dump) {
|
||||
ESP_LOGVV(TAG, "send_message %s: %s", name, dump);
|
||||
void APIServerConnectionBase::log_send_message_(const LogString *name, const char *dump) {
|
||||
ESP_LOGVV(TAG, "send_message %s: %s", LOG_STR_ARG(name), dump);
|
||||
}
|
||||
void APIServerConnectionBase::log_receive_message_(const LogString *name, const ProtoMessage &msg) {
|
||||
DumpBuffer dump_buf;
|
||||
@@ -20,7 +21,7 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
// Check authentication/connection requirements
|
||||
switch (msg_type) {
|
||||
case HelloRequest::MESSAGE_TYPE: // No setup required
|
||||
|
||||
@@ -8,238 +8,234 @@
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
class APIServerConnectionBase : public ProtoService {
|
||||
class APIServerConnectionBase {
|
||||
public:
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
protected:
|
||||
void log_send_message_(const char *name, const char *dump);
|
||||
void log_send_message_(const LogString *name, const char *dump);
|
||||
void log_receive_message_(const LogString *name, const ProtoMessage &msg);
|
||||
void log_receive_message_(const LogString *name);
|
||||
|
||||
public:
|
||||
#endif
|
||||
|
||||
virtual void on_hello_request(const HelloRequest &value){};
|
||||
void on_hello_request(const HelloRequest &value){};
|
||||
|
||||
virtual void on_disconnect_request(){};
|
||||
virtual void on_disconnect_response(){};
|
||||
virtual void on_ping_request(){};
|
||||
virtual void on_ping_response(){};
|
||||
virtual void on_device_info_request(){};
|
||||
void on_disconnect_request(){};
|
||||
void on_disconnect_response(){};
|
||||
void on_ping_request(){};
|
||||
void on_ping_response(){};
|
||||
void on_device_info_request(){};
|
||||
|
||||
virtual void on_list_entities_request(){};
|
||||
void on_list_entities_request(){};
|
||||
|
||||
virtual void on_subscribe_states_request(){};
|
||||
void on_subscribe_states_request(){};
|
||||
|
||||
#ifdef USE_COVER
|
||||
virtual void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
virtual void on_fan_command_request(const FanCommandRequest &value){};
|
||||
void on_fan_command_request(const FanCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
virtual void on_light_command_request(const LightCommandRequest &value){};
|
||||
void on_light_command_request(const LightCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
virtual void on_subscribe_homeassistant_services_request(){};
|
||||
void on_subscribe_homeassistant_services_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||
void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
virtual void on_subscribe_home_assistant_states_request(){};
|
||||
void on_subscribe_home_assistant_states_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
#endif
|
||||
|
||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||
void on_get_time_response(const GetTimeResponse &value){};
|
||||
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||
void on_camera_image_request(const CameraImageRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
virtual void on_number_command_request(const NumberCommandRequest &value){};
|
||||
void on_number_command_request(const NumberCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
virtual void on_select_command_request(const SelectCommandRequest &value){};
|
||||
void on_select_command_request(const SelectCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SIREN
|
||||
virtual void on_siren_command_request(const SirenCommandRequest &value){};
|
||||
void on_siren_command_request(const SirenCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
virtual void on_lock_command_request(const LockCommandRequest &value){};
|
||||
void on_lock_command_request(const LockCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
virtual void on_button_command_request(const ButtonCommandRequest &value){};
|
||||
void on_button_command_request(const ButtonCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_subscribe_bluetooth_connections_free_request(){};
|
||||
void on_subscribe_bluetooth_connections_free_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_unsubscribe_bluetooth_le_advertisements_request(){};
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request(){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
|
||||
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){};
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
|
||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
|
||||
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
|
||||
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
|
||||
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
virtual void on_text_command_request(const TextCommandRequest &value){};
|
||||
void on_text_command_request(const TextCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
virtual void on_date_command_request(const DateCommandRequest &value){};
|
||||
void on_date_command_request(const DateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
virtual void on_time_command_request(const TimeCommandRequest &value){};
|
||||
void on_time_command_request(const TimeCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_VALVE
|
||||
virtual void on_valve_command_request(const ValveCommandRequest &value){};
|
||||
void on_valve_command_request(const ValveCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
virtual void on_update_command_request(const UpdateCommandRequest &value){};
|
||||
void on_update_command_request(const UpdateCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
|
||||
void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){};
|
||||
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){};
|
||||
void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){};
|
||||
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){};
|
||||
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
virtual void on_serial_proxy_request(const SerialProxyRequest &value){};
|
||||
void on_serial_proxy_request(const SerialProxyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
|
||||
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -46,10 +46,8 @@ void APIServer::setup() {
|
||||
|
||||
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||
// Only load saved PSK if not set from YAML
|
||||
SavedNoisePsk noise_pref_saved{};
|
||||
if (this->noise_pref_.load(&noise_pref_saved)) {
|
||||
if (this->load_and_apply_noise_psk_()) {
|
||||
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
||||
this->set_noise_psk(noise_pref_saved.psk);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -110,7 +108,7 @@ void APIServer::setup() {
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
// Set warning status if reboot timeout is enabled
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
this->status_set_warning();
|
||||
this->status_set_warning(LOG_STR("waiting for client connection"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +187,7 @@ void APIServer::remove_client_(size_t client_index) {
|
||||
|
||||
// Last client disconnected - set warning and start tracking for reboot timeout
|
||||
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||
this->status_set_warning();
|
||||
this->status_set_warning(LOG_STR("waiting for client connection"));
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
|
||||
@@ -514,7 +512,7 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg,
|
||||
const LogString *fail_log_msg, const psk_t &active_psk, bool make_active) {
|
||||
const LogString *fail_log_msg, bool make_active) {
|
||||
if (!this->noise_pref_.save(&new_psk)) {
|
||||
ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg));
|
||||
return false;
|
||||
@@ -526,9 +524,14 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString
|
||||
}
|
||||
ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg));
|
||||
if (make_active) {
|
||||
this->set_timeout(100, [this, active_psk]() {
|
||||
this->set_timeout(100, [this]() {
|
||||
// Re-read the PSK from preferences rather than capturing the 32-byte array
|
||||
// in the lambda (which would exceed std::function SBO and heap-allocate).
|
||||
if (!this->load_and_apply_noise_psk_()) {
|
||||
ESP_LOGW(TAG, "Failed to load saved PSK for activation");
|
||||
return;
|
||||
}
|
||||
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
||||
this->set_noise_psk(active_psk);
|
||||
for (auto &c : this->clients_) {
|
||||
DisconnectRequest req;
|
||||
c->send_message(req);
|
||||
@@ -538,6 +541,14 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString
|
||||
return true;
|
||||
}
|
||||
|
||||
bool APIServer::load_and_apply_noise_psk_() {
|
||||
SavedNoisePsk saved{};
|
||||
if (!this->noise_pref_.load(&saved))
|
||||
return false;
|
||||
this->set_noise_psk(saved.psk);
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -552,7 +563,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||
}
|
||||
|
||||
SavedNoisePsk new_saved_psk{psk};
|
||||
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk,
|
||||
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"),
|
||||
make_active);
|
||||
#endif
|
||||
}
|
||||
@@ -564,8 +575,7 @@ bool APIServer::clear_noise_psk(bool make_active) {
|
||||
return false;
|
||||
#else
|
||||
SavedNoisePsk empty_psk{};
|
||||
psk_t empty{};
|
||||
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty,
|
||||
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"),
|
||||
make_active);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -36,11 +36,11 @@ struct SavedNoisePsk {
|
||||
} PACKED; // NOLINT
|
||||
#endif
|
||||
|
||||
class APIServer : public Component,
|
||||
public Controller
|
||||
class APIServer final : public Component,
|
||||
public Controller
|
||||
#ifdef USE_CAMERA
|
||||
,
|
||||
public camera::CameraListener
|
||||
public camera::CameraListener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
@@ -239,7 +239,9 @@ class APIServer : public Component,
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
||||
const psk_t &active_psk, bool make_active);
|
||||
bool make_active);
|
||||
// Load saved PSK from preferences and apply it. Returns true on success.
|
||||
bool load_and_apply_noise_psk_();
|
||||
#endif // USE_API_NOISE
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
// Helper methods to reduce code duplication
|
||||
|
||||
@@ -32,7 +32,11 @@ if TYPE_CHECKING:
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
async def async_run_logs(
|
||||
config: dict[str, Any],
|
||||
addresses: list[str],
|
||||
subscribe_states: bool = True,
|
||||
) -> None:
|
||||
"""Run the logs command in the event loop."""
|
||||
conf = config["api"]
|
||||
name = config["esphome"]["name"]
|
||||
@@ -89,14 +93,20 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
config, raw_line, backtrace_state=backtrace_state
|
||||
)
|
||||
|
||||
stop = await async_run(cli, on_log, name=name)
|
||||
stop = await async_run(cli, on_log, name=name, subscribe_states=subscribe_states)
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
finally:
|
||||
await stop()
|
||||
|
||||
|
||||
def run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
def run_logs(
|
||||
config: dict[str, Any],
|
||||
addresses: list[str],
|
||||
subscribe_states: bool = True,
|
||||
) -> None:
|
||||
"""Run the logs command."""
|
||||
with contextlib.suppress(KeyboardInterrupt):
|
||||
asyncio.run(async_run_logs(config, addresses))
|
||||
asyncio.run(
|
||||
async_run_logs(config, addresses, subscribe_states=subscribe_states)
|
||||
)
|
||||
|
||||
@@ -136,8 +136,9 @@ class CustomAPIDevice {
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
|
||||
auto *obj = static_cast<T *>(this);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
|
||||
[obj, callback](StringRef state) { (obj->*callback)(state); });
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
|
||||
@@ -148,10 +149,12 @@ class CustomAPIDevice {
|
||||
ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
auto *obj = static_cast<T *>(this);
|
||||
// Explicit type to disambiguate overload resolution
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(f));
|
||||
global_api_server->subscribe_home_assistant_state(
|
||||
entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(
|
||||
[obj, callback](const std::string &state) { (obj->*callback)(state); }));
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||
@@ -176,8 +179,10 @@ class CustomAPIDevice {
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), std::move(f));
|
||||
auto *obj = static_cast<T *>(this);
|
||||
global_api_server->subscribe_home_assistant_state(
|
||||
entity_id, optional<std::string>(attribute),
|
||||
[obj, callback, entity_id](StringRef state) { (obj->*callback)(entity_id, state); });
|
||||
}
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version).
|
||||
@@ -188,10 +193,12 @@ class CustomAPIDevice {
|
||||
ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0")
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
auto *obj = static_cast<T *>(this);
|
||||
// Explicit type to disambiguate overload resolution
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(f));
|
||||
global_api_server->subscribe_home_assistant_state(
|
||||
entity_id, optional<std::string>(attribute),
|
||||
std::function<void(const std::string &)>(
|
||||
[obj, callback, entity_id](const std::string &state) { (obj->*callback)(entity_id, state); }));
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
|
||||
@@ -257,7 +257,13 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
||||
uint32_t val;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
// Protobuf fixed32 is little-endian — direct load on LE platforms
|
||||
memcpy(&val, ptr, 4);
|
||||
#else
|
||||
val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
||||
#endif
|
||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
#include <cassert>
|
||||
@@ -152,8 +153,7 @@ class ProtoVarInt {
|
||||
#endif
|
||||
};
|
||||
|
||||
// Forward declarations for decode_to_message and related encoding helpers
|
||||
class ProtoDecodableMessage;
|
||||
// Forward declarations for encoding helpers
|
||||
class ProtoMessage;
|
||||
class ProtoSize;
|
||||
|
||||
@@ -166,16 +166,9 @@ class ProtoLengthDelimited {
|
||||
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.
|
||||
*
|
||||
* This method allows decoding without templates, enabling use in contexts
|
||||
* where the message type is not known at compile time. The ProtoDecodableMessage's
|
||||
* decode() method will be called with the raw data and length.
|
||||
*
|
||||
* @param msg The ProtoDecodableMessage instance to decode into
|
||||
*/
|
||||
void decode_to_message(ProtoDecodableMessage &msg) const;
|
||||
/// Decode the length-delimited data into a message instance.
|
||||
/// Template preserves concrete type so decode() resolves statically.
|
||||
template<typename T> void decode_to_message(T &msg) const;
|
||||
|
||||
protected:
|
||||
const uint8_t *const value_;
|
||||
@@ -236,6 +229,32 @@ class ProtoWriteBuffer {
|
||||
* Following https://protobuf.dev/programming-guides/encoding/#structure
|
||||
*/
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
|
||||
/// Write a single precomputed tag byte. Tag must be < 128.
|
||||
inline void write_raw_byte(uint8_t b) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = b;
|
||||
}
|
||||
/// Write raw bytes to the buffer (no tag, no length prefix).
|
||||
inline void encode_raw(const void *data, size_t len) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(len);
|
||||
std::memcpy(this->pos_, data, len);
|
||||
this->pos_ += len;
|
||||
}
|
||||
/// Write a precomputed tag byte + 32-bit value in one operation.
|
||||
/// Tag must be a single-byte varint (< 128). No zero check.
|
||||
inline void write_tag_and_fixed32(uint8_t tag, uint32_t value) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(5);
|
||||
this->pos_[0] = tag;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
std::memcpy(this->pos_ + 1, &value, 4);
|
||||
#else
|
||||
this->pos_[1] = static_cast<uint8_t>(value & 0xFF);
|
||||
this->pos_[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
|
||||
this->pos_[3] = static_cast<uint8_t>((value >> 16) & 0xFF);
|
||||
this->pos_[4] = static_cast<uint8_t>((value >> 24) & 0xFF);
|
||||
#endif
|
||||
this->pos_ += 5;
|
||||
}
|
||||
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
@@ -276,8 +295,7 @@ class ProtoWriteBuffer {
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = value ? 0x01 : 0x00;
|
||||
}
|
||||
// noinline: 51 call sites; inlining causes net code growth vs a single out-of-line copy
|
||||
__attribute__((noinline)) void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
@@ -394,6 +412,23 @@ class DumpBuffer {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Append a PROGMEM string (flash-safe on ESP8266, regular append on other platforms)
|
||||
DumpBuffer &append_p(const char *str) {
|
||||
if (str) {
|
||||
#ifdef USE_ESP8266
|
||||
append_p_esp8266(str);
|
||||
#else
|
||||
append_impl_(str, strlen(str));
|
||||
#endif
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
/// Out-of-line ESP8266 PROGMEM append to avoid inlining strlen_P/memcpy_P at every call site
|
||||
void append_p_esp8266(const char *str);
|
||||
#endif
|
||||
|
||||
const char *c_str() const { return buf_; }
|
||||
size_t size() const { return pos_; }
|
||||
|
||||
@@ -439,7 +474,7 @@ class ProtoMessage {
|
||||
uint32_t calculate_size() const { return 0; }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
virtual const char *dump_to(DumpBuffer &out) const = 0;
|
||||
virtual const char *message_name() const { return "unknown"; }
|
||||
virtual const LogString *message_name() const { return LOG_STR("unknown"); }
|
||||
#endif
|
||||
|
||||
#ifndef USE_HOST
|
||||
@@ -454,7 +489,7 @@ class ProtoMessage {
|
||||
// Base class for messages that support decoding
|
||||
class ProtoDecodableMessage : public ProtoMessage {
|
||||
public:
|
||||
virtual void decode(const uint8_t *buffer, size_t length);
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
|
||||
/**
|
||||
* Count occurrences of a repeated field in a protobuf buffer.
|
||||
@@ -477,6 +512,12 @@ class ProtoDecodableMessage : public ProtoMessage {
|
||||
|
||||
class ProtoSize {
|
||||
public:
|
||||
// Varint encoding thresholds: values below each threshold fit in N bytes
|
||||
static constexpr uint32_t VARINT_THRESHOLD_1_BYTE = 1 << 7; // 128
|
||||
static constexpr uint32_t VARINT_THRESHOLD_2_BYTE = 1 << 14; // 16384
|
||||
static constexpr uint32_t VARINT_THRESHOLD_3_BYTE = 1 << 21; // 2097152
|
||||
static constexpr uint32_t VARINT_THRESHOLD_4_BYTE = 1 << 28; // 268435456
|
||||
|
||||
/**
|
||||
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
||||
*
|
||||
@@ -484,7 +525,7 @@ class ProtoSize {
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint(uint32_t value) {
|
||||
if (value < 128) [[likely]]
|
||||
if (value < VARINT_THRESHOLD_1_BYTE) [[likely]]
|
||||
return 1; // Fast path: 7 bits, most common case
|
||||
if (__builtin_is_constant_evaluated())
|
||||
return varint_wide(value);
|
||||
@@ -496,11 +537,11 @@ class ProtoSize {
|
||||
static uint32_t varint_slow(uint32_t value) __attribute__((noinline));
|
||||
// Shared cascade for values >= 128 (used by both constexpr and noinline paths)
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint_wide(uint32_t value) {
|
||||
if (value < 16384)
|
||||
if (value < VARINT_THRESHOLD_2_BYTE)
|
||||
return 2;
|
||||
if (value < 2097152)
|
||||
if (value < VARINT_THRESHOLD_3_BYTE)
|
||||
return 3;
|
||||
if (value < 268435456)
|
||||
if (value < VARINT_THRESHOLD_4_BYTE)
|
||||
return 4;
|
||||
return 5;
|
||||
}
|
||||
@@ -606,7 +647,7 @@ class ProtoSize {
|
||||
static constexpr uint32_t calc_sint32(uint32_t field_id_size, int32_t value) {
|
||||
return value ? field_id_size + varint(encode_zigzag32(value)) : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_sint32_force(uint32_t field_id_size, int32_t value) {
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_sint32_force(uint32_t field_id_size, int32_t value) {
|
||||
return field_id_size + varint(encode_zigzag32(value));
|
||||
}
|
||||
static constexpr uint32_t calc_int64(uint32_t field_id_size, int64_t value) {
|
||||
@@ -618,13 +659,13 @@ class ProtoSize {
|
||||
static constexpr uint32_t calc_uint64(uint32_t field_id_size, uint64_t value) {
|
||||
return value ? field_id_size + varint(value) : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_uint64_force(uint32_t field_id_size, uint64_t value) {
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_uint64_force(uint32_t field_id_size, uint64_t value) {
|
||||
return field_id_size + varint(value);
|
||||
}
|
||||
static constexpr uint32_t calc_length(uint32_t field_id_size, size_t len) {
|
||||
return len ? field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len) : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_length_force(uint32_t field_id_size, size_t len) {
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_length_force(uint32_t field_id_size, size_t len) {
|
||||
return field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
|
||||
}
|
||||
static constexpr uint32_t calc_sint64(uint32_t field_id_size, int64_t value) {
|
||||
@@ -642,7 +683,8 @@ class ProtoSize {
|
||||
static constexpr uint32_t calc_message(uint32_t field_id_size, uint32_t nested_size) {
|
||||
return nested_size ? field_id_size + varint(nested_size) + nested_size : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_message_force(uint32_t field_id_size, uint32_t nested_size) {
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_message_force(uint32_t field_id_size,
|
||||
uint32_t nested_size) {
|
||||
return field_id_size + varint(nested_size) + nested_size;
|
||||
}
|
||||
};
|
||||
@@ -683,33 +725,14 @@ template<typename T> inline void ProtoWriteBuffer::encode_optional_sub_message(u
|
||||
this->encode_optional_sub_message(field_id, value.calculate_size(), &value, &proto_encode_msg<T>);
|
||||
}
|
||||
|
||||
// Implementation of decode_to_message - must be after ProtoDecodableMessage is defined
|
||||
inline void ProtoLengthDelimited::decode_to_message(ProtoDecodableMessage &msg) const {
|
||||
// Template decode_to_message - preserves concrete type so decode() resolves statically
|
||||
template<typename T> void ProtoLengthDelimited::decode_to_message(T &msg) const {
|
||||
msg.decode(this->value_, this->length_);
|
||||
}
|
||||
|
||||
template<typename T> const char *proto_enum_to_string(T value);
|
||||
|
||||
class ProtoService {
|
||||
public:
|
||||
protected:
|
||||
virtual bool is_authenticated() = 0;
|
||||
virtual bool is_connection_setup() = 0;
|
||||
virtual void on_fatal_error() = 0;
|
||||
virtual void on_no_setup_connection() = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
|
||||
virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0;
|
||||
|
||||
// Authentication helper methods
|
||||
inline bool check_connection_setup_() {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool check_authenticated_() { return this->check_connection_setup_(); }
|
||||
};
|
||||
// ProtoService removed — its methods were inlined into APIConnection.
|
||||
// APIConnection is the concrete server-side implementation; the extra virtual layer was unnecessary.
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -2,11 +2,9 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ANGLE,
|
||||
CONF_GAIN,
|
||||
CONF_ID,
|
||||
CONF_MAGNITUDE,
|
||||
CONF_POSITION,
|
||||
CONF_STATUS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_MAGNET,
|
||||
@@ -21,7 +19,6 @@ DEPENDENCIES = ["as5600"]
|
||||
|
||||
AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent)
|
||||
|
||||
CONF_RAW_ANGLE = "raw_angle"
|
||||
CONF_RAW_POSITION = "raw_position"
|
||||
CONF_SLOW_FILTER = "slow_filter"
|
||||
CONF_FAST_FILTER = "fast_filter"
|
||||
@@ -89,18 +86,6 @@ async def to_code(config):
|
||||
if out_of_range_mode_config := config.get(CONF_OUT_OF_RANGE_MODE):
|
||||
cg.add(var.set_out_of_range_mode(out_of_range_mode_config))
|
||||
|
||||
if angle_config := config.get(CONF_ANGLE):
|
||||
sens = await sensor.new_sensor(angle_config)
|
||||
cg.add(var.set_angle_sensor(sens))
|
||||
|
||||
if raw_angle_config := config.get(CONF_RAW_ANGLE):
|
||||
sens = await sensor.new_sensor(raw_angle_config)
|
||||
cg.add(var.set_raw_angle_sensor(sens))
|
||||
|
||||
if position_config := config.get(CONF_POSITION):
|
||||
sens = await sensor.new_sensor(position_config)
|
||||
cg.add(var.set_position_sensor(sens))
|
||||
|
||||
if raw_position_config := config.get(CONF_RAW_POSITION):
|
||||
sens = await sensor.new_sensor(raw_position_config)
|
||||
cg.add(var.set_raw_position_sensor(sens))
|
||||
|
||||
@@ -25,27 +25,10 @@ static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
|
||||
void AS5600Sensor::dump_config() {
|
||||
LOG_SENSOR("", "AS5600 Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Out of Range Mode: %u", this->out_of_range_mode_);
|
||||
if (this->angle_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Angle Sensor", this->angle_sensor_);
|
||||
}
|
||||
if (this->raw_angle_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Raw Angle Sensor", this->raw_angle_sensor_);
|
||||
}
|
||||
if (this->position_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Position Sensor", this->position_sensor_);
|
||||
}
|
||||
if (this->raw_position_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_);
|
||||
}
|
||||
if (this->gain_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_);
|
||||
}
|
||||
if (this->magnitude_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_);
|
||||
}
|
||||
if (this->status_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Status Sensor", this->status_sensor_);
|
||||
}
|
||||
LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_);
|
||||
LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_);
|
||||
LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_);
|
||||
LOG_SENSOR(" ", "Status Sensor", this->status_sensor_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@ class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>,
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_angle_sensor(sensor::Sensor *angle_sensor) { this->angle_sensor_ = angle_sensor; }
|
||||
void set_raw_angle_sensor(sensor::Sensor *raw_angle_sensor) { this->raw_angle_sensor_ = raw_angle_sensor; }
|
||||
void set_position_sensor(sensor::Sensor *position_sensor) { this->position_sensor_ = position_sensor; }
|
||||
void set_raw_position_sensor(sensor::Sensor *raw_position_sensor) {
|
||||
this->raw_position_sensor_ = raw_position_sensor;
|
||||
}
|
||||
@@ -28,9 +25,6 @@ class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>,
|
||||
OutRangeMode get_out_of_range_mode() { return this->out_of_range_mode_; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *angle_sensor_{nullptr};
|
||||
sensor::Sensor *raw_angle_sensor_{nullptr};
|
||||
sensor::Sensor *position_sensor_{nullptr};
|
||||
sensor::Sensor *raw_position_sensor_{nullptr};
|
||||
sensor::Sensor *gain_sensor_{nullptr};
|
||||
sensor::Sensor *magnitude_sensor_{nullptr};
|
||||
|
||||
@@ -52,11 +52,12 @@ bool AsyncClient::connect(const char *host, uint16_t port) {
|
||||
connect_cb_(connect_arg_, this);
|
||||
return true;
|
||||
}
|
||||
if (errno != EINPROGRESS) {
|
||||
ESP_LOGE(TAG, "Connect failed: %d", errno);
|
||||
const int saved_errno = errno;
|
||||
if (saved_errno != EINPROGRESS) {
|
||||
ESP_LOGE(TAG, "Connect failed: %d", saved_errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
error_cb_(error_arg_, this, saved_errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -79,11 +80,12 @@ size_t AsyncClient::write(const char *data, size_t len) {
|
||||
|
||||
ssize_t sent = socket_->write(data, len);
|
||||
if (sent < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
ESP_LOGE(TAG, "Write error: %d", errno);
|
||||
const int err = errno;
|
||||
if (err != EAGAIN && err != EWOULDBLOCK) {
|
||||
ESP_LOGE(TAG, "Write error: %d", err);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
error_cb_(error_arg_, this, err);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -129,10 +131,11 @@ void AsyncClient::loop() {
|
||||
error_cb_(error_arg_, this, error);
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Select error: %d", errno);
|
||||
const int err = errno;
|
||||
ESP_LOGE(TAG, "Select error: %d", err);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
error_cb_(error_arg_, this, err);
|
||||
}
|
||||
} else if (connected_) {
|
||||
// For connected sockets, use the Application's select() results
|
||||
@@ -148,11 +151,14 @@ void AsyncClient::loop() {
|
||||
} else if (len > 0) {
|
||||
if (data_cb_)
|
||||
data_cb_(data_arg_, this, buf, len);
|
||||
} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
ESP_LOGW(TAG, "Read error: %d", errno);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, errno);
|
||||
} else {
|
||||
const int err = errno;
|
||||
if (err != EAGAIN && err != EWOULDBLOCK) {
|
||||
ESP_LOGW(TAG, "Read error: %d", err);
|
||||
close();
|
||||
if (error_cb_)
|
||||
error_cb_(error_arg_, this, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,8 +550,8 @@ float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_angle_(uint8_t phase) {
|
||||
uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
|
||||
return (val > 180) ? (float) (val - 360.0f) : (float) val;
|
||||
float val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0f;
|
||||
return (val > 180.0f) ? val - 360.0f : val;
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
|
||||
|
||||
@@ -134,7 +134,6 @@ class ATM90E32Component : public PollingComponent,
|
||||
void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; }
|
||||
#endif
|
||||
uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier);
|
||||
int32_t last_periodic_millis = millis();
|
||||
|
||||
protected:
|
||||
#ifdef USE_NUMBER
|
||||
|
||||
@@ -204,7 +204,7 @@ async def to_code(config):
|
||||
|
||||
add_idf_component(
|
||||
name="esphome/esp-audio-libs",
|
||||
ref="2.0.3",
|
||||
ref="2.0.4",
|
||||
)
|
||||
|
||||
data = _get_data()
|
||||
@@ -214,4 +214,4 @@ async def to_code(config):
|
||||
cg.add_define("USE_AUDIO_MP3_SUPPORT")
|
||||
if data.opus_support:
|
||||
cg.add_define("USE_AUDIO_OPUS_SUPPORT")
|
||||
add_idf_component(name="esphome/micro-opus", ref="0.3.5")
|
||||
add_idf_component(name="esphome/micro-opus", ref="0.3.6")
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "esphome/components/audio/audio_decoder.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome::audio_file {
|
||||
@@ -249,7 +250,7 @@ void AudioFileMediaSource::decode_task(void *params) {
|
||||
|
||||
audio::AudioStreamInfo stream_info = decoder->get_audio_stream_info().value();
|
||||
|
||||
ESP_LOGD(TAG, "Bits per sample: %d, Channels: %d, Sample rate: %d", stream_info.get_bits_per_sample(),
|
||||
ESP_LOGD(TAG, "Bits per sample: %d, Channels: %d, Sample rate: %" PRIu32, stream_info.get_bits_per_sample(),
|
||||
stream_info.get_channels(), stream_info.get_sample_rate());
|
||||
|
||||
if (stream_info.get_bits_per_sample() != 16 || stream_info.get_channels() > 2) {
|
||||
|
||||
@@ -183,7 +183,7 @@ class BedjetCodec {
|
||||
|
||||
BedjetPacket packet_;
|
||||
|
||||
BedjetStatusPacket *status_packet_;
|
||||
BedjetStatusPacket *status_packet_{nullptr};
|
||||
BedjetStatusPacket buf_;
|
||||
};
|
||||
|
||||
|
||||
@@ -120,25 +120,15 @@ BinarySensorInitiallyOff = binary_sensor_ns.class_(
|
||||
BinarySensorPtr = BinarySensor.operator("ptr")
|
||||
|
||||
# Triggers
|
||||
PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template())
|
||||
ReleaseTrigger = binary_sensor_ns.class_(
|
||||
"ReleaseTrigger", automation.Trigger.template()
|
||||
)
|
||||
ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template())
|
||||
DoubleClickTrigger = binary_sensor_ns.class_(
|
||||
"DoubleClickTrigger", automation.Trigger.template()
|
||||
)
|
||||
MultiClickTrigger = binary_sensor_ns.class_(
|
||||
"MultiClickTrigger", automation.Trigger.template(), cg.Component
|
||||
MultiClickTriggerBase = binary_sensor_ns.class_(
|
||||
"MultiClickTriggerBase", automation.Trigger.template(), cg.Component
|
||||
)
|
||||
MultiClickTrigger = binary_sensor_ns.class_("MultiClickTrigger", MultiClickTriggerBase)
|
||||
MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
|
||||
StateTrigger = binary_sensor_ns.class_(
|
||||
"StateTrigger", automation.Trigger.template(bool)
|
||||
)
|
||||
StateChangeTrigger = binary_sensor_ns.class_(
|
||||
"StateChangeTrigger",
|
||||
automation.Trigger.template(cg.optional.template(bool), cg.optional.template(bool)),
|
||||
)
|
||||
|
||||
BinarySensorPublishAction = binary_sensor_ns.class_(
|
||||
"BinarySensorPublishAction", automation.Action
|
||||
@@ -266,6 +256,7 @@ async def delayed_off_filter_to_code(config, filter_id):
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
cv.Length(max=254),
|
||||
),
|
||||
)
|
||||
async def autorepeat_filter_to_code(config, filter_id):
|
||||
@@ -294,7 +285,7 @@ async def autorepeat_filter_to_code(config, filter_id):
|
||||
),
|
||||
)
|
||||
]
|
||||
var = cg.new_Pvariable(filter_id, timings)
|
||||
var = cg.new_Pvariable(filter_id, cg.TemplateArguments(len(timings)), timings)
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
@@ -458,16 +449,8 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
{
|
||||
@@ -502,23 +485,17 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
|
||||
cv.Required(CONF_TIMING): cv.All(
|
||||
[parse_multi_click_timing_str], validate_multi_click_timing
|
||||
[parse_multi_click_timing_str],
|
||||
validate_multi_click_timing,
|
||||
cv.Length(min=1, max=255),
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_INVALID_COOLDOWN, default="1s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateChangeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -556,13 +533,14 @@ def binary_sensor_schema(
|
||||
|
||||
@coroutine_with_priority(CoroPriority.AUTOMATION)
|
||||
async def _build_binary_sensor_automations(var, config):
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_RELEASE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf_key, forwarder in (
|
||||
(CONF_ON_PRESS, automation.TriggerOnTrueForwarder),
|
||||
(CONF_ON_RELEASE, automation.TriggerOnFalseForwarder),
|
||||
):
|
||||
for conf in config.get(conf_key, []):
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [], conf, forwarder=forwarder
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_CLICK, []):
|
||||
trigger = cg.new_Pvariable(
|
||||
@@ -586,20 +564,23 @@ async def _build_binary_sensor_automations(var, config):
|
||||
)
|
||||
for tim in conf[CONF_TIMING]
|
||||
]
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], cg.TemplateArguments(len(timings)), var, timings
|
||||
)
|
||||
if CONF_INVALID_COOLDOWN in conf:
|
||||
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
||||
await cg.register_component(trigger, conf)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_state_callback", [(bool, "x")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE_CHANGE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_full_state_callback",
|
||||
[
|
||||
(cg.optional.template(bool), "x_previous"),
|
||||
(cg.optional.template(bool), "x"),
|
||||
|
||||
@@ -13,7 +13,7 @@ constexpr uint32_t MULTICLICK_COOLDOWN_ID = 1;
|
||||
constexpr uint32_t MULTICLICK_IS_VALID_ID = 2;
|
||||
constexpr uint32_t MULTICLICK_IS_NOT_VALID_ID = 3;
|
||||
|
||||
void MultiClickTrigger::on_state_(bool state) {
|
||||
void MultiClickTriggerBase::on_state_(bool state) {
|
||||
// Handle duplicate events
|
||||
if (state == this->last_state_) {
|
||||
return;
|
||||
@@ -32,7 +32,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
ESP_LOGV(TAG, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length);
|
||||
ESP_LOGV(TAG, "Multi Click: Starting multi click action!");
|
||||
this->at_index_ = 1;
|
||||
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
|
||||
if (this->timing_count_ == 1 && evt.max_length == 4294967294UL) {
|
||||
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
|
||||
} else {
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
@@ -50,7 +50,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (*this->at_index_ == this->timing_.size()) {
|
||||
if (*this->at_index_ == this->timing_count_) {
|
||||
this->trigger_();
|
||||
return;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
this->schedule_is_not_valid_(evt.max_length);
|
||||
} else if (*this->at_index_ + 1 != this->timing_.size()) {
|
||||
} else if (*this->at_index_ + 1 != this->timing_count_) {
|
||||
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
@@ -74,7 +74,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
|
||||
*this->at_index_ = *this->at_index_ + 1;
|
||||
}
|
||||
void MultiClickTrigger::schedule_cooldown_() {
|
||||
void MultiClickTriggerBase::schedule_cooldown_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
|
||||
this->is_in_cooldown_ = true;
|
||||
this->set_timeout(MULTICLICK_COOLDOWN_ID, this->invalid_cooldown_, [this]() {
|
||||
@@ -86,7 +86,7 @@ void MultiClickTrigger::schedule_cooldown_() {
|
||||
this->cancel_timeout(MULTICLICK_IS_VALID_ID);
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
}
|
||||
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
void MultiClickTriggerBase::schedule_is_valid_(uint32_t min_length) {
|
||||
if (min_length == 0) {
|
||||
this->is_valid_ = true;
|
||||
return;
|
||||
@@ -97,19 +97,19 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
this->is_valid_ = true;
|
||||
});
|
||||
}
|
||||
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||
void MultiClickTriggerBase::schedule_is_not_valid_(uint32_t max_length) {
|
||||
this->set_timeout(MULTICLICK_IS_NOT_VALID_ID, max_length, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
});
|
||||
}
|
||||
void MultiClickTrigger::cancel() {
|
||||
void MultiClickTriggerBase::cancel() {
|
||||
ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
}
|
||||
void MultiClickTrigger::trigger_() {
|
||||
void MultiClickTriggerBase::trigger_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
|
||||
this->at_index_.reset();
|
||||
this->cancel_timeout(MULTICLICK_TRIGGER_ID);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
|
||||
@@ -89,15 +90,14 @@ class DoubleClickTrigger : public Trigger<> {
|
||||
uint32_t max_length_; /// Maximum length of click. 0 means no maximum.
|
||||
};
|
||||
|
||||
class MultiClickTrigger : public Trigger<>, public Component {
|
||||
/// Non-template base for MultiClickTrigger (keeps large method bodies out of the header).
|
||||
class MultiClickTriggerBase : public Trigger<>, public Component {
|
||||
public:
|
||||
explicit MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
|
||||
: parent_(parent), timing_(timing) {}
|
||||
explicit MultiClickTriggerBase(BinarySensor *parent) : parent_(parent) {}
|
||||
|
||||
void setup() override {
|
||||
this->last_state_ = this->parent_->get_state_default(false);
|
||||
auto f = std::bind(&MultiClickTrigger::on_state_, this, std::placeholders::_1);
|
||||
this->parent_->add_on_state_callback(f);
|
||||
this->parent_->add_on_state_callback([this](bool state) { this->on_state_(state); });
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
@@ -105,6 +105,8 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
||||
void set_invalid_cooldown(uint32_t invalid_cooldown) { this->invalid_cooldown_ = invalid_cooldown; }
|
||||
|
||||
void cancel();
|
||||
MultiClickTriggerBase(const MultiClickTriggerBase &) = delete;
|
||||
MultiClickTriggerBase &operator=(const MultiClickTriggerBase &) = delete;
|
||||
|
||||
protected:
|
||||
void on_state_(bool state);
|
||||
@@ -114,14 +116,30 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
||||
void trigger_();
|
||||
|
||||
BinarySensor *parent_;
|
||||
FixedVector<MultiClickTriggerEvent> timing_;
|
||||
const MultiClickTriggerEvent *timing_{nullptr};
|
||||
uint32_t invalid_cooldown_{1000};
|
||||
optional<size_t> at_index_{};
|
||||
uint8_t timing_count_{0};
|
||||
bool last_state_{false};
|
||||
bool is_in_cooldown_{false};
|
||||
bool is_valid_{false};
|
||||
};
|
||||
|
||||
/// Template wrapper that provides inline std::array storage for timing events.
|
||||
/// N is set by code generation to match the exact number of timing events configured in YAML.
|
||||
template<size_t N> class MultiClickTrigger : public MultiClickTriggerBase {
|
||||
public:
|
||||
MultiClickTrigger(BinarySensor *parent, std::initializer_list<MultiClickTriggerEvent> timing)
|
||||
: MultiClickTriggerBase(parent) {
|
||||
init_array_from(this->timing_storage_, timing);
|
||||
this->timing_ = this->timing_storage_.data();
|
||||
this->timing_count_ = N;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::array<MultiClickTriggerEvent, N> timing_storage_{};
|
||||
};
|
||||
|
||||
class StateTrigger : public Trigger<bool> {
|
||||
public:
|
||||
explicit StateTrigger(BinarySensor *parent) {
|
||||
|
||||
@@ -32,20 +32,13 @@ void BinarySensor::publish_initial_state(bool new_state) {
|
||||
this->invalidate_state();
|
||||
this->publish_state(new_state);
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool new_state) {
|
||||
// copy the new state to the visible property for backwards compatibility, before any callbacks
|
||||
this->state = new_state;
|
||||
// Note that set_new_state_ de-dups and will only trigger callbacks if the state has actually changed
|
||||
this->set_new_state(new_state);
|
||||
}
|
||||
|
||||
bool BinarySensor::set_new_state(const optional<bool> &new_state) {
|
||||
if (StatefulEntityBase::set_new_state(new_state)) {
|
||||
// weirdly, this file could be compiled even without USE_BINARY_SENSOR defined
|
||||
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_binary_sensor_update(this);
|
||||
#endif
|
||||
ESP_LOGD(TAG, "'%s' >> %s", this->get_name().c_str(), ONOFFMAYBE(new_state));
|
||||
ESP_LOGV(TAG, "'%s' >> %s", this->get_name().c_str(), ONOFFMAYBE(new_state));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -32,7 +32,10 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi
|
||||
*/
|
||||
class BinarySensor : public StatefulEntityBase<bool> {
|
||||
public:
|
||||
explicit BinarySensor(){};
|
||||
explicit BinarySensor() = default;
|
||||
|
||||
const bool &get_state() const override { return this->state; }
|
||||
void set_trigger_on_initial_state(bool value) { this->trigger_on_initial_state_ = value; }
|
||||
|
||||
/** Publish a new state to the front-end.
|
||||
*
|
||||
@@ -54,16 +57,24 @@ class BinarySensor : public StatefulEntityBase<bool> {
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool new_state);
|
||||
void send_state_internal(bool new_state) {
|
||||
// Fast path: skip virtual dispatch when state hasn't changed
|
||||
if (this->flags_.has_state && this->state == new_state)
|
||||
return;
|
||||
this->set_new_state(new_state);
|
||||
}
|
||||
|
||||
/// Return whether this binary sensor has outputted a state.
|
||||
virtual bool is_status_binary_sensor() const;
|
||||
|
||||
// For backward compatibility, provide an accessible property
|
||||
|
||||
/// The current state of this binary sensor. Also used as the backing storage for StatefulEntityBase.
|
||||
bool state{};
|
||||
|
||||
protected:
|
||||
bool get_trigger_on_initial_state() const override { return this->trigger_on_initial_state_; }
|
||||
void set_state_value(const bool &value) override { this->state = value; }
|
||||
|
||||
bool trigger_on_initial_state_{true};
|
||||
#ifdef USE_BINARY_SENSOR_FILTER
|
||||
Filter *filter_list_{nullptr};
|
||||
#endif
|
||||
@@ -73,7 +84,7 @@ class BinarySensor : public StatefulEntityBase<bool> {
|
||||
|
||||
class BinarySensorInitiallyOff : public BinarySensor {
|
||||
public:
|
||||
bool has_state() const override { return true; }
|
||||
BinarySensorInitiallyOff() { this->set_has_state(true); }
|
||||
};
|
||||
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -76,14 +76,11 @@ float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARD
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
||||
|
||||
AutorepeatFilter::AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings) : timings_(timings) {}
|
||||
|
||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
// AutorepeatFilterBase
|
||||
optional<bool> AutorepeatFilterBase::new_value(bool value) {
|
||||
if (value) {
|
||||
// Ignore if already running
|
||||
if (this->active_timing_ != 0)
|
||||
return {};
|
||||
|
||||
this->next_timing_();
|
||||
return true;
|
||||
} else {
|
||||
@@ -94,34 +91,26 @@ optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
}
|
||||
}
|
||||
|
||||
void AutorepeatFilter::next_timing_() {
|
||||
// Entering this method
|
||||
// 1st time: starts waiting the first delay
|
||||
// 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on
|
||||
// last time: no delay to start but have to bump the index to reflect the last
|
||||
if (this->active_timing_ < this->timings_.size()) {
|
||||
void AutorepeatFilterBase::next_timing_() {
|
||||
if (this->active_timing_ < this->timings_count_) {
|
||||
this->set_timeout(AUTOREPEAT_TIMING_ID, this->timings_[this->active_timing_].delay,
|
||||
[this]() { this->next_timing_(); });
|
||||
}
|
||||
|
||||
if (this->active_timing_ <= this->timings_.size()) {
|
||||
if (this->active_timing_ <= this->timings_count_) {
|
||||
this->active_timing_++;
|
||||
}
|
||||
|
||||
if (this->active_timing_ == 2)
|
||||
this->next_value_(false);
|
||||
|
||||
// Leaving this method: if the toggling is started, it has to use [active_timing_ - 2] for the intervals
|
||||
}
|
||||
|
||||
void AutorepeatFilter::next_value_(bool val) {
|
||||
void AutorepeatFilterBase::next_value_(bool val) {
|
||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||
this->output(val); // This is at least the second one so not initial
|
||||
this->output(val);
|
||||
this->set_timeout(AUTOREPEAT_ON_OFF_ID, val ? timing.time_on : timing.time_off,
|
||||
[this, val]() { this->next_value_(!val); });
|
||||
}
|
||||
|
||||
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
float AutorepeatFilterBase::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_BINARY_SENSOR_FILTER
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -37,7 +39,7 @@ class TimeoutFilter : public Filter, public Component {
|
||||
TemplatableValue<uint32_t> timeout_delay_{};
|
||||
};
|
||||
|
||||
class DelayedOnOffFilter : public Filter, public Component {
|
||||
class DelayedOnOffFilter final : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
@@ -86,22 +88,39 @@ struct AutorepeatFilterTiming {
|
||||
uint32_t time_on;
|
||||
};
|
||||
|
||||
class AutorepeatFilter : public Filter, public Component {
|
||||
/// Non-template base for AutorepeatFilter — all methods in filter.cpp.
|
||||
/// Lambdas capture this base pointer, so set_timeout/cancel_timeout are instantiated once.
|
||||
class AutorepeatFilterBase : public Filter, public Component {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings);
|
||||
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
AutorepeatFilterBase(const AutorepeatFilterBase &) = delete;
|
||||
AutorepeatFilterBase &operator=(const AutorepeatFilterBase &) = delete;
|
||||
|
||||
protected:
|
||||
AutorepeatFilterBase() = default;
|
||||
void next_timing_();
|
||||
void next_value_(bool val);
|
||||
|
||||
FixedVector<AutorepeatFilterTiming> timings_;
|
||||
const AutorepeatFilterTiming *timings_{nullptr};
|
||||
uint8_t timings_count_{0};
|
||||
uint8_t active_timing_{0};
|
||||
};
|
||||
|
||||
/// Template wrapper that provides inline std::array storage for timings.
|
||||
/// N is set by code generation to match the exact number of timings configured in YAML.
|
||||
template<size_t N> class AutorepeatFilter : public AutorepeatFilterBase {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::initializer_list<AutorepeatFilterTiming> timings) {
|
||||
init_array_from(this->timings_storage_, timings);
|
||||
this->timings_ = this->timings_storage_.data();
|
||||
this->timings_count_ = N;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::array<AutorepeatFilterTiming, N> timings_storage_{};
|
||||
};
|
||||
|
||||
class LambdaFilter : public Filter {
|
||||
public:
|
||||
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
|
||||
|
||||
@@ -124,7 +124,7 @@ def set_reference_values(config):
|
||||
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)
|
||||
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_EREF)
|
||||
else:
|
||||
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
|
||||
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
|
||||
|
||||
@@ -47,6 +47,8 @@ void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
|
||||
switch (event) {
|
||||
// server response on RSSI request:
|
||||
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||
if (!this->parent()->check_addr(param->read_rssi_cmpl.remote_addr))
|
||||
return;
|
||||
if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) {
|
||||
int8_t rssi = param->read_rssi_cmpl.rssi;
|
||||
ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi);
|
||||
|
||||
@@ -102,6 +102,10 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.value_len == 0) {
|
||||
ESP_LOGW(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: empty value", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
if (param->notify.handle != this->handle)
|
||||
@@ -131,8 +135,10 @@ float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
|
||||
if (this->has_data_to_value_) {
|
||||
std::vector<uint8_t> data(value, value + value_len);
|
||||
return this->data_to_value_func_(data);
|
||||
} else {
|
||||
} else if (value_len > 0) {
|
||||
return value[0];
|
||||
} else {
|
||||
return NAN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,10 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.handle != this->handle)
|
||||
break;
|
||||
if (param->notify.value_len == 0) {
|
||||
ESP_LOGW(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: empty value", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
this->publish_state(reinterpret_cast<const char *>(param->notify.value), param->notify.value_len);
|
||||
|
||||
@@ -103,17 +103,17 @@ size_t BLENUS::available() {
|
||||
#endif
|
||||
}
|
||||
|
||||
uart::FlushResult BLENUS::flush() {
|
||||
uart::UARTFlushResult BLENUS::flush() {
|
||||
constexpr uint32_t timeout_500ms = 500;
|
||||
uint32_t start = millis();
|
||||
while (atomic_get(&this->tx_status_) != TX_DISABLED && !ring_buf_is_empty(&global_ble_tx_ring_buf)) {
|
||||
if (millis() - start > timeout_500ms) {
|
||||
ESP_LOGW(TAG, "Flush timeout");
|
||||
return uart::FlushResult::TIMEOUT;
|
||||
return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
return uart::FlushResult::SUCCESS;
|
||||
return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void BLENUS::connected(bt_conn *conn, uint8_t err) {
|
||||
|
||||
@@ -26,7 +26,7 @@ class BLENUS : public uart::UARTComponent, public Component {
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
size_t available() override;
|
||||
uart::FlushResult flush() override;
|
||||
uart::UARTFlushResult flush() override;
|
||||
void check_logger_conflict() override {}
|
||||
void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; }
|
||||
#ifdef USE_LOGGER
|
||||
|
||||
@@ -30,6 +30,19 @@ void BluetoothProxy::setup() {
|
||||
this->configured_scan_active_ = this->parent_->get_scan_active();
|
||||
|
||||
this->parent_->add_scanner_state_listener(this);
|
||||
|
||||
this->set_interval(100, [this]() {
|
||||
if (api::global_api_server->is_connected() && this->api_connection_ != nullptr) {
|
||||
this->flush_pending_advertisements_();
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) {
|
||||
@@ -101,25 +114,15 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
|
||||
|
||||
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
|
||||
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
|
||||
this->flush_pending_advertisements();
|
||||
this->flush_pending_advertisements_();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BluetoothProxy::flush_pending_advertisements() {
|
||||
if (this->response_.advertisements_len == 0 || !api::global_api_server->is_connected() ||
|
||||
this->api_connection_ == nullptr)
|
||||
return;
|
||||
|
||||
// Send the message
|
||||
this->api_connection_->send_message(this->response_);
|
||||
|
||||
void BluetoothProxy::log_advertisement_flush_() {
|
||||
ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
|
||||
|
||||
// Reset the length for the next batch
|
||||
this->response_.advertisements_len = 0;
|
||||
}
|
||||
|
||||
void BluetoothProxy::dump_config() {
|
||||
@@ -130,27 +133,6 @@ void BluetoothProxy::dump_config() {
|
||||
YESNO(this->active_), this->connection_count_);
|
||||
}
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
// Flush accumulated advertisements every 100ms
|
||||
if (now - this->last_advertisement_flush_time_ >= 100) {
|
||||
this->flush_pending_advertisements();
|
||||
this->last_advertisement_flush_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
|
||||
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
|
||||
}
|
||||
|
||||
@@ -65,8 +65,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override;
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void flush_pending_advertisements();
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
void register_connection(BluetoothConnection *connection) {
|
||||
@@ -150,6 +148,18 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
protected:
|
||||
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
||||
|
||||
/// Caller must ensure api_connection_ is non-null and API server is connected.
|
||||
void flush_pending_advertisements_() {
|
||||
if (this->response_.advertisements_len == 0)
|
||||
return;
|
||||
this->api_connection_->send_message(this->response_);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
this->log_advertisement_flush_();
|
||||
#endif
|
||||
this->response_.advertisements_len = 0;
|
||||
}
|
||||
void log_advertisement_flush_();
|
||||
|
||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||
void log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state);
|
||||
void log_connection_info_(BluetoothConnection *connection, const char *message);
|
||||
@@ -166,9 +176,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
// BLE advertisement batching
|
||||
api::BluetoothLERawAdvertisementsResponse response_;
|
||||
|
||||
// Group 3: 4-byte types
|
||||
uint32_t last_advertisement_flush_time_{0};
|
||||
|
||||
// Pre-allocated response message - always ready to send
|
||||
api::BluetoothConnectionsFreeResponse connections_free_response_;
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "bm8563.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::bm8563 {
|
||||
@@ -56,7 +59,6 @@ void BM8563::read_time() {
|
||||
ESPTime rtc_time;
|
||||
this->get_time_(rtc_time);
|
||||
this->get_date_(rtc_time);
|
||||
rtc_time.day_of_year = 1; // unused by recalc_timestamp_utc, but needs to be valid
|
||||
ESP_LOGD(TAG, "Read time: %i-%i-%i %i, %i:%i:%i", rtc_time.year, rtc_time.month, rtc_time.day_of_month,
|
||||
rtc_time.day_of_week, rtc_time.hour, rtc_time.minute, rtc_time.second);
|
||||
|
||||
@@ -147,10 +149,10 @@ optional<uint8_t> BM8563::read_register_(uint8_t reg) {
|
||||
}
|
||||
|
||||
void BM8563::set_timer_irq_(uint32_t duration_s) {
|
||||
ESP_LOGI(TAG, "Timer Duration: %u s", duration_s);
|
||||
ESP_LOGI(TAG, "Timer Duration: %" PRIu32 " s", duration_s);
|
||||
|
||||
if (duration_s > MAX_TIMER_DURATION_S) {
|
||||
ESP_LOGW(TAG, "Timer duration %u s exceeds maximum %u s", duration_s, MAX_TIMER_DURATION_S);
|
||||
ESP_LOGW(TAG, "Timer duration %" PRIu32 " s exceeds maximum %" PRIu32 " s", duration_s, MAX_TIMER_DURATION_S);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -521,7 +521,7 @@ int BME680BSECComponent::reinit_bsec_lib_() {
|
||||
}
|
||||
|
||||
void BME680BSECComponent::load_state_() {
|
||||
uint32_t hash = fnv1_hash("bme680_bsec_state_" + this->device_id_);
|
||||
uint32_t hash = fnv1_hash_extend(fnv1_hash("bme680_bsec_state_"), this->device_id_);
|
||||
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
|
||||
|
||||
if (!this->bsec_state_.load(&this->bsec_state_data_)) {
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
#ifdef USE_BSEC2
|
||||
#include "bme68x_bsec2.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace bme68x_bsec2 {
|
||||
namespace esphome::bme68x_bsec2 {
|
||||
|
||||
#define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression")
|
||||
#define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days")
|
||||
@@ -18,9 +15,19 @@ namespace bme68x_bsec2 {
|
||||
|
||||
static const char *const TAG = "bme68x_bsec2.sensor";
|
||||
|
||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||
static constexpr const char *const IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||
|
||||
static bool is_no_new_data_warning(int8_t status) {
|
||||
#ifdef BME68X_W_NO_NEW_DATA
|
||||
return status == BME68X_W_NO_NEW_DATA;
|
||||
#else
|
||||
return status == 2;
|
||||
#endif
|
||||
}
|
||||
|
||||
void BME68xBSEC2Component::setup() {
|
||||
this->warn_if_blocking_over_ = 60; // initial reads may block for up to 60ms
|
||||
|
||||
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
@@ -82,7 +89,7 @@ void BME68xBSEC2Component::dump_config() {
|
||||
" Operating age: %s\n"
|
||||
" Sample rate: %s\n"
|
||||
" Voltage: %s\n"
|
||||
" State save interval: %ims\n"
|
||||
" State save interval: %" PRIu32 "ms\n"
|
||||
" Temperature offset: %.2f",
|
||||
BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_), BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_),
|
||||
BME68X_BSEC2_VOLTAGE_LOG(this->voltage_), this->state_save_interval_ms_, this->temperature_offset_);
|
||||
@@ -114,7 +121,8 @@ void BME68xBSEC2Component::loop() {
|
||||
} else {
|
||||
this->status_clear_error();
|
||||
}
|
||||
if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) {
|
||||
const bool has_bme68x_warning = this->bme68x_status_ > BME68X_OK && !is_no_new_data_warning(this->bme68x_status_);
|
||||
if (this->bsec_status_ > BSEC_OK || has_bme68x_warning) {
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
@@ -130,7 +138,7 @@ void BME68xBSEC2Component::loop() {
|
||||
|
||||
void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) {
|
||||
if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) {
|
||||
ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE");
|
||||
ESP_LOGE(TAG, "Configuration blob too large");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -212,14 +220,12 @@ void BME68xBSEC2Component::run_() {
|
||||
if (curr_time_ns < this->bsec_settings_.next_call) {
|
||||
return;
|
||||
}
|
||||
uint8_t status;
|
||||
|
||||
ESP_LOGV(TAG, "Performing sensor run");
|
||||
|
||||
struct bme68x_conf bme68x_conf;
|
||||
this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_);
|
||||
if (this->bsec_status_ < BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_);
|
||||
ESP_LOGW(TAG, "Fetching control settings failed (BSEC2 error code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,9 +241,9 @@ void BME68xBSEC2Component::run_() {
|
||||
this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature;
|
||||
this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration;
|
||||
|
||||
// status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
|
||||
status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
|
||||
status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
|
||||
// this->bme68x_status_ = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
|
||||
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
|
||||
this->bme68x_status_ = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
|
||||
this->op_mode_ = BME68X_FORCED_MODE;
|
||||
ESP_LOGV(TAG, "Using forced mode");
|
||||
|
||||
@@ -259,9 +265,8 @@ void BME68xBSEC2Component::run_() {
|
||||
BSEC_TOTAL_HEAT_DUR -
|
||||
(bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000));
|
||||
|
||||
status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
|
||||
|
||||
status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
|
||||
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
|
||||
this->bme68x_status_ = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
|
||||
this->op_mode_ = BME68X_PARALLEL_MODE;
|
||||
ESP_LOGV(TAG, "Using parallel mode");
|
||||
}
|
||||
@@ -276,29 +281,21 @@ void BME68xBSEC2Component::run_() {
|
||||
}
|
||||
|
||||
if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) {
|
||||
uint32_t meas_dur = 0;
|
||||
meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
|
||||
ESP_LOGV(TAG, "Queueing read in %uus", meas_dur);
|
||||
this->set_timeout("read", meas_dur / 1000, [this, curr_time_ns]() { this->read_(curr_time_ns); });
|
||||
bme68x_get_conf(&bme68x_conf, &this->bme68x_);
|
||||
uint32_t meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
|
||||
ESP_LOGV(TAG, "Queueing read in %" PRIu32 "us", meas_dur);
|
||||
this->trigger_time_ns_ = curr_time_ns;
|
||||
this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); });
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Measurement not required");
|
||||
this->read_(curr_time_ns);
|
||||
ESP_LOGV(TAG, "Measurement not required, queueing immediate read");
|
||||
this->trigger_time_ns_ = curr_time_ns;
|
||||
this->set_timeout("read", 0, [this]() { this->read_(this->trigger_time_ns_); });
|
||||
}
|
||||
}
|
||||
|
||||
void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
|
||||
ESP_LOGV(TAG, "Reading data");
|
||||
|
||||
if (this->bsec_settings_.trigger_measurement) {
|
||||
uint8_t current_op_mode;
|
||||
this->bme68x_status_ = bme68x_get_op_mode(¤t_op_mode, &this->bme68x_);
|
||||
|
||||
if (current_op_mode == BME68X_SLEEP_MODE) {
|
||||
ESP_LOGV(TAG, "Still in sleep mode, doing nothing");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->bsec_settings_.process_data) {
|
||||
ESP_LOGV(TAG, "Data processing not required");
|
||||
return;
|
||||
@@ -308,12 +305,16 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
|
||||
uint8_t nFields = 0;
|
||||
this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_);
|
||||
|
||||
if (is_no_new_data_warning(this->bme68x_status_)) {
|
||||
ESP_LOGV(TAG, "BME68X did not provide new data");
|
||||
return;
|
||||
}
|
||||
if (this->bme68x_status_ != BME68X_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_);
|
||||
ESP_LOGW(TAG, "Fetching data failed (BME68X error code %d)", this->bme68x_status_);
|
||||
return;
|
||||
}
|
||||
if (nFields < 1) {
|
||||
ESP_LOGD(TAG, "BME68X did not provide new data");
|
||||
ESP_LOGV(TAG, "BME68X did not provide new fields");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -372,7 +373,7 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
|
||||
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
|
||||
this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_);
|
||||
ESP_LOGW(TAG, "Signal processing failed (BSEC2 error code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
if (num_outputs < 1) {
|
||||
@@ -473,7 +474,7 @@ void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value,
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
|
||||
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const char *value) {
|
||||
if (!sensor || (sensor->has_state() && sensor->state == value)) {
|
||||
return;
|
||||
}
|
||||
@@ -525,6 +526,5 @@ void BME68xBSEC2Component::save_state_(uint8_t accuracy) {
|
||||
ESP_LOGI(TAG, "Saved state");
|
||||
}
|
||||
|
||||
} // namespace bme68x_bsec2
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bme68x_bsec2
|
||||
#endif
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
|
||||
#include <bsec2.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace bme68x_bsec2 {
|
||||
namespace esphome::bme68x_bsec2 {
|
||||
|
||||
enum AlgorithmOutput {
|
||||
ALGORITHM_OUTPUT_IAQ,
|
||||
@@ -97,7 +96,7 @@ class BME68xBSEC2Component : public Component {
|
||||
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
|
||||
void publish_sensor_(text_sensor::TextSensor *sensor, const char *value);
|
||||
#endif
|
||||
|
||||
void load_state_();
|
||||
@@ -108,37 +107,12 @@ class BME68xBSEC2Component : public Component {
|
||||
struct bme68x_dev bme68x_;
|
||||
bsec_bme_settings_t bsec_settings_;
|
||||
bsec_version_t version_;
|
||||
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
|
||||
|
||||
struct bme68x_heatr_conf bme68x_heatr_conf_;
|
||||
uint8_t op_mode_; // operating mode of sensor
|
||||
bsec_library_return_t bsec_status_{BSEC_OK};
|
||||
int8_t bme68x_status_{BME68X_OK};
|
||||
|
||||
int64_t last_time_ms_{0};
|
||||
uint32_t millis_overflow_counter_{0};
|
||||
|
||||
std::queue<std::function<void()>> queue_;
|
||||
ESPPreferenceObject bsec_state_;
|
||||
|
||||
uint8_t const *bsec2_configuration_{nullptr};
|
||||
uint32_t bsec2_configuration_length_{0};
|
||||
bool bsec2_blob_configured_{false};
|
||||
|
||||
ESPPreferenceObject bsec_state_;
|
||||
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
||||
uint32_t last_state_save_ms_ = 0;
|
||||
|
||||
float temperature_offset_{0};
|
||||
|
||||
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
|
||||
OperatingAge operating_age_{OPERATING_AGE_28D};
|
||||
Voltage voltage_{VOLTAGE_3_3V};
|
||||
|
||||
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
|
||||
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
@@ -153,8 +127,32 @@ class BME68xBSEC2Component : public Component {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr};
|
||||
#endif
|
||||
|
||||
int64_t last_time_ms_{0};
|
||||
int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit
|
||||
// toolchains with small std::function SBO
|
||||
|
||||
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
||||
uint32_t last_state_save_ms_{0};
|
||||
uint32_t millis_overflow_counter_{0};
|
||||
uint32_t bsec2_configuration_length_{0};
|
||||
bsec_library_return_t bsec_status_{BSEC_OK};
|
||||
|
||||
float temperature_offset_{0};
|
||||
|
||||
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
|
||||
OperatingAge operating_age_{OPERATING_AGE_28D};
|
||||
Voltage voltage_{VOLTAGE_3_3V};
|
||||
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
|
||||
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
|
||||
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
|
||||
uint8_t op_mode_; // operating mode of sensor
|
||||
int8_t bme68x_status_{BME68X_OK};
|
||||
bool bsec2_blob_configured_{false};
|
||||
};
|
||||
|
||||
} // namespace bme68x_bsec2
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bme68x_bsec2
|
||||
#endif
|
||||
|
||||
@@ -126,7 +126,7 @@ void BMP581Component::setup() {
|
||||
}
|
||||
|
||||
// verify id
|
||||
if (chip_id != BMP581_ASIC_ID) {
|
||||
if (chip_id != BMP581_ASIC_ID && chip_id != BMP585_ASIC_ID) {
|
||||
ESP_LOGE(TAG, "Unknown chip ID");
|
||||
|
||||
this->error_code_ = ERROR_WRONG_CHIP_ID;
|
||||
@@ -469,14 +469,18 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &
|
||||
}
|
||||
|
||||
bool BMP581Component::reset_() {
|
||||
// - activates interface (only relevant for SPI mode)
|
||||
// - writes reset command to the command register
|
||||
// - waits for sensor to complete reset
|
||||
// - activates interface (only relevant for SPI mode)
|
||||
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
|
||||
|
||||
// activates communication interface (SPI only)
|
||||
this->activate_interface();
|
||||
|
||||
// writes reset command to BMP's command register
|
||||
if (!this->bmp_write_byte(BMP581_COMMAND, RESET_COMMAND)) {
|
||||
ESP_LOGE(TAG, "Failed to write reset command");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -484,6 +488,9 @@ bool BMP581Component::reset_() {
|
||||
// - round up to 3 ms
|
||||
delay(3);
|
||||
|
||||
// reactivates communication interface after reset (SPI only)
|
||||
this->activate_interface();
|
||||
|
||||
// read interrupt status register
|
||||
if (!this->bmp_read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||
@@ -491,7 +498,7 @@ bool BMP581Component::reset_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Power-On-Reboot bit is asserted if sensor successfully reset
|
||||
// power-On-Reboot bit is asserted if sensor successfully reset
|
||||
return this->int_status_.bit.por;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
namespace esphome::bmp581_base {
|
||||
|
||||
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
|
||||
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
||||
static const uint8_t BMP585_ASIC_ID = 0x51;
|
||||
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
||||
|
||||
// BMP581 Register Addresses
|
||||
enum {
|
||||
@@ -87,6 +88,9 @@ class BMP581Component : public PollingComponent {
|
||||
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||
virtual bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||
|
||||
// Interface activation function. Only used for SPI interface; no-op for I2C.
|
||||
virtual void activate_interface() {}
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
|
||||
|
||||
0
esphome/components/bmp581_spi/__init__.py
Normal file
0
esphome/components/bmp581_spi/__init__.py
Normal file
73
esphome/components/bmp581_spi/bmp581_spi.cpp
Normal file
73
esphome/components/bmp581_spi/bmp581_spi.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#include "bmp581_spi.h"
|
||||
#include "esphome/components/bmp581_base/bmp581_base.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome::bmp581_spi {
|
||||
|
||||
static const char *const TAG = "bmp581_spi";
|
||||
|
||||
// OR (|) register with BMP_SPI_READ for read
|
||||
inline constexpr uint8_t BMP_SPI_READ = 0x80;
|
||||
|
||||
// AND (&) register with BMP_SPI_WRITE for write
|
||||
inline constexpr uint8_t BMP_SPI_WRITE = 0x7F;
|
||||
|
||||
void BMP581SPIComponent::dump_config() {
|
||||
BMP581Component::dump_config();
|
||||
LOG_SPI_DEVICE(this);
|
||||
}
|
||||
|
||||
void BMP581SPIComponent::setup() {
|
||||
this->spi_setup();
|
||||
BMP581Component::setup();
|
||||
}
|
||||
|
||||
void BMP581SPIComponent::activate_interface() {
|
||||
// - forces the device into SPI mode using a dummy read
|
||||
uint8_t dummy_read = 0;
|
||||
this->bmp_read_byte(bmp581_base::BMP581_CHIP_ID, &dummy_read);
|
||||
}
|
||||
|
||||
// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used
|
||||
// and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read).
|
||||
// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte
|
||||
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
|
||||
// The expressions BMP_SPI_READ (| with register) and BMP_SPI_WRITE (& with register)
|
||||
// are defined for readability.
|
||||
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp581-ds004.pdf
|
||||
|
||||
bool BMP581SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
|
||||
this->enable();
|
||||
this->transfer_byte(a_register | BMP_SPI_READ);
|
||||
*data = this->transfer_byte(0);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
|
||||
this->enable();
|
||||
this->transfer_byte(a_register & BMP_SPI_WRITE);
|
||||
this->transfer_byte(data);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
this->enable();
|
||||
this->transfer_byte(a_register | BMP_SPI_READ);
|
||||
this->read_array(data, len);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581SPIComponent::bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
this->enable();
|
||||
this->transfer_byte(a_register & BMP_SPI_WRITE);
|
||||
this->write_array(data, len);
|
||||
this->disable();
|
||||
return true;
|
||||
}
|
||||
} // namespace esphome::bmp581_spi
|
||||
24
esphome/components/bmp581_spi/bmp581_spi.h
Normal file
24
esphome/components/bmp581_spi/bmp581_spi.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/bmp581_base/bmp581_base.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome::bmp581_spi {
|
||||
|
||||
// BMP581 is technically compatible with SPI Mode0 and Mode3. Default to Mode3.
|
||||
class BMP581SPIComponent : public esphome::bmp581_base::BMP581Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
|
||||
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void activate_interface() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::bmp581_spi
|
||||
48
esphome/components/bmp581_spi/sensor.py
Normal file
48
esphome/components/bmp581_spi/sensor.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import spi
|
||||
from esphome.components.spi import CONF_SPI_MODE
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from ..bmp581_base import CONFIG_SCHEMA_BASE, to_code_base
|
||||
|
||||
AUTO_LOAD = ["bmp581_base"]
|
||||
CODEOWNERS = ["@kahrendt", "@danielkent-net"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
VALID_SPI_MODES = {
|
||||
0: "MODE0",
|
||||
"0": "MODE0",
|
||||
"MODE0": "MODE0",
|
||||
3: "MODE3",
|
||||
"3": "MODE3",
|
||||
"MODE3": "MODE3",
|
||||
}
|
||||
|
||||
bmp581_ns = cg.esphome_ns.namespace("bmp581_spi")
|
||||
BMP581SPIComponent = bmp581_ns.class_(
|
||||
"BMP581SPIComponent", cg.PollingComponent, spi.SPIDevice
|
||||
)
|
||||
|
||||
|
||||
def check_spi_mode(config):
|
||||
spi_mode = config.get(CONF_SPI_MODE)
|
||||
if spi_mode not in VALID_SPI_MODES:
|
||||
raise cv.Invalid("BMP581 only supports SPI mode 3")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema(default_mode="mode3")).extend(
|
||||
{cv.GenerateID(): cv.declare_id(BMP581SPIComponent)}
|
||||
),
|
||||
check_spi_mode,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await to_code_base(config)
|
||||
await spi.register_spi_device(var, config)
|
||||
@@ -10,7 +10,12 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_idf_version.h>
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
#include <psa/crypto.h>
|
||||
#else
|
||||
#include "mbedtls/ccm.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace bthome_mithermometer {
|
||||
@@ -196,6 +201,37 @@ bool BTHomeMiThermometer::decrypt_bthome_payload_(const std::vector<uint8_t> &da
|
||||
const uint8_t *ciphertext = data.data() + 1;
|
||||
const uint8_t *mic = data.data() + data.size() - BTHOME_MIC_SIZE;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
// PSA AEAD expects ciphertext + tag concatenated
|
||||
// BLE advertisement max payload is 31 bytes, so this is always sufficient
|
||||
static constexpr size_t MAX_CT_WITH_TAG = 32;
|
||||
uint8_t ct_with_tag[MAX_CT_WITH_TAG];
|
||||
size_t ct_with_tag_size = ciphertext_size + BTHOME_MIC_SIZE;
|
||||
memcpy(ct_with_tag, ciphertext, ciphertext_size);
|
||||
memcpy(ct_with_tag + ciphertext_size, mic, BTHOME_MIC_SIZE);
|
||||
|
||||
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
|
||||
psa_set_key_bits(&attributes, BTHOME_BINDKEY_SIZE * 8);
|
||||
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT);
|
||||
psa_set_key_algorithm(&attributes, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, BTHOME_MIC_SIZE));
|
||||
|
||||
mbedtls_svc_key_id_t key_id;
|
||||
if (psa_import_key(&attributes, this->bindkey_, BTHOME_BINDKEY_SIZE, &key_id) != PSA_SUCCESS) {
|
||||
ESP_LOGVV(TAG, "psa_import_key() failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t plaintext_length;
|
||||
psa_status_t status = psa_aead_decrypt(key_id, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, BTHOME_MIC_SIZE),
|
||||
nonce.data(), nonce.size(), nullptr, 0, ct_with_tag, ct_with_tag_size,
|
||||
payload.data(), ciphertext_size, &plaintext_length);
|
||||
psa_destroy_key(key_id);
|
||||
if (status != PSA_SUCCESS || plaintext_length != ciphertext_size) {
|
||||
ESP_LOGVV(TAG, "BTHome decryption failed.");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
mbedtls_ccm_context ctx;
|
||||
mbedtls_ccm_init(&ctx);
|
||||
|
||||
@@ -213,6 +249,7 @@ bool BTHomeMiThermometer::decrypt_bthome_payload_(const std::vector<uint8_t> &da
|
||||
ESP_LOGVV(TAG, "BTHome decryption failed (ret=%d).", ret);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_PRESS,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WEB_SERVER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_IDENTIFY,
|
||||
@@ -41,10 +40,6 @@ ButtonPtr = Button.operator("ptr")
|
||||
|
||||
PressAction = button_ns.class_("PressAction", automation.Action)
|
||||
|
||||
ButtonPressTrigger = button_ns.class_(
|
||||
"ButtonPressTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
|
||||
@@ -55,11 +50,7 @@ _BUTTON_SCHEMA = (
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -91,8 +82,9 @@ def button_schema(
|
||||
@setup_entity("button")
|
||||
async def setup_button_core_(var, config):
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_press_callback", [], conf
|
||||
)
|
||||
|
||||
setup_device_class(config)
|
||||
|
||||
|
||||
@@ -20,6 +20,5 @@ void Button::press() {
|
||||
this->press_action();
|
||||
this->press_callback_.call();
|
||||
}
|
||||
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
|
||||
|
||||
} // namespace esphome::button
|
||||
|
||||
@@ -34,7 +34,9 @@ class Button : public EntityBase {
|
||||
*
|
||||
* @param callback The void() callback.
|
||||
*/
|
||||
void add_on_press_callback(std::function<void()> &&callback);
|
||||
template<typename F> void add_on_press_callback(F &&callback) {
|
||||
this->press_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
protected:
|
||||
/** You should implement this virtual method if you want to create your own button.
|
||||
|
||||
@@ -50,7 +50,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID])
|
||||
cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
||||
if config[CONF_TYPE] == ESP32_CAMERA_ENCODER:
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.6")
|
||||
cg.add_define("USE_ESP32_CAMERA_JPEG_ENCODER")
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
|
||||
@@ -91,10 +91,7 @@ class Canbus : public Component {
|
||||
* - rtr If this is a remote transmission request
|
||||
* - data The message data
|
||||
*/
|
||||
void add_callback(
|
||||
std::function<void(uint32_t can_id, bool extended_id, bool rtr, const std::vector<uint8_t> &data)> callback) {
|
||||
this->callback_manager_.add(std::move(callback));
|
||||
}
|
||||
template<typename F> void add_callback(F &&callback) { this->callback_manager_.add(std::forward<F>(callback)); }
|
||||
|
||||
protected:
|
||||
template<typename... Ts> friend class CanbusSendAction;
|
||||
|
||||
@@ -100,8 +100,9 @@ void DNSServer::process_next_request() {
|
||||
&client_addr_len);
|
||||
|
||||
if (len < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
|
||||
ESP_LOGE(TAG, "recvfrom failed: %d", errno);
|
||||
const int err = errno;
|
||||
if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) {
|
||||
ESP_LOGE(TAG, "recvfrom failed: %d", err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,12 +124,6 @@ bool CH422GComponent::write_outputs_() {
|
||||
|
||||
float CH422GComponent::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
// Run our loop() method very early in the loop, so that we cache read values
|
||||
// before other components call our digital_read() method.
|
||||
float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
||||
#endif
|
||||
|
||||
void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ class CH422GComponent : public Component, public i2c::I2CDevice {
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
float get_loop_priority() const override;
|
||||
#endif
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -129,12 +129,6 @@ bool CH423Component::write_outputs_() {
|
||||
|
||||
float CH423Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
// Run our loop() method very early in the loop, so that we cache read values
|
||||
// before other components call our digital_read() method.
|
||||
float CH423Component::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
||||
#endif
|
||||
|
||||
void CH423GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool CH423GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
|
||||
|
||||
|
||||
@@ -22,9 +22,6 @@ class CH423Component : public Component, public i2c::I2CDevice {
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
#ifdef USE_LOOP_PRIORITY
|
||||
float get_loop_priority() const override;
|
||||
#endif
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user