mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +00:00
Compare commits
1176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fe61d9ec7 | ||
|
|
6114d331c9 | ||
|
|
e2e074a3fd | ||
|
|
4d340dc029 | ||
|
|
fb6c5ebe9a | ||
|
|
af3273d930 | ||
|
|
8f1eb77e05 | ||
|
|
89d0d41c5a | ||
|
|
452ca8e4c6 | ||
|
|
e51b0ca15e | ||
|
|
5eeb110d74 | ||
|
|
60b2d57dc3 | ||
|
|
91898cb814 | ||
|
|
818a7f1c78 | ||
|
|
dedf343bd5 | ||
|
|
251240cc90 | ||
|
|
e5b45b6b4b | ||
|
|
a77784a6da | ||
|
|
f63f9168ff | ||
|
|
b5b2036971 | ||
|
|
a96b6e7002 | ||
|
|
f34c9b33fc | ||
|
|
faf577a9dd | ||
|
|
7708b81ef5 | ||
|
|
08998caabc | ||
|
|
848a5f1680 | ||
|
|
2e7c1d7345 | ||
|
|
28a72fa56b | ||
|
|
cd1353ae54 | ||
|
|
c9ee513fa8 | ||
|
|
2a12caa955 | ||
|
|
5e5f8d5f9b | ||
|
|
2c0fe49b86 | ||
|
|
1e227e8051 | ||
|
|
d5cf4b7eac | ||
|
|
570ec36fe3 | ||
|
|
69879920eb | ||
|
|
2b60b0f1fa | ||
|
|
c17624adab | ||
|
|
0f151a8f6b | ||
|
|
e62bf333a2 | ||
|
|
88b46b7487 | ||
|
|
fa290fbce8 | ||
|
|
1883ce1876 | ||
|
|
f3fe2bde38 | ||
|
|
811c13d7d1 | ||
|
|
521dfe08f2 | ||
|
|
2c77d121da | ||
|
|
c5dc736c53 | ||
|
|
8e93735861 | ||
|
|
ac25b138f5 | ||
|
|
b17e0c298e | ||
|
|
422f0ad7a9 | ||
|
|
34d37961c3 | ||
|
|
bdf004867d | ||
|
|
08ecca86bc | ||
|
|
520c4331e3 | ||
|
|
342d5166a0 | ||
|
|
69d39ef0cd | ||
|
|
6588e9380e | ||
|
|
4d6277330b | ||
|
|
87154e9b6f | ||
|
|
b91723344e | ||
|
|
92b36720b6 | ||
|
|
3d0310d0e0 | ||
|
|
f81cddf22e | ||
|
|
808ce6eecb | ||
|
|
665d0fd759 | ||
|
|
c519c02de8 | ||
|
|
eacac78099 | ||
|
|
a925036ff8 | ||
|
|
1468293f3e | ||
|
|
25924ca4e8 | ||
|
|
6c8ace0ce8 | ||
|
|
acc1af0f51 | ||
|
|
c92c439d17 | ||
|
|
410fad3b41 | ||
|
|
dce20680d7 | ||
|
|
f95be6a0df | ||
|
|
a342302114 | ||
|
|
925a992d1b | ||
|
|
61ecbe4273 | ||
|
|
65c7e27a43 | ||
|
|
57b56010da | ||
|
|
ef89249019 | ||
|
|
bc64cf3e47 | ||
|
|
def70dde72 | ||
|
|
b52f7cfe86 | ||
|
|
81b512a7b3 | ||
|
|
57d6185374 | ||
|
|
e288aa07fb | ||
|
|
50006e4c42 | ||
|
|
23cf120977 | ||
|
|
04d8593f38 | ||
|
|
28e39f7f76 | ||
|
|
de3377132d | ||
|
|
b351cd94d7 | ||
|
|
d238e06f86 | ||
|
|
2fc59ecc30 | ||
|
|
0047d24698 | ||
|
|
89a89e1785 | ||
|
|
1952d275f7 | ||
|
|
043095b605 | ||
|
|
bccaa78a90 | ||
|
|
f402c89539 | ||
|
|
431d3578a5 | ||
|
|
5f02d86841 | ||
|
|
a7ec57d4be | ||
|
|
4eeb111fa3 | ||
|
|
1ea5cc497f | ||
|
|
b601cf6bc6 | ||
|
|
5057caa7fc | ||
|
|
95bef53d37 | ||
|
|
ea019a057b | ||
|
|
1d378e416d | ||
|
|
d3b758d10a | ||
|
|
7b9c2d2978 | ||
|
|
9d38543cb0 | ||
|
|
b860a317b9 | ||
|
|
9591c903f7 | ||
|
|
65fbb8bc60 | ||
|
|
97428f2ba2 | ||
|
|
9ab6a7b7ff | ||
|
|
e2ad6fe3d8 | ||
|
|
6781d08c9b | ||
|
|
36a2ce2c23 | ||
|
|
c7c71089ce | ||
|
|
52c67d715b | ||
|
|
f084ab339b | ||
|
|
8352f52fef | ||
|
|
b28735d63b | ||
|
|
4c105398f7 | ||
|
|
828f7946ea | ||
|
|
23c663d5d4 | ||
|
|
6a99789c92 | ||
|
|
52e13164b4 | ||
|
|
28f2582256 | ||
|
|
652f6058d1 | ||
|
|
717aab7c8b | ||
|
|
5e799b5284 | ||
|
|
9f36b25d4e | ||
|
|
d9a2651a5a | ||
|
|
5fcd1e391d | ||
|
|
36089a1400 | ||
|
|
e7b1d2efaa | ||
|
|
bf453ad8cd | ||
|
|
fbc1b3e316 | ||
|
|
400819175d | ||
|
|
3c34b539b0 | ||
|
|
c8058e9636 | ||
|
|
f2474c5c45 | ||
|
|
7acc36d39d | ||
|
|
b01db991a5 | ||
|
|
86385a1c19 | ||
|
|
96ab6b51b8 | ||
|
|
8c849b9002 | ||
|
|
6a0d4cb5a9 | ||
|
|
72002ce70e | ||
|
|
c67539cf5b | ||
|
|
dcd3d2084d | ||
|
|
585bb6dac8 | ||
|
|
02dc49c272 | ||
|
|
5df398ec31 | ||
|
|
699696e8d1 | ||
|
|
93e35a53ed | ||
|
|
a269098a0e | ||
|
|
cac3055261 | ||
|
|
6f7e6cc944 | ||
|
|
0a841fcc50 | ||
|
|
3c64c9b0e9 | ||
|
|
34df25da39 | ||
|
|
9586fb95d1 | ||
|
|
3ee6348e41 | ||
|
|
543f2c8152 | ||
|
|
16d11be213 | ||
|
|
e49b568fd4 | ||
|
|
22ab830ff3 | ||
|
|
095d3181cd | ||
|
|
9aa14a2e83 | ||
|
|
ac15ce576b | ||
|
|
498b59e998 | ||
|
|
63c420254a | ||
|
|
765e641d08 | ||
|
|
be16d10b7d | ||
|
|
4b808611e9 | ||
|
|
7afe202e20 | ||
|
|
039810eef3 | ||
|
|
ff43b45113 | ||
|
|
7cd4c3bdd3 | ||
|
|
c12c9e97c2 | ||
|
|
b3169deda7 | ||
|
|
0ea41e2f71 | ||
|
|
3afb564a48 | ||
|
|
7ff3f752e2 | ||
|
|
d821ead92a | ||
|
|
e42ce64127 | ||
|
|
9d2b0b4e03 | ||
|
|
b5e6ae0d69 | ||
|
|
08f1eac8b2 | ||
|
|
6ed3da33a2 | ||
|
|
a9a00f139b | ||
|
|
63d8071dbd | ||
|
|
d20caa9d60 | ||
|
|
7e40d4246c | ||
|
|
5a2b14cfa4 | ||
|
|
f2d218e5ad | ||
|
|
b493d5bba5 | ||
|
|
c9055f2aef | ||
|
|
9fed7cab5f | ||
|
|
eb5c4b7c4f | ||
|
|
2ab3534a4b | ||
|
|
9816e677a6 | ||
|
|
fc01a70b65 | ||
|
|
7221337442 | ||
|
|
051a1e4772 | ||
|
|
274741a9d5 | ||
|
|
25c01adf51 | ||
|
|
bf2d54c3ef | ||
|
|
49cb8fd9d3 | ||
|
|
e536316e3d | ||
|
|
a6c46eb8e5 | ||
|
|
1a270374e0 | ||
|
|
e73eafbd88 | ||
|
|
9fc3e05b76 | ||
|
|
31c604331c | ||
|
|
3fcdaaefe0 | ||
|
|
20dd744680 | ||
|
|
e4636b99f7 | ||
|
|
10e7abb579 | ||
|
|
d3f03b7acb | ||
|
|
7e53fc9d6a | ||
|
|
0059a6de46 | ||
|
|
22e1758d5b | ||
|
|
59cdc32970 | ||
|
|
adb51cf733 | ||
|
|
d97a9bf8e8 | ||
|
|
f034472e2a | ||
|
|
ed328d2df8 | ||
|
|
1520dc8755 | ||
|
|
bf601c3126 | ||
|
|
ab48e4a466 | ||
|
|
8ef0f5b047 | ||
|
|
2c14d134be | ||
|
|
9cd21bb5a0 | ||
|
|
bd061ac2ee | ||
|
|
dd3e821857 | ||
|
|
b38b7019ea | ||
|
|
2c71ee7853 | ||
|
|
540c62061d | ||
|
|
221ef07c8b | ||
|
|
29fc7ea154 | ||
|
|
7b157aeff1 | ||
|
|
e50644edee | ||
|
|
5f619e6f01 | ||
|
|
b266fb37a3 | ||
|
|
c9caf44c2e | ||
|
|
0c87a9ad2c | ||
|
|
c680b437f5 | ||
|
|
4988349677 | ||
|
|
e35d56defe | ||
|
|
5c86f332b2 | ||
|
|
002861f13b | ||
|
|
dbec3d7c50 | ||
|
|
89cde158d6 | ||
|
|
d7b76aadb2 | ||
|
|
e09fefd389 | ||
|
|
febc485da6 | ||
|
|
2ae709c2ba | ||
|
|
0ccfdd4711 | ||
|
|
0e59243b83 | ||
|
|
01bbd04a5a | ||
|
|
a3b2d384f5 | ||
|
|
50238f8d72 | ||
|
|
704470d606 | ||
|
|
f7e6195466 | ||
|
|
a0bb7c3ed0 | ||
|
|
e3a6c9a6cf | ||
|
|
99598d87a9 | ||
|
|
b5df50893b | ||
|
|
f46b3d15cd | ||
|
|
041b4ec66e | ||
|
|
703e9673c2 | ||
|
|
e7bd93b4b0 | ||
|
|
a401c71d3e | ||
|
|
5bae233334 | ||
|
|
c4edd3047f | ||
|
|
c50da1593a | ||
|
|
1d06426281 | ||
|
|
9c5b693dd5 | ||
|
|
5fecc70db1 | ||
|
|
ff24023b39 | ||
|
|
1a04e2d1b8 | ||
|
|
52c4dd0e35 | ||
|
|
ff90f6a440 | ||
|
|
e24d5c172f | ||
|
|
0918f452a0 | ||
|
|
839fe49e61 | ||
|
|
ff050d634a | ||
|
|
228670df78 | ||
|
|
cfe4638665 | ||
|
|
b7352b1345 | ||
|
|
32ae8fc2d0 | ||
|
|
86df4c1d8d | ||
|
|
dc4a88029c | ||
|
|
5da9b2ede7 | ||
|
|
29cfcfaf0f | ||
|
|
61bfd347ea | ||
|
|
b7436c0b22 | ||
|
|
8a45dfac5c | ||
|
|
69f5d8cd0f | ||
|
|
9a57e8fcb0 | ||
|
|
a9d75ca4f4 | ||
|
|
ccb6fc3010 | ||
|
|
4e9a05fe11 | ||
|
|
8a294e4134 | ||
|
|
aad9a539c1 | ||
|
|
fd6ac529fb | ||
|
|
009cea1abf | ||
|
|
4c3c14ec32 | ||
|
|
636c9db1e3 | ||
|
|
71f625bbd3 | ||
|
|
aea2e9a6bb | ||
|
|
3f6f3c14c4 | ||
|
|
b1d77b7c03 | ||
|
|
49233e4734 | ||
|
|
e2b5ecb78b | ||
|
|
351ce67eae | ||
|
|
44af5e439c | ||
|
|
dbc0d500d8 | ||
|
|
86736aa480 | ||
|
|
57eb05c0e3 | ||
|
|
f6fe6e6bff | ||
|
|
18560f9430 | ||
|
|
cb0ba647ed | ||
|
|
f9fceb7ffc | ||
|
|
2697c9465b | ||
|
|
8d204655be | ||
|
|
8414a22356 | ||
|
|
36e4a8b444 | ||
|
|
949c71dc97 | ||
|
|
a6f6b8da7f | ||
|
|
a9f123e864 | ||
|
|
a64a505817 | ||
|
|
352004221e | ||
|
|
fc6a3e31c2 | ||
|
|
a32b58fdf1 | ||
|
|
2d50ecbecf | ||
|
|
87f1ffec05 | ||
|
|
f0dfde9fa1 | ||
|
|
e36dc2d05e | ||
|
|
08c8fa2c90 | ||
|
|
fe6621357e | ||
|
|
4a0067a2c5 | ||
|
|
b270ff335d | ||
|
|
7d2fcf59fd | ||
|
|
d26c43103d | ||
|
|
389889ad70 | ||
|
|
0af73c7903 | ||
|
|
8aa73bba10 | ||
|
|
5396b05237 | ||
|
|
e898345ff1 | ||
|
|
f39bf9c1e5 | ||
|
|
9483a9c8c4 | ||
|
|
52639a0a7c | ||
|
|
a1e10f384e | ||
|
|
27d4b3b8ad | ||
|
|
6438bd84fb | ||
|
|
4b8e910e3f | ||
|
|
9c0e463698 | ||
|
|
2092939353 | ||
|
|
b9d55fd1ed | ||
|
|
c1748d586e | ||
|
|
4c55b9c58c | ||
|
|
312958c573 | ||
|
|
65a489ebdd | ||
|
|
bd392e2efc | ||
|
|
25ad33a377 | ||
|
|
abc83f6cb0 | ||
|
|
f61a82a568 | ||
|
|
1c3ed71d36 | ||
|
|
8215a018e9 | ||
|
|
bf3b678727 | ||
|
|
f6e3070dd8 | ||
|
|
4996967c79 | ||
|
|
5887fe8302 | ||
|
|
0fc8e8d483 | ||
|
|
55388724af | ||
|
|
32efa5d83e | ||
|
|
275c12150e | ||
|
|
62468198d6 | ||
|
|
43d5e7a8cc | ||
|
|
7524493c3c | ||
|
|
5759de079b | ||
|
|
f3d5d27c8f | ||
|
|
c030be4d3f | ||
|
|
50cf57affb | ||
|
|
25c62319a3 | ||
|
|
27abb1280a | ||
|
|
c73f0eee4c | ||
|
|
9208227d92 | ||
|
|
725e8c69f5 | ||
|
|
facd4f63ec | ||
|
|
2e1d14b8b1 | ||
|
|
e8272759be | ||
|
|
ebbfab608c | ||
|
|
a5e1f8fe19 | ||
|
|
1b2de953d0 | ||
|
|
df5d03b0bc | ||
|
|
11c1fe8827 | ||
|
|
f29622abe1 | ||
|
|
10e411f8c1 | ||
|
|
6405799cc2 | ||
|
|
0afa41d08a | ||
|
|
48f3dfe455 | ||
|
|
6f0bfb286a | ||
|
|
2e54d3f98d | ||
|
|
67b4dcf8ae | ||
|
|
ad91362571 | ||
|
|
76a3e75bc7 | ||
|
|
e88418a01b | ||
|
|
27e08d37ea | ||
|
|
e069687477 | ||
|
|
ef0e611e52 | ||
|
|
d58d0e89c7 | ||
|
|
dfbf225403 | ||
|
|
e962762046 | ||
|
|
8166d0de79 | ||
|
|
d9c33f19e2 | ||
|
|
0cc3902ffc | ||
|
|
4752096520 | ||
|
|
dcadcdf056 | ||
|
|
b6394b7c6d | ||
|
|
3ec9bcaed6 | ||
|
|
d5c59292c8 | ||
|
|
a20e71b32a | ||
|
|
1254ec2849 | ||
|
|
ca144bae90 | ||
|
|
850368b529 | ||
|
|
7bbdfc5553 | ||
|
|
ba73cb1b75 | ||
|
|
eca36cb868 | ||
|
|
2a14473e8c | ||
|
|
9880a425f4 | ||
|
|
13696b1ec4 | ||
|
|
764eb960c6 | ||
|
|
17b55fc23d | ||
|
|
e20c994b82 | ||
|
|
465cd3d1f9 | ||
|
|
412351fd1e | ||
|
|
5776e70d7c | ||
|
|
1ccc6e342c | ||
|
|
a53481e2da | ||
|
|
01b1b688b1 | ||
|
|
3d78248aaf | ||
|
|
cf703f6ac4 | ||
|
|
2012c769f6 | ||
|
|
c8a4eb426c | ||
|
|
351ecf9bd4 | ||
|
|
e6f42fa6f0 | ||
|
|
582ac4ac81 | ||
|
|
6e30bacae3 | ||
|
|
5d136a18af | ||
|
|
1a47e4b524 | ||
|
|
c296b4c348 | ||
|
|
c52cb7bbad | ||
|
|
1923e0312b | ||
|
|
c8998941a5 | ||
|
|
e41ea42074 | ||
|
|
ed5f207eba | ||
|
|
a76b8e745b | ||
|
|
68d29c5af5 | ||
|
|
c3acf08c02 | ||
|
|
ef9e6e4d6e | ||
|
|
f3158c8b24 | ||
|
|
ac4a179703 | ||
|
|
dd4ea51d1f | ||
|
|
45f6926a5a | ||
|
|
5ca4bac10a | ||
|
|
ec026ab3a8 | ||
|
|
0e5e559283 | ||
|
|
417a3cdf51 | ||
|
|
7fa98e288f | ||
|
|
c693c219f4 | ||
|
|
e5d4e12457 | ||
|
|
33212d1abf | ||
|
|
7a16f846eb | ||
|
|
6873f09f57 | ||
|
|
382793de9a | ||
|
|
d4e76185bd | ||
|
|
8566dd9100 | ||
|
|
f479eae714 | ||
|
|
31067530a5 | ||
|
|
6505c30c2b | ||
|
|
7487ffcbcb | ||
|
|
f79299c240 | ||
|
|
2f07225984 | ||
|
|
c99b2b59c2 | ||
|
|
78633c5768 | ||
|
|
c041cc483c | ||
|
|
83a12d980e | ||
|
|
491f7e96f0 | ||
|
|
bfb9cb6732 | ||
|
|
3c9712d683 | ||
|
|
ca4107d450 | ||
|
|
148f5d9418 | ||
|
|
51ab0f0b78 | ||
|
|
bf0cce4ad8 | ||
|
|
82d5d50d61 | ||
|
|
ecb1c77f8b | ||
|
|
f9a8629157 | ||
|
|
e2b655a6cc | ||
|
|
04e6f475b4 | ||
|
|
0082c5b459 | ||
|
|
3fd130869e | ||
|
|
d000440927 | ||
|
|
4fb750de43 | ||
|
|
9632ac0e02 | ||
|
|
35078fd52f | ||
|
|
0bb81e5b2d | ||
|
|
35a2258f12 | ||
|
|
b650704877 | ||
|
|
0c0dec2534 | ||
|
|
d1b051a6bd | ||
|
|
27204aa53c | ||
|
|
8aedac81a5 | ||
|
|
42007d03d4 | ||
|
|
821c1a8bbd | ||
|
|
bab562dc3a | ||
|
|
f63fd9696f | ||
|
|
cd7af19e7c | ||
|
|
64bd33a94e | ||
|
|
60e6366521 | ||
|
|
072b2c445c | ||
|
|
219fe41831 | ||
|
|
dcc8bb83af | ||
|
|
a8e3521f3c | ||
|
|
706dc6d116 | ||
|
|
84accb6df6 | ||
|
|
8421570b18 | ||
|
|
d355543ac9 | ||
|
|
3a597c5aa6 | ||
|
|
c7dddaded4 | ||
|
|
aae4b2ea5d | ||
|
|
310e2a0b20 | ||
|
|
0b04d143ac | ||
|
|
d1f4c2ab57 | ||
|
|
7e4d12f880 | ||
|
|
ecd65003d4 | ||
|
|
fed37b48a1 | ||
|
|
3fba3a5e2e | ||
|
|
9e7e8ab116 | ||
|
|
1bec1faf6d | ||
|
|
e64246f642 | ||
|
|
fb2b7ade41 | ||
|
|
4de44a99eb | ||
|
|
39fbf9c56f | ||
|
|
021055f0b8 | ||
|
|
c2e0ea97d8 | ||
|
|
153aadadae | ||
|
|
1319ff1129 | ||
|
|
7df72ddb96 | ||
|
|
1d9ce2afc5 | ||
|
|
53986279d7 | ||
|
|
26d6738abb | ||
|
|
7fa5cab8e3 | ||
|
|
4f2e2fa297 | ||
|
|
0473c4c79f | ||
|
|
a62b6548d2 | ||
|
|
da390d32f8 | ||
|
|
8b92456ded | ||
|
|
14e9375262 | ||
|
|
d49ee47018 | ||
|
|
af66753c1b | ||
|
|
39b35b79ba | ||
|
|
a2a83c5004 | ||
|
|
ba1222eae4 | ||
|
|
31ae337931 | ||
|
|
bab0ba9c0f | ||
|
|
c9e224e999 | ||
|
|
6123cb7c69 | ||
|
|
8040e3cf95 | ||
|
|
d447548893 | ||
|
|
269812e781 | ||
|
|
65f4d30fd0 | ||
|
|
c1dfed5c08 | ||
|
|
835079ad43 | ||
|
|
17fd9d5107 | ||
|
|
8613c02d5c | ||
|
|
dea6675c21 | ||
|
|
43cf3063e0 | ||
|
|
3b7a47fb90 | ||
|
|
79248e8b74 | ||
|
|
4620ad6124 | ||
|
|
25cdbacecc | ||
|
|
4ec636c08f | ||
|
|
4cb30a22ac | ||
|
|
c632b0e1d4 | ||
|
|
714d28a61a | ||
|
|
c60989a7be | ||
|
|
11b727fdf7 | ||
|
|
a1dfd355f7 | ||
|
|
fcb2cc2471 | ||
|
|
177617e6e3 | ||
|
|
e0b4226930 | ||
|
|
426e6a1b46 | ||
|
|
66083c5e97 | ||
|
|
aff4f1e9e2 | ||
|
|
3c68348868 | ||
|
|
7f2a6e7403 | ||
|
|
11069085e3 | ||
|
|
854d735ab3 | ||
|
|
a4ab52918b | ||
|
|
eb895d2095 | ||
|
|
67cbaabd99 | ||
|
|
4402a6eb4c | ||
|
|
6ae1efcf9f | ||
|
|
1d136ab0df | ||
|
|
7721049ed7 | ||
|
|
e6f21873c3 | ||
|
|
499903bd3d | ||
|
|
2d0d794a9d | ||
|
|
a55787f40c | ||
|
|
990a4d4774 | ||
|
|
6a60f01753 | ||
|
|
30ecb58e06 | ||
|
|
3b689ef39c | ||
|
|
170d52e0db | ||
|
|
92d93d658c | ||
|
|
d7a2816c58 | ||
|
|
a30d2f291c | ||
|
|
d33a158585 | ||
|
|
e21dbc4b60 | ||
|
|
8a754421fe | ||
|
|
a73fd55fc2 | ||
|
|
45630d74f3 | ||
|
|
a6d31f05ee | ||
|
|
05f9dede70 | ||
|
|
7c870556c6 | ||
|
|
420c860424 | ||
|
|
828e291538 | ||
|
|
eea78531a1 | ||
|
|
c8ccb06f11 | ||
|
|
f5b7cc81d8 | ||
|
|
ae784dc74c | ||
|
|
056c72d50d | ||
|
|
b5714cd70f | ||
|
|
74aca2137b | ||
|
|
d09dff3ae3 | ||
|
|
d280380c8d | ||
|
|
8a08d1fb5d | ||
|
|
ea652e3587 | ||
|
|
7a6df38515 | ||
|
|
bba6d6897d | ||
|
|
33c08812cc | ||
|
|
f68a3a9334 | ||
|
|
0698be3995 | ||
|
|
064589934c | ||
|
|
e9e92afc9e | ||
|
|
e86f2e993f | ||
|
|
d26cd85306 | ||
|
|
73f80a8ea1 | ||
|
|
b7dff4bbab | ||
|
|
31d964c16a | ||
|
|
7f895abc24 | ||
|
|
0f406c38eb | ||
|
|
6433da13a3 | ||
|
|
fa1adfd934 | ||
|
|
36ffef083b | ||
|
|
fe89dcdc08 | ||
|
|
be36eef939 | ||
|
|
6a0268b852 | ||
|
|
b7b23ffdb2 | ||
|
|
ad76709d00 | ||
|
|
53c231a7eb | ||
|
|
d44ce82aa1 | ||
|
|
a055de48e4 | ||
|
|
37b8d665fe | ||
|
|
dd7c8dabb1 | ||
|
|
e41a9875e3 | ||
|
|
c5c42c4338 | ||
|
|
531428b8b0 | ||
|
|
ea8068e001 | ||
|
|
7842a55c81 | ||
|
|
51d39862b1 | ||
|
|
bfea6ca79b | ||
|
|
6297395018 | ||
|
|
a5b49dbfa6 | ||
|
|
7c0d777173 | ||
|
|
74878276fc | ||
|
|
226e3b1dad | ||
|
|
7752794fc5 | ||
|
|
b3094d6a53 | ||
|
|
e3640e710f | ||
|
|
2ef64b55c5 | ||
|
|
7f6672bb37 | ||
|
|
68a3b31628 | ||
|
|
1b35855e68 | ||
|
|
1e1837000d | ||
|
|
e2d5257632 | ||
|
|
387c75793b | ||
|
|
4f3a74d08a | ||
|
|
072cd5b83e | ||
|
|
cfd42ea162 | ||
|
|
b55544b860 | ||
|
|
5becaebdda | ||
|
|
1814e4a46b | ||
|
|
4f8f59f705 | ||
|
|
aca306d120 | ||
|
|
694395ac91 | ||
|
|
092bca0d63 | ||
|
|
a386bb476f | ||
|
|
39a520f552 | ||
|
|
663f84f8b4 | ||
|
|
8677d47777 | ||
|
|
4f1a28d460 | ||
|
|
7b142525b4 | ||
|
|
7d4f279206 | ||
|
|
51233e1931 | ||
|
|
907c14aa98 | ||
|
|
fb055750df | ||
|
|
fad05d5a2e | ||
|
|
9580b13b9f | ||
|
|
367ae906c3 | ||
|
|
f8d98ac494 | ||
|
|
3e8fd48dc0 | ||
|
|
003326f2eb | ||
|
|
b5af3aa048 | ||
|
|
a919b015b4 | ||
|
|
f94e9b6b1e | ||
|
|
1ed8e63d59 | ||
|
|
d97bc95798 | ||
|
|
5c56f15c67 | ||
|
|
3fdb68cba8 | ||
|
|
85c46becdf | ||
|
|
0cbd373817 | ||
|
|
fdbc59a159 | ||
|
|
0db37bb55c | ||
|
|
8027facb39 | ||
|
|
2ff2750628 | ||
|
|
16c2dc2aaf | ||
|
|
eae5c17b87 | ||
|
|
a59cde91ad | ||
|
|
1f243ae37e | ||
|
|
603f82977e | ||
|
|
2d70422a6f | ||
|
|
e2c8b21195 | ||
|
|
7adaeacd0b | ||
|
|
dc2279b74f | ||
|
|
75275c4e93 | ||
|
|
65d3dc9cb8 | ||
|
|
66aa02fc34 | ||
|
|
be6b4ee47f | ||
|
|
90f909d2ea | ||
|
|
3aaa92fdff | ||
|
|
5efd076c08 | ||
|
|
09fd505f08 | ||
|
|
042ccde441 | ||
|
|
442030b6ca | ||
|
|
1df9ae53f8 | ||
|
|
5f535e9756 | ||
|
|
70faeb2fa8 | ||
|
|
469c0db981 | ||
|
|
440e428aa4 | ||
|
|
dde70c95a4 | ||
|
|
09d1846261 | ||
|
|
34d26a517d | ||
|
|
d24b88271c | ||
|
|
f22115792a | ||
|
|
82a30558e1 | ||
|
|
847fe5adca | ||
|
|
775b51c6a1 | ||
|
|
e0ad5a9009 | ||
|
|
1bf01a9081 | ||
|
|
6ae59bb43d | ||
|
|
adf2a463fd | ||
|
|
80aaf66963 | ||
|
|
560251ab2a | ||
|
|
864c5d8908 | ||
|
|
742c21506c | ||
|
|
a6faccb4d9 | ||
|
|
69fd3e8937 | ||
|
|
41233d7f25 | ||
|
|
07286d1d76 | ||
|
|
08148c5830 | ||
|
|
969bdb06ce | ||
|
|
b0bb692af4 | ||
|
|
7bf6fd316f | ||
|
|
c1f5e04d6c | ||
|
|
5a67e72389 | ||
|
|
91c9b11647 | ||
|
|
929600d7f7 | ||
|
|
163d0c55ab | ||
|
|
327ccb241e | ||
|
|
6b3c7b0854 | ||
|
|
681dcb51da | ||
|
|
576d5021fd | ||
|
|
6cd76f00ac | ||
|
|
6f63a62a8d | ||
|
|
8867a0fcfb | ||
|
|
bb2582717f | ||
|
|
d62ef35860 | ||
|
|
59c5956f93 | ||
|
|
e4f055597c | ||
|
|
4c49beb3c7 | ||
|
|
8ff742d9ab | ||
|
|
d63cd8b4cd | ||
|
|
42b4a166ec | ||
|
|
c27fd0f01a | ||
|
|
dcb4a0a81e | ||
|
|
17da9fddc3 | ||
|
|
31aa3c55ca | ||
|
|
eca3685ea0 | ||
|
|
bd216c5c63 | ||
|
|
31ff76427c | ||
|
|
3f0503c296 | ||
|
|
c18050bda0 | ||
|
|
6542be5588 | ||
|
|
9fb60b8015 | ||
|
|
1177b856a0 | ||
|
|
c0adaa8de8 | ||
|
|
ae8700447e | ||
|
|
d64a4ef2b4 | ||
|
|
2229aa6ccc | ||
|
|
872b468415 | ||
|
|
9f022a7433 | ||
|
|
85a958e300 | ||
|
|
0ee56195ae | ||
|
|
48f52db1d9 | ||
|
|
d2c7afeef0 | ||
|
|
644aec791e | ||
|
|
b70a0325c5 | ||
|
|
268387f829 | ||
|
|
b975caef1e | ||
|
|
54fe1c7d55 | ||
|
|
89c1274d56 | ||
|
|
f9ca3f1c27 | ||
|
|
26dbc30279 | ||
|
|
4bee316425 | ||
|
|
1e22b1e959 | ||
|
|
e077ad56bd | ||
|
|
f1e00f8c8e | ||
|
|
9a152e588e | ||
|
|
16f42a3d03 | ||
|
|
6c8d0f1852 | ||
|
|
b59cf6572b | ||
|
|
4fa11dfa68 | ||
|
|
352bdd9fb5 | ||
|
|
96ff9a162c | ||
|
|
af15a4e710 | ||
|
|
18426b71e4 | ||
|
|
7063aa6009 | ||
|
|
286ca07cc8 | ||
|
|
58b6311821 | ||
|
|
e553c0768e | ||
|
|
62d4b29662 | ||
|
|
0759140dc2 | ||
|
|
a943bc6c80 | ||
|
|
16bc60644d | ||
|
|
347393b864 | ||
|
|
bf6b11222a | ||
|
|
823ae7d1aa | ||
|
|
28031cfa3e | ||
|
|
292c2d0c53 | ||
|
|
869775ec7a | ||
|
|
783b179af7 | ||
|
|
28454ea4cd | ||
|
|
9f4b666ef0 | ||
|
|
80214640b1 | ||
|
|
4310c14497 | ||
|
|
aebb6d2fcc | ||
|
|
af35c9258e | ||
|
|
7a43231c43 | ||
|
|
1bf55c130b | ||
|
|
95a74a7f19 | ||
|
|
b78b28ea0e | ||
|
|
aed7b3fbb2 | ||
|
|
1ade7bcb2d | ||
|
|
21bbafb63d | ||
|
|
1cfc6ac3c6 | ||
|
|
c3aa834d80 | ||
|
|
68d0d045c0 | ||
|
|
72d6471ab8 | ||
|
|
22aecdfc6f | ||
|
|
ee0b6e835f | ||
|
|
8292024306 | ||
|
|
84cfcf2b4a | ||
|
|
ea762b7295 | ||
|
|
d51c0f13c0 | ||
|
|
6ceb975a3a | ||
|
|
9a40d6ef50 | ||
|
|
32195f77d9 | ||
|
|
578e5a0d7a | ||
|
|
1242f43769 | ||
|
|
3bb6430495 | ||
|
|
aae633277f | ||
|
|
996c50e8f2 | ||
|
|
95c883ae9b | ||
|
|
35a725fa1e | ||
|
|
78c1adafcd | ||
|
|
e15071228e | ||
|
|
ac48ff1fd6 | ||
|
|
428684bc1e | ||
|
|
81b7653c9c | ||
|
|
45736707bd | ||
|
|
cdfbe5b523 | ||
|
|
cdb9c59662 | ||
|
|
9c30f4cc68 | ||
|
|
acf3f6fb65 | ||
|
|
23f99908db | ||
|
|
a0046a2e55 | ||
|
|
e30512931b | ||
|
|
e207c6ad84 | ||
|
|
9d7f76773d | ||
|
|
5f2808ec2f | ||
|
|
be91cfb261 | ||
|
|
0eadda77b0 | ||
|
|
b2388b6fe7 | ||
|
|
1a763ae974 | ||
|
|
38dfab11b4 | ||
|
|
7c31592850 | ||
|
|
57bee74225 | ||
|
|
fa351cd37c | ||
|
|
68e7e5a51c | ||
|
|
4d31ad3bdc | ||
|
|
f4f1164b94 | ||
|
|
d4e0e1518a | ||
|
|
bd0be41064 | ||
|
|
4118a289a6 | ||
|
|
1d5f8d5a52 | ||
|
|
fd1dc24ac6 | ||
|
|
be1e4c0a1d | ||
|
|
c2028f7378 | ||
|
|
4b0f203049 | ||
|
|
23ff8178a0 | ||
|
|
93cfee8026 | ||
|
|
b6920025b2 | ||
|
|
fb29ac27a2 | ||
|
|
4c03cebef3 | ||
|
|
244c4be8cc | ||
|
|
9b28c732c6 | ||
|
|
5dfb33ebee | ||
|
|
2b30cde125 | ||
|
|
f9b3e61c0f | ||
|
|
83a92f03fc | ||
|
|
d27291b997 | ||
|
|
2c995cf145 | ||
|
|
2822fa4a40 | ||
|
|
ccf3da2a5a | ||
|
|
5348b36a7c | ||
|
|
947a6034e3 | ||
|
|
65d08beaa4 | ||
|
|
9770bc371b | ||
|
|
22f9f75914 | ||
|
|
54c9dd4173 | ||
|
|
0fc267dfc7 | ||
|
|
c5db457700 | ||
|
|
15a7d2ef75 | ||
|
|
071272a27f | ||
|
|
655327a8b1 | ||
|
|
15b87af8ed | ||
|
|
a0b3d861fe | ||
|
|
718c494013 | ||
|
|
5c9755ecc1 | ||
|
|
11e88019c2 | ||
|
|
a783637a7a | ||
|
|
7210ad7ed9 | ||
|
|
1876c21e3e | ||
|
|
6516a6ff7e | ||
|
|
85195436c1 | ||
|
|
c6512013bb | ||
|
|
81a070d03d | ||
|
|
0ef1d178d2 | ||
|
|
762f1b1fc9 | ||
|
|
7ad593d674 | ||
|
|
13522c8f19 | ||
|
|
d2938e82db | ||
|
|
f95d4ca106 | ||
|
|
486bafd009 | ||
|
|
341c99b4fa | ||
|
|
83095e8989 | ||
|
|
71ba4bc31c | ||
|
|
894ec07cc8 | ||
|
|
59091100e4 | ||
|
|
e5485ab650 | ||
|
|
6c493d10d2 | ||
|
|
840f599631 | ||
|
|
5a76e61b1e | ||
|
|
7b4366bfef | ||
|
|
8dee5c5fe8 | ||
|
|
b2e6d222cd | ||
|
|
2712c44004 | ||
|
|
82625a3080 | ||
|
|
49f9ad66db | ||
|
|
0dfab4d93c | ||
|
|
5cd7f23065 | ||
|
|
27453afa4e | ||
|
|
369d175694 | ||
|
|
fc465d6d93 | ||
|
|
904a0b26ea | ||
|
|
c13f132399 | ||
|
|
db968bc6b0 | ||
|
|
7abe8875bd | ||
|
|
dc9f304d94 | ||
|
|
1ca241615d | ||
|
|
b8aa84002a | ||
|
|
10cc0b1d5b | ||
|
|
11d9c203c1 | ||
|
|
c9ab454c3c | ||
|
|
4a55692885 | ||
|
|
88c129e705 | ||
|
|
a09bd80636 | ||
|
|
237ecb3adf | ||
|
|
9d65b77f13 | ||
|
|
97f2becc9e | ||
|
|
80b48f01fb | ||
|
|
f4160c363b | ||
|
|
4fee9cc039 | ||
|
|
36f47ade70 | ||
|
|
8db6f3129c | ||
|
|
75630a36f8 | ||
|
|
642bc91a76 | ||
|
|
d69926ee56 | ||
|
|
4758403d44 | ||
|
|
4b0ec5c28a | ||
|
|
4b2a9e5e49 | ||
|
|
1449c51d49 | ||
|
|
a451705e0b | ||
|
|
2e6db39173 | ||
|
|
373f75253c | ||
|
|
724842084e | ||
|
|
8f3635b167 | ||
|
|
11605a36f7 | ||
|
|
533f81d625 | ||
|
|
aacb9e44e8 | ||
|
|
c6e3f1bca6 | ||
|
|
a933d4aeb6 | ||
|
|
d2be58ba31 | ||
|
|
bbeb0461c4 | ||
|
|
14fd08e225 | ||
|
|
f99352f7e0 | ||
|
|
b51cbc4207 | ||
|
|
7a895adec9 | ||
|
|
4fe0c95ccb | ||
|
|
726b0e73d9 | ||
|
|
88ccd60a08 | ||
|
|
e6c16e9981 | ||
|
|
1bd408937a | ||
|
|
4d00dfd308 | ||
|
|
75326d2271 | ||
|
|
76fe2e4871 | ||
|
|
f977e9da2b | ||
|
|
16ae46e958 | ||
|
|
73eea154d5 | ||
|
|
0d36e66125 | ||
|
|
970838ed09 | ||
|
|
0a21816a5a | ||
|
|
30a542e763 | ||
|
|
ebe64e24f1 | ||
|
|
caa5b20791 | ||
|
|
e2ad9ed746 | ||
|
|
32c0e7c2ae | ||
|
|
6c564c7b7f | ||
|
|
c81e3a3be4 | ||
|
|
6b1b9ef7ec | ||
|
|
c26a8b8718 | ||
|
|
4a89a475bd | ||
|
|
c53483a3b2 | ||
|
|
fe24745815 | ||
|
|
b5e75793e1 | ||
|
|
734cc989de | ||
|
|
2642750466 | ||
|
|
ec9cc72320 | ||
|
|
c97a9d83c6 | ||
|
|
8cf15c7f5c | ||
|
|
adc76ca1b8 | ||
|
|
8f8892440c | ||
|
|
570843150d | ||
|
|
f3fc9e4142 | ||
|
|
075fcb77a8 | ||
|
|
e5899ff717 | ||
|
|
2fb3970027 | ||
|
|
1a4efa1b8c | ||
|
|
f31c1480f3 | ||
|
|
291d4be772 | ||
|
|
52584ec2be | ||
|
|
3bc08e5222 | ||
|
|
672f8d1719 | ||
|
|
420c8b49e2 | ||
|
|
f921997ee6 | ||
|
|
4e520d13dd | ||
|
|
72f656ffef | ||
|
|
b60239d5e5 | ||
|
|
d02e280c3c | ||
|
|
6535b0966e | ||
|
|
82dbacbee5 | ||
|
|
2432901974 | ||
|
|
ebb5d58c14 | ||
|
|
605e365405 | ||
|
|
2617e5092b | ||
|
|
d41ddf380c | ||
|
|
a72c3ea9d7 | ||
|
|
8be733efee | ||
|
|
b9609286ea | ||
|
|
2b186fdb0d | ||
|
|
3012fee013 | ||
|
|
01db114724 | ||
|
|
e05688d639 | ||
|
|
5ab995d8ca | ||
|
|
4248741b11 | ||
|
|
4b8ecc7634 | ||
|
|
25d04c759c | ||
|
|
b4ec84030e | ||
|
|
925b030718 | ||
|
|
9eba789c32 | ||
|
|
3e6ae4afda | ||
|
|
27abb38ecb | ||
|
|
29e8761373 | ||
|
|
a04299c59e | ||
|
|
d7bf3c51d9 | ||
|
|
3ddf5a4ec7 | ||
|
|
f2d6817d8a | ||
|
|
31821e6309 | ||
|
|
1ce257c721 | ||
|
|
8dd971b25e | ||
|
|
7f507935b1 | ||
|
|
3b1ba27043 | ||
|
|
4b7c5aa05c | ||
|
|
5fca02c712 | ||
|
|
31ddd3f668 | ||
|
|
f35f6d2348 | ||
|
|
02d34a0238 | ||
|
|
e4bbb56f6b | ||
|
|
96d30e28d4 | ||
|
|
41b73ff892 | ||
|
|
afc4e45fb0 | ||
|
|
8778ddd5c5 | ||
|
|
3089ffa8e7 | ||
|
|
15cb0e4ff3 | ||
|
|
2f3c6d5b58 | ||
|
|
ead5e8d855 | ||
|
|
fd9a9ecc63 | ||
|
|
bbb8ea7ec2 | ||
|
|
667ed94e29 | ||
|
|
23f1798d20 | ||
|
|
6f3c126805 | ||
|
|
a9ae70cff1 | ||
|
|
d7a8c50c98 | ||
|
|
9e56318498 | ||
|
|
2570f2d6f2 | ||
|
|
928df2dcd1 | ||
|
|
2decb8115c | ||
|
|
9d26c16471 | ||
|
|
5893506528 | ||
|
|
df0d33c3cd | ||
|
|
359f54d3c1 | ||
|
|
68ce1b18c4 | ||
|
|
61ba2e0f35 | ||
|
|
76d7802650 | ||
|
|
9fa1a334e6 | ||
|
|
9be16916b7 | ||
|
|
4a5365f6a0 | ||
|
|
0ced5509fc | ||
|
|
bd6b9ff1da | ||
|
|
edee28acf0 | ||
|
|
127efe9a52 | ||
|
|
53e8b3ed3e | ||
|
|
a23ebead68 | ||
|
|
f39d459555 | ||
|
|
6e7d25ed42 | ||
|
|
ac0b095941 | ||
|
|
cda9bad233 | ||
|
|
41db8a1264 | ||
|
|
e7e785fd60 | ||
|
|
300d3a1f46 | ||
|
|
356554c08d | ||
|
|
ced28ad006 |
2
.coveragerc
Normal file
2
.coveragerc
Normal file
@@ -0,0 +1,2 @@
|
||||
[run]
|
||||
omit = esphome/components/*
|
||||
32
.devcontainer/devcontainer.json
Normal file
32
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "ESPHome Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "../docker/Dockerfile.dev",
|
||||
"postCreateCommand": "mkdir -p config && pip3 install -e .",
|
||||
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
|
||||
"appPort": 6052,
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"redhat.vscode-yaml"
|
||||
],
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"yaml.customTags": [
|
||||
"!secret scalar",
|
||||
"!lambda scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -25,3 +25,4 @@ indent_size = 2
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
quote_type = single
|
||||
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github:
|
||||
patreon: ottowinter
|
||||
open_collective:
|
||||
ko_fi:
|
||||
tidelift:
|
||||
custom: https://esphome.io/guides/supporters.html
|
||||
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Issue Tracker
|
||||
url: https://github.com/esphome/issues
|
||||
about: Please create bug reports in the dedicated issue tracker.
|
||||
- name: Feature Request Tracker
|
||||
url: https://github.com/esphome/feature-requests
|
||||
about: Please create feature requests in the dedicated feature request tracker.
|
||||
- name: Frequently Asked Question
|
||||
url: https://esphome.io/guides/faq.html
|
||||
about: Please view the FAQ for common questions and what to include in a bug report.
|
||||
|
||||
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,13 +1,47 @@
|
||||
## Description:
|
||||
# What does this implement/fix?
|
||||
|
||||
Quick description
|
||||
|
||||
**Related issue (if applicable):** fixes <link to issue>
|
||||
## Types of changes
|
||||
|
||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Configuration change (this will require users to update their yaml configuration files to keep working)
|
||||
|
||||
**Related issue or feature (if applicable):** fixes <link to issue>
|
||||
|
||||
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>
|
||||
|
||||
# Test Environment
|
||||
|
||||
- [ ] ESP32
|
||||
- [ ] ESP8266
|
||||
- [ ] Windows
|
||||
- [ ] Mac OS
|
||||
- [ ] Linux
|
||||
|
||||
## Example entry for `config.yaml`:
|
||||
<!--
|
||||
Supplying a configuration snippet, makes it easier for a maintainer to test
|
||||
your PR. Furthermore, for new integrations, it gives an impression of how
|
||||
the configuration would look like.
|
||||
Note: Remove this section if this PR does not have an example entry.
|
||||
-->
|
||||
|
||||
```yaml
|
||||
# Example config.yaml
|
||||
|
||||
```
|
||||
|
||||
# Explain your changes
|
||||
|
||||
Describe your changes here to communicate to the maintainers **why we should accept this pull request**.
|
||||
Very important to fill if no issue linked
|
||||
|
||||
## Checklist:
|
||||
- [ ] The code change is tested and works locally.
|
||||
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||
|
||||
|
||||
If user exposed functionality or configuration variables are added/changed:
|
||||
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
||||
|
||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
ignore:
|
||||
# Hypotehsis is only used for testing and is updated quite often
|
||||
- dependency-name: hypothesis
|
||||
36
.github/lock.yml
vendored
Normal file
36
.github/lock.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 7
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- keep-open
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: false
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: false
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
59
.github/stale.yml
vendored
Normal file
59
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- not-stale
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 10
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: pulls
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
||||
55
.github/workflows/ci-docker.yml
vendored
Normal file
55
.github/workflows/ci-docker.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: CI for docker images
|
||||
|
||||
# Only run when docker paths change
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, master]
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
|
||||
jobs:
|
||||
check-docker:
|
||||
name: Build docker containers
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.0.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=ci" \
|
||||
--cache-from "${BUILD_TO}:dev" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
160
.github/workflows/ci.yml
vendored
Normal file
160
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
|
||||
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
# On dev branch release-dev already performs CI checks
|
||||
# On other branches the `pull_request` trigger will be used
|
||||
branches: [beta, master]
|
||||
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
16
.github/workflows/matchers/ci-custom.json
vendored
Normal file
16
.github/workflows/matchers/ci-custom.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "ci-custom",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
.github/workflows/matchers/clang-tidy.json
vendored
Normal file
17
.github/workflows/matchers/clang-tidy.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "clang-tidy",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.github/workflows/matchers/gcc.json
vendored
Normal file
18
.github/workflows/matchers/gcc.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "gcc",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
28
.github/workflows/matchers/lint-python.json
vendored
Normal file
28
.github/workflows/matchers/lint-python.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "flake8",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"message": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "pylint",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"message": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.github/workflows/matchers/python.json
vendored
Normal file
18
.github/workflows/matchers/python.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "python",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^\\s*File\\s\\\"(.*)\\\",\\sline\\s(\\d+),\\sin\\s(.*)$",
|
||||
"file": 1,
|
||||
"line": 2
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s*raise\\s(.*)\\(\\'(.*)\\'\\)$",
|
||||
"message": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
246
.github/workflows/release-dev.yml
vendored
Normal file
246
.github/workflows/release-dev.yml
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
name: Publish dev releases to docker hub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
|
||||
|
||||
lint-clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
# Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly
|
||||
build_type: ["docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_SHA:0:7}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.0.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=${TAG}" \
|
||||
--tag "${BUILD_TO}:${TAG}" \
|
||||
--tag "${BUILD_TO}:dev" \
|
||||
--cache-from "${BUILD_TO}:dev" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- run: |
|
||||
docker push "${BUILD_TO}:${TAG}"
|
||||
docker push "${BUILD_TO}:dev"
|
||||
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_SHA:0:7}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- name: "Create the manifest"
|
||||
run: |
|
||||
docker manifest create esphome/esphome:${TAG} \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:${TAG}
|
||||
|
||||
docker manifest create esphome/esphome:dev \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:dev
|
||||
309
.github/workflows/release.yml
vendored
Normal file
309
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
name: Publish Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
|
||||
|
||||
lint-clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
|
||||
deploy-pypi:
|
||||
name: Build and publish to PyPi
|
||||
if: github.repository == 'esphome/esphome'
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Set up python environment
|
||||
run: |
|
||||
script/setup
|
||||
pip install setuptools wheel twine
|
||||
- name: Build
|
||||
run: python setup.py sdist bdist_wheel
|
||||
- name: Upload
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
||||
run: twine upload dist/*
|
||||
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.0.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
|
||||
cache_tag="beta"
|
||||
else
|
||||
cache_tag="latest"
|
||||
fi
|
||||
|
||||
# Set env variables so these values don't need to be calculated again
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=${TAG}" \
|
||||
--tag "${BUILD_TO}:${TAG}" \
|
||||
--cache-from "${BUILD_TO}:${CACHE_TAG}" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- run: docker push "${BUILD_TO}:${TAG}"
|
||||
|
||||
# Always publish to beta tag (also full releases)
|
||||
- name: Publish docker beta tag
|
||||
run: |
|
||||
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
|
||||
docker push "${BUILD_TO}:beta"
|
||||
|
||||
- if: ${{ !github.event.release.prerelease }}
|
||||
name: Publish docker latest tag
|
||||
run: |
|
||||
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
|
||||
docker push "${BUILD_TO}:latest"
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- name: "Create the manifest"
|
||||
run: |
|
||||
docker manifest create esphome/esphome:${TAG} \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:${TAG}
|
||||
|
||||
- name: Publish docker beta tag
|
||||
run: |
|
||||
docker manifest create esphome/esphome:beta \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:beta
|
||||
|
||||
- name: Publish docker latest tag
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
run: |
|
||||
docker manifest create esphome/esphome:latest \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:latest
|
||||
|
||||
deploy-hassio-repo:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
curl \
|
||||
-u ":$TOKEN" \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
|
||||
-d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}"
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -10,6 +10,9 @@ __pycache__/
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Intellij Idea
|
||||
.idea
|
||||
|
||||
# Hide some OS X stuff
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
@@ -48,8 +51,10 @@ htmlcov/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
.esphome
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
cov.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
@@ -75,7 +80,9 @@ venv.bak/
|
||||
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.vscode
|
||||
.pio
|
||||
.vscode/
|
||||
!.vscode/tasks.json
|
||||
CMakeListsPrivate.txt
|
||||
CMakeLists.txt
|
||||
|
||||
@@ -113,3 +120,4 @@ config/
|
||||
tests/build/
|
||||
tests/.esphome/
|
||||
/.temp-clang-tidy.cpp
|
||||
.pio/
|
||||
|
||||
342
.gitlab-ci.yml
342
.gitlab-ci.yml
@@ -1,342 +0,0 @@
|
||||
---
|
||||
# Based on https://gitlab.com/hassio-addons/addon-node-red/blob/master/.gitlab-ci.yml
|
||||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_HOST: tcp://docker:2375/
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- deploy
|
||||
|
||||
.lint: &lint
|
||||
image: esphome/esphome-base-amd64
|
||||
stage: lint
|
||||
before_script:
|
||||
- script/setup
|
||||
tags:
|
||||
- docker
|
||||
|
||||
.test: &test
|
||||
image: esphome/esphome-base-amd64
|
||||
stage: test
|
||||
before_script:
|
||||
- script/setup
|
||||
tags:
|
||||
- docker
|
||||
variables:
|
||||
TZ: UTC
|
||||
|
||||
.docker-base: &docker-base
|
||||
image: esphome/esphome-base-builder
|
||||
before_script:
|
||||
- docker info
|
||||
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
|
||||
script:
|
||||
- docker run --rm --privileged hassioaddons/qemu-user-static:latest
|
||||
- TAG="${CI_COMMIT_TAG#v}"
|
||||
- TAG="${TAG:-${CI_COMMIT_SHA:0:7}}"
|
||||
- echo "Tag ${TAG}"
|
||||
|
||||
- |
|
||||
if [[ "${IS_HASSIO}" == "YES" ]]; then
|
||||
BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.5.1
|
||||
BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH}
|
||||
DOCKERFILE=docker/Dockerfile.hassio
|
||||
else
|
||||
BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.5.1
|
||||
if [[ "${BUILD_ARCH}" == "amd64" ]]; then
|
||||
BUILD_TO=esphome/esphome
|
||||
else
|
||||
BUILD_TO=esphome/esphome-${BUILD_ARCH}
|
||||
fi
|
||||
DOCKERFILE=docker/Dockerfile
|
||||
fi
|
||||
|
||||
- |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=${TAG}" \
|
||||
--tag "${BUILD_TO}:${TAG}" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- |
|
||||
if [[ "${RELEASE}" = "YES" ]]; then
|
||||
echo "Pushing to ${BUILD_TO}:${TAG}"
|
||||
docker push "${BUILD_TO}:${TAG}"
|
||||
fi
|
||||
- |
|
||||
if [[ "${LATEST}" = "YES" ]]; then
|
||||
echo "Pushing to :latest"
|
||||
docker tag ${BUILD_TO}:${TAG} ${BUILD_TO}:latest
|
||||
docker push ${BUILD_TO}:latest
|
||||
fi
|
||||
- |
|
||||
if [[ "${BETA}" = "YES" ]]; then
|
||||
echo "Pushing to :beta"
|
||||
docker tag \
|
||||
${BUILD_TO}:${TAG} \
|
||||
${BUILD_TO}:beta
|
||||
docker push ${BUILD_TO}:beta
|
||||
fi
|
||||
- |
|
||||
if [[ "${DEV}" = "YES" ]]; then
|
||||
echo "Pushing to :dev"
|
||||
docker tag \
|
||||
${BUILD_TO}:${TAG} \
|
||||
${BUILD_TO}:dev
|
||||
docker push ${BUILD_TO}:dev
|
||||
fi
|
||||
services:
|
||||
- docker:dind
|
||||
tags:
|
||||
- docker
|
||||
stage: deploy
|
||||
|
||||
lint-python:
|
||||
<<: *lint
|
||||
script:
|
||||
- script/ci-custom.py
|
||||
- script/lint-python
|
||||
|
||||
test1:
|
||||
<<: *test
|
||||
script:
|
||||
- esphome tests/test1.yaml compile
|
||||
|
||||
test2:
|
||||
<<: *test
|
||||
script:
|
||||
- esphome tests/test2.yaml compile
|
||||
|
||||
test3:
|
||||
<<: *test
|
||||
script:
|
||||
- esphome tests/test3.yaml compile
|
||||
|
||||
.deploy-pypi: &deploy-pypi
|
||||
stage: deploy
|
||||
image: python:2.7
|
||||
before_script:
|
||||
- pip install -e .
|
||||
- pip install twine
|
||||
script:
|
||||
- python setup.py sdist bdist_wheel
|
||||
- twine upload dist/*
|
||||
tags:
|
||||
- docker
|
||||
|
||||
deploy-release:pypi:
|
||||
<<: *deploy-pypi
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
except:
|
||||
- /^(?!master).+@/
|
||||
|
||||
deploy-beta:pypi:
|
||||
<<: *deploy-pypi
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+b\d+$/
|
||||
except:
|
||||
- /^(?!rc).+@/
|
||||
|
||||
.latest: &latest
|
||||
<<: *docker-base
|
||||
only:
|
||||
- /^v([0-9\.]+)$/
|
||||
except:
|
||||
- branches
|
||||
|
||||
.latest-vars: &latest-vars
|
||||
RELEASE: YES
|
||||
LATEST: YES
|
||||
# Also push to beta tag
|
||||
BETA: YES
|
||||
|
||||
.beta: &beta
|
||||
<<: *docker-base
|
||||
only:
|
||||
- /^v([0-9\.]+b\d+)$/
|
||||
except:
|
||||
- branches
|
||||
|
||||
.beta-vars: &beta-vars
|
||||
RELEASE: YES
|
||||
BETA: YES
|
||||
|
||||
.dev: &dev
|
||||
<<: *docker-base
|
||||
only:
|
||||
- dev
|
||||
|
||||
.dev-vars: &dev-vars
|
||||
DEV: YES
|
||||
|
||||
#aarch64-beta-docker:
|
||||
# <<: *beta
|
||||
# variables:
|
||||
# BETA: "YES"
|
||||
# BUILD_ARCH: aarch64
|
||||
# IS_HASSIO: "NO"
|
||||
# RELEASE: "YES"
|
||||
#aarch64-beta-hassio:
|
||||
# <<: *beta
|
||||
# variables:
|
||||
# BETA: "YES"
|
||||
# BUILD_ARCH: aarch64
|
||||
# IS_HASSIO: "YES"
|
||||
# RELEASE: "YES"
|
||||
#aarch64-dev-docker:
|
||||
# <<: *dev
|
||||
# variables:
|
||||
# BUILD_ARCH: aarch64
|
||||
# DEV: "YES"
|
||||
# IS_HASSIO: "NO"
|
||||
#aarch64-dev-hassio:
|
||||
# <<: *dev
|
||||
# variables:
|
||||
# BUILD_ARCH: aarch64
|
||||
# DEV: "YES"
|
||||
# IS_HASSIO: "YES"
|
||||
#aarch64-latest-docker:
|
||||
# <<: *latest
|
||||
# variables:
|
||||
# BETA: "YES"
|
||||
# BUILD_ARCH: aarch64
|
||||
# IS_HASSIO: "NO"
|
||||
# LATEST: "YES"
|
||||
# RELEASE: "YES"
|
||||
#aarch64-latest-hassio:
|
||||
# <<: *latest
|
||||
# variables:
|
||||
# BETA: "YES"
|
||||
# BUILD_ARCH: aarch64
|
||||
# IS_HASSIO: "YES"
|
||||
# LATEST: "YES"
|
||||
# RELEASE: "YES"
|
||||
amd64-beta-docker:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: amd64
|
||||
IS_HASSIO: "NO"
|
||||
RELEASE: "YES"
|
||||
amd64-beta-hassio:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: amd64
|
||||
IS_HASSIO: "YES"
|
||||
RELEASE: "YES"
|
||||
amd64-dev-docker:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: amd64
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "NO"
|
||||
amd64-dev-hassio:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: amd64
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "YES"
|
||||
amd64-latest-docker:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: amd64
|
||||
IS_HASSIO: "NO"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
amd64-latest-hassio:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: amd64
|
||||
IS_HASSIO: "YES"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
armhf-beta-docker:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: armhf
|
||||
IS_HASSIO: "NO"
|
||||
RELEASE: "YES"
|
||||
armhf-beta-hassio:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: armhf
|
||||
IS_HASSIO: "YES"
|
||||
RELEASE: "YES"
|
||||
armhf-dev-docker:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: armhf
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "NO"
|
||||
armhf-dev-hassio:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: armhf
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "YES"
|
||||
armhf-latest-docker:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: armhf
|
||||
IS_HASSIO: "NO"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
armhf-latest-hassio:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: armhf
|
||||
IS_HASSIO: "YES"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
i386-beta-docker:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: i386
|
||||
IS_HASSIO: "NO"
|
||||
RELEASE: "YES"
|
||||
i386-beta-hassio:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: i386
|
||||
IS_HASSIO: "YES"
|
||||
RELEASE: "YES"
|
||||
i386-dev-docker:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: i386
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "NO"
|
||||
i386-dev-hassio:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: i386
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "YES"
|
||||
i386-latest-docker:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: i386
|
||||
IS_HASSIO: "NO"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
i386-latest-hassio:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: i386
|
||||
IS_HASSIO: "YES"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
@@ -2,5 +2,5 @@ ports:
|
||||
- port: 6052
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- before: script/setup
|
||||
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
|
||||
command: python -m esphome config dashboard
|
||||
|
||||
27
.pre-commit-config.yaml
Normal file
27
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 20.8b1
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.8.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==5.1.1
|
||||
files: ^(esphome|tests)/.+\.py$
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.4.0
|
||||
hooks:
|
||||
- id: no-commit-to-branch
|
||||
args:
|
||||
- --branch=dev
|
||||
- --branch=master
|
||||
- --branch=beta
|
||||
56
.travis.yml
56
.travis.yml
@@ -1,56 +0,0 @@
|
||||
sudo: false
|
||||
language: python
|
||||
install: script/setup
|
||||
cache:
|
||||
directories:
|
||||
- "~/.platformio"
|
||||
- "$TRAVIS_BUILD_DIR/.piolibdeps"
|
||||
- "$TRAVIS_BUILD_DIR/tests/build/test1/.piolibdeps"
|
||||
- "$TRAVIS_BUILD_DIR/tests/build/test2/.piolibdeps"
|
||||
- "$TRAVIS_BUILD_DIR/tests/build/test3/.piolibdeps"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: "2.7"
|
||||
env: TARGET=Lint2.7
|
||||
script:
|
||||
- script/ci-custom.py
|
||||
- flake8 esphome
|
||||
- pylint esphome
|
||||
- python: "3.5.3"
|
||||
env: TARGET=Lint3.5
|
||||
script:
|
||||
- script/ci-custom.py
|
||||
- flake8 esphome
|
||||
- pylint esphome
|
||||
- python: "2.7"
|
||||
env: TARGET=Test2.7
|
||||
script:
|
||||
- esphome tests/test1.yaml compile
|
||||
- esphome tests/test2.yaml compile
|
||||
- esphome tests/test3.yaml compile
|
||||
- env: TARGET=Cpp-Lint
|
||||
dist: trusty
|
||||
sudo: required
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty-7
|
||||
packages:
|
||||
- clang-tidy-7
|
||||
- clang-format-7
|
||||
before_script:
|
||||
- pio init --ide atom
|
||||
- |
|
||||
if ! patch -R -p0 -s -f --dry-run <script/.neopixelbus.patch; then
|
||||
patch -p0 < script/.neopixelbus.patch
|
||||
fi
|
||||
- clang-tidy-7 -version
|
||||
- clang-format-7 -version
|
||||
- clang-apply-replacements-7 -version
|
||||
script:
|
||||
- script/clang-tidy --all-headers -j 2 --fix
|
||||
- script/clang-format -i -j 2
|
||||
- script/ci-suggest-changes
|
||||
11
.vscode/tasks.json
vendored
Normal file
11
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "run",
|
||||
"type": "shell",
|
||||
"command": "python3 -m esphome config dashboard",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
114
CODEOWNERS
Normal file
114
CODEOWNERS
Normal file
@@ -0,0 +1,114 @@
|
||||
# This file is generated by script/build_codeowners.py
|
||||
# People marked here will be automatically requested for a review
|
||||
# when the code that they own is touched.
|
||||
#
|
||||
# Every time an issue is created with a label corresponding to an integration,
|
||||
# the integration's code owner is automatically notified.
|
||||
|
||||
# Core Code
|
||||
setup.py @esphome/core
|
||||
esphome/*.py @esphome/core
|
||||
esphome/core/* @esphome/core
|
||||
|
||||
# Integrations
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/api/* @OttoWinter
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate6/* @jesserockz
|
||||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
esphome/components/mcp23017/* @jesserockz
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
||||
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
|
||||
esphome/components/mcp23x08_base/* @jesserockz
|
||||
esphome/components/mcp23x17_base/* @jesserockz
|
||||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/midea_ac/* @dudanov
|
||||
esphome/components/midea_dongle/* @dudanov
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nfc/* @jesserockz
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/pulse_meter/* @stevebaxter
|
||||
esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
esphome/components/ssd1325_base/* @kbx81
|
||||
esphome/components/ssd1325_spi/* @kbx81
|
||||
esphome/components/ssd1327_base/* @kbx81
|
||||
esphome/components/ssd1327_i2c/* @kbx81
|
||||
esphome/components/ssd1327_spi/* @kbx81
|
||||
esphome/components/ssd1331_base/* @kbx81
|
||||
esphome/components/ssd1331_spi/* @kbx81
|
||||
esphome/components/ssd1351_base/* @kbx81
|
||||
esphome/components/ssd1351_spi/* @kbx81
|
||||
esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
@@ -1,5 +1,7 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include requirements.txt
|
||||
include esphome/dashboard/templates/*.html
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||
recursive-include esphome *.cpp *.h *.tcc
|
||||
recursive-include esphome LICENSE.txt
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:1.5.1
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
COPY . .
|
||||
RUN pip2 install --no-cache-dir -e .
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Then copy esphome and install
|
||||
COPY . .
|
||||
RUN pip3 install --no-cache-dir -e .
|
||||
|
||||
# Settings for dashboard
|
||||
ENV USERNAME="" PASSWORD=""
|
||||
|
||||
# Expose the dashboard to Docker
|
||||
EXPOSE 6052
|
||||
|
||||
# Run healthcheck (heartbeat)
|
||||
HEALTHCHECK --interval=30s --timeout=30s \
|
||||
CMD curl --fail http://localhost:6052 || exit 1
|
||||
|
||||
# The directory the user should mount their configuration files to
|
||||
WORKDIR /config
|
||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
|
||||
# in every docker command twice
|
||||
ENTRYPOINT ["esphome"]
|
||||
# When no arguments given, start the dashboard in the workdir
|
||||
CMD ["/config", "dashboard"]
|
||||
|
||||
13
docker/Dockerfile.dev
Normal file
13
docker/Dockerfile.dev
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM esphome/esphome-base-amd64:3.0.0
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3-wheel \
|
||||
net-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /workspaces
|
||||
ENV SHELL /bin/bash
|
||||
@@ -1,12 +1,18 @@
|
||||
ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/rootfs/ /
|
||||
COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
|
||||
COPY esphome /opt/esphome/esphome
|
||||
|
||||
RUN pip2 install --no-cache-dir -e /opt/esphome
|
||||
# Then copy esphome and install
|
||||
COPY . /opt/esphome/
|
||||
RUN pip3 install --no-cache-dir -e /opt/esphome
|
||||
|
||||
# Build arguments
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
FROM python:2.7
|
||||
FROM esphome/esphome-lint-base:3.0.0
|
||||
|
||||
COPY requirements.txt /requirements.txt
|
||||
COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
RUN pip install -r /requirements.txt && \
|
||||
pip install flake8==3.6.0 pylint==1.9.4 pillow
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
FROM ubuntu:bionic
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python \
|
||||
python-pip \
|
||||
python-setuptools \
|
||||
python-pil \
|
||||
git \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
pip install --no-cache-dir platformio && \
|
||||
platformio settings set enable_telemetry No && \
|
||||
platformio settings set check_libraries_interval 1000000 && \
|
||||
platformio settings set check_platformio_interval 1000000 && \
|
||||
platformio settings set check_platforms_interval 1000000
|
||||
|
||||
COPY docker/platformio.ini /pio/platformio.ini
|
||||
RUN platformio run -d /pio; rm -rf /pio
|
||||
|
||||
COPY requirements.txt /requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir -r /requirements.txt
|
||||
@@ -1,12 +0,0 @@
|
||||
; This file allows the docker build file to install the required platformio
|
||||
; platforms
|
||||
|
||||
[env:espressif8266]
|
||||
platform = espressif8266@1.8.0
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
|
||||
[env:espressif32]
|
||||
platform = espressif32@1.5.0
|
||||
board = nodemcu-32s
|
||||
framework = arduino
|
||||
20
docker/platformio_install_deps.py
Executable file
20
docker/platformio_install_deps.py
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
# This script is used in the docker containers to preinstall
|
||||
# all platformio libraries in the global storage
|
||||
|
||||
import configparser
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(sys.argv[1])
|
||||
libs = []
|
||||
for line in config['common']['lib_deps'].splitlines():
|
||||
# Format: '1655@1.0.2 ; TinyGPSPlus (has name conflict)' (includes comment)
|
||||
m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line)
|
||||
if m is None:
|
||||
continue
|
||||
libs.append(m.group(1))
|
||||
|
||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
|
||||
12
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
12
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
@@ -8,8 +8,16 @@ declare esphome_version
|
||||
|
||||
if bashio::config.has_value 'esphome_version'; then
|
||||
esphome_version=$(bashio::config 'esphome_version')
|
||||
full_url="https://github.com/esphome/esphome/archive/${esphome_version}.zip"
|
||||
if [[ $esphome_version == *":"* ]]; then
|
||||
IFS=':' read -r -a array <<< "$esphome_version"
|
||||
username=${array[0]}
|
||||
ref=${array[1]}
|
||||
else
|
||||
username="esphome"
|
||||
ref=$esphome_version
|
||||
fi
|
||||
full_url="https://github.com/${username}/esphome/archive/${ref}.zip"
|
||||
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
|
||||
pip2 install -U --no-cache-dir "${full_url}" \
|
||||
pip3 install -U --no-cache-dir "${full_url}" \
|
||||
|| bashio::exit.nok "Failed installing esphome pinned version."
|
||||
fi
|
||||
|
||||
0
docker/rootfs/etc/cont-init.d/40-migrate.sh
Normal file → Executable file
0
docker/rootfs/etc/cont-init.d/40-migrate.sh
Normal file → Executable file
0
docker/rootfs/etc/nginx/nginx.conf
Executable file → Normal file
0
docker/rootfs/etc/nginx/nginx.conf
Executable file → Normal file
@@ -4,6 +4,11 @@ server {
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
include /etc/nginx/includes/ssl_params.conf;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /ssl/%%certfile%%;
|
||||
ssl_certificate_key /ssl/%%keyfile%%;
|
||||
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "";
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import logging
|
||||
@@ -10,42 +8,46 @@ from datetime import datetime
|
||||
from esphome import const, writer, yaml_util
|
||||
import esphome.codegen as cg
|
||||
from esphome.config import iter_components, read_config, strip_default_ids
|
||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
||||
CONF_PASSWORD, CONF_PORT
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_BROKER,
|
||||
CONF_LOGGER,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_ESPHOME,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||
from esphome.helpers import color, indent
|
||||
from esphome.py_compat import IS_PY2, safe_input
|
||||
from esphome.util import run_external_command, run_external_process, safe_print
|
||||
from esphome.util import (
|
||||
run_external_command,
|
||||
run_external_process,
|
||||
safe_print,
|
||||
list_yaml_files,
|
||||
get_serial_ports,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_serial_ports():
|
||||
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
||||
from serial.tools.list_ports import comports
|
||||
result = []
|
||||
for port, desc, info in comports():
|
||||
if not port:
|
||||
continue
|
||||
if "VID:PID" in info:
|
||||
result.append((port, desc))
|
||||
result.sort(key=lambda x: x[0])
|
||||
return result
|
||||
|
||||
|
||||
def choose_prompt(options):
|
||||
if not options:
|
||||
raise ValueError
|
||||
raise EsphomeError(
|
||||
"Found no valid options for upload/logging, please make sure relevant "
|
||||
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
||||
"device is plugged in."
|
||||
)
|
||||
|
||||
if len(options) == 1:
|
||||
return options[0][1]
|
||||
|
||||
safe_print(u"Found multiple options, please choose one:")
|
||||
safe_print("Found multiple options, please choose one:")
|
||||
for i, (desc, _) in enumerate(options):
|
||||
safe_print(u" [{}] {}".format(i + 1, desc))
|
||||
safe_print(f" [{i+1}] {desc}")
|
||||
|
||||
while True:
|
||||
opt = safe_input('(number): ')
|
||||
opt = input("(number): ")
|
||||
if opt in options:
|
||||
opt = options.index(opt)
|
||||
break
|
||||
@@ -55,22 +57,22 @@ def choose_prompt(options):
|
||||
raise ValueError
|
||||
break
|
||||
except ValueError:
|
||||
safe_print(color('red', u"Invalid option: '{}'".format(opt)))
|
||||
safe_print(color("red", f"Invalid option: '{opt}'"))
|
||||
return options[opt - 1][1]
|
||||
|
||||
|
||||
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
||||
options = []
|
||||
for res, desc in get_serial_ports():
|
||||
options.append((u"{} ({})".format(res, desc), res))
|
||||
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
|
||||
options.append((u"Over The Air ({})".format(CORE.address), CORE.address))
|
||||
if default == 'OTA':
|
||||
for port in get_serial_ports():
|
||||
options.append((f"{port.path} ({port.description})", port.path))
|
||||
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||
if default == "OTA":
|
||||
return CORE.address
|
||||
if show_mqtt and 'mqtt' in CORE.config:
|
||||
options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
||||
if default == 'OTA':
|
||||
return 'MQTT'
|
||||
if show_mqtt and "mqtt" in CORE.config:
|
||||
options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
|
||||
if default == "OTA":
|
||||
return "MQTT"
|
||||
if default is not None:
|
||||
return default
|
||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||
@@ -79,11 +81,11 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
|
||||
|
||||
|
||||
def get_port_type(port):
|
||||
if port.startswith('/') or port.startswith('COM'):
|
||||
return 'SERIAL'
|
||||
if port == 'MQTT':
|
||||
return 'MQTT'
|
||||
return 'NETWORK'
|
||||
if port.startswith("/") or port.startswith("COM"):
|
||||
return "SERIAL"
|
||||
if port == "MQTT":
|
||||
return "MQTT"
|
||||
return "NETWORK"
|
||||
|
||||
|
||||
def run_miniterm(config, port):
|
||||
@@ -93,7 +95,7 @@ def run_miniterm(config, port):
|
||||
if CONF_LOGGER not in config:
|
||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||
return
|
||||
baud_rate = config['logger'][CONF_BAUD_RATE]
|
||||
baud_rate = config["logger"][CONF_BAUD_RATE]
|
||||
if baud_rate == 0:
|
||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||
@@ -106,17 +108,18 @@ def run_miniterm(config, port):
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return
|
||||
if IS_PY2:
|
||||
line = raw.replace('\r', '').replace('\n', '')
|
||||
else:
|
||||
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8',
|
||||
'backslashreplace')
|
||||
time = datetime.now().time().strftime('[%H:%M:%S]')
|
||||
line = (
|
||||
raw.replace(b"\r", b"")
|
||||
.replace(b"\n", b"")
|
||||
.decode("utf8", "backslashreplace")
|
||||
)
|
||||
time = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
message = time + line
|
||||
safe_print(message)
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state)
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
|
||||
|
||||
def wrap_to_code(name, comp):
|
||||
@@ -125,15 +128,22 @@ def wrap_to_code(name, comp):
|
||||
@functools.wraps(comp.to_code)
|
||||
@coroutine_with_priority(coro.priority)
|
||||
def wrapped(conf):
|
||||
cg.add(cg.LineComment(u"{}:".format(name)))
|
||||
cg.add(cg.LineComment(f"{name}:"))
|
||||
if comp.config_schema is not None:
|
||||
cg.add(cg.LineComment(indent(yaml_util.dump(conf).decode('utf-8'))))
|
||||
conf_str = yaml_util.dump(conf)
|
||||
conf_str = conf_str.replace("//", "")
|
||||
cg.add(cg.LineComment(indent(conf_str)))
|
||||
yield coro(conf)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def write_cpp(config):
|
||||
generate_cpp_contents(config)
|
||||
return write_cpp_file()
|
||||
|
||||
|
||||
def generate_cpp_contents(config):
|
||||
_LOGGER.info("Generating C++ source...")
|
||||
|
||||
for name, component, conf in iter_components(CORE.config):
|
||||
@@ -143,6 +153,8 @@ def write_cpp(config):
|
||||
|
||||
CORE.flush_tasks()
|
||||
|
||||
|
||||
def write_cpp_file():
|
||||
writer.write_platformio_project()
|
||||
|
||||
code_s = indent(CORE.cpp_main_section)
|
||||
@@ -154,54 +166,91 @@ def compile_program(args, config):
|
||||
from esphome import platformio_api
|
||||
|
||||
_LOGGER.info("Compiling app...")
|
||||
return platformio_api.run_compile(config, args.verbose)
|
||||
return platformio_api.run_compile(config, CORE.verbose)
|
||||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
path = CORE.firmware_bin
|
||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
||||
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||
"upload_speed", 460800
|
||||
)
|
||||
|
||||
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
|
||||
import esptool
|
||||
# pylint: disable=protected-access
|
||||
return run_external_command(esptool._main, *cmd)
|
||||
def run_esptool(baud_rate):
|
||||
cmd = [
|
||||
"esptool.py",
|
||||
"--before",
|
||||
"default_reset",
|
||||
"--after",
|
||||
"hard_reset",
|
||||
"--baud",
|
||||
str(baud_rate),
|
||||
"--chip",
|
||||
"esp8266",
|
||||
"--port",
|
||||
port,
|
||||
"write_flash",
|
||||
"0x0",
|
||||
path,
|
||||
]
|
||||
|
||||
return run_external_process(*cmd)
|
||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||
import esptool
|
||||
|
||||
# pylint: disable=protected-access
|
||||
return run_external_command(esptool._main, *cmd)
|
||||
|
||||
return run_external_process(*cmd)
|
||||
|
||||
rc = run_esptool(first_baudrate)
|
||||
if rc == 0 or first_baudrate == 115200:
|
||||
return rc
|
||||
# Try with 115200 baud rate, with some serial chips the faster baud rates do not work well
|
||||
_LOGGER.info(
|
||||
"Upload with baud rate %s failed. Trying again with baud rate 115200.",
|
||||
first_baudrate,
|
||||
)
|
||||
return run_esptool(115200)
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
# if upload is to a serial port use platformio, otherwise assume ota
|
||||
if get_port_type(host) == 'SERIAL':
|
||||
if get_port_type(host) == "SERIAL":
|
||||
from esphome import platformio_api
|
||||
|
||||
if CORE.is_esp8266:
|
||||
return upload_using_esptool(config, host)
|
||||
return platformio_api.run_upload(config, args.verbose, host)
|
||||
return platformio_api.run_upload(config, CORE.verbose, host)
|
||||
|
||||
from esphome import espota2
|
||||
|
||||
if CONF_OTA not in config:
|
||||
raise EsphomeError(
|
||||
"Cannot upload Over the Air as the config does not include the ota: "
|
||||
"component"
|
||||
)
|
||||
|
||||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
password = ota_conf[CONF_PASSWORD]
|
||||
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
return res
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
|
||||
|
||||
def show_logs(config, args, port):
|
||||
if 'logger' not in config:
|
||||
if "logger" not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
if get_port_type(port) == 'SERIAL':
|
||||
if get_port_type(port) == "SERIAL":
|
||||
run_miniterm(config, port)
|
||||
return 0
|
||||
if get_port_type(port) == 'NETWORK' and 'api' in config:
|
||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||
from esphome.api.client import run_logs
|
||||
|
||||
return run_logs(config, port)
|
||||
if get_port_type(port) == 'MQTT' and 'mqtt' in config:
|
||||
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
|
||||
return mqtt.show_logs(
|
||||
config, args.topic, args.username, args.password, args.client_id
|
||||
)
|
||||
|
||||
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||
|
||||
@@ -209,37 +258,47 @@ def show_logs(config, args, port):
|
||||
def clean_mqtt(config, args):
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
|
||||
return mqtt.clear_topic(
|
||||
config, args.topic, args.username, args.password, args.client_id
|
||||
)
|
||||
|
||||
|
||||
def setup_log(debug=False, quiet=False):
|
||||
if debug:
|
||||
log_level = logging.DEBUG
|
||||
CORE.verbose = True
|
||||
elif quiet:
|
||||
log_level = logging.CRITICAL
|
||||
else:
|
||||
log_level = logging.INFO
|
||||
logging.basicConfig(level=log_level)
|
||||
fmt = "%(levelname)s %(message)s"
|
||||
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
|
||||
datefmt = '%H:%M:%S'
|
||||
colorfmt = f"%(log_color)s{fmt}%(reset)s"
|
||||
datefmt = "%H:%M:%S"
|
||||
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
|
||||
try:
|
||||
import colorama
|
||||
|
||||
colorama.init(strip=True)
|
||||
|
||||
from colorlog import ColoredFormatter
|
||||
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
|
||||
colorfmt,
|
||||
datefmt=datefmt,
|
||||
reset=True,
|
||||
log_colors={
|
||||
'DEBUG': 'cyan',
|
||||
'INFO': 'green',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red',
|
||||
}
|
||||
))
|
||||
|
||||
logging.getLogger().handlers[0].setFormatter(
|
||||
ColoredFormatter(
|
||||
colorfmt,
|
||||
datefmt=datefmt,
|
||||
reset=True,
|
||||
log_colors={
|
||||
"DEBUG": "cyan",
|
||||
"INFO": "green",
|
||||
"WARNING": "yellow",
|
||||
"ERROR": "red",
|
||||
"CRITICAL": "red",
|
||||
},
|
||||
)
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -247,12 +306,12 @@ def setup_log(debug=False, quiet=False):
|
||||
def command_wizard(args):
|
||||
from esphome import wizard
|
||||
|
||||
return wizard.wizard(args.configuration)
|
||||
return wizard.wizard(args.configuration[0])
|
||||
|
||||
|
||||
def command_config(args, config):
|
||||
_LOGGER.info("Configuration is valid!")
|
||||
if not args.verbose:
|
||||
if not CORE.verbose:
|
||||
config = strip_default_ids(config)
|
||||
safe_print(yaml_util.dump(config))
|
||||
return 0
|
||||
@@ -261,7 +320,9 @@ def command_config(args, config):
|
||||
def command_vscode(args):
|
||||
from esphome import vscode
|
||||
|
||||
CORE.config_path = args.configuration
|
||||
logging.disable(logging.INFO)
|
||||
logging.disable(logging.WARNING)
|
||||
CORE.config_path = args.configuration[0]
|
||||
vscode.read_config(args)
|
||||
|
||||
|
||||
@@ -270,28 +331,38 @@ def command_compile(args, config):
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
if args.only_generate:
|
||||
_LOGGER.info(u"Successfully generated source code.")
|
||||
_LOGGER.info("Successfully generated source code.")
|
||||
return 0
|
||||
exit_code = compile_program(args, config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully compiled program.")
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
return 0
|
||||
|
||||
|
||||
def command_upload(args, config):
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=False)
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
show_mqtt=False,
|
||||
show_api=False,
|
||||
)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully uploaded program.")
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
return 0
|
||||
|
||||
|
||||
def command_logs(args, config):
|
||||
port = choose_upload_log_host(default=args.serial_port, check_default=None,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
port = choose_upload_log_host(
|
||||
default=args.serial_port,
|
||||
check_default=None,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
show_api=True,
|
||||
)
|
||||
return show_logs(config, args, port)
|
||||
|
||||
|
||||
@@ -302,17 +373,27 @@ def command_run(args, config):
|
||||
exit_code = compile_program(args, config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully compiled program.")
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=True)
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
show_mqtt=False,
|
||||
show_api=True,
|
||||
)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully uploaded program.")
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
if args.no_logs:
|
||||
return 0
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
check_default=port,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
show_api=True,
|
||||
)
|
||||
return show_logs(config, args, port)
|
||||
|
||||
|
||||
@@ -327,7 +408,7 @@ def command_mqtt_fingerprint(args, config):
|
||||
|
||||
|
||||
def command_version(args):
|
||||
safe_print(u"Version: {}".format(const.__version__))
|
||||
safe_print(f"Version: {const.__version__}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -347,101 +428,203 @@ def command_dashboard(args):
|
||||
return dashboard.start_web_server(args)
|
||||
|
||||
|
||||
def command_update_all(args):
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration[0])
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
middle_text = f" {middle_text} "
|
||||
width = len(click.unstyle(middle_text))
|
||||
half_line = "=" * ((twidth - width) // 2)
|
||||
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||
|
||||
for f in files:
|
||||
print("Updating {}".format(color("cyan", f)))
|
||||
print("-" * twidth)
|
||||
print()
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA"
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color("bold_green", "SUCCESS"), f))
|
||||
success[f] = True
|
||||
else:
|
||||
print_bar("[{}] {}".format(color("bold_red", "ERROR"), f))
|
||||
success[f] = False
|
||||
|
||||
print()
|
||||
print()
|
||||
print()
|
||||
|
||||
print_bar("[{}]".format(color("bold_white", "SUMMARY")))
|
||||
failed = 0
|
||||
for f in files:
|
||||
if success[f]:
|
||||
print(" - {}: {}".format(f, color("green", "SUCCESS")))
|
||||
else:
|
||||
print(" - {}: {}".format(f, color("bold_red", "FAILED")))
|
||||
failed += 1
|
||||
return failed
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
'wizard': command_wizard,
|
||||
'version': command_version,
|
||||
'dashboard': command_dashboard,
|
||||
'vscode': command_vscode,
|
||||
"wizard": command_wizard,
|
||||
"version": command_version,
|
||||
"dashboard": command_dashboard,
|
||||
"vscode": command_vscode,
|
||||
"update-all": command_update_all,
|
||||
}
|
||||
|
||||
POST_CONFIG_ACTIONS = {
|
||||
'config': command_config,
|
||||
'compile': command_compile,
|
||||
'upload': command_upload,
|
||||
'logs': command_logs,
|
||||
'run': command_run,
|
||||
'clean-mqtt': command_clean_mqtt,
|
||||
'mqtt-fingerprint': command_mqtt_fingerprint,
|
||||
'clean': command_clean,
|
||||
"config": command_config,
|
||||
"compile": command_compile,
|
||||
"upload": command_upload,
|
||||
"logs": command_logs,
|
||||
"run": command_run,
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||
"clean": command_clean,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(prog='esphome')
|
||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
||||
parser.add_argument('configuration', help='Your YAML configuration file.')
|
||||
parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}")
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", help="Enable verbose esphome logs.", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q", "--quiet", help="Disable all esphome logs.", action="store_true"
|
||||
)
|
||||
parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true")
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--substitution",
|
||||
nargs=2,
|
||||
action="append",
|
||||
help="Add a substitution",
|
||||
metavar=("key", "value"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"configuration", help="Your YAML configuration file.", nargs="*"
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||
subparsers = parser.add_subparsers(help="Commands", dest="command")
|
||||
subparsers.required = True
|
||||
subparsers.add_parser('config', help='Validate the configuration and spit it out.')
|
||||
subparsers.add_parser("config", help="Validate the configuration and spit it out.")
|
||||
|
||||
parser_compile = subparsers.add_parser('compile',
|
||||
help='Read the configuration and compile a program.')
|
||||
parser_compile.add_argument('--only-generate',
|
||||
help="Only generate source code, do not compile.",
|
||||
action='store_true')
|
||||
parser_compile = subparsers.add_parser(
|
||||
"compile", help="Read the configuration and compile a program."
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"--only-generate",
|
||||
help="Only generate source code, do not compile.",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
|
||||
'and upload the latest binary.')
|
||||
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
parser_upload = subparsers.add_parser(
|
||||
"upload", help="Validate the configuration " "and upload the latest binary."
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--upload-port",
|
||||
help="Manually specify the upload port to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
)
|
||||
|
||||
parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
|
||||
'and show all MQTT logs.')
|
||||
parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
||||
parser_logs.add_argument('--username', help='Manually set the username.')
|
||||
parser_logs.add_argument('--password', help='Manually set the password.')
|
||||
parser_logs.add_argument('--client-id', help='Manually set the client id.')
|
||||
parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
parser_logs = subparsers.add_parser(
|
||||
"logs", help="Validate the configuration " "and show all MQTT logs."
|
||||
)
|
||||
parser_logs.add_argument("--topic", help="Manually set the topic to subscribe to.")
|
||||
parser_logs.add_argument("--username", help="Manually set the username.")
|
||||
parser_logs.add_argument("--password", help="Manually set the password.")
|
||||
parser_logs.add_argument("--client-id", help="Manually set the client id.")
|
||||
parser_logs.add_argument(
|
||||
"--serial-port",
|
||||
help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
)
|
||||
|
||||
parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
|
||||
'upload it, and start MQTT logs.')
|
||||
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
|
||||
action='store_true')
|
||||
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
||||
parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
|
||||
parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
|
||||
parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
|
||||
parser_run = subparsers.add_parser(
|
||||
"run",
|
||||
help="Validate the configuration, create a binary, "
|
||||
"upload it, and start MQTT logs.",
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--upload-port",
|
||||
help="Manually specify the upload port/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--no-logs", help="Disable starting MQTT logs.", action="store_true"
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--topic", help="Manually set the topic to subscribe to for logs."
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--username", help="Manually set the MQTT username for logs."
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--password", help="Manually set the MQTT password for logs."
|
||||
)
|
||||
parser_run.add_argument("--client-id", help="Manually set the client id for logs.")
|
||||
|
||||
parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
|
||||
"retain messages.")
|
||||
parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
||||
parser_clean.add_argument('--username', help='Manually set the username.')
|
||||
parser_clean.add_argument('--password', help='Manually set the password.')
|
||||
parser_clean.add_argument('--client-id', help='Manually set the client id.')
|
||||
parser_clean = subparsers.add_parser(
|
||||
"clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages."
|
||||
)
|
||||
parser_clean.add_argument("--topic", help="Manually set the topic to subscribe to.")
|
||||
parser_clean.add_argument("--username", help="Manually set the username.")
|
||||
parser_clean.add_argument("--password", help="Manually set the password.")
|
||||
parser_clean.add_argument("--client-id", help="Manually set the client id.")
|
||||
|
||||
subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.")
|
||||
subparsers.add_parser(
|
||||
"wizard",
|
||||
help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.",
|
||||
)
|
||||
|
||||
subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
|
||||
subparsers.add_parser(
|
||||
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
|
||||
)
|
||||
|
||||
subparsers.add_parser('version', help="Print the esphome version and exit.")
|
||||
subparsers.add_parser("version", help="Print the esphome version and exit.")
|
||||
|
||||
subparsers.add_parser('clean', help="Delete all temporary build files.")
|
||||
subparsers.add_parser("clean", help="Delete all temporary build files.")
|
||||
|
||||
dashboard = subparsers.add_parser('dashboard',
|
||||
help="Create a simple web server for a dashboard.")
|
||||
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int, default=6052)
|
||||
dashboard.add_argument("--password", help="The optional password to require for all requests.",
|
||||
type=str, default='')
|
||||
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
||||
action='store_true')
|
||||
dashboard.add_argument("--hassio",
|
||||
help=argparse.SUPPRESS,
|
||||
action="store_true")
|
||||
dashboard.add_argument("--socket",
|
||||
help="Make the dashboard serve under a unix socket", type=str)
|
||||
dashboard = subparsers.add_parser(
|
||||
"dashboard", help="Create a simple web server for a dashboard."
|
||||
)
|
||||
dashboard.add_argument(
|
||||
"--port",
|
||||
help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int,
|
||||
default=6052,
|
||||
)
|
||||
dashboard.add_argument(
|
||||
"--username",
|
||||
help="The optional username to require " "for authentication.",
|
||||
type=str,
|
||||
default="",
|
||||
)
|
||||
dashboard.add_argument(
|
||||
"--password",
|
||||
help="The optional password to require " "for authentication.",
|
||||
type=str,
|
||||
default="",
|
||||
)
|
||||
dashboard.add_argument(
|
||||
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
|
||||
)
|
||||
dashboard.add_argument("--hassio", help=argparse.SUPPRESS, action="store_true")
|
||||
dashboard.add_argument(
|
||||
"--socket", help="Make the dashboard serve under a unix socket", type=str
|
||||
)
|
||||
|
||||
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
||||
vscode.add_argument('--ace', action='store_true')
|
||||
vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS)
|
||||
vscode.add_argument("--ace", action="store_true")
|
||||
|
||||
subparsers.add_parser("update-all", help=argparse.SUPPRESS)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
|
||||
@@ -451,6 +634,17 @@ def run_esphome(argv):
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
setup_log(args.verbose, args.quiet)
|
||||
if args.command != "version" and not args.configuration:
|
||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||
return 1
|
||||
|
||||
if sys.version_info < (3, 6, 0):
|
||||
_LOGGER.error(
|
||||
"You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
|
||||
"with this Python version. Please reinstall ESPHome with Python 3.6+"
|
||||
)
|
||||
return 1
|
||||
|
||||
if args.command in PRE_CONFIG_ACTIONS:
|
||||
try:
|
||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||
@@ -458,21 +652,28 @@ def run_esphome(argv):
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
|
||||
CORE.config_path = args.configuration
|
||||
for conf_path in args.configuration:
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
config = read_config(args.verbose)
|
||||
if config is None:
|
||||
return 1
|
||||
CORE.config = config
|
||||
config = read_config(dict(args.substitution) if args.substitution else {})
|
||||
if config is None:
|
||||
return 1
|
||||
CORE.config = config
|
||||
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(f"Unknown command {args.command}")
|
||||
|
||||
if args.command in POST_CONFIG_ACTIONS:
|
||||
try:
|
||||
return POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
safe_print(u"Unknown command {}".format(args.command))
|
||||
return 1
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
CORE.reset()
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
// The Home Assistant protocol is structured as a simple
|
||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
||||
// First, a message in this protocol has a specific format:
|
||||
// * VarInt denoting the size of the message object. (type is not part of this)
|
||||
// * VarInt denoting the type of message.
|
||||
// * The message object encoded as a ProtoBuf message
|
||||
|
||||
// The connection is established in 4 steps:
|
||||
// * First, the client connects to the server and sends a "Hello Request" identifying itself
|
||||
// * The server responds with a "Hello Response" and selects the protocol version
|
||||
// * After receiving this message, the client attempts to authenticate itself using
|
||||
// the password and a "Connect Request"
|
||||
// * The server responds with a "Connect Response" and notifies of invalid password.
|
||||
// If anything in this initial process fails, the connection must immediately closed
|
||||
// by both sides and _no_ disconnection message is to be sent.
|
||||
|
||||
// Message sent at the beginning of each connection
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message HelloRequest {
|
||||
// Description of client (like User Agent)
|
||||
// For example "Home Assistant"
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message HelloResponse {
|
||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
||||
// for compatibility and if necessary adopt to an older API.
|
||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
||||
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
|
||||
uint32 api_version_major = 1;
|
||||
uint32 api_version_minor = 2;
|
||||
|
||||
// A string identifying the server (ESP); like client info this may be empty
|
||||
// and only exists for debugging/logging purposes.
|
||||
// For example "ESPHome v1.10.0 on ESP8266"
|
||||
string server_info = 3;
|
||||
}
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message ConnectRequest {
|
||||
// The password to log in with
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message ConnectResponse {
|
||||
bool invalid_password = 1;
|
||||
}
|
||||
|
||||
// Request to close the connection.
|
||||
// Can be sent by both the client and server
|
||||
message DisconnectRequest {
|
||||
// Do not close the connection before the acknowledgement arrives
|
||||
}
|
||||
|
||||
message DisconnectResponse {
|
||||
// Empty - Both parties are required to close the connection after this
|
||||
// message has been received.
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message PingResponse {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoResponse {
|
||||
bool uses_password = 1;
|
||||
|
||||
// The name of the node, given by "App.set_name()"
|
||||
string name = 2;
|
||||
|
||||
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
|
||||
string mac_address = 3;
|
||||
|
||||
// A string describing the ESPHome version. For example "1.10.0"
|
||||
string esphome_core_version = 4;
|
||||
|
||||
// A string describing the date of compilation, this is generated by the compiler
|
||||
// and therefore may not be in the same format all the time.
|
||||
// If the user isn't using esphome, this will also not be set.
|
||||
string compilation_time = 5;
|
||||
|
||||
// The model of the board. For example NodeMCU
|
||||
string model = 6;
|
||||
|
||||
bool has_deep_sleep = 7;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
}
|
||||
message ListEntitiesCoverResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool is_optimistic = 5;
|
||||
}
|
||||
message ListEntitiesFanResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
}
|
||||
message ListEntitiesLightResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_brightness = 5;
|
||||
bool supports_rgb = 6;
|
||||
bool supports_white_value = 7;
|
||||
bool supports_color_temperature = 8;
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
}
|
||||
message ListEntitiesSensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
}
|
||||
message ListEntitiesSwitchResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool optimistic = 6;
|
||||
}
|
||||
message ListEntitiesTextSensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
}
|
||||
message ListEntitiesDoneResponse {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message SubscribeStatesRequest {
|
||||
// Empty
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
message CoverStateResponse {
|
||||
fixed32 key = 1;
|
||||
enum CoverState {
|
||||
OPEN = 0;
|
||||
CLOSED = 1;
|
||||
}
|
||||
CoverState state = 2;
|
||||
}
|
||||
enum FanSpeed {
|
||||
LOW = 0;
|
||||
MEDIUM = 1;
|
||||
HIGH = 2;
|
||||
}
|
||||
message FanStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
}
|
||||
message LightStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
float white = 7;
|
||||
float color_temperature = 8;
|
||||
string effect = 9;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
float state = 2;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
message CoverCommandRequest {
|
||||
fixed32 key = 1;
|
||||
enum CoverCommand {
|
||||
OPEN = 0;
|
||||
CLOSE = 1;
|
||||
STOP = 2;
|
||||
}
|
||||
bool has_state = 2;
|
||||
CoverCommand command = 3;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
}
|
||||
message LightCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_rgb = 6;
|
||||
float red = 7;
|
||||
float green = 8;
|
||||
float blue = 9;
|
||||
bool has_white = 10;
|
||||
float white = 11;
|
||||
bool has_color_temperature = 12;
|
||||
float color_temperature = 13;
|
||||
bool has_transition_length = 14;
|
||||
uint32 transition_length = 15;
|
||||
bool has_flash_length = 16;
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19;
|
||||
}
|
||||
message SwitchCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
enum LogLevel {
|
||||
NONE = 0;
|
||||
ERROR = 1;
|
||||
WARN = 2;
|
||||
INFO = 3;
|
||||
DEBUG = 4;
|
||||
VERBOSE = 5;
|
||||
VERY_VERBOSE = 6;
|
||||
}
|
||||
|
||||
message SubscribeLogsRequest {
|
||||
LogLevel level = 1;
|
||||
bool dump_config = 2;
|
||||
}
|
||||
|
||||
message SubscribeLogsResponse {
|
||||
LogLevel level = 1;
|
||||
string tag = 2;
|
||||
string message = 3;
|
||||
bool send_failed = 4;
|
||||
}
|
||||
|
||||
message SubscribeServiceCallsRequest {
|
||||
|
||||
}
|
||||
|
||||
message ServiceCallResponse {
|
||||
string service = 1;
|
||||
map<string, string> data = 2;
|
||||
map<string, string> data_template = 3;
|
||||
map<string, string> variables = 4;
|
||||
}
|
||||
|
||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
||||
message SubscribeHomeAssistantStatesRequest {
|
||||
|
||||
}
|
||||
|
||||
message SubscribeHomeAssistantStateResponse {
|
||||
string entity_id = 1;
|
||||
}
|
||||
|
||||
message HomeAssistantStateResponse {
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
message GetTimeRequest {
|
||||
|
||||
}
|
||||
|
||||
message GetTimeResponse {
|
||||
fixed32 epoch_seconds = 1;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -14,7 +14,6 @@ import esphome.api.api_pb2 as pb
|
||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import resolve_ip_address, indent, color
|
||||
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte, format_bytes
|
||||
from esphome.util import safe_print
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -67,16 +66,16 @@ MESSAGE_TYPE_TO_PROTO = {
|
||||
|
||||
def _varuint_to_bytes(value):
|
||||
if value <= 0x7F:
|
||||
return byte_to_bytes(value)
|
||||
return bytes([value])
|
||||
|
||||
ret = bytes()
|
||||
while value:
|
||||
temp = value & 0x7F
|
||||
value >>= 7
|
||||
if value:
|
||||
ret += byte_to_bytes(temp | 0x80)
|
||||
ret += bytes([temp | 0x80])
|
||||
else:
|
||||
ret += byte_to_bytes(temp)
|
||||
ret += bytes([temp])
|
||||
|
||||
return ret
|
||||
|
||||
@@ -84,8 +83,7 @@ def _varuint_to_bytes(value):
|
||||
def _bytes_to_varuint(value):
|
||||
result = 0
|
||||
bitpos = 0
|
||||
for c in value:
|
||||
val = char_to_byte(c)
|
||||
for val in value:
|
||||
result |= (val & 0x7F) << bitpos
|
||||
bitpos += 7
|
||||
if (val & 0x80) == 0:
|
||||
@@ -108,7 +106,6 @@ class APIClient(threading.Thread):
|
||||
self._message_handlers = []
|
||||
self._keepalive = 5
|
||||
self._ping_timer = None
|
||||
self._refresh_ping()
|
||||
|
||||
self.on_disconnect = None
|
||||
self.on_connect = None
|
||||
@@ -132,8 +129,8 @@ class APIClient(threading.Thread):
|
||||
if self._connected:
|
||||
try:
|
||||
self.ping()
|
||||
except APIConnectionError:
|
||||
self._fatal_error()
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error(err)
|
||||
else:
|
||||
self._refresh_ping()
|
||||
|
||||
@@ -175,16 +172,20 @@ class APIClient(threading.Thread):
|
||||
raise APIConnectionError("You need to call start() first!")
|
||||
|
||||
if self._connected:
|
||||
raise APIConnectionError("Already connected!")
|
||||
self.disconnect(on_disconnect=False)
|
||||
|
||||
try:
|
||||
ip = resolve_ip_address(self._address)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
self._address)
|
||||
_LOGGER.warning("(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)")
|
||||
raise APIConnectionError(err)
|
||||
_LOGGER.warning(
|
||||
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
self._address,
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)"
|
||||
)
|
||||
raise APIConnectionError(err) from err
|
||||
|
||||
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
@@ -192,30 +193,38 @@ class APIClient(threading.Thread):
|
||||
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
try:
|
||||
self._socket.connect((ip, self._port))
|
||||
except socket.error as err:
|
||||
self._fatal_error()
|
||||
raise APIConnectionError("Error connecting to {}: {}".format(ip, err))
|
||||
except OSError as err:
|
||||
err = APIConnectionError(f"Error connecting to {ip}: {err}")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
self._socket.settimeout(0.1)
|
||||
|
||||
self._socket_open_event.set()
|
||||
|
||||
hello = pb.HelloRequest()
|
||||
hello.client_info = 'ESPHome v{}'.format(const.__version__)
|
||||
hello.client_info = f"ESPHome v{const.__version__}"
|
||||
try:
|
||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error()
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
_LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
|
||||
resp.server_info, resp.api_version_major, resp.api_version_minor)
|
||||
_LOGGER.debug(
|
||||
"Successfully connected to %s ('%s' API=%s.%s)",
|
||||
self._address,
|
||||
resp.server_info,
|
||||
resp.api_version_major,
|
||||
resp.api_version_minor,
|
||||
)
|
||||
self._connected = True
|
||||
self._refresh_ping()
|
||||
if self.on_connect is not None:
|
||||
self.on_connect()
|
||||
|
||||
def _check_connected(self):
|
||||
if not self._connected:
|
||||
self._fatal_error()
|
||||
raise APIConnectionError("Must be connected!")
|
||||
err = APIConnectionError("Must be connected!")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
|
||||
def login(self):
|
||||
self._check_connected()
|
||||
@@ -233,25 +242,26 @@ class APIClient(threading.Thread):
|
||||
if self.on_login is not None:
|
||||
self.on_login()
|
||||
|
||||
def _fatal_error(self):
|
||||
def _fatal_error(self, err):
|
||||
was_connected = self._connected
|
||||
|
||||
self._close_socket()
|
||||
|
||||
if was_connected and self.on_disconnect is not None:
|
||||
self.on_disconnect()
|
||||
self.on_disconnect(err)
|
||||
|
||||
def _write(self, data): # type: (bytes) -> None
|
||||
if self._socket is None:
|
||||
raise APIConnectionError("Socket closed")
|
||||
|
||||
_LOGGER.debug("Write: %s", format_bytes(data))
|
||||
# _LOGGER.debug("Write: %s", format_bytes(data))
|
||||
with self._socket_write_lock:
|
||||
try:
|
||||
self._socket.sendall(data)
|
||||
except socket.error as err:
|
||||
self._fatal_error()
|
||||
raise APIConnectionError("Error while writing data: {}".format(err))
|
||||
except OSError as err:
|
||||
err = APIConnectionError(f"Error while writing data: {err}")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
|
||||
def _send_message(self, msg):
|
||||
# type: (message.Message) -> None
|
||||
@@ -262,18 +272,16 @@ class APIClient(threading.Thread):
|
||||
raise ValueError
|
||||
|
||||
encoded = msg.SerializeToString()
|
||||
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg)))
|
||||
if IS_PY2:
|
||||
req = chr(0x00)
|
||||
else:
|
||||
req = bytes([0])
|
||||
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
|
||||
req = bytes([0])
|
||||
req += _varuint_to_bytes(len(encoded))
|
||||
req += _varuint_to_bytes(message_type)
|
||||
req += encoded
|
||||
self._write(req)
|
||||
self._refresh_ping()
|
||||
|
||||
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=1):
|
||||
def _send_message_await_response_complex(
|
||||
self, send_msg, do_append, do_stop, timeout=5
|
||||
):
|
||||
event = threading.Event()
|
||||
responses = []
|
||||
|
||||
@@ -294,38 +302,43 @@ class APIClient(threading.Thread):
|
||||
raise APIConnectionError("Timeout while waiting for message response!")
|
||||
return responses
|
||||
|
||||
def _send_message_await_response(self, send_msg, response_type, timeout=1):
|
||||
def _send_message_await_response(self, send_msg, response_type, timeout=5):
|
||||
def is_response(msg):
|
||||
return isinstance(msg, response_type)
|
||||
|
||||
return self._send_message_await_response_complex(send_msg, is_response, is_response,
|
||||
timeout)[0]
|
||||
return self._send_message_await_response_complex(
|
||||
send_msg, is_response, is_response, timeout
|
||||
)[0]
|
||||
|
||||
def device_info(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(pb.DeviceInfoRequest(), pb.DeviceInfoResponse)
|
||||
return self._send_message_await_response(
|
||||
pb.DeviceInfoRequest(), pb.DeviceInfoResponse
|
||||
)
|
||||
|
||||
def ping(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
|
||||
|
||||
def disconnect(self):
|
||||
def disconnect(self, on_disconnect=True):
|
||||
self._check_connected()
|
||||
|
||||
try:
|
||||
self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
|
||||
self._send_message_await_response(
|
||||
pb.DisconnectRequest(), pb.DisconnectResponse
|
||||
)
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
if self.on_disconnect is not None:
|
||||
self.on_disconnect()
|
||||
if self.on_disconnect is not None and on_disconnect:
|
||||
self.on_disconnect(None)
|
||||
|
||||
def _check_authenticated(self):
|
||||
if not self._authenticated:
|
||||
raise APIConnectionError("Must login first!")
|
||||
|
||||
def subscribe_logs(self, on_log, log_level=None, dump_config=False):
|
||||
def subscribe_logs(self, on_log, log_level=7, dump_config=False):
|
||||
self._check_authenticated()
|
||||
|
||||
def on_msg(msg):
|
||||
@@ -334,8 +347,7 @@ class APIClient(threading.Thread):
|
||||
|
||||
self._message_handlers.append(on_msg)
|
||||
req = pb.SubscribeLogsRequest(dump_config=dump_config)
|
||||
if log_level is not None:
|
||||
req.level = log_level
|
||||
req.level = log_level
|
||||
self._send_message(req)
|
||||
|
||||
def _recv(self, amount):
|
||||
@@ -350,18 +362,18 @@ class APIClient(threading.Thread):
|
||||
raise APIConnectionError("No socket!")
|
||||
try:
|
||||
val = self._socket.recv(amount - len(ret))
|
||||
except AttributeError:
|
||||
raise APIConnectionError("Socket was closed")
|
||||
except AttributeError as err:
|
||||
raise APIConnectionError("Socket was closed") from err
|
||||
except socket.timeout:
|
||||
continue
|
||||
except socket.error as err:
|
||||
raise APIConnectionError("Error while receiving data: {}".format(err))
|
||||
except OSError as err:
|
||||
raise APIConnectionError(f"Error while receiving data: {err}") from err
|
||||
ret += val
|
||||
return ret
|
||||
|
||||
def _recv_varint(self):
|
||||
raw = bytes()
|
||||
while not raw or char_to_byte(raw[-1]) & 0x80:
|
||||
while not raw or raw[-1] & 0x80:
|
||||
raw += self._recv(1)
|
||||
return _bytes_to_varuint(raw)
|
||||
|
||||
@@ -370,7 +382,7 @@ class APIClient(threading.Thread):
|
||||
return
|
||||
|
||||
# Preamble
|
||||
if char_to_byte(self._recv(1)[0]) != 0x00:
|
||||
if self._recv(1)[0] != 0x00:
|
||||
raise APIConnectionError("Invalid preamble")
|
||||
|
||||
length = self._recv_varint()
|
||||
@@ -387,7 +399,6 @@ class APIClient(threading.Thread):
|
||||
for msg_handler in self._message_handlers[:]:
|
||||
msg_handler(msg)
|
||||
self._handle_internal_messages(msg)
|
||||
self._refresh_ping()
|
||||
|
||||
def run(self):
|
||||
self._running_event.set()
|
||||
@@ -399,7 +410,7 @@ class APIClient(threading.Thread):
|
||||
break
|
||||
if self._connected:
|
||||
_LOGGER.error("Error while reading incoming messages: %s", err)
|
||||
self._fatal_error()
|
||||
self._fatal_error(err)
|
||||
self._running_event.clear()
|
||||
|
||||
def _handle_internal_messages(self, msg):
|
||||
@@ -410,7 +421,7 @@ class APIClient(threading.Thread):
|
||||
self._socket = None
|
||||
self._connected = False
|
||||
if self.on_disconnect is not None:
|
||||
self.on_disconnect()
|
||||
self.on_disconnect(None)
|
||||
elif isinstance(msg, pb.PingRequest):
|
||||
self._send_message(pb.PingResponse())
|
||||
elif isinstance(msg, pb.GetTimeRequest):
|
||||
@@ -420,7 +431,7 @@ class APIClient(threading.Thread):
|
||||
|
||||
|
||||
def run_logs(config, address):
|
||||
conf = config['api']
|
||||
conf = config["api"]
|
||||
port = conf[CONF_PORT]
|
||||
password = conf[CONF_PASSWORD]
|
||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||
@@ -431,12 +442,12 @@ def run_logs(config, address):
|
||||
|
||||
has_connects = []
|
||||
|
||||
def try_connect(tries=0, is_disconnect=True):
|
||||
def try_connect(err, tries=0):
|
||||
if stopping:
|
||||
return
|
||||
|
||||
if is_disconnect:
|
||||
_LOGGER.warning(u"Disconnected from API.")
|
||||
if err:
|
||||
_LOGGER.warning("Disconnected from API: %s", err)
|
||||
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
@@ -445,31 +456,42 @@ def run_logs(config, address):
|
||||
try:
|
||||
cli.connect()
|
||||
cli.login()
|
||||
except APIConnectionError as err: # noqa
|
||||
error = err
|
||||
except APIConnectionError as err2: # noqa
|
||||
error = err2
|
||||
|
||||
if error is None:
|
||||
_LOGGER.info("Successfully connected to %s", address)
|
||||
return
|
||||
|
||||
wait_time = min(2**tries, 300)
|
||||
wait_time = int(min(1.5 ** min(tries, 100), 30))
|
||||
if not has_connects:
|
||||
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected "
|
||||
u"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error, wait_time)
|
||||
_LOGGER.warning(
|
||||
"Initial connection failed. The ESP might not be connected "
|
||||
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error,
|
||||
wait_time,
|
||||
)
|
||||
else:
|
||||
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error, wait_time)
|
||||
timer = threading.Timer(wait_time, functools.partial(try_connect, tries + 1, is_disconnect))
|
||||
_LOGGER.warning(
|
||||
"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error,
|
||||
wait_time,
|
||||
)
|
||||
timer = threading.Timer(
|
||||
wait_time, functools.partial(try_connect, None, tries + 1)
|
||||
)
|
||||
timer.start()
|
||||
retry_timer.append(timer)
|
||||
|
||||
def on_log(msg):
|
||||
time_ = datetime.now().time().strftime(u'[%H:%M:%S]')
|
||||
time_ = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
text = msg.message
|
||||
if msg.send_failed:
|
||||
text = color('white', '(Message skipped because it was too big to fit in '
|
||||
'TCP buffer - This is only cosmetic)')
|
||||
text = color(
|
||||
"white",
|
||||
"(Message skipped because it was too big to fit in "
|
||||
"TCP buffer - This is only cosmetic)",
|
||||
)
|
||||
safe_print(time_ + text)
|
||||
|
||||
def on_login():
|
||||
@@ -484,7 +506,7 @@ def run_logs(config, address):
|
||||
cli.start()
|
||||
|
||||
try:
|
||||
try_connect(is_disconnect=False)
|
||||
try_connect(None)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
|
||||
CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME
|
||||
from esphome.const import (
|
||||
CONF_AUTOMATION_ID,
|
||||
CONF_CONDITION,
|
||||
CONF_ELSE,
|
||||
CONF_ID,
|
||||
CONF_THEN,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE_ID,
|
||||
CONF_TIME,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
from esphome.jsonschema import jschema_extractor
|
||||
from esphome.util import Registry
|
||||
|
||||
|
||||
def maybe_simple_id(*validators):
|
||||
return maybe_conf(CONF_ID, *validators)
|
||||
|
||||
|
||||
def maybe_conf(conf, *validators):
|
||||
validator = cv.All(*validators)
|
||||
|
||||
@jschema_extractor("maybe")
|
||||
def validate(value):
|
||||
# pylint: disable=comparison-with-callable
|
||||
if value == jschema_extractor:
|
||||
return validator
|
||||
|
||||
if isinstance(value, dict):
|
||||
return validator(value)
|
||||
with cv.remove_prepend_path([CONF_ID]):
|
||||
return validator({CONF_ID: value})
|
||||
with cv.remove_prepend_path([conf]):
|
||||
return validator({conf: value})
|
||||
|
||||
return validate
|
||||
|
||||
@@ -26,36 +44,34 @@ def register_condition(name, condition_type, schema):
|
||||
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
||||
|
||||
|
||||
Action = cg.esphome_ns.class_('Action')
|
||||
Trigger = cg.esphome_ns.class_('Trigger')
|
||||
Action = cg.esphome_ns.class_("Action")
|
||||
Trigger = cg.esphome_ns.class_("Trigger")
|
||||
ACTION_REGISTRY = Registry()
|
||||
Condition = cg.esphome_ns.class_('Condition')
|
||||
Condition = cg.esphome_ns.class_("Condition")
|
||||
CONDITION_REGISTRY = Registry()
|
||||
validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY)
|
||||
validate_action_list = cv.validate_registry('action', ACTION_REGISTRY)
|
||||
validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY)
|
||||
validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY)
|
||||
validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY)
|
||||
validate_action_list = cv.validate_registry("action", ACTION_REGISTRY)
|
||||
validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY)
|
||||
validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY)
|
||||
|
||||
|
||||
def validate_potentially_and_condition(value):
|
||||
if isinstance(value, list):
|
||||
with cv.remove_prepend_path(['and']):
|
||||
return validate_condition({
|
||||
'and': value
|
||||
})
|
||||
with cv.remove_prepend_path(["and"]):
|
||||
return validate_condition({"and": value})
|
||||
return validate_condition(value)
|
||||
|
||||
|
||||
DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component)
|
||||
LambdaAction = cg.esphome_ns.class_('LambdaAction', Action)
|
||||
IfAction = cg.esphome_ns.class_('IfAction', Action)
|
||||
WhileAction = cg.esphome_ns.class_('WhileAction', Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
|
||||
Automation = cg.esphome_ns.class_('Automation')
|
||||
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
|
||||
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
||||
ForCondition = cg.esphome_ns.class_('ForCondition', Condition)
|
||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
||||
|
||||
|
||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
@@ -79,9 +95,10 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
try:
|
||||
return cv.Schema([schema])(value)
|
||||
except cv.Invalid as err2:
|
||||
if u'extra keys not allowed' in str(err2) and len(err2.path) == 2:
|
||||
if "extra keys not allowed" in str(err2) and len(err2.path) == 2:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise err
|
||||
if u'Unable to find action' in str(err):
|
||||
if "Unable to find action" in str(err):
|
||||
raise err2
|
||||
raise cv.MultipleInvalid([err, err2])
|
||||
elif isinstance(value, dict):
|
||||
@@ -92,7 +109,13 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
# This should only happen with invalid configs, but let's have a nice error message.
|
||||
return [schema(value)]
|
||||
|
||||
@jschema_extractor("automation")
|
||||
def validator(value):
|
||||
# hack to get the schema
|
||||
# pylint: disable=comparison-with-callable
|
||||
if value == jschema_extractor:
|
||||
return schema
|
||||
|
||||
value = validator_(value)
|
||||
if extra_validators is not None:
|
||||
value = cv.Schema([extra_validators])(value)
|
||||
@@ -105,47 +128,59 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
return validator
|
||||
|
||||
|
||||
AUTOMATION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
})
|
||||
AUTOMATION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}
|
||||
)
|
||||
|
||||
AndCondition = cg.esphome_ns.class_('AndCondition', Condition)
|
||||
OrCondition = cg.esphome_ns.class_('OrCondition', Condition)
|
||||
NotCondition = cg.esphome_ns.class_('NotCondition', Condition)
|
||||
AndCondition = cg.esphome_ns.class_("AndCondition", Condition)
|
||||
OrCondition = cg.esphome_ns.class_("OrCondition", Condition)
|
||||
NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
|
||||
|
||||
|
||||
@register_condition('and', AndCondition, validate_condition_list)
|
||||
@register_condition("and", AndCondition, validate_condition_list)
|
||||
def and_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition('or', OrCondition, validate_condition_list)
|
||||
@register_condition("or", OrCondition, validate_condition_list)
|
||||
def or_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition('not', NotCondition, validate_potentially_and_condition)
|
||||
@register_condition("not", NotCondition, validate_potentially_and_condition)
|
||||
def not_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
|
||||
|
||||
@register_condition('lambda', LambdaCondition, cv.lambda_)
|
||||
@register_condition("lambda", LambdaCondition, cv.lambda_)
|
||||
def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_condition('for', ForCondition, cv.Schema({
|
||||
cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
}).extend(cv.COMPONENT_SCHEMA))
|
||||
@register_condition(
|
||||
"for",
|
||||
ForCondition,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TIME): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
)
|
||||
def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), [])
|
||||
condition = yield build_condition(
|
||||
config[CONF_CONDITION], cg.TemplateArguments(), []
|
||||
)
|
||||
var = cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
yield cg.register_component(var, config)
|
||||
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||
@@ -153,7 +188,9 @@ def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds))
|
||||
@register_action(
|
||||
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
||||
)
|
||||
def delay_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_component(var, {})
|
||||
@@ -162,11 +199,18 @@ def delay_action_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('if', IfAction, cv.All({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_THEN): validate_action_list,
|
||||
cv.Optional(CONF_ELSE): validate_action_list,
|
||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE)))
|
||||
@register_action(
|
||||
"if",
|
||||
IfAction,
|
||||
cv.All(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_THEN): validate_action_list,
|
||||
cv.Optional(CONF_ELSE): validate_action_list,
|
||||
},
|
||||
cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
|
||||
),
|
||||
)
|
||||
def if_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
@@ -179,10 +223,16 @@ def if_action_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('while', WhileAction, cv.Schema({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}))
|
||||
@register_action(
|
||||
"while",
|
||||
WhileAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}
|
||||
),
|
||||
)
|
||||
def while_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
@@ -192,15 +242,17 @@ def while_action_to_code(config, action_id, template_arg, args):
|
||||
|
||||
|
||||
def validate_wait_until(value):
|
||||
schema = cv.Schema({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
})
|
||||
schema = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
}
|
||||
)
|
||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||
return schema(value)
|
||||
return validate_wait_until({CONF_CONDITION: value})
|
||||
|
||||
|
||||
@register_action('wait_until', WaitUntilAction, validate_wait_until)
|
||||
@register_action("wait_until", WaitUntilAction, validate_wait_until)
|
||||
def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
@@ -208,15 +260,21 @@ def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('lambda', LambdaAction, cv.lambda_)
|
||||
@register_action("lambda", LambdaAction, cv.lambda_)
|
||||
def lambda_action_to_code(config, action_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
|
||||
yield cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_action('component.update', UpdateComponentAction, maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||
}))
|
||||
@register_action(
|
||||
"component.update",
|
||||
UpdateComponentAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
def component_update_action_to_code(config, action_id, template_arg, args):
|
||||
comp = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, comp)
|
||||
@@ -224,7 +282,9 @@ def component_update_action_to_code(config, action_id, template_arg, args):
|
||||
|
||||
@coroutine
|
||||
def build_action(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(ACTION_REGISTRY, full_config)
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
ACTION_REGISTRY, full_config
|
||||
)
|
||||
action_id = full_config[CONF_TYPE_ID]
|
||||
builder = registry_entry.coroutine_fun
|
||||
yield builder(config, action_id, template_arg, args)
|
||||
@@ -241,7 +301,9 @@ def build_action_list(config, templ, arg_type):
|
||||
|
||||
@coroutine
|
||||
def build_condition(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(CONDITION_REGISTRY, full_config)
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
CONDITION_REGISTRY, full_config
|
||||
)
|
||||
action_id = full_config[CONF_TYPE_ID]
|
||||
builder = registry_entry.coroutine_fun
|
||||
yield builder(config, action_id, template_arg, args)
|
||||
|
||||
@@ -9,18 +9,71 @@
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from esphome.cpp_generator import ( # noqa
|
||||
Expression, RawExpression, RawStatement, TemplateArguments,
|
||||
StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment,
|
||||
progmem_array, statement, variable, Pvariable, new_Pvariable,
|
||||
add, add_global, add_library, add_build_flag, add_define,
|
||||
get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj,
|
||||
MockObjClass)
|
||||
Expression,
|
||||
RawExpression,
|
||||
RawStatement,
|
||||
TemplateArguments,
|
||||
StructInitializer,
|
||||
ArrayInitializer,
|
||||
safe_exp,
|
||||
Statement,
|
||||
LineComment,
|
||||
progmem_array,
|
||||
statement,
|
||||
variable,
|
||||
new_variable,
|
||||
Pvariable,
|
||||
new_Pvariable,
|
||||
add,
|
||||
add_global,
|
||||
add_library,
|
||||
add_build_flag,
|
||||
add_define,
|
||||
get_variable,
|
||||
get_variable_with_full_id,
|
||||
process_lambda,
|
||||
is_template,
|
||||
templatable,
|
||||
MockObj,
|
||||
MockObjClass,
|
||||
)
|
||||
from esphome.cpp_helpers import ( # noqa
|
||||
gpio_pin_expression, register_component, build_registry_entry,
|
||||
build_registry_list, extract_registry_entry_config, register_parented)
|
||||
gpio_pin_expression,
|
||||
register_component,
|
||||
build_registry_entry,
|
||||
build_registry_list,
|
||||
extract_registry_entry_config,
|
||||
register_parented,
|
||||
)
|
||||
from esphome.cpp_types import ( # noqa
|
||||
global_ns, void, nullptr, float_, double, bool_, std_ns, std_string,
|
||||
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
|
||||
esphome_ns, App, Nameable, Component, ComponentPtr,
|
||||
PollingComponent, Application, optional, arduino_json_ns, JsonObject,
|
||||
JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin)
|
||||
global_ns,
|
||||
void,
|
||||
nullptr,
|
||||
float_,
|
||||
double,
|
||||
bool_,
|
||||
int_,
|
||||
std_ns,
|
||||
std_string,
|
||||
std_vector,
|
||||
uint8,
|
||||
uint16,
|
||||
uint32,
|
||||
int32,
|
||||
const_char_ptr,
|
||||
NAN,
|
||||
esphome_ns,
|
||||
App,
|
||||
Nameable,
|
||||
Component,
|
||||
ComponentPtr,
|
||||
PollingComponent,
|
||||
Application,
|
||||
optional,
|
||||
arduino_json_ns,
|
||||
JsonObject,
|
||||
JsonObjectRef,
|
||||
JsonObjectConstRef,
|
||||
Controller,
|
||||
GPIOPin,
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ void A4988::setup() {
|
||||
if (this->sleep_pin_ != nullptr) {
|
||||
this->sleep_pin_->setup();
|
||||
this->sleep_pin_->digital_write(false);
|
||||
this->sleep_pin_state_ = false;
|
||||
}
|
||||
this->step_pin_->setup();
|
||||
this->step_pin_->digital_write(false);
|
||||
@@ -27,7 +28,12 @@ void A4988::dump_config() {
|
||||
void A4988::loop() {
|
||||
bool at_target = this->has_reached_target();
|
||||
if (this->sleep_pin_ != nullptr) {
|
||||
bool sleep_rising_edge = !sleep_pin_state_ & !at_target;
|
||||
this->sleep_pin_->digital_write(!at_target);
|
||||
this->sleep_pin_state_ = !at_target;
|
||||
if (sleep_rising_edge) {
|
||||
delayMicroseconds(1000);
|
||||
}
|
||||
}
|
||||
if (at_target) {
|
||||
this->high_freq_.stop();
|
||||
|
||||
@@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component {
|
||||
GPIOPin *step_pin_;
|
||||
GPIOPin *dir_pin_;
|
||||
GPIOPin *sleep_pin_{nullptr};
|
||||
bool sleep_pin_state_;
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,15 +5,17 @@ import esphome.codegen as cg
|
||||
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
|
||||
|
||||
|
||||
a4988_ns = cg.esphome_ns.namespace('a4988')
|
||||
A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component)
|
||||
a4988_ns = cg.esphome_ns.namespace("a4988")
|
||||
A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({
|
||||
cv.Required(CONF_ID): cv.declare_id(A4988),
|
||||
cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(A4988),
|
||||
cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
||||
0
esphome/components/ac_dimmer/__init__.py
Normal file
0
esphome/components/ac_dimmer/__init__.py
Normal file
217
esphome/components/ac_dimmer/ac_dimmer.cpp
Normal file
217
esphome/components/ac_dimmer/ac_dimmer.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "ac_dimmer.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include <core_esp8266_waveform.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
|
||||
static const char *TAG = "ac_dimmer";
|
||||
|
||||
// Global array to store dimmer objects
|
||||
static AcDimmerDataStore *all_dimmers[32];
|
||||
|
||||
/// Time in microseconds the gate should be held high
|
||||
/// 10µs should be long enough for most triacs
|
||||
/// For reference: BT136 datasheet says 2µs nominal (page 7)
|
||||
static uint32_t GATE_ENABLE_TIME = 10;
|
||||
|
||||
/// Function called from timer interrupt
|
||||
/// Input is current time in microseconds (micros())
|
||||
/// Returns when next "event" is expected in µs, or 0 if no such event known.
|
||||
uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
// If no ZC signal received yet.
|
||||
if (this->crossed_zero_at == 0)
|
||||
return 0;
|
||||
|
||||
uint32_t time_since_zc = now - this->crossed_zero_at;
|
||||
if (this->value == 65535 || this->value == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
|
||||
this->enable_time_us = 0;
|
||||
this->gate_pin->digital_write(true);
|
||||
// Prevent too short pulses
|
||||
this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
|
||||
}
|
||||
if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) {
|
||||
this->disable_time_us = 0;
|
||||
this->gate_pin->digital_write(false);
|
||||
}
|
||||
|
||||
if (time_since_zc < this->enable_time_us)
|
||||
// Next event is enable, return time until that event
|
||||
return this->enable_time_us - time_since_zc;
|
||||
else if (time_since_zc < disable_time_us) {
|
||||
// Next event is disable, return time until that event
|
||||
return this->disable_time_us - time_since_zc;
|
||||
}
|
||||
|
||||
if (time_since_zc >= this->cycle_time_us) {
|
||||
// Already past last cycle time, schedule next call shortly
|
||||
return 100;
|
||||
}
|
||||
|
||||
return this->cycle_time_us - time_since_zc;
|
||||
}
|
||||
|
||||
/// Run timer interrupt code and return in how many µs the next event is expected
|
||||
uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
|
||||
// run at least with 1kHz
|
||||
uint32_t min_dt_us = 1000;
|
||||
uint32_t now = micros();
|
||||
for (auto *dimmer : all_dimmers) {
|
||||
if (dimmer == nullptr)
|
||||
// no more dimmers
|
||||
break;
|
||||
uint32_t res = dimmer->timer_intr(now);
|
||||
if (res != 0 && res < min_dt_us)
|
||||
min_dt_us = res;
|
||||
}
|
||||
// return time until next timer1 interrupt in µs
|
||||
return min_dt_us;
|
||||
}
|
||||
|
||||
/// GPIO interrupt routine, called when ZC pin triggers
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
uint32_t prev_crossed = this->crossed_zero_at;
|
||||
|
||||
// 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms
|
||||
// in any case the cycle last at least 5ms
|
||||
this->crossed_zero_at = micros();
|
||||
uint32_t cycle_time = this->crossed_zero_at - prev_crossed;
|
||||
if (cycle_time > 5000) {
|
||||
this->cycle_time_us = cycle_time;
|
||||
} else {
|
||||
// Otherwise this is noise and this is 2nd (or 3rd...) fall in the same pulse
|
||||
// Consider this is the right fall edge and accumulate the cycle time instead
|
||||
this->cycle_time_us += cycle_time;
|
||||
}
|
||||
|
||||
if (this->value == 65535) {
|
||||
// fully on, enable output immediately
|
||||
this->gate_pin->digital_write(true);
|
||||
} else if (this->init_cycle) {
|
||||
// send a full cycle
|
||||
this->init_cycle = false;
|
||||
this->enable_time_us = 0;
|
||||
this->disable_time_us = cycle_time_us;
|
||||
} else if (this->value == 0) {
|
||||
// fully off, disable output immediately
|
||||
this->gate_pin->digital_write(false);
|
||||
} else {
|
||||
if (this->method == DIM_METHOD_TRAILING) {
|
||||
this->enable_time_us = 1; // cannot be 0
|
||||
this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
|
||||
} else {
|
||||
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
|
||||
// also take into account min_power
|
||||
auto min_us = this->cycle_time_us * this->min_power / 1000;
|
||||
this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
|
||||
if (this->method == DIM_METHOD_LEADING_PULSE) {
|
||||
// Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
|
||||
// this is for brightness near 99%
|
||||
this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
|
||||
} else {
|
||||
this->gate_pin->digital_write(false);
|
||||
this->disable_time_us = this->cycle_time_us;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
// Attaching pin interrupts on the same pin will override the previous interupt
|
||||
// However, the user expects that multiple dimmers sharing the same ZC pin will work.
|
||||
// We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
|
||||
// if any of them are using the same ZC pin, and also trigger the interrupt for *them*.
|
||||
for (auto *dimmer : all_dimmers) {
|
||||
if (dimmer == nullptr)
|
||||
break;
|
||||
if (dimmer->zero_cross_pin_number == store->zero_cross_pin_number) {
|
||||
dimmer->gpio_intr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// ESP32 implementation, uses basically the same code but needs to wrap
|
||||
// timer_interrupt() function to auto-reschedule
|
||||
static hw_timer_t *dimmer_timer = nullptr;
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||
#endif
|
||||
|
||||
void AcDimmer::setup() {
|
||||
// extend all_dimmers array with our dimmer
|
||||
|
||||
// Need to be sure the zero cross pin is setup only once, ESP8266 fails and ESP32 seems to fail silently
|
||||
auto setup_zero_cross_pin = true;
|
||||
|
||||
for (auto &all_dimmer : all_dimmers) {
|
||||
if (all_dimmer == nullptr) {
|
||||
all_dimmer = &this->store_;
|
||||
break;
|
||||
}
|
||||
if (all_dimmer->zero_cross_pin_number == this->zero_cross_pin_->get_pin()) {
|
||||
setup_zero_cross_pin = false;
|
||||
}
|
||||
}
|
||||
|
||||
this->gate_pin_->setup();
|
||||
this->store_.gate_pin = this->gate_pin_->to_isr();
|
||||
this->store_.zero_cross_pin_number = this->zero_cross_pin_->get_pin();
|
||||
this->store_.min_power = static_cast<uint16_t>(this->min_power_ * 1000);
|
||||
this->min_power_ = 0;
|
||||
this->store_.method = this->method_;
|
||||
|
||||
if (setup_zero_cross_pin) {
|
||||
this->zero_cross_pin_->setup();
|
||||
this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
|
||||
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING);
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
// Uses ESP8266 waveform (soft PWM) class
|
||||
// PWM and AcDimmer can even run at the same time this way
|
||||
setTimer1Callback(&timer_interrupt);
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// 80 Divider -> 1 count=1µs
|
||||
dimmer_timer = timerBegin(0, 80, true);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
|
||||
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
||||
// are not callable from ISR (placed in flash storage).
|
||||
// Here we just use an interrupt firing every 50 µs.
|
||||
timerAlarmWrite(dimmer_timer, 50, true);
|
||||
timerAlarmEnable(dimmer_timer);
|
||||
#endif
|
||||
}
|
||||
void AcDimmer::write_state(float state) {
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
if (new_value != 0 && this->store_.value == 0)
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
this->store_.value = new_value;
|
||||
}
|
||||
void AcDimmer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AcDimmer:");
|
||||
LOG_PIN(" Output Pin: ", this->gate_pin_);
|
||||
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f);
|
||||
ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
|
||||
if (method_ == DIM_METHOD_LEADING_PULSE)
|
||||
ESP_LOGCONFIG(TAG, " Method: leading pulse");
|
||||
else if (method_ == DIM_METHOD_LEADING)
|
||||
ESP_LOGCONFIG(TAG, " Method: leading");
|
||||
else
|
||||
ESP_LOGCONFIG(TAG, " Method: trailing");
|
||||
|
||||
LOG_FLOAT_OUTPUT(this);
|
||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||
}
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
66
esphome/components/ac_dimmer/ac_dimmer.h
Normal file
66
esphome/components/ac_dimmer/ac_dimmer.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
|
||||
enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING };
|
||||
|
||||
struct AcDimmerDataStore {
|
||||
/// Zero-cross pin
|
||||
ISRInternalGPIOPin *zero_cross_pin;
|
||||
/// Zero-cross pin number - used to share ZC pin across multiple dimmers
|
||||
uint8_t zero_cross_pin_number;
|
||||
/// Output pin to write to
|
||||
ISRInternalGPIOPin *gate_pin;
|
||||
/// Value of the dimmer - 0 to 65535.
|
||||
uint16_t value;
|
||||
/// Minimum power for activation
|
||||
uint16_t min_power;
|
||||
/// Time between the last two ZC pulses
|
||||
uint32_t cycle_time_us;
|
||||
/// Time (in micros()) of last ZC signal
|
||||
uint32_t crossed_zero_at;
|
||||
/// Time since last ZC pulse to enable gate pin. 0 means not set.
|
||||
uint32_t enable_time_us;
|
||||
/// Time since last ZC pulse to disable gate pin. 0 means no disable.
|
||||
uint32_t disable_time_us;
|
||||
/// Set to send the first half ac cycle complete
|
||||
bool init_cycle;
|
||||
/// Dimmer method
|
||||
DimMethod method;
|
||||
|
||||
uint32_t timer_intr(uint32_t now);
|
||||
|
||||
void gpio_intr();
|
||||
static void s_gpio_intr(AcDimmerDataStore *store);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static void s_timer_intr();
|
||||
#endif
|
||||
};
|
||||
|
||||
class AcDimmer : public output::FloatOutput, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; }
|
||||
void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
|
||||
void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
|
||||
void set_method(DimMethod method) { method_ = method; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
|
||||
GPIOPin *gate_pin_;
|
||||
GPIOPin *zero_cross_pin_;
|
||||
AcDimmerDataStore store_;
|
||||
bool init_with_half_cycle_;
|
||||
DimMethod method_;
|
||||
};
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
49
esphome/components/ac_dimmer/output.py
Normal file
49
esphome/components/ac_dimmer/output.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
||||
|
||||
CODEOWNERS = ["@glmnet"]
|
||||
|
||||
ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer")
|
||||
AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component)
|
||||
|
||||
DimMethod = ac_dimmer_ns.enum("DimMethod")
|
||||
DIM_METHODS = {
|
||||
"LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE,
|
||||
"LEADING": DimMethod.DIM_METHOD_LEADING,
|
||||
"TRAILING": DimMethod.DIM_METHOD_TRAILING,
|
||||
}
|
||||
|
||||
CONF_GATE_PIN = "gate_pin"
|
||||
CONF_ZERO_CROSS_PIN = "zero_cross_pin"
|
||||
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
|
||||
DIM_METHODS, upper=True, space="_"
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
# override default min power to 10%
|
||||
if CONF_MIN_POWER not in config:
|
||||
config[CONF_MIN_POWER] = 0.1
|
||||
yield output.register_output(var, config)
|
||||
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_GATE_PIN])
|
||||
cg.add(var.set_gate_pin(pin))
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
|
||||
cg.add(var.set_zero_cross_pin(pin))
|
||||
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
|
||||
cg.add(var.set_method(config[CONF_METHOD]))
|
||||
28
esphome/components/adalight/__init__.py
Normal file
28
esphome/components/adalight/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.components.light.types import AddressableLightEffect
|
||||
from esphome.components.light.effects import register_addressable_effect
|
||||
from esphome.const import CONF_NAME, CONF_UART_ID
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
adalight_ns = cg.esphome_ns.namespace("adalight")
|
||||
AdalightLightEffect = adalight_ns.class_(
|
||||
"AdalightLightEffect", uart.UARTDevice, AddressableLightEffect
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({})
|
||||
|
||||
|
||||
@register_addressable_effect(
|
||||
"adalight",
|
||||
AdalightLightEffect,
|
||||
"Adalight",
|
||||
{cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
|
||||
)
|
||||
def adalight_light_effect_to_code(config, effect_id):
|
||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||
yield uart.register_uart_device(effect, config)
|
||||
|
||||
yield effect
|
||||
140
esphome/components/adalight/adalight_light_effect.cpp
Normal file
140
esphome/components/adalight/adalight_light_effect.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "adalight_light_effect.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adalight {
|
||||
|
||||
static const char *TAG = "adalight_light_effect";
|
||||
|
||||
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
|
||||
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
|
||||
|
||||
AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
|
||||
void AdalightLightEffect::start() {
|
||||
AddressableLightEffect::start();
|
||||
|
||||
last_ack_ = 0;
|
||||
last_byte_ = 0;
|
||||
last_reset_ = 0;
|
||||
}
|
||||
|
||||
void AdalightLightEffect::stop() {
|
||||
frame_.resize(0);
|
||||
|
||||
AddressableLightEffect::stop();
|
||||
}
|
||||
|
||||
int AdalightLightEffect::get_frame_size_(int led_count) const {
|
||||
// 3 bytes: Ada
|
||||
// 2 bytes: LED count
|
||||
// 1 byte: checksum
|
||||
// 3 bytes per LED
|
||||
return 3 + 2 + 1 + led_count * 3;
|
||||
}
|
||||
|
||||
void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
|
||||
int buffer_capacity = get_frame_size_(it.size());
|
||||
|
||||
frame_.clear();
|
||||
frame_.reserve(buffer_capacity);
|
||||
}
|
||||
|
||||
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
|
||||
for (int led = it.size(); led-- > 0;) {
|
||||
it[led].set(COLOR_BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) {
|
||||
ESP_LOGV(TAG, "Sending ACK");
|
||||
this->write_str("Ada\n");
|
||||
this->last_ack_ = now;
|
||||
}
|
||||
|
||||
if (!this->last_reset_) {
|
||||
ESP_LOGW(TAG, "Frame: Reset.");
|
||||
reset_frame_(it);
|
||||
blank_all_leds_(it);
|
||||
this->last_reset_ = now;
|
||||
}
|
||||
|
||||
if (!this->frame_.empty() && now - this->last_byte_ >= ADALIGHT_RECEIVE_TIMEOUT) {
|
||||
ESP_LOGW(TAG, "Frame: Receive timeout (size=%zu).", this->frame_.size());
|
||||
reset_frame_(it);
|
||||
blank_all_leds_(it);
|
||||
}
|
||||
|
||||
if (this->available() > 0) {
|
||||
ESP_LOGV(TAG, "Frame: Available (size=%d).", this->available());
|
||||
}
|
||||
|
||||
while (this->available() != 0) {
|
||||
uint8_t data;
|
||||
if (!this->read_byte(&data))
|
||||
break;
|
||||
this->frame_.push_back(data);
|
||||
this->last_byte_ = now;
|
||||
|
||||
switch (this->parse_frame_(it)) {
|
||||
case INVALID:
|
||||
ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=%d).", this->frame_.size(), this->frame_[0]);
|
||||
reset_frame_(it);
|
||||
break;
|
||||
|
||||
case PARTIAL:
|
||||
break;
|
||||
|
||||
case CONSUMED:
|
||||
ESP_LOGV(TAG, "Frame: Consumed (size=%zu).", this->frame_.size());
|
||||
reset_frame_(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableLight &it) {
|
||||
if (frame_.empty())
|
||||
return INVALID;
|
||||
|
||||
// Check header: `Ada`
|
||||
if (frame_[0] != 'A')
|
||||
return INVALID;
|
||||
if (frame_.size() > 1 && frame_[1] != 'd')
|
||||
return INVALID;
|
||||
if (frame_.size() > 2 && frame_[2] != 'a')
|
||||
return INVALID;
|
||||
|
||||
// 3 bytes: Count Hi, Count Lo, Checksum
|
||||
if (frame_.size() < 6)
|
||||
return PARTIAL;
|
||||
|
||||
// Check checksum
|
||||
uint16_t checksum = frame_[3] ^ frame_[4] ^ 0x55;
|
||||
if (checksum != frame_[5])
|
||||
return INVALID;
|
||||
|
||||
// Check if we received the full frame
|
||||
uint16_t led_count = (frame_[3] << 8) + frame_[4] + 1;
|
||||
auto buffer_size = get_frame_size_(led_count);
|
||||
if (frame_.size() < buffer_size)
|
||||
return PARTIAL;
|
||||
|
||||
// Apply lights
|
||||
auto accepted_led_count = std::min<int>(led_count, it.size());
|
||||
uint8_t *led_data = &frame_[6];
|
||||
|
||||
for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
|
||||
auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]);
|
||||
|
||||
it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
|
||||
}
|
||||
|
||||
return CONSUMED;
|
||||
}
|
||||
|
||||
} // namespace adalight
|
||||
} // namespace esphome
|
||||
41
esphome/components/adalight/adalight_light_effect.h
Normal file
41
esphome/components/adalight/adalight_light_effect.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/light/addressable_light_effect.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace adalight {
|
||||
|
||||
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
|
||||
public:
|
||||
AdalightLightEffect(const std::string &name);
|
||||
|
||||
public:
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void apply(light::AddressableLight &it, const Color ¤t_color) override;
|
||||
|
||||
protected:
|
||||
enum Frame {
|
||||
INVALID,
|
||||
PARTIAL,
|
||||
CONSUMED,
|
||||
};
|
||||
|
||||
int get_frame_size_(int led_count) const;
|
||||
void reset_frame_(light::AddressableLight &it);
|
||||
void blank_all_leds_(light::AddressableLight &it);
|
||||
Frame parse_frame_(light::AddressableLight &it);
|
||||
|
||||
protected:
|
||||
uint32_t last_ack_{0};
|
||||
uint32_t last_byte_{0};
|
||||
uint32_t last_reset_{0};
|
||||
std::vector<uint8_t> frame_;
|
||||
};
|
||||
|
||||
} // namespace adalight
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
@@ -16,7 +16,9 @@ void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuati
|
||||
|
||||
void ADCSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||
#ifndef USE_ADC_SENSOR_VCC
|
||||
GPIOPin(this->pin_, INPUT).setup();
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
||||
@@ -58,7 +60,7 @@ void ADCSensor::update() {
|
||||
}
|
||||
float ADCSensor::sample() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
float value_v = analogRead(this->pin_) / 4095.0f;
|
||||
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
|
||||
switch (this->attenuation_) {
|
||||
case ADC_0db:
|
||||
value_v *= 1.1;
|
||||
@@ -80,7 +82,7 @@ float ADCSensor::sample() {
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
return ESP.getVcc() / 1024.0f;
|
||||
#else
|
||||
return analogRead(this->pin_) / 1024.0f;
|
||||
return analogRead(this->pin_) / 1024.0f; // NOLINT
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2,36 +2,51 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT
|
||||
from esphome.const import (
|
||||
CONF_ATTENUATION,
|
||||
CONF_ID,
|
||||
CONF_PIN,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
|
||||
AUTO_LOAD = ['voltage_sampler']
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
|
||||
ATTENUATION_MODES = {
|
||||
'0db': cg.global_ns.ADC_0db,
|
||||
'2.5db': cg.global_ns.ADC_2_5db,
|
||||
'6db': cg.global_ns.ADC_6db,
|
||||
'11db': cg.global_ns.ADC_11db,
|
||||
"0db": cg.global_ns.ADC_0db,
|
||||
"2.5db": cg.global_ns.ADC_2_5db,
|
||||
"6db": cg.global_ns.ADC_6db,
|
||||
"11db": cg.global_ns.ADC_11db,
|
||||
}
|
||||
|
||||
|
||||
def validate_adc_pin(value):
|
||||
vcc = str(value).upper()
|
||||
if vcc == 'VCC':
|
||||
if vcc == "VCC":
|
||||
return cv.only_on_esp8266(vcc)
|
||||
return pins.analog_pin(value)
|
||||
|
||||
|
||||
adc_ns = cg.esphome_ns.namespace('adc')
|
||||
ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler)
|
||||
adc_ns = cg.esphome_ns.namespace("adc")
|
||||
ADCSensor = adc_ns.class_(
|
||||
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({
|
||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||
cv.Required(CONF_PIN): validate_adc_pin,
|
||||
cv.SplitDefault(CONF_ATTENUATION, esp32='0db'):
|
||||
cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)),
|
||||
}).extend(cv.polling_component_schema('60s'))
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||
cv.Required(CONF_PIN): validate_adc_pin,
|
||||
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
|
||||
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
@@ -39,8 +54,8 @@ def to_code(config):
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
|
||||
if config[CONF_PIN] == 'VCC':
|
||||
cg.add_define('USE_ADC_SENSOR_VCC')
|
||||
if config[CONF_PIN] == "VCC":
|
||||
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||
else:
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
|
||||
|
||||
0
esphome/components/addressable_light/__init__.py
Normal file
0
esphome/components/addressable_light/__init__.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "addressable_light_display.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace addressable_light {
|
||||
|
||||
static const char* TAG = "addressable_light.display";
|
||||
|
||||
int AddressableLightDisplay::get_width_internal() { return this->width_; }
|
||||
int AddressableLightDisplay::get_height_internal() { return this->height_; }
|
||||
|
||||
void AddressableLightDisplay::setup() {
|
||||
this->addressable_light_buffer_.resize(this->width_ * this->height_, {0, 0, 0, 0});
|
||||
}
|
||||
|
||||
void AddressableLightDisplay::update() {
|
||||
if (!this->enabled_)
|
||||
return;
|
||||
|
||||
this->do_update_();
|
||||
this->display();
|
||||
}
|
||||
|
||||
void AddressableLightDisplay::display() {
|
||||
bool dirty = false;
|
||||
uint8_t old_r, old_g, old_b, old_w;
|
||||
Color* c;
|
||||
|
||||
for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
|
||||
c = &(this->addressable_light_buffer_[offset]);
|
||||
|
||||
light::ESPColorView pixel = (*this->light_)[offset];
|
||||
|
||||
// Track the original values for the pixel view. If it has changed updating, then
|
||||
// we trigger a redraw. Avoiding redraws == avoiding flicker!
|
||||
old_r = pixel.get_red();
|
||||
old_g = pixel.get_green();
|
||||
old_b = pixel.get_blue();
|
||||
old_w = pixel.get_white();
|
||||
|
||||
pixel.set_rgbw(c->r, c->g, c->b, c->w);
|
||||
|
||||
// If the actual value of the pixel changed, then schedule a redraw.
|
||||
if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b ||
|
||||
pixel.get_white() != old_w) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
this->light_->schedule_show();
|
||||
}
|
||||
}
|
||||
|
||||
void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
|
||||
return;
|
||||
|
||||
if (this->pixel_mapper_f_.has_value()) {
|
||||
// Params are passed by reference, so they may be modified in call.
|
||||
this->addressable_light_buffer_[(*this->pixel_mapper_f_)(x, y)] = color;
|
||||
} else {
|
||||
this->addressable_light_buffer_[y * this->get_width_internal() + x] = color;
|
||||
}
|
||||
}
|
||||
} // namespace addressable_light
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
#include "esphome/components/light/addressable_light.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace addressable_light {
|
||||
|
||||
class AddressableLightDisplay : public display::DisplayBuffer, public PollingComponent {
|
||||
public:
|
||||
light::AddressableLight *get_light() const { return this->light_; }
|
||||
|
||||
void set_width(int32_t width) { width_ = width; }
|
||||
void set_height(int32_t height) { height_ = height; }
|
||||
void set_light(light::LightState *state) {
|
||||
light_state_ = state;
|
||||
light_ = static_cast<light::AddressableLight *>(state->get_output());
|
||||
}
|
||||
void set_enabled(bool enabled) {
|
||||
if (light_state_) {
|
||||
if (enabled_ && !enabled) { // enabled -> disabled
|
||||
// - Tell the parent light to refresh, effectively wiping the display. Also
|
||||
// restores the previous effect (if any).
|
||||
light_state_->make_call().set_effect(this->last_effect_).perform();
|
||||
|
||||
} else if (!enabled_ && enabled) { // disabled -> enabled
|
||||
// - Save the current effect.
|
||||
this->last_effect_ = light_state_->get_effect_name();
|
||||
// - Disable any current effect.
|
||||
light_state_->make_call().set_effect(0).perform();
|
||||
}
|
||||
}
|
||||
enabled_ = enabled;
|
||||
}
|
||||
bool get_enabled() { return enabled_; }
|
||||
|
||||
void set_pixel_mapper(std::function<int(int, int)> &&pixel_mapper_f) { this->pixel_mapper_f_ = pixel_mapper_f; }
|
||||
void setup() override;
|
||||
void display();
|
||||
|
||||
protected:
|
||||
int get_width_internal() override;
|
||||
int get_height_internal() override;
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
void update() override;
|
||||
|
||||
light::LightState *light_state_;
|
||||
light::AddressableLight *light_;
|
||||
bool enabled_{true};
|
||||
int32_t width_;
|
||||
int32_t height_;
|
||||
std::vector<Color> addressable_light_buffer_;
|
||||
optional<std::string> last_effect_;
|
||||
optional<std::function<int(int, int)>> pixel_mapper_f_;
|
||||
};
|
||||
} // namespace addressable_light
|
||||
} // namespace esphome
|
||||
63
esphome/components/addressable_light/display.py
Normal file
63
esphome/components/addressable_light/display.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import display, light
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_PAGES,
|
||||
CONF_ADDRESSABLE_LIGHT_ID,
|
||||
CONF_HEIGHT,
|
||||
CONF_WIDTH,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
CONF_PIXEL_MAPPER,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@justfalter"]
|
||||
|
||||
addressable_light_ns = cg.esphome_ns.namespace("addressable_light")
|
||||
AddressableLightDisplay = addressable_light_ns.class_(
|
||||
"AddressableLightDisplay", display.DisplayBuffer, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AddressableLightDisplay),
|
||||
cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id(
|
||||
light.AddressableLightState
|
||||
),
|
||||
cv.Required(CONF_WIDTH): cv.positive_int,
|
||||
cv.Required(CONF_HEIGHT): cv.positive_int,
|
||||
cv.Optional(
|
||||
CONF_UPDATE_INTERVAL, default="16ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_PIXEL_MAPPER): cv.returning_lambda,
|
||||
}
|
||||
),
|
||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
wrapped_light = yield cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
|
||||
cg.add(var.set_width(config[CONF_WIDTH]))
|
||||
cg.add(var.set_height(config[CONF_HEIGHT]))
|
||||
cg.add(var.set_light(wrapped_light))
|
||||
|
||||
yield cg.register_component(var, config)
|
||||
yield display.register_display(var, config)
|
||||
|
||||
if CONF_PIXEL_MAPPER in config:
|
||||
pixel_mapper_template_ = yield cg.process_lambda(
|
||||
config[CONF_PIXEL_MAPPER],
|
||||
[(int, "x"), (int, "y")],
|
||||
return_type=cg.int_,
|
||||
)
|
||||
cg.add(var.set_pixel_mapper(pixel_mapper_template_))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
0
esphome/components/ade7953/__init__.py
Normal file
0
esphome/components/ade7953/__init__.py
Normal file
54
esphome/components/ade7953/ade7953.cpp
Normal file
54
esphome/components/ade7953/ade7953.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "ade7953.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7953 {
|
||||
|
||||
static const char *TAG = "ade7953";
|
||||
|
||||
void ADE7953::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADE7953:");
|
||||
if (this->has_irq_) {
|
||||
ESP_LOGCONFIG(TAG, " IRQ Pin: GPIO%u", this->irq_pin_number_);
|
||||
}
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current A Sensor", this->current_a_sensor_);
|
||||
LOG_SENSOR(" ", "Current B Sensor", this->current_b_sensor_);
|
||||
LOG_SENSOR(" ", "Active Power A Sensor", this->active_power_a_sensor_);
|
||||
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
|
||||
}
|
||||
|
||||
#define ADE_PUBLISH_(name, factor) \
|
||||
if (name && this->name##_sensor_) { \
|
||||
float value = *name / factor; \
|
||||
this->name##_sensor_->publish_state(value); \
|
||||
}
|
||||
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
|
||||
|
||||
void ADE7953::update() {
|
||||
if (!this->is_setup_)
|
||||
return;
|
||||
|
||||
auto active_power_a = this->ade_read_<int32_t>(0x0312);
|
||||
ADE_PUBLISH(active_power_a, 154.0f);
|
||||
auto active_power_b = this->ade_read_<int32_t>(0x0313);
|
||||
ADE_PUBLISH(active_power_b, 154.0f);
|
||||
auto current_a = this->ade_read_<uint32_t>(0x031A);
|
||||
ADE_PUBLISH(current_a, 100000.0f);
|
||||
auto current_b = this->ade_read_<uint32_t>(0x031B);
|
||||
ADE_PUBLISH(current_b, 100000.0f);
|
||||
auto voltage = this->ade_read_<uint32_t>(0x031C);
|
||||
ADE_PUBLISH(voltage, 26000.0f);
|
||||
|
||||
// auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
|
||||
// auto apparent_power_b = this->ade_read_<int32_t>(0x0311);
|
||||
// auto reactive_power_a = this->ade_read_<int32_t>(0x0314);
|
||||
// auto reactive_power_b = this->ade_read_<int32_t>(0x0315);
|
||||
// auto power_factor_a = this->ade_read_<int16_t>(0x010A);
|
||||
// auto power_factor_b = this->ade_read_<int16_t>(0x010B);
|
||||
}
|
||||
|
||||
} // namespace ade7953
|
||||
} // namespace esphome
|
||||
79
esphome/components/ade7953/ade7953.h
Normal file
79
esphome/components/ade7953/ade7953.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7953 {
|
||||
|
||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
public:
|
||||
void set_irq_pin(uint8_t irq_pin) {
|
||||
has_irq_ = true;
|
||||
irq_pin_number_ = irq_pin;
|
||||
}
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
|
||||
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
|
||||
void set_active_power_a_sensor(sensor::Sensor *active_power_a_sensor) {
|
||||
active_power_a_sensor_ = active_power_a_sensor;
|
||||
}
|
||||
void set_active_power_b_sensor(sensor::Sensor *active_power_b_sensor) {
|
||||
active_power_b_sensor_ = active_power_b_sensor;
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
if (this->has_irq_) {
|
||||
auto pin = GPIOPin(this->irq_pin_number_, INPUT);
|
||||
this->irq_pin_ = &pin;
|
||||
this->irq_pin_->setup();
|
||||
}
|
||||
this->set_timeout(100, [this]() {
|
||||
this->ade_write_<uint8_t>(0x0010, 0x04);
|
||||
this->ade_write_<uint8_t>(0x00FE, 0xAD);
|
||||
this->ade_write_<uint16_t>(0x0120, 0x0030);
|
||||
this->is_setup_ = true;
|
||||
});
|
||||
}
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
template<typename T> bool ade_write_(uint16_t reg, T value) {
|
||||
std::vector<uint8_t> data;
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
for (int i = sizeof(T) - 1; i >= 0; i--)
|
||||
data.push_back(value >> (i * 8));
|
||||
return this->write_bytes_raw(data);
|
||||
}
|
||||
template<typename T> optional<T> ade_read_(uint16_t reg) {
|
||||
uint8_t hi = reg >> 8;
|
||||
uint8_t lo = reg >> 0;
|
||||
if (!this->write_bytes_raw({hi, lo}))
|
||||
return {};
|
||||
auto ret = this->read_bytes_raw<sizeof(T)>();
|
||||
if (!ret.has_value())
|
||||
return {};
|
||||
T result = 0;
|
||||
for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--)
|
||||
result |= T((*ret)[i]) << (j * 8);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool has_irq_ = false;
|
||||
uint8_t irq_pin_number_;
|
||||
GPIOPin *irq_pin_{nullptr};
|
||||
bool is_setup_{false};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_a_sensor_{nullptr};
|
||||
sensor::Sensor *current_b_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_a_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_b_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ade7953
|
||||
} // namespace esphome
|
||||
74
esphome/components/ade7953/sensor.py
Normal file
74
esphome/components/ade7953/sensor.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, i2c
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ade7953_ns = cg.esphome_ns.namespace("ade7953")
|
||||
ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONF_IRQ_PIN = "irq_pin"
|
||||
CONF_CURRENT_A = "current_a"
|
||||
CONF_CURRENT_B = "current_b"
|
||||
CONF_ACTIVE_POWER_A = "active_power_a"
|
||||
CONF_ACTIVE_POWER_B = "active_power_b"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_IRQ_PIN in config:
|
||||
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
|
||||
|
||||
for key in [
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT_A,
|
||||
CONF_CURRENT_B,
|
||||
CONF_ACTIVE_POWER_A,
|
||||
CONF_ACTIVE_POWER_B,
|
||||
]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||
@@ -3,19 +3,29 @@ import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
AUTO_LOAD = ['sensor', 'voltage_sampler']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "voltage_sampler"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ads1115_ns = cg.esphome_ns.namespace('ads1115')
|
||||
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
|
||||
ads1115_ns = cg.esphome_ns.namespace("ads1115")
|
||||
ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
|
||||
CONF_CONTINUOUS_MODE = "continuous_mode"
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(None))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
||||
|
||||
@@ -29,9 +29,15 @@ void ADS1115Component::setup() {
|
||||
// 0bxxxx000xxxxxxxxx
|
||||
config |= ADS1115_GAIN_6P144 << 9;
|
||||
|
||||
// Set singleshot mode
|
||||
// 0bxxxxxxx1xxxxxxxx
|
||||
config |= 0b0000000100000000;
|
||||
if (this->continuous_mode_) {
|
||||
// Set continuous mode
|
||||
// 0bxxxxxxx0xxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
} else {
|
||||
// Set singleshot mode
|
||||
// 0bxxxxxxx1xxxxxxxx
|
||||
config |= 0b0000000100000000;
|
||||
}
|
||||
|
||||
// Set data rate - 860 samples per second (we're in singleshot mode)
|
||||
// 0bxxxxxxxx100xxxxx
|
||||
@@ -57,6 +63,8 @@ void ADS1115Component::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
this->set_interval(sensor->get_name(), sensor->update_interval(),
|
||||
[this, sensor] { this->request_measurement(sensor); });
|
||||
@@ -75,13 +83,8 @@ void ADS1115Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", sensor->get_gain());
|
||||
}
|
||||
}
|
||||
float ADS1115Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
uint16_t config;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONFIG, &config)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
uint16_t config = this->prev_config_;
|
||||
// Multiplexer
|
||||
// 0bxBBBxxxxxxxxxxxx
|
||||
config &= 0b1000111111111111;
|
||||
@@ -91,25 +94,31 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
// 0bxxxxBBBxxxxxxxxx
|
||||
config &= 0b1111000111111111;
|
||||
config |= (sensor->get_gain() & 0b111) << 9;
|
||||
// Start conversion
|
||||
config |= 0b1000000000000000;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
if (!this->continuous_mode_) {
|
||||
// Start conversion
|
||||
config |= 0b1000000000000000;
|
||||
}
|
||||
|
||||
// about 1.6 ms with 860 samples per second
|
||||
delay(2);
|
||||
|
||||
uint32_t start = millis();
|
||||
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
||||
if (millis() - start > 100) {
|
||||
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
||||
if (!this->continuous_mode_ || this->prev_config_ != config) {
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
this->prev_config_ = config;
|
||||
|
||||
// about 1.6 ms with 860 samples per second
|
||||
delay(2);
|
||||
|
||||
uint32_t start = millis();
|
||||
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
||||
if (millis() - start > 100) {
|
||||
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t raw_conversion;
|
||||
@@ -144,13 +153,9 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return millivolts / 1e4f;
|
||||
return millivolts / 1e3f;
|
||||
}
|
||||
|
||||
uint8_t ADS1115Sensor::get_multiplexer() const { return this->multiplexer_; }
|
||||
void ADS1115Sensor::set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
|
||||
uint8_t ADS1115Sensor::get_gain() const { return this->gain_; }
|
||||
void ADS1115Sensor::set_gain(ADS1115Gain gain) { this->gain_ = gain; }
|
||||
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
|
||||
void ADS1115Sensor::update() {
|
||||
float v = this->parent_->request_measurement(this);
|
||||
|
||||
@@ -37,13 +37,16 @@ class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// HARDWARE_LATE setup priority
|
||||
float get_setup_priority() const override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
|
||||
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
float request_measurement(ADS1115Sensor *sensor);
|
||||
|
||||
protected:
|
||||
std::vector<ADS1115Sensor *> sensors_;
|
||||
uint16_t prev_config_{0};
|
||||
bool continuous_mode_;
|
||||
};
|
||||
|
||||
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||
@@ -51,12 +54,12 @@ class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public vol
|
||||
public:
|
||||
ADS1115Sensor(ADS1115Component *parent) : parent_(parent) {}
|
||||
void update() override;
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer);
|
||||
void set_gain(ADS1115Gain gain);
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer) { multiplexer_ = multiplexer; }
|
||||
void set_gain(ADS1115Gain gain) { gain_ = gain; }
|
||||
|
||||
float sample() override;
|
||||
uint8_t get_multiplexer() const;
|
||||
uint8_t get_gain() const;
|
||||
uint8_t get_multiplexer() const { return multiplexer_; }
|
||||
uint8_t get_gain() const { return gain_; }
|
||||
|
||||
protected:
|
||||
ADS1115Component *parent_;
|
||||
|
||||
@@ -1,54 +1,67 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
|
||||
from esphome.py_compat import string_types
|
||||
from esphome.const import (
|
||||
CONF_GAIN,
|
||||
CONF_MULTIPLEXER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
UNIT_VOLT,
|
||||
CONF_ID,
|
||||
)
|
||||
from . import ads1115_ns, ADS1115Component
|
||||
|
||||
DEPENDENCIES = ['ads1115']
|
||||
DEPENDENCIES = ["ads1115"]
|
||||
|
||||
ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
|
||||
ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer")
|
||||
MUX = {
|
||||
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||
'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||
'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||
'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||
'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
"A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
"A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
"A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||
"A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||
"A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||
"A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||
"A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||
"A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
}
|
||||
|
||||
ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
|
||||
ADS1115Gain = ads1115_ns.enum("ADS1115Gain")
|
||||
GAIN = {
|
||||
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
'4.096': ADS1115Gain.ADS1115_GAIN_4P096,
|
||||
'2.048': ADS1115Gain.ADS1115_GAIN_2P048,
|
||||
'1.024': ADS1115Gain.ADS1115_GAIN_1P024,
|
||||
'0.512': ADS1115Gain.ADS1115_GAIN_0P512,
|
||||
'0.256': ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
"6.144": ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
"4.096": ADS1115Gain.ADS1115_GAIN_4P096,
|
||||
"2.048": ADS1115Gain.ADS1115_GAIN_2P048,
|
||||
"1.024": ADS1115Gain.ADS1115_GAIN_1P024,
|
||||
"0.512": ADS1115Gain.ADS1115_GAIN_0P512,
|
||||
"0.256": ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
}
|
||||
|
||||
|
||||
def validate_gain(value):
|
||||
if isinstance(value, float):
|
||||
value = u'{:0.03f}'.format(value)
|
||||
elif not isinstance(value, string_types):
|
||||
raise cv.Invalid('invalid gain "{}"'.format(value))
|
||||
value = f"{value:0.03f}"
|
||||
elif not isinstance(value, str):
|
||||
raise cv.Invalid(f'invalid gain "{value}"')
|
||||
|
||||
return cv.enum(GAIN)(value)
|
||||
|
||||
|
||||
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler)
|
||||
ADS1115Sensor = ads1115_ns.class_(
|
||||
"ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONF_ADS1115_ID = 'ads1115_id'
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
|
||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'),
|
||||
cv.Required(CONF_GAIN): validate_gain,
|
||||
}).extend(cv.polling_component_schema('60s'))
|
||||
CONF_ADS1115_ID = "ads1115_id"
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
|
||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
|
||||
cv.Required(CONF_GAIN): validate_gain,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
||||
0
esphome/components/aht10/__init__.py
Normal file
0
esphome/components/aht10/__init__.py
Normal file
127
esphome/components/aht10/aht10.cpp
Normal file
127
esphome/components/aht10/aht10.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
// Implementation based on:
|
||||
// - AHT10: https://github.com/Thinary/AHT10
|
||||
// - Official Datasheet (cn):
|
||||
// http://www.aosong.com/userfiles/files/media/aht10%E8%A7%84%E6%A0%BC%E4%B9%A6v1_1%EF%BC%8820191015%EF%BC%89.pdf
|
||||
// - Unofficial Translated Datasheet (en):
|
||||
// https://wiki.liutyi.info/download/attachments/30507639/Aosong_AHT10_en_draft_0c.pdf
|
||||
//
|
||||
// When configured for humidity, the log 'Components should block for at most 20-30ms in loop().' will be generated in
|
||||
// verbose mode. This is due to technical specs of the sensor and can not be avoided.
|
||||
//
|
||||
// According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost
|
||||
// immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best
|
||||
// results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time.
|
||||
|
||||
#include "aht10.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace aht10 {
|
||||
|
||||
static const char *TAG = "aht10";
|
||||
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
|
||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
static const uint8_t AHT10_ATTEMPS = 3; // safety margin, normally 3 attemps are enough: 3*30=90ms
|
||||
|
||||
void AHT10Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT10...");
|
||||
|
||||
if (!this->write_bytes(0, AHT10_CALIBRATE_CMD, sizeof(AHT10_CALIBRATE_CMD))) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint8_t data;
|
||||
if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
|
||||
ESP_LOGE(TAG, "AHT10 calibration failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "AHT10 calibrated");
|
||||
}
|
||||
|
||||
void AHT10Component::update() {
|
||||
if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
uint8_t data[6];
|
||||
uint8_t delay = AHT10_DEFAULT_DELAY;
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
delay = AHT10_HUMIDITY_DELAY;
|
||||
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
|
||||
if (!this->read_bytes(0, data, 6, delay)) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
|
||||
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
|
||||
// Unrealistic humidity (0x0)
|
||||
if (this->humidity_sensor_ == nullptr) {
|
||||
ESP_LOGVV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
|
||||
break;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
|
||||
if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// data is valid, we can break the loop
|
||||
ESP_LOGVV(TAG, "Answer at %6ld", millis());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((data[0] & 0x80) == 0x80) {
|
||||
ESP_LOGE(TAG, "Measurements reading timed-out!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
|
||||
|
||||
float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0;
|
||||
float humidity;
|
||||
if (raw_humidity == 0) { // unrealistic value
|
||||
humidity = NAN;
|
||||
} else {
|
||||
humidity = (float) raw_humidity * 100.0 / 1048576.0;
|
||||
}
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
if (isnan(humidity))
|
||||
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void AHT10Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AHT10:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
}
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
}
|
||||
|
||||
} // namespace aht10
|
||||
} // namespace esphome
|
||||
26
esphome/components/aht10/aht10.h
Normal file
26
esphome/components/aht10/aht10.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace aht10 {
|
||||
|
||||
class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
};
|
||||
|
||||
} // namespace aht10
|
||||
} // namespace esphome
|
||||
48
esphome/components/aht10/sensor.py
Normal file
48
esphome/components/aht10/sensor.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
aht10_ns = cg.esphome_ns.namespace("aht10")
|
||||
AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AHT10Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
@@ -1,19 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
am2320_ns = cg.esphome_ns.namespace('am2320')
|
||||
AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
am2320_ns = cg.esphome_ns.namespace("am2320")
|
||||
AM2320Component = am2320_ns.class_(
|
||||
"AM2320Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||
),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x5C))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
||||
106
esphome/components/animation/__init__.py
Normal file
106
esphome/components/animation/__init__.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import logging
|
||||
|
||||
from esphome import core
|
||||
from esphome.components import display, font
|
||||
import esphome.components.image as espImage
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ["display"]
|
||||
MULTI_CONF = True
|
||||
|
||||
Animation_ = display.display_ns.class_("Animation")
|
||||
|
||||
CONF_RAW_DATA_ID = "raw_data_id"
|
||||
|
||||
ANIMATION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||
cv.Required(CONF_FILE): cv.file_,
|
||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
|
||||
espImage.IMAGE_TYPE, upper=True
|
||||
),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
|
||||
|
||||
CODEOWNERS = ["@syndlex"]
|
||||
|
||||
|
||||
def to_code(config):
|
||||
from PIL import Image
|
||||
|
||||
path = CORE.relative_config_path(config[CONF_FILE])
|
||||
try:
|
||||
image = Image.open(path)
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(f"Could not load image file {path}: {e}")
|
||||
|
||||
width, height = image.size
|
||||
frames = image.n_frames
|
||||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
width, height = image.size
|
||||
else:
|
||||
if width > 500 or height > 500:
|
||||
_LOGGER.warning(
|
||||
"The image you requested is very big. Please consider using"
|
||||
" the resize parameter."
|
||||
)
|
||||
|
||||
if config[CONF_TYPE] == "GRAYSCALE":
|
||||
data = [0 for _ in range(height * width * frames)]
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert("L", dither=Image.NONE)
|
||||
pixels = list(frame.getdata())
|
||||
for pix in pixels:
|
||||
data[pos] = pix
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == "RGB24":
|
||||
data = [0 for _ in range(height * width * 3 * frames)]
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert("RGB")
|
||||
pixels = list(frame.getdata())
|
||||
for pix in pixels:
|
||||
data[pos] = pix[0]
|
||||
pos += 1
|
||||
data[pos] = pix[1]
|
||||
pos += 1
|
||||
data[pos] = pix[2]
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == "BINARY":
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range((height * width8 // 8) * frames)]
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert("1", dither=Image.NONE)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if frame.getpixel((x, y)):
|
||||
continue
|
||||
pos = x + y * width8 + (height * width8 * frameIndex)
|
||||
data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
prog_arr,
|
||||
width,
|
||||
height,
|
||||
frames,
|
||||
espImage.IMAGE_TYPE[config[CONF_TYPE]],
|
||||
)
|
||||
@@ -3,18 +3,24 @@ import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_APDS9960_ID = 'apds9960_id'
|
||||
CONF_APDS9960_ID = "apds9960_id"
|
||||
|
||||
apds9960_nds = cg.esphome_ns.namespace('apds9960')
|
||||
APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice)
|
||||
apds9960_nds = cg.esphome_ns.namespace("apds9960")
|
||||
APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(APDS9960),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39))
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(APDS9960),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x39))
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
||||
@@ -4,20 +4,24 @@ from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING
|
||||
from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ['apds9960']
|
||||
DEPENDENCIES = ["apds9960"]
|
||||
|
||||
DIRECTIONS = {
|
||||
'UP': 'set_up_direction',
|
||||
'DOWN': 'set_down_direction',
|
||||
'LEFT': 'set_left_direction',
|
||||
'RIGHT': 'set_right_direction',
|
||||
"UP": "set_up_direction",
|
||||
"DOWN": "set_down_direction",
|
||||
"LEFT": "set_left_direction",
|
||||
"RIGHT": "set_right_direction",
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class,
|
||||
})
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING
|
||||
): binary_sensor.device_class,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB
|
||||
from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, UNIT_PERCENT, ICON_LIGHTBULB
|
||||
from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ['apds9960']
|
||||
DEPENDENCIES = ["apds9960"]
|
||||
|
||||
TYPES = {
|
||||
'CLEAR': 'set_clear_channel',
|
||||
'RED': 'set_red_channel',
|
||||
'GREEN': 'set_green_channel',
|
||||
'BLUE': 'set_blue_channel',
|
||||
'PROXIMITY': 'set_proximity',
|
||||
"CLEAR": "set_clear_channel",
|
||||
"RED": "set_red_channel",
|
||||
"GREEN": "set_green_channel",
|
||||
"BLUE": "set_blue_channel",
|
||||
"PROXIMITY": "set_proximity",
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
})
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
||||
@@ -2,48 +2,69 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
||||
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_DATA_TEMPLATE,
|
||||
CONF_ID,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_SERVICE,
|
||||
CONF_VARIABLES,
|
||||
CONF_SERVICES,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_EVENT,
|
||||
CONF_TAG,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
DEPENDENCIES = ['network']
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["async_tcp"]
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
|
||||
api_ns = cg.esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
||||
KeyValuePair = api_ns.class_('KeyValuePair')
|
||||
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
|
||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
||||
api_ns = cg.esphome_ns.namespace("api")
|
||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_(
|
||||
"HomeAssistantServiceCallAction", automation.Action
|
||||
)
|
||||
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
||||
|
||||
UserService = api_ns.class_('UserService', automation.Trigger)
|
||||
ServiceTypeArgument = api_ns.class_('ServiceTypeArgument')
|
||||
ServiceArgType = api_ns.enum('ServiceArgType')
|
||||
SERVICE_ARG_TYPES = {
|
||||
'bool': ServiceArgType.SERVICE_ARG_TYPE_BOOL,
|
||||
'int': ServiceArgType.SERVICE_ARG_TYPE_INT,
|
||||
'float': ServiceArgType.SERVICE_ARG_TYPE_FLOAT,
|
||||
'string': ServiceArgType.SERVICE_ARG_TYPE_STRING,
|
||||
}
|
||||
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
||||
ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument")
|
||||
SERVICE_ARG_NATIVE_TYPES = {
|
||||
'bool': bool,
|
||||
'int': cg.int32,
|
||||
'float': float,
|
||||
'string': cg.std_string,
|
||||
"bool": bool,
|
||||
"int": cg.int32,
|
||||
"float": float,
|
||||
"string": cg.std_string,
|
||||
"bool[]": cg.std_vector.template(bool),
|
||||
"int[]": cg.std_vector.template(cg.int32),
|
||||
"float[]": cg.std_vector.template(float),
|
||||
"string[]": cg.std_vector.template(cg.std_string),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserService),
|
||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_TYPES, lower=True),
|
||||
}),
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
|
||||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="15min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SERVICES): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||
{
|
||||
cv.validate_id_name: cv.one_of(
|
||||
*SERVICE_ARG_NATIVE_TYPES, lower=True
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
@@ -58,61 +79,126 @@ def to_code(config):
|
||||
for conf in config.get(CONF_SERVICES, []):
|
||||
template_args = []
|
||||
func_args = []
|
||||
service_type_args = []
|
||||
service_arg_names = []
|
||||
for name, var_ in conf[CONF_VARIABLES].items():
|
||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||
template_args.append(native)
|
||||
func_args.append((native, name))
|
||||
service_type_args.append(ServiceTypeArgument(name, SERVICE_ARG_TYPES[var_]))
|
||||
service_arg_names.append(name)
|
||||
templ = cg.TemplateArguments(*template_args)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
||||
conf[CONF_SERVICE], service_type_args)
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
|
||||
)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
yield automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
cg.add_define('USE_API')
|
||||
if CORE.is_esp32:
|
||||
cg.add_library('AsyncTCP', '1.0.3')
|
||||
elif CORE.is_esp8266:
|
||||
cg.add_library('ESPAsyncTCP', '1.2.0')
|
||||
cg.add_define("USE_API")
|
||||
cg.add_global(api_ns.using)
|
||||
|
||||
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_SERVICE): cv.string,
|
||||
cv.Optional(CONF_DATA): cv.Schema({
|
||||
cv.string: cv.string,
|
||||
}),
|
||||
cv.Optional(CONF_DATA_TEMPLATE): cv.Schema({
|
||||
cv.string: cv.string,
|
||||
}),
|
||||
cv.Optional(CONF_VARIABLES): cv.Schema({
|
||||
cv.string: cv.returning_lambda,
|
||||
}),
|
||||
})
|
||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
|
||||
{cv.string: cv.returning_lambda}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action('homeassistant.service', HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
||||
@automation.register_action(
|
||||
"homeassistant.service",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv)
|
||||
cg.add(var.set_service(config[CONF_SERVICE]))
|
||||
if CONF_DATA in config:
|
||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()]
|
||||
cg.add(var.set_data(datas))
|
||||
if CONF_DATA_TEMPLATE in config:
|
||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()]
|
||||
cg.add(var.set_data_template(datas))
|
||||
if CONF_VARIABLES in config:
|
||||
datas = []
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
value_ = yield cg.process_lambda(value, [])
|
||||
datas.append(TemplatableKeyValuePair(key, value_))
|
||||
cg.add(var.set_variables(datas))
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||
templ = yield cg.templatable(config[CONF_SERVICE], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data(key, templ))
|
||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data_template(key, templ))
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_variable(key, templ))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_condition('api.connected', APIConnectedCondition, {})
|
||||
def validate_homeassistant_event(value):
|
||||
value = cv.string(value)
|
||||
if not value.startswith("esphome."):
|
||||
raise cv.Invalid(
|
||||
"ESPHome can only generate Home Assistant events that begin with "
|
||||
"esphome. For example 'esphome.xyz'"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"homeassistant.event",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
templ = yield cg.templatable(config[CONF_EVENT], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data(key, templ))
|
||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data_template(key, templ))
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_variable(key, templ))
|
||||
yield var
|
||||
|
||||
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
|
||||
},
|
||||
key=CONF_TAG,
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"homeassistant.tag_scanned",
|
||||
HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||
)
|
||||
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
cg.add(var.set_service("esphome.tag_scanned"))
|
||||
templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string)
|
||||
cg.add(var.add_data("tag_id", templ))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_condition("api.connected", APIConnectedCondition, {})
|
||||
def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
yield cg.new_Pvariable(condition_id, template_arg)
|
||||
|
||||
@@ -1,11 +1,52 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "api_options.proto";
|
||||
|
||||
service APIConnection {
|
||||
rpc hello (HelloRequest) returns (HelloResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc connect (ConnectRequest) returns (ConnectResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc ping (PingRequest) returns (PingResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc device_info (DeviceInfoRequest) returns (DeviceInfoResponse) {
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc list_entities (ListEntitiesRequest) returns (void) {}
|
||||
rpc subscribe_states (SubscribeStatesRequest) returns (void) {}
|
||||
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
||||
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
||||
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
||||
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
||||
|
||||
rpc cover_command (CoverCommandRequest) returns (void) {}
|
||||
rpc fan_command (FanCommandRequest) returns (void) {}
|
||||
rpc light_command (LightCommandRequest) returns (void) {}
|
||||
rpc switch_command (SwitchCommandRequest) returns (void) {}
|
||||
rpc camera_image (CameraImageRequest) returns (void) {}
|
||||
rpc climate_command (ClimateCommandRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
// ==================== BASE PACKETS ====================
|
||||
|
||||
// The Home Assistant protocol is structured as a simple
|
||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
||||
// First, a message in this protocol has a specific format:
|
||||
// * A zero byte.
|
||||
// * VarInt denoting the size of the message object. (type is not part of this)
|
||||
// * VarInt denoting the type of message.
|
||||
// * The message object encoded as a ProtoBuf message
|
||||
@@ -21,8 +62,11 @@ syntax = "proto3";
|
||||
|
||||
// Message sent at the beginning of each connection
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
// ID: 1
|
||||
message HelloRequest {
|
||||
option (id) = 1;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Description of client (like User Agent)
|
||||
// For example "Home Assistant"
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
@@ -32,8 +76,11 @@ message HelloRequest {
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
// ID: 2
|
||||
message HelloResponse {
|
||||
option (id) = 2;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
||||
// for compatibility and if necessary adopt to an older API.
|
||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
||||
@@ -49,49 +96,66 @@ message HelloResponse {
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
// ID: 3
|
||||
message ConnectRequest {
|
||||
option (id) = 3;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
// The password to log in with
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
// ID: 4
|
||||
message ConnectResponse {
|
||||
option (id) = 4;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
bool invalid_password = 1;
|
||||
}
|
||||
|
||||
// Request to close the connection.
|
||||
// Can be sent by both the client and server
|
||||
// ID: 5
|
||||
message DisconnectRequest {
|
||||
option (id) = 5;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Do not close the connection before the acknowledgement arrives
|
||||
}
|
||||
|
||||
// ID: 6
|
||||
message DisconnectResponse {
|
||||
option (id) = 6;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Empty - Both parties are required to close the connection after this
|
||||
// message has been received.
|
||||
}
|
||||
|
||||
// ID: 7
|
||||
message PingRequest {
|
||||
option (id) = 7;
|
||||
option (source) = SOURCE_BOTH;
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ID: 8
|
||||
message PingResponse {
|
||||
option (id) = 8;
|
||||
option (source) = SOURCE_BOTH;
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ID: 9
|
||||
message DeviceInfoRequest {
|
||||
option (id) = 9;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ID: 10
|
||||
message DeviceInfoResponse {
|
||||
option (id) = 10;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
bool uses_password = 1;
|
||||
|
||||
// The name of the node, given by "App.set_name()"
|
||||
@@ -101,7 +165,7 @@ message DeviceInfoResponse {
|
||||
string mac_address = 3;
|
||||
|
||||
// A string describing the ESPHome version. For example "1.10.0"
|
||||
string esphome_core_version = 4;
|
||||
string esphome_version = 4;
|
||||
|
||||
// A string describing the date of compilation, this is generated by the compiler
|
||||
// and therefore may not be in the same format all the time.
|
||||
@@ -114,22 +178,29 @@ message DeviceInfoResponse {
|
||||
bool has_deep_sleep = 7;
|
||||
}
|
||||
|
||||
// ID: 11
|
||||
message ListEntitiesRequest {
|
||||
option (id) = 11;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
// ID: 19
|
||||
message ListEntitiesDoneResponse {
|
||||
option (id) = 19;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
// Empty
|
||||
}
|
||||
// ID: 20
|
||||
message SubscribeStatesRequest {
|
||||
option (id) = 20;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ==================== BINARY SENSOR ====================
|
||||
// ID: 12
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
option (id) = 12;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
@@ -138,15 +209,25 @@ message ListEntitiesBinarySensorResponse {
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
}
|
||||
// ID: 21
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
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
|
||||
bool missing_state = 3;
|
||||
}
|
||||
|
||||
// ==================== COVER ====================
|
||||
// ID: 13
|
||||
message ListEntitiesCoverResponse {
|
||||
option (id) = 13;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_COVER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
@@ -157,38 +238,47 @@ message ListEntitiesCoverResponse {
|
||||
bool supports_tilt = 7;
|
||||
string device_class = 8;
|
||||
}
|
||||
// ID: 22
|
||||
message CoverStateResponse {
|
||||
fixed32 key = 1;
|
||||
|
||||
enum LegacyCoverState {
|
||||
LEGACY_COVER_STATE_OPEN = 0;
|
||||
LEGACY_COVER_STATE_CLOSED = 1;
|
||||
}
|
||||
enum CoverOperation {
|
||||
COVER_OPERATION_IDLE = 0;
|
||||
COVER_OPERATION_IS_OPENING = 1;
|
||||
COVER_OPERATION_IS_CLOSING = 2;
|
||||
}
|
||||
message CoverStateResponse {
|
||||
option (id) = 22;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
// legacy: state has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
enum LegacyCoverState {
|
||||
OPEN = 0;
|
||||
CLOSED = 1;
|
||||
}
|
||||
LegacyCoverState legacy_state = 2;
|
||||
|
||||
float position = 3;
|
||||
float tilt = 4;
|
||||
enum CoverOperation {
|
||||
IDLE = 0;
|
||||
IS_OPENING = 1;
|
||||
IS_CLOSING = 2;
|
||||
}
|
||||
CoverOperation current_operation = 5;
|
||||
}
|
||||
// ID: 30
|
||||
|
||||
enum LegacyCoverCommand {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0;
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1;
|
||||
LEGACY_COVER_COMMAND_STOP = 2;
|
||||
}
|
||||
message CoverCommandRequest {
|
||||
option (id) = 30;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
|
||||
// legacy: command has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
enum LegacyCoverCommand {
|
||||
OPEN = 0;
|
||||
CLOSE = 1;
|
||||
STOP = 2;
|
||||
}
|
||||
bool has_legacy_command = 2;
|
||||
LegacyCoverCommand legacy_command = 3;
|
||||
|
||||
@@ -200,8 +290,11 @@ message CoverCommandRequest {
|
||||
}
|
||||
|
||||
// ==================== FAN ====================
|
||||
// ID: 14
|
||||
message ListEntitiesFanResponse {
|
||||
option (id) = 14;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_FAN";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
@@ -209,33 +302,56 @@ message ListEntitiesFanResponse {
|
||||
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
bool supports_direction = 7;
|
||||
int32 supported_speed_count = 8;
|
||||
}
|
||||
enum FanSpeed {
|
||||
LOW = 0;
|
||||
MEDIUM = 1;
|
||||
HIGH = 2;
|
||||
FAN_SPEED_LOW = 0;
|
||||
FAN_SPEED_MEDIUM = 1;
|
||||
FAN_SPEED_HIGH = 2;
|
||||
}
|
||||
enum FanDirection {
|
||||
FAN_DIRECTION_FORWARD = 0;
|
||||
FAN_DIRECTION_REVERSE = 1;
|
||||
}
|
||||
// ID: 23
|
||||
message FanStateResponse {
|
||||
option (id) = 23;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
FanSpeed speed = 4 [deprecated = true];
|
||||
FanDirection direction = 5;
|
||||
int32 speed_level = 6;
|
||||
}
|
||||
// ID: 31
|
||||
message FanCommandRequest {
|
||||
option (id) = 31;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_speed = 4 [deprecated = true];
|
||||
FanSpeed speed = 5 [deprecated = true];
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
bool has_direction = 8;
|
||||
FanDirection direction = 9;
|
||||
bool has_speed_level = 10;
|
||||
int32 speed_level = 11;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
// ID: 15
|
||||
message ListEntitiesLightResponse {
|
||||
option (id) = 15;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
@@ -249,8 +365,12 @@ message ListEntitiesLightResponse {
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
}
|
||||
// ID: 24
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
@@ -261,8 +381,12 @@ message LightStateResponse {
|
||||
float color_temperature = 8;
|
||||
string effect = 9;
|
||||
}
|
||||
// ID: 32
|
||||
message LightCommandRequest {
|
||||
option (id) = 32;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
@@ -285,8 +409,11 @@ message LightCommandRequest {
|
||||
}
|
||||
|
||||
// ==================== SENSOR ====================
|
||||
// ID: 16
|
||||
message ListEntitiesSensorResponse {
|
||||
option (id) = 16;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
@@ -295,16 +422,28 @@ message ListEntitiesSensorResponse {
|
||||
string icon = 5;
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
bool force_update = 8;
|
||||
string device_class = 9;
|
||||
}
|
||||
// ID: 25
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
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
|
||||
bool missing_state = 3;
|
||||
}
|
||||
|
||||
// ==================== SWITCH ====================
|
||||
// ID: 17
|
||||
message ListEntitiesSwitchResponse {
|
||||
option (id) = 17;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
@@ -313,20 +452,31 @@ message ListEntitiesSwitchResponse {
|
||||
string icon = 5;
|
||||
bool assumed_state = 6;
|
||||
}
|
||||
// ID: 26
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
// ID: 33
|
||||
message SwitchCommandRequest {
|
||||
option (id) = 33;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
// ==================== TEXT SENSOR ====================
|
||||
// ID: 18
|
||||
message ListEntitiesTextSensorResponse {
|
||||
option (id) = 18;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
@@ -334,29 +484,41 @@ message ListEntitiesTextSensorResponse {
|
||||
|
||||
string icon = 5;
|
||||
}
|
||||
// ID: 27
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
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
|
||||
bool missing_state = 3;
|
||||
}
|
||||
|
||||
// ==================== SUBSCRIBE LOGS ====================
|
||||
enum LogLevel {
|
||||
NONE = 0;
|
||||
ERROR = 1;
|
||||
WARN = 2;
|
||||
INFO = 3;
|
||||
DEBUG = 4;
|
||||
VERBOSE = 5;
|
||||
VERY_VERBOSE = 6;
|
||||
LOG_LEVEL_NONE = 0;
|
||||
LOG_LEVEL_ERROR = 1;
|
||||
LOG_LEVEL_WARN = 2;
|
||||
LOG_LEVEL_INFO = 3;
|
||||
LOG_LEVEL_DEBUG = 4;
|
||||
LOG_LEVEL_VERBOSE = 5;
|
||||
LOG_LEVEL_VERY_VERBOSE = 6;
|
||||
}
|
||||
// ID: 28
|
||||
message SubscribeLogsRequest {
|
||||
option (id) = 28;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
LogLevel level = 1;
|
||||
bool dump_config = 2;
|
||||
}
|
||||
// ID: 29
|
||||
message SubscribeLogsResponse {
|
||||
option (id) = 29;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (log) = false;
|
||||
option (no_delay) = false;
|
||||
|
||||
LogLevel level = 1;
|
||||
string tag = 2;
|
||||
string message = 3;
|
||||
@@ -364,109 +526,181 @@ message SubscribeLogsResponse {
|
||||
}
|
||||
|
||||
// ==================== HOMEASSISTANT.SERVICE ====================
|
||||
// ID: 34
|
||||
message SubscribeServiceCallsRequest {
|
||||
|
||||
message SubscribeHomeassistantServicesRequest {
|
||||
option (id) = 34;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
}
|
||||
|
||||
// ID: 35
|
||||
message ServiceCallResponse {
|
||||
message HomeassistantServiceMap {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message HomeassistantServiceResponse {
|
||||
option (id) = 35;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
string service = 1;
|
||||
map<string, string> data = 2;
|
||||
map<string, string> data_template = 3;
|
||||
map<string, string> variables = 4;
|
||||
repeated HomeassistantServiceMap data = 2;
|
||||
repeated HomeassistantServiceMap data_template = 3;
|
||||
repeated HomeassistantServiceMap variables = 4;
|
||||
bool is_event = 5;
|
||||
}
|
||||
|
||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
||||
// ID: 38
|
||||
message SubscribeHomeAssistantStatesRequest {
|
||||
|
||||
option (id) = 38;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
}
|
||||
|
||||
// ID: 39
|
||||
message SubscribeHomeAssistantStateResponse {
|
||||
option (id) = 39;
|
||||
option (source) = SOURCE_SERVER;
|
||||
string entity_id = 1;
|
||||
}
|
||||
|
||||
// ID: 40
|
||||
message HomeAssistantStateResponse {
|
||||
option (id) = 40;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
// ==================== IMPORT TIME ====================
|
||||
// ID: 36
|
||||
message GetTimeRequest {
|
||||
|
||||
option (id) = 36;
|
||||
option (source) = SOURCE_BOTH;
|
||||
}
|
||||
|
||||
// ID: 37
|
||||
message GetTimeResponse {
|
||||
option (id) = 37;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 epoch_seconds = 1;
|
||||
}
|
||||
|
||||
// ==================== USER-DEFINES SERVICES ====================
|
||||
enum ServiceArgType {
|
||||
SERVICE_ARG_TYPE_BOOL = 0;
|
||||
SERVICE_ARG_TYPE_INT = 1;
|
||||
SERVICE_ARG_TYPE_FLOAT = 2;
|
||||
SERVICE_ARG_TYPE_STRING = 3;
|
||||
SERVICE_ARG_TYPE_BOOL_ARRAY = 4;
|
||||
SERVICE_ARG_TYPE_INT_ARRAY = 5;
|
||||
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6;
|
||||
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
|
||||
}
|
||||
message ListEntitiesServicesArgument {
|
||||
string name = 1;
|
||||
enum Type {
|
||||
BOOL = 0;
|
||||
INT = 1;
|
||||
FLOAT = 2;
|
||||
STRING = 3;
|
||||
}
|
||||
Type type = 2;
|
||||
ServiceArgType type = 2;
|
||||
}
|
||||
// ID: 41
|
||||
message ListEntitiesServicesResponse {
|
||||
option (id) = 41;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
string name = 1;
|
||||
fixed32 key = 2;
|
||||
repeated ListEntitiesServicesArgument args = 3;
|
||||
}
|
||||
message ExecuteServiceArgument {
|
||||
bool bool_ = 1;
|
||||
int32 int_ = 2;
|
||||
int32 legacy_int = 2;
|
||||
float float_ = 3;
|
||||
string string_ = 4;
|
||||
// ESPHome 1.14 (api v1.3) make int a signed value
|
||||
sint32 int_ = 5;
|
||||
repeated bool bool_array = 6 [packed=false];
|
||||
repeated sint32 int_array = 7 [packed=false];
|
||||
repeated float float_array = 8 [packed=false];
|
||||
repeated string string_array = 9;
|
||||
}
|
||||
// ID: 42
|
||||
message ExecuteServiceRequest {
|
||||
option (id) = 42;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
repeated ExecuteServiceArgument args = 2;
|
||||
}
|
||||
|
||||
// ==================== CAMERA ====================
|
||||
// ID: 43
|
||||
message ListEntitiesCameraResponse {
|
||||
option (id) = 43;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
}
|
||||
|
||||
// ID: 44
|
||||
message CameraImageResponse {
|
||||
option (id) = 44;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
|
||||
fixed32 key = 1;
|
||||
bytes data = 2;
|
||||
bool done = 3;
|
||||
}
|
||||
// ID: 45
|
||||
message CameraImageRequest {
|
||||
option (id) = 45;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
option (no_delay) = true;
|
||||
|
||||
bool single = 1;
|
||||
bool stream = 2;
|
||||
}
|
||||
|
||||
// ==================== CLIMATE ====================
|
||||
enum ClimateMode {
|
||||
OFF = 0;
|
||||
AUTO = 1;
|
||||
COOL = 2;
|
||||
HEAT = 3;
|
||||
CLIMATE_MODE_OFF = 0;
|
||||
CLIMATE_MODE_AUTO = 1;
|
||||
CLIMATE_MODE_COOL = 2;
|
||||
CLIMATE_MODE_HEAT = 3;
|
||||
CLIMATE_MODE_FAN_ONLY = 4;
|
||||
CLIMATE_MODE_DRY = 5;
|
||||
}
|
||||
enum ClimateFanMode {
|
||||
CLIMATE_FAN_ON = 0;
|
||||
CLIMATE_FAN_OFF = 1;
|
||||
CLIMATE_FAN_AUTO = 2;
|
||||
CLIMATE_FAN_LOW = 3;
|
||||
CLIMATE_FAN_MEDIUM = 4;
|
||||
CLIMATE_FAN_HIGH = 5;
|
||||
CLIMATE_FAN_MIDDLE = 6;
|
||||
CLIMATE_FAN_FOCUS = 7;
|
||||
CLIMATE_FAN_DIFFUSE = 8;
|
||||
}
|
||||
enum ClimateSwingMode {
|
||||
CLIMATE_SWING_OFF = 0;
|
||||
CLIMATE_SWING_BOTH = 1;
|
||||
CLIMATE_SWING_VERTICAL = 2;
|
||||
CLIMATE_SWING_HORIZONTAL = 3;
|
||||
}
|
||||
enum ClimateAction {
|
||||
CLIMATE_ACTION_OFF = 0;
|
||||
// values same as mode for readability
|
||||
CLIMATE_ACTION_COOLING = 2;
|
||||
CLIMATE_ACTION_HEATING = 3;
|
||||
CLIMATE_ACTION_IDLE = 4;
|
||||
CLIMATE_ACTION_DRYING = 5;
|
||||
CLIMATE_ACTION_FAN = 6;
|
||||
}
|
||||
// ID: 46
|
||||
message ListEntitiesClimateResponse {
|
||||
option (id) = 46;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
@@ -479,9 +713,16 @@ message ListEntitiesClimateResponse {
|
||||
float visual_max_temperature = 9;
|
||||
float visual_temperature_step = 10;
|
||||
bool supports_away = 11;
|
||||
bool supports_action = 12;
|
||||
repeated ClimateFanMode supported_fan_modes = 13;
|
||||
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||
}
|
||||
// ID: 47
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
ClimateMode mode = 2;
|
||||
float current_temperature = 3;
|
||||
@@ -489,9 +730,16 @@ message ClimateStateResponse {
|
||||
float target_temperature_low = 5;
|
||||
float target_temperature_high = 6;
|
||||
bool away = 7;
|
||||
ClimateAction action = 8;
|
||||
ClimateFanMode fan_mode = 9;
|
||||
ClimateSwingMode swing_mode = 10;
|
||||
}
|
||||
// ID: 48
|
||||
message ClimateCommandRequest {
|
||||
option (id) = 48;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_mode = 2;
|
||||
ClimateMode mode = 3;
|
||||
@@ -503,4 +751,8 @@ message ClimateCommandRequest {
|
||||
float target_temperature_high = 9;
|
||||
bool has_away = 10;
|
||||
bool away = 11;
|
||||
bool has_fan_mode = 12;
|
||||
ClimateFanMode fan_mode = 13;
|
||||
bool has_swing_mode = 14;
|
||||
ClimateSwingMode swing_mode = 15;
|
||||
}
|
||||
|
||||
714
esphome/components/api/api_connection.cpp
Normal file
714
esphome/components/api/api_connection.cpp
Normal file
@@ -0,0 +1,714 @@
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.connection";
|
||||
|
||||
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
|
||||
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
|
||||
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
|
||||
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
|
||||
this);
|
||||
this->client_->onData([](void *s, AsyncClient *c, void *buf,
|
||||
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
|
||||
this);
|
||||
|
||||
this->send_buffer_.reserve(64);
|
||||
this->recv_buffer_.reserve(32);
|
||||
this->client_info_ = this->client_->remoteIP().toString().c_str();
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
APIConnection::~APIConnection() { delete this->client_; }
|
||||
void APIConnection::on_error_(int8_t error) { this->remove_ = true; }
|
||||
void APIConnection::on_disconnect_() { this->remove_ = true; }
|
||||
void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); }
|
||||
void APIConnection::on_data_(uint8_t *buf, size_t len) {
|
||||
if (len == 0 || buf == nullptr)
|
||||
return;
|
||||
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
|
||||
}
|
||||
void APIConnection::parse_recv_buffer_() {
|
||||
if (this->recv_buffer_.empty() || this->remove_)
|
||||
return;
|
||||
|
||||
while (!this->recv_buffer_.empty()) {
|
||||
if (this->recv_buffer_[0] != 0x00) {
|
||||
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
uint32_t i = 1;
|
||||
const uint32_t size = this->recv_buffer_.size();
|
||||
uint32_t consumed;
|
||||
auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
|
||||
if (!msg_size_varint.has_value())
|
||||
// not enough data there yet
|
||||
return;
|
||||
i += consumed;
|
||||
uint32_t msg_size = msg_size_varint->as_uint32();
|
||||
|
||||
auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
|
||||
if (!msg_type_varint.has_value())
|
||||
// not enough data there yet
|
||||
return;
|
||||
i += consumed;
|
||||
uint32_t msg_type = msg_type_varint->as_uint32();
|
||||
|
||||
if (size - i < msg_size)
|
||||
// message body not fully received
|
||||
return;
|
||||
|
||||
uint8_t *msg = &this->recv_buffer_[i];
|
||||
this->read_message(msg_size, msg_type, msg);
|
||||
if (this->remove_)
|
||||
return;
|
||||
// pop front
|
||||
uint32_t total = i + msg_size;
|
||||
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::disconnect_client() {
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->remove_)
|
||||
return;
|
||||
|
||||
if (this->next_close_) {
|
||||
this->disconnect_client();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!network_is_connected()) {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
if (this->client_->disconnected()) {
|
||||
// failsafe for disconnect logic
|
||||
this->on_disconnect_();
|
||||
return;
|
||||
}
|
||||
this->parse_recv_buffer_();
|
||||
|
||||
this->list_entities_iterator_.advance();
|
||||
this->initial_state_iterator_.advance();
|
||||
|
||||
const uint32_t keepalive = 60000;
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (millis() - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
this->disconnect_client();
|
||||
}
|
||||
} else if (millis() - this->last_traffic_ > keepalive) {
|
||||
this->sent_ping_ = true;
|
||||
this->send_ping_request(PingRequest());
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (this->image_reader_.available()) {
|
||||
uint32_t space = this->client_->space();
|
||||
// reserve 15 bytes for metadata, and at least 64 bytes of data
|
||||
if (space >= 15 + 64) {
|
||||
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
|
||||
auto buffer = this->create_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
|
||||
// bytes data = 2;
|
||||
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
|
||||
// bool done = 3;
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
buffer.encode_bool(3, done);
|
||||
bool success = this->send_buffer(buffer, 44);
|
||||
|
||||
if (success) {
|
||||
this->image_reader_.consume_data(to_send);
|
||||
}
|
||||
if (success && done) {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
BinarySensorStateResponse resp;
|
||||
resp.key = binary_sensor->get_object_id_hash();
|
||||
resp.state = state;
|
||||
resp.missing_state = !binary_sensor->has_state();
|
||||
return this->send_binary_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
|
||||
ListEntitiesBinarySensorResponse msg;
|
||||
msg.object_id = binary_sensor->get_object_id();
|
||||
msg.key = binary_sensor->get_object_id_hash();
|
||||
msg.name = binary_sensor->get_name();
|
||||
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
|
||||
msg.device_class = binary_sensor->get_device_class();
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
return this->send_list_entities_binary_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = cover->get_traits();
|
||||
CoverStateResponse resp{};
|
||||
resp.key = cover->get_object_id_hash();
|
||||
resp.legacy_state =
|
||||
(cover->position == cover::COVER_OPEN) ? enums::LEGACY_COVER_STATE_OPEN : enums::LEGACY_COVER_STATE_CLOSED;
|
||||
resp.position = cover->position;
|
||||
if (traits.get_supports_tilt())
|
||||
resp.tilt = cover->tilt;
|
||||
resp.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
|
||||
return this->send_cover_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
auto traits = cover->get_traits();
|
||||
ListEntitiesCoverResponse msg;
|
||||
msg.key = cover->get_object_id_hash();
|
||||
msg.object_id = cover->get_object_id();
|
||||
msg.name = cover->get_name();
|
||||
msg.unique_id = get_default_unique_id("cover", cover);
|
||||
msg.assumed_state = traits.get_is_assumed_state();
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
msg.supports_tilt = traits.get_supports_tilt();
|
||||
msg.device_class = cover->get_device_class();
|
||||
return this->send_list_entities_cover_response(msg);
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
cover::Cover *cover = App.get_cover_by_key(msg.key);
|
||||
if (cover == nullptr)
|
||||
return;
|
||||
|
||||
auto call = cover->make_call();
|
||||
if (msg.has_legacy_command) {
|
||||
switch (msg.legacy_command) {
|
||||
case enums::LEGACY_COVER_COMMAND_OPEN:
|
||||
call.set_command_open();
|
||||
break;
|
||||
case enums::LEGACY_COVER_COMMAND_CLOSE:
|
||||
call.set_command_close();
|
||||
break;
|
||||
case enums::LEGACY_COVER_COMMAND_STOP:
|
||||
call.set_command_stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (msg.has_position)
|
||||
call.set_position(msg.position);
|
||||
if (msg.has_tilt)
|
||||
call.set_tilt(msg.tilt);
|
||||
if (msg.stop)
|
||||
call.set_command_stop();
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = fan->get_traits();
|
||||
FanStateResponse resp{};
|
||||
resp.key = fan->get_object_id_hash();
|
||||
resp.state = fan->state;
|
||||
if (traits.supports_oscillation())
|
||||
resp.oscillating = fan->oscillating;
|
||||
if (traits.supports_speed()) {
|
||||
resp.speed_level = fan->speed;
|
||||
resp.speed = static_cast<enums::FanSpeed>(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count()));
|
||||
}
|
||||
if (traits.supports_direction())
|
||||
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
return this->send_fan_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||
auto traits = fan->get_traits();
|
||||
ListEntitiesFanResponse msg;
|
||||
msg.key = fan->get_object_id_hash();
|
||||
msg.object_id = fan->get_object_id();
|
||||
msg.name = fan->get_name();
|
||||
msg.unique_id = get_default_unique_id("fan", fan);
|
||||
msg.supports_oscillation = traits.supports_oscillation();
|
||||
msg.supports_speed = traits.supports_speed();
|
||||
msg.supports_direction = traits.supports_direction();
|
||||
msg.supported_speed_count = traits.supported_speed_count();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
fan::FanState *fan = App.get_fan_by_key(msg.key);
|
||||
if (fan == nullptr)
|
||||
return;
|
||||
|
||||
auto traits = fan->get_traits();
|
||||
|
||||
auto call = fan->make_call();
|
||||
if (msg.has_state)
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_oscillating)
|
||||
call.set_oscillating(msg.oscillating);
|
||||
if (msg.has_speed_level) {
|
||||
// Prefer level
|
||||
call.set_speed(msg.speed_level);
|
||||
} else if (msg.has_speed) {
|
||||
call.set_speed(fan::speed_enum_to_level(static_cast<fan::FanSpeed>(msg.speed), traits.supported_speed_count()));
|
||||
}
|
||||
if (msg.has_direction)
|
||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
bool APIConnection::send_light_state(light::LightState *light) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = light->get_traits();
|
||||
auto values = light->remote_values;
|
||||
LightStateResponse resp{};
|
||||
|
||||
resp.key = light->get_object_id_hash();
|
||||
resp.state = values.is_on();
|
||||
if (traits.get_supports_brightness())
|
||||
resp.brightness = values.get_brightness();
|
||||
if (traits.get_supports_rgb()) {
|
||||
resp.red = values.get_red();
|
||||
resp.green = values.get_green();
|
||||
resp.blue = values.get_blue();
|
||||
}
|
||||
if (traits.get_supports_rgb_white_value())
|
||||
resp.white = values.get_white();
|
||||
if (traits.get_supports_color_temperature())
|
||||
resp.color_temperature = values.get_color_temperature();
|
||||
if (light->supports_effects())
|
||||
resp.effect = light->get_effect_name();
|
||||
return this->send_light_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_light_info(light::LightState *light) {
|
||||
auto traits = light->get_traits();
|
||||
ListEntitiesLightResponse msg;
|
||||
msg.key = light->get_object_id_hash();
|
||||
msg.object_id = light->get_object_id();
|
||||
msg.name = light->get_name();
|
||||
msg.unique_id = get_default_unique_id("light", light);
|
||||
msg.supports_brightness = traits.get_supports_brightness();
|
||||
msg.supports_rgb = traits.get_supports_rgb();
|
||||
msg.supports_white_value = traits.get_supports_rgb_white_value();
|
||||
msg.supports_color_temperature = traits.get_supports_color_temperature();
|
||||
if (msg.supports_color_temperature) {
|
||||
msg.min_mireds = traits.get_min_mireds();
|
||||
msg.max_mireds = traits.get_max_mireds();
|
||||
}
|
||||
if (light->supports_effects()) {
|
||||
msg.effects.emplace_back("None");
|
||||
for (auto *effect : light->get_effects())
|
||||
msg.effects.push_back(effect->get_name());
|
||||
}
|
||||
return this->send_list_entities_light_response(msg);
|
||||
}
|
||||
void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
light::LightState *light = App.get_light_by_key(msg.key);
|
||||
if (light == nullptr)
|
||||
return;
|
||||
|
||||
auto call = light->make_call();
|
||||
if (msg.has_state)
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_brightness)
|
||||
call.set_brightness(msg.brightness);
|
||||
if (msg.has_rgb) {
|
||||
call.set_red(msg.red);
|
||||
call.set_green(msg.green);
|
||||
call.set_blue(msg.blue);
|
||||
}
|
||||
if (msg.has_white)
|
||||
call.set_white(msg.white);
|
||||
if (msg.has_color_temperature)
|
||||
call.set_color_temperature(msg.color_temperature);
|
||||
if (msg.has_transition_length)
|
||||
call.set_transition_length(msg.transition_length);
|
||||
if (msg.has_flash_length)
|
||||
call.set_flash_length(msg.flash_length);
|
||||
if (msg.has_effect)
|
||||
call.set_effect(msg.effect);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
SensorStateResponse resp{};
|
||||
resp.key = sensor->get_object_id_hash();
|
||||
resp.state = state;
|
||||
resp.missing_state = !sensor->has_state();
|
||||
return this->send_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
ListEntitiesSensorResponse msg;
|
||||
msg.key = sensor->get_object_id_hash();
|
||||
msg.object_id = sensor->get_object_id();
|
||||
msg.name = sensor->get_name();
|
||||
msg.unique_id = sensor->unique_id();
|
||||
if (msg.unique_id.empty())
|
||||
msg.unique_id = get_default_unique_id("sensor", sensor);
|
||||
msg.icon = sensor->get_icon();
|
||||
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||
msg.force_update = sensor->get_force_update();
|
||||
msg.device_class = sensor->get_device_class();
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
SwitchStateResponse resp{};
|
||||
resp.key = a_switch->get_object_id_hash();
|
||||
resp.state = state;
|
||||
return this->send_switch_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
ListEntitiesSwitchResponse msg;
|
||||
msg.key = a_switch->get_object_id_hash();
|
||||
msg.object_id = a_switch->get_object_id();
|
||||
msg.name = a_switch->get_name();
|
||||
msg.unique_id = get_default_unique_id("switch", a_switch);
|
||||
msg.icon = a_switch->get_icon();
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
return this->send_list_entities_switch_response(msg);
|
||||
}
|
||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
|
||||
if (a_switch == nullptr)
|
||||
return;
|
||||
|
||||
if (msg.state)
|
||||
a_switch->turn_on();
|
||||
else
|
||||
a_switch->turn_off();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
TextSensorStateResponse resp{};
|
||||
resp.key = text_sensor->get_object_id_hash();
|
||||
resp.state = std::move(state);
|
||||
resp.missing_state = !text_sensor->has_state();
|
||||
return this->send_text_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
|
||||
ListEntitiesTextSensorResponse msg;
|
||||
msg.key = text_sensor->get_object_id_hash();
|
||||
msg.object_id = text_sensor->get_object_id();
|
||||
msg.name = text_sensor->get_name();
|
||||
msg.unique_id = text_sensor->unique_id();
|
||||
if (msg.unique_id.empty())
|
||||
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
|
||||
msg.icon = text_sensor->get_icon();
|
||||
return this->send_list_entities_text_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = climate->get_traits();
|
||||
ClimateStateResponse resp{};
|
||||
resp.key = climate->get_object_id_hash();
|
||||
resp.mode = static_cast<enums::ClimateMode>(climate->mode);
|
||||
resp.action = static_cast<enums::ClimateAction>(climate->action);
|
||||
if (traits.get_supports_current_temperature())
|
||||
resp.current_temperature = climate->current_temperature;
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
resp.target_temperature_low = climate->target_temperature_low;
|
||||
resp.target_temperature_high = climate->target_temperature_high;
|
||||
} else {
|
||||
resp.target_temperature = climate->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_away())
|
||||
resp.away = climate->away;
|
||||
if (traits.get_supports_fan_modes())
|
||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode);
|
||||
if (traits.get_supports_swing_modes())
|
||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||
return this->send_climate_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
auto traits = climate->get_traits();
|
||||
ListEntitiesClimateResponse msg;
|
||||
msg.key = climate->get_object_id_hash();
|
||||
msg.object_id = climate->get_object_id();
|
||||
msg.name = climate->get_name();
|
||||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) {
|
||||
if (traits.supports_mode(mode))
|
||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||
}
|
||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
||||
msg.supports_away = traits.get_supports_away();
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO,
|
||||
climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
|
||||
climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) {
|
||||
if (traits.supports_fan_mode(fan_mode))
|
||||
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
||||
}
|
||||
for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL}) {
|
||||
if (traits.supports_swing_mode(swing_mode))
|
||||
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
|
||||
}
|
||||
return this->send_list_entities_climate_response(msg);
|
||||
}
|
||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
climate::Climate *climate = App.get_climate_by_key(msg.key);
|
||||
if (climate == nullptr)
|
||||
return;
|
||||
|
||||
auto call = climate->make_call();
|
||||
if (msg.has_mode)
|
||||
call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
|
||||
if (msg.has_target_temperature)
|
||||
call.set_target_temperature(msg.target_temperature);
|
||||
if (msg.has_target_temperature_low)
|
||||
call.set_target_temperature_low(msg.target_temperature_low);
|
||||
if (msg.has_target_temperature_high)
|
||||
call.set_target_temperature_high(msg.target_temperature_high);
|
||||
if (msg.has_away)
|
||||
call.set_away(msg.away);
|
||||
if (msg.has_fan_mode)
|
||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||
if (msg.has_swing_mode)
|
||||
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
return;
|
||||
if (this->image_reader_.available())
|
||||
return;
|
||||
this->image_reader_.set_image(image);
|
||||
}
|
||||
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
ListEntitiesCameraResponse msg;
|
||||
msg.key = camera->get_object_id_hash();
|
||||
msg.object_id = camera->get_object_id();
|
||||
msg.name = camera->get_name();
|
||||
msg.unique_id = get_default_unique_id("camera", camera);
|
||||
return this->send_list_entities_camera_response(msg);
|
||||
}
|
||||
void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||
if (esp32_camera::global_esp32_camera == nullptr)
|
||||
return;
|
||||
|
||||
if (msg.single)
|
||||
esp32_camera::global_esp32_camera->request_image();
|
||||
if (msg.stream)
|
||||
esp32_camera::global_esp32_camera->request_stream();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||
if (homeassistant::global_homeassistant_time != nullptr)
|
||||
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
|
||||
// Send raw so that we don't copy too much
|
||||
auto buffer = this->create_buffer();
|
||||
// LogLevel level = 1;
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
||||
// string tag = 2;
|
||||
// buffer.encode_string(2, tag, strlen(tag));
|
||||
// string message = 3;
|
||||
buffer.encode_string(3, line, strlen(line));
|
||||
// SubscribeLogsResponse - 29
|
||||
bool success = this->send_buffer(buffer, 29);
|
||||
if (!success) {
|
||||
buffer = this->create_buffer();
|
||||
// bool send_failed = 4;
|
||||
buffer.encode_bool(4, true);
|
||||
return this->send_buffer(buffer, 29);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str();
|
||||
this->client_info_ += ")";
|
||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 4;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
}
|
||||
ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
||||
bool correct = this->parent_->check_password(msg.password);
|
||||
|
||||
ConnectResponse resp;
|
||||
// bool invalid_password = 1;
|
||||
resp.invalid_password = !correct;
|
||||
if (correct) {
|
||||
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
||||
this->connection_state_ = ConnectionState::AUTHENTICATED;
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
this->send_time_request();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
DeviceInfoResponse resp{};
|
||||
resp.uses_password = this->parent_->uses_password();
|
||||
resp.name = App.get_name();
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
#ifdef ARDUINO_BOARD
|
||||
resp.model = ARDUINO_BOARD;
|
||||
#endif
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs())
|
||||
if (it.entity_id == msg.entity_id)
|
||||
it.callback(msg.state);
|
||||
}
|
||||
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
bool found = false;
|
||||
for (auto *service : this->parent_->get_user_services()) {
|
||||
if (service->execute_service(msg)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGV(TAG, "Could not find matching service!");
|
||||
}
|
||||
}
|
||||
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
|
||||
if (this->remove_)
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> header;
|
||||
header.push_back(0x00);
|
||||
ProtoVarInt(buffer.get_buffer()->size()).encode(header);
|
||||
ProtoVarInt(message_type).encode(header);
|
||||
|
||||
size_t needed_space = buffer.get_buffer()->size() + header.size();
|
||||
|
||||
if (needed_space > this->client_->space()) {
|
||||
delay(0);
|
||||
if (needed_space > this->client_->space()) {
|
||||
// SubscribeLogsResponse
|
||||
if (message_type != 29) {
|
||||
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
|
||||
}
|
||||
delay(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->client_->add(reinterpret_cast<char *>(header.data()), header.size(),
|
||||
ASYNC_WRITE_FLAG_COPY | ASYNC_WRITE_FLAG_MORE);
|
||||
this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size(),
|
||||
ASYNC_WRITE_FLAG_COPY);
|
||||
bool ret = this->client_->send();
|
||||
return ret;
|
||||
}
|
||||
void APIConnection::on_unauthenticated_access() {
|
||||
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
}
|
||||
void APIConnection::on_no_setup_connection() {
|
||||
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
}
|
||||
void APIConnection::on_fatal_error() {
|
||||
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
172
esphome/components/api/api_connection.h
Normal file
172
esphome/components/api/api_connection.h
Normal file
@@ -0,0 +1,172 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIConnection : public APIServerConnection {
|
||||
public:
|
||||
APIConnection(AsyncClient *client, APIServer *parent);
|
||||
virtual ~APIConnection();
|
||||
|
||||
void disconnect_client();
|
||||
void loop();
|
||||
|
||||
bool send_list_info_done() {
|
||||
ListEntitiesDoneResponse resp;
|
||||
return this->send_list_entities_done_response(resp);
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
|
||||
bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state(cover::Cover *cover);
|
||||
bool send_cover_info(cover::Cover *cover);
|
||||
void cover_command(const CoverCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state(fan::FanState *fan);
|
||||
bool send_fan_info(fan::FanState *fan);
|
||||
void fan_command(const FanCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state(light::LightState *light);
|
||||
bool send_light_info(light::LightState *light);
|
||||
void light_command(const LightCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state(sensor::Sensor *sensor, float state);
|
||||
bool send_sensor_info(sensor::Sensor *sensor);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state(switch_::Switch *a_switch, bool state);
|
||||
bool send_switch_info(switch_::Switch *a_switch);
|
||||
void switch_command(const SwitchCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
|
||||
bool send_text_sensor_info(text_sensor::TextSensor *text_sensor);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
||||
bool send_camera_info(esp32_camera::ESP32Camera *camera);
|
||||
void camera_image(const CameraImageRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
bool send_climate_info(climate::Climate *climate);
|
||||
void climate_command(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
if (!this->service_call_subscription_)
|
||||
return;
|
||||
this->send_homeassistant_service_response(call);
|
||||
}
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void send_time_request() {
|
||||
GetTimeRequest req;
|
||||
this->send_get_time_request(req);
|
||||
}
|
||||
#endif
|
||||
|
||||
void on_disconnect_response(const DisconnectResponse &value) override {
|
||||
// we initiated disconnect_client
|
||||
this->next_close_ = true;
|
||||
}
|
||||
void on_ping_response(const PingResponse &value) override {
|
||||
// we initiated ping
|
||||
this->sent_ping_ = false;
|
||||
}
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void on_get_time_response(const GetTimeResponse &value) override;
|
||||
#endif
|
||||
HelloResponse hello(const HelloRequest &msg) override;
|
||||
ConnectResponse connect(const ConnectRequest &msg) override;
|
||||
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
|
||||
// remote initiated disconnect_client
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
}
|
||||
PingResponse ping(const PingRequest &msg) override { return {}; }
|
||||
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
||||
this->state_subscription_ = true;
|
||||
this->initial_state_iterator_.begin();
|
||||
}
|
||||
void subscribe_logs(const SubscribeLogsRequest &msg) override {
|
||||
this->log_subscription_ = msg.level;
|
||||
if (msg.dump_config)
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
|
||||
this->service_call_subscription_ = true;
|
||||
}
|
||||
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
GetTimeResponse get_time(const GetTimeRequest &msg) override {
|
||||
// TODO
|
||||
return {};
|
||||
}
|
||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
|
||||
bool is_connection_setup() override {
|
||||
return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
|
||||
}
|
||||
void on_fatal_error() override;
|
||||
void on_unauthenticated_access() override;
|
||||
void on_no_setup_connection() override;
|
||||
ProtoWriteBuffer create_buffer() override {
|
||||
this->send_buffer_.clear();
|
||||
return {&this->send_buffer_};
|
||||
}
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
|
||||
|
||||
protected:
|
||||
friend APIServer;
|
||||
|
||||
void on_error_(int8_t error);
|
||||
void on_disconnect_();
|
||||
void on_timeout_(uint32_t time);
|
||||
void on_data_(uint8_t *buf, size_t len);
|
||||
void parse_recv_buffer_();
|
||||
|
||||
enum class ConnectionState {
|
||||
WAITING_FOR_HELLO,
|
||||
CONNECTED,
|
||||
AUTHENTICATED,
|
||||
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
|
||||
|
||||
bool remove_{false};
|
||||
|
||||
std::vector<uint8_t> send_buffer_;
|
||||
std::vector<uint8_t> recv_buffer_;
|
||||
|
||||
std::string client_info_;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
esp32_camera::CameraImageReader image_reader_;
|
||||
#endif
|
||||
|
||||
bool state_subscription_{false};
|
||||
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
bool current_nodelay_{false};
|
||||
bool next_close_{false};
|
||||
AsyncClient *client_;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -1,79 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
enum class APIMessageType {
|
||||
HELLO_REQUEST = 1,
|
||||
HELLO_RESPONSE = 2,
|
||||
CONNECT_REQUEST = 3,
|
||||
CONNECT_RESPONSE = 4,
|
||||
DISCONNECT_REQUEST = 5,
|
||||
DISCONNECT_RESPONSE = 6,
|
||||
PING_REQUEST = 7,
|
||||
PING_RESPONSE = 8,
|
||||
DEVICE_INFO_REQUEST = 9,
|
||||
DEVICE_INFO_RESPONSE = 10,
|
||||
|
||||
LIST_ENTITIES_REQUEST = 11,
|
||||
LIST_ENTITIES_BINARY_SENSOR_RESPONSE = 12,
|
||||
LIST_ENTITIES_COVER_RESPONSE = 13,
|
||||
LIST_ENTITIES_FAN_RESPONSE = 14,
|
||||
LIST_ENTITIES_LIGHT_RESPONSE = 15,
|
||||
LIST_ENTITIES_SENSOR_RESPONSE = 16,
|
||||
LIST_ENTITIES_SWITCH_RESPONSE = 17,
|
||||
LIST_ENTITIES_TEXT_SENSOR_RESPONSE = 18,
|
||||
LIST_ENTITIES_SERVICE_RESPONSE = 41,
|
||||
LIST_ENTITIES_CAMERA_RESPONSE = 43,
|
||||
LIST_ENTITIES_CLIMATE_RESPONSE = 46,
|
||||
LIST_ENTITIES_DONE_RESPONSE = 19,
|
||||
|
||||
SUBSCRIBE_STATES_REQUEST = 20,
|
||||
BINARY_SENSOR_STATE_RESPONSE = 21,
|
||||
COVER_STATE_RESPONSE = 22,
|
||||
FAN_STATE_RESPONSE = 23,
|
||||
LIGHT_STATE_RESPONSE = 24,
|
||||
SENSOR_STATE_RESPONSE = 25,
|
||||
SWITCH_STATE_RESPONSE = 26,
|
||||
TEXT_SENSOR_STATE_RESPONSE = 27,
|
||||
CAMERA_IMAGE_RESPONSE = 44,
|
||||
CLIMATE_STATE_RESPONSE = 47,
|
||||
|
||||
SUBSCRIBE_LOGS_REQUEST = 28,
|
||||
SUBSCRIBE_LOGS_RESPONSE = 29,
|
||||
|
||||
COVER_COMMAND_REQUEST = 30,
|
||||
FAN_COMMAND_REQUEST = 31,
|
||||
LIGHT_COMMAND_REQUEST = 32,
|
||||
SWITCH_COMMAND_REQUEST = 33,
|
||||
CAMERA_IMAGE_REQUEST = 45,
|
||||
CLIMATE_COMMAND_REQUEST = 48,
|
||||
|
||||
SUBSCRIBE_SERVICE_CALLS_REQUEST = 34,
|
||||
SERVICE_CALL_RESPONSE = 35,
|
||||
GET_TIME_REQUEST = 36,
|
||||
GET_TIME_RESPONSE = 37,
|
||||
|
||||
SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST = 38,
|
||||
SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE = 39,
|
||||
HOME_ASSISTANT_STATE_RESPONSE = 40,
|
||||
|
||||
EXECUTE_SERVICE_REQUEST = 42,
|
||||
};
|
||||
|
||||
class APIMessage {
|
||||
public:
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
virtual bool decode_varint(uint32_t field_id, uint32_t value);
|
||||
virtual bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len);
|
||||
virtual bool decode_32bit(uint32_t field_id, uint32_t value);
|
||||
virtual APIMessageType message_type() const = 0;
|
||||
|
||||
virtual void encode(APIBuffer &buffer);
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
24
esphome/components/api/api_options.proto
Normal file
24
esphome/components/api/api_options.proto
Normal file
@@ -0,0 +1,24 @@
|
||||
syntax = "proto2";
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
|
||||
enum APISourceType {
|
||||
SOURCE_BOTH = 0;
|
||||
SOURCE_SERVER = 1;
|
||||
SOURCE_CLIENT = 2;
|
||||
}
|
||||
|
||||
message void {}
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
optional bool needs_setup_connection = 1038 [default=true];
|
||||
optional bool needs_authentication = 1039 [default=true];
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
optional uint32 id = 1036 [default=0];
|
||||
optional APISourceType source = 1037 [default=SOURCE_BOTH];
|
||||
optional string ifdef = 1038;
|
||||
optional bool log = 1039 [default=true];
|
||||
optional bool no_delay = 1040 [default=false];
|
||||
}
|
||||
3021
esphome/components/api/api_pb2.cpp
Normal file
3021
esphome/components/api/api_pb2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
750
esphome/components/api/api_pb2.h
Normal file
750
esphome/components/api/api_pb2.h
Normal file
@@ -0,0 +1,750 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See scripts/api_protobuf/api_protobuf.py
|
||||
#pragma once
|
||||
|
||||
#include "proto.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
namespace enums {
|
||||
|
||||
enum LegacyCoverState : uint32_t {
|
||||
LEGACY_COVER_STATE_OPEN = 0,
|
||||
LEGACY_COVER_STATE_CLOSED = 1,
|
||||
};
|
||||
enum CoverOperation : uint32_t {
|
||||
COVER_OPERATION_IDLE = 0,
|
||||
COVER_OPERATION_IS_OPENING = 1,
|
||||
COVER_OPERATION_IS_CLOSING = 2,
|
||||
};
|
||||
enum LegacyCoverCommand : uint32_t {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0,
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
||||
LEGACY_COVER_COMMAND_STOP = 2,
|
||||
};
|
||||
enum FanSpeed : uint32_t {
|
||||
FAN_SPEED_LOW = 0,
|
||||
FAN_SPEED_MEDIUM = 1,
|
||||
FAN_SPEED_HIGH = 2,
|
||||
};
|
||||
enum FanDirection : uint32_t {
|
||||
FAN_DIRECTION_FORWARD = 0,
|
||||
FAN_DIRECTION_REVERSE = 1,
|
||||
};
|
||||
enum LogLevel : uint32_t {
|
||||
LOG_LEVEL_NONE = 0,
|
||||
LOG_LEVEL_ERROR = 1,
|
||||
LOG_LEVEL_WARN = 2,
|
||||
LOG_LEVEL_INFO = 3,
|
||||
LOG_LEVEL_DEBUG = 4,
|
||||
LOG_LEVEL_VERBOSE = 5,
|
||||
LOG_LEVEL_VERY_VERBOSE = 6,
|
||||
};
|
||||
enum ServiceArgType : uint32_t {
|
||||
SERVICE_ARG_TYPE_BOOL = 0,
|
||||
SERVICE_ARG_TYPE_INT = 1,
|
||||
SERVICE_ARG_TYPE_FLOAT = 2,
|
||||
SERVICE_ARG_TYPE_STRING = 3,
|
||||
SERVICE_ARG_TYPE_BOOL_ARRAY = 4,
|
||||
SERVICE_ARG_TYPE_INT_ARRAY = 5,
|
||||
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6,
|
||||
SERVICE_ARG_TYPE_STRING_ARRAY = 7,
|
||||
};
|
||||
enum ClimateMode : uint32_t {
|
||||
CLIMATE_MODE_OFF = 0,
|
||||
CLIMATE_MODE_AUTO = 1,
|
||||
CLIMATE_MODE_COOL = 2,
|
||||
CLIMATE_MODE_HEAT = 3,
|
||||
CLIMATE_MODE_FAN_ONLY = 4,
|
||||
CLIMATE_MODE_DRY = 5,
|
||||
};
|
||||
enum ClimateFanMode : uint32_t {
|
||||
CLIMATE_FAN_ON = 0,
|
||||
CLIMATE_FAN_OFF = 1,
|
||||
CLIMATE_FAN_AUTO = 2,
|
||||
CLIMATE_FAN_LOW = 3,
|
||||
CLIMATE_FAN_MEDIUM = 4,
|
||||
CLIMATE_FAN_HIGH = 5,
|
||||
CLIMATE_FAN_MIDDLE = 6,
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
};
|
||||
enum ClimateSwingMode : uint32_t {
|
||||
CLIMATE_SWING_OFF = 0,
|
||||
CLIMATE_SWING_BOTH = 1,
|
||||
CLIMATE_SWING_VERTICAL = 2,
|
||||
CLIMATE_SWING_HORIZONTAL = 3,
|
||||
};
|
||||
enum ClimateAction : uint32_t {
|
||||
CLIMATE_ACTION_OFF = 0,
|
||||
CLIMATE_ACTION_COOLING = 2,
|
||||
CLIMATE_ACTION_HEATING = 3,
|
||||
CLIMATE_ACTION_IDLE = 4,
|
||||
CLIMATE_ACTION_DRYING = 5,
|
||||
CLIMATE_ACTION_FAN = 6,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string client_info{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HelloResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t api_version_major{0}; // NOLINT
|
||||
uint32_t api_version_minor{0}; // NOLINT
|
||||
std::string server_info{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ConnectRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string password{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ConnectResponse : public ProtoMessage {
|
||||
public:
|
||||
bool invalid_password{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DisconnectRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DisconnectResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class PingRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class PingResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DeviceInfoRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DeviceInfoResponse : public ProtoMessage {
|
||||
public:
|
||||
bool uses_password{false}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string mac_address{}; // NOLINT
|
||||
std::string esphome_version{}; // NOLINT
|
||||
std::string compilation_time{}; // NOLINT
|
||||
std::string model{}; // NOLINT
|
||||
bool has_deep_sleep{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class ListEntitiesDoneResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class SubscribeStatesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
bool is_status_binary_sensor{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BinarySensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool assumed_state{false}; // NOLINT
|
||||
bool supports_position{false}; // NOLINT
|
||||
bool supports_tilt{false}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CoverStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
enums::LegacyCoverState legacy_state{}; // NOLINT
|
||||
float position{0.0f}; // NOLINT
|
||||
float tilt{0.0f}; // NOLINT
|
||||
enums::CoverOperation current_operation{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CoverCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_legacy_command{false}; // NOLINT
|
||||
enums::LegacyCoverCommand legacy_command{}; // NOLINT
|
||||
bool has_position{false}; // NOLINT
|
||||
float position{0.0f}; // NOLINT
|
||||
bool has_tilt{false}; // NOLINT
|
||||
float tilt{0.0f}; // NOLINT
|
||||
bool stop{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesFanResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_oscillation{false}; // NOLINT
|
||||
bool supports_speed{false}; // NOLINT
|
||||
bool supports_direction{false}; // NOLINT
|
||||
int32_t supported_speed_count{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
int32_t speed_level{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_speed{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
bool has_oscillating{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
bool has_direction{false}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
bool has_speed_level{false}; // NOLINT
|
||||
int32_t speed_level{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesLightResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_brightness{false}; // NOLINT
|
||||
bool supports_rgb{false}; // NOLINT
|
||||
bool supports_white_value{false}; // NOLINT
|
||||
bool supports_color_temperature{false}; // NOLINT
|
||||
float min_mireds{0.0f}; // NOLINT
|
||||
float max_mireds{0.0f}; // NOLINT
|
||||
std::vector<std::string> effects{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LightStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
float brightness{0.0f}; // NOLINT
|
||||
float red{0.0f}; // NOLINT
|
||||
float green{0.0f}; // NOLINT
|
||||
float blue{0.0f}; // NOLINT
|
||||
float white{0.0f}; // NOLINT
|
||||
float color_temperature{0.0f}; // NOLINT
|
||||
std::string effect{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LightCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_brightness{false}; // NOLINT
|
||||
float brightness{0.0f}; // NOLINT
|
||||
bool has_rgb{false}; // NOLINT
|
||||
float red{0.0f}; // NOLINT
|
||||
float green{0.0f}; // NOLINT
|
||||
float blue{0.0f}; // NOLINT
|
||||
bool has_white{false}; // NOLINT
|
||||
float white{0.0f}; // NOLINT
|
||||
bool has_color_temperature{false}; // NOLINT
|
||||
float color_temperature{0.0f}; // NOLINT
|
||||
bool has_transition_length{false}; // NOLINT
|
||||
uint32_t transition_length{0}; // NOLINT
|
||||
bool has_flash_length{false}; // NOLINT
|
||||
uint32_t flash_length{0}; // NOLINT
|
||||
bool has_effect{false}; // NOLINT
|
||||
std::string effect{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
std::string unit_of_measurement{}; // NOLINT
|
||||
int32_t accuracy_decimals{0}; // NOLINT
|
||||
bool force_update{false}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
float state{0.0f}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
bool assumed_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SwitchStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SwitchCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class TextSensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string state{}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeLogsRequest : public ProtoMessage {
|
||||
public:
|
||||
enums::LogLevel level{}; // NOLINT
|
||||
bool dump_config{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeLogsResponse : public ProtoMessage {
|
||||
public:
|
||||
enums::LogLevel level{}; // NOLINT
|
||||
std::string tag{}; // NOLINT
|
||||
std::string message{}; // NOLINT
|
||||
bool send_failed{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class HomeassistantServiceMap : public ProtoMessage {
|
||||
public:
|
||||
std::string key{}; // NOLINT
|
||||
std::string value{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HomeassistantServiceResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string service{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> data{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
|
||||
bool is_event{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string entity_id{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HomeAssistantStateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string entity_id{}; // NOLINT
|
||||
std::string state{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class GetTimeRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class GetTimeResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t epoch_seconds{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
};
|
||||
class ListEntitiesServicesArgument : public ProtoMessage {
|
||||
public:
|
||||
std::string name{}; // NOLINT
|
||||
enums::ServiceArgType type{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesServicesResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string name{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ExecuteServiceArgument : public ProtoMessage {
|
||||
public:
|
||||
bool bool_{false}; // NOLINT
|
||||
int32_t legacy_int{0}; // NOLINT
|
||||
float float_{0.0f}; // NOLINT
|
||||
std::string string_{}; // NOLINT
|
||||
int32_t int_{0}; // NOLINT
|
||||
std::vector<bool> bool_array{}; // NOLINT
|
||||
std::vector<int32_t> int_array{}; // NOLINT
|
||||
std::vector<float> float_array{}; // NOLINT
|
||||
std::vector<std::string> string_array{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ExecuteServiceRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::vector<ExecuteServiceArgument> args{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesCameraResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class CameraImageResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string data{}; // NOLINT
|
||||
bool done{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CameraImageRequest : public ProtoMessage {
|
||||
public:
|
||||
bool single{false}; // NOLINT
|
||||
bool stream{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_current_temperature{false}; // NOLINT
|
||||
bool supports_two_point_target_temperature{false}; // NOLINT
|
||||
std::vector<enums::ClimateMode> supported_modes{}; // NOLINT
|
||||
float visual_min_temperature{0.0f}; // NOLINT
|
||||
float visual_max_temperature{0.0f}; // NOLINT
|
||||
float visual_temperature_step{0.0f}; // NOLINT
|
||||
bool supports_away{false}; // NOLINT
|
||||
bool supports_action{false}; // NOLINT
|
||||
std::vector<enums::ClimateFanMode> supported_fan_modes{}; // NOLINT
|
||||
std::vector<enums::ClimateSwingMode> supported_swing_modes{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
enums::ClimateMode mode{}; // NOLINT
|
||||
float current_temperature{0.0f}; // NOLINT
|
||||
float target_temperature{0.0f}; // NOLINT
|
||||
float target_temperature_low{0.0f}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
enums::ClimateAction action{}; // NOLINT
|
||||
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_mode{false}; // NOLINT
|
||||
enums::ClimateMode mode{}; // NOLINT
|
||||
bool has_target_temperature{false}; // NOLINT
|
||||
float target_temperature{0.0f}; // NOLINT
|
||||
bool has_target_temperature_low{false}; // NOLINT
|
||||
float target_temperature_low{0.0f}; // NOLINT
|
||||
bool has_target_temperature_high{false}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool has_away{false}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
bool has_fan_mode{false}; // NOLINT
|
||||
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||
bool has_swing_mode{false}; // NOLINT
|
||||
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
552
esphome/components/api/api_pb2_service.cpp
Normal file
552
esphome/components/api/api_pb2_service.cpp
Normal file
@@ -0,0 +1,552 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See scripts/api_protobuf/api_protobuf.py
|
||||
#include "api_pb2_service.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.service";
|
||||
|
||||
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<HelloResponse>(msg, 2);
|
||||
}
|
||||
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ConnectResponse>(msg, 4);
|
||||
}
|
||||
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
||||
return this->send_message_<DisconnectRequest>(msg, 5);
|
||||
}
|
||||
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<DisconnectResponse>(msg, 6);
|
||||
}
|
||||
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
||||
return this->send_message_<PingRequest>(msg, 7);
|
||||
}
|
||||
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<PingResponse>(msg, 8);
|
||||
}
|
||||
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
||||
}
|
||||
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<CoverStateResponse>(msg, 22);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<FanStateResponse>(msg, 23);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<LightStateResponse>(msg, 24);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<SensorStateResponse>(msg, 25);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<SwitchStateResponse>(msg, 26);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
||||
}
|
||||
#endif
|
||||
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
||||
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
||||
}
|
||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
||||
}
|
||||
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
||||
const SubscribeHomeAssistantStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
||||
}
|
||||
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
||||
return this->send_message_<GetTimeRequest>(msg, 36);
|
||||
}
|
||||
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<GetTimeResponse>(msg, 37);
|
||||
}
|
||||
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
||||
}
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<CameraImageResponse>(msg, 44);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ClimateStateResponse>(msg, 47);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
HelloRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
|
||||
this->on_hello_request(msg);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
ConnectRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
|
||||
this->on_connect_request(msg);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
DisconnectRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
|
||||
this->on_disconnect_request(msg);
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
DisconnectResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
|
||||
this->on_disconnect_response(msg);
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
PingRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
|
||||
this->on_ping_request(msg);
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
PingResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
|
||||
this->on_ping_response(msg);
|
||||
break;
|
||||
}
|
||||
case 9: {
|
||||
DeviceInfoRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
|
||||
this->on_device_info_request(msg);
|
||||
break;
|
||||
}
|
||||
case 11: {
|
||||
ListEntitiesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
|
||||
this->on_list_entities_request(msg);
|
||||
break;
|
||||
}
|
||||
case 20: {
|
||||
SubscribeStatesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_states_request(msg);
|
||||
break;
|
||||
}
|
||||
case 28: {
|
||||
SubscribeLogsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_logs_request(msg);
|
||||
break;
|
||||
}
|
||||
case 30: {
|
||||
#ifdef USE_COVER
|
||||
CoverCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
|
||||
this->on_cover_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 31: {
|
||||
#ifdef USE_FAN
|
||||
FanCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
|
||||
this->on_fan_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 32: {
|
||||
#ifdef USE_LIGHT
|
||||
LightCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
|
||||
this->on_light_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 33: {
|
||||
#ifdef USE_SWITCH
|
||||
SwitchCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
|
||||
this->on_switch_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
SubscribeHomeassistantServicesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_homeassistant_services_request(msg);
|
||||
break;
|
||||
}
|
||||
case 36: {
|
||||
GetTimeRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
|
||||
this->on_get_time_request(msg);
|
||||
break;
|
||||
}
|
||||
case 37: {
|
||||
GetTimeResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
|
||||
this->on_get_time_response(msg);
|
||||
break;
|
||||
}
|
||||
case 38: {
|
||||
SubscribeHomeAssistantStatesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_home_assistant_states_request(msg);
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
HomeAssistantStateResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
this->on_home_assistant_state_response(msg);
|
||||
break;
|
||||
}
|
||||
case 42: {
|
||||
ExecuteServiceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
|
||||
this->on_execute_service_request(msg);
|
||||
break;
|
||||
}
|
||||
case 45: {
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CameraImageRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
|
||||
this->on_camera_image_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 48: {
|
||||
#ifdef USE_CLIMATE
|
||||
ClimateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
|
||||
this->on_climate_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void APIServerConnection::on_hello_request(const HelloRequest &msg) {
|
||||
HelloResponse ret = this->hello(msg);
|
||||
if (!this->send_hello_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
|
||||
ConnectResponse ret = this->connect(msg);
|
||||
if (!this->send_connect_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
||||
DisconnectResponse ret = this->disconnect(msg);
|
||||
if (!this->send_disconnect_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_ping_request(const PingRequest &msg) {
|
||||
PingResponse ret = this->ping(msg);
|
||||
if (!this->send_ping_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
DeviceInfoResponse ret = this->device_info(msg);
|
||||
if (!this->send_device_info_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->list_entities(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_states(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_logs(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_homeassistant_services_request(
|
||||
const SubscribeHomeassistantServicesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_homeassistant_services(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_home_assistant_states(msg);
|
||||
}
|
||||
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
GetTimeResponse ret = this->get_time(msg);
|
||||
if (!this->send_get_time_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->execute_service(msg);
|
||||
}
|
||||
#ifdef USE_COVER
|
||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->cover_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->fan_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->light_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->switch_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->camera_image(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->climate_command(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
185
esphome/components/api/api_pb2_service.h
Normal file
185
esphome/components/api/api_pb2_service.h
Normal file
@@ -0,0 +1,185 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See scripts/api_protobuf/api_protobuf.py
|
||||
#pragma once
|
||||
|
||||
#include "api_pb2.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServerConnectionBase : public ProtoService {
|
||||
public:
|
||||
virtual void on_hello_request(const HelloRequest &value){};
|
||||
bool send_hello_response(const HelloResponse &msg);
|
||||
virtual void on_connect_request(const ConnectRequest &value){};
|
||||
bool send_connect_response(const ConnectResponse &msg);
|
||||
bool send_disconnect_request(const DisconnectRequest &msg);
|
||||
virtual void on_disconnect_request(const DisconnectRequest &value){};
|
||||
bool send_disconnect_response(const DisconnectResponse &msg);
|
||||
virtual void on_disconnect_response(const DisconnectResponse &value){};
|
||||
bool send_ping_request(const PingRequest &msg);
|
||||
virtual void on_ping_request(const PingRequest &value){};
|
||||
bool send_ping_response(const PingResponse &msg);
|
||||
virtual void on_ping_response(const PingResponse &value){};
|
||||
virtual void on_device_info_request(const DeviceInfoRequest &value){};
|
||||
bool send_device_info_response(const DeviceInfoResponse &msg);
|
||||
virtual void on_list_entities_request(const ListEntitiesRequest &value){};
|
||||
bool send_list_entities_done_response(const ListEntitiesDoneResponse &msg);
|
||||
virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){};
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state_response(const BinarySensorStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_list_entities_cover_response(const ListEntitiesCoverResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state_response(const CoverStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
virtual void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_list_entities_fan_response(const ListEntitiesFanResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state_response(const FanStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual void on_fan_command_request(const FanCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_list_entities_light_response(const ListEntitiesLightResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state_response(const LightStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual void on_light_command_request(const LightCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state_response(const SensorStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state_response(const SwitchStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state_response(const TextSensorStateResponse &msg);
|
||||
#endif
|
||||
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
|
||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
|
||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||
bool send_subscribe_home_assistant_state_response(const SubscribeHomeAssistantStateResponse &msg);
|
||||
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
bool send_get_time_request(const GetTimeRequest &msg);
|
||||
virtual void on_get_time_request(const GetTimeRequest &value){};
|
||||
bool send_get_time_response(const GetTimeResponse &msg);
|
||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||
bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg);
|
||||
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool send_list_entities_camera_response(const ListEntitiesCameraResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool send_camera_image_response(const CameraImageResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_list_entities_climate_response(const ListEntitiesClimateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state_response(const ClimateStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
class APIServerConnection : public APIServerConnectionBase {
|
||||
public:
|
||||
virtual HelloResponse hello(const HelloRequest &msg) = 0;
|
||||
virtual ConnectResponse connect(const ConnectRequest &msg) = 0;
|
||||
virtual DisconnectResponse disconnect(const DisconnectRequest &msg) = 0;
|
||||
virtual PingResponse ping(const PingRequest &msg) = 0;
|
||||
virtual DeviceInfoResponse device_info(const DeviceInfoRequest &msg) = 0;
|
||||
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
|
||||
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
|
||||
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
|
||||
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||
#ifdef USE_COVER
|
||||
virtual void cover_command(const CoverCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual void fan_command(const FanCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual void light_command(const LightCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual void camera_image(const CameraImageRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
void on_connect_request(const ConnectRequest &msg) override;
|
||||
void on_disconnect_request(const DisconnectRequest &msg) override;
|
||||
void on_ping_request(const PingRequest &msg) override;
|
||||
void on_device_info_request(const DeviceInfoRequest &msg) override;
|
||||
void on_list_entities_request(const ListEntitiesRequest &msg) override;
|
||||
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
|
||||
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||
#ifdef USE_COVER
|
||||
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_command_request(const FanCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_command_request(const LightCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_command_request(const SwitchCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void on_camera_image_request(const CameraImageRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -1,18 +1,11 @@
|
||||
#include <utility>
|
||||
|
||||
#include "api_server.h"
|
||||
#include "basic_messages.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
@@ -209,9 +202,9 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
APIServer *global_api_server = nullptr;
|
||||
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
void APIServer::send_service_call(ServiceCallResponse &call) {
|
||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
for (auto *client : this->clients_) {
|
||||
client->send_service_call(call);
|
||||
client->send_homeassistant_service_call(call);
|
||||
}
|
||||
}
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
@@ -237,965 +230,10 @@ void APIServer::request_time() {
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
void APIServer::on_shutdown() {
|
||||
for (auto *c : this->clients_) {
|
||||
c->send_disconnect_request();
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// APIConnection
|
||||
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
|
||||
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
|
||||
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
|
||||
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
|
||||
this);
|
||||
this->client_->onData([](void *s, AsyncClient *c, void *buf,
|
||||
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
|
||||
this);
|
||||
|
||||
this->send_buffer_.reserve(64);
|
||||
this->recv_buffer_.reserve(32);
|
||||
this->client_info_ = this->client_->remoteIP().toString().c_str();
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
APIConnection::~APIConnection() { delete this->client_; }
|
||||
void APIConnection::on_error_(int8_t error) {
|
||||
// disconnect will also be called, nothing to do here
|
||||
this->remove_ = true;
|
||||
}
|
||||
void APIConnection::on_disconnect_() {
|
||||
// delete self, generally unsafe but not in this case.
|
||||
this->remove_ = true;
|
||||
}
|
||||
void APIConnection::on_timeout_(uint32_t time) { this->disconnect_client(); }
|
||||
void APIConnection::on_data_(uint8_t *buf, size_t len) {
|
||||
if (len == 0 || buf == nullptr)
|
||||
return;
|
||||
|
||||
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
|
||||
// TODO: On ESP32, use queue to notify main thread of new data
|
||||
}
|
||||
void APIConnection::parse_recv_buffer_() {
|
||||
if (this->recv_buffer_.empty() || this->remove_)
|
||||
return;
|
||||
|
||||
while (!this->recv_buffer_.empty()) {
|
||||
if (this->recv_buffer_[0] != 0x00) {
|
||||
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
uint32_t i = 1;
|
||||
const uint32_t size = this->recv_buffer_.size();
|
||||
uint32_t msg_size = 0;
|
||||
while (i < size) {
|
||||
const uint8_t dat = this->recv_buffer_[i];
|
||||
msg_size |= (dat & 0x7F);
|
||||
// consume
|
||||
i += 1;
|
||||
if ((dat & 0x80) == 0x00) {
|
||||
break;
|
||||
} else {
|
||||
msg_size <<= 7;
|
||||
}
|
||||
}
|
||||
if (i == size)
|
||||
// not enough data there yet
|
||||
return;
|
||||
|
||||
uint32_t msg_type = 0;
|
||||
bool msg_type_done = false;
|
||||
while (i < size) {
|
||||
const uint8_t dat = this->recv_buffer_[i];
|
||||
msg_type |= (dat & 0x7F);
|
||||
// consume
|
||||
i += 1;
|
||||
if ((dat & 0x80) == 0x00) {
|
||||
msg_type_done = true;
|
||||
break;
|
||||
} else {
|
||||
msg_type <<= 7;
|
||||
}
|
||||
}
|
||||
if (!msg_type_done)
|
||||
// not enough data there yet
|
||||
return;
|
||||
|
||||
if (size - i < msg_size)
|
||||
// message body not fully received
|
||||
return;
|
||||
|
||||
// ESP_LOGVV(TAG, "RECV Message: Size=%u Type=%u", msg_size, msg_type);
|
||||
|
||||
if (!this->valid_rx_message_type_(msg_type)) {
|
||||
ESP_LOGE(TAG, "Not a valid message type: %u", msg_type);
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *msg = &this->recv_buffer_[i];
|
||||
this->read_message_(msg_size, msg_type, msg);
|
||||
if (this->remove_)
|
||||
return;
|
||||
// pop front
|
||||
uint32_t total = i + msg_size;
|
||||
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
|
||||
}
|
||||
}
|
||||
void APIConnection::read_message_(uint32_t size, uint32_t type, uint8_t *msg) {
|
||||
this->last_traffic_ = millis();
|
||||
|
||||
switch (static_cast<APIMessageType>(type)) {
|
||||
case APIMessageType::HELLO_REQUEST: {
|
||||
HelloRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_hello_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::HELLO_RESPONSE: {
|
||||
// Invalid
|
||||
break;
|
||||
}
|
||||
case APIMessageType::CONNECT_REQUEST: {
|
||||
ConnectRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_connect_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::CONNECT_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::DISCONNECT_REQUEST: {
|
||||
DisconnectRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_disconnect_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::DISCONNECT_RESPONSE: {
|
||||
DisconnectResponse req;
|
||||
req.decode(msg, size);
|
||||
this->on_disconnect_response_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::PING_REQUEST: {
|
||||
PingRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_ping_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::PING_RESPONSE: {
|
||||
PingResponse req;
|
||||
req.decode(msg, size);
|
||||
this->on_ping_response_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::DEVICE_INFO_REQUEST: {
|
||||
DeviceInfoRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_device_info_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::DEVICE_INFO_RESPONSE: {
|
||||
// Invalid
|
||||
break;
|
||||
}
|
||||
case APIMessageType::LIST_ENTITIES_REQUEST: {
|
||||
ListEntitiesRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_list_entities_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_COVER_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_FAN_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_DONE_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::SUBSCRIBE_STATES_REQUEST: {
|
||||
SubscribeStatesRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_subscribe_states_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::BINARY_SENSOR_STATE_RESPONSE:
|
||||
case APIMessageType::COVER_STATE_RESPONSE:
|
||||
case APIMessageType::FAN_STATE_RESPONSE:
|
||||
case APIMessageType::LIGHT_STATE_RESPONSE:
|
||||
case APIMessageType::SENSOR_STATE_RESPONSE:
|
||||
case APIMessageType::SWITCH_STATE_RESPONSE:
|
||||
case APIMessageType::TEXT_SENSOR_STATE_RESPONSE:
|
||||
case APIMessageType::CAMERA_IMAGE_RESPONSE:
|
||||
case APIMessageType::CLIMATE_STATE_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::SUBSCRIBE_LOGS_REQUEST: {
|
||||
SubscribeLogsRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_subscribe_logs_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType ::SUBSCRIBE_LOGS_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::COVER_COMMAND_REQUEST: {
|
||||
#ifdef USE_COVER
|
||||
CoverCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_cover_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::FAN_COMMAND_REQUEST: {
|
||||
#ifdef USE_FAN
|
||||
FanCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_fan_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::LIGHT_COMMAND_REQUEST: {
|
||||
#ifdef USE_LIGHT
|
||||
LightCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_light_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SWITCH_COMMAND_REQUEST: {
|
||||
#ifdef USE_SWITCH
|
||||
SwitchCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_switch_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::CLIMATE_COMMAND_REQUEST: {
|
||||
#ifdef USE_CLIMATE
|
||||
ClimateCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_climate_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST: {
|
||||
SubscribeServiceCallsRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_subscribe_service_calls_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SERVICE_CALL_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::GET_TIME_REQUEST:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::GET_TIME_RESPONSE: {
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
homeassistant::GetTimeResponse req;
|
||||
req.decode(msg, size);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST: {
|
||||
SubscribeHomeAssistantStatesRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_subscribe_home_assistant_states_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::HOME_ASSISTANT_STATE_RESPONSE: {
|
||||
HomeAssistantStateResponse req;
|
||||
req.decode(msg, size);
|
||||
this->on_home_assistant_state_response_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::EXECUTE_SERVICE_REQUEST: {
|
||||
ExecuteServiceRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_execute_service_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::CAMERA_IMAGE_REQUEST: {
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CameraImageRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_camera_image_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIConnection::on_hello_request_(const HelloRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_hello_request_(client_info='%s')", req.get_client_info().c_str());
|
||||
this->client_info_ = req.get_client_info() + " (" + this->client_->remoteIP().toString().c_str();
|
||||
this->client_info_ += ")";
|
||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// uint32 api_version_major = 1; -> 1
|
||||
buffer.encode_uint32(1, 1);
|
||||
// uint32 api_version_minor = 2; -> 1
|
||||
buffer.encode_uint32(2, 1);
|
||||
|
||||
// string server_info = 3;
|
||||
buffer.encode_string(3, App.get_name() + " (esphome v" ESPHOME_VERSION ")");
|
||||
bool success = this->send_buffer(APIMessageType::HELLO_RESPONSE);
|
||||
if (!success) {
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
|
||||
this->connection_state_ = ConnectionState::WAITING_FOR_CONNECT;
|
||||
}
|
||||
void APIConnection::on_connect_request_(const ConnectRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_connect_request_(password='%s')", req.get_password().c_str());
|
||||
bool correct = this->parent_->check_password(req.get_password());
|
||||
auto buffer = this->get_buffer();
|
||||
// bool invalid_password = 1;
|
||||
buffer.encode_bool(1, !correct);
|
||||
bool success = this->send_buffer(APIMessageType::CONNECT_RESPONSE);
|
||||
if (!success) {
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (correct) {
|
||||
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
this->send_time_request();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void APIConnection::on_disconnect_request_(const DisconnectRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_disconnect_request_");
|
||||
// remote initiated disconnect_client
|
||||
if (!this->send_empty_message(APIMessageType::DISCONNECT_RESPONSE)) {
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
this->disconnect_client();
|
||||
}
|
||||
void APIConnection::on_disconnect_response_(const DisconnectResponse &req) {
|
||||
ESP_LOGVV(TAG, "on_disconnect_response_");
|
||||
// we initiated disconnect_client
|
||||
this->disconnect_client();
|
||||
}
|
||||
void APIConnection::on_ping_request_(const PingRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_ping_request_");
|
||||
PingResponse resp;
|
||||
this->send_message(resp);
|
||||
}
|
||||
void APIConnection::on_ping_response_(const PingResponse &req) {
|
||||
ESP_LOGVV(TAG, "on_ping_response_");
|
||||
// we initiated ping
|
||||
this->sent_ping_ = false;
|
||||
}
|
||||
void APIConnection::on_device_info_request_(const DeviceInfoRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_device_info_request_");
|
||||
auto buffer = this->get_buffer();
|
||||
// bool uses_password = 1;
|
||||
buffer.encode_bool(1, this->parent_->uses_password());
|
||||
// string name = 2;
|
||||
buffer.encode_string(2, App.get_name());
|
||||
// string mac_address = 3;
|
||||
buffer.encode_string(3, get_mac_address_pretty());
|
||||
// string esphome_version = 4;
|
||||
buffer.encode_string(4, ESPHOME_VERSION);
|
||||
// string compilation_time = 5;
|
||||
buffer.encode_string(5, App.get_compilation_time());
|
||||
#ifdef ARDUINO_BOARD
|
||||
// string model = 6;
|
||||
buffer.encode_string(6, ARDUINO_BOARD);
|
||||
#endif
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
// bool has_deep_sleep = 7;
|
||||
buffer.encode_bool(7, deep_sleep::global_has_deep_sleep);
|
||||
#endif
|
||||
this->send_buffer(APIMessageType::DEVICE_INFO_RESPONSE);
|
||||
}
|
||||
void APIConnection::on_list_entities_request_(const ListEntitiesRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_list_entities_request_");
|
||||
this->list_entities_iterator_.begin();
|
||||
}
|
||||
void APIConnection::on_subscribe_states_request_(const SubscribeStatesRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_subscribe_states_request_");
|
||||
this->state_subscription_ = true;
|
||||
this->initial_state_iterator_.begin();
|
||||
}
|
||||
void APIConnection::on_subscribe_logs_request_(const SubscribeLogsRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_subscribe_logs_request_");
|
||||
this->log_subscription_ = req.get_level();
|
||||
if (req.get_dump_config()) {
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::fatal_error_() {
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
bool APIConnection::valid_rx_message_type_(uint32_t type) {
|
||||
switch (static_cast<APIMessageType>(type)) {
|
||||
case APIMessageType::HELLO_RESPONSE:
|
||||
case APIMessageType::CONNECT_RESPONSE:
|
||||
return false;
|
||||
case APIMessageType::HELLO_REQUEST:
|
||||
return this->connection_state_ == ConnectionState::WAITING_FOR_HELLO;
|
||||
case APIMessageType::CONNECT_REQUEST:
|
||||
return this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT;
|
||||
case APIMessageType::PING_REQUEST:
|
||||
case APIMessageType::PING_RESPONSE:
|
||||
case APIMessageType::DISCONNECT_REQUEST:
|
||||
case APIMessageType::DISCONNECT_RESPONSE:
|
||||
case APIMessageType::DEVICE_INFO_REQUEST:
|
||||
if (this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT)
|
||||
return true;
|
||||
default:
|
||||
return this->connection_state_ == ConnectionState::CONNECTED;
|
||||
}
|
||||
}
|
||||
bool APIConnection::send_message(APIMessage &msg) {
|
||||
this->send_buffer_.clear();
|
||||
APIBuffer buf(&this->send_buffer_);
|
||||
msg.encode(buf);
|
||||
return this->send_buffer(msg.message_type());
|
||||
}
|
||||
bool APIConnection::send_empty_message(APIMessageType type) {
|
||||
this->send_buffer_.clear();
|
||||
return this->send_buffer(type);
|
||||
}
|
||||
|
||||
void APIConnection::disconnect_client() {
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
void encode_varint(uint8_t *dat, uint8_t *len, uint32_t value) {
|
||||
if (value <= 0x7F) {
|
||||
*dat = value;
|
||||
(*len)++;
|
||||
return;
|
||||
}
|
||||
|
||||
while (value) {
|
||||
uint8_t temp = value & 0x7F;
|
||||
value >>= 7;
|
||||
if (value) {
|
||||
*dat = temp | 0x80;
|
||||
} else {
|
||||
*dat = temp;
|
||||
}
|
||||
dat++;
|
||||
(*len)++;
|
||||
}
|
||||
}
|
||||
|
||||
bool APIConnection::send_buffer(APIMessageType type) {
|
||||
uint8_t header[20];
|
||||
header[0] = 0x00;
|
||||
uint8_t header_len = 1;
|
||||
encode_varint(header + header_len, &header_len, this->send_buffer_.size());
|
||||
encode_varint(header + header_len, &header_len, static_cast<uint32_t>(type));
|
||||
|
||||
size_t needed_space = this->send_buffer_.size() + header_len;
|
||||
|
||||
if (needed_space > this->client_->space()) {
|
||||
delay(5);
|
||||
if (needed_space > this->client_->space()) {
|
||||
if (type != APIMessageType::SUBSCRIBE_LOGS_RESPONSE) {
|
||||
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
|
||||
}
|
||||
delay(5);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// char buffer[512];
|
||||
// uint32_t offset = 0;
|
||||
// for (int j = 0; j < header_len; j++) {
|
||||
// offset += snprintf(buffer + offset, 512 - offset, "0x%02X ", header[j]);
|
||||
// }
|
||||
// offset += snprintf(buffer + offset, 512 - offset, "| ");
|
||||
// for (auto &it : this->send_buffer_) {
|
||||
// int i = snprintf(buffer + offset, 512 - offset, "0x%02X ", it);
|
||||
// if (i <= 0)
|
||||
// break;
|
||||
// offset += i;
|
||||
// }
|
||||
// ESP_LOGVV(TAG, "SEND %s", buffer);
|
||||
|
||||
this->client_->add(reinterpret_cast<char *>(header), header_len);
|
||||
this->client_->add(reinterpret_cast<char *>(this->send_buffer_.data()), this->send_buffer_.size());
|
||||
return this->client_->send();
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (!network_is_connected()) {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
if (this->client_->disconnected()) {
|
||||
// failsafe for disconnect logic
|
||||
this->on_disconnect_();
|
||||
return;
|
||||
}
|
||||
this->parse_recv_buffer_();
|
||||
|
||||
this->list_entities_iterator_.advance();
|
||||
this->initial_state_iterator_.advance();
|
||||
|
||||
const uint32_t keepalive = 60000;
|
||||
if (this->sent_ping_) {
|
||||
if (millis() - this->last_traffic_ > (keepalive * 3) / 2) {
|
||||
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
this->disconnect_client();
|
||||
}
|
||||
} else if (millis() - this->last_traffic_ > keepalive) {
|
||||
this->sent_ping_ = true;
|
||||
this->send_ping_request();
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (this->image_reader_.available()) {
|
||||
uint32_t space = this->client_->space();
|
||||
// reserve 15 bytes for metadata, and at least 64 bytes of data
|
||||
if (space >= 15 + 64) {
|
||||
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
|
||||
// bytes data = 2;
|
||||
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
|
||||
// bool done = 3;
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
buffer.encode_bool(3, done);
|
||||
bool success = this->send_buffer(APIMessageType::CAMERA_IMAGE_RESPONSE);
|
||||
if (success) {
|
||||
this->image_reader_.consume_data(to_send);
|
||||
}
|
||||
if (success && done) {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, binary_sensor->get_object_id_hash());
|
||||
// bool state = 2;
|
||||
buffer.encode_bool(2, state);
|
||||
return this->send_buffer(APIMessageType::BINARY_SENSOR_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
auto traits = cover->get_traits();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, cover->get_object_id_hash());
|
||||
// enum LegacyCoverState {
|
||||
// OPEN = 0;
|
||||
// CLOSED = 1;
|
||||
// }
|
||||
// LegacyCoverState legacy_state = 2;
|
||||
uint32_t state = (cover->position == cover::COVER_OPEN) ? 0 : 1;
|
||||
buffer.encode_uint32(2, state);
|
||||
// float position = 3;
|
||||
buffer.encode_float(3, cover->position);
|
||||
if (traits.get_supports_tilt()) {
|
||||
// float tilt = 4;
|
||||
buffer.encode_float(4, cover->tilt);
|
||||
}
|
||||
// enum CoverCurrentOperation {
|
||||
// IDLE = 0;
|
||||
// IS_OPENING = 1;
|
||||
// IS_CLOSING = 2;
|
||||
// }
|
||||
// CoverCurrentOperation current_operation = 5;
|
||||
buffer.encode_uint32(5, cover->current_operation);
|
||||
return this->send_buffer(APIMessageType::COVER_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, fan->get_object_id_hash());
|
||||
// bool state = 2;
|
||||
buffer.encode_bool(2, fan->state);
|
||||
// bool oscillating = 3;
|
||||
if (fan->get_traits().supports_oscillation()) {
|
||||
buffer.encode_bool(3, fan->oscillating);
|
||||
}
|
||||
// enum FanSpeed {
|
||||
// LOW = 0;
|
||||
// MEDIUM = 1;
|
||||
// HIGH = 2;
|
||||
// }
|
||||
// FanSpeed speed = 4;
|
||||
if (fan->get_traits().supports_speed()) {
|
||||
buffer.encode_uint32(4, fan->speed);
|
||||
}
|
||||
return this->send_buffer(APIMessageType::FAN_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
bool APIConnection::send_light_state(light::LightState *light) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
auto traits = light->get_traits();
|
||||
auto values = light->remote_values;
|
||||
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, light->get_object_id_hash());
|
||||
// bool state = 2;
|
||||
buffer.encode_bool(2, values.get_state() != 0.0f);
|
||||
// float brightness = 3;
|
||||
if (traits.get_supports_brightness()) {
|
||||
buffer.encode_float(3, values.get_brightness());
|
||||
}
|
||||
if (traits.get_supports_rgb()) {
|
||||
// float red = 4;
|
||||
buffer.encode_float(4, values.get_red());
|
||||
// float green = 5;
|
||||
buffer.encode_float(5, values.get_green());
|
||||
// float blue = 6;
|
||||
buffer.encode_float(6, values.get_blue());
|
||||
}
|
||||
// float white = 7;
|
||||
if (traits.get_supports_rgb_white_value()) {
|
||||
buffer.encode_float(7, values.get_white());
|
||||
}
|
||||
// float color_temperature = 8;
|
||||
if (traits.get_supports_color_temperature()) {
|
||||
buffer.encode_float(8, values.get_color_temperature());
|
||||
}
|
||||
// string effect = 9;
|
||||
if (light->supports_effects()) {
|
||||
buffer.encode_string(9, light->get_effect_name());
|
||||
}
|
||||
return this->send_buffer(APIMessageType::LIGHT_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, sensor->get_object_id_hash());
|
||||
// float state = 2;
|
||||
buffer.encode_float(2, state);
|
||||
return this->send_buffer(APIMessageType::SENSOR_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, a_switch->get_object_id_hash());
|
||||
// bool state = 2;
|
||||
buffer.encode_bool(2, state);
|
||||
return this->send_buffer(APIMessageType::SWITCH_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, text_sensor->get_object_id_hash());
|
||||
// string state = 2;
|
||||
buffer.encode_string(2, state);
|
||||
return this->send_buffer(APIMessageType::TEXT_SENSOR_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
auto traits = climate->get_traits();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, climate->get_object_id_hash());
|
||||
// ClimateMode mode = 2;
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(climate->mode));
|
||||
// float current_temperature = 3;
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
buffer.encode_float(3, climate->current_temperature);
|
||||
}
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
// float target_temperature_low = 5;
|
||||
buffer.encode_float(5, climate->target_temperature_low);
|
||||
// float target_temperature_high = 6;
|
||||
buffer.encode_float(6, climate->target_temperature_high);
|
||||
} else {
|
||||
// float target_temperature = 4;
|
||||
buffer.encode_float(4, climate->target_temperature);
|
||||
}
|
||||
// bool away = 7;
|
||||
if (traits.get_supports_away()) {
|
||||
buffer.encode_bool(7, climate->away);
|
||||
}
|
||||
return this->send_buffer(APIMessageType::CLIMATE_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// LogLevel level = 1;
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
||||
// string tag = 2;
|
||||
// buffer.encode_string(2, tag, strlen(tag));
|
||||
// string message = 3;
|
||||
buffer.encode_string(3, line, strlen(line));
|
||||
bool success = this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE);
|
||||
|
||||
if (!success) {
|
||||
buffer = this->get_buffer();
|
||||
// bool send_failed = 4;
|
||||
buffer.encode_bool(4, true);
|
||||
return this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool APIConnection::send_disconnect_request() {
|
||||
DisconnectRequest req;
|
||||
return this->send_message(req);
|
||||
}
|
||||
bool APIConnection::send_ping_request() {
|
||||
ESP_LOGVV(TAG, "Sending ping...");
|
||||
PingRequest req;
|
||||
return this->send_message(req);
|
||||
}
|
||||
|
||||
#ifdef USE_COVER
|
||||
void APIConnection::on_cover_command_request_(const CoverCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_cover_command_request_");
|
||||
cover::Cover *cover = App.get_cover_by_key(req.get_key());
|
||||
if (cover == nullptr)
|
||||
return;
|
||||
|
||||
auto call = cover->make_call();
|
||||
if (req.get_legacy_command().has_value()) {
|
||||
auto cmd = *req.get_legacy_command();
|
||||
switch (cmd) {
|
||||
case LEGACY_COVER_COMMAND_OPEN:
|
||||
call.set_command_open();
|
||||
break;
|
||||
case LEGACY_COVER_COMMAND_CLOSE:
|
||||
call.set_command_close();
|
||||
break;
|
||||
case LEGACY_COVER_COMMAND_STOP:
|
||||
call.set_command_stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (req.get_position().has_value()) {
|
||||
auto pos = *req.get_position();
|
||||
call.set_position(pos);
|
||||
}
|
||||
if (req.get_tilt().has_value()) {
|
||||
auto tilt = *req.get_tilt();
|
||||
call.set_tilt(tilt);
|
||||
}
|
||||
if (req.get_stop()) {
|
||||
call.set_command_stop();
|
||||
}
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void APIConnection::on_fan_command_request_(const FanCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_fan_command_request_");
|
||||
fan::FanState *fan = App.get_fan_by_key(req.get_key());
|
||||
if (fan == nullptr)
|
||||
return;
|
||||
|
||||
auto call = fan->make_call();
|
||||
call.set_state(req.get_state());
|
||||
call.set_oscillating(req.get_oscillating());
|
||||
call.set_speed(req.get_speed());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void APIConnection::on_light_command_request_(const LightCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_light_command_request_");
|
||||
light::LightState *light = App.get_light_by_key(req.get_key());
|
||||
if (light == nullptr)
|
||||
return;
|
||||
|
||||
auto call = light->make_call();
|
||||
call.set_state(req.get_state());
|
||||
call.set_brightness(req.get_brightness());
|
||||
call.set_red(req.get_red());
|
||||
call.set_green(req.get_green());
|
||||
call.set_blue(req.get_blue());
|
||||
call.set_white(req.get_white());
|
||||
call.set_color_temperature(req.get_color_temperature());
|
||||
call.set_transition_length(req.get_transition_length());
|
||||
call.set_flash_length(req.get_flash_length());
|
||||
call.set_effect(req.get_effect());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void APIConnection::on_switch_command_request_(const SwitchCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_switch_command_request_");
|
||||
switch_::Switch *a_switch = App.get_switch_by_key(req.get_key());
|
||||
if (a_switch == nullptr || a_switch->is_internal())
|
||||
return;
|
||||
|
||||
if (req.get_state()) {
|
||||
a_switch->turn_on();
|
||||
} else {
|
||||
a_switch->turn_off();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void APIConnection::on_climate_command_request_(const ClimateCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_climate_command_request_");
|
||||
climate::Climate *climate = App.get_climate_by_key(req.get_key());
|
||||
if (climate == nullptr)
|
||||
return;
|
||||
|
||||
auto call = climate->make_call();
|
||||
if (req.get_mode().has_value())
|
||||
call.set_mode(*req.get_mode());
|
||||
if (req.get_target_temperature().has_value())
|
||||
call.set_target_temperature(*req.get_target_temperature());
|
||||
if (req.get_target_temperature_low().has_value())
|
||||
call.set_target_temperature_low(*req.get_target_temperature_low());
|
||||
if (req.get_target_temperature_high().has_value())
|
||||
call.set_target_temperature_high(*req.get_target_temperature_high());
|
||||
if (req.get_away().has_value())
|
||||
call.set_away(*req.get_away());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIConnection::on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req) {
|
||||
this->service_call_subscription_ = true;
|
||||
}
|
||||
void APIConnection::send_service_call(ServiceCallResponse &call) {
|
||||
if (!this->service_call_subscription_)
|
||||
return;
|
||||
|
||||
this->send_message(call);
|
||||
}
|
||||
void APIConnection::on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
auto buffer = this->get_buffer();
|
||||
// string entity_id = 1;
|
||||
buffer.encode_string(1, it.entity_id);
|
||||
this->send_buffer(APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE);
|
||||
}
|
||||
}
|
||||
void APIConnection::on_home_assistant_state_response_(const HomeAssistantStateResponse &req) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
if (it.entity_id == req.get_entity_id()) {
|
||||
it.callback(req.get_state());
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIConnection::on_execute_service_(const ExecuteServiceRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_execute_service_");
|
||||
bool found = false;
|
||||
for (auto *service : this->parent_->get_user_services()) {
|
||||
if (service->execute_service(req)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGV(TAG, "Could not find matching service!");
|
||||
}
|
||||
}
|
||||
|
||||
APIBuffer APIConnection::get_buffer() {
|
||||
this->send_buffer_.clear();
|
||||
return {&this->send_buffer_};
|
||||
}
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIConnection::send_time_request() { this->send_empty_message(APIMessageType::GET_TIME_REQUEST); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
return;
|
||||
if (this->image_reader_.available())
|
||||
return;
|
||||
this->image_reader_.set_image(image);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::on_camera_image_request_(const CameraImageRequest &req) {
|
||||
if (esp32_camera::global_esp32_camera == nullptr)
|
||||
return;
|
||||
|
||||
ESP_LOGV(TAG, "on_camera_image_request_ stream=%s single=%s", YESNO(req.get_stream()), YESNO(req.get_single()));
|
||||
if (req.get_single()) {
|
||||
esp32_camera::global_esp32_camera->request_image();
|
||||
}
|
||||
if (req.get_stream()) {
|
||||
esp32_camera::global_esp32_camera->request_stream();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "util.h"
|
||||
#include "api_message.h"
|
||||
#include "basic_messages.h"
|
||||
#include "list_entities.h"
|
||||
#include "subscribe_state.h"
|
||||
#include "subscribe_logs.h"
|
||||
#include "command_messages.h"
|
||||
#include "service_call_message.h"
|
||||
#include "homeassistant_service.h"
|
||||
#include "user_services.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
@@ -24,130 +22,6 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServer;
|
||||
|
||||
class APIConnection {
|
||||
public:
|
||||
APIConnection(AsyncClient *client, APIServer *parent);
|
||||
~APIConnection();
|
||||
|
||||
void disconnect_client();
|
||||
APIBuffer get_buffer();
|
||||
bool send_buffer(APIMessageType type);
|
||||
bool send_message(APIMessage &msg);
|
||||
bool send_empty_message(APIMessageType type);
|
||||
void loop();
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state(cover::Cover *cover);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state(fan::FanState *fan);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state(light::LightState *light);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state(sensor::Sensor *sensor, float state);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state(switch_::Switch *a_switch, bool state);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
bool send_disconnect_request();
|
||||
bool send_ping_request();
|
||||
void send_service_call(ServiceCallResponse &call);
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void send_time_request();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
friend APIServer;
|
||||
|
||||
void on_error_(int8_t error);
|
||||
void on_disconnect_();
|
||||
void on_timeout_(uint32_t time);
|
||||
void on_data_(uint8_t *buf, size_t len);
|
||||
void fatal_error_();
|
||||
bool valid_rx_message_type_(uint32_t msg_type);
|
||||
void read_message_(uint32_t size, uint32_t type, uint8_t *msg);
|
||||
void parse_recv_buffer_();
|
||||
|
||||
// request types
|
||||
void on_hello_request_(const HelloRequest &req);
|
||||
void on_connect_request_(const ConnectRequest &req);
|
||||
void on_disconnect_request_(const DisconnectRequest &req);
|
||||
void on_disconnect_response_(const DisconnectResponse &req);
|
||||
void on_ping_request_(const PingRequest &req);
|
||||
void on_ping_response_(const PingResponse &req);
|
||||
void on_device_info_request_(const DeviceInfoRequest &req);
|
||||
void on_list_entities_request_(const ListEntitiesRequest &req);
|
||||
void on_subscribe_states_request_(const SubscribeStatesRequest &req);
|
||||
void on_subscribe_logs_request_(const SubscribeLogsRequest &req);
|
||||
#ifdef USE_COVER
|
||||
void on_cover_command_request_(const CoverCommandRequest &req);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_command_request_(const FanCommandRequest &req);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_command_request_(const LightCommandRequest &req);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_command_request_(const SwitchCommandRequest &req);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_command_request_(const ClimateCommandRequest &req);
|
||||
#endif
|
||||
void on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req);
|
||||
void on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req);
|
||||
void on_home_assistant_state_response_(const HomeAssistantStateResponse &req);
|
||||
void on_execute_service_(const ExecuteServiceRequest &req);
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void on_camera_image_request_(const CameraImageRequest &req);
|
||||
#endif
|
||||
|
||||
enum class ConnectionState {
|
||||
WAITING_FOR_HELLO,
|
||||
WAITING_FOR_CONNECT,
|
||||
CONNECTED,
|
||||
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
|
||||
|
||||
bool remove_{false};
|
||||
|
||||
std::vector<uint8_t> send_buffer_;
|
||||
std::vector<uint8_t> recv_buffer_;
|
||||
|
||||
std::string client_info_;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
esp32_camera::CameraImageReader image_reader_;
|
||||
#endif
|
||||
|
||||
bool state_subscription_{false};
|
||||
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
AsyncClient *client_;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class HomeAssistantServiceCallAction;
|
||||
|
||||
class APIServer : public Component, public Controller {
|
||||
public:
|
||||
APIServer();
|
||||
@@ -187,7 +61,7 @@ class APIServer : public Component, public Controller {
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
#endif
|
||||
void send_service_call(ServiceCallResponse &call);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void request_time();
|
||||
@@ -217,22 +91,6 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
extern APIServer *global_api_server;
|
||||
|
||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit HomeAssistantServiceCallAction(APIServer *parent) : parent_(parent) {}
|
||||
void set_service(const std::string &service) { this->resp_.set_service(service); }
|
||||
void set_data(const std::vector<KeyValuePair> &data) { this->resp_.set_data(data); }
|
||||
void set_data_template(const std::vector<KeyValuePair> &data_template) {
|
||||
this->resp_.set_data_template(data_template);
|
||||
}
|
||||
void set_variables(const std::vector<TemplatableKeyValuePair> &variables) { this->resp_.set_variables(variables); }
|
||||
void play(Ts... x) override { this->parent_->send_service_call(this->resp_); }
|
||||
|
||||
protected:
|
||||
APIServer *parent_;
|
||||
ServiceCallResponse resp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#include "basic_messages.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
// Hello
|
||||
bool HelloRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 1: // string client_info = 1;
|
||||
this->client_info_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const std::string &HelloRequest::get_client_info() const { return this->client_info_; }
|
||||
void HelloRequest::set_client_info(const std::string &client_info) { this->client_info_ = client_info; }
|
||||
APIMessageType HelloRequest::message_type() const { return APIMessageType::HELLO_REQUEST; }
|
||||
|
||||
// Connect
|
||||
bool ConnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 1: // string password = 1;
|
||||
this->password_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const std::string &ConnectRequest::get_password() const { return this->password_; }
|
||||
void ConnectRequest::set_password(const std::string &password) { this->password_ = password; }
|
||||
APIMessageType ConnectRequest::message_type() const { return APIMessageType::CONNECT_REQUEST; }
|
||||
|
||||
APIMessageType DeviceInfoRequest::message_type() const { return APIMessageType::DEVICE_INFO_REQUEST; }
|
||||
APIMessageType DisconnectRequest::message_type() const { return APIMessageType::DISCONNECT_REQUEST; }
|
||||
bool DisconnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 1: // string reason = 1;
|
||||
this->reason_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const std::string &DisconnectRequest::get_reason() const { return this->reason_; }
|
||||
void DisconnectRequest::set_reason(const std::string &reason) { this->reason_ = reason; }
|
||||
void DisconnectRequest::encode(APIBuffer &buffer) {
|
||||
// string reason = 1;
|
||||
buffer.encode_string(1, this->reason_);
|
||||
}
|
||||
APIMessageType DisconnectResponse::message_type() const { return APIMessageType::DISCONNECT_RESPONSE; }
|
||||
APIMessageType PingRequest::message_type() const { return APIMessageType::PING_REQUEST; }
|
||||
APIMessageType PingResponse::message_type() const { return APIMessageType::PING_RESPONSE; }
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class HelloRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
const std::string &get_client_info() const;
|
||||
void set_client_info(const std::string &client_info);
|
||||
APIMessageType message_type() const override;
|
||||
|
||||
protected:
|
||||
std::string client_info_;
|
||||
};
|
||||
|
||||
class ConnectRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
const std::string &get_password() const;
|
||||
void set_password(const std::string &password);
|
||||
APIMessageType message_type() const override;
|
||||
|
||||
protected:
|
||||
std::string password_;
|
||||
};
|
||||
|
||||
class DeviceInfoRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class DisconnectRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
void encode(APIBuffer &buffer) override;
|
||||
APIMessageType message_type() const override;
|
||||
const std::string &get_reason() const;
|
||||
void set_reason(const std::string &reason);
|
||||
|
||||
protected:
|
||||
std::string reason_;
|
||||
};
|
||||
|
||||
class DisconnectResponse : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class PingRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class PingResponse : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -1,417 +0,0 @@
|
||||
#include "command_messages.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_COVER
|
||||
bool CoverCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool has_legacy_command = 2;
|
||||
this->has_legacy_command_ = value;
|
||||
return true;
|
||||
case 3:
|
||||
// enum LegacyCoverCommand {
|
||||
// OPEN = 0;
|
||||
// CLOSE = 1;
|
||||
// STOP = 2;
|
||||
// }
|
||||
// LegacyCoverCommand legacy_command_ = 3;
|
||||
this->legacy_command_ = static_cast<LegacyCoverCommand>(value);
|
||||
return true;
|
||||
case 4:
|
||||
// bool has_position = 4;
|
||||
this->has_position_ = value;
|
||||
return true;
|
||||
case 6:
|
||||
// bool has_tilt = 6;
|
||||
this->has_tilt_ = value;
|
||||
return true;
|
||||
case 8:
|
||||
// bool stop = 8;
|
||||
this->stop_ = value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool CoverCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
case 5:
|
||||
// float position = 5;
|
||||
this->position_ = as_float(value);
|
||||
return true;
|
||||
case 7:
|
||||
// float tilt = 7;
|
||||
this->tilt_ = as_float(value);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType CoverCommandRequest::message_type() const { return APIMessageType ::COVER_COMMAND_REQUEST; }
|
||||
uint32_t CoverCommandRequest::get_key() const { return this->key_; }
|
||||
optional<LegacyCoverCommand> CoverCommandRequest::get_legacy_command() const {
|
||||
if (!this->has_legacy_command_)
|
||||
return {};
|
||||
return this->legacy_command_;
|
||||
}
|
||||
optional<float> CoverCommandRequest::get_position() const {
|
||||
if (!this->has_position_)
|
||||
return {};
|
||||
return this->position_;
|
||||
}
|
||||
optional<float> CoverCommandRequest::get_tilt() const {
|
||||
if (!this->has_tilt_)
|
||||
return {};
|
||||
return this->tilt_;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
bool FanCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool has_state = 2;
|
||||
this->has_state_ = value;
|
||||
return true;
|
||||
case 3:
|
||||
// bool state = 3;
|
||||
this->state_ = value;
|
||||
return true;
|
||||
case 4:
|
||||
// bool has_speed = 4;
|
||||
this->has_speed_ = value;
|
||||
return true;
|
||||
case 5:
|
||||
// FanSpeed speed = 5;
|
||||
this->speed_ = static_cast<fan::FanSpeed>(value);
|
||||
return true;
|
||||
case 6:
|
||||
// bool has_oscillating = 6;
|
||||
this->has_oscillating_ = value;
|
||||
return true;
|
||||
case 7:
|
||||
// bool oscillating = 7;
|
||||
this->oscillating_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool FanCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType FanCommandRequest::message_type() const { return APIMessageType::FAN_COMMAND_REQUEST; }
|
||||
uint32_t FanCommandRequest::get_key() const { return this->key_; }
|
||||
optional<bool> FanCommandRequest::get_state() const {
|
||||
if (!this->has_state_)
|
||||
return {};
|
||||
return this->state_;
|
||||
}
|
||||
optional<fan::FanSpeed> FanCommandRequest::get_speed() const {
|
||||
if (!this->has_speed_)
|
||||
return {};
|
||||
return this->speed_;
|
||||
}
|
||||
optional<bool> FanCommandRequest::get_oscillating() const {
|
||||
if (!this->has_oscillating_)
|
||||
return {};
|
||||
return this->oscillating_;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
bool LightCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool has_state = 2;
|
||||
this->has_state_ = value;
|
||||
return true;
|
||||
case 3:
|
||||
// bool state = 3;
|
||||
this->state_ = value;
|
||||
return true;
|
||||
case 4:
|
||||
// bool has_brightness = 4;
|
||||
this->has_brightness_ = value;
|
||||
return true;
|
||||
case 6:
|
||||
// bool has_rgb = 6;
|
||||
this->has_rgb_ = value;
|
||||
return true;
|
||||
case 10:
|
||||
// bool has_white = 10;
|
||||
this->has_white_ = value;
|
||||
return true;
|
||||
case 12:
|
||||
// bool has_color_temperature = 12;
|
||||
this->has_color_temperature_ = value;
|
||||
return true;
|
||||
case 14:
|
||||
// bool has_transition_length = 14;
|
||||
this->has_transition_length_ = value;
|
||||
return true;
|
||||
case 15:
|
||||
// uint32 transition_length = 15;
|
||||
this->transition_length_ = value;
|
||||
return true;
|
||||
case 16:
|
||||
// bool has_flash_length = 16;
|
||||
this->has_flash_length_ = value;
|
||||
return true;
|
||||
case 17:
|
||||
// uint32 flash_length = 17;
|
||||
this->flash_length_ = value;
|
||||
return true;
|
||||
case 18:
|
||||
// bool has_effect = 18;
|
||||
this->has_effect_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LightCommandRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 19:
|
||||
// string effect = 19;
|
||||
this->effect_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LightCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
case 5:
|
||||
// float brightness = 5;
|
||||
this->brightness_ = as_float(value);
|
||||
return true;
|
||||
case 7:
|
||||
// float red = 7;
|
||||
this->red_ = as_float(value);
|
||||
return true;
|
||||
case 8:
|
||||
// float green = 8;
|
||||
this->green_ = as_float(value);
|
||||
return true;
|
||||
case 9:
|
||||
// float blue = 9;
|
||||
this->blue_ = as_float(value);
|
||||
return true;
|
||||
case 11:
|
||||
// float white = 11;
|
||||
this->white_ = as_float(value);
|
||||
return true;
|
||||
case 13:
|
||||
// float color_temperature = 13;
|
||||
this->color_temperature_ = as_float(value);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType LightCommandRequest::message_type() const { return APIMessageType::LIGHT_COMMAND_REQUEST; }
|
||||
uint32_t LightCommandRequest::get_key() const { return this->key_; }
|
||||
optional<bool> LightCommandRequest::get_state() const {
|
||||
if (!this->has_state_)
|
||||
return {};
|
||||
return this->state_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_brightness() const {
|
||||
if (!this->has_brightness_)
|
||||
return {};
|
||||
return this->brightness_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_red() const {
|
||||
if (!this->has_rgb_)
|
||||
return {};
|
||||
return this->red_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_green() const {
|
||||
if (!this->has_rgb_)
|
||||
return {};
|
||||
return this->green_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_blue() const {
|
||||
if (!this->has_rgb_)
|
||||
return {};
|
||||
return this->blue_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_white() const {
|
||||
if (!this->has_white_)
|
||||
return {};
|
||||
return this->white_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_color_temperature() const {
|
||||
if (!this->has_color_temperature_)
|
||||
return {};
|
||||
return this->color_temperature_;
|
||||
}
|
||||
optional<uint32_t> LightCommandRequest::get_transition_length() const {
|
||||
if (!this->has_transition_length_)
|
||||
return {};
|
||||
return this->transition_length_;
|
||||
}
|
||||
optional<uint32_t> LightCommandRequest::get_flash_length() const {
|
||||
if (!this->has_flash_length_)
|
||||
return {};
|
||||
return this->flash_length_;
|
||||
}
|
||||
optional<std::string> LightCommandRequest::get_effect() const {
|
||||
if (!this->has_effect_)
|
||||
return {};
|
||||
return this->effect_;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
bool SwitchCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool state = 2;
|
||||
this->state_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool SwitchCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType SwitchCommandRequest::message_type() const { return APIMessageType::SWITCH_COMMAND_REQUEST; }
|
||||
uint32_t SwitchCommandRequest::get_key() const { return this->key_; }
|
||||
bool SwitchCommandRequest::get_state() const { return this->state_; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool CameraImageRequest::get_single() const { return this->single_; }
|
||||
bool CameraImageRequest::get_stream() const { return this->stream_; }
|
||||
bool CameraImageRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// bool single = 1;
|
||||
this->single_ = value;
|
||||
return true;
|
||||
case 2:
|
||||
// bool stream = 2;
|
||||
this->stream_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType CameraImageRequest::message_type() const { return APIMessageType::CAMERA_IMAGE_REQUEST; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool has_mode = 2;
|
||||
this->has_mode_ = value;
|
||||
return true;
|
||||
case 3:
|
||||
// ClimateMode mode = 3;
|
||||
this->mode_ = static_cast<climate::ClimateMode>(value);
|
||||
return true;
|
||||
case 4:
|
||||
// bool has_target_temperature = 4;
|
||||
this->has_target_temperature_ = value;
|
||||
return true;
|
||||
case 6:
|
||||
// bool has_target_temperature_low = 6;
|
||||
this->has_target_temperature_low_ = value;
|
||||
return true;
|
||||
case 8:
|
||||
// bool has_target_temperature_high = 8;
|
||||
this->has_target_temperature_high_ = value;
|
||||
return true;
|
||||
case 10:
|
||||
// bool has_away = 10;
|
||||
this->has_away_ = value;
|
||||
return true;
|
||||
case 11:
|
||||
// bool away = 11;
|
||||
this->away_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ClimateCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
case 5:
|
||||
// float target_temperature = 5;
|
||||
this->target_temperature_ = as_float(value);
|
||||
return true;
|
||||
case 7:
|
||||
// float target_temperature_low = 7;
|
||||
this->target_temperature_low_ = as_float(value);
|
||||
return true;
|
||||
case 9:
|
||||
// float target_temperature_high = 9;
|
||||
this->target_temperature_high_ = as_float(value);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType ClimateCommandRequest::message_type() const { return APIMessageType::CLIMATE_COMMAND_REQUEST; }
|
||||
uint32_t ClimateCommandRequest::get_key() const { return this->key_; }
|
||||
optional<climate::ClimateMode> ClimateCommandRequest::get_mode() const {
|
||||
if (!this->has_mode_)
|
||||
return {};
|
||||
return this->mode_;
|
||||
}
|
||||
optional<float> ClimateCommandRequest::get_target_temperature() const {
|
||||
if (!this->has_target_temperature_)
|
||||
return {};
|
||||
return this->target_temperature_;
|
||||
}
|
||||
optional<float> ClimateCommandRequest::get_target_temperature_low() const {
|
||||
if (!this->has_target_temperature_low_)
|
||||
return {};
|
||||
return this->target_temperature_low_;
|
||||
}
|
||||
optional<float> ClimateCommandRequest::get_target_temperature_high() const {
|
||||
if (!this->has_target_temperature_high_)
|
||||
return {};
|
||||
return this->target_temperature_high_;
|
||||
}
|
||||
optional<bool> ClimateCommandRequest::get_away() const {
|
||||
if (!this->has_away_)
|
||||
return {};
|
||||
return this->away_;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -1,162 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_COVER
|
||||
enum LegacyCoverCommand {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0,
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
||||
LEGACY_COVER_COMMAND_STOP = 2,
|
||||
};
|
||||
|
||||
class CoverCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
optional<LegacyCoverCommand> get_legacy_command() const;
|
||||
optional<float> get_position() const;
|
||||
optional<float> get_tilt() const;
|
||||
bool get_stop() const { return this->stop_; }
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool has_legacy_command_{false};
|
||||
LegacyCoverCommand legacy_command_{LEGACY_COVER_COMMAND_OPEN};
|
||||
bool has_position_{false};
|
||||
float position_{0.0f};
|
||||
bool has_tilt_{false};
|
||||
float tilt_{0.0f};
|
||||
bool stop_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
class FanCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
optional<bool> get_state() const;
|
||||
optional<fan::FanSpeed> get_speed() const;
|
||||
optional<bool> get_oscillating() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool has_state_{false};
|
||||
bool state_{false};
|
||||
bool has_speed_{false};
|
||||
fan::FanSpeed speed_{fan::FAN_SPEED_LOW};
|
||||
bool has_oscillating_{false};
|
||||
bool oscillating_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
class LightCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
optional<bool> get_state() const;
|
||||
optional<float> get_brightness() const;
|
||||
optional<float> get_red() const;
|
||||
optional<float> get_green() const;
|
||||
optional<float> get_blue() const;
|
||||
optional<float> get_white() const;
|
||||
optional<float> get_color_temperature() const;
|
||||
optional<uint32_t> get_transition_length() const;
|
||||
optional<uint32_t> get_flash_length() const;
|
||||
optional<std::string> get_effect() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool has_state_{false};
|
||||
bool state_{false};
|
||||
bool has_brightness_{false};
|
||||
float brightness_{0.0f};
|
||||
bool has_rgb_{false};
|
||||
float red_{0.0f};
|
||||
float green_{0.0f};
|
||||
float blue_{0.0f};
|
||||
bool has_white_{false};
|
||||
float white_{0.0f};
|
||||
bool has_color_temperature_{false};
|
||||
float color_temperature_{0.0f};
|
||||
bool has_transition_length_{false};
|
||||
uint32_t transition_length_{0};
|
||||
bool has_flash_length_{false};
|
||||
uint32_t flash_length_{0};
|
||||
bool has_effect_{false};
|
||||
std::string effect_{};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
class SwitchCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
bool get_state() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool state_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
class CameraImageRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool get_single() const;
|
||||
bool get_stream() const;
|
||||
APIMessageType message_type() const override;
|
||||
|
||||
protected:
|
||||
bool single_{false};
|
||||
bool stream_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
class ClimateCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
optional<climate::ClimateMode> get_mode() const;
|
||||
optional<float> get_target_temperature() const;
|
||||
optional<float> get_target_temperature_low() const;
|
||||
optional<float> get_target_temperature_high() const;
|
||||
optional<bool> get_away() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool has_mode_{false};
|
||||
climate::ClimateMode mode_{climate::CLIMATE_MODE_OFF};
|
||||
bool has_target_temperature_{false};
|
||||
float target_temperature_{0.0f};
|
||||
bool has_target_temperature_low_{false};
|
||||
float target_temperature_low_{0.0f};
|
||||
bool has_target_temperature_high_{false};
|
||||
float target_temperature_high_{0.0f};
|
||||
bool has_away_{false};
|
||||
bool away_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
214
esphome/components/api/custom_api_device.h
Normal file
214
esphome/components/api/custom_api_device.h
Normal file
@@ -0,0 +1,214 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "user_services.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||
public:
|
||||
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||
void (T::*callback)(Ts...))
|
||||
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
||||
|
||||
protected:
|
||||
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
||||
|
||||
T *obj_;
|
||||
void (T::*callback_)(Ts...);
|
||||
};
|
||||
|
||||
class CustomAPIDevice {
|
||||
public:
|
||||
/// Return if a client (such as Home Assistant) is connected to the native API.
|
||||
bool is_connected() const { return global_api_server->is_connected(); }
|
||||
|
||||
/** Register a custom native API service that will show up in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* register_service(&CustomNativeAPI::on_start_washer_cycle, "start_washer_cycle",
|
||||
* {"cycle_length"});
|
||||
* }
|
||||
*
|
||||
* void on_start_washer_cycle(int cycle_length) {
|
||||
* // Start washer cycle.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @tparam Ts The argument types for the service, automatically deduced from the function arguments.
|
||||
* @param callback The member function to call when the service is triggered.
|
||||
* @param name The name of the service to register.
|
||||
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
template<typename T, typename... Ts>
|
||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
/** Register a custom native API service that will show up in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* register_service(&CustomNativeAPI::on_hello_world, "hello_world");
|
||||
* }
|
||||
*
|
||||
* void on_hello_world() {
|
||||
* // Hello World service called.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the service is triggered.
|
||||
* @param name The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
/** Subscribe to the state of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string state) {
|
||||
* // State of sensor.weather_forecast is `state`
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param entity_id The entity_id to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||
}
|
||||
|
||||
/** Subscribe to the state of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string entity_id, std::string state) {
|
||||
* // State of `entity_id` is `state`
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param entity_id The entity_id to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||
}
|
||||
|
||||
/** Call a Home Assistant service from ESPHome.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* call_homeassistant_service("homeassistant.restart");
|
||||
* ```
|
||||
*
|
||||
* @param service_name The service to call.
|
||||
*/
|
||||
void call_homeassistant_service(const std::string &service_name) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Call a Home Assistant service from ESPHome.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* call_homeassistant_service("light.turn_on", {
|
||||
* {"entity_id", "light.my_light"},
|
||||
* {"brightness", "127"},
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param service_name The service to call.
|
||||
* @param data The data for the service call, mapping from string to string.
|
||||
*/
|
||||
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
for (auto &it : data) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.first;
|
||||
kv.value = it.second;
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Fire an ESPHome event in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* fire_homeassistant_event("esphome.something_happened");
|
||||
* ```
|
||||
*
|
||||
* @param event_name The event to fire.
|
||||
*/
|
||||
void fire_homeassistant_event(const std::string &event_name) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = event_name;
|
||||
resp.is_event = true;
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Fire an ESPHome event in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* fire_homeassistant_event("esphome.something_happened", {
|
||||
* {"my_value", "500"},
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param event_name The event to fire.
|
||||
* @param data The data for the event, mapping from string to string.
|
||||
*/
|
||||
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
resp.is_event = true;
|
||||
for (auto &it : data) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.first;
|
||||
kv.value = it.second;
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
67
esphome/components/api/homeassistant_service.h
Normal file
67
esphome/components/api/homeassistant_service.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<typename... Ts> class TemplatableKeyValuePair {
|
||||
public:
|
||||
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
|
||||
std::string key;
|
||||
TemplatableStringValue<Ts...> value;
|
||||
};
|
||||
|
||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
||||
|
||||
TEMPLATABLE_STRING_VALUE(service);
|
||||
template<typename T> void add_data(std::string key, T value) {
|
||||
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
template<typename T> void add_data_template(std::string key, T value) {
|
||||
this->data_template_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
template<typename T> void add_variable(std::string key, T value) {
|
||||
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
|
||||
void play(Ts... x) override {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = this->service_.value(x...);
|
||||
resp.is_event = this->is_event_;
|
||||
for (auto &it : this->data_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
for (auto &it : this->data_template_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.data_template.push_back(kv);
|
||||
}
|
||||
for (auto &it : this->variables_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.variables.push_back(kv);
|
||||
}
|
||||
this->parent_->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
protected:
|
||||
APIServer *parent_;
|
||||
bool is_event_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -2,189 +2,54 @@
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_connection.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(binary_sensor);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("binary_sensor", binary_sensor));
|
||||
// string device_class = 5;
|
||||
buffer.encode_string(5, binary_sensor->get_device_class());
|
||||
// bool is_status_binary_sensor = 6;
|
||||
buffer.encode_bool(6, binary_sensor->is_status_binary_sensor());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE);
|
||||
return this->client_->send_binary_sensor_info(binary_sensor);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(cover);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("cover", cover));
|
||||
auto traits = cover->get_traits();
|
||||
|
||||
// bool assumed_state = 5;
|
||||
buffer.encode_bool(5, traits.get_is_assumed_state());
|
||||
// bool supports_position = 6;
|
||||
buffer.encode_bool(6, traits.get_supports_position());
|
||||
// bool supports_tilt = 7;
|
||||
buffer.encode_bool(7, traits.get_supports_tilt());
|
||||
// string device_class = 8;
|
||||
buffer.encode_string(8, cover->get_device_class());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_COVER_RESPONSE);
|
||||
}
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); }
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool ListEntitiesIterator::on_fan(fan::FanState *fan) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(fan);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("fan", fan));
|
||||
// bool supports_oscillation = 5;
|
||||
buffer.encode_bool(5, fan->get_traits().supports_oscillation());
|
||||
// bool supports_speed = 6;
|
||||
buffer.encode_bool(6, fan->get_traits().supports_speed());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_FAN_RESPONSE);
|
||||
}
|
||||
bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); }
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(light);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("light", light));
|
||||
// bool supports_brightness = 5;
|
||||
auto traits = light->get_traits();
|
||||
buffer.encode_bool(5, traits.get_supports_brightness());
|
||||
// bool supports_rgb = 6;
|
||||
buffer.encode_bool(6, traits.get_supports_rgb());
|
||||
// bool supports_white_value = 7;
|
||||
buffer.encode_bool(7, traits.get_supports_rgb_white_value());
|
||||
// bool supports_color_temperature = 8;
|
||||
buffer.encode_bool(8, traits.get_supports_color_temperature());
|
||||
if (traits.get_supports_color_temperature()) {
|
||||
// float min_mireds = 9;
|
||||
buffer.encode_float(9, traits.get_min_mireds());
|
||||
// float max_mireds = 10;
|
||||
buffer.encode_float(10, traits.get_max_mireds());
|
||||
}
|
||||
// repeated string effects = 11;
|
||||
if (light->supports_effects()) {
|
||||
buffer.encode_string(11, "None");
|
||||
for (auto *effect : light->get_effects()) {
|
||||
buffer.encode_string(11, effect->get_name());
|
||||
}
|
||||
}
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE);
|
||||
}
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); }
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(sensor);
|
||||
// string unique_id = 4;
|
||||
std::string unique_id = sensor->unique_id();
|
||||
if (unique_id.empty())
|
||||
unique_id = get_default_unique_id("sensor", sensor);
|
||||
buffer.encode_string(4, unique_id);
|
||||
// string icon = 5;
|
||||
buffer.encode_string(5, sensor->get_icon());
|
||||
// string unit_of_measurement = 6;
|
||||
buffer.encode_string(6, sensor->get_unit_of_measurement());
|
||||
// int32 accuracy_decimals = 7;
|
||||
buffer.encode_int32(7, sensor->get_accuracy_decimals());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE);
|
||||
}
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); }
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(a_switch);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("switch", a_switch));
|
||||
// string icon = 5;
|
||||
buffer.encode_string(5, a_switch->get_icon());
|
||||
// bool assumed_state = 6;
|
||||
buffer.encode_bool(6, a_switch->assumed_state());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE);
|
||||
}
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(text_sensor);
|
||||
// string unique_id = 4;
|
||||
std::string unique_id = text_sensor->unique_id();
|
||||
if (unique_id.empty())
|
||||
unique_id = get_default_unique_id("text_sensor", text_sensor);
|
||||
buffer.encode_string(4, unique_id);
|
||||
// string icon = 5;
|
||||
buffer.encode_string(5, text_sensor->get_icon());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE);
|
||||
return this->client_->send_text_sensor_info(text_sensor);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() {
|
||||
return this->client_->send_empty_message(APIMessageType::LIST_ENTITIES_DONE_RESPONSE);
|
||||
}
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
service->encode_list_service_response(buffer);
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE);
|
||||
auto resp = service->encode_list_service_response();
|
||||
return this->client_->send_list_entities_services_response(resp);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(camera);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("camera", camera));
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE);
|
||||
return this->client_->send_camera_info(camera);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(climate);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("climate", climate));
|
||||
|
||||
auto traits = climate->get_traits();
|
||||
// bool supports_current_temperature = 5;
|
||||
buffer.encode_bool(5, traits.get_supports_current_temperature());
|
||||
// bool supports_two_point_target_temperature = 6;
|
||||
buffer.encode_bool(6, traits.get_supports_two_point_target_temperature());
|
||||
// repeated ClimateMode supported_modes = 7;
|
||||
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_HEAT}) {
|
||||
if (traits.supports_mode(mode))
|
||||
buffer.encode_uint32(7, mode, true);
|
||||
}
|
||||
|
||||
// float visual_min_temperature = 8;
|
||||
buffer.encode_float(8, traits.get_visual_min_temperature());
|
||||
// float visual_max_temperature = 9;
|
||||
buffer.encode_float(9, traits.get_visual_max_temperature());
|
||||
// float visual_temperature_step = 10;
|
||||
buffer.encode_float(10, traits.get_visual_temperature_step());
|
||||
// bool supports_away = 11;
|
||||
buffer.encode_bool(11, traits.get_supports_away());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE);
|
||||
}
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
|
||||
#endif
|
||||
|
||||
APIMessageType ListEntitiesRequest::message_type() const { return APIMessageType::LIST_ENTITIES_REQUEST; }
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "api_message.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class ListEntitiesRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class APIConnection;
|
||||
|
||||
class ListEntitiesIterator : public ComponentIterator {
|
||||
|
||||
@@ -1,61 +1,59 @@
|
||||
#include "api_message.h"
|
||||
#include "proto.h"
|
||||
#include "util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.message";
|
||||
static const char *TAG = "api.proto";
|
||||
|
||||
bool APIMessage::decode_varint(uint32_t field_id, uint32_t value) { return false; }
|
||||
bool APIMessage::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { return false; }
|
||||
bool APIMessage::decode_32bit(uint32_t field_id, uint32_t value) { return false; }
|
||||
void APIMessage::encode(APIBuffer &buffer) {}
|
||||
void APIMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
uint32_t i = 0;
|
||||
bool error = false;
|
||||
while (i < length) {
|
||||
uint32_t consumed;
|
||||
auto res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
|
||||
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid field start at %u", i);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t field_type = (*res) & 0b111;
|
||||
uint32_t field_id = (*res) >> 3;
|
||||
uint32_t field_type = (res->as_uint32()) & 0b111;
|
||||
uint32_t field_id = (res->as_uint32()) >> 3;
|
||||
i += consumed;
|
||||
|
||||
switch (field_type) {
|
||||
case 0: { // VarInt
|
||||
res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid VarInt at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_varint(field_id, *res)) {
|
||||
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, *res);
|
||||
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32());
|
||||
}
|
||||
i += consumed;
|
||||
break;
|
||||
}
|
||||
case 2: { // Length-delimited
|
||||
res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
i += consumed;
|
||||
if (*res > length - i) {
|
||||
if (field_length > length - i) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_length_delimited(field_id, &buffer[i], *res)) {
|
||||
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
|
||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
|
||||
}
|
||||
i += *res;
|
||||
i += field_length;
|
||||
break;
|
||||
}
|
||||
case 5: { // 32-bit
|
||||
@@ -64,9 +62,8 @@ void APIMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
|
||||
(uint32_t(buffer[i + 3]) << 24);
|
||||
if (!this->decode_32bit(field_id, val)) {
|
||||
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
|
||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
|
||||
}
|
||||
i += 4;
|
||||
@@ -83,5 +80,11 @@ void APIMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string ProtoMessage::dump() const {
|
||||
std::string out;
|
||||
this->dump_to(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
278
esphome/components/api/proto.h
Normal file
278
esphome/components/api/proto.h
Normal file
@@ -0,0 +1,278 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
|
||||
class ProtoVarInt {
|
||||
public:
|
||||
ProtoVarInt() : value_(0) {}
|
||||
explicit ProtoVarInt(uint64_t value) : value_(value) {}
|
||||
|
||||
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
|
||||
if (consumed != nullptr)
|
||||
*consumed = 0;
|
||||
|
||||
if (len == 0)
|
||||
return {};
|
||||
|
||||
uint64_t result = 0;
|
||||
uint8_t bitpos = 0;
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
uint8_t val = buffer[i];
|
||||
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
|
||||
bitpos += 7;
|
||||
if ((val & 0x80) == 0) {
|
||||
if (consumed != nullptr)
|
||||
*consumed = i + 1;
|
||||
return ProtoVarInt(result);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t as_uint32() const { return this->value_; }
|
||||
uint64_t as_uint64() const { return this->value_; }
|
||||
bool as_bool() const { return this->value_; }
|
||||
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
|
||||
int32_t as_int32() const {
|
||||
// Not ZigZag encoded
|
||||
return static_cast<int32_t>(this->as_int64());
|
||||
}
|
||||
int64_t as_int64() const {
|
||||
// Not ZigZag encoded
|
||||
return static_cast<int64_t>(this->value_);
|
||||
}
|
||||
int32_t as_sint32() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1)
|
||||
return static_cast<int32_t>(~(this->value_ >> 1));
|
||||
else
|
||||
return static_cast<int32_t>(this->value_ >> 1);
|
||||
}
|
||||
int64_t as_sint64() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1)
|
||||
return static_cast<int64_t>(~(this->value_ >> 1));
|
||||
else
|
||||
return static_cast<int64_t>(this->value_ >> 1);
|
||||
}
|
||||
void encode(std::vector<uint8_t> &out) {
|
||||
uint32_t val = this->value_;
|
||||
if (val <= 0x7F) {
|
||||
out.push_back(val);
|
||||
return;
|
||||
}
|
||||
while (val) {
|
||||
uint8_t temp = val & 0x7F;
|
||||
val >>= 7;
|
||||
if (val) {
|
||||
out.push_back(temp | 0x80);
|
||||
} else {
|
||||
out.push_back(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
class ProtoLengthDelimited {
|
||||
public:
|
||||
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
||||
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
||||
template<class C> C as_message() const {
|
||||
auto msg = C();
|
||||
msg.decode(this->value_, this->length_);
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint8_t *const value_;
|
||||
const size_t length_;
|
||||
};
|
||||
|
||||
class Proto32Bit {
|
||||
public:
|
||||
explicit Proto32Bit(uint32_t value) : value_(value) {}
|
||||
uint32_t as_fixed32() const { return this->value_; }
|
||||
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
|
||||
float as_float() const {
|
||||
union {
|
||||
uint32_t raw;
|
||||
float value;
|
||||
} s{};
|
||||
s.raw = this->value_;
|
||||
return s.value;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint32_t value_;
|
||||
};
|
||||
|
||||
class Proto64Bit {
|
||||
public:
|
||||
explicit Proto64Bit(uint64_t value) : value_(value) {}
|
||||
uint64_t as_fixed64() const { return this->value_; }
|
||||
int64_t as_sfixed64() const { return static_cast<int64_t>(this->value_); }
|
||||
double as_double() const {
|
||||
union {
|
||||
uint64_t raw;
|
||||
double value;
|
||||
} s{};
|
||||
s.raw = this->value_;
|
||||
return s.value;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint64_t value_;
|
||||
};
|
||||
|
||||
class ProtoWriteBuffer {
|
||||
public:
|
||||
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
|
||||
void write(uint8_t value) { this->buffer_->push_back(value); }
|
||||
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
|
||||
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) {
|
||||
uint32_t val = (field_id << 3) | (type & 0b111);
|
||||
this->encode_varint_raw(val);
|
||||
}
|
||||
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 2);
|
||||
this->encode_varint_raw(len);
|
||||
auto *data = reinterpret_cast<const uint8_t *>(string);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
this->write(data[i]);
|
||||
}
|
||||
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
||||
this->encode_string(field_id, value.data(), value.size());
|
||||
}
|
||||
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||
}
|
||||
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(value);
|
||||
}
|
||||
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(ProtoVarInt(value));
|
||||
}
|
||||
void encode_bool(uint32_t field_id, bool value, bool force = false) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->write(0x01);
|
||||
}
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 5);
|
||||
this->write((value >> 0) & 0xFF);
|
||||
this->write((value >> 8) & 0xFF);
|
||||
this->write((value >> 16) & 0xFF);
|
||||
this->write((value >> 24) & 0xFF);
|
||||
}
|
||||
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
|
||||
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||
}
|
||||
void encode_float(uint32_t field_id, float value, bool force = false) {
|
||||
if (value == 0.0f && !force)
|
||||
return;
|
||||
|
||||
union {
|
||||
float value;
|
||||
uint32_t raw;
|
||||
} val{};
|
||||
val.value = value;
|
||||
this->encode_fixed32(field_id, val.raw);
|
||||
}
|
||||
void encode_int32(uint32_t field_id, int32_t value, bool force = false) {
|
||||
if (value < 0) {
|
||||
// negative int32 is always 10 byte long
|
||||
this->encode_int64(field_id, value, force);
|
||||
return;
|
||||
}
|
||||
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||
}
|
||||
void encode_int64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
||||
}
|
||||
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
||||
uint32_t uvalue;
|
||||
if (value < 0)
|
||||
uvalue = ~(value << 1);
|
||||
else
|
||||
uvalue = value << 1;
|
||||
this->encode_uint32(field_id, uvalue, force);
|
||||
}
|
||||
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
|
||||
this->encode_field_raw(field_id, 2);
|
||||
size_t begin = this->buffer_->size();
|
||||
|
||||
value.encode(*this);
|
||||
|
||||
const uint32_t nested_length = this->buffer_->size() - begin;
|
||||
// add size varint
|
||||
std::vector<uint8_t> var;
|
||||
ProtoVarInt(nested_length).encode(var);
|
||||
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
|
||||
}
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual void encode(ProtoWriteBuffer buffer) const = 0;
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
std::string dump() const;
|
||||
virtual void dump_to(std::string &out) const = 0;
|
||||
|
||||
protected:
|
||||
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; }
|
||||
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
|
||||
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
|
||||
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
|
||||
};
|
||||
|
||||
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_unauthenticated_access() = 0;
|
||||
virtual void on_no_setup_connection() = 0;
|
||||
virtual ProtoWriteBuffer create_buffer() = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
|
||||
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||
|
||||
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
||||
auto buffer = this->create_buffer();
|
||||
msg.encode(buffer);
|
||||
return this->send_buffer(buffer, message_type);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -1,49 +0,0 @@
|
||||
#include "service_call_message.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
APIMessageType SubscribeServiceCallsRequest::message_type() const {
|
||||
return APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST;
|
||||
}
|
||||
|
||||
APIMessageType ServiceCallResponse::message_type() const { return APIMessageType::SERVICE_CALL_RESPONSE; }
|
||||
void ServiceCallResponse::encode(APIBuffer &buffer) {
|
||||
// string service = 1;
|
||||
buffer.encode_string(1, this->service_);
|
||||
// map<string, string> data = 2;
|
||||
for (auto &it : this->data_) {
|
||||
auto nested = buffer.begin_nested(2);
|
||||
buffer.encode_string(1, it.key);
|
||||
buffer.encode_string(2, it.value);
|
||||
buffer.end_nested(nested);
|
||||
}
|
||||
// map<string, string> data_template = 3;
|
||||
for (auto &it : this->data_template_) {
|
||||
auto nested = buffer.begin_nested(3);
|
||||
buffer.encode_string(1, it.key);
|
||||
buffer.encode_string(2, it.value);
|
||||
buffer.end_nested(nested);
|
||||
}
|
||||
// map<string, string> variables = 4;
|
||||
for (auto &it : this->variables_) {
|
||||
auto nested = buffer.begin_nested(4);
|
||||
buffer.encode_string(1, it.key);
|
||||
buffer.encode_string(2, it.value());
|
||||
buffer.end_nested(nested);
|
||||
}
|
||||
}
|
||||
void ServiceCallResponse::set_service(const std::string &service) { this->service_ = service; }
|
||||
void ServiceCallResponse::set_data(const std::vector<KeyValuePair> &data) { this->data_ = data; }
|
||||
void ServiceCallResponse::set_data_template(const std::vector<KeyValuePair> &data_template) {
|
||||
this->data_template_ = data_template;
|
||||
}
|
||||
void ServiceCallResponse::set_variables(const std::vector<TemplatableKeyValuePair> &variables) {
|
||||
this->variables_ = variables;
|
||||
}
|
||||
|
||||
KeyValuePair::KeyValuePair(const std::string &key, const std::string &value) : key(key), value(value) {}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user