mirror of
https://github.com/esphome/esphome.git
synced 2025-11-04 17:11:51 +00:00
Compare commits
839 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eaffee160 | ||
|
|
557a622f71 | ||
|
|
e9c6556296 | ||
|
|
ae9b247f47 | ||
|
|
a59761d292 | ||
|
|
030c87d142 | ||
|
|
fddb05c845 | ||
|
|
3b13e62d3f | ||
|
|
9e7acd6f75 | ||
|
|
7d9c043f1d | ||
|
|
bafbeefb37 | ||
|
|
07f2931841 | ||
|
|
616ad04131 | ||
|
|
b966e58f9e | ||
|
|
f33c2a48eb | ||
|
|
44d5be6e7b | ||
|
|
44562dbef1 | ||
|
|
cafdcaec29 | ||
|
|
b660e5a7fa | ||
|
|
3b4ea0ed0a | ||
|
|
403b6e32e3 | ||
|
|
229bf719a2 | ||
|
|
2225594ee8 | ||
|
|
b91a1aa027 | ||
|
|
13dbdd9b16 | ||
|
|
37bc0b3b5a | ||
|
|
be70a96651 | ||
|
|
d83d214497 | ||
|
|
6ec0f80b76 | ||
|
|
06f566346d | ||
|
|
b680649113 | ||
|
|
5ab2ef4079 | ||
|
|
392ed64375 | ||
|
|
566c129435 | ||
|
|
c903eb2d01 | ||
|
|
69c78651d5 | ||
|
|
f7232b199a | ||
|
|
ffc6fe9ca0 | ||
|
|
06b8e4df27 | ||
|
|
b103be99e8 | ||
|
|
28ed42d879 | ||
|
|
98f0d75180 | ||
|
|
34487c9de4 | ||
|
|
822377be8b | ||
|
|
dd4fb85170 | ||
|
|
07b3327102 | ||
|
|
02aa75f68c | ||
|
|
07db9319ad | ||
|
|
4ae4a4ee88 | ||
|
|
a7c648b60b | ||
|
|
4d7c1ae143 | ||
|
|
cc6d1e85cc | ||
|
|
7fb116d87d | ||
|
|
cc43e01e34 | ||
|
|
6d3ccf4df5 | ||
|
|
bb3d0706d3 | ||
|
|
820dedbcd2 | ||
|
|
aed6f2b1ea | ||
|
|
bf1885af3f | ||
|
|
d8e4f5d56b | ||
|
|
af616473aa | ||
|
|
2033ac34e5 | ||
|
|
5e239d3d88 | ||
|
|
30893afd67 | ||
|
|
a39bb7b92f | ||
|
|
b53f9f2a81 | ||
|
|
586e36906d | ||
|
|
e6f8e73705 | ||
|
|
eaf9735eda | ||
|
|
2e50e1f506 | ||
|
|
76a6c39f25 | ||
|
|
bf01c22e1f | ||
|
|
99f14e03d4 | ||
|
|
d92c8ccadf | ||
|
|
7964b724ed | ||
|
|
e0c5b45694 | ||
|
|
26407e001b | ||
|
|
3ecae3f16f | ||
|
|
5c359856ff | ||
|
|
2d618768d5 | ||
|
|
808e3be324 | ||
|
|
9e23987db8 | ||
|
|
ad76312f66 | ||
|
|
2028362fd5 | ||
|
|
13e0d6b9a1 | ||
|
|
a6255c31fe | ||
|
|
46356cbc4a | ||
|
|
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 | ||
|
|
82d5d50d61 | ||
|
|
ecb1c77f8b | ||
|
|
f9a8629157 | ||
|
|
e2b655a6cc | ||
|
|
04e6f475b4 | ||
|
|
0082c5b459 | ||
|
|
3fd130869e | ||
|
|
d000440927 | ||
|
|
4fb750de43 | ||
|
|
9632ac0e02 | ||
|
|
35078fd52f | ||
|
|
0bb81e5b2d | ||
|
|
35a2258f12 | ||
|
|
b650704877 | ||
|
|
0c0dec2534 | ||
|
|
d1b051a6bd | ||
|
|
27204aa53c | ||
|
|
8aedac81a5 | ||
|
|
42007d03d4 | ||
|
|
821c1a8bbd | ||
|
|
bab562dc3a | ||
|
|
f63fd9696f | ||
|
|
cd7af19e7c | ||
|
|
64bd33a94e | ||
|
|
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 | ||
|
|
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 | ||
|
|
8027facb39 | ||
|
|
16c2dc2aaf | ||
|
|
dc2279b74f | ||
|
|
75275c4e93 | ||
|
|
65d3dc9cb8 | ||
|
|
66aa02fc34 | ||
|
|
be6b4ee47f | ||
|
|
90f909d2ea | ||
|
|
09fd505f08 | ||
|
|
042ccde441 | ||
|
|
442030b6ca | ||
|
|
1df9ae53f8 | ||
|
|
adf2a463fd | ||
|
|
80aaf66963 | ||
|
|
560251ab2a | ||
|
|
864c5d8908 | ||
|
|
742c21506c | ||
|
|
a6faccb4d9 | ||
|
|
69fd3e8937 | ||
|
|
41233d7f25 | ||
|
|
07286d1d76 | ||
|
|
08148c5830 | ||
|
|
969bdb06ce | ||
|
|
b0bb692af4 | ||
|
|
7bf6fd316f | ||
|
|
c1f5e04d6c | ||
|
|
5a67e72389 | ||
|
|
91c9b11647 | ||
|
|
bb2582717f | ||
|
|
d62ef35860 | ||
|
|
59c5956f93 | ||
|
|
e4f055597c | ||
|
|
4c49beb3c7 | ||
|
|
8ff742d9ab | ||
|
|
d63cd8b4cd | ||
|
|
3f0503c296 | ||
|
|
c18050bda0 | ||
|
|
6542be5588 | ||
|
|
9fb60b8015 | ||
|
|
1177b856a0 | ||
|
|
c0adaa8de8 | ||
|
|
ae8700447e | ||
|
|
d64a4ef2b4 | ||
|
|
1e22b1e959 | ||
|
|
e077ad56bd | ||
|
|
f1e00f8c8e | ||
|
|
9a152e588e | ||
|
|
16f42a3d03 | ||
|
|
6c8d0f1852 | ||
|
|
b59cf6572b | ||
|
|
4fa11dfa68 | ||
|
|
352bdd9fb5 | ||
|
|
96ff9a162c | ||
|
|
af15a4e710 | ||
|
|
18426b71e4 | ||
|
|
7063aa6009 | ||
|
|
286ca07cc8 | ||
|
|
58b6311821 | ||
|
|
e553c0768e | ||
|
|
62d4b29662 | ||
|
|
16bc60644d |
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}]
|
[*.{yaml,yml}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
quote_type = single
|
||||||
7
.github/FUNDING.yml
vendored
7
.github/FUNDING.yml
vendored
@@ -1,8 +1,3 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github:
|
custom: https://www.nabucasa.com
|
||||||
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>
|
**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:
|
## Checklist:
|
||||||
- [ ] The code change is tested and works locally.
|
- [ ] The code change is tested and works locally.
|
||||||
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||||
|
|
||||||
If user exposed functionality or configuration variables are added/changed:
|
If user exposed functionality or configuration variables are added/changed:
|
||||||
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
- [ ] 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
|
||||||
36
.github/workflows/docker-lint-build.yml
vendored
Normal file
36
.github/workflows/docker-lint-build.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Build and publish lint docker image
|
||||||
|
|
||||||
|
# Only run when docker paths change
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [dev]
|
||||||
|
paths:
|
||||||
|
- 'docker/Dockerfile.lint'
|
||||||
|
- 'requirements.txt'
|
||||||
|
- 'requirements_test.txt'
|
||||||
|
- 'platformio.ini'
|
||||||
|
- '.github/workflows/docker-lint-build.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-docker-lint-iage:
|
||||||
|
name: Build docker containers
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Pull for cache
|
||||||
|
run: |
|
||||||
|
docker pull "esphome/esphome-lint:latest" || true
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
--cache-from "esphome/esphome-lint:latest" \
|
||||||
|
--file "docker/Dockerfile.lint" \
|
||||||
|
--tag "esphome/esphome-lint:latest" \
|
||||||
|
.
|
||||||
|
- 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 "esphome/esphome-lint:latest"
|
||||||
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-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Intellij Idea
|
||||||
|
.idea
|
||||||
|
|
||||||
# Hide some OS X stuff
|
# Hide some OS X stuff
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
@@ -48,8 +51,10 @@ htmlcov/
|
|||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
|
.esphome
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
|
cov.xml
|
||||||
*.cover
|
*.cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
@@ -76,7 +81,8 @@ venv.bak/
|
|||||||
.pioenvs
|
.pioenvs
|
||||||
.piolibdeps
|
.piolibdeps
|
||||||
.pio
|
.pio
|
||||||
.vscode
|
.vscode/
|
||||||
|
!.vscode/tasks.json
|
||||||
CMakeListsPrivate.txt
|
CMakeListsPrivate.txt
|
||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
|
|
||||||
@@ -114,4 +120,4 @@ config/
|
|||||||
tests/build/
|
tests/build/
|
||||||
tests/.esphome/
|
tests/.esphome/
|
||||||
/.temp-clang-tidy.cpp
|
/.temp-clang-tidy.cpp
|
||||||
/.idea/
|
.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/
|
|
||||||
BASE_VERSION: '2.1.1'
|
|
||||||
TZ: UTC
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- lint
|
|
||||||
- test
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
.lint: &lint
|
|
||||||
image: esphome/esphome-lint:latest
|
|
||||||
stage: lint
|
|
||||||
before_script:
|
|
||||||
- script/setup
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
.test: &test
|
|
||||||
image: esphome/esphome-lint:latest
|
|
||||||
stage: test
|
|
||||||
before_script:
|
|
||||||
- script/setup
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
.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 multiarch/qemu-user-static:5.0.0-2 --reset -p yes
|
|
||||||
- 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}:${BASE_VERSION}
|
|
||||||
BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH}
|
|
||||||
DOCKERFILE=docker/Dockerfile.hassio
|
|
||||||
else
|
|
||||||
BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:${BASE_VERSION}
|
|
||||||
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-custom:
|
|
||||||
<<: *lint
|
|
||||||
script:
|
|
||||||
- script/ci-custom.py
|
|
||||||
|
|
||||||
lint-python:
|
|
||||||
<<: *lint
|
|
||||||
script:
|
|
||||||
- script/lint-python
|
|
||||||
|
|
||||||
lint-tidy:
|
|
||||||
<<: *lint
|
|
||||||
script:
|
|
||||||
- pio init --ide atom
|
|
||||||
- script/clang-tidy --all-headers --fix
|
|
||||||
- script/ci-suggest-changes
|
|
||||||
|
|
||||||
lint-format:
|
|
||||||
<<: *lint
|
|
||||||
script:
|
|
||||||
- script/clang-format -i
|
|
||||||
- script/ci-suggest-changes
|
|
||||||
|
|
||||||
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
|
|
||||||
<<: *lint
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- pip install twine wheel
|
|
||||||
- python setup.py sdist bdist_wheel
|
|
||||||
- twine upload dist/*
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
.beta: &beta
|
|
||||||
<<: *docker-base
|
|
||||||
only:
|
|
||||||
- /^v([0-9\.]+b\d+)$/
|
|
||||||
except:
|
|
||||||
- branches
|
|
||||||
|
|
||||||
.dev: &dev
|
|
||||||
<<: *docker-base
|
|
||||||
only:
|
|
||||||
- dev
|
|
||||||
|
|
||||||
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"
|
|
||||||
armv7-beta-docker:
|
|
||||||
<<: *beta
|
|
||||||
variables:
|
|
||||||
BETA: "YES"
|
|
||||||
BUILD_ARCH: armv7
|
|
||||||
IS_HASSIO: "NO"
|
|
||||||
RELEASE: "YES"
|
|
||||||
armv7-beta-hassio:
|
|
||||||
<<: *beta
|
|
||||||
variables:
|
|
||||||
BETA: "YES"
|
|
||||||
BUILD_ARCH: armv7
|
|
||||||
IS_HASSIO: "YES"
|
|
||||||
RELEASE: "YES"
|
|
||||||
armv7-dev-docker:
|
|
||||||
<<: *dev
|
|
||||||
variables:
|
|
||||||
BUILD_ARCH: armv7
|
|
||||||
DEV: "YES"
|
|
||||||
IS_HASSIO: "NO"
|
|
||||||
armv7-dev-hassio:
|
|
||||||
<<: *dev
|
|
||||||
variables:
|
|
||||||
BUILD_ARCH: armv7
|
|
||||||
DEV: "YES"
|
|
||||||
IS_HASSIO: "YES"
|
|
||||||
armv7-latest-docker:
|
|
||||||
<<: *latest
|
|
||||||
variables:
|
|
||||||
BETA: "YES"
|
|
||||||
BUILD_ARCH: armv7
|
|
||||||
IS_HASSIO: "NO"
|
|
||||||
LATEST: "YES"
|
|
||||||
RELEASE: "YES"
|
|
||||||
armv7-latest-hassio:
|
|
||||||
<<: *latest
|
|
||||||
variables:
|
|
||||||
BETA: "YES"
|
|
||||||
BUILD_ARCH: armv7
|
|
||||||
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
|
- port: 6052
|
||||||
onOpen: open-preview
|
onOpen: open-preview
|
||||||
tasks:
|
tasks:
|
||||||
- before: script/setup
|
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
|
||||||
command: python -m esphome config dashboard
|
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
|
||||||
49
.travis.yml
49
.travis.yml
@@ -1,49 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: python
|
|
||||||
python: '3.6'
|
|
||||||
install: script/setup
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- "~/.platformio"
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
include:
|
|
||||||
- python: "3.7"
|
|
||||||
env: TARGET=Lint3.7
|
|
||||||
script:
|
|
||||||
- script/ci-custom.py
|
|
||||||
- flake8 esphome
|
|
||||||
- pylint esphome
|
|
||||||
- python: "3.6"
|
|
||||||
env: TARGET=Test3.6
|
|
||||||
script:
|
|
||||||
- esphome tests/test1.yaml compile
|
|
||||||
- esphome tests/test2.yaml compile
|
|
||||||
- esphome tests/test3.yaml compile
|
|
||||||
- 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
|
|
||||||
- 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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
124
CODEOWNERS
Normal file
124
CODEOWNERS
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# 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/b_parasite/* @rbaron
|
||||||
|
esphome/components/bang_bang/* @OttoWinter
|
||||||
|
esphome/components/binary_sensor/* @esphome/core
|
||||||
|
esphome/components/ble_client/* @buxtronix
|
||||||
|
esphome/components/bme680_bsec/* @trvrnrth
|
||||||
|
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/fingerprint_grow/* @OnFreund @loongyh
|
||||||
|
esphome/components/globals/* @esphome/core
|
||||||
|
esphome/components/gpio/* @esphome/core
|
||||||
|
esphome/components/gps/* @coogle
|
||||||
|
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/sgp40/* @SenexCrenshaw
|
||||||
|
esphome/components/sht4x/* @sjtrny
|
||||||
|
esphome/components/shutdown/* @esphome/core
|
||||||
|
esphome/components/sim800l/* @glmnet
|
||||||
|
esphome/components/sm2135/* @BoukeHaarsma23
|
||||||
|
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/tca9548a/* @andreashergert1984
|
||||||
|
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/tof10120/* @wstrzalka
|
||||||
|
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
|
||||||
@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
|
|||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment include:
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
- Using welcoming and inclusive language
|
||||||
* Being respectful of differing viewpoints and experiences
|
- Being respectful of differing viewpoints and experiences
|
||||||
* Gracefully accepting constructive criticism
|
- Gracefully accepting constructive criticism
|
||||||
* Focusing on what is best for the community
|
- Focusing on what is best for the community
|
||||||
* Showing empathy towards other community members
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
|||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@otto-winter.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@nabucasa.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
include LICENSE
|
include LICENSE
|
||||||
include README.md
|
include README.md
|
||||||
|
include requirements.txt
|
||||||
include esphome/dashboard/templates/*.html
|
include esphome/dashboard/templates/*.html
|
||||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||||
recursive-include esphome *.cpp *.h *.tcc
|
recursive-include esphome *.cpp *.h *.tcc
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.1.1
|
ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0
|
||||||
FROM ${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
|
||||||
|
|
||||||
|
# Then copy esphome and install
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pip3 install --no-cache-dir -e .
|
RUN pip3 install --no-cache-dir -e .
|
||||||
|
|
||||||
ENV USERNAME=""
|
# Settings for dashboard
|
||||||
ENV PASSWORD=""
|
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
|
WORKDIR /config
|
||||||
|
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
|
||||||
|
# in every docker command twice
|
||||||
ENTRYPOINT ["esphome"]
|
ENTRYPOINT ["esphome"]
|
||||||
|
# When no arguments given, start the dashboard in the workdir
|
||||||
CMD ["/config", "dashboard"]
|
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,11 +1,17 @@
|
|||||||
ARG BUILD_FROM
|
ARG BUILD_FROM
|
||||||
FROM ${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 root filesystem
|
||||||
COPY docker/rootfs/ /
|
COPY docker/rootfs/ /
|
||||||
COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
|
|
||||||
COPY esphome /opt/esphome/esphome
|
|
||||||
|
|
||||||
|
# Then copy esphome and install
|
||||||
|
COPY . /opt/esphome/
|
||||||
RUN pip3 install --no-cache-dir -e /opt/esphome
|
RUN pip3 install --no-cache-dir -e /opt/esphome
|
||||||
|
|
||||||
# Build arguments
|
# Build arguments
|
||||||
|
|||||||
@@ -1,20 +1,9 @@
|
|||||||
FROM esphome/esphome-base-amd64:2.1.1
|
FROM esphome/esphome-lint-base:3.0.0
|
||||||
|
|
||||||
|
COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
|
||||||
RUN \
|
RUN \
|
||||||
apt-get update \
|
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& /platformio_install_deps.py /platformio.ini
|
||||||
clang-format-7 \
|
|
||||||
clang-tidy-7 \
|
|
||||||
patch \
|
|
||||||
&& rm -rf \
|
|
||||||
/tmp/* \
|
|
||||||
/var/{cache,log}/* \
|
|
||||||
/var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY requirements_test.txt /requirements_test.txt
|
|
||||||
RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
|
|
||||||
|
|
||||||
RUN ln -s /usr/bin/pip3 /usr/bin/pip && ln -f -s /usr/bin/python3 /usr/bin/python
|
|
||||||
|
|
||||||
VOLUME ["/esphome"]
|
VOLUME ["/esphome"]
|
||||||
WORKDIR /esphome
|
WORKDIR /esphome
|
||||||
|
|||||||
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])
|
||||||
10
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
10
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
@@ -8,7 +8,15 @@ declare esphome_version
|
|||||||
|
|
||||||
if bashio::config.has_value 'esphome_version'; then
|
if bashio::config.has_value 'esphome_version'; then
|
||||||
esphome_version=$(bashio::config 'esphome_version')
|
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})..."
|
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
|
||||||
pip3 install -U --no-cache-dir "${full_url}" \
|
pip3 install -U --no-cache-dir "${full_url}" \
|
||||||
|| bashio::exit.nok "Failed installing esphome pinned version."
|
|| bashio::exit.nok "Failed installing esphome pinned version."
|
||||||
|
|||||||
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
@@ -1,5 +1,3 @@
|
|||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
@@ -10,44 +8,47 @@ from datetime import datetime
|
|||||||
from esphome import const, writer, yaml_util
|
from esphome import const, writer, yaml_util
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.config import iter_components, read_config, strip_default_ids
|
from esphome.config import iter_components, read_config, strip_default_ids
|
||||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
from esphome.const import (
|
||||||
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
|
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.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||||
from esphome.helpers import color, indent
|
from esphome.helpers import indent
|
||||||
from esphome.py_compat import IS_PY2, safe_input, IS_PY3
|
from esphome.util import (
|
||||||
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
|
run_external_command,
|
||||||
|
run_external_process,
|
||||||
|
safe_print,
|
||||||
|
list_yaml_files,
|
||||||
|
get_serial_ports,
|
||||||
|
)
|
||||||
|
from esphome.log import color, setup_log, Fore
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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(include_links=True):
|
|
||||||
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):
|
def choose_prompt(options):
|
||||||
if not options:
|
if not options:
|
||||||
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
raise EsphomeError(
|
||||||
"sections (ota, mqtt, ...) are in your configuration and/or the device "
|
"Found no valid options for upload/logging, please make sure relevant "
|
||||||
"is plugged in.")
|
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
||||||
|
"device is plugged in."
|
||||||
|
)
|
||||||
|
|
||||||
if len(options) == 1:
|
if len(options) == 1:
|
||||||
return options[0][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):
|
for i, (desc, _) in enumerate(options):
|
||||||
safe_print(u" [{}] {}".format(i + 1, desc))
|
safe_print(f" [{i+1}] {desc}")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
opt = safe_input('(number): ')
|
opt = input("(number): ")
|
||||||
if opt in options:
|
if opt in options:
|
||||||
opt = options.index(opt)
|
opt = options.index(opt)
|
||||||
break
|
break
|
||||||
@@ -57,22 +58,22 @@ def choose_prompt(options):
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
safe_print(color('red', u"Invalid option: '{}'".format(opt)))
|
safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
|
||||||
return options[opt - 1][1]
|
return options[opt - 1][1]
|
||||||
|
|
||||||
|
|
||||||
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
||||||
options = []
|
options = []
|
||||||
for res, desc in get_serial_ports():
|
for port in get_serial_ports():
|
||||||
options.append((u"{} ({})".format(res, desc), res))
|
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):
|
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))
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||||
if default == 'OTA':
|
if default == "OTA":
|
||||||
return CORE.address
|
return CORE.address
|
||||||
if show_mqtt and 'mqtt' in CORE.config:
|
if show_mqtt and "mqtt" in CORE.config:
|
||||||
options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
|
||||||
if default == 'OTA':
|
if default == "OTA":
|
||||||
return 'MQTT'
|
return "MQTT"
|
||||||
if default is not None:
|
if default is not None:
|
||||||
return default
|
return default
|
||||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||||
@@ -81,11 +82,11 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
|
|||||||
|
|
||||||
|
|
||||||
def get_port_type(port):
|
def get_port_type(port):
|
||||||
if port.startswith('/') or port.startswith('COM'):
|
if port.startswith("/") or port.startswith("COM"):
|
||||||
return 'SERIAL'
|
return "SERIAL"
|
||||||
if port == 'MQTT':
|
if port == "MQTT":
|
||||||
return 'MQTT'
|
return "MQTT"
|
||||||
return 'NETWORK'
|
return "NETWORK"
|
||||||
|
|
||||||
|
|
||||||
def run_miniterm(config, port):
|
def run_miniterm(config, port):
|
||||||
@@ -95,7 +96,7 @@ def run_miniterm(config, port):
|
|||||||
if CONF_LOGGER not in config:
|
if CONF_LOGGER not in config:
|
||||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||||
return
|
return
|
||||||
baud_rate = config['logger'][CONF_BAUD_RATE]
|
baud_rate = config["logger"][CONF_BAUD_RATE]
|
||||||
if baud_rate == 0:
|
if baud_rate == 0:
|
||||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
_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)
|
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||||
@@ -108,17 +109,18 @@ def run_miniterm(config, port):
|
|||||||
except serial.SerialException:
|
except serial.SerialException:
|
||||||
_LOGGER.error("Serial port closed!")
|
_LOGGER.error("Serial port closed!")
|
||||||
return
|
return
|
||||||
if IS_PY2:
|
line = (
|
||||||
line = raw.replace('\r', '').replace('\n', '')
|
raw.replace(b"\r", b"")
|
||||||
else:
|
.replace(b"\n", b"")
|
||||||
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8',
|
.decode("utf8", "backslashreplace")
|
||||||
'backslashreplace')
|
)
|
||||||
time = datetime.now().time().strftime('[%H:%M:%S]')
|
time = datetime.now().time().strftime("[%H:%M:%S]")
|
||||||
message = time + line
|
message = time + line
|
||||||
safe_print(message)
|
safe_print(message)
|
||||||
|
|
||||||
backtrace_state = platformio_api.process_stacktrace(
|
backtrace_state = platformio_api.process_stacktrace(
|
||||||
config, line, backtrace_state=backtrace_state)
|
config, line, backtrace_state=backtrace_state
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def wrap_to_code(name, comp):
|
def wrap_to_code(name, comp):
|
||||||
@@ -127,12 +129,10 @@ def wrap_to_code(name, comp):
|
|||||||
@functools.wraps(comp.to_code)
|
@functools.wraps(comp.to_code)
|
||||||
@coroutine_with_priority(coro.priority)
|
@coroutine_with_priority(coro.priority)
|
||||||
def wrapped(conf):
|
def wrapped(conf):
|
||||||
cg.add(cg.LineComment(u"{}:".format(name)))
|
cg.add(cg.LineComment(f"{name}:"))
|
||||||
if comp.config_schema is not None:
|
if comp.config_schema is not None:
|
||||||
conf_str = yaml_util.dump(conf)
|
conf_str = yaml_util.dump(conf)
|
||||||
if IS_PY2:
|
conf_str = conf_str.replace("//", "")
|
||||||
conf_str = conf_str.decode('utf-8')
|
|
||||||
conf_str = conf_str.replace('//', '')
|
|
||||||
cg.add(cg.LineComment(indent(conf_str)))
|
cg.add(cg.LineComment(indent(conf_str)))
|
||||||
yield coro(conf)
|
yield coro(conf)
|
||||||
|
|
||||||
@@ -140,6 +140,11 @@ def wrap_to_code(name, comp):
|
|||||||
|
|
||||||
|
|
||||||
def write_cpp(config):
|
def write_cpp(config):
|
||||||
|
generate_cpp_contents(config)
|
||||||
|
return write_cpp_file()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cpp_contents(config):
|
||||||
_LOGGER.info("Generating C++ source...")
|
_LOGGER.info("Generating C++ source...")
|
||||||
|
|
||||||
for name, component, conf in iter_components(CORE.config):
|
for name, component, conf in iter_components(CORE.config):
|
||||||
@@ -149,6 +154,8 @@ def write_cpp(config):
|
|||||||
|
|
||||||
CORE.flush_tasks()
|
CORE.flush_tasks()
|
||||||
|
|
||||||
|
|
||||||
|
def write_cpp_file():
|
||||||
writer.write_platformio_project()
|
writer.write_platformio_project()
|
||||||
|
|
||||||
code_s = indent(CORE.cpp_main_section)
|
code_s = indent(CORE.cpp_main_section)
|
||||||
@@ -165,15 +172,31 @@ def compile_program(args, config):
|
|||||||
|
|
||||||
def upload_using_esptool(config, port):
|
def upload_using_esptool(config, port):
|
||||||
path = CORE.firmware_bin
|
path = CORE.firmware_bin
|
||||||
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800)
|
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||||
|
"upload_speed", 460800
|
||||||
|
)
|
||||||
|
|
||||||
def run_esptool(baud_rate):
|
def run_esptool(baud_rate):
|
||||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
cmd = [
|
||||||
'--baud', str(baud_rate),
|
"esptool.py",
|
||||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
"--before",
|
||||||
|
"default_reset",
|
||||||
|
"--after",
|
||||||
|
"hard_reset",
|
||||||
|
"--baud",
|
||||||
|
str(baud_rate),
|
||||||
|
"--chip",
|
||||||
|
"esp8266",
|
||||||
|
"--port",
|
||||||
|
port,
|
||||||
|
"write_flash",
|
||||||
|
"0x0",
|
||||||
|
path,
|
||||||
|
]
|
||||||
|
|
||||||
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
|
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||||
import esptool
|
import esptool
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
return run_external_command(esptool._main, *cmd)
|
return run_external_command(esptool._main, *cmd)
|
||||||
|
|
||||||
@@ -183,14 +206,16 @@ def upload_using_esptool(config, port):
|
|||||||
if rc == 0 or first_baudrate == 115200:
|
if rc == 0 or first_baudrate == 115200:
|
||||||
return rc
|
return rc
|
||||||
# Try with 115200 baud rate, with some serial chips the faster baud rates do not work well
|
# 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.",
|
_LOGGER.info(
|
||||||
first_baudrate)
|
"Upload with baud rate %s failed. Trying again with baud rate 115200.",
|
||||||
|
first_baudrate,
|
||||||
|
)
|
||||||
return run_esptool(115200)
|
return run_esptool(115200)
|
||||||
|
|
||||||
|
|
||||||
def upload_program(config, args, host):
|
def upload_program(config, args, host):
|
||||||
# if upload is to a serial port use platformio, otherwise assume ota
|
# 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
|
from esphome import platformio_api
|
||||||
|
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
@@ -199,6 +224,12 @@ def upload_program(config, args, host):
|
|||||||
|
|
||||||
from esphome import espota2
|
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]
|
ota_conf = config[CONF_OTA]
|
||||||
remote_port = ota_conf[CONF_PORT]
|
remote_port = ota_conf[CONF_PORT]
|
||||||
password = ota_conf[CONF_PASSWORD]
|
password = ota_conf[CONF_PASSWORD]
|
||||||
@@ -206,19 +237,21 @@ def upload_program(config, args, host):
|
|||||||
|
|
||||||
|
|
||||||
def show_logs(config, args, port):
|
def show_logs(config, args, port):
|
||||||
if 'logger' not in config:
|
if "logger" not in config:
|
||||||
raise EsphomeError("Logger is not configured!")
|
raise EsphomeError("Logger is not configured!")
|
||||||
if get_port_type(port) == 'SERIAL':
|
if get_port_type(port) == "SERIAL":
|
||||||
run_miniterm(config, port)
|
run_miniterm(config, port)
|
||||||
return 0
|
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
|
from esphome.api.client import run_logs
|
||||||
|
|
||||||
return run_logs(config, port)
|
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
|
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)")
|
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||||
|
|
||||||
@@ -226,40 +259,9 @@ def show_logs(config, args, port):
|
|||||||
def clean_mqtt(config, args):
|
def clean_mqtt(config, args):
|
||||||
from esphome import mqtt
|
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'
|
|
||||||
|
|
||||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
try:
|
|
||||||
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',
|
|
||||||
}
|
|
||||||
))
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def command_wizard(args):
|
def command_wizard(args):
|
||||||
@@ -279,6 +281,8 @@ def command_config(args, config):
|
|||||||
def command_vscode(args):
|
def command_vscode(args):
|
||||||
from esphome import vscode
|
from esphome import vscode
|
||||||
|
|
||||||
|
logging.disable(logging.INFO)
|
||||||
|
logging.disable(logging.WARNING)
|
||||||
CORE.config_path = args.configuration[0]
|
CORE.config_path = args.configuration[0]
|
||||||
vscode.read_config(args)
|
vscode.read_config(args)
|
||||||
|
|
||||||
@@ -288,28 +292,38 @@ def command_compile(args, config):
|
|||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
if args.only_generate:
|
if args.only_generate:
|
||||||
_LOGGER.info(u"Successfully generated source code.")
|
_LOGGER.info("Successfully generated source code.")
|
||||||
return 0
|
return 0
|
||||||
exit_code = compile_program(args, config)
|
exit_code = compile_program(args, config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info(u"Successfully compiled program.")
|
_LOGGER.info("Successfully compiled program.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_upload(args, config):
|
def command_upload(args, config):
|
||||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
port = choose_upload_log_host(
|
||||||
show_ota=True, show_mqtt=False, show_api=False)
|
default=args.upload_port,
|
||||||
|
check_default=None,
|
||||||
|
show_ota=True,
|
||||||
|
show_mqtt=False,
|
||||||
|
show_api=False,
|
||||||
|
)
|
||||||
exit_code = upload_program(config, args, port)
|
exit_code = upload_program(config, args, port)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info(u"Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_logs(args, config):
|
def command_logs(args, config):
|
||||||
port = choose_upload_log_host(default=args.serial_port, check_default=None,
|
port = choose_upload_log_host(
|
||||||
show_ota=False, show_mqtt=True, show_api=True)
|
default=args.serial_port,
|
||||||
|
check_default=None,
|
||||||
|
show_ota=False,
|
||||||
|
show_mqtt=True,
|
||||||
|
show_api=True,
|
||||||
|
)
|
||||||
return show_logs(config, args, port)
|
return show_logs(config, args, port)
|
||||||
|
|
||||||
|
|
||||||
@@ -320,17 +334,27 @@ def command_run(args, config):
|
|||||||
exit_code = compile_program(args, config)
|
exit_code = compile_program(args, config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info(u"Successfully compiled program.")
|
_LOGGER.info("Successfully compiled program.")
|
||||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
port = choose_upload_log_host(
|
||||||
show_ota=True, show_mqtt=False, show_api=True)
|
default=args.upload_port,
|
||||||
|
check_default=None,
|
||||||
|
show_ota=True,
|
||||||
|
show_mqtt=False,
|
||||||
|
show_api=True,
|
||||||
|
)
|
||||||
exit_code = upload_program(config, args, port)
|
exit_code = upload_program(config, args, port)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info(u"Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
if args.no_logs:
|
if args.no_logs:
|
||||||
return 0
|
return 0
|
||||||
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
port = choose_upload_log_host(
|
||||||
show_ota=False, show_mqtt=True, show_api=True)
|
default=args.upload_port,
|
||||||
|
check_default=port,
|
||||||
|
show_ota=False,
|
||||||
|
show_mqtt=True,
|
||||||
|
show_api=True,
|
||||||
|
)
|
||||||
return show_logs(config, args, port)
|
return show_logs(config, args, port)
|
||||||
|
|
||||||
|
|
||||||
@@ -345,7 +369,7 @@ def command_mqtt_fingerprint(args, config):
|
|||||||
|
|
||||||
|
|
||||||
def command_version(args):
|
def command_version(args):
|
||||||
safe_print(u"Version: {}".format(const.__version__))
|
safe_print(f"Version: {const.__version__}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -373,141 +397,195 @@ def command_update_all(args):
|
|||||||
twidth = 60
|
twidth = 60
|
||||||
|
|
||||||
def print_bar(middle_text):
|
def print_bar(middle_text):
|
||||||
middle_text = " {} ".format(middle_text)
|
middle_text = f" {middle_text} "
|
||||||
width = len(click.unstyle(middle_text))
|
width = len(click.unstyle(middle_text))
|
||||||
half_line = "=" * ((twidth - width) // 2)
|
half_line = "=" * ((twidth - width) // 2)
|
||||||
click.echo("%s%s%s" % (half_line, middle_text, half_line))
|
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
print("Updating {}".format(color('cyan', f)))
|
print("Updating {}".format(color(Fore.CYAN, f)))
|
||||||
print('-' * twidth)
|
print("-" * twidth)
|
||||||
print()
|
print()
|
||||||
rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port',
|
rc = run_external_process(
|
||||||
'OTA')
|
"esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA"
|
||||||
|
)
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
|
print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
|
||||||
success[f] = True
|
success[f] = True
|
||||||
else:
|
else:
|
||||||
print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
|
print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
|
||||||
success[f] = False
|
success[f] = False
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
|
print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
|
||||||
failed = 0
|
failed = 0
|
||||||
for f in files:
|
for f in files:
|
||||||
if success[f]:
|
if success[f]:
|
||||||
print(" - {}: {}".format(f, color('green', 'SUCCESS')))
|
print(" - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
|
||||||
else:
|
else:
|
||||||
print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
|
print(" - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
|
||||||
failed += 1
|
failed += 1
|
||||||
return failed
|
return failed
|
||||||
|
|
||||||
|
|
||||||
PRE_CONFIG_ACTIONS = {
|
PRE_CONFIG_ACTIONS = {
|
||||||
'wizard': command_wizard,
|
"wizard": command_wizard,
|
||||||
'version': command_version,
|
"version": command_version,
|
||||||
'dashboard': command_dashboard,
|
"dashboard": command_dashboard,
|
||||||
'vscode': command_vscode,
|
"vscode": command_vscode,
|
||||||
'update-all': command_update_all,
|
"update-all": command_update_all,
|
||||||
}
|
}
|
||||||
|
|
||||||
POST_CONFIG_ACTIONS = {
|
POST_CONFIG_ACTIONS = {
|
||||||
'config': command_config,
|
"config": command_config,
|
||||||
'compile': command_compile,
|
"compile": command_compile,
|
||||||
'upload': command_upload,
|
"upload": command_upload,
|
||||||
'logs': command_logs,
|
"logs": command_logs,
|
||||||
'run': command_run,
|
"run": command_run,
|
||||||
'clean-mqtt': command_clean_mqtt,
|
"clean-mqtt": command_clean_mqtt,
|
||||||
'mqtt-fingerprint': command_mqtt_fingerprint,
|
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||||
'clean': command_clean,
|
"clean": command_clean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv):
|
def parse_args(argv):
|
||||||
parser = argparse.ArgumentParser(description='ESPHome v{}'.format(const.__version__))
|
parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}")
|
||||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
parser.add_argument(
|
||||||
action='store_true')
|
"-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(
|
||||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
"-q", "--quiet", help="Disable all esphome logs.", action="store_true"
|
||||||
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
)
|
||||||
|
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.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',
|
parser_compile = subparsers.add_parser(
|
||||||
help='Read the configuration and compile a program.')
|
"compile", help="Read the configuration and compile a program."
|
||||||
parser_compile.add_argument('--only-generate',
|
)
|
||||||
help="Only generate source code, do not compile.",
|
parser_compile.add_argument(
|
||||||
action='store_true')
|
"--only-generate",
|
||||||
|
help="Only generate source code, do not compile.",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
|
parser_upload = subparsers.add_parser(
|
||||||
'and upload the latest binary.')
|
"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.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 '
|
parser_logs = subparsers.add_parser(
|
||||||
'and show all MQTT logs.')
|
"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("--topic", help="Manually set the topic to subscribe to.")
|
||||||
parser_logs.add_argument('--password', help='Manually set the password.')
|
parser_logs.add_argument("--username", help="Manually set the username.")
|
||||||
parser_logs.add_argument('--client-id', help='Manually set the client id.')
|
parser_logs.add_argument("--password", help="Manually set the password.")
|
||||||
parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
|
parser_logs.add_argument("--client-id", help="Manually set the client id.")
|
||||||
"For example /dev/cu.SLAB_USBtoUART.")
|
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, '
|
parser_run = subparsers.add_parser(
|
||||||
'upload it, and start MQTT logs.')
|
"run",
|
||||||
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
help="Validate the configuration, create a binary, "
|
||||||
"For example /dev/cu.SLAB_USBtoUART.")
|
"upload it, and start MQTT logs.",
|
||||||
parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
|
)
|
||||||
action='store_true')
|
parser_run.add_argument(
|
||||||
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
"--upload-port",
|
||||||
parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
|
help="Manually specify the upload port/ip to use. "
|
||||||
parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
|
"For example /dev/cu.SLAB_USBtoUART.",
|
||||||
parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
|
)
|
||||||
|
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 "
|
parser_clean = subparsers.add_parser(
|
||||||
"retain messages.")
|
"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("--topic", help="Manually set the topic to subscribe to.")
|
||||||
parser_clean.add_argument('--password', help='Manually set the password.')
|
parser_clean.add_argument("--username", help="Manually set the username.")
|
||||||
parser_clean.add_argument('--client-id', help='Manually set the client id.')
|
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 "
|
subparsers.add_parser(
|
||||||
"you through setting up esphome.")
|
"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',
|
dashboard = subparsers.add_parser(
|
||||||
help="Create a simple web server for a dashboard.")
|
"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(
|
||||||
dashboard.add_argument("--username", help="The optional username to require "
|
"--port",
|
||||||
"for authentication.",
|
help="The HTTP port to open connections on. Defaults to 6052.",
|
||||||
type=str, default='')
|
type=int,
|
||||||
dashboard.add_argument("--password", help="The optional password to require "
|
default=6052,
|
||||||
"for authentication.",
|
)
|
||||||
type=str, default='')
|
dashboard.add_argument(
|
||||||
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
"--username",
|
||||||
action='store_true')
|
help="The optional username to require " "for authentication.",
|
||||||
dashboard.add_argument("--hassio",
|
type=str,
|
||||||
help=argparse.SUPPRESS,
|
default="",
|
||||||
action="store_true")
|
)
|
||||||
dashboard.add_argument("--socket",
|
dashboard.add_argument(
|
||||||
help="Make the dashboard serve under a unix socket", type=str)
|
"--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 = subparsers.add_parser("vscode", help=argparse.SUPPRESS)
|
||||||
vscode.add_argument('--ace', action='store_true')
|
vscode.add_argument("--ace", action="store_true")
|
||||||
|
|
||||||
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
subparsers.add_parser("update-all", help=argparse.SUPPRESS)
|
||||||
|
|
||||||
return parser.parse_args(argv[1:])
|
return parser.parse_args(argv[1:])
|
||||||
|
|
||||||
@@ -517,18 +595,16 @@ def run_esphome(argv):
|
|||||||
CORE.dashboard = args.dashboard
|
CORE.dashboard = args.dashboard
|
||||||
|
|
||||||
setup_log(args.verbose, args.quiet)
|
setup_log(args.verbose, args.quiet)
|
||||||
if args.command != 'version' and not args.configuration:
|
if args.command != "version" and not args.configuration:
|
||||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if IS_PY2:
|
if sys.version_info < (3, 7, 0):
|
||||||
_LOGGER.warning("You're using ESPHome with python 2. Support for python 2 is deprecated "
|
_LOGGER.error(
|
||||||
"and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 "
|
"You're running ESPHome with Python <3.7. ESPHome is no longer compatible "
|
||||||
"or higher.")
|
"with this Python version. Please reinstall ESPHome with Python 3.7+"
|
||||||
elif IS_PY3 and sys.version_info < (3, 6, 0):
|
)
|
||||||
_LOGGER.warning("You're using ESPHome with python 3.5. Support for python 3.5 is "
|
return 1
|
||||||
"deprecated and will be removed in 1.15.0. Please reinstall ESPHome with "
|
|
||||||
"python 3.6 or higher.")
|
|
||||||
|
|
||||||
if args.command in PRE_CONFIG_ACTIONS:
|
if args.command in PRE_CONFIG_ACTIONS:
|
||||||
try:
|
try:
|
||||||
@@ -541,13 +617,13 @@ def run_esphome(argv):
|
|||||||
CORE.config_path = conf_path
|
CORE.config_path = conf_path
|
||||||
CORE.dashboard = args.dashboard
|
CORE.dashboard = args.dashboard
|
||||||
|
|
||||||
config = read_config()
|
config = read_config(dict(args.substitution) if args.substitution else {})
|
||||||
if config is None:
|
if config is None:
|
||||||
return 1
|
return 1
|
||||||
CORE.config = config
|
CORE.config = config
|
||||||
|
|
||||||
if args.command not in POST_CONFIG_ACTIONS:
|
if args.command not in POST_CONFIG_ACTIONS:
|
||||||
safe_print(u"Unknown command {}".format(args.command))
|
safe_print(f"Unknown command {args.command}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -13,8 +13,8 @@ from esphome import const
|
|||||||
import esphome.api.api_pb2 as pb
|
import esphome.api.api_pb2 as pb
|
||||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||||
from esphome.core import EsphomeError
|
from esphome.core import EsphomeError
|
||||||
from esphome.helpers import resolve_ip_address, indent, color
|
from esphome.helpers import resolve_ip_address, indent
|
||||||
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte
|
from esphome.log import color, Fore
|
||||||
from esphome.util import safe_print
|
from esphome.util import safe_print
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -67,16 +67,16 @@ MESSAGE_TYPE_TO_PROTO = {
|
|||||||
|
|
||||||
def _varuint_to_bytes(value):
|
def _varuint_to_bytes(value):
|
||||||
if value <= 0x7F:
|
if value <= 0x7F:
|
||||||
return byte_to_bytes(value)
|
return bytes([value])
|
||||||
|
|
||||||
ret = bytes()
|
ret = bytes()
|
||||||
while value:
|
while value:
|
||||||
temp = value & 0x7F
|
temp = value & 0x7F
|
||||||
value >>= 7
|
value >>= 7
|
||||||
if value:
|
if value:
|
||||||
ret += byte_to_bytes(temp | 0x80)
|
ret += bytes([temp | 0x80])
|
||||||
else:
|
else:
|
||||||
ret += byte_to_bytes(temp)
|
ret += bytes([temp])
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -84,8 +84,7 @@ def _varuint_to_bytes(value):
|
|||||||
def _bytes_to_varuint(value):
|
def _bytes_to_varuint(value):
|
||||||
result = 0
|
result = 0
|
||||||
bitpos = 0
|
bitpos = 0
|
||||||
for c in value:
|
for val in value:
|
||||||
val = char_to_byte(c)
|
|
||||||
result |= (val & 0x7F) << bitpos
|
result |= (val & 0x7F) << bitpos
|
||||||
bitpos += 7
|
bitpos += 7
|
||||||
if (val & 0x80) == 0:
|
if (val & 0x80) == 0:
|
||||||
@@ -179,11 +178,15 @@ class APIClient(threading.Thread):
|
|||||||
try:
|
try:
|
||||||
ip = resolve_ip_address(self._address)
|
ip = resolve_ip_address(self._address)
|
||||||
except EsphomeError as err:
|
except EsphomeError as err:
|
||||||
_LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?",
|
_LOGGER.warning(
|
||||||
self._address)
|
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||||
_LOGGER.warning("(If this error persists, please set a static IP address: "
|
self._address,
|
||||||
"https://esphome.io/components/wifi.html#manual-ips)")
|
)
|
||||||
raise APIConnectionError(err)
|
_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)
|
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
||||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
@@ -191,8 +194,8 @@ class APIClient(threading.Thread):
|
|||||||
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
try:
|
try:
|
||||||
self._socket.connect((ip, self._port))
|
self._socket.connect((ip, self._port))
|
||||||
except socket.error as err:
|
except OSError as err:
|
||||||
err = APIConnectionError("Error connecting to {}: {}".format(ip, err))
|
err = APIConnectionError(f"Error connecting to {ip}: {err}")
|
||||||
self._fatal_error(err)
|
self._fatal_error(err)
|
||||||
raise err
|
raise err
|
||||||
self._socket.settimeout(0.1)
|
self._socket.settimeout(0.1)
|
||||||
@@ -200,14 +203,19 @@ class APIClient(threading.Thread):
|
|||||||
self._socket_open_event.set()
|
self._socket_open_event.set()
|
||||||
|
|
||||||
hello = pb.HelloRequest()
|
hello = pb.HelloRequest()
|
||||||
hello.client_info = 'ESPHome v{}'.format(const.__version__)
|
hello.client_info = f"ESPHome v{const.__version__}"
|
||||||
try:
|
try:
|
||||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||||
except APIConnectionError as err:
|
except APIConnectionError as err:
|
||||||
self._fatal_error(err)
|
self._fatal_error(err)
|
||||||
raise err
|
raise err
|
||||||
_LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
|
_LOGGER.debug(
|
||||||
resp.server_info, resp.api_version_major, resp.api_version_minor)
|
"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._connected = True
|
||||||
self._refresh_ping()
|
self._refresh_ping()
|
||||||
if self.on_connect is not None:
|
if self.on_connect is not None:
|
||||||
@@ -251,8 +259,8 @@ class APIClient(threading.Thread):
|
|||||||
with self._socket_write_lock:
|
with self._socket_write_lock:
|
||||||
try:
|
try:
|
||||||
self._socket.sendall(data)
|
self._socket.sendall(data)
|
||||||
except socket.error as err:
|
except OSError as err:
|
||||||
err = APIConnectionError("Error while writing data: {}".format(err))
|
err = APIConnectionError(f"Error while writing data: {err}")
|
||||||
self._fatal_error(err)
|
self._fatal_error(err)
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
@@ -265,17 +273,16 @@ class APIClient(threading.Thread):
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
encoded = msg.SerializeToString()
|
encoded = msg.SerializeToString()
|
||||||
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg)))
|
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
|
||||||
if IS_PY2:
|
req = bytes([0])
|
||||||
req = chr(0x00)
|
|
||||||
else:
|
|
||||||
req = bytes([0])
|
|
||||||
req += _varuint_to_bytes(len(encoded))
|
req += _varuint_to_bytes(len(encoded))
|
||||||
req += _varuint_to_bytes(message_type)
|
req += _varuint_to_bytes(message_type)
|
||||||
req += encoded
|
req += encoded
|
||||||
self._write(req)
|
self._write(req)
|
||||||
|
|
||||||
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5):
|
def _send_message_await_response_complex(
|
||||||
|
self, send_msg, do_append, do_stop, timeout=5
|
||||||
|
):
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
responses = []
|
responses = []
|
||||||
|
|
||||||
@@ -300,12 +307,15 @@ class APIClient(threading.Thread):
|
|||||||
def is_response(msg):
|
def is_response(msg):
|
||||||
return isinstance(msg, response_type)
|
return isinstance(msg, response_type)
|
||||||
|
|
||||||
return self._send_message_await_response_complex(send_msg, is_response, is_response,
|
return self._send_message_await_response_complex(
|
||||||
timeout)[0]
|
send_msg, is_response, is_response, timeout
|
||||||
|
)[0]
|
||||||
|
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
self._check_connected()
|
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):
|
def ping(self):
|
||||||
self._check_connected()
|
self._check_connected()
|
||||||
@@ -315,7 +325,9 @@ class APIClient(threading.Thread):
|
|||||||
self._check_connected()
|
self._check_connected()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
|
self._send_message_await_response(
|
||||||
|
pb.DisconnectRequest(), pb.DisconnectResponse
|
||||||
|
)
|
||||||
except APIConnectionError:
|
except APIConnectionError:
|
||||||
pass
|
pass
|
||||||
self._close_socket()
|
self._close_socket()
|
||||||
@@ -351,18 +363,18 @@ class APIClient(threading.Thread):
|
|||||||
raise APIConnectionError("No socket!")
|
raise APIConnectionError("No socket!")
|
||||||
try:
|
try:
|
||||||
val = self._socket.recv(amount - len(ret))
|
val = self._socket.recv(amount - len(ret))
|
||||||
except AttributeError:
|
except AttributeError as err:
|
||||||
raise APIConnectionError("Socket was closed")
|
raise APIConnectionError("Socket was closed") from err
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
continue
|
continue
|
||||||
except socket.error as err:
|
except OSError as err:
|
||||||
raise APIConnectionError("Error while receiving data: {}".format(err))
|
raise APIConnectionError(f"Error while receiving data: {err}") from err
|
||||||
ret += val
|
ret += val
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _recv_varint(self):
|
def _recv_varint(self):
|
||||||
raw = bytes()
|
raw = bytes()
|
||||||
while not raw or char_to_byte(raw[-1]) & 0x80:
|
while not raw or raw[-1] & 0x80:
|
||||||
raw += self._recv(1)
|
raw += self._recv(1)
|
||||||
return _bytes_to_varuint(raw)
|
return _bytes_to_varuint(raw)
|
||||||
|
|
||||||
@@ -371,7 +383,7 @@ class APIClient(threading.Thread):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Preamble
|
# Preamble
|
||||||
if char_to_byte(self._recv(1)[0]) != 0x00:
|
if self._recv(1)[0] != 0x00:
|
||||||
raise APIConnectionError("Invalid preamble")
|
raise APIConnectionError("Invalid preamble")
|
||||||
|
|
||||||
length = self._recv_varint()
|
length = self._recv_varint()
|
||||||
@@ -420,7 +432,7 @@ class APIClient(threading.Thread):
|
|||||||
|
|
||||||
|
|
||||||
def run_logs(config, address):
|
def run_logs(config, address):
|
||||||
conf = config['api']
|
conf = config["api"]
|
||||||
port = conf[CONF_PORT]
|
port = conf[CONF_PORT]
|
||||||
password = conf[CONF_PASSWORD]
|
password = conf[CONF_PASSWORD]
|
||||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||||
@@ -436,7 +448,7 @@ def run_logs(config, address):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if err:
|
if err:
|
||||||
_LOGGER.warning(u"Disconnected from API: %s", err)
|
_LOGGER.warning("Disconnected from API: %s", err)
|
||||||
|
|
||||||
while retry_timer:
|
while retry_timer:
|
||||||
retry_timer.pop(0).cancel()
|
retry_timer.pop(0).cancel()
|
||||||
@@ -452,24 +464,35 @@ def run_logs(config, address):
|
|||||||
_LOGGER.info("Successfully connected to %s", address)
|
_LOGGER.info("Successfully connected to %s", address)
|
||||||
return
|
return
|
||||||
|
|
||||||
wait_time = int(min(1.5**min(tries, 100), 30))
|
wait_time = int(min(1.5 ** min(tries, 100), 30))
|
||||||
if not has_connects:
|
if not has_connects:
|
||||||
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected "
|
_LOGGER.warning(
|
||||||
u"to WiFi yet (%s). Re-Trying in %s seconds",
|
"Initial connection failed. The ESP might not be connected "
|
||||||
error, wait_time)
|
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||||
|
error,
|
||||||
|
wait_time,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
_LOGGER.warning(
|
||||||
error, wait_time)
|
"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||||
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
|
error,
|
||||||
|
wait_time,
|
||||||
|
)
|
||||||
|
timer = threading.Timer(
|
||||||
|
wait_time, functools.partial(try_connect, None, tries + 1)
|
||||||
|
)
|
||||||
timer.start()
|
timer.start()
|
||||||
retry_timer.append(timer)
|
retry_timer.append(timer)
|
||||||
|
|
||||||
def on_log(msg):
|
def on_log(msg):
|
||||||
time_ = datetime.now().time().strftime(u'[%H:%M:%S]')
|
time_ = datetime.now().time().strftime("[%H:%M:%S]")
|
||||||
text = msg.message
|
text = msg.message
|
||||||
if msg.send_failed:
|
if msg.send_failed:
|
||||||
text = color('white', '(Message skipped because it was too big to fit in '
|
text = color(
|
||||||
'TCP buffer - This is only cosmetic)')
|
Fore.WHITE,
|
||||||
|
"(Message skipped because it was too big to fit in "
|
||||||
|
"TCP buffer - This is only cosmetic)",
|
||||||
|
)
|
||||||
safe_print(time_ + text)
|
safe_print(time_ + text)
|
||||||
|
|
||||||
def on_login():
|
def on_login():
|
||||||
|
|||||||
@@ -1,19 +1,37 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
|
from esphome.const import (
|
||||||
CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME
|
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.core import coroutine
|
||||||
|
from esphome.jsonschema import jschema_extractor
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
|
|
||||||
|
|
||||||
def maybe_simple_id(*validators):
|
def maybe_simple_id(*validators):
|
||||||
|
return maybe_conf(CONF_ID, *validators)
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_conf(conf, *validators):
|
||||||
validator = cv.All(*validators)
|
validator = cv.All(*validators)
|
||||||
|
|
||||||
|
@jschema_extractor("maybe")
|
||||||
def validate(value):
|
def validate(value):
|
||||||
|
# pylint: disable=comparison-with-callable
|
||||||
|
if value == jschema_extractor:
|
||||||
|
return validator
|
||||||
|
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return validator(value)
|
return validator(value)
|
||||||
with cv.remove_prepend_path([CONF_ID]):
|
with cv.remove_prepend_path([conf]):
|
||||||
return validator({CONF_ID: value})
|
return validator({conf: value})
|
||||||
|
|
||||||
return validate
|
return validate
|
||||||
|
|
||||||
@@ -26,36 +44,34 @@ def register_condition(name, condition_type, schema):
|
|||||||
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
||||||
|
|
||||||
|
|
||||||
Action = cg.esphome_ns.class_('Action')
|
Action = cg.esphome_ns.class_("Action")
|
||||||
Trigger = cg.esphome_ns.class_('Trigger')
|
Trigger = cg.esphome_ns.class_("Trigger")
|
||||||
ACTION_REGISTRY = Registry()
|
ACTION_REGISTRY = Registry()
|
||||||
Condition = cg.esphome_ns.class_('Condition')
|
Condition = cg.esphome_ns.class_("Condition")
|
||||||
CONDITION_REGISTRY = Registry()
|
CONDITION_REGISTRY = Registry()
|
||||||
validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY)
|
validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY)
|
||||||
validate_action_list = cv.validate_registry('action', ACTION_REGISTRY)
|
validate_action_list = cv.validate_registry("action", ACTION_REGISTRY)
|
||||||
validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY)
|
validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY)
|
||||||
validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY)
|
validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY)
|
||||||
|
|
||||||
|
|
||||||
def validate_potentially_and_condition(value):
|
def validate_potentially_and_condition(value):
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
with cv.remove_prepend_path(['and']):
|
with cv.remove_prepend_path(["and"]):
|
||||||
return validate_condition({
|
return validate_condition({"and": value})
|
||||||
'and': value
|
|
||||||
})
|
|
||||||
return validate_condition(value)
|
return validate_condition(value)
|
||||||
|
|
||||||
|
|
||||||
DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component)
|
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||||
LambdaAction = cg.esphome_ns.class_('LambdaAction', Action)
|
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||||
IfAction = cg.esphome_ns.class_('IfAction', Action)
|
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||||
WhileAction = cg.esphome_ns.class_('WhileAction', Action)
|
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||||
WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component)
|
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
|
||||||
UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
|
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||||
Automation = cg.esphome_ns.class_('Automation')
|
Automation = cg.esphome_ns.class_("Automation")
|
||||||
|
|
||||||
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
|
||||||
ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component)
|
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
|
||||||
|
|
||||||
|
|
||||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
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:
|
try:
|
||||||
return cv.Schema([schema])(value)
|
return cv.Schema([schema])(value)
|
||||||
except cv.Invalid as err2:
|
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
|
raise err
|
||||||
if u'Unable to find action' in str(err):
|
if "Unable to find action" in str(err):
|
||||||
raise err2
|
raise err2
|
||||||
raise cv.MultipleInvalid([err, err2])
|
raise cv.MultipleInvalid([err, err2])
|
||||||
elif isinstance(value, dict):
|
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.
|
# This should only happen with invalid configs, but let's have a nice error message.
|
||||||
return [schema(value)]
|
return [schema(value)]
|
||||||
|
|
||||||
|
@jschema_extractor("automation")
|
||||||
def validator(value):
|
def validator(value):
|
||||||
|
# hack to get the schema
|
||||||
|
# pylint: disable=comparison-with-callable
|
||||||
|
if value == jschema_extractor:
|
||||||
|
return schema
|
||||||
|
|
||||||
value = validator_(value)
|
value = validator_(value)
|
||||||
if extra_validators is not None:
|
if extra_validators is not None:
|
||||||
value = cv.Schema([extra_validators])(value)
|
value = cv.Schema([extra_validators])(value)
|
||||||
@@ -105,47 +128,59 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
|||||||
return validator
|
return validator
|
||||||
|
|
||||||
|
|
||||||
AUTOMATION_SCHEMA = cv.Schema({
|
AUTOMATION_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
{
|
||||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
||||||
cv.Required(CONF_THEN): validate_action_list,
|
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
||||||
})
|
cv.Required(CONF_THEN): validate_action_list,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
AndCondition = cg.esphome_ns.class_('AndCondition', Condition)
|
AndCondition = cg.esphome_ns.class_("AndCondition", Condition)
|
||||||
OrCondition = cg.esphome_ns.class_('OrCondition', Condition)
|
OrCondition = cg.esphome_ns.class_("OrCondition", Condition)
|
||||||
NotCondition = cg.esphome_ns.class_('NotCondition', 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):
|
def and_condition_to_code(config, condition_id, template_arg, args):
|
||||||
conditions = yield build_condition_list(config, template_arg, args)
|
conditions = yield build_condition_list(config, template_arg, args)
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
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):
|
def or_condition_to_code(config, condition_id, template_arg, args):
|
||||||
conditions = yield build_condition_list(config, template_arg, args)
|
conditions = yield build_condition_list(config, template_arg, args)
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
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):
|
def not_condition_to_code(config, condition_id, template_arg, args):
|
||||||
condition = yield build_condition(config, template_arg, args)
|
condition = yield build_condition(config, template_arg, args)
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, condition)
|
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):
|
def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||||
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
|
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
|
||||||
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||||
|
|
||||||
|
|
||||||
@register_condition('for', ForCondition, cv.Schema({
|
@register_condition(
|
||||||
cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds),
|
"for",
|
||||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
ForCondition,
|
||||||
}).extend(cv.COMPONENT_SCHEMA))
|
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):
|
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)
|
var = cg.new_Pvariable(condition_id, template_arg, condition)
|
||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
|
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
|
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):
|
def delay_action_to_code(config, action_id, template_arg, args):
|
||||||
var = cg.new_Pvariable(action_id, template_arg)
|
var = cg.new_Pvariable(action_id, template_arg)
|
||||||
yield cg.register_component(var, {})
|
yield cg.register_component(var, {})
|
||||||
@@ -162,11 +199,18 @@ def delay_action_to_code(config, action_id, template_arg, args):
|
|||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
@register_action('if', IfAction, cv.All({
|
@register_action(
|
||||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
"if",
|
||||||
cv.Optional(CONF_THEN): validate_action_list,
|
IfAction,
|
||||||
cv.Optional(CONF_ELSE): validate_action_list,
|
cv.All(
|
||||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE)))
|
{
|
||||||
|
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):
|
def if_action_to_code(config, action_id, template_arg, args):
|
||||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
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
|
yield var
|
||||||
|
|
||||||
|
|
||||||
@register_action('while', WhileAction, cv.Schema({
|
@register_action(
|
||||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
"while",
|
||||||
cv.Required(CONF_THEN): validate_action_list,
|
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):
|
def while_action_to_code(config, action_id, template_arg, args):
|
||||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
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):
|
def validate_wait_until(value):
|
||||||
schema = cv.Schema({
|
schema = cv.Schema(
|
||||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
{
|
||||||
})
|
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||||
|
}
|
||||||
|
)
|
||||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||||
return schema(value)
|
return schema(value)
|
||||||
return validate_wait_until({CONF_CONDITION: 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):
|
def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
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
|
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):
|
def lambda_action_to_code(config, action_id, template_arg, args):
|
||||||
lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
|
lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
|
||||||
yield cg.new_Pvariable(action_id, template_arg, lambda_)
|
yield cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||||
|
|
||||||
|
|
||||||
@register_action('component.update', UpdateComponentAction, maybe_simple_id({
|
@register_action(
|
||||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
"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):
|
def component_update_action_to_code(config, action_id, template_arg, args):
|
||||||
comp = yield cg.get_variable(config[CONF_ID])
|
comp = yield cg.get_variable(config[CONF_ID])
|
||||||
yield cg.new_Pvariable(action_id, template_arg, comp)
|
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
|
@coroutine
|
||||||
def build_action(full_config, template_arg, args):
|
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]
|
action_id = full_config[CONF_TYPE_ID]
|
||||||
builder = registry_entry.coroutine_fun
|
builder = registry_entry.coroutine_fun
|
||||||
yield builder(config, action_id, template_arg, args)
|
yield builder(config, action_id, template_arg, args)
|
||||||
@@ -241,7 +301,9 @@ def build_action_list(config, templ, arg_type):
|
|||||||
|
|
||||||
@coroutine
|
@coroutine
|
||||||
def build_condition(full_config, template_arg, args):
|
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]
|
action_id = full_config[CONF_TYPE_ID]
|
||||||
builder = registry_entry.coroutine_fun
|
builder = registry_entry.coroutine_fun
|
||||||
yield builder(config, action_id, template_arg, args)
|
yield builder(config, action_id, template_arg, args)
|
||||||
|
|||||||
@@ -9,18 +9,71 @@
|
|||||||
|
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
from esphome.cpp_generator import ( # noqa
|
from esphome.cpp_generator import ( # noqa
|
||||||
Expression, RawExpression, RawStatement, TemplateArguments,
|
Expression,
|
||||||
StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment,
|
RawExpression,
|
||||||
progmem_array, statement, variable, Pvariable, new_Pvariable,
|
RawStatement,
|
||||||
add, add_global, add_library, add_build_flag, add_define,
|
TemplateArguments,
|
||||||
get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj,
|
StructInitializer,
|
||||||
MockObjClass)
|
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
|
from esphome.cpp_helpers import ( # noqa
|
||||||
gpio_pin_expression, register_component, build_registry_entry,
|
gpio_pin_expression,
|
||||||
build_registry_list, extract_registry_entry_config, register_parented)
|
register_component,
|
||||||
|
build_registry_entry,
|
||||||
|
build_registry_list,
|
||||||
|
extract_registry_entry_config,
|
||||||
|
register_parented,
|
||||||
|
)
|
||||||
from esphome.cpp_types import ( # noqa
|
from esphome.cpp_types import ( # noqa
|
||||||
global_ns, void, nullptr, float_, double, bool_, std_ns, std_string,
|
global_ns,
|
||||||
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
|
void,
|
||||||
esphome_ns, App, Nameable, Component, ComponentPtr,
|
nullptr,
|
||||||
PollingComponent, Application, optional, arduino_json_ns, JsonObject,
|
float_,
|
||||||
JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin)
|
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) {
|
if (this->sleep_pin_ != nullptr) {
|
||||||
this->sleep_pin_->setup();
|
this->sleep_pin_->setup();
|
||||||
this->sleep_pin_->digital_write(false);
|
this->sleep_pin_->digital_write(false);
|
||||||
|
this->sleep_pin_state_ = false;
|
||||||
}
|
}
|
||||||
this->step_pin_->setup();
|
this->step_pin_->setup();
|
||||||
this->step_pin_->digital_write(false);
|
this->step_pin_->digital_write(false);
|
||||||
@@ -27,7 +28,12 @@ void A4988::dump_config() {
|
|||||||
void A4988::loop() {
|
void A4988::loop() {
|
||||||
bool at_target = this->has_reached_target();
|
bool at_target = this->has_reached_target();
|
||||||
if (this->sleep_pin_ != nullptr) {
|
if (this->sleep_pin_ != nullptr) {
|
||||||
|
bool sleep_rising_edge = !sleep_pin_state_ & !at_target;
|
||||||
this->sleep_pin_->digital_write(!at_target);
|
this->sleep_pin_->digital_write(!at_target);
|
||||||
|
this->sleep_pin_state_ = !at_target;
|
||||||
|
if (sleep_rising_edge) {
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (at_target) {
|
if (at_target) {
|
||||||
this->high_freq_.stop();
|
this->high_freq_.stop();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component {
|
|||||||
GPIOPin *step_pin_;
|
GPIOPin *step_pin_;
|
||||||
GPIOPin *dir_pin_;
|
GPIOPin *dir_pin_;
|
||||||
GPIOPin *sleep_pin_{nullptr};
|
GPIOPin *sleep_pin_{nullptr};
|
||||||
|
bool sleep_pin_state_;
|
||||||
HighFrequencyLoopRequester high_freq_;
|
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
|
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
|
||||||
|
|
||||||
|
|
||||||
a4988_ns = cg.esphome_ns.namespace('a4988')
|
a4988_ns = cg.esphome_ns.namespace("a4988")
|
||||||
A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component)
|
A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
|
||||||
|
|
||||||
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({
|
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_ID): cv.declare_id(A4988),
|
||||||
cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
|
||||||
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
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() {
|
void ADCSensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||||
|
#ifndef USE_ADC_SENSOR_VCC
|
||||||
GPIOPin(this->pin_, INPUT).setup();
|
GPIOPin(this->pin_, INPUT).setup();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
||||||
@@ -58,7 +60,7 @@ void ADCSensor::update() {
|
|||||||
}
|
}
|
||||||
float ADCSensor::sample() {
|
float ADCSensor::sample() {
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
float value_v = analogRead(this->pin_) / 4095.0f;
|
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
|
||||||
switch (this->attenuation_) {
|
switch (this->attenuation_) {
|
||||||
case ADC_0db:
|
case ADC_0db:
|
||||||
value_v *= 1.1;
|
value_v *= 1.1;
|
||||||
@@ -80,7 +82,7 @@ float ADCSensor::sample() {
|
|||||||
#ifdef USE_ADC_SENSOR_VCC
|
#ifdef USE_ADC_SENSOR_VCC
|
||||||
return ESP.getVcc() / 1024.0f;
|
return ESP.getVcc() / 1024.0f;
|
||||||
#else
|
#else
|
||||||
return analogRead(this->pin_) / 1024.0f;
|
return analogRead(this->pin_) / 1024.0f; // NOLINT
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,36 +2,51 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.components import sensor, voltage_sampler
|
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 = {
|
ATTENUATION_MODES = {
|
||||||
'0db': cg.global_ns.ADC_0db,
|
"0db": cg.global_ns.ADC_0db,
|
||||||
'2.5db': cg.global_ns.ADC_2_5db,
|
"2.5db": cg.global_ns.ADC_2_5db,
|
||||||
'6db': cg.global_ns.ADC_6db,
|
"6db": cg.global_ns.ADC_6db,
|
||||||
'11db': cg.global_ns.ADC_11db,
|
"11db": cg.global_ns.ADC_11db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_adc_pin(value):
|
def validate_adc_pin(value):
|
||||||
vcc = str(value).upper()
|
vcc = str(value).upper()
|
||||||
if vcc == 'VCC':
|
if vcc == "VCC":
|
||||||
return cv.only_on_esp8266(vcc)
|
return cv.only_on_esp8266(vcc)
|
||||||
return pins.analog_pin(value)
|
return pins.analog_pin(value)
|
||||||
|
|
||||||
|
|
||||||
adc_ns = cg.esphome_ns.namespace('adc')
|
adc_ns = cg.esphome_ns.namespace("adc")
|
||||||
ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent,
|
ADCSensor = adc_ns.class_(
|
||||||
voltage_sampler.VoltageSampler)
|
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
|
||||||
cv.Required(CONF_PIN): validate_adc_pin,
|
.extend(
|
||||||
cv.SplitDefault(CONF_ATTENUATION, esp32='0db'):
|
{
|
||||||
cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)),
|
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||||
}).extend(cv.polling_component_schema('60s'))
|
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):
|
def to_code(config):
|
||||||
@@ -39,8 +54,8 @@ def to_code(config):
|
|||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
yield sensor.register_sensor(var, config)
|
yield sensor.register_sensor(var, config)
|
||||||
|
|
||||||
if config[CONF_PIN] == 'VCC':
|
if config[CONF_PIN] == "VCC":
|
||||||
cg.add_define('USE_ADC_SENSOR_VCC')
|
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_pin(config[CONF_PIN]))
|
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_))
|
||||||
@@ -8,6 +8,9 @@ static const char *TAG = "ade7953";
|
|||||||
|
|
||||||
void ADE7953::dump_config() {
|
void ADE7953::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "ADE7953:");
|
ESP_LOGCONFIG(TAG, "ADE7953:");
|
||||||
|
if (this->has_irq_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " IRQ Pin: GPIO%u", this->irq_pin_number_);
|
||||||
|
}
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
|
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
|
||||||
@@ -18,7 +21,7 @@ void ADE7953::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define ADE_PUBLISH_(name, factor) \
|
#define ADE_PUBLISH_(name, factor) \
|
||||||
if (name) { \
|
if (name && this->name##_sensor_) { \
|
||||||
float value = *name / factor; \
|
float value = *name / factor; \
|
||||||
this->name##_sensor_->publish_state(value); \
|
this->name##_sensor_->publish_state(value); \
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ namespace ade7953 {
|
|||||||
|
|
||||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||||
public:
|
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_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_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_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
|
||||||
@@ -20,6 +24,11 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setup() override {
|
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->set_timeout(100, [this]() {
|
||||||
this->ade_write_<uint8_t>(0x0010, 0x04);
|
this->ade_write_<uint8_t>(0x0010, 0x04);
|
||||||
this->ade_write_<uint8_t>(0x00FE, 0xAD);
|
this->ade_write_<uint8_t>(0x00FE, 0xAD);
|
||||||
@@ -55,6 +64,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool has_irq_ = false;
|
||||||
|
uint8_t irq_pin_number_;
|
||||||
|
GPIOPin *irq_pin_{nullptr};
|
||||||
bool is_setup_{false};
|
bool is_setup_{false};
|
||||||
sensor::Sensor *voltage_sensor_{nullptr};
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
sensor::Sensor *current_a_sensor_{nullptr};
|
sensor::Sensor *current_a_sensor_{nullptr};
|
||||||
|
|||||||
@@ -1,28 +1,55 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, i2c
|
from esphome.components import sensor, i2c
|
||||||
from esphome.const import CONF_ID, CONF_VOLTAGE, \
|
from esphome import pins
|
||||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
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']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
ace7953_ns = cg.esphome_ns.namespace('ade7953')
|
ade7953_ns = cg.esphome_ns.namespace("ade7953")
|
||||||
ADE7953 = ace7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
|
ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
CONF_CURRENT_A = 'current_a'
|
CONF_IRQ_PIN = "irq_pin"
|
||||||
CONF_CURRENT_B = 'current_b'
|
CONF_CURRENT_A = "current_a"
|
||||||
CONF_ACTIVE_POWER_A = 'active_power_a'
|
CONF_CURRENT_B = "current_b"
|
||||||
CONF_ACTIVE_POWER_B = 'active_power_b'
|
CONF_ACTIVE_POWER_A = "active_power_a"
|
||||||
|
CONF_ACTIVE_POWER_B = "active_power_b"
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
cv.Schema(
|
||||||
|
{
|
||||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
|
||||||
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
),
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
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):
|
def to_code(config):
|
||||||
@@ -30,10 +57,18 @@ def to_code(config):
|
|||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
yield i2c.register_i2c_device(var, config)
|
yield i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
|
if CONF_IRQ_PIN in config:
|
||||||
CONF_ACTIVE_POWER_B]:
|
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:
|
if key not in config:
|
||||||
continue
|
continue
|
||||||
conf = config[key]
|
conf = config[key]
|
||||||
sens = yield sensor.new_sensor(conf)
|
sens = yield sensor.new_sensor(conf)
|
||||||
cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens))
|
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||||
|
|||||||
@@ -3,18 +3,24 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import i2c
|
from esphome.components import i2c
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
AUTO_LOAD = ['sensor', 'voltage_sampler']
|
AUTO_LOAD = ["sensor", "voltage_sampler"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
ads1115_ns = cg.esphome_ns.namespace('ads1115')
|
ads1115_ns = cg.esphome_ns.namespace("ads1115")
|
||||||
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
|
ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice)
|
||||||
|
|
||||||
CONF_CONTINUOUS_MODE = 'continuous_mode'
|
CONF_CONTINUOUS_MODE = "continuous_mode"
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
cv.Schema(
|
||||||
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
{
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
|
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):
|
def to_code(config):
|
||||||
|
|||||||
@@ -1,54 +1,67 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, voltage_sampler
|
from esphome.components import sensor, voltage_sampler
|
||||||
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
|
from esphome.const import (
|
||||||
from esphome.py_compat import string_types
|
CONF_GAIN,
|
||||||
|
CONF_MULTIPLEXER,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
UNIT_VOLT,
|
||||||
|
CONF_ID,
|
||||||
|
)
|
||||||
from . import ads1115_ns, ADS1115Component
|
from . import ads1115_ns, ADS1115Component
|
||||||
|
|
||||||
DEPENDENCIES = ['ads1115']
|
DEPENDENCIES = ["ads1115"]
|
||||||
|
|
||||||
ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
|
ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer")
|
||||||
MUX = {
|
MUX = {
|
||||||
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
"A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||||
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
"A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||||
'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
"A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||||
'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
"A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||||
'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
"A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||||
'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
"A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||||
'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
"A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||||
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
"A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||||
}
|
}
|
||||||
|
|
||||||
ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
|
ADS1115Gain = ads1115_ns.enum("ADS1115Gain")
|
||||||
GAIN = {
|
GAIN = {
|
||||||
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
"6.144": ADS1115Gain.ADS1115_GAIN_6P144,
|
||||||
'4.096': ADS1115Gain.ADS1115_GAIN_4P096,
|
"4.096": ADS1115Gain.ADS1115_GAIN_4P096,
|
||||||
'2.048': ADS1115Gain.ADS1115_GAIN_2P048,
|
"2.048": ADS1115Gain.ADS1115_GAIN_2P048,
|
||||||
'1.024': ADS1115Gain.ADS1115_GAIN_1P024,
|
"1.024": ADS1115Gain.ADS1115_GAIN_1P024,
|
||||||
'0.512': ADS1115Gain.ADS1115_GAIN_0P512,
|
"0.512": ADS1115Gain.ADS1115_GAIN_0P512,
|
||||||
'0.256': ADS1115Gain.ADS1115_GAIN_0P256,
|
"0.256": ADS1115Gain.ADS1115_GAIN_0P256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_gain(value):
|
def validate_gain(value):
|
||||||
if isinstance(value, float):
|
if isinstance(value, float):
|
||||||
value = u'{:0.03f}'.format(value)
|
value = f"{value:0.03f}"
|
||||||
elif not isinstance(value, string_types):
|
elif not isinstance(value, str):
|
||||||
raise cv.Invalid('invalid gain "{}"'.format(value))
|
raise cv.Invalid(f'invalid gain "{value}"')
|
||||||
|
|
||||||
return cv.enum(GAIN)(value)
|
return cv.enum(GAIN)(value)
|
||||||
|
|
||||||
|
|
||||||
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent,
|
ADS1115Sensor = ads1115_ns.class_(
|
||||||
voltage_sampler.VoltageSampler)
|
"ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||||
|
)
|
||||||
|
|
||||||
CONF_ADS1115_ID = 'ads1115_id'
|
CONF_ADS1115_ID = "ads1115_id"
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
|
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE)
|
||||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
.extend(
|
||||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'),
|
{
|
||||||
cv.Required(CONF_GAIN): validate_gain,
|
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
|
||||||
}).extend(cv.polling_component_schema('60s'))
|
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):
|
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.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
|
from esphome.const import (
|
||||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
|
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')
|
am2320_ns = cg.esphome_ns.namespace("am2320")
|
||||||
AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice)
|
AM2320Component = am2320_ns.class_(
|
||||||
|
"AM2320Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
cv.Schema(
|
||||||
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),
|
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C))
|
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):
|
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.components import i2c
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONF_APDS9960_ID = 'apds9960_id'
|
CONF_APDS9960_ID = "apds9960_id"
|
||||||
|
|
||||||
apds9960_nds = cg.esphome_ns.namespace('apds9960')
|
apds9960_nds = cg.esphome_ns.namespace("apds9960")
|
||||||
APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice)
|
APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(APDS9960),
|
cv.Schema(
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39))
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(APDS9960),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x39))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
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 esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING
|
||||||
from . import APDS9960, CONF_APDS9960_ID
|
from . import APDS9960, CONF_APDS9960_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['apds9960']
|
DEPENDENCIES = ["apds9960"]
|
||||||
|
|
||||||
DIRECTIONS = {
|
DIRECTIONS = {
|
||||||
'UP': 'set_up_direction',
|
"UP": "set_up_direction",
|
||||||
'DOWN': 'set_down_direction',
|
"DOWN": "set_down_direction",
|
||||||
'LEFT': 'set_left_direction',
|
"LEFT": "set_left_direction",
|
||||||
'RIGHT': 'set_right_direction',
|
"RIGHT": "set_right_direction",
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
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.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
||||||
cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class,
|
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):
|
def to_code(config):
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor
|
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
|
from . import APDS9960, CONF_APDS9960_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['apds9960']
|
DEPENDENCIES = ["apds9960"]
|
||||||
|
|
||||||
TYPES = {
|
TYPES = {
|
||||||
'CLEAR': 'set_clear_channel',
|
"CLEAR": "set_clear_channel",
|
||||||
'RED': 'set_red_channel',
|
"RED": "set_red_channel",
|
||||||
'GREEN': 'set_green_channel',
|
"GREEN": "set_green_channel",
|
||||||
'BLUE': 'set_blue_channel',
|
"BLUE": "set_blue_channel",
|
||||||
'PROXIMITY': 'set_proximity',
|
"PROXIMITY": "set_proximity",
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({
|
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY
|
||||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
).extend(
|
||||||
})
|
{
|
||||||
|
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||||
|
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
|||||||
@@ -2,44 +2,69 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import Condition
|
from esphome.automation import Condition
|
||||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
from esphome.const import (
|
||||||
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT
|
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
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
DEPENDENCIES = ['network']
|
DEPENDENCIES = ["network"]
|
||||||
AUTO_LOAD = ['async_tcp']
|
AUTO_LOAD = ["async_tcp"]
|
||||||
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|
||||||
api_ns = cg.esphome_ns.namespace('api')
|
api_ns = cg.esphome_ns.namespace("api")
|
||||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
HomeAssistantServiceCallAction = api_ns.class_(
|
||||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
"HomeAssistantServiceCallAction", automation.Action
|
||||||
|
)
|
||||||
|
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
|
||||||
|
|
||||||
UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
|
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
||||||
ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
|
ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument")
|
||||||
SERVICE_ARG_NATIVE_TYPES = {
|
SERVICE_ARG_NATIVE_TYPES = {
|
||||||
'bool': bool,
|
"bool": bool,
|
||||||
'int': cg.int32,
|
"int": cg.int32,
|
||||||
'float': float,
|
"float": float,
|
||||||
'string': cg.std_string,
|
"string": cg.std_string,
|
||||||
'bool[]': cg.std_vector.template(bool),
|
"bool[]": cg.std_vector.template(bool),
|
||||||
'int[]': cg.std_vector.template(cg.int32),
|
"int[]": cg.std_vector.template(cg.int32),
|
||||||
'float[]': cg.std_vector.template(float),
|
"float[]": cg.std_vector.template(float),
|
||||||
'string[]': cg.std_vector.template(cg.std_string),
|
"string[]": cg.std_vector.template(cg.std_string),
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.declare_id(APIServer),
|
{
|
||||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
cv.GenerateID(): cv.declare_id(APIServer),
|
||||||
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
|
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
|
||||||
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
cv.Optional(
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
CONF_REBOOT_TIMEOUT, default="15min"
|
||||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
): cv.positive_time_period_milliseconds,
|
||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
cv.Optional(CONF_SERVICES): automation.validate_automation(
|
||||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
|
{
|
||||||
}),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||||
}),
|
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
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)
|
@coroutine_with_priority(40.0)
|
||||||
@@ -61,28 +86,36 @@ def to_code(config):
|
|||||||
func_args.append((native, name))
|
func_args.append((native, name))
|
||||||
service_arg_names.append(name)
|
service_arg_names.append(name)
|
||||||
templ = cg.TemplateArguments(*template_args)
|
templ = cg.TemplateArguments(*template_args)
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
trigger = cg.new_Pvariable(
|
||||||
conf[CONF_SERVICE], service_arg_names)
|
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
|
||||||
|
)
|
||||||
cg.add(var.register_user_service(trigger))
|
cg.add(var.register_user_service(trigger))
|
||||||
yield automation.build_automation(trigger, func_args, conf)
|
yield automation.build_automation(trigger, func_args, conf)
|
||||||
|
|
||||||
cg.add_define('USE_API')
|
cg.add_define("USE_API")
|
||||||
cg.add_global(api_ns.using)
|
cg.add_global(api_ns.using)
|
||||||
|
|
||||||
|
|
||||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||||
|
|
||||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.use_id(APIServer),
|
{
|
||||||
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
cv.GenerateID(): cv.use_id(APIServer),
|
||||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}),
|
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,
|
@automation.register_action(
|
||||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
"homeassistant.service",
|
||||||
|
HomeAssistantServiceCallAction,
|
||||||
|
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
|
||||||
|
)
|
||||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||||
serv = yield cg.get_variable(config[CONF_ID])
|
serv = yield cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||||
@@ -102,23 +135,30 @@ def homeassistant_service_to_code(config, action_id, template_arg, args):
|
|||||||
|
|
||||||
def validate_homeassistant_event(value):
|
def validate_homeassistant_event(value):
|
||||||
value = cv.string(value)
|
value = cv.string(value)
|
||||||
if not value.startswith(u'esphome.'):
|
if not value.startswith("esphome."):
|
||||||
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
|
raise cv.Invalid(
|
||||||
"esphome. For example 'esphome.xyz'")
|
"ESPHome can only generate Home Assistant events that begin with "
|
||||||
|
"esphome. For example 'esphome.xyz'"
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
|
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.use_id(APIServer),
|
{
|
||||||
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
cv.GenerateID(): cv.use_id(APIServer),
|
||||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
||||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||||
cv.Optional(CONF_VARIABLES, 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,
|
@automation.register_action(
|
||||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA)
|
"homeassistant.event",
|
||||||
|
HomeAssistantServiceCallAction,
|
||||||
|
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
|
||||||
|
)
|
||||||
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||||
serv = yield cg.get_variable(config[CONF_ID])
|
serv = yield cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||||
@@ -136,6 +176,29 @@ def homeassistant_event_to_code(config, action_id, template_arg, args):
|
|||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
@automation.register_condition('api.connected', APIConnectedCondition, {})
|
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):
|
def api_connected_to_code(config, condition_id, template_arg, args):
|
||||||
yield cg.new_Pvariable(condition_id, template_arg)
|
yield cg.new_Pvariable(condition_id, template_arg)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ service APIConnection {
|
|||||||
// The Home Assistant protocol is structured as a simple
|
// The Home Assistant protocol is structured as a simple
|
||||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
// TCP socket with short binary messages encoded in the protocol buffers format
|
||||||
// First, a message in this protocol has a specific 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 size of the message object. (type is not part of this)
|
||||||
// * VarInt denoting the type of message.
|
// * VarInt denoting the type of message.
|
||||||
// * The message object encoded as a ProtoBuf message
|
// * The message object encoded as a ProtoBuf message
|
||||||
@@ -301,12 +302,18 @@ message ListEntitiesFanResponse {
|
|||||||
|
|
||||||
bool supports_oscillation = 5;
|
bool supports_oscillation = 5;
|
||||||
bool supports_speed = 6;
|
bool supports_speed = 6;
|
||||||
|
bool supports_direction = 7;
|
||||||
|
int32 supported_speed_count = 8;
|
||||||
}
|
}
|
||||||
enum FanSpeed {
|
enum FanSpeed {
|
||||||
FAN_SPEED_LOW = 0;
|
FAN_SPEED_LOW = 0;
|
||||||
FAN_SPEED_MEDIUM = 1;
|
FAN_SPEED_MEDIUM = 1;
|
||||||
FAN_SPEED_HIGH = 2;
|
FAN_SPEED_HIGH = 2;
|
||||||
}
|
}
|
||||||
|
enum FanDirection {
|
||||||
|
FAN_DIRECTION_FORWARD = 0;
|
||||||
|
FAN_DIRECTION_REVERSE = 1;
|
||||||
|
}
|
||||||
message FanStateResponse {
|
message FanStateResponse {
|
||||||
option (id) = 23;
|
option (id) = 23;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
@@ -316,7 +323,9 @@ message FanStateResponse {
|
|||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
bool oscillating = 3;
|
bool oscillating = 3;
|
||||||
FanSpeed speed = 4;
|
FanSpeed speed = 4 [deprecated = true];
|
||||||
|
FanDirection direction = 5;
|
||||||
|
int32 speed_level = 6;
|
||||||
}
|
}
|
||||||
message FanCommandRequest {
|
message FanCommandRequest {
|
||||||
option (id) = 31;
|
option (id) = 31;
|
||||||
@@ -327,10 +336,14 @@ message FanCommandRequest {
|
|||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_state = 2;
|
bool has_state = 2;
|
||||||
bool state = 3;
|
bool state = 3;
|
||||||
bool has_speed = 4;
|
bool has_speed = 4 [deprecated = true];
|
||||||
FanSpeed speed = 5;
|
FanSpeed speed = 5 [deprecated = true];
|
||||||
bool has_oscillating = 6;
|
bool has_oscillating = 6;
|
||||||
bool oscillating = 7;
|
bool oscillating = 7;
|
||||||
|
bool has_direction = 8;
|
||||||
|
FanDirection direction = 9;
|
||||||
|
bool has_speed_level = 10;
|
||||||
|
int32 speed_level = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== LIGHT ====================
|
// ==================== LIGHT ====================
|
||||||
@@ -410,6 +423,7 @@ message ListEntitiesSensorResponse {
|
|||||||
string unit_of_measurement = 6;
|
string unit_of_measurement = 6;
|
||||||
int32 accuracy_decimals = 7;
|
int32 accuracy_decimals = 7;
|
||||||
bool force_update = 8;
|
bool force_update = 8;
|
||||||
|
string device_class = 9;
|
||||||
}
|
}
|
||||||
message SensorStateResponse {
|
message SensorStateResponse {
|
||||||
option (id) = 25;
|
option (id) = 25;
|
||||||
@@ -653,12 +667,34 @@ enum ClimateMode {
|
|||||||
CLIMATE_MODE_AUTO = 1;
|
CLIMATE_MODE_AUTO = 1;
|
||||||
CLIMATE_MODE_COOL = 2;
|
CLIMATE_MODE_COOL = 2;
|
||||||
CLIMATE_MODE_HEAT = 3;
|
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 {
|
enum ClimateAction {
|
||||||
CLIMATE_ACTION_OFF = 0;
|
CLIMATE_ACTION_OFF = 0;
|
||||||
// values same as mode for readability
|
// values same as mode for readability
|
||||||
CLIMATE_ACTION_COOLING = 2;
|
CLIMATE_ACTION_COOLING = 2;
|
||||||
CLIMATE_ACTION_HEATING = 3;
|
CLIMATE_ACTION_HEATING = 3;
|
||||||
|
CLIMATE_ACTION_IDLE = 4;
|
||||||
|
CLIMATE_ACTION_DRYING = 5;
|
||||||
|
CLIMATE_ACTION_FAN = 6;
|
||||||
}
|
}
|
||||||
message ListEntitiesClimateResponse {
|
message ListEntitiesClimateResponse {
|
||||||
option (id) = 46;
|
option (id) = 46;
|
||||||
@@ -678,6 +714,8 @@ message ListEntitiesClimateResponse {
|
|||||||
float visual_temperature_step = 10;
|
float visual_temperature_step = 10;
|
||||||
bool supports_away = 11;
|
bool supports_away = 11;
|
||||||
bool supports_action = 12;
|
bool supports_action = 12;
|
||||||
|
repeated ClimateFanMode supported_fan_modes = 13;
|
||||||
|
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||||
}
|
}
|
||||||
message ClimateStateResponse {
|
message ClimateStateResponse {
|
||||||
option (id) = 47;
|
option (id) = 47;
|
||||||
@@ -693,6 +731,8 @@ message ClimateStateResponse {
|
|||||||
float target_temperature_high = 6;
|
float target_temperature_high = 6;
|
||||||
bool away = 7;
|
bool away = 7;
|
||||||
ClimateAction action = 8;
|
ClimateAction action = 8;
|
||||||
|
ClimateFanMode fan_mode = 9;
|
||||||
|
ClimateSwingMode swing_mode = 10;
|
||||||
}
|
}
|
||||||
message ClimateCommandRequest {
|
message ClimateCommandRequest {
|
||||||
option (id) = 48;
|
option (id) = 48;
|
||||||
@@ -711,4 +751,8 @@ message ClimateCommandRequest {
|
|||||||
float target_temperature_high = 9;
|
float target_temperature_high = 9;
|
||||||
bool has_away = 10;
|
bool has_away = 10;
|
||||||
bool away = 11;
|
bool away = 11;
|
||||||
|
bool has_fan_mode = 12;
|
||||||
|
ClimateFanMode fan_mode = 13;
|
||||||
|
bool has_swing_mode = 14;
|
||||||
|
ClimateSwingMode swing_mode = 15;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
|
#include "esphome/components/fan/fan_helpers.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
@@ -137,7 +140,6 @@ void APIConnection::loop() {
|
|||||||
// bool done = 3;
|
// bool done = 3;
|
||||||
bool done = this->image_reader_.available() == to_send;
|
bool done = this->image_reader_.available() == to_send;
|
||||||
buffer.encode_bool(3, done);
|
buffer.encode_bool(3, done);
|
||||||
this->set_nodelay(false);
|
|
||||||
bool success = this->send_buffer(buffer, 44);
|
bool success = this->send_buffer(buffer, 44);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -247,8 +249,12 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
|
|||||||
resp.state = fan->state;
|
resp.state = fan->state;
|
||||||
if (traits.supports_oscillation())
|
if (traits.supports_oscillation())
|
||||||
resp.oscillating = fan->oscillating;
|
resp.oscillating = fan->oscillating;
|
||||||
if (traits.supports_speed())
|
if (traits.supports_speed()) {
|
||||||
resp.speed = static_cast<enums::FanSpeed>(fan->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);
|
return this->send_fan_state_response(resp);
|
||||||
}
|
}
|
||||||
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||||
@@ -260,6 +266,8 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
|||||||
msg.unique_id = get_default_unique_id("fan", fan);
|
msg.unique_id = get_default_unique_id("fan", fan);
|
||||||
msg.supports_oscillation = traits.supports_oscillation();
|
msg.supports_oscillation = traits.supports_oscillation();
|
||||||
msg.supports_speed = traits.supports_speed();
|
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);
|
return this->send_list_entities_fan_response(msg);
|
||||||
}
|
}
|
||||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||||
@@ -267,13 +275,21 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
|||||||
if (fan == nullptr)
|
if (fan == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
auto traits = fan->get_traits();
|
||||||
|
|
||||||
auto call = fan->make_call();
|
auto call = fan->make_call();
|
||||||
if (msg.has_state)
|
if (msg.has_state)
|
||||||
call.set_state(msg.state);
|
call.set_state(msg.state);
|
||||||
if (msg.has_oscillating)
|
if (msg.has_oscillating)
|
||||||
call.set_oscillating(msg.oscillating);
|
call.set_oscillating(msg.oscillating);
|
||||||
if (msg.has_speed)
|
if (msg.has_speed_level) {
|
||||||
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
// 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();
|
call.perform();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -378,6 +394,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
|||||||
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
||||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||||
msg.force_update = sensor->get_force_update();
|
msg.force_update = sensor->get_force_update();
|
||||||
|
msg.device_class = sensor->get_device_class();
|
||||||
return this->send_list_entities_sensor_response(msg);
|
return this->send_list_entities_sensor_response(msg);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -458,6 +475,10 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
|||||||
}
|
}
|
||||||
if (traits.get_supports_away())
|
if (traits.get_supports_away())
|
||||||
resp.away = climate->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);
|
return this->send_climate_state_response(resp);
|
||||||
}
|
}
|
||||||
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||||
@@ -470,7 +491,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
|||||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_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,
|
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||||
climate::CLIMATE_MODE_HEAT}) {
|
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) {
|
||||||
if (traits.supports_mode(mode))
|
if (traits.supports_mode(mode))
|
||||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||||
}
|
}
|
||||||
@@ -479,6 +500,17 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
|||||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
||||||
msg.supports_away = traits.get_supports_away();
|
msg.supports_away = traits.get_supports_away();
|
||||||
msg.supports_action = traits.get_supports_action();
|
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);
|
return this->send_list_entities_climate_response(msg);
|
||||||
}
|
}
|
||||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||||
@@ -497,6 +529,10 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
|||||||
call.set_target_temperature_high(msg.target_temperature_high);
|
call.set_target_temperature_high(msg.target_temperature_high);
|
||||||
if (msg.has_away)
|
if (msg.has_away)
|
||||||
call.set_away(msg.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();
|
call.perform();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -539,8 +575,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
|
|||||||
if (this->log_subscription_ < level)
|
if (this->log_subscription_ < level)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this->set_nodelay(false);
|
|
||||||
|
|
||||||
// Send raw so that we don't copy too much
|
// Send raw so that we don't copy too much
|
||||||
auto buffer = this->create_buffer();
|
auto buffer = this->create_buffer();
|
||||||
// LogLevel level = 1;
|
// LogLevel level = 1;
|
||||||
@@ -568,7 +602,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
|||||||
|
|
||||||
HelloResponse resp;
|
HelloResponse resp;
|
||||||
resp.api_version_major = 1;
|
resp.api_version_major = 1;
|
||||||
resp.api_version_minor = 3;
|
resp.api_version_minor = 4;
|
||||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||||
this->connection_state_ = ConnectionState::CONNECTED;
|
this->connection_state_ = ConnectionState::CONNECTED;
|
||||||
return resp;
|
return resp;
|
||||||
@@ -655,8 +689,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->client_->add(reinterpret_cast<char *>(header.data()), header.size());
|
this->client_->add(reinterpret_cast<char *>(header.data()), header.size(),
|
||||||
this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->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();
|
bool ret = this->client_->send();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,12 +138,6 @@ class APIConnection : public APIServerConnection {
|
|||||||
void on_timeout_(uint32_t time);
|
void on_timeout_(uint32_t time);
|
||||||
void on_data_(uint8_t *buf, size_t len);
|
void on_data_(uint8_t *buf, size_t len);
|
||||||
void parse_recv_buffer_();
|
void parse_recv_buffer_();
|
||||||
void set_nodelay(bool nodelay) override {
|
|
||||||
if (nodelay == this->current_nodelay_)
|
|
||||||
return;
|
|
||||||
this->client_->setNoDelay(nodelay);
|
|
||||||
this->current_nodelay_ = nodelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class ConnectionState {
|
enum class ConnectionState {
|
||||||
WAITING_FOR_HELLO,
|
WAITING_FOR_HELLO,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// This file was automatically generated with a tool.
|
||||||
|
// See scripts/api_protobuf/api_protobuf.py
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
@@ -50,6 +52,16 @@ template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed val
|
|||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirection value) {
|
||||||
|
switch (value) {
|
||||||
|
case enums::FAN_DIRECTION_FORWARD:
|
||||||
|
return "FAN_DIRECTION_FORWARD";
|
||||||
|
case enums::FAN_DIRECTION_REVERSE:
|
||||||
|
return "FAN_DIRECTION_REVERSE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case enums::LOG_LEVEL_NONE:
|
case enums::LOG_LEVEL_NONE:
|
||||||
@@ -102,6 +114,48 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
|
|||||||
return "CLIMATE_MODE_COOL";
|
return "CLIMATE_MODE_COOL";
|
||||||
case enums::CLIMATE_MODE_HEAT:
|
case enums::CLIMATE_MODE_HEAT:
|
||||||
return "CLIMATE_MODE_HEAT";
|
return "CLIMATE_MODE_HEAT";
|
||||||
|
case enums::CLIMATE_MODE_FAN_ONLY:
|
||||||
|
return "CLIMATE_MODE_FAN_ONLY";
|
||||||
|
case enums::CLIMATE_MODE_DRY:
|
||||||
|
return "CLIMATE_MODE_DRY";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::ClimateFanMode value) {
|
||||||
|
switch (value) {
|
||||||
|
case enums::CLIMATE_FAN_ON:
|
||||||
|
return "CLIMATE_FAN_ON";
|
||||||
|
case enums::CLIMATE_FAN_OFF:
|
||||||
|
return "CLIMATE_FAN_OFF";
|
||||||
|
case enums::CLIMATE_FAN_AUTO:
|
||||||
|
return "CLIMATE_FAN_AUTO";
|
||||||
|
case enums::CLIMATE_FAN_LOW:
|
||||||
|
return "CLIMATE_FAN_LOW";
|
||||||
|
case enums::CLIMATE_FAN_MEDIUM:
|
||||||
|
return "CLIMATE_FAN_MEDIUM";
|
||||||
|
case enums::CLIMATE_FAN_HIGH:
|
||||||
|
return "CLIMATE_FAN_HIGH";
|
||||||
|
case enums::CLIMATE_FAN_MIDDLE:
|
||||||
|
return "CLIMATE_FAN_MIDDLE";
|
||||||
|
case enums::CLIMATE_FAN_FOCUS:
|
||||||
|
return "CLIMATE_FAN_FOCUS";
|
||||||
|
case enums::CLIMATE_FAN_DIFFUSE:
|
||||||
|
return "CLIMATE_FAN_DIFFUSE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::ClimateSwingMode value) {
|
||||||
|
switch (value) {
|
||||||
|
case enums::CLIMATE_SWING_OFF:
|
||||||
|
return "CLIMATE_SWING_OFF";
|
||||||
|
case enums::CLIMATE_SWING_BOTH:
|
||||||
|
return "CLIMATE_SWING_BOTH";
|
||||||
|
case enums::CLIMATE_SWING_VERTICAL:
|
||||||
|
return "CLIMATE_SWING_VERTICAL";
|
||||||
|
case enums::CLIMATE_SWING_HORIZONTAL:
|
||||||
|
return "CLIMATE_SWING_HORIZONTAL";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
@@ -114,6 +168,12 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
|
|||||||
return "CLIMATE_ACTION_COOLING";
|
return "CLIMATE_ACTION_COOLING";
|
||||||
case enums::CLIMATE_ACTION_HEATING:
|
case enums::CLIMATE_ACTION_HEATING:
|
||||||
return "CLIMATE_ACTION_HEATING";
|
return "CLIMATE_ACTION_HEATING";
|
||||||
|
case enums::CLIMATE_ACTION_IDLE:
|
||||||
|
return "CLIMATE_ACTION_IDLE";
|
||||||
|
case enums::CLIMATE_ACTION_DRYING:
|
||||||
|
return "CLIMATE_ACTION_DRYING";
|
||||||
|
case enums::CLIMATE_ACTION_FAN:
|
||||||
|
return "CLIMATE_ACTION_FAN";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
@@ -710,6 +770,14 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
|
|||||||
this->supports_speed = value.as_bool();
|
this->supports_speed = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 7: {
|
||||||
|
this->supports_direction = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 8: {
|
||||||
|
this->supported_speed_count = value.as_int32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -749,6 +817,8 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_string(4, this->unique_id);
|
buffer.encode_string(4, this->unique_id);
|
||||||
buffer.encode_bool(5, this->supports_oscillation);
|
buffer.encode_bool(5, this->supports_oscillation);
|
||||||
buffer.encode_bool(6, this->supports_speed);
|
buffer.encode_bool(6, this->supports_speed);
|
||||||
|
buffer.encode_bool(7, this->supports_direction);
|
||||||
|
buffer.encode_int32(8, this->supported_speed_count);
|
||||||
}
|
}
|
||||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -777,6 +847,15 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" supports_speed: ");
|
out.append(" supports_speed: ");
|
||||||
out.append(YESNO(this->supports_speed));
|
out.append(YESNO(this->supports_speed));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" supports_direction: ");
|
||||||
|
out.append(YESNO(this->supports_direction));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" supported_speed_count: ");
|
||||||
|
sprintf(buffer, "%d", this->supported_speed_count);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -793,6 +872,14 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||||||
this->speed = value.as_enum<enums::FanSpeed>();
|
this->speed = value.as_enum<enums::FanSpeed>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 5: {
|
||||||
|
this->direction = value.as_enum<enums::FanDirection>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 6: {
|
||||||
|
this->speed_level = value.as_int32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -812,6 +899,8 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_bool(2, this->state);
|
buffer.encode_bool(2, this->state);
|
||||||
buffer.encode_bool(3, this->oscillating);
|
buffer.encode_bool(3, this->oscillating);
|
||||||
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
||||||
|
buffer.encode_enum<enums::FanDirection>(5, this->direction);
|
||||||
|
buffer.encode_int32(6, this->speed_level);
|
||||||
}
|
}
|
||||||
void FanStateResponse::dump_to(std::string &out) const {
|
void FanStateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -832,6 +921,15 @@ void FanStateResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" speed: ");
|
out.append(" speed: ");
|
||||||
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
|
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" direction: ");
|
||||||
|
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" speed_level: ");
|
||||||
|
sprintf(buffer, "%d", this->speed_level);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -860,6 +958,22 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||||||
this->oscillating = value.as_bool();
|
this->oscillating = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 8: {
|
||||||
|
this->has_direction = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 9: {
|
||||||
|
this->direction = value.as_enum<enums::FanDirection>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 10: {
|
||||||
|
this->has_speed_level = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 11: {
|
||||||
|
this->speed_level = value.as_int32();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -882,6 +996,10 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_enum<enums::FanSpeed>(5, this->speed);
|
buffer.encode_enum<enums::FanSpeed>(5, this->speed);
|
||||||
buffer.encode_bool(6, this->has_oscillating);
|
buffer.encode_bool(6, this->has_oscillating);
|
||||||
buffer.encode_bool(7, this->oscillating);
|
buffer.encode_bool(7, this->oscillating);
|
||||||
|
buffer.encode_bool(8, this->has_direction);
|
||||||
|
buffer.encode_enum<enums::FanDirection>(9, this->direction);
|
||||||
|
buffer.encode_bool(10, this->has_speed_level);
|
||||||
|
buffer.encode_int32(11, this->speed_level);
|
||||||
}
|
}
|
||||||
void FanCommandRequest::dump_to(std::string &out) const {
|
void FanCommandRequest::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -914,6 +1032,23 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" oscillating: ");
|
out.append(" oscillating: ");
|
||||||
out.append(YESNO(this->oscillating));
|
out.append(YESNO(this->oscillating));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_direction: ");
|
||||||
|
out.append(YESNO(this->has_direction));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" direction: ");
|
||||||
|
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_speed_level: ");
|
||||||
|
out.append(YESNO(this->has_speed_level));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" speed_level: ");
|
||||||
|
sprintf(buffer, "%d", this->speed_level);
|
||||||
|
out.append(buffer);
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -1398,6 +1533,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
|||||||
this->unit_of_measurement = value.as_string();
|
this->unit_of_measurement = value.as_string();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 9: {
|
||||||
|
this->device_class = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1421,6 +1560,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_string(6, this->unit_of_measurement);
|
buffer.encode_string(6, this->unit_of_measurement);
|
||||||
buffer.encode_int32(7, this->accuracy_decimals);
|
buffer.encode_int32(7, this->accuracy_decimals);
|
||||||
buffer.encode_bool(8, this->force_update);
|
buffer.encode_bool(8, this->force_update);
|
||||||
|
buffer.encode_string(9, this->device_class);
|
||||||
}
|
}
|
||||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -1458,6 +1598,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" force_update: ");
|
out.append(" force_update: ");
|
||||||
out.append(YESNO(this->force_update));
|
out.append(YESNO(this->force_update));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" device_class: ");
|
||||||
|
out.append("'").append(this->device_class).append("'");
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -2458,6 +2602,14 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
|||||||
this->supports_action = value.as_bool();
|
this->supports_action = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 13: {
|
||||||
|
this->supported_fan_modes.push_back(value.as_enum<enums::ClimateFanMode>());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 14: {
|
||||||
|
this->supported_swing_modes.push_back(value.as_enum<enums::ClimateSwingMode>());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2517,6 +2669,12 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_float(10, this->visual_temperature_step);
|
buffer.encode_float(10, this->visual_temperature_step);
|
||||||
buffer.encode_bool(11, this->supports_away);
|
buffer.encode_bool(11, this->supports_away);
|
||||||
buffer.encode_bool(12, this->supports_action);
|
buffer.encode_bool(12, this->supports_action);
|
||||||
|
for (auto &it : this->supported_fan_modes) {
|
||||||
|
buffer.encode_enum<enums::ClimateFanMode>(13, it, true);
|
||||||
|
}
|
||||||
|
for (auto &it : this->supported_swing_modes) {
|
||||||
|
buffer.encode_enum<enums::ClimateSwingMode>(14, it, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -2574,6 +2732,18 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" supports_action: ");
|
out.append(" supports_action: ");
|
||||||
out.append(YESNO(this->supports_action));
|
out.append(YESNO(this->supports_action));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
for (const auto &it : this->supported_fan_modes) {
|
||||||
|
out.append(" supported_fan_modes: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateFanMode>(it));
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &it : this->supported_swing_modes) {
|
||||||
|
out.append(" supported_swing_modes: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(it));
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -2590,6 +2760,14 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||||||
this->action = value.as_enum<enums::ClimateAction>();
|
this->action = value.as_enum<enums::ClimateAction>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 9: {
|
||||||
|
this->fan_mode = value.as_enum<enums::ClimateFanMode>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 10: {
|
||||||
|
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2629,6 +2807,8 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_float(6, this->target_temperature_high);
|
buffer.encode_float(6, this->target_temperature_high);
|
||||||
buffer.encode_bool(7, this->away);
|
buffer.encode_bool(7, this->away);
|
||||||
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
||||||
|
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
|
||||||
|
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
|
||||||
}
|
}
|
||||||
void ClimateStateResponse::dump_to(std::string &out) const {
|
void ClimateStateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -2669,6 +2849,14 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" action: ");
|
out.append(" action: ");
|
||||||
out.append(proto_enum_to_string<enums::ClimateAction>(this->action));
|
out.append(proto_enum_to_string<enums::ClimateAction>(this->action));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" fan_mode: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateFanMode>(this->fan_mode));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" swing_mode: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
@@ -2701,6 +2889,22 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
|||||||
this->away = value.as_bool();
|
this->away = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 12: {
|
||||||
|
this->has_fan_mode = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 13: {
|
||||||
|
this->fan_mode = value.as_enum<enums::ClimateFanMode>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 14: {
|
||||||
|
this->has_swing_mode = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 15: {
|
||||||
|
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2739,6 +2943,10 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_float(9, this->target_temperature_high);
|
buffer.encode_float(9, this->target_temperature_high);
|
||||||
buffer.encode_bool(10, this->has_away);
|
buffer.encode_bool(10, this->has_away);
|
||||||
buffer.encode_bool(11, this->away);
|
buffer.encode_bool(11, this->away);
|
||||||
|
buffer.encode_bool(12, this->has_fan_mode);
|
||||||
|
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
|
||||||
|
buffer.encode_bool(14, this->has_swing_mode);
|
||||||
|
buffer.encode_enum<enums::ClimateSwingMode>(15, this->swing_mode);
|
||||||
}
|
}
|
||||||
void ClimateCommandRequest::dump_to(std::string &out) const {
|
void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -2790,6 +2998,22 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
|||||||
out.append(" away: ");
|
out.append(" away: ");
|
||||||
out.append(YESNO(this->away));
|
out.append(YESNO(this->away));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_fan_mode: ");
|
||||||
|
out.append(YESNO(this->has_fan_mode));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" fan_mode: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateFanMode>(this->fan_mode));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_swing_mode: ");
|
||||||
|
out.append(YESNO(this->has_swing_mode));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" swing_mode: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// This file was automatically generated with a tool.
|
||||||
|
// See scripts/api_protobuf/api_protobuf.py
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
@@ -26,6 +28,10 @@ enum FanSpeed : uint32_t {
|
|||||||
FAN_SPEED_MEDIUM = 1,
|
FAN_SPEED_MEDIUM = 1,
|
||||||
FAN_SPEED_HIGH = 2,
|
FAN_SPEED_HIGH = 2,
|
||||||
};
|
};
|
||||||
|
enum FanDirection : uint32_t {
|
||||||
|
FAN_DIRECTION_FORWARD = 0,
|
||||||
|
FAN_DIRECTION_REVERSE = 1,
|
||||||
|
};
|
||||||
enum LogLevel : uint32_t {
|
enum LogLevel : uint32_t {
|
||||||
LOG_LEVEL_NONE = 0,
|
LOG_LEVEL_NONE = 0,
|
||||||
LOG_LEVEL_ERROR = 1,
|
LOG_LEVEL_ERROR = 1,
|
||||||
@@ -50,11 +56,33 @@ enum ClimateMode : uint32_t {
|
|||||||
CLIMATE_MODE_AUTO = 1,
|
CLIMATE_MODE_AUTO = 1,
|
||||||
CLIMATE_MODE_COOL = 2,
|
CLIMATE_MODE_COOL = 2,
|
||||||
CLIMATE_MODE_HEAT = 3,
|
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 {
|
enum ClimateAction : uint32_t {
|
||||||
CLIMATE_ACTION_OFF = 0,
|
CLIMATE_ACTION_OFF = 0,
|
||||||
CLIMATE_ACTION_COOLING = 2,
|
CLIMATE_ACTION_COOLING = 2,
|
||||||
CLIMATE_ACTION_HEATING = 3,
|
CLIMATE_ACTION_HEATING = 3,
|
||||||
|
CLIMATE_ACTION_IDLE = 4,
|
||||||
|
CLIMATE_ACTION_DRYING = 5,
|
||||||
|
CLIMATE_ACTION_FAN = 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace enums
|
} // namespace enums
|
||||||
@@ -255,6 +283,8 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
|||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{}; // NOLINT
|
||||||
bool supports_oscillation{false}; // NOLINT
|
bool supports_oscillation{false}; // NOLINT
|
||||||
bool supports_speed{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 encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -265,10 +295,12 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class FanStateResponse : public ProtoMessage {
|
class FanStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0}; // NOLINT
|
||||||
bool state{false}; // NOLINT
|
bool state{false}; // NOLINT
|
||||||
bool oscillating{false}; // NOLINT
|
bool oscillating{false}; // NOLINT
|
||||||
enums::FanSpeed speed{}; // NOLINT
|
enums::FanSpeed speed{}; // NOLINT
|
||||||
|
enums::FanDirection direction{}; // NOLINT
|
||||||
|
int32_t speed_level{0}; // NOLINT
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -278,13 +310,17 @@ class FanStateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class FanCommandRequest : public ProtoMessage {
|
class FanCommandRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0}; // NOLINT
|
||||||
bool has_state{false}; // NOLINT
|
bool has_state{false}; // NOLINT
|
||||||
bool state{false}; // NOLINT
|
bool state{false}; // NOLINT
|
||||||
bool has_speed{false}; // NOLINT
|
bool has_speed{false}; // NOLINT
|
||||||
enums::FanSpeed speed{}; // NOLINT
|
enums::FanSpeed speed{}; // NOLINT
|
||||||
bool has_oscillating{false}; // NOLINT
|
bool has_oscillating{false}; // NOLINT
|
||||||
bool 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 encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -371,6 +407,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
|||||||
std::string unit_of_measurement{}; // NOLINT
|
std::string unit_of_measurement{}; // NOLINT
|
||||||
int32_t accuracy_decimals{0}; // NOLINT
|
int32_t accuracy_decimals{0}; // NOLINT
|
||||||
bool force_update{false}; // NOLINT
|
bool force_update{false}; // NOLINT
|
||||||
|
std::string device_class{}; // NOLINT
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -643,18 +680,20 @@ class CameraImageRequest : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ListEntitiesClimateResponse : public ProtoMessage {
|
class ListEntitiesClimateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string object_id{}; // NOLINT
|
std::string object_id{}; // NOLINT
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0}; // NOLINT
|
||||||
std::string name{}; // NOLINT
|
std::string name{}; // NOLINT
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{}; // NOLINT
|
||||||
bool supports_current_temperature{false}; // NOLINT
|
bool supports_current_temperature{false}; // NOLINT
|
||||||
bool supports_two_point_target_temperature{false}; // NOLINT
|
bool supports_two_point_target_temperature{false}; // NOLINT
|
||||||
std::vector<enums::ClimateMode> supported_modes{}; // NOLINT
|
std::vector<enums::ClimateMode> supported_modes{}; // NOLINT
|
||||||
float visual_min_temperature{0.0f}; // NOLINT
|
float visual_min_temperature{0.0f}; // NOLINT
|
||||||
float visual_max_temperature{0.0f}; // NOLINT
|
float visual_max_temperature{0.0f}; // NOLINT
|
||||||
float visual_temperature_step{0.0f}; // NOLINT
|
float visual_temperature_step{0.0f}; // NOLINT
|
||||||
bool supports_away{false}; // NOLINT
|
bool supports_away{false}; // NOLINT
|
||||||
bool supports_action{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 encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -665,14 +704,16 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class ClimateStateResponse : public ProtoMessage {
|
class ClimateStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0}; // NOLINT
|
||||||
enums::ClimateMode mode{}; // NOLINT
|
enums::ClimateMode mode{}; // NOLINT
|
||||||
float current_temperature{0.0f}; // NOLINT
|
float current_temperature{0.0f}; // NOLINT
|
||||||
float target_temperature{0.0f}; // NOLINT
|
float target_temperature{0.0f}; // NOLINT
|
||||||
float target_temperature_low{0.0f}; // NOLINT
|
float target_temperature_low{0.0f}; // NOLINT
|
||||||
float target_temperature_high{0.0f}; // NOLINT
|
float target_temperature_high{0.0f}; // NOLINT
|
||||||
bool away{false}; // NOLINT
|
bool away{false}; // NOLINT
|
||||||
enums::ClimateAction action{}; // NOLINT
|
enums::ClimateAction action{}; // NOLINT
|
||||||
|
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||||
|
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
@@ -693,6 +734,10 @@ class ClimateCommandRequest : public ProtoMessage {
|
|||||||
float target_temperature_high{0.0f}; // NOLINT
|
float target_temperature_high{0.0f}; // NOLINT
|
||||||
bool has_away{false}; // NOLINT
|
bool has_away{false}; // NOLINT
|
||||||
bool 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 encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// This file was automatically generated with a tool.
|
||||||
|
// See scripts/api_protobuf/api_protobuf.py
|
||||||
#include "api_pb2_service.h"
|
#include "api_pb2_service.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
@@ -8,69 +10,57 @@ static const char *TAG = "api.service";
|
|||||||
|
|
||||||
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<HelloResponse>(msg, 2);
|
return this->send_message_<HelloResponse>(msg, 2);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<ConnectResponse>(msg, 4);
|
return this->send_message_<ConnectResponse>(msg, 4);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
||||||
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<DisconnectRequest>(msg, 5);
|
return this->send_message_<DisconnectRequest>(msg, 5);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<DisconnectResponse>(msg, 6);
|
return this->send_message_<DisconnectResponse>(msg, 6);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
||||||
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<PingRequest>(msg, 7);
|
return this->send_message_<PingRequest>(msg, 7);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<PingResponse>(msg, 8);
|
return this->send_message_<PingResponse>(msg, 8);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
||||||
}
|
}
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
|
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());
|
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<CoverStateResponse>(msg, 22);
|
return this->send_message_<CoverStateResponse>(msg, 22);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -79,14 +69,12 @@ bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse
|
|||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<FanStateResponse>(msg, 23);
|
return this->send_message_<FanStateResponse>(msg, 23);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -95,14 +83,12 @@ bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &ms
|
|||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<LightStateResponse>(msg, 24);
|
return this->send_message_<LightStateResponse>(msg, 24);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -111,28 +97,24 @@ bool APIServerConnectionBase::send_light_state_response(const LightStateResponse
|
|||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<SensorStateResponse>(msg, 25);
|
return this->send_message_<SensorStateResponse>(msg, 25);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<SwitchStateResponse>(msg, 26);
|
return this->send_message_<SwitchStateResponse>(msg, 26);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -141,58 +123,48 @@ bool APIServerConnectionBase::send_switch_state_response(const SwitchStateRespon
|
|||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
|
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());
|
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
||||||
const SubscribeHomeAssistantStateResponse &msg) {
|
const SubscribeHomeAssistantStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
||||||
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<GetTimeRequest>(msg, 36);
|
return this->send_message_<GetTimeRequest>(msg, 36);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<GetTimeResponse>(msg, 37);
|
return this->send_message_<GetTimeResponse>(msg, 37);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
||||||
}
|
}
|
||||||
#ifdef USE_ESP32_CAMERA
|
#ifdef USE_ESP32_CAMERA
|
||||||
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP32_CAMERA
|
#ifdef USE_ESP32_CAMERA
|
||||||
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<CameraImageResponse>(msg, 44);
|
return this->send_message_<CameraImageResponse>(msg, 44);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -201,14 +173,12 @@ bool APIServerConnectionBase::send_camera_image_response(const CameraImageRespon
|
|||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<ClimateStateResponse>(msg, 47);
|
return this->send_message_<ClimateStateResponse>(msg, 47);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// This file was automatically generated with a tool.
|
||||||
|
// See scripts/api_protobuf/api_protobuf.py
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
template<typename T> void add_variable(std::string key, T value) {
|
template<typename T> void add_variable(std::string key, T value) {
|
||||||
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantServiceResponse resp;
|
||||||
resp.service = this->service_.value(x...);
|
resp.service = this->service_.value(x...);
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
|||||||
error = true;
|
error = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
|
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
|
||||||
(uint32_t(buffer[i + 3]) << 24);
|
|
||||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
|
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,7 +266,6 @@ class ProtoService {
|
|||||||
virtual ProtoWriteBuffer create_buffer() = 0;
|
virtual ProtoWriteBuffer create_buffer() = 0;
|
||||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 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;
|
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||||
virtual void set_nodelay(bool nodelay) = 0;
|
|
||||||
|
|
||||||
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
||||||
auto buffer = this->create_buffer();
|
auto buffer = this->create_buffer();
|
||||||
|
|||||||
@@ -1,33 +1,43 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.const import CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \
|
from esphome.const import (
|
||||||
CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \
|
CONF_INDOOR,
|
||||||
CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE
|
CONF_WATCHDOG_THRESHOLD,
|
||||||
|
CONF_NOISE_LEVEL,
|
||||||
|
CONF_SPIKE_REJECTION,
|
||||||
|
CONF_LIGHTNING_THRESHOLD,
|
||||||
|
CONF_MASK_DISTURBER,
|
||||||
|
CONF_DIV_RATIO,
|
||||||
|
CONF_CAPACITANCE,
|
||||||
|
)
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine
|
||||||
|
|
||||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONF_AS3935_ID = 'as3935_id'
|
CONF_AS3935_ID = "as3935_id"
|
||||||
|
|
||||||
as3935_ns = cg.esphome_ns.namespace('as3935')
|
as3935_ns = cg.esphome_ns.namespace("as3935")
|
||||||
AS3935 = as3935_ns.class_('AS3935Component', cg.Component)
|
AS3935 = as3935_ns.class_("AS3935Component", cg.Component)
|
||||||
|
|
||||||
CONF_IRQ_PIN = 'irq_pin'
|
CONF_IRQ_PIN = "irq_pin"
|
||||||
AS3935_SCHEMA = cv.Schema({
|
AS3935_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(): cv.declare_id(AS3935),
|
{
|
||||||
cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
|
cv.GenerateID(): cv.declare_id(AS3935),
|
||||||
|
cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_INDOOR, default=True): cv.boolean,
|
cv.Optional(CONF_INDOOR, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
|
cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
|
||||||
cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
|
cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
|
||||||
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
||||||
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
|
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(
|
||||||
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
1, 5, 9, 16, int=True
|
||||||
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 22, 64, 128, int=True),
|
),
|
||||||
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
||||||
})
|
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
|
||||||
|
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
@coroutine
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ void AS3935Component::setup() {
|
|||||||
void AS3935Component::dump_config() {
|
void AS3935Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "AS3935:");
|
ESP_LOGCONFIG(TAG, "AS3935:");
|
||||||
LOG_PIN(" Interrupt Pin: ", this->irq_pin_);
|
LOG_PIN(" Interrupt Pin: ", this->irq_pin_);
|
||||||
|
LOG_BINARY_SENSOR(" ", "Thunder alert", this->thunder_alert_binary_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Lightning energy", this->energy_sensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
|
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import binary_sensor
|
from esphome.components import binary_sensor
|
||||||
from . import AS3935, CONF_AS3935_ID
|
from . import AS3935, CONF_AS3935_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['as3935']
|
DEPENDENCIES = ["as3935"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
{
|
||||||
})
|
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor
|
||||||
from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \
|
from esphome.const import (
|
||||||
UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH
|
CONF_DISTANCE,
|
||||||
|
CONF_LIGHTNING_ENERGY,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
UNIT_KILOMETER,
|
||||||
|
UNIT_EMPTY,
|
||||||
|
ICON_SIGNAL_DISTANCE_VARIANT,
|
||||||
|
ICON_FLASH,
|
||||||
|
)
|
||||||
from . import AS3935, CONF_AS3935_ID
|
from . import AS3935, CONF_AS3935_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['as3935']
|
DEPENDENCIES = ["as3935"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
{
|
||||||
cv.Optional(CONF_DISTANCE):
|
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||||
sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1),
|
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||||
cv.Optional(CONF_LIGHTNING_ENERGY):
|
UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY
|
||||||
sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1),
|
),
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
|
||||||
|
UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
@@ -27,4 +38,4 @@ def to_code(config):
|
|||||||
if CONF_LIGHTNING_ENERGY in config:
|
if CONF_LIGHTNING_ENERGY in config:
|
||||||
conf = config[CONF_LIGHTNING_ENERGY]
|
conf = config[CONF_LIGHTNING_ENERGY]
|
||||||
lightning_energy_sensor = yield sensor.new_sensor(conf)
|
lightning_energy_sensor = yield sensor.new_sensor(conf)
|
||||||
cg.add(hub.set_distance_sensor(lightning_energy_sensor))
|
cg.add(hub.set_energy_sensor(lightning_energy_sensor))
|
||||||
|
|||||||
@@ -3,15 +3,21 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import as3935, i2c
|
from esphome.components import as3935, i2c
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
AUTO_LOAD = ['as3935']
|
AUTO_LOAD = ["as3935"]
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c')
|
as3935_i2c_ns = cg.esphome_ns.namespace("as3935_i2c")
|
||||||
I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice)
|
I2CAS3935 = as3935_i2c_ns.class_("I2CAS3935Component", as3935.AS3935, i2c.I2CDevice)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
as3935.AS3935_SCHEMA.extend(
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03)))
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(0x03))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
|||||||
@@ -3,15 +3,21 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import as3935, spi
|
from esphome.components import as3935, spi
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
AUTO_LOAD = ['as3935']
|
AUTO_LOAD = ["as3935"]
|
||||||
DEPENDENCIES = ['spi']
|
DEPENDENCIES = ["spi"]
|
||||||
|
|
||||||
as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
|
as3935_spi_ns = cg.esphome_ns.namespace("as3935_spi")
|
||||||
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
|
SPIAS3935 = as3935_spi_ns.class_("SPIAS3935Component", as3935.AS3935, spi.SPIDevice)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.GenerateID(): cv.declare_id(SPIAS3935)
|
as3935.AS3935_SCHEMA.extend(
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.SPI_DEVICE_SCHEMA))
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(SPIAS3935),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(spi.spi_device_schema(cs_pin_required=True))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(200.0)
|
@coroutine_with_priority(200.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
# https://github.com/OttoWinter/AsyncTCP/blob/master/library.json
|
# https://github.com/esphome/AsyncTCP/blob/master/library.json
|
||||||
cg.add_library('AsyncTCP-esphome', '1.1.1')
|
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
|
||||||
elif CORE.is_esp8266:
|
elif CORE.is_esp8266:
|
||||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||||
cg.add_library('ESPAsyncTCP-esphome', '1.2.2')
|
cg.add_library("ESPAsyncTCP-esphome", "1.2.3")
|
||||||
|
|||||||
0
esphome/components/atc_mithermometer/__init__.py
Normal file
0
esphome/components/atc_mithermometer/__init__.py
Normal file
137
esphome/components/atc_mithermometer/atc_mithermometer.cpp
Normal file
137
esphome/components/atc_mithermometer/atc_mithermometer.cpp
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#include "atc_mithermometer.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace atc_mithermometer {
|
||||||
|
|
||||||
|
static const char *TAG = "atc_mithermometer";
|
||||||
|
|
||||||
|
void ATCMiThermometer::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "ATC MiThermometer");
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||||
|
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||||
|
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||||
|
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||||
|
if (device.address_uint64() != this->address_) {
|
||||||
|
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
for (auto &service_data : device.get_service_datas()) {
|
||||||
|
auto res = parse_header(service_data);
|
||||||
|
if (res->is_duplicate) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(parse_message(service_data.data, *res))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(report_results(res, device.address_str()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (res->temperature.has_value() && this->temperature_ != nullptr)
|
||||||
|
this->temperature_->publish_state(*res->temperature);
|
||||||
|
if (res->humidity.has_value() && this->humidity_ != nullptr)
|
||||||
|
this->humidity_->publish_state(*res->humidity);
|
||||||
|
if (res->battery_level.has_value() && this->battery_level_ != nullptr)
|
||||||
|
this->battery_level_->publish_state(*res->battery_level);
|
||||||
|
if (res->battery_voltage.has_value() && this->battery_voltage_ != nullptr)
|
||||||
|
this->battery_voltage_->publish_state(*res->battery_voltage);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) {
|
||||||
|
ParseResult result;
|
||||||
|
if (!service_data.uuid.contains(0x1A, 0x18)) {
|
||||||
|
ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto raw = service_data.data;
|
||||||
|
|
||||||
|
static uint8_t last_frame_count = 0;
|
||||||
|
if (last_frame_count == raw[12]) {
|
||||||
|
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count));
|
||||||
|
result.is_duplicate = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
last_frame_count = raw[12];
|
||||||
|
result.is_duplicate = false;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseResult &result) {
|
||||||
|
// Byte 0-5 mac in correct order
|
||||||
|
// Byte 6-7 Temperature in uint16
|
||||||
|
// Byte 8 Humidity in percent
|
||||||
|
// Byte 9 Battery in percent
|
||||||
|
// Byte 10-11 Battery in mV uint16_t
|
||||||
|
// Byte 12 frame packet counter
|
||||||
|
|
||||||
|
const uint8_t *data = message.data();
|
||||||
|
const int data_length = 13;
|
||||||
|
|
||||||
|
if (message.size() != data_length) {
|
||||||
|
ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
|
||||||
|
const int16_t temperature = uint16_t(data[7]) | (uint16_t(data[6]) << 8);
|
||||||
|
result.temperature = temperature / 10.0f;
|
||||||
|
|
||||||
|
// humidity, 1 byte, 8-bit unsigned integer, 1.0 %
|
||||||
|
result.humidity = data[8];
|
||||||
|
|
||||||
|
// battery, 1 byte, 8-bit unsigned integer, 1.0 %
|
||||||
|
result.battery_level = data[9];
|
||||||
|
|
||||||
|
// battery, 2 bytes, 16-bit unsigned integer, 0.001 V
|
||||||
|
const int16_t battery_voltage = uint16_t(data[11]) | (uint16_t(data[10]) << 8);
|
||||||
|
result.battery_voltage = battery_voltage / 1.0e3f;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ATCMiThermometer::report_results(const optional<ParseResult> &result, const std::string &address) {
|
||||||
|
if (!result.has_value()) {
|
||||||
|
ESP_LOGVV(TAG, "report_results(): no results available.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str());
|
||||||
|
|
||||||
|
if (result->temperature.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature);
|
||||||
|
}
|
||||||
|
if (result->humidity.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Humidity: %.0f %%", *result->humidity);
|
||||||
|
}
|
||||||
|
if (result->battery_level.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Battery Level: %.0f %%", *result->battery_level);
|
||||||
|
}
|
||||||
|
if (result->battery_voltage.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Battery Voltage: %.3f V", *result->battery_voltage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace atc_mithermometer
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
||||||
48
esphome/components/atc_mithermometer/atc_mithermometer.h
Normal file
48
esphome/components/atc_mithermometer/atc_mithermometer.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace atc_mithermometer {
|
||||||
|
|
||||||
|
struct ParseResult {
|
||||||
|
optional<float> temperature;
|
||||||
|
optional<float> humidity;
|
||||||
|
optional<float> battery_level;
|
||||||
|
optional<float> battery_voltage;
|
||||||
|
bool is_duplicate;
|
||||||
|
int raw_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||||
|
public:
|
||||||
|
void set_address(uint64_t address) { address_ = address; };
|
||||||
|
|
||||||
|
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||||
|
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||||
|
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
|
||||||
|
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint64_t address_;
|
||||||
|
sensor::Sensor *temperature_{nullptr};
|
||||||
|
sensor::Sensor *humidity_{nullptr};
|
||||||
|
sensor::Sensor *battery_level_{nullptr};
|
||||||
|
sensor::Sensor *battery_voltage_{nullptr};
|
||||||
|
|
||||||
|
optional<ParseResult> parse_header(const esp32_ble_tracker::ServiceData &service_data);
|
||||||
|
bool parse_message(const std::vector<uint8_t> &message, ParseResult &result);
|
||||||
|
bool report_results(const optional<ParseResult> &result, const std::string &address);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace atc_mithermometer
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
||||||
72
esphome/components/atc_mithermometer/sensor.py
Normal file
72
esphome/components/atc_mithermometer/sensor.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor, esp32_ble_tracker
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_BATTERY_LEVEL,
|
||||||
|
CONF_BATTERY_VOLTAGE,
|
||||||
|
CONF_MAC_ADDRESS,
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
CONF_ID,
|
||||||
|
DEVICE_CLASS_BATTERY,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
UNIT_VOLT,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@ahpohl"]
|
||||||
|
|
||||||
|
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||||
|
|
||||||
|
atc_mithermometer_ns = cg.esphome_ns.namespace("atc_mithermometer")
|
||||||
|
ATCMiThermometer = atc_mithermometer_ns.class_(
|
||||||
|
"ATCMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
||||||
|
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
|
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, 0, DEVICE_CLASS_HUMIDITY
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||||
|
UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||||
|
UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||||
|
|
||||||
|
if CONF_TEMPERATURE in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||||
|
cg.add(var.set_temperature(sens))
|
||||||
|
if CONF_HUMIDITY in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||||
|
cg.add(var.set_humidity(sens))
|
||||||
|
if CONF_BATTERY_LEVEL in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||||
|
cg.add(var.set_battery_level(sens))
|
||||||
|
if CONF_BATTERY_VOLTAGE in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
|
||||||
|
cg.add(var.set_battery_voltage(sens))
|
||||||
@@ -40,19 +40,63 @@ void ATM90E32Component::update() {
|
|||||||
if (this->phase_[2].power_sensor_ != nullptr) {
|
if (this->phase_[2].power_sensor_ != nullptr) {
|
||||||
this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_());
|
this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_());
|
||||||
}
|
}
|
||||||
|
if (this->phase_[0].reactive_power_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].reactive_power_sensor_->publish_state(this->get_reactive_power_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].reactive_power_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].reactive_power_sensor_->publish_state(this->get_reactive_power_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].reactive_power_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].reactive_power_sensor_->publish_state(this->get_reactive_power_c_());
|
||||||
|
}
|
||||||
|
if (this->phase_[0].power_factor_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].power_factor_sensor_->publish_state(this->get_power_factor_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].power_factor_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].power_factor_sensor_->publish_state(this->get_power_factor_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].power_factor_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
|
||||||
|
}
|
||||||
|
if (this->phase_[0].forward_active_energy_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].forward_active_energy_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].forward_active_energy_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_c_());
|
||||||
|
}
|
||||||
|
if (this->phase_[0].reverse_active_energy_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].reverse_active_energy_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].reverse_active_energy_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_c_());
|
||||||
|
}
|
||||||
if (this->freq_sensor_ != nullptr) {
|
if (this->freq_sensor_ != nullptr) {
|
||||||
this->freq_sensor_->publish_state(this->get_frequency_());
|
this->freq_sensor_->publish_state(this->get_frequency_());
|
||||||
}
|
}
|
||||||
|
if (this->chip_temperature_sensor_ != nullptr) {
|
||||||
|
this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_());
|
||||||
|
}
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::setup() {
|
void ATM90E32Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up ATM90E32Component...");
|
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
|
|
||||||
uint16_t mmode0 = 0x185;
|
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||||
if (line_freq_ == 60) {
|
if (line_freq_ == 60) {
|
||||||
mmode0 |= 1 << 12;
|
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_phases_ == 2) {
|
||||||
|
mmode0 |= 1 << 8; // sets 8th bit to 1, 3P3W
|
||||||
|
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
||||||
}
|
}
|
||||||
|
|
||||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||||
@@ -63,13 +107,15 @@ void ATM90E32Component::setup() {
|
|||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000
|
||||||
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0x0A55); // ZX2, ZX1, ZX0 pin config
|
this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default)
|
||||||
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
|
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // ZX2, ZX1, ZX0 pin config
|
||||||
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
|
||||||
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x0AFC); // Active Startup Power Threshold = 50%
|
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
||||||
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x0AEC); // Reactive Startup Power Threshold = 50%
|
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
|
||||||
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x00BC); // Active Phase Threshold = 10%
|
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||||
|
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
|
||||||
|
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
|
||||||
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain
|
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain
|
||||||
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain
|
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain
|
||||||
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain
|
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain
|
||||||
@@ -89,13 +135,26 @@ void ATM90E32Component::dump_config() {
|
|||||||
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_);
|
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_);
|
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[0].forward_active_energy_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[0].reverse_active_energy_sensor_);
|
||||||
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[1].forward_active_energy_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[1].reverse_active_energy_sensor_);
|
||||||
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_)
|
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[2].forward_active_energy_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[2].reverse_active_energy_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||||
}
|
}
|
||||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; }
|
float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
@@ -180,9 +239,61 @@ float ATM90E32Component::get_active_power_c_() {
|
|||||||
int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB);
|
int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB);
|
||||||
return val * 0.00032f;
|
return val * 0.00032f;
|
||||||
}
|
}
|
||||||
|
float ATM90E32Component::get_reactive_power_a_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_QMEANA, ATM90E32_REGISTER_QMEANALSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_reactive_power_b_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_QMEANB, ATM90E32_REGISTER_QMEANBLSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_reactive_power_c_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_QMEANC, ATM90E32_REGISTER_QMEANCLSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_power_factor_a_() {
|
||||||
|
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANA);
|
||||||
|
return (float) pf / 1000;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_power_factor_b_() {
|
||||||
|
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANB);
|
||||||
|
return (float) pf / 1000;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_power_factor_c_() {
|
||||||
|
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
|
||||||
|
return (float) pf / 1000;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_forward_active_energy_a_() {
|
||||||
|
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYA);
|
||||||
|
return (float) val * 10 / 3200; // convert register value to WattHours
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_forward_active_energy_b_() {
|
||||||
|
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYB);
|
||||||
|
return (float) val * 10 / 3200;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_forward_active_energy_c_() {
|
||||||
|
uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGYC);
|
||||||
|
return (float) val * 10 / 3200;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_reverse_active_energy_a_() {
|
||||||
|
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYA);
|
||||||
|
return (float) val * 10 / 3200;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_reverse_active_energy_b_() {
|
||||||
|
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYB);
|
||||||
|
return (float) val * 10 / 3200;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_reverse_active_energy_c_() {
|
||||||
|
uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGYC);
|
||||||
|
return (float) val * 10 / 3200;
|
||||||
|
}
|
||||||
float ATM90E32Component::get_frequency_() {
|
float ATM90E32Component::get_frequency_() {
|
||||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||||
return (float) freq / 100;
|
return (float) freq / 100;
|
||||||
}
|
}
|
||||||
|
float ATM90E32Component::get_chip_temperature_() {
|
||||||
|
uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP);
|
||||||
|
return (float) ctemp;
|
||||||
|
}
|
||||||
} // namespace atm90e32
|
} // namespace atm90e32
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -19,11 +19,23 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
|
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
|
||||||
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
||||||
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
||||||
|
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
|
||||||
|
void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) {
|
||||||
|
this->phase_[phase].forward_active_energy_sensor_ = obj;
|
||||||
|
}
|
||||||
|
void set_reverse_active_energy_sensor(int phase, sensor::Sensor *obj) {
|
||||||
|
this->phase_[phase].reverse_active_energy_sensor_ = obj;
|
||||||
|
}
|
||||||
|
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
|
||||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
||||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||||
|
|
||||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||||
|
void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
|
||||||
|
chip_temperature_sensor_ = chip_temperature_sensor;
|
||||||
|
}
|
||||||
void set_line_freq(int freq) { line_freq_ = freq; }
|
void set_line_freq(int freq) { line_freq_ = freq; }
|
||||||
|
void set_current_phases(int phases) { current_phases_ = phases; }
|
||||||
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -40,18 +52,37 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
float get_active_power_a_();
|
float get_active_power_a_();
|
||||||
float get_active_power_b_();
|
float get_active_power_b_();
|
||||||
float get_active_power_c_();
|
float get_active_power_c_();
|
||||||
|
float get_reactive_power_a_();
|
||||||
|
float get_reactive_power_b_();
|
||||||
|
float get_reactive_power_c_();
|
||||||
|
float get_power_factor_a_();
|
||||||
|
float get_power_factor_b_();
|
||||||
|
float get_power_factor_c_();
|
||||||
|
float get_forward_active_energy_a_();
|
||||||
|
float get_forward_active_energy_b_();
|
||||||
|
float get_forward_active_energy_c_();
|
||||||
|
float get_reverse_active_energy_a_();
|
||||||
|
float get_reverse_active_energy_b_();
|
||||||
|
float get_reverse_active_energy_c_();
|
||||||
float get_frequency_();
|
float get_frequency_();
|
||||||
|
float get_chip_temperature_();
|
||||||
|
|
||||||
struct ATM90E32Phase {
|
struct ATM90E32Phase {
|
||||||
uint16_t volt_gain_{41820};
|
uint16_t volt_gain_{7305};
|
||||||
uint16_t ct_gain_{25498};
|
uint16_t ct_gain_{27961};
|
||||||
sensor::Sensor *voltage_sensor_{nullptr};
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
sensor::Sensor *current_sensor_{nullptr};
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
sensor::Sensor *power_sensor_{nullptr};
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
|
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||||
|
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||||
|
sensor::Sensor *forward_active_energy_sensor_{nullptr};
|
||||||
|
sensor::Sensor *reverse_active_energy_sensor_{nullptr};
|
||||||
} phase_[3];
|
} phase_[3];
|
||||||
sensor::Sensor *freq_sensor_{nullptr};
|
sensor::Sensor *freq_sensor_{nullptr};
|
||||||
|
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||||
uint16_t pga_gain_{0x15};
|
uint16_t pga_gain_{0x15};
|
||||||
int line_freq_{60};
|
int line_freq_{60};
|
||||||
|
int current_phases_{3};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace atm90e32
|
} // namespace atm90e32
|
||||||
|
|||||||
@@ -234,12 +234,12 @@ static const uint16_t ATM90E32_REGISTER_IRMSBLSB = 0xEE; // Lower Word (B RMS
|
|||||||
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current)
|
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current)
|
||||||
|
|
||||||
/* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/
|
/* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNUA = 0xF1; // A Voltage THD+N
|
static const uint16_t ATM90E32_REGISTER_UPEAKA = 0xF1; // A Voltage Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNUB = 0xF2; // B Voltage THD+N
|
static const uint16_t ATM90E32_REGISTER_UPEAKB = 0xF2; // B Voltage Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNUC = 0xF3; // C Voltage THD+N
|
static const uint16_t ATM90E32_REGISTER_UPEAKC = 0xF3; // C Voltage Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNIA = 0xF5; // A Current THD+N
|
static const uint16_t ATM90E32_REGISTER_IPEAKA = 0xF5; // A Current Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNIB = 0xF6; // B Current THD+N
|
static const uint16_t ATM90E32_REGISTER_IPEAKB = 0xF6; // B Current Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNIC = 0xF7; // C Current THD+N
|
static const uint16_t ATM90E32_REGISTER_IPEAKC = 0xF7; // C Current Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency
|
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency
|
||||||
static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle
|
static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle
|
||||||
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle
|
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle
|
||||||
|
|||||||
@@ -1,48 +1,116 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, spi
|
from esphome.components import sensor, spi
|
||||||
from esphome.const import \
|
from esphome.const import (
|
||||||
CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_FREQUENCY, \
|
CONF_ID,
|
||||||
ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_HERTZ, ICON_CURRENT_AC
|
CONF_VOLTAGE,
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_POWER,
|
||||||
|
CONF_POWER_FACTOR,
|
||||||
|
CONF_FREQUENCY,
|
||||||
|
CONF_FORWARD_ACTIVE_ENERGY,
|
||||||
|
CONF_REVERSE_ACTIVE_ENERGY,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_EMPTY,
|
||||||
|
ICON_LIGHTBULB,
|
||||||
|
ICON_CURRENT_AC,
|
||||||
|
UNIT_HERTZ,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_WATT,
|
||||||
|
UNIT_EMPTY,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_VOLT_AMPS_REACTIVE,
|
||||||
|
UNIT_WATT_HOURS,
|
||||||
|
)
|
||||||
|
|
||||||
CONF_PHASE_A = 'phase_a'
|
CONF_PHASE_A = "phase_a"
|
||||||
CONF_PHASE_B = 'phase_b'
|
CONF_PHASE_B = "phase_b"
|
||||||
CONF_PHASE_C = 'phase_c'
|
CONF_PHASE_C = "phase_c"
|
||||||
|
|
||||||
CONF_LINE_FREQUENCY = 'line_frequency'
|
CONF_REACTIVE_POWER = "reactive_power"
|
||||||
CONF_GAIN_PGA = 'gain_pga'
|
CONF_LINE_FREQUENCY = "line_frequency"
|
||||||
CONF_GAIN_VOLTAGE = 'gain_voltage'
|
CONF_CHIP_TEMPERATURE = "chip_temperature"
|
||||||
CONF_GAIN_CT = 'gain_ct'
|
CONF_GAIN_PGA = "gain_pga"
|
||||||
|
CONF_CURRENT_PHASES = "current_phases"
|
||||||
|
CONF_GAIN_VOLTAGE = "gain_voltage"
|
||||||
|
CONF_GAIN_CT = "gain_ct"
|
||||||
LINE_FREQS = {
|
LINE_FREQS = {
|
||||||
'50HZ': 50,
|
"50HZ": 50,
|
||||||
'60HZ': 60,
|
"60HZ": 60,
|
||||||
|
}
|
||||||
|
CURRENT_PHASES = {
|
||||||
|
"2": 2,
|
||||||
|
"3": 3,
|
||||||
}
|
}
|
||||||
PGA_GAINS = {
|
PGA_GAINS = {
|
||||||
'1X': 0x0,
|
"1X": 0x0,
|
||||||
'2X': 0x15,
|
"2X": 0x15,
|
||||||
'4X': 0x2A,
|
"4X": 0x2A,
|
||||||
}
|
}
|
||||||
|
|
||||||
atm90e32_ns = cg.esphome_ns.namespace('atm90e32')
|
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
|
||||||
ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice)
|
ATM90E32Component = atm90e32_ns.class_(
|
||||||
|
"ATM90E32Component", cg.PollingComponent, spi.SPIDevice
|
||||||
|
)
|
||||||
|
|
||||||
ATM90E32_PHASE_SCHEMA = cv.Schema({
|
ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2),
|
{
|
||||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2),
|
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE
|
||||||
cv.Optional(CONF_GAIN_VOLTAGE, default=41820): cv.uint16_t,
|
),
|
||||||
cv.Optional(CONF_GAIN_CT, default=25498): cv.uint16_t,
|
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||||
})
|
UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||||
|
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
|
||||||
|
UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||||
|
UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||||
|
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||||
|
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||||
|
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = (
|
||||||
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
cv.Schema(
|
||||||
cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
|
{
|
||||||
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
||||||
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
|
||||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1),
|
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
||||||
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
||||||
cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True),
|
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA)
|
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
|
||||||
|
),
|
||||||
|
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
||||||
|
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
|
||||||
|
CURRENT_PHASES, upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(spi.spi_device_schema())
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
@@ -65,8 +133,24 @@ def to_code(config):
|
|||||||
if CONF_POWER in conf:
|
if CONF_POWER in conf:
|
||||||
sens = yield sensor.new_sensor(conf[CONF_POWER])
|
sens = yield sensor.new_sensor(conf[CONF_POWER])
|
||||||
cg.add(var.set_power_sensor(i, sens))
|
cg.add(var.set_power_sensor(i, sens))
|
||||||
|
if CONF_REACTIVE_POWER in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_REACTIVE_POWER])
|
||||||
|
cg.add(var.set_reactive_power_sensor(i, sens))
|
||||||
|
if CONF_POWER_FACTOR in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_POWER_FACTOR])
|
||||||
|
cg.add(var.set_power_factor_sensor(i, sens))
|
||||||
|
if CONF_FORWARD_ACTIVE_ENERGY in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_FORWARD_ACTIVE_ENERGY])
|
||||||
|
cg.add(var.set_forward_active_energy_sensor(i, sens))
|
||||||
|
if CONF_REVERSE_ACTIVE_ENERGY in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY])
|
||||||
|
cg.add(var.set_reverse_active_energy_sensor(i, sens))
|
||||||
if CONF_FREQUENCY in config:
|
if CONF_FREQUENCY in config:
|
||||||
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
|
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
|
||||||
cg.add(var.set_freq_sensor(sens))
|
cg.add(var.set_freq_sensor(sens))
|
||||||
|
if CONF_CHIP_TEMPERATURE in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
|
||||||
|
cg.add(var.set_chip_temperature_sensor(sens))
|
||||||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||||
|
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
|
||||||
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
||||||
|
|||||||
0
esphome/components/b_parasite/__init__.py
Normal file
0
esphome/components/b_parasite/__init__.py
Normal file
82
esphome/components/b_parasite/b_parasite.cpp
Normal file
82
esphome/components/b_parasite/b_parasite.cpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#include "b_parasite.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace b_parasite {
|
||||||
|
|
||||||
|
static const char* TAG = "b_parasite";
|
||||||
|
|
||||||
|
void BParasite::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "b_parasite");
|
||||||
|
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||||
|
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||||
|
LOG_SENSOR(" ", "Soil Moisture", this->soil_moisture_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice& device) {
|
||||||
|
if (device.address_uint64() != address_) {
|
||||||
|
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||||
|
const auto& service_datas = device.get_service_datas();
|
||||||
|
if (service_datas.size() != 1) {
|
||||||
|
ESP_LOGE(TAG, "Unexpected service_datas size (%d)", service_datas.size());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& service_data = service_datas[0];
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Service data:");
|
||||||
|
for (const uint8_t byte : service_data.data) {
|
||||||
|
ESP_LOGVV(TAG, "0x%02x", byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& data = service_data.data;
|
||||||
|
|
||||||
|
// Counter for deduplicating messages.
|
||||||
|
uint8_t counter = data[1] & 0x0f;
|
||||||
|
if (last_processed_counter_ == counter) {
|
||||||
|
ESP_LOGVV(TAG, "Skipping already processed counter (%u)", counter);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery voltage in millivolts.
|
||||||
|
uint16_t battery_millivolt = data[2] << 8 | data[3];
|
||||||
|
float battery_voltage = battery_millivolt / 1000.0f;
|
||||||
|
|
||||||
|
// Temperature in 1000 * Celcius.
|
||||||
|
uint16_t temp_millicelcius = data[4] << 8 | data[5];
|
||||||
|
float temp_celcius = temp_millicelcius / 1000.0f;
|
||||||
|
|
||||||
|
// Relative air humidity in the range [0, 2^16).
|
||||||
|
uint16_t humidity = data[6] << 8 | data[7];
|
||||||
|
float humidity_percent = (100.0f * humidity) / (1 << 16);
|
||||||
|
|
||||||
|
// Relative soil moisture in [0 - 2^16).
|
||||||
|
uint16_t soil_moisture = data[8] << 8 | data[9];
|
||||||
|
float moisture_percent = (100.0f * soil_moisture) / (1 << 16);
|
||||||
|
|
||||||
|
if (battery_voltage_ != nullptr) {
|
||||||
|
battery_voltage_->publish_state(battery_voltage);
|
||||||
|
}
|
||||||
|
if (temperature_ != nullptr) {
|
||||||
|
temperature_->publish_state(temp_celcius);
|
||||||
|
}
|
||||||
|
if (humidity_ != nullptr) {
|
||||||
|
humidity_->publish_state(humidity_percent);
|
||||||
|
}
|
||||||
|
if (soil_moisture_ != nullptr) {
|
||||||
|
soil_moisture_->publish_state(moisture_percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_processed_counter_ = counter;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace b_parasite
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // ARDUINO_ARCH_ESP32
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user