mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
Compare commits
2702 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b5927322e6 | ||
![]() |
1cf4107e1c | ||
![]() |
c12408326c | ||
![]() |
4434e59e5a | ||
![]() |
45180d98f6 | ||
![]() |
44494ad18e | ||
![]() |
1447536906 | ||
![]() |
27ec517084 | ||
![]() |
ce1f034bac | ||
![]() |
f1f96f16e9 | ||
![]() |
7665e9b076 | ||
![]() |
227d94f38d | ||
![]() |
b724ae9e0e | ||
![]() |
df6cc14201 | ||
![]() |
d981d7859d | ||
![]() |
0f1ec515c1 | ||
![]() |
78e18256f7 | ||
![]() |
4899dfe642 | ||
![]() |
d6b6e94059 | ||
![]() |
310355a00b | ||
![]() |
8cf26d6f3c | ||
![]() |
b15a10f905 | ||
![]() |
5dcf1debd7 | ||
![]() |
9b57e1ac1d | ||
![]() |
68683e3a50 | ||
![]() |
d83324c4dc | ||
![]() |
ecde4c1d2d | ||
![]() |
bd8e470726 | ||
![]() |
d2913fe627 | ||
![]() |
43acc7dc2c | ||
![]() |
e2a16d758b | ||
![]() |
17ea0efb08 | ||
![]() |
2fbd33267e | ||
![]() |
cf3977f088 | ||
![]() |
d20d4947ac | ||
![]() |
7810ad40d7 | ||
![]() |
7e1e799b3a | ||
![]() |
dfafc41ce6 | ||
![]() |
e460792c43 | ||
![]() |
a9dc491a54 | ||
![]() |
ac6693f177 | ||
![]() |
c6742117d3 | ||
![]() |
b5c47b9669 | ||
![]() |
40df3aa55e | ||
![]() |
393ca64d70 | ||
![]() |
d3627f0972 | ||
![]() |
124ab31f22 | ||
![]() |
1b66fa5004 | ||
![]() |
9494c27ad8 | ||
![]() |
3facfa5c21 | ||
![]() |
93ddce2e79 | ||
![]() |
0bf6e21e1a | ||
![]() |
6b7b076875 | ||
![]() |
8d6ffb9169 | ||
![]() |
e95d6041d8 | ||
![]() |
0554b06b7e | ||
![]() |
e3d9c44bdc | ||
![]() |
e847766514 | ||
![]() |
d4a8df04b8 | ||
![]() |
4af4649e23 | ||
![]() |
8bcddef39d | ||
![]() |
4ac96ccea2 | ||
![]() |
3c5de77ae9 | ||
![]() |
a2925b1d37 | ||
![]() |
73748e9e20 | ||
![]() |
034b47c23a | ||
![]() |
3e017efa30 | ||
![]() |
aca56fcdcc | ||
![]() |
75c9823899 | ||
![]() |
c8c0bd3351 | ||
![]() |
e1cdeb7c8f | ||
![]() |
7f97f42552 | ||
![]() |
aa7f3569ec | ||
![]() |
2d0a08442e | ||
![]() |
d2380756b2 | ||
![]() |
e778a445d9 | ||
![]() |
ded86493c2 | ||
![]() |
4d72eb42a5 | ||
![]() |
267f0587c6 | ||
![]() |
4a374a466a | ||
![]() |
b27a328d1e | ||
![]() |
d94e9d92ca | ||
![]() |
36c2e770bf | ||
![]() |
79040c116d | ||
![]() |
4aac76c549 | ||
![]() |
0ea97df1af | ||
![]() |
92e66a2764 | ||
![]() |
925e3cb6c9 | ||
![]() |
6757acba56 | ||
![]() |
5cc91cdd95 | ||
![]() |
2b41886819 | ||
![]() |
72c6efd6a0 | ||
![]() |
a1f1804112 | ||
![]() |
a8b1ceb4e9 | ||
![]() |
4fb0f7f8c6 | ||
![]() |
958cadeca8 | ||
![]() |
00f2655f1a | ||
![]() |
074f5029eb | ||
![]() |
615d591367 | ||
![]() |
e236c53f05 | ||
![]() |
10c7055b41 | ||
![]() |
a127e60e1b | ||
![]() |
66a3361e9d | ||
![]() |
13cfe11a19 | ||
![]() |
6d65671f92 | ||
![]() |
f2eafa8fbe | ||
![]() |
e4ca3b18cc | ||
![]() |
84698ae888 | ||
![]() |
fd6d6cfb6c | ||
![]() |
8cad9dfc83 | ||
![]() |
5e2f33fde5 | ||
![]() |
029ac75a04 | ||
![]() |
3aa5953cd9 | ||
![]() |
582d90ad72 | ||
![]() |
bbb0105c2f | ||
![]() |
37d17feecf | ||
![]() |
4bf5faf808 | ||
![]() |
ddedc1cd76 | ||
![]() |
1bb90f304c | ||
![]() |
efc6a8df35 | ||
![]() |
e35f90d6e4 | ||
![]() |
11518364a1 | ||
![]() |
05420291ce | ||
![]() |
442faf92c6 | ||
![]() |
62c68f4d60 | ||
![]() |
c301ae3645 | ||
![]() |
3d2d681a7b | ||
![]() |
a45646af1b | ||
![]() |
27185265f6 | ||
![]() |
a9b7d98194 | ||
![]() |
ed4a7210d3 | ||
![]() |
351ea04517 | ||
![]() |
86a8e1f4a6 | ||
![]() |
1cf3424ebe | ||
![]() |
a19f0c0db0 | ||
![]() |
530df91044 | ||
![]() |
c16c0b11cb | ||
![]() |
74556b28a8 | ||
![]() |
48340d41d6 | ||
![]() |
6306348379 | ||
![]() |
b1f1329cee | ||
![]() |
75dff1e102 | ||
![]() |
fe55f3a43d | ||
![]() |
657fd9d0d5 | ||
![]() |
1511a6ebcd | ||
![]() |
ecac26aeba | ||
![]() |
19bf9b1e36 | ||
![]() |
119a6920f2 | ||
![]() |
8237e13c44 | ||
![]() |
53b60ac817 | ||
![]() |
a18ab748fd | ||
![]() |
917488bbc3 | ||
![]() |
7e376ae952 | ||
![]() |
57a1c207c2 | ||
![]() |
50e8e92f0b | ||
![]() |
ff4fd497c4 | ||
![]() |
33b1a853b9 | ||
![]() |
f2df542cb1 | ||
![]() |
ecbbf2d3f4 | ||
![]() |
b76c7a0131 | ||
![]() |
0b0984f9a0 | ||
![]() |
9767856784 | ||
![]() |
c1f09684e6 | ||
![]() |
22b384363b | ||
![]() |
5b23331751 | ||
![]() |
7a2bb32843 | ||
![]() |
c0a4e07e5a | ||
![]() |
322158cccb | ||
![]() |
8db3b59e0f | ||
![]() |
1691976587 | ||
![]() |
60e6b4d21e | ||
![]() |
5750591df2 | ||
![]() |
0d50caa179 | ||
![]() |
8b06135b41 | ||
![]() |
a75da54455 | ||
![]() |
de7f6c3f5f | ||
![]() |
4245480656 | ||
![]() |
1824c8131e | ||
![]() |
573ea55187 | ||
![]() |
b48b5d6cc7 | ||
![]() |
4e9606d2e0 | ||
![]() |
78500fa933 | ||
![]() |
9c69b98a49 | ||
![]() |
e6d8ef98d3 | ||
![]() |
3f1af1690b | ||
![]() |
39af967433 | ||
![]() |
83b5e01a28 | ||
![]() |
1eacbd50fa | ||
![]() |
84374b6b1e | ||
![]() |
391316c9b5 | ||
![]() |
705c62ebd7 | ||
![]() |
cb520c00a5 | ||
![]() |
2f24138345 | ||
![]() |
96512b80cc | ||
![]() |
fcb9b51978 | ||
![]() |
9bf7c97775 | ||
![]() |
24bf3674f3 | ||
![]() |
f408f1a368 | ||
![]() |
7d8d563c62 | ||
![]() |
0a1f705fda | ||
![]() |
1952c1880b | ||
![]() |
c47dc09d34 | ||
![]() |
db3096c6e1 | ||
![]() |
b03967dac1 | ||
![]() |
bcae2596a6 | ||
![]() |
fc0347c86c | ||
![]() |
eef578f4b8 | ||
![]() |
d9563d4de1 | ||
![]() |
cc7e2bf8db | ||
![]() |
5d98e2923b | ||
![]() |
d4e232f267 | ||
![]() |
cc45945fcf | ||
![]() |
07197d12f6 | ||
![]() |
7b0a298497 | ||
![]() |
21679cf2ba | ||
![]() |
0c24d951ff | ||
![]() |
92e44b8238 | ||
![]() |
4be7cd12a1 | ||
![]() |
34387adbcd | ||
![]() |
dee4d0ccb7 | ||
![]() |
c6885c1bf4 | ||
![]() |
8b8efb57af | ||
![]() |
499e120aa4 | ||
![]() |
6f198a4736 | ||
![]() |
f500f448b1 | ||
![]() |
6ad9baa870 | ||
![]() |
f843925301 | ||
![]() |
4a3b628946 | ||
![]() |
4ffdc38cf5 | ||
![]() |
f83f1bff19 | ||
![]() |
9370ff3dfa | ||
![]() |
2053b02c61 | ||
![]() |
f34e797a0d | ||
![]() |
30a2fc1273 | ||
![]() |
48da5ef1c4 | ||
![]() |
7209dd8bae | ||
![]() |
ab736c89bb | ||
![]() |
6911639617 | ||
![]() |
dbbbba3cf8 | ||
![]() |
16e523ca68 | ||
![]() |
3b2bbd306f | ||
![]() |
54caed36f7 | ||
![]() |
dfcccda69e | ||
![]() |
de352c1609 | ||
![]() |
c30068fc97 | ||
![]() |
f28f712827 | ||
![]() |
b9720d0715 | ||
![]() |
47b3267ed4 | ||
![]() |
e16ba2adb5 | ||
![]() |
0a19b1e32c | ||
![]() |
bae9a950c0 | ||
![]() |
b63ade298f | ||
![]() |
f117d4f50a | ||
![]() |
6e4267b797 | ||
![]() |
2dd032475b | ||
![]() |
db0ed055dd | ||
![]() |
106c1bfac2 | ||
![]() |
eb2a0f45db | ||
![]() |
2b4fdd6c39 | ||
![]() |
3669320398 | ||
![]() |
d706f40ce1 | ||
![]() |
027284c29c | ||
![]() |
c55e01ff3f | ||
![]() |
a59ce7bfa2 | ||
![]() |
a6196267c9 | ||
![]() |
eb664b99ba | ||
![]() |
8414bb9a7a | ||
![]() |
ccef7c322f | ||
![]() |
120327866f | ||
![]() |
bc5c2d4eb4 | ||
![]() |
d5ff8f6117 | ||
![]() |
ad0d6f6337 | ||
![]() |
873de13b3d | ||
![]() |
56de8e5cc4 | ||
![]() |
73c82862cf | ||
![]() |
75573a3ed1 | ||
![]() |
1166d93805 | ||
![]() |
ac112a32c9 | ||
![]() |
cee45c1221 | ||
![]() |
fb56b5388e | ||
![]() |
ed42cefeee | ||
![]() |
9052947a71 | ||
![]() |
53e0af18fb | ||
![]() |
c5f59fad62 | ||
![]() |
b089a4ea80 | ||
![]() |
72b2943332 | ||
![]() |
4ec0ef7548 | ||
![]() |
25bc6761f6 | ||
![]() |
81b6562c25 | ||
![]() |
ae74189fc2 | ||
![]() |
555bba7698 | ||
![]() |
294901fbe9 | ||
![]() |
ec576bf9f9 | ||
![]() |
9273e3775b | ||
![]() |
ce5cedb466 | ||
![]() |
b184b01600 | ||
![]() |
81b4078871 | ||
![]() |
d067c8f80b | ||
![]() |
9e516efe10 | ||
![]() |
366e29439e | ||
![]() |
1c9c700d7f | ||
![]() |
b2e6b9d31f | ||
![]() |
7623f63846 | ||
![]() |
2bfaf9dce3 | ||
![]() |
5c2c1560bb | ||
![]() |
8975b4b3f6 | ||
![]() |
20da03f8c6 | ||
![]() |
ef26677b67 | ||
![]() |
91925b1826 | ||
![]() |
1f33ad037d | ||
![]() |
fef60e335e | ||
![]() |
195c78846f | ||
![]() |
0f9c956c04 | ||
![]() |
7258a82875 | ||
![]() |
c1f696c32a | ||
![]() |
f2b63d9c67 | ||
![]() |
7896a7783b | ||
![]() |
621771e1ee | ||
![]() |
2b032e8606 | ||
![]() |
5e1b724697 | ||
![]() |
e6db61c2f0 | ||
![]() |
c2e198311c | ||
![]() |
d874626662 | ||
![]() |
eead72333e | ||
![]() |
f7096ab78e | ||
![]() |
98f8feb625 | ||
![]() |
9944ca414e | ||
![]() |
719c212009 | ||
![]() |
65030e1c37 | ||
![]() |
3f88b63920 | ||
![]() |
70f1c71a9f | ||
![]() |
816df5ad47 | ||
![]() |
f7b1602adf | ||
![]() |
7e88eea532 | ||
![]() |
d1cdfd3b72 | ||
![]() |
d6a03d48f5 | ||
![]() |
d453b42b1a | ||
![]() |
7c19b961e2 | ||
![]() |
147b113b62 | ||
![]() |
d924702825 | ||
![]() |
e8784ba383 | ||
![]() |
e3a454d1a6 | ||
![]() |
392dc8b0db | ||
![]() |
2f62426f09 | ||
![]() |
58fda40389 | ||
![]() |
6a73699a38 | ||
![]() |
3bd6456fbe | ||
![]() |
608be4e050 | ||
![]() |
10f590324b | ||
![]() |
cb2d9e4bec | ||
![]() |
9e3ee28744 | ||
![]() |
472dcebf2c | ||
![]() |
39f0f748bf | ||
![]() |
9efe59a984 | ||
![]() |
fcb02af782 | ||
![]() |
9f30f53c6b | ||
![]() |
2f18ae00c5 | ||
![]() |
27a339fa12 | ||
![]() |
3aeef1afd4 | ||
![]() |
a45ee8f4ac | ||
![]() |
31b62d7dca | ||
![]() |
22f81475db | ||
![]() |
cc7cf73d59 | ||
![]() |
9682e60a25 | ||
![]() |
2c2e68123a | ||
![]() |
fcec7d45cb | ||
![]() |
7c8f502e7e | ||
![]() |
dc17c47634 | ||
![]() |
3d927c2f44 | ||
![]() |
0ae61410d2 | ||
![]() |
2455589f61 | ||
![]() |
c6afae0da5 | ||
![]() |
3155f02be6 | ||
![]() |
4fa0e860ad | ||
![]() |
8c122aa372 | ||
![]() |
5a0bf9fee9 | ||
![]() |
dc794918ed | ||
![]() |
02b15dbc4a | ||
![]() |
ed316b1ce3 | ||
![]() |
d7858f16c1 | ||
![]() |
291deb12ad | ||
![]() |
3e110681c9 | ||
![]() |
65fbfa2097 | ||
![]() |
16ebf9da4c | ||
![]() |
2c76381fcd | ||
![]() |
90683223dd | ||
![]() |
de79171815 | ||
![]() |
1554c5700e | ||
![]() |
5cf257b251 | ||
![]() |
04883e14f6 | ||
![]() |
0a649c184f | ||
![]() |
0e66c899ce | ||
![]() |
e7d236f939 | ||
![]() |
fae4d03473 | ||
![]() |
fd8b9fb028 | ||
![]() |
97bd3e7320 | ||
![]() |
dfca2f88d3 | ||
![]() |
8cad93de37 | ||
![]() |
bdf1813b3a | ||
![]() |
45b6c93f5f | ||
![]() |
e5b8dd7f2d | ||
![]() |
a1c8b8092b | ||
![]() |
109ca2406d | ||
![]() |
3a689112fd | ||
![]() |
40e0cd0f03 | ||
![]() |
bf4d3df906 | ||
![]() |
0e30c49e3f | ||
![]() |
e61a01f7bb | ||
![]() |
f8640cf2cd | ||
![]() |
4bcfeb6e33 | ||
![]() |
a5d4ca0f6d | ||
![]() |
85faecb2fd | ||
![]() |
991fc54994 | ||
![]() |
2de891dc32 | ||
![]() |
9865cb7f55 | ||
![]() |
f97252b93a | ||
![]() |
6124531479 | ||
![]() |
b8549d323c | ||
![]() |
01adece673 | ||
![]() |
0220934e4c | ||
![]() |
ca09693efa | ||
![]() |
e96d7483b3 | ||
![]() |
f2c4f018de | ||
![]() |
237c7dd169 | ||
![]() |
b781b8d77d | ||
![]() |
60b7d1c8a1 | ||
![]() |
8161222b33 | ||
![]() |
1000c4466f | ||
![]() |
60717b074e | ||
![]() |
c3fba97b4c | ||
![]() |
d93f35701f | ||
![]() |
702b60ce66 | ||
![]() |
f8ce597918 | ||
![]() |
22e0a944c8 | ||
![]() |
3a134ef009 | ||
![]() |
96e8cb66b6 | ||
![]() |
615288c151 | ||
![]() |
6153bcc6ad | ||
![]() |
e87edcc77a | ||
![]() |
a21c3e8e2d | ||
![]() |
d7576f67e8 | ||
![]() |
138de643a2 | ||
![]() |
f30e54d177 | ||
![]() |
41b5cb06d3 | ||
![]() |
4ac72d7d08 | ||
![]() |
06ac4980ba | ||
![]() |
ccbfa20bb9 | ||
![]() |
58cd754e07 | ||
![]() |
d1263e583b | ||
![]() |
67c911c37f | ||
![]() |
288e3c3e3e | ||
![]() |
a84378c6c2 | ||
![]() |
b6073408f4 | ||
![]() |
b2d91ac5de | ||
![]() |
8bf34e09f4 | ||
![]() |
be914f2c15 | ||
![]() |
8bb670521d | ||
![]() |
225b3c1494 | ||
![]() |
4bf94e0757 | ||
![]() |
3b21d1d81e | ||
![]() |
5ec1588110 | ||
![]() |
71387be72e | ||
![]() |
98171c9f49 | ||
![]() |
a6c999dea0 | ||
![]() |
bf15b1d302 | ||
![]() |
f422fabab4 | ||
![]() |
01b130ec59 | ||
![]() |
48a1797e72 | ||
![]() |
b34d24735a | ||
![]() |
fe38b36c26 | ||
![]() |
03fca8d91e | ||
![]() |
9f9980e338 | ||
![]() |
19900b004b | ||
![]() |
a8ff0a8913 | ||
![]() |
45861456f1 | ||
![]() |
44b335e7e3 | ||
![]() |
de23bbace2 | ||
![]() |
edff9ae322 | ||
![]() |
3c2766448d | ||
![]() |
786c8b6cfe | ||
![]() |
e8de6a3a67 | ||
![]() |
3c320c4c83 | ||
![]() |
3b83f967e4 | ||
![]() |
5e96b8ef7c | ||
![]() |
5df0e82c37 | ||
![]() |
fd57b21aff | ||
![]() |
6087183a0c | ||
![]() |
01b7c4200e | ||
![]() |
7171286c3c | ||
![]() |
2d58239b74 | ||
![]() |
6b52f62531 | ||
![]() |
1001d9c04e | ||
![]() |
d220d41182 | ||
![]() |
c3a8972550 | ||
![]() |
263b603188 | ||
![]() |
584b722e7e | ||
![]() |
05edfd0e82 | ||
![]() |
16249c02a5 | ||
![]() |
e8ff36d1f3 | ||
![]() |
ed443c6153 | ||
![]() |
f4a84765cd | ||
![]() |
106de3530d | ||
![]() |
119c3f6f46 | ||
![]() |
d2c1c7507c | ||
![]() |
efdb3d1f40 | ||
![]() |
8095db6715 | ||
![]() |
ce2e161b08 | ||
![]() |
9dbc32b85f | ||
![]() |
66226abb48 | ||
![]() |
34bef2f2ca | ||
![]() |
6ef93452f5 | ||
![]() |
fdd4ca6837 | ||
![]() |
9655362f23 | ||
![]() |
130c9fad22 | ||
![]() |
3de0b601bf | ||
![]() |
91560ae4e9 | ||
![]() |
fd6135aebb | ||
![]() |
68ea59f3ae | ||
![]() |
e5fe5d1249 | ||
![]() |
9a69769a7e | ||
![]() |
63b42f3608 | ||
![]() |
d56107e97f | ||
![]() |
33f296e05b | ||
![]() |
3572c62315 | ||
![]() |
97e067a277 | ||
![]() |
1444cddda9 | ||
![]() |
5f56cf3128 | ||
![]() |
5c4e83ebdc | ||
![]() |
91f1c25fcc | ||
![]() |
47a7a239ae | ||
![]() |
fb9984e21f | ||
![]() |
b2db524366 | ||
![]() |
ab8674a5c7 | ||
![]() |
d1c85fc3fa | ||
![]() |
55ad45e3ee | ||
![]() |
f6e5a8cb2a | ||
![]() |
7a91ca9809 | ||
![]() |
71dd04b09e | ||
![]() |
7deabbb512 | ||
![]() |
625a575e49 | ||
![]() |
df4d0da221 | ||
![]() |
6b23b7cad7 | ||
![]() |
cea7deab91 | ||
![]() |
c61abf6aca | ||
![]() |
917bbc669c | ||
![]() |
0ac4c055de | ||
![]() |
78b55d86e9 | ||
![]() |
aaf50fc2e6 | ||
![]() |
6a8f4e92df | ||
![]() |
6d267fda01 | ||
![]() |
88943103a2 | ||
![]() |
e557dc7208 | ||
![]() |
3558806b0e | ||
![]() |
f1e8cc2cf0 | ||
![]() |
6236db1a27 | ||
![]() |
f4b0917239 | ||
![]() |
a5e3cd1a42 | ||
![]() |
b3cca5dcb6 | ||
![]() |
49465223a4 | ||
![]() |
15f0e54cbf | ||
![]() |
ed8f343aad | ||
![]() |
cbd8d70431 | ||
![]() |
9ff187c3f8 | ||
![]() |
be473b97c4 | ||
![]() |
9a5f865eea | ||
![]() |
790280ace9 | ||
![]() |
8ba207fc7f | ||
![]() |
d66b2a1778 | ||
![]() |
e3f2562047 | ||
![]() |
f77118a90c | ||
![]() |
041eb8f6cc | ||
![]() |
733a84df75 | ||
![]() |
acd0b50b40 | ||
![]() |
635851807a | ||
![]() |
89fd367297 | ||
![]() |
60e46d485e | ||
![]() |
5bf0c92318 | ||
![]() |
39d493c278 | ||
![]() |
d2ce62aa13 | ||
![]() |
c8eb30ef27 | ||
![]() |
c317422ed7 | ||
![]() |
614eb81ad7 | ||
![]() |
219c5953f1 | ||
![]() |
5d712c73ea | ||
![]() |
7097b7677e | ||
![]() |
7a4cf13e0c | ||
![]() |
4788a6182e | ||
![]() |
e3dad7c632 | ||
![]() |
1b4156646e | ||
![]() |
31ad75d01b | ||
![]() |
aa2eb29274 | ||
![]() |
22eb4f9cb9 | ||
![]() |
3acc8e7479 | ||
![]() |
2650441013 | ||
![]() |
71697df2b6 | ||
![]() |
acd55b9601 | ||
![]() |
0907de8662 | ||
![]() |
15eb9605a8 | ||
![]() |
6d5cb866db | ||
![]() |
768490089e | ||
![]() |
4d66fab360 | ||
![]() |
bd6bc283b6 | ||
![]() |
3120a0ba83 | ||
![]() |
b2199d5464 | ||
![]() |
84bac8356a | ||
![]() |
2819166539 | ||
![]() |
8fa18ca7c7 | ||
![]() |
63290a265c | ||
![]() |
b854e17995 | ||
![]() |
5dec9d88f6 | ||
![]() |
3d0a85ee78 | ||
![]() |
ac3cdf487f | ||
![]() |
a47e92f2bc | ||
![]() |
fc15ddfa91 | ||
![]() |
d546ef941f | ||
![]() |
b4bbe3d7b5 | ||
![]() |
5561d4eaeb | ||
![]() |
0f6dab394a | ||
![]() |
adc8c1aa38 | ||
![]() |
3a82f500d4 | ||
![]() |
7d1d4831a8 | ||
![]() |
43539f2dbf | ||
![]() |
df6830110d | ||
![]() |
1a98e882dc | ||
![]() |
c943d84036 | ||
![]() |
d07a6704d5 | ||
![]() |
8cfcd5904c | ||
![]() |
fb8846bb45 | ||
![]() |
0d0733dd94 | ||
![]() |
1a524a5a50 | ||
![]() |
1a2288cccf | ||
![]() |
8df27d4c3f | ||
![]() |
0688deca6b | ||
![]() |
01a4443b6c | ||
![]() |
e008b054cb | ||
![]() |
a67d58948d | ||
![]() |
84c051d097 | ||
![]() |
b918abfd54 | ||
![]() |
7f41b7cd93 | ||
![]() |
4759b4fe2e | ||
![]() |
e2c8e69d12 | ||
![]() |
1cf213dad8 | ||
![]() |
aeb81e547b | ||
![]() |
479f7703a2 | ||
![]() |
c7dc396b6d | ||
![]() |
917e8e155c | ||
![]() |
80e3030811 | ||
![]() |
a97e3d827d | ||
![]() |
029014d9d6 | ||
![]() |
e4c2922536 | ||
![]() |
7133ae6aaa | ||
![]() |
2f7f0ff3a1 | ||
![]() |
df853bf61e | ||
![]() |
f0ac753f9b | ||
![]() |
4d56a975e6 | ||
![]() |
d56c53c848 | ||
![]() |
f83b16320d | ||
![]() |
ac10e27f08 | ||
![]() |
34df7a6072 | ||
![]() |
e2cddf1005 | ||
![]() |
ced423748e | ||
![]() |
77fb02729e | ||
![]() |
fef39b9fbe | ||
![]() |
02810105fb | ||
![]() |
baad92515b | ||
![]() |
ab86ddcf02 | ||
![]() |
bf8eddb13b | ||
![]() |
e5eaf7a3fe | ||
![]() |
50f32a3aa5 | ||
![]() |
eb878710c1 | ||
![]() |
311980e0e4 | ||
![]() |
df73170e5a | ||
![]() |
a12c6b5f35 | ||
![]() |
522646c64d | ||
![]() |
4791093e48 | ||
![]() |
599a455150 | ||
![]() |
2deef16ebe | ||
![]() |
989b7be99b | ||
![]() |
cd473e1395 | ||
![]() |
e246ebfb2e | ||
![]() |
8546ae56da | ||
![]() |
9e227b0192 | ||
![]() |
ba7737e9f8 | ||
![]() |
98aa3d51ed | ||
![]() |
e7cfb5492e | ||
![]() |
9ed136dc3a | ||
![]() |
9217216723 | ||
![]() |
936c408a58 | ||
![]() |
54427eac9a | ||
![]() |
e809488cc0 | ||
![]() |
ed26c57d99 | ||
![]() |
2a49811f6e | ||
![]() |
c95acd2568 | ||
![]() |
6a4e0cf667 | ||
![]() |
04f4dd8a22 | ||
![]() |
f33d829ce9 | ||
![]() |
578671ea94 | ||
![]() |
d10300c330 | ||
![]() |
e0555e140f | ||
![]() |
093989406f | ||
![]() |
cdb16f08f6 | ||
![]() |
53139c293b | ||
![]() |
6a8bdcc315 | ||
![]() |
fe535939a3 | ||
![]() |
09e6c11d73 | ||
![]() |
8112bdfaa8 | ||
![]() |
f7db9aaa9f | ||
![]() |
435f972357 | ||
![]() |
f82b46c16b | ||
![]() |
bca96f91b2 | ||
![]() |
6f83a49c63 | ||
![]() |
72cce391ab | ||
![]() |
ccc13cc9e1 | ||
![]() |
020b2c05c8 | ||
![]() |
4c37c17df1 | ||
![]() |
8f67acadd8 | ||
![]() |
ca13c4c1a6 | ||
![]() |
d0c646c721 | ||
![]() |
8a055675af | ||
![]() |
28d2949ebe | ||
![]() |
c4a0015997 | ||
![]() |
f564be6aea | ||
![]() |
988f15e6af | ||
![]() |
37b6d442bd | ||
![]() |
fb2467f6f0 | ||
![]() |
29045b0435 | ||
![]() |
311a48c64e | ||
![]() |
01b3815f27 | ||
![]() |
b0d1c801bd | ||
![]() |
5aaac06f5b | ||
![]() |
34adbf0588 | ||
![]() |
0a4213182e | ||
![]() |
b0c0258e70 | ||
![]() |
8110e591d0 | ||
![]() |
fe05d7aec1 | ||
![]() |
57f5884070 | ||
![]() |
f329c74a15 | ||
![]() |
7c86f3fa9e | ||
![]() |
203b8b01bf | ||
![]() |
8a1034a92f | ||
![]() |
aa0c2dedd9 | ||
![]() |
d045908e05 | ||
![]() |
f002a23d2d | ||
![]() |
29d6d0a906 | ||
![]() |
c8b58b5c23 | ||
![]() |
01bfafc5f1 | ||
![]() |
8c9948bb56 | ||
![]() |
2d1abaa68e | ||
![]() |
664a3df0b4 | ||
![]() |
9ff893881c | ||
![]() |
94f6c6861a | ||
![]() |
b1d614e6c4 | ||
![]() |
7fceb070e5 | ||
![]() |
06440d0202 | ||
![]() |
0ecf9f4f2f | ||
![]() |
5c7c0834c0 | ||
![]() |
f3a25de11d | ||
![]() |
041bef8bcd | ||
![]() |
8998c5f6dd | ||
![]() |
6e83790308 | ||
![]() |
d2d4eb4eae | ||
![]() |
5942a3898c | ||
![]() |
93421f0fa7 | ||
![]() |
3a9ab50dd2 | ||
![]() |
5abd91d6d5 | ||
![]() |
c3da42516b | ||
![]() |
6cb5cd48c2 | ||
![]() |
ec1fae6883 | ||
![]() |
746fd1122f | ||
![]() |
9663760ec5 | ||
![]() |
a3d73d1e23 | ||
![]() |
d63e14a4b6 | ||
![]() |
03944e6cd8 | ||
![]() |
0d1028be2e | ||
![]() |
6a85259e4d | ||
![]() |
ebca936b7e | ||
![]() |
31c4551890 | ||
![]() |
dd470d4197 | ||
![]() |
612822490b | ||
![]() |
f8969605e8 | ||
![]() |
dd24ffa24e | ||
![]() |
d0dda48932 | ||
![]() |
6349b5f654 | ||
![]() |
a6ff02a3cf | ||
![]() |
4f57bf786b | ||
![]() |
6221f6d47d | ||
![]() |
a922efeafa | ||
![]() |
5aa42e5e66 | ||
![]() |
708672ec7e | ||
![]() |
d2cefbf224 | ||
![]() |
adb7aa6950 | ||
![]() |
77f322166e | ||
![]() |
f3f6e54818 | ||
![]() |
fb0fec1f25 | ||
![]() |
b66af9fb4d | ||
![]() |
6617d576a7 | ||
![]() |
cd35ead890 | ||
![]() |
9dc804ee27 | ||
![]() |
a8ceeaa7b0 | ||
![]() |
7092f7663e | ||
![]() |
d9d2edeb08 | ||
![]() |
dda1ddcb26 | ||
![]() |
f0c890f160 | ||
![]() |
4f52d43347 | ||
![]() |
0ed7db979b | ||
![]() |
9c78049359 | ||
![]() |
7882105661 | ||
![]() |
c000e1d6dd | ||
![]() |
420dacb22d | ||
![]() |
ae2f6ad4d1 | ||
![]() |
2c28d79bf8 | ||
![]() |
c5069edc78 | ||
![]() |
282d9e138c | ||
![]() |
72fcf2cbe1 | ||
![]() |
6f49f5465b | ||
![]() |
17b8bd8316 | ||
![]() |
9b6b9c1fa2 | ||
![]() |
609a2ca592 | ||
![]() |
6dabf24bf3 | ||
![]() |
7e88938932 | ||
![]() |
c707e64685 | ||
![]() |
a639690716 | ||
![]() |
01222dbab7 | ||
![]() |
93e2506279 | ||
![]() |
f62d5d3b9d | ||
![]() |
0665acd190 | ||
![]() |
fea05e9d33 | ||
![]() |
7a03c7d56f | ||
![]() |
2dc2aec954 | ||
![]() |
39c6c2417a | ||
![]() |
ff72d6a146 | ||
![]() |
603d0d0c7c | ||
![]() |
28883f711b | ||
![]() |
e914828add | ||
![]() |
c1480029fb | ||
![]() |
40f622949e | ||
![]() |
63096ac2bc | ||
![]() |
03d5a0ec1d | ||
![]() |
1c873e0034 | ||
![]() |
bcb47c306c | ||
![]() |
01c4d3c225 | ||
![]() |
c2aaae4818 | ||
![]() |
3f678e218d | ||
![]() |
c2a59cb476 | ||
![]() |
f8a1bd4e79 | ||
![]() |
d6e039a1d1 | ||
![]() |
0f1a7c2b69 | ||
![]() |
40ad9f4911 | ||
![]() |
4116caff6a | ||
![]() |
0b69f72315 | ||
![]() |
c569f5ddcf | ||
![]() |
62f9e181e0 | ||
![]() |
235a97ea10 | ||
![]() |
e541ae400c | ||
![]() |
4822abde86 | ||
![]() |
b7e52812f8 | ||
![]() |
69118120d9 | ||
![]() |
7cba0c6fb0 | ||
![]() |
5fac67ce15 | ||
![]() |
98c733108e | ||
![]() |
782186e13d | ||
![]() |
4e1f6518e8 | ||
![]() |
53e0fe8e51 | ||
![]() |
0e547390da | ||
![]() |
86b52df839 | ||
![]() |
d685fdf54a | ||
![]() |
d9caab4108 | ||
![]() |
44b68f140e | ||
![]() |
3a3d97dfa7 | ||
![]() |
47898b527c | ||
![]() |
a35f36ad39 | ||
![]() |
d13a397f8e | ||
![]() |
df999723f8 | ||
![]() |
8236e840a7 | ||
![]() |
e5b3625f73 | ||
![]() |
2e4645310b | ||
![]() |
50a32b387e | ||
![]() |
2059283707 | ||
![]() |
8e3af515c9 | ||
![]() |
6f88f0ea3f | ||
![]() |
d2f37cf3f9 | ||
![]() |
7c30d6254e | ||
![]() |
64fb39a653 | ||
![]() |
91895aa70c | ||
![]() |
68dfaf238b | ||
![]() |
ebf13a0ba0 | ||
![]() |
2bff9937b7 | ||
![]() |
256395c28d | ||
![]() |
3346bc8bba | ||
![]() |
6fe22a7e62 | ||
![]() |
757b98748b | ||
![]() |
7a778f3f33 | ||
![]() |
993044c870 | ||
![]() |
a8c1b63edb | ||
![]() |
db7d946e1b | ||
![]() |
41d9059a2f | ||
![]() |
e26e0d7c01 | ||
![]() |
ad41c07a1f | ||
![]() |
9576d246ee | ||
![]() |
988d3ea8ba | ||
![]() |
0767b92b62 | ||
![]() |
5732f3b044 | ||
![]() |
712115b6ce | ||
![]() |
9283559c6b | ||
![]() |
6b393438e9 | ||
![]() |
2064abe16d | ||
![]() |
b605982f94 | ||
![]() |
343b9ab455 | ||
![]() |
dcb226b202 | ||
![]() |
2243021b58 | ||
![]() |
d5134e88b1 | ||
![]() |
c59adf612f | ||
![]() |
93b628d9a8 | ||
![]() |
6bac551d9f | ||
![]() |
70a35656e4 | ||
![]() |
047c18eac0 | ||
![]() |
b4a86ce6cf | ||
![]() |
a82d8ea0c3 | ||
![]() |
b778eed419 | ||
![]() |
ad57faa9a9 | ||
![]() |
a9b5e8d036 | ||
![]() |
8be704e591 | ||
![]() |
b622a8fa58 | ||
![]() |
a519e5c475 | ||
![]() |
d620b6dd5e | ||
![]() |
99335d986e | ||
![]() |
7895cd92cd | ||
![]() |
8b2c032da6 | ||
![]() |
da336247eb | ||
![]() |
dabd27d4be | ||
![]() |
fdda47db6e | ||
![]() |
efa6fd03e5 | ||
![]() |
9e3e34acf5 | ||
![]() |
a2d0c1bf18 | ||
![]() |
7663716ae8 | ||
![]() |
c2cacb3478 | ||
![]() |
84666b54b9 | ||
![]() |
2b91c23bf3 | ||
![]() |
3297267a16 | ||
![]() |
a9e653724c | ||
![]() |
5e79a1f500 | ||
![]() |
d4ff98680a | ||
![]() |
ba8d255cb4 | ||
![]() |
06f4ad922c | ||
![]() |
bff06e448b | ||
![]() |
d48ffa2913 | ||
![]() |
d97c3a7e01 | ||
![]() |
0b1161f7ef | ||
![]() |
061e1a471d | ||
![]() |
a39d874600 | ||
![]() |
de96376565 | ||
![]() |
c54c20ab3c | ||
![]() |
70fafa473b | ||
![]() |
2e436eae6b | ||
![]() |
fd7e861ff5 | ||
![]() |
792108686c | ||
![]() |
fa1b5117fd | ||
![]() |
b0bd9e0a34 | ||
![]() |
05dc97099a | ||
![]() |
9de61fcf58 | ||
![]() |
fc7348d46d | ||
![]() |
8be2456c7e | ||
![]() |
bb5f7249a6 | ||
![]() |
7f7175b184 | ||
![]() |
cf5c640ae4 | ||
![]() |
6b9371d105 | ||
![]() |
9a82057303 | ||
![]() |
48584e94c4 | ||
![]() |
fc94a5d0ee | ||
![]() |
d8024a5928 | ||
![]() |
2034ab4f6c | ||
![]() |
24029cc918 | ||
![]() |
9a9d5964ee | ||
![]() |
4e4a512107 | ||
![]() |
0729ed538e | ||
![]() |
24b75b7ed6 | ||
![]() |
58b70b42dd | ||
![]() |
1496bc1b07 | ||
![]() |
bfbf88b2ea | ||
![]() |
e621b938e3 | ||
![]() |
ec3618ecb8 | ||
![]() |
792a24f38d | ||
![]() |
652e8a015b | ||
![]() |
59e6e798dd | ||
![]() |
e5c2dbc7ec | ||
![]() |
756f71c382 | ||
![]() |
b7535693fa | ||
![]() |
06a3505698 | ||
![]() |
0372d17a11 | ||
![]() |
4525588116 | ||
![]() |
68e957c147 | ||
![]() |
99f5ed1461 | ||
![]() |
59f67796dc | ||
![]() |
aafdfa933e | ||
![]() |
3208c8ed1e | ||
![]() |
6bf733e24e | ||
![]() |
65d3e8fbfc | ||
![]() |
a29d65d47c | ||
![]() |
efa8f0730d | ||
![]() |
0af1edefff | ||
![]() |
023d26f521 | ||
![]() |
5068619f1b | ||
![]() |
5b2457af0b | ||
![]() |
900b4f1af9 | ||
![]() |
4c22a98b0b | ||
![]() |
3b8ca80900 | ||
![]() |
1ef6fd8fb0 | ||
![]() |
942b0de7fd | ||
![]() |
859cca49d1 | ||
![]() |
dc6eff83ea | ||
![]() |
38ff66debd | ||
![]() |
1d2e0f74ea | ||
![]() |
8f7ff25624 | ||
![]() |
97aca8e54c | ||
![]() |
95acf19067 | ||
![]() |
3d0899aa58 | ||
![]() |
bf60e40d0b | ||
![]() |
c9094ca537 | ||
![]() |
68b3fd6b8f | ||
![]() |
9323b3a248 | ||
![]() |
b55e9329d9 | ||
![]() |
a5b4105971 | ||
![]() |
d1feaa935d | ||
![]() |
6919930aaa | ||
![]() |
69633826bb | ||
![]() |
771162bfb1 | ||
![]() |
ba785e29e9 | ||
![]() |
138d6e505b | ||
![]() |
2748e6ba29 | ||
![]() |
dbd4e927d8 | ||
![]() |
e73d47918f | ||
![]() |
b881bc071e | ||
![]() |
1d0395d1c7 | ||
![]() |
616c787e37 | ||
![]() |
0c4de2bc97 | ||
![]() |
2c7b104f4a | ||
![]() |
78951c197a | ||
![]() |
07c1cf7137 | ||
![]() |
d26141151a | ||
![]() |
f59dbe4a88 | ||
![]() |
8dae7f8225 | ||
![]() |
5811389891 | ||
![]() |
debcaf6fb7 | ||
![]() |
b8d10a62c2 | ||
![]() |
d2b209234f | ||
![]() |
34c9d8be50 | ||
![]() |
ae57ad0c81 | ||
![]() |
0c1520dd9c | ||
![]() |
d594f43ebd | ||
![]() |
125c693e3f | ||
![]() |
ad2f857e15 | ||
![]() |
e445d6aada | ||
![]() |
88fbb0ffbb | ||
![]() |
231908fe9f | ||
![]() |
f137cc10f4 | ||
![]() |
c2f5ac9eba | ||
![]() |
5764c988af | ||
![]() |
ccc2fbfd67 | ||
![]() |
1a8f8adc2a | ||
![]() |
7a242bb4ed | ||
![]() |
10b4adb8e6 | ||
![]() |
3b8bb09ae3 | ||
![]() |
83b7181bcb | ||
![]() |
8886b7e141 | ||
![]() |
7dcc4d030b | ||
![]() |
b9398897c1 | ||
![]() |
140db85d21 | ||
![]() |
ccce4b19e8 | ||
![]() |
8cb9be7560 | ||
![]() |
953f0569fb | ||
![]() |
34c229fd33 | ||
![]() |
958ad0d750 | ||
![]() |
36ddd9dd69 | ||
![]() |
38259c96c9 | ||
![]() |
c054fb8a2c | ||
![]() |
5a0b8328d8 | ||
![]() |
ffa19426d7 | ||
![]() |
c123804294 | ||
![]() |
4e24551b90 | ||
![]() |
657b1c60ae | ||
![]() |
dc54b17778 | ||
![]() |
1fb214165b | ||
![]() |
51cb5da7f0 | ||
![]() |
81b2fd78f5 | ||
![]() |
69002fb1e6 | ||
![]() |
75332a752d | ||
![]() |
b528f48417 | ||
![]() |
ec7a79049a | ||
![]() |
6ddad6b299 | ||
![]() |
16dc7762f9 | ||
![]() |
41f84447cc | ||
![]() |
ce073a704b | ||
![]() |
113232ebb6 | ||
![]() |
dc0ed8857f | ||
![]() |
bb6b77bd98 | ||
![]() |
dcc80f9032 | ||
![]() |
dd554bcdf4 | ||
![]() |
f376a39e55 | ||
![]() |
8dcc9d6b66 | ||
![]() |
a13a1225b7 | ||
![]() |
0ec84be5da | ||
![]() |
b1cefb7e3e | ||
![]() |
cc0c1c08b9 | ||
![]() |
3a67884451 | ||
![]() |
72e716cdf1 | ||
![]() |
40e06c9819 | ||
![]() |
ad6c5ff11d | ||
![]() |
335512e232 | ||
![]() |
2622e59b0b | ||
![]() |
35e6a13cd1 | ||
![]() |
a576c9f21f | ||
![]() |
b48490badc | ||
![]() |
71a438e2cb | ||
![]() |
272d6f2a8b | ||
![]() |
5c22065135 | ||
![]() |
e7dd6c52ac | ||
![]() |
3bf042dce9 | ||
![]() |
09ed1aed93 | ||
![]() |
53d3718028 | ||
![]() |
2b5dce5232 | ||
![]() |
9ad84150aa | ||
![]() |
64f798d4b2 | ||
![]() |
f43e04e15a | ||
![]() |
88d72f8c9a | ||
![]() |
9826726a72 | ||
![]() |
c66d0550e8 | ||
![]() |
4aeacfd16e | ||
![]() |
58fa63ad88 | ||
![]() |
94f944dc9c | ||
![]() |
116ddbdd01 | ||
![]() |
1c0697b5d4 | ||
![]() |
434ca47ea0 | ||
![]() |
397ef72b16 | ||
![]() |
7ca9245735 | ||
![]() |
69856286e8 | ||
![]() |
ad43d6a5bc | ||
![]() |
1e5004f495 | ||
![]() |
253161d3d0 | ||
![]() |
ab47e201c7 | ||
![]() |
42984fa72a | ||
![]() |
e7864a28a1 | ||
![]() |
21803607e7 | ||
![]() |
c0523590b4 | ||
![]() |
c7f091ab10 | ||
![]() |
7479e0aada | ||
![]() |
62b366a5ec | ||
![]() |
2b39988707 | ||
![]() |
f9e7291050 | ||
![]() |
4de642ff28 | ||
![]() |
0384efcfc2 | ||
![]() |
bf91443f38 | ||
![]() |
4a5970b4af | ||
![]() |
e3fd68c849 | ||
![]() |
df0de2fc2d | ||
![]() |
0c3568fad5 | ||
![]() |
976f5d91ed | ||
![]() |
0f3d4d9a47 | ||
![]() |
ad1f4429c9 | ||
![]() |
7590d5eacb | ||
![]() |
c5974b8833 | ||
![]() |
511c8de6f3 | ||
![]() |
a718ac7ee0 | ||
![]() |
ef832becf1 | ||
![]() |
3a62455948 | ||
![]() |
297824e2d7 | ||
![]() |
d92f297bc0 | ||
![]() |
7a0827e3d0 | ||
![]() |
ef256a64b8 | ||
![]() |
1de941e837 | ||
![]() |
28b65cb810 | ||
![]() |
6ff3942e8b | ||
![]() |
ef5d959788 | ||
![]() |
5bbee1a1fe | ||
![]() |
6a2c58fcc0 | ||
![]() |
bdb9546ca3 | ||
![]() |
46af4cad6e | ||
![]() |
76a238912b | ||
![]() |
4e6bdb31ac | ||
![]() |
80d03a631e | ||
![]() |
6b27f2d2cf | ||
![]() |
7cb6729fa7 | ||
![]() |
2f46267994 | ||
![]() |
cdda648360 | ||
![]() |
f2d677d51a | ||
![]() |
c2ee0f0864 | ||
![]() |
2a84db7f85 | ||
![]() |
8187a4bce9 | ||
![]() |
97681d142e | ||
![]() |
b2430097f2 | ||
![]() |
7da12a878f | ||
![]() |
a31700e16f | ||
![]() |
7854522792 | ||
![]() |
a6a9ebfde2 | ||
![]() |
c6cbe2748e | ||
![]() |
f9a7f00843 | ||
![]() |
f0b183a552 | ||
![]() |
338ada5c9f | ||
![]() |
ef88f9923f | ||
![]() |
6f8c7d9ec4 | ||
![]() |
ec769ccf72 | ||
![]() |
045952939e | ||
![]() |
1c51cac5ba | ||
![]() |
ea11462e1e | ||
![]() |
62f9736b1d | ||
![]() |
1f8a1f0046 | ||
![]() |
172507acb5 | ||
![]() |
434ab65c16 | ||
![]() |
909a526967 | ||
![]() |
cd6f4fb93f | ||
![]() |
c19458696e | ||
![]() |
cb5f793ede | ||
![]() |
318b930e9f | ||
![]() |
9296a078a7 | ||
![]() |
5dc776e55f | ||
![]() |
72d60f30f7 | ||
![]() |
869743a742 | ||
![]() |
7b03e07908 | ||
![]() |
348f880e15 | ||
![]() |
737188ae50 | ||
![]() |
db21731b14 | ||
![]() |
cdb4fa2487 | ||
![]() |
514204f0d4 | ||
![]() |
ead597d0fb | ||
![]() |
afbf989715 | ||
![]() |
01b62a16c3 | ||
![]() |
c5eba04517 | ||
![]() |
282313ab52 | ||
![]() |
d274545e77 | ||
![]() |
45ac577c4d | ||
![]() |
09402fdb22 | ||
![]() |
89e7448007 | ||
![]() |
1ea6f957bc | ||
![]() |
f44fca0a4b | ||
![]() |
52d2f62a57 | ||
![]() |
d3fda37615 | ||
![]() |
cbe3092404 | ||
![]() |
6dfe3039d0 | ||
![]() |
d6009453df | ||
![]() |
2a8668ea60 | ||
![]() |
cc0d433621 | ||
![]() |
c81323ef91 | ||
![]() |
1fe89fb364 | ||
![]() |
961c27f1c2 | ||
![]() |
fe4a14e6cc | ||
![]() |
ee58ad1ac0 | ||
![]() |
c0ff899812 | ||
![]() |
d9c938de33 | ||
![]() |
56547b3d50 | ||
![]() |
5026bc7a78 | ||
![]() |
27364ee72c | ||
![]() |
ece71a0228 | ||
![]() |
073828235f | ||
![]() |
41bcc8c0f4 | ||
![]() |
a0ea2aae6e | ||
![]() |
f34b46a621 | ||
![]() |
7217a4f7a4 | ||
![]() |
6383eca54a | ||
![]() |
e55bd1e559 | ||
![]() |
9e8b701dea | ||
![]() |
a4431abea8 | ||
![]() |
5844c1767b | ||
![]() |
9a70bfa471 | ||
![]() |
b406c6403c | ||
![]() |
499625f266 | ||
![]() |
6b773553fc | ||
![]() |
15fe049a99 | ||
![]() |
e4555f6997 | ||
![]() |
470071e0b0 | ||
![]() |
ea1be8e7bf | ||
![]() |
84a830195f | ||
![]() |
e62c3e00c1 | ||
![]() |
07e790f900 | ||
![]() |
640142fc0c | ||
![]() |
5c339d4597 | ||
![]() |
a4931f5d78 | ||
![]() |
5e1e543b06 | ||
![]() |
df929f9445 | ||
![]() |
d8e719d1c4 | ||
![]() |
3067e482fc | ||
![]() |
ed5930e934 | ||
![]() |
ffea3597f4 | ||
![]() |
193d3e0206 | ||
![]() |
c8f4fbb7dd | ||
![]() |
c855bc31b4 | ||
![]() |
b924b179ab | ||
![]() |
3df0fee3de | ||
![]() |
b601560e81 | ||
![]() |
e5775cf812 | ||
![]() |
26dd1f8532 | ||
![]() |
5143a5b5c5 | ||
![]() |
15ce27992e | ||
![]() |
dbc2812022 | ||
![]() |
dce3713f12 | ||
![]() |
f849d45bb6 | ||
![]() |
8ad06fb9ea | ||
![]() |
9124d9d6e6 | ||
![]() |
45ebe51e4f | ||
![]() |
407661d56b | ||
![]() |
998d4229af | ||
![]() |
a02d2e2e11 | ||
![]() |
72fa68849f | ||
![]() |
33f17f75a0 | ||
![]() |
23edb18d7e | ||
![]() |
07ff3a853f | ||
![]() |
2cf36bdb46 | ||
![]() |
50848c2f4d | ||
![]() |
d32633b3c7 | ||
![]() |
b37739eec2 | ||
![]() |
28f87dc804 | ||
![]() |
41879e41e6 | ||
![]() |
fc0a6546a2 | ||
![]() |
ffd4280d6c | ||
![]() |
f859b346a6 | ||
![]() |
cb0677cafe | ||
![]() |
c6956527d1 | ||
![]() |
72c6bfaa50 | ||
![]() |
7927b5f624 | ||
![]() |
b7aad39daf | ||
![]() |
f48de6dd43 | ||
![]() |
79d73d8f8b | ||
![]() |
cc5947467f | ||
![]() |
e152f128c8 | ||
![]() |
99bd808ebe | ||
![]() |
beb5f3dc9d | ||
![]() |
f5c3b3446f | ||
![]() |
db3b955b0f | ||
![]() |
f431c7402f | ||
![]() |
5516f65971 | ||
![]() |
9471df0a1b | ||
![]() |
6d39f64be7 | ||
![]() |
4907e6f6d7 | ||
![]() |
1ccee86705 | ||
![]() |
542fb2175b | ||
![]() |
6ec9cfb044 | ||
![]() |
66e0ff8392 | ||
![]() |
1fb0a7109d | ||
![]() |
b89d0a9a73 | ||
![]() |
4bb779d9a5 | ||
![]() |
386a5b6362 | ||
![]() |
e32a999cd0 | ||
![]() |
192eb49589 | ||
![]() |
5d70ff702b | ||
![]() |
a7b05db2a1 | ||
![]() |
45e346cf1b | ||
![]() |
80e2bfada3 | ||
![]() |
16e7bd0388 | ||
![]() |
b3fb35783e | ||
![]() |
a79c6aa9e0 | ||
![]() |
4bb58b2de9 | ||
![]() |
4e10881331 | ||
![]() |
cec4a81e14 | ||
![]() |
da45923d05 | ||
![]() |
31a61b598b | ||
![]() |
9c0506592b | ||
![]() |
beeb0c7c5a | ||
![]() |
b2f05faee0 | ||
![]() |
bfbc6a4bad | ||
![]() |
8c9e0e552d | ||
![]() |
8aaf9fd83f | ||
![]() |
08057720b8 | ||
![]() |
bfaa648837 | ||
![]() |
d504daef91 | ||
![]() |
8375e1d64d | ||
![]() |
cf5193d3e5 | ||
![]() |
b8d3ef2f49 | ||
![]() |
3bf6320030 | ||
![]() |
708b928c73 | ||
![]() |
649366ff44 | ||
![]() |
e5c9e87fad | ||
![]() |
c490388e80 | ||
![]() |
24ec5a6e9d | ||
![]() |
f3d9d707b6 | ||
![]() |
090e10730c | ||
![]() |
fbc84861c7 | ||
![]() |
e763469af8 | ||
![]() |
6df1d5222d | ||
![]() |
58fb7a02f6 | ||
![]() |
3d51ac8df0 | ||
![]() |
6fe4ff7f85 | ||
![]() |
3c0c514e44 | ||
![]() |
2253d4bc16 | ||
![]() |
e5cc19de43 | ||
![]() |
ed5e2dd332 | ||
![]() |
09b7c6f550 | ||
![]() |
df315a1f51 | ||
![]() |
7ee4bb621c | ||
![]() |
24874f4c3c | ||
![]() |
c128880033 | ||
![]() |
a66e94a0b0 | ||
![]() |
56870ed4a8 | ||
![]() |
3ac720df47 | ||
![]() |
1bc757ad06 | ||
![]() |
f72abc6f3d | ||
![]() |
5ac88de985 | ||
![]() |
5404617d43 | ||
![]() |
12467a18e6 | ||
![]() |
1db7043a4d | ||
![]() |
49932747b3 | ||
![]() |
55db190875 | ||
![]() |
71fe2f7ed3 | ||
![]() |
ffc112c9d0 | ||
![]() |
d3e48e296f | ||
![]() |
14f6ae75ea | ||
![]() |
c84efe64d3 | ||
![]() |
10e89a7dbb | ||
![]() |
ef44acbf10 | ||
![]() |
06da540ab0 | ||
![]() |
0826b367d6 | ||
![]() |
329bf861d6 | ||
![]() |
9dcd3d18a0 | ||
![]() |
40c017fd54 | ||
![]() |
db66cd88b6 | ||
![]() |
f0bcf81a98 | ||
![]() |
86c205fe43 | ||
![]() |
6a0b343289 | ||
![]() |
c6414138c7 | ||
![]() |
36b355eb82 | ||
![]() |
9ca4e8f32a | ||
![]() |
1b88b7a166 | ||
![]() |
caf352ff06 | ||
![]() |
54106179a1 | ||
![]() |
607601b3a4 | ||
![]() |
f58828cb82 | ||
![]() |
11330af05f | ||
![]() |
fbe1bca1b9 | ||
![]() |
24a5325db3 | ||
![]() |
1ec3140759 | ||
![]() |
ca8db7696e | ||
![]() |
c9190574a9 | ||
![]() |
bfeb0b3639 | ||
![]() |
cbc1334b8d | ||
![]() |
08cbb97ec9 | ||
![]() |
5719cc1a24 | ||
![]() |
d9513e5ff2 | ||
![]() |
b5a0e8b2c0 | ||
![]() |
b32b918936 | ||
![]() |
0f47ffd908 | ||
![]() |
cd018ad3a5 | ||
![]() |
24dfecb6f0 | ||
![]() |
ab027a6ae2 | ||
![]() |
556d071e7f | ||
![]() |
939fb313df | ||
![]() |
b5639a6472 | ||
![]() |
f50e40e0b8 | ||
![]() |
6f07421911 | ||
![]() |
adf48246a9 | ||
![]() |
7be9291b13 | ||
![]() |
ea9e75039b | ||
![]() |
a5fb036011 | ||
![]() |
e55506f9db | ||
![]() |
50ec1d0445 | ||
![]() |
3d5e1d8d91 | ||
![]() |
db2128a344 | ||
![]() |
cae283dc86 | ||
![]() |
7afcb0fb04 | ||
![]() |
10f830c3ef | ||
![]() |
7a5c3aa7ed | ||
![]() |
2b50406856 | ||
![]() |
10a2a7e0fc | ||
![]() |
7a564b222d | ||
![]() |
21db43db06 | ||
![]() |
5009b3029f | ||
![]() |
57a029189c | ||
![]() |
671d68bc2c | ||
![]() |
5946c37925 | ||
![]() |
17a37b1de9 | ||
![]() |
e7827a6997 | ||
![]() |
2347e043a9 | ||
![]() |
00965fe19e | ||
![]() |
9681dfb458 | ||
![]() |
5e631bc6ba | ||
![]() |
b5f660398c | ||
![]() |
d50bdf619f | ||
![]() |
4e448b21ff | ||
![]() |
2a78c2970d | ||
![]() |
0cb715bb76 | ||
![]() |
7d03823afd | ||
![]() |
8e1c9f5042 | ||
![]() |
980b7cda8f | ||
![]() |
3a72dd5cb6 | ||
![]() |
3178243811 | ||
![]() |
d30e2f2a4f | ||
![]() |
3637be251e | ||
![]() |
2aea27d272 | ||
![]() |
ceb9b1d1ff | ||
![]() |
ccfa1e23f0 | ||
![]() |
290da8df2d | ||
![]() |
4b1d73791d | ||
![]() |
7e8012c1a0 | ||
![]() |
15cd602e8b | ||
![]() |
598f5b241f | ||
![]() |
335e69e6cd | ||
![]() |
05fe5db030 | ||
![]() |
710096b1c6 | ||
![]() |
07b882c801 | ||
![]() |
3e5331a263 | ||
![]() |
897277992b | ||
![]() |
1424091ee5 | ||
![]() |
61ec16cdfc | ||
![]() |
e5cb5756aa | ||
![]() |
9e1c3e8f01 | ||
![]() |
448e1690aa | ||
![]() |
8267f01ccd | ||
![]() |
6f9439e1bc | ||
![]() |
06994c0dfc | ||
![]() |
dee5d639e2 | ||
![]() |
df6730be55 | ||
![]() |
6226dae05c | ||
![]() |
9c6a475a6e | ||
![]() |
8294d10d5b | ||
![]() |
67558bec47 | ||
![]() |
6c1ef398bb | ||
![]() |
84873d4074 | ||
![]() |
58a0b28a39 | ||
![]() |
0469e19f54 | ||
![]() |
dbcfa7b599 | ||
![]() |
b37d3a66cc | ||
![]() |
7e495a5e27 | ||
![]() |
c41547fd4a | ||
![]() |
0d47d41c85 | ||
![]() |
df68403b6d | ||
![]() |
57bdc2b885 | ||
![]() |
f565ff5def | ||
![]() |
8ece639987 | ||
![]() |
b35f509784 | ||
![]() |
41a3a17456 | ||
![]() |
cbbafbcca2 | ||
![]() |
c75566b374 | ||
![]() |
f1954df573 | ||
![]() |
7279f1fcc1 | ||
![]() |
d7432f7c10 | ||
![]() |
b0a0a153f3 | ||
![]() |
9e4fa5dcf1 | ||
![]() |
024632dbd0 | ||
![]() |
0a545a28b9 | ||
![]() |
0f2df59998 | ||
![]() |
0809673ba9 | ||
![]() |
b386284180 | ||
![]() |
515519bc87 | ||
![]() |
5404163be0 | ||
![]() |
29a7d32f77 | ||
![]() |
687a7e9b2f | ||
![]() |
09e8782318 | ||
![]() |
0b193eee43 | ||
![]() |
f2aea02210 | ||
![]() |
194f922312 | ||
![]() |
fea3c48098 | ||
![]() |
c2f57baec2 | ||
![]() |
f4a140e126 | ||
![]() |
ab506b09fe | ||
![]() |
87e1cdeedb | ||
![]() |
81a36146ef | ||
![]() |
7333123ba4 | ||
![]() |
d99c5ed890 | ||
![]() |
04740fbcbb | ||
![]() |
6a7440f7d3 | ||
![]() |
14299bb2cc | ||
![]() |
66cebfc992 | ||
![]() |
108b8e6705 | ||
![]() |
4eaa6afa4d | ||
![]() |
f643a46bbf | ||
![]() |
aae63a7ff3 | ||
![]() |
582567696e | ||
![]() |
2e0c89409d | ||
![]() |
7fa4a68a27 | ||
![]() |
f1c5e2ef81 | ||
![]() |
b526155cce | ||
![]() |
62c3f301e7 | ||
![]() |
38cb988809 | ||
![]() |
7bb7456a8b | ||
![]() |
0372e12b81 | ||
![]() |
a6873c1520 | ||
![]() |
f11220da3a | ||
![]() |
b976ac54c8 | ||
![]() |
78026e766f | ||
![]() |
b4cd8d21a5 | ||
![]() |
7552893311 | ||
![]() |
21c896d8f8 | ||
![]() |
4b7fe202ec | ||
![]() |
bb9793d5b7 | ||
![]() |
e99af991ec | ||
![]() |
abf3708cc2 | ||
![]() |
4395d6156d | ||
![]() |
9f4519210f | ||
![]() |
b0506afa5b | ||
![]() |
8cbb379898 | ||
![]() |
04ba53c870 | ||
![]() |
b226215593 | ||
![]() |
19970729a9 | ||
![]() |
f310cacd41 | ||
![]() |
5ff7c8418c | ||
![]() |
0bdb48bcac | ||
![]() |
99c775d8cb | ||
![]() |
4d43396835 | ||
![]() |
92321e219a | ||
![]() |
c422b2fb0b | ||
![]() |
8aa72f4c1e | ||
![]() |
d8e33c5a69 | ||
![]() |
15f9677d33 | ||
![]() |
219b225ac0 | ||
![]() |
2ac232e634 | ||
![]() |
710866ff4e | ||
![]() |
662773b075 | ||
![]() |
875b803483 | ||
![]() |
6e5cfac927 | ||
![]() |
97eaf3d4a1 | ||
![]() |
366552a969 | ||
![]() |
57b07441a1 | ||
![]() |
fb57ab0add | ||
![]() |
d6717c0032 | ||
![]() |
f72389147d | ||
![]() |
a509f6ccd2 | ||
![]() |
add484a2ea | ||
![]() |
a17a6d5346 | ||
![]() |
be9439f10d | ||
![]() |
96a50f5c6b | ||
![]() |
3c0414c420 | ||
![]() |
b450d4c734 | ||
![]() |
d536509a63 | ||
![]() |
11f1e28139 | ||
![]() |
379c3e98f5 | ||
![]() |
5ea77894b7 | ||
![]() |
d54b4e7c44 | ||
![]() |
d8b3af3815 | ||
![]() |
2b04152482 | ||
![]() |
331a3ac387 | ||
![]() |
7eee3cdc7f | ||
![]() |
b12c7432e0 | ||
![]() |
696643d037 | ||
![]() |
7e54f97003 | ||
![]() |
77dbf84e55 | ||
![]() |
2350c5054c | ||
![]() |
73accf747f | ||
![]() |
0d3e6b2c4c | ||
![]() |
b3d7cc637b | ||
![]() |
2147bcbc29 | ||
![]() |
980c2d4cae | ||
![]() |
d2ebfd2833 | ||
![]() |
bd782fc828 | ||
![]() |
23560e608c | ||
![]() |
f1377b560e | ||
![]() |
72108684ea | ||
![]() |
c6adaaea97 | ||
![]() |
91999a38ca | ||
![]() |
b34eed125d | ||
![]() |
2abe09529a | ||
![]() |
9aaaf4dd4b | ||
![]() |
cbfbcf7f1b | ||
![]() |
68316cbcf9 | ||
![]() |
2f4b9263c3 | ||
![]() |
c2623a08e3 | ||
![]() |
9f625ee7d1 | ||
![]() |
2f85c27a05 | ||
![]() |
c612a3bf60 | ||
![]() |
a01f5f5cf1 | ||
![]() |
87328686a0 | ||
![]() |
81c11ba1f7 | ||
![]() |
49b17c5a2d | ||
![]() |
de06a781ff | ||
![]() |
8e77e3c685 | ||
![]() |
a687b083ae | ||
![]() |
b9e5c7eb35 | ||
![]() |
1a6a063e04 | ||
![]() |
d85b7a6bd0 | ||
![]() |
1c4700f447 | ||
![]() |
c7651dc40d | ||
![]() |
eda1c471ad | ||
![]() |
c7ef18fbc4 | ||
![]() |
901ec918b1 | ||
![]() |
6bdae55ee1 | ||
![]() |
dfb96e4b7f | ||
![]() |
ff2c316b18 | ||
![]() |
5be52f71f9 | ||
![]() |
42873dd37c | ||
![]() |
f93e7d4e3a | ||
![]() |
bbcd523967 | ||
![]() |
68cbe58d00 | ||
![]() |
115bca98f1 | ||
![]() |
ed0b34b2fe | ||
![]() |
ab34401421 | ||
![]() |
eed0c18d65 | ||
![]() |
83400d0417 | ||
![]() |
77a6461c9d | ||
![]() |
6db9d1122f | ||
![]() |
83bef85415 | ||
![]() |
b5b3914bbf | ||
![]() |
0d90ef94ae | ||
![]() |
c08b21b7cd | ||
![]() |
be3cb9ef00 | ||
![]() |
f7b3f52731 | ||
![]() |
9220d9fc52 | ||
![]() |
68c8547067 | ||
![]() |
1468acfced | ||
![]() |
b141aea4c0 | ||
![]() |
a88c022406 | ||
![]() |
5389382798 | ||
![]() |
4765173778 | ||
![]() |
07a9cb910f | ||
![]() |
f408f074c4 | ||
![]() |
f1f2640d0e | ||
![]() |
27d7d7ca69 | ||
![]() |
c0fc5b48ae | ||
![]() |
8735d3b83e | ||
![]() |
ca59dd1302 | ||
![]() |
eccdef8211 | ||
![]() |
f2ebfe7aef | ||
![]() |
cac5b356db | ||
![]() |
e5a38ce748 | ||
![]() |
7d9d9fcf36 | ||
![]() |
156104d5f5 | ||
![]() |
f0aba6ceb2 | ||
![]() |
c248ba4043 | ||
![]() |
ab07ee57c6 | ||
![]() |
c615dc573a | ||
![]() |
eae3d72a4d | ||
![]() |
7b8d826704 | ||
![]() |
e7baa42e63 | ||
![]() |
2f32833a22 | ||
![]() |
f6935a4b4b | ||
![]() |
332c9e891b | ||
![]() |
1caabb6419 | ||
![]() |
f41f7994a3 | ||
![]() |
e39f314e7a | ||
![]() |
7f34561e53 | ||
![]() |
34606b0f1f | ||
![]() |
c51b509501 | ||
![]() |
b91ee4847f | ||
![]() |
625463d871 | ||
![]() |
6433a01e07 | ||
![]() |
56cc31e8e7 | ||
![]() |
3af297aa76 | ||
![]() |
996ec59d28 | ||
![]() |
95593eeeab | ||
![]() |
dad244fb7a | ||
![]() |
15b5968418 | ||
![]() |
64a45dc6a6 | ||
![]() |
7cfede5b83 | ||
![]() |
e4d17e0b15 | ||
![]() |
e79f7ce290 | ||
![]() |
adb5d27d95 | ||
![]() |
8456a8cecb | ||
![]() |
b9f66373c1 | ||
![]() |
9ac365feef | ||
![]() |
bcc77c73e1 | ||
![]() |
8b11e5aeb1 | ||
![]() |
43bbd58a44 | ||
![]() |
7feffa64f3 | ||
![]() |
ea0977abb4 | ||
![]() |
cb48394e8a | ||
![]() |
4c83dc7c28 | ||
![]() |
e10ab1da78 | ||
![]() |
1b0e60374b | ||
![]() |
3a760fbb44 | ||
![]() |
f5441a87e3 | ||
![]() |
e2a812fa4b | ||
![]() |
5b5ead872b | ||
![]() |
6ef57a2973 | ||
![]() |
3e9c7f2e9f | ||
![]() |
430598b7a1 | ||
![]() |
03cfd78c59 | ||
![]() |
91611b09b4 | ||
![]() |
ecd115851f | ||
![]() |
4a1e50fed1 | ||
![]() |
d6d037047b | ||
![]() |
b5734c2b20 | ||
![]() |
723fb7eaac | ||
![]() |
63a9acaa19 | ||
![]() |
0524f8c677 | ||
![]() |
70b62f272e | ||
![]() |
ced11bc707 | ||
![]() |
6b9c084162 | ||
![]() |
644ce2a26c | ||
![]() |
5425e45851 | ||
![]() |
f0089b7940 | ||
![]() |
4b44280d53 | ||
![]() |
f045382d20 | ||
![]() |
db3fa1ade7 | ||
![]() |
f83950fd75 | ||
![]() |
4dd1bf920d | ||
![]() |
98755f3621 | ||
![]() |
c3a8a044b9 | ||
![]() |
15b5ea43a7 | ||
![]() |
12fce7a08d | ||
![]() |
0991ab3543 | ||
![]() |
65d2b37496 | ||
![]() |
94d518a418 | ||
![]() |
7cca673902 | ||
![]() |
384f8d97d8 | ||
![]() |
c82d5d63e3 | ||
![]() |
653a3d5d11 | ||
![]() |
884b7201de | ||
![]() |
85d2f24447 | ||
![]() |
935992bcb3 | ||
![]() |
dc15d1c8ec | ||
![]() |
6beb9e568a | ||
![]() |
7178f10bda | ||
![]() |
ec683fc227 | ||
![]() |
d4e65eb82a | ||
![]() |
10c6601b0a | ||
![]() |
73940bc1bd | ||
![]() |
9b7fb829f9 | ||
![]() |
c51d8c9021 | ||
![]() |
d8a6dfe5ce | ||
![]() |
5f7cef0b06 | ||
![]() |
48ff2ffc68 | ||
![]() |
b3b9ccd314 | ||
![]() |
1308236429 | ||
![]() |
63d6b610b8 | ||
![]() |
8823024509 | ||
![]() |
4896f870f0 | ||
![]() |
7e482901d9 | ||
![]() |
07b309e65d | ||
![]() |
6bbb5e9b56 | ||
![]() |
867fecd157 | ||
![]() |
05388d2dfc | ||
![]() |
e06b6d7140 | ||
![]() |
859e508392 | ||
![]() |
534ce11d54 | ||
![]() |
e63c7b483b | ||
![]() |
f57980b069 | ||
![]() |
7006aa0d2a | ||
![]() |
bb86db869a | ||
![]() |
4c4dd23e15 | ||
![]() |
8051c1ca99 | ||
![]() |
fe5a6847b5 | ||
![]() |
a779592414 | ||
![]() |
112215848d | ||
![]() |
3dee057826 | ||
![]() |
4406a08fa7 | ||
![]() |
c33077bc61 | ||
![]() |
34db9d9ef2 | ||
![]() |
1184bbc976 | ||
![]() |
a3eb2a7ee0 | ||
![]() |
d13134135b | ||
![]() |
b4f57972fb | ||
![]() |
6a5eb43454 | ||
![]() |
04ec1c8b56 | ||
![]() |
d7ad155885 | ||
![]() |
85461a752a | ||
![]() |
039fbc677d | ||
![]() |
ea56a39e11 | ||
![]() |
55e9560e74 | ||
![]() |
3cb4b4ca03 | ||
![]() |
11d2866755 | ||
![]() |
2c517e3e8c | ||
![]() |
42739f0b22 | ||
![]() |
a1f9b0d7f2 | ||
![]() |
c3b8c84131 | ||
![]() |
471b82f727 | ||
![]() |
92b85f98e8 | ||
![]() |
c092d92d45 | ||
![]() |
e514a1fcd4 | ||
![]() |
a1b28cb36e | ||
![]() |
3f2d9abfe6 | ||
![]() |
f3ec4b514d | ||
![]() |
fc5798fa71 | ||
![]() |
95d7ad543f | ||
![]() |
d9b2903d78 | ||
![]() |
32a664eedc | ||
![]() |
e7477890cf | ||
![]() |
9bf72ff05f | ||
![]() |
5461f87ff0 | ||
![]() |
1c58b17235 | ||
![]() |
d34a1c3ed6 | ||
![]() |
22e3bc7cfe | ||
![]() |
955c96731e | ||
![]() |
54a173dbf1 | ||
![]() |
9ff8240802 | ||
![]() |
7bbb5213f3 | ||
![]() |
b8b30599ee | ||
![]() |
e083d7f4d0 | ||
![]() |
a57580b5ab | ||
![]() |
e22f1fc044 | ||
![]() |
e09ee8f23d | ||
![]() |
6ec546a6a4 | ||
![]() |
877367677b | ||
![]() |
8be4086224 | ||
![]() |
871d3b66fb | ||
![]() |
87358e8843 | ||
![]() |
5c06cd8eb3 | ||
![]() |
46b4c970d1 | ||
![]() |
49f46a7cdd | ||
![]() |
1627dff166 | ||
![]() |
cee08debff | ||
![]() |
912793eddf | ||
![]() |
eaa5200a35 | ||
![]() |
a7687c3e17 | ||
![]() |
932e0469f7 | ||
![]() |
15ab8918af | ||
![]() |
d0dfc94a61 | ||
![]() |
5a2984d03a | ||
![]() |
c89018a431 | ||
![]() |
1031ea4313 | ||
![]() |
5b0fbbaada | ||
![]() |
0e4f1ac40d | ||
![]() |
946db3fd50 | ||
![]() |
3dfc8d4291 | ||
![]() |
4f5e4f3b86 | ||
![]() |
505d1d78fb | ||
![]() |
7af1c04493 | ||
![]() |
855c98d815 | ||
![]() |
c26ea7e4e0 | ||
![]() |
c39ac9edfe | ||
![]() |
af04f565cf | ||
![]() |
2b9054d3b2 | ||
![]() |
c83ecf764d | ||
![]() |
a2485a18cb | ||
![]() |
8ef2ad17b5 | ||
![]() |
4579f78bf9 | ||
![]() |
1853407645 | ||
![]() |
cb5efc1c42 | ||
![]() |
2234f6aacf | ||
![]() |
be965a60eb | ||
![]() |
5596751c2c | ||
![]() |
6417d8132d | ||
![]() |
5624fafb3a | ||
![]() |
2eb5f89d82 | ||
![]() |
e30f17f64f | ||
![]() |
1ba560dc9e | ||
![]() |
8c86a18dc6 | ||
![]() |
b2d516c70a | ||
![]() |
45940b0514 | ||
![]() |
97e76d64d6 | ||
![]() |
756c6721e9 | ||
![]() |
4c390d9f9f | ||
![]() |
0d0954d74b | ||
![]() |
7672ba2c8d | ||
![]() |
4d28afc153 | ||
![]() |
7246f42a8e | ||
![]() |
bdcffc7ba9 | ||
![]() |
95a6715b2b | ||
![]() |
5342edf04a | ||
![]() |
d344b1ca0e | ||
![]() |
278863d027 | ||
![]() |
8503e08ee6 | ||
![]() |
aec02afcdc | ||
![]() |
52dd79691b | ||
![]() |
aea2491fa4 | ||
![]() |
963b28181f | ||
![]() |
210a9a4162 | ||
![]() |
a27a884191 | ||
![]() |
17dcba8f8a | ||
![]() |
ea6a7a22ff | ||
![]() |
5ddba719c5 | ||
![]() |
b398d826c1 | ||
![]() |
edb557f79e | ||
![]() |
f463cd98f8 | ||
![]() |
262d69308d | ||
![]() |
0406e27100 | ||
![]() |
ed3ad615d8 | ||
![]() |
66761ff340 | ||
![]() |
8bebf138ee | ||
![]() |
fd836e982e | ||
![]() |
e32722db70 | ||
![]() |
b20760c93c | ||
![]() |
654e31124e | ||
![]() |
8e36e1b92e | ||
![]() |
9fe7b08874 | ||
![]() |
f1364d4af4 | ||
![]() |
ed593544d8 | ||
![]() |
0929a0f8aa | ||
![]() |
13b3412b45 | ||
![]() |
888e315553 | ||
![]() |
11daabc9c2 | ||
![]() |
40e0100c1e | ||
![]() |
c51352d04d | ||
![]() |
c8a8acd46e | ||
![]() |
bbac1534a3 | ||
![]() |
637b55bfbf | ||
![]() |
92a24d52be | ||
![]() |
491f8cc611 | ||
![]() |
71fc61117b | ||
![]() |
24f445dade | ||
![]() |
7c884329eb | ||
![]() |
bac58bba4d | ||
![]() |
250bf3f054 | ||
![]() |
e65a7d887f | ||
![]() |
ac0d921413 | ||
![]() |
1e8e471dec | ||
![]() |
2d7f8b3bdf | ||
![]() |
7452ef23b1 | ||
![]() |
9ebe075f9b | ||
![]() |
3052c64dd7 | ||
![]() |
5e345783bd | ||
![]() |
81685573e1 | ||
![]() |
945ed5d3bd | ||
![]() |
fff5ba03c2 | ||
![]() |
82eca13d7b | ||
![]() |
5f21b925da | ||
![]() |
272ceadbb0 | ||
![]() |
d26c2b1a44 | ||
![]() |
8bda8e5393 | ||
![]() |
954b8a0cff | ||
![]() |
7c17e72db4 | ||
![]() |
d180aee57f | ||
![]() |
e3ffecefc0 | ||
![]() |
4c61cf153c | ||
![]() |
c78fb90e2f | ||
![]() |
a990898256 | ||
![]() |
c60c618204 | ||
![]() |
53bd197c44 | ||
![]() |
dbb195691b | ||
![]() |
50da630811 | ||
![]() |
30eca885c9 | ||
![]() |
f76685fccf | ||
![]() |
68d547595e | ||
![]() |
64341d1d18 | ||
![]() |
2e49039c01 | ||
![]() |
321504cf29 | ||
![]() |
0f4a7bf1f5 | ||
![]() |
711e74a12b | ||
![]() |
8f3a739da7 | ||
![]() |
aed140d802 | ||
![]() |
c69b88bb55 | ||
![]() |
c6dc8a11e2 | ||
![]() |
6366ff6421 | ||
![]() |
607ddaa632 | ||
![]() |
d281e59f3a | ||
![]() |
2db8c42e1d | ||
![]() |
aa8eb2c92a | ||
![]() |
b422a63b2a | ||
![]() |
ad5f2cd748 | ||
![]() |
efae363739 | ||
![]() |
2d79d21c50 | ||
![]() |
3b9d126322 | ||
![]() |
0ea77de98c | ||
![]() |
19014331d8 | ||
![]() |
b276ac0588 | ||
![]() |
de33cbd7e7 | ||
![]() |
103ba4c696 | ||
![]() |
5a90b83f63 | ||
![]() |
716039e452 | ||
![]() |
896654aaef | ||
![]() |
5fad38f65f | ||
![]() |
89f2ea5725 | ||
![]() |
a32ad33b4e | ||
![]() |
a328fff5a7 | ||
![]() |
233783c76c | ||
![]() |
39a18fb358 | ||
![]() |
460a144ca8 | ||
![]() |
23ead416d5 | ||
![]() |
1b5f11bbee | ||
![]() |
4cc2817fcd | ||
![]() |
d437cc915c | ||
![]() |
dd3f2f6c7e | ||
![]() |
5f61897bec | ||
![]() |
c5d26a5b4a | ||
![]() |
855112dfc3 | ||
![]() |
0da97289e6 | ||
![]() |
b9767bdcbc | ||
![]() |
91f12a50cf | ||
![]() |
e92a9d1d9e | ||
![]() |
4eb51ab4d6 | ||
![]() |
e6b0a0ca2b | ||
![]() |
924df1e7de | ||
![]() |
ed7983af41 | ||
![]() |
40c474cd83 | ||
![]() |
a2d2863c72 | ||
![]() |
133a17d6eb | ||
![]() |
fe47ddc27a | ||
![]() |
aad03f1bf5 | ||
![]() |
a4867a00ea | ||
![]() |
e0cff214b2 | ||
![]() |
c6109024aa | ||
![]() |
4e308f551c | ||
![]() |
8a2b1d9359 | ||
![]() |
63a186bdf9 | ||
![]() |
d594a6fcbc | ||
![]() |
e18dfdd656 | ||
![]() |
3aa107142b | ||
![]() |
0cd24c629a | ||
![]() |
f31e0532c4 | ||
![]() |
f0b6aabc96 | ||
![]() |
97a18717e6 | ||
![]() |
ede1de9021 | ||
![]() |
f1a8d957f8 | ||
![]() |
9821a3442b | ||
![]() |
87842e097b | ||
![]() |
7dd40e2014 | ||
![]() |
3d71e2e189 | ||
![]() |
affaaf7d2c | ||
![]() |
a719998220 | ||
![]() |
bad161a5c1 | ||
![]() |
09a6fdf1c7 | ||
![]() |
d2616cbdfc | ||
![]() |
faf1c8bee8 | ||
![]() |
f09aca4865 | ||
![]() |
cc52f37933 | ||
![]() |
e5051eefbc | ||
![]() |
9e5cd0da51 | ||
![]() |
4e120a291e | ||
![]() |
2790d72bff | ||
![]() |
e44f447d85 | ||
![]() |
4356581db0 | ||
![]() |
f87a701b28 | ||
![]() |
fa2eb46cd6 | ||
![]() |
f924e80f43 | ||
![]() |
6180ee8065 | ||
![]() |
1be106c0b5 | ||
![]() |
b0533db2eb | ||
![]() |
dba502c756 | ||
![]() |
d9cb64b893 | ||
![]() |
2d91e6b977 | ||
![]() |
4a6f1f150a | ||
![]() |
7f76f3726f | ||
![]() |
e2d97b6f36 | ||
![]() |
2a653642f5 | ||
![]() |
97eba1eecc | ||
![]() |
ff6bed54c6 | ||
![]() |
f9b0666adf | ||
![]() |
ca12b8aa56 | ||
![]() |
77508f7e44 | ||
![]() |
54de0ca0da | ||
![]() |
f364788c03 | ||
![]() |
00aaf84c37 | ||
![]() |
b01bc76dc5 | ||
![]() |
910f812737 | ||
![]() |
a4d024f43d | ||
![]() |
9937ad7fa0 | ||
![]() |
edcd88123d | ||
![]() |
ea1b5e19f0 | ||
![]() |
54337befc2 | ||
![]() |
140ef791aa | ||
![]() |
58350b6c99 | ||
![]() |
f186ff8b46 | ||
![]() |
03190611bb | ||
![]() |
37f322585e | ||
![]() |
9218e85bd6 | ||
![]() |
f923ba87c0 | ||
![]() |
fac49896df | ||
![]() |
56225701f9 | ||
![]() |
b5de43b225 | ||
![]() |
b955527f6c | ||
![]() |
94b28102f5 | ||
![]() |
de871862a8 | ||
![]() |
3be56fd502 | ||
![]() |
5086cd716f | ||
![]() |
4937af0cd9 | ||
![]() |
877a5fda41 | ||
![]() |
39cd2838df | ||
![]() |
565473c90c | ||
![]() |
1fac91a659 | ||
![]() |
0a4837c1f0 | ||
![]() |
e7404183a0 | ||
![]() |
44f8dcfb6e | ||
![]() |
481e0e98f8 | ||
![]() |
9de40c26eb | ||
![]() |
ad953f02d1 | ||
![]() |
3869e56521 | ||
![]() |
63d87b17aa | ||
![]() |
ed68a0e773 | ||
![]() |
e2640c8368 | ||
![]() |
eff626248f | ||
![]() |
ce29a3b07a | ||
![]() |
1b89174558 | ||
![]() |
1c1ad32610 | ||
![]() |
71237e2f76 | ||
![]() |
518c271eba | ||
![]() |
d71996e58d | ||
![]() |
2f33cd2db5 | ||
![]() |
5ec9bb0fb5 | ||
![]() |
8cc3cbb22e | ||
![]() |
b0fa317302 | ||
![]() |
5cb56bc677 | ||
![]() |
21f8fd9fa5 | ||
![]() |
2100ef63a9 | ||
![]() |
29db77c9c9 | ||
![]() |
2e59ad90cc | ||
![]() |
1b8c9edcde | ||
![]() |
d4c2a85f9c | ||
![]() |
8c75b87e94 | ||
![]() |
409d4b9d47 | ||
![]() |
4e3b95d120 | ||
![]() |
61a9c9fa33 | ||
![]() |
9c605f2d46 | ||
![]() |
44bb5a89c8 | ||
![]() |
cbdb96f105 | ||
![]() |
9ee3463d07 | ||
![]() |
f0b14055b6 | ||
![]() |
fbd9e87b51 | ||
![]() |
edb3b77916 | ||
![]() |
ebaa84617f | ||
![]() |
8eb18995cb | ||
![]() |
ebabf0e7d8 | ||
![]() |
607e1f823d | ||
![]() |
3b52a306cd | ||
![]() |
0c370d5897 | ||
![]() |
8bf0448f41 | ||
![]() |
14e04eb231 | ||
![]() |
1be9bac3a9 | ||
![]() |
02b5a3efb8 | ||
![]() |
bd457f64d8 | ||
![]() |
9efeea14f2 | ||
![]() |
9b48ff5775 | ||
![]() |
117b58ebe6 | ||
![]() |
303b699005 | ||
![]() |
9173da0416 | ||
![]() |
d2cd65f5db | ||
![]() |
2735f96516 | ||
![]() |
6847645782 | ||
![]() |
b0bc898278 | ||
![]() |
c0f6af7213 | ||
![]() |
5edebaf468 | ||
![]() |
d436409153 | ||
![]() |
8c41fc2b1d | ||
![]() |
46f17bea66 | ||
![]() |
11477dbc03 | ||
![]() |
947c104eff | ||
![]() |
e5366dbbe7 | ||
![]() |
d3375193a9 | ||
![]() |
6144ce1fe0 | ||
![]() |
1771e673d2 | ||
![]() |
d258e06fd7 | ||
![]() |
854f4a8896 | ||
![]() |
f94c221a9a | ||
![]() |
6a2f0f5143 | ||
![]() |
183e2a8471 | ||
![]() |
c6c2842bdb | ||
![]() |
f26767b65e | ||
![]() |
98d32876b5 | ||
![]() |
e5d0f3c036 | ||
![]() |
cc15aaacbb | ||
![]() |
553df1d57b | ||
![]() |
b92311402a | ||
![]() |
93796491af | ||
![]() |
c038cf27a7 | ||
![]() |
1f42d32eb5 | ||
![]() |
06bde559da | ||
![]() |
922f7167f5 | ||
![]() |
90c0d3e12f | ||
![]() |
bf5f846fc6 | ||
![]() |
926bcc71ae | ||
![]() |
ea4a458214 | ||
![]() |
b3ae3e1feb | ||
![]() |
fe7af21c91 | ||
![]() |
29f72037fe | ||
![]() |
1d6b4bfcef | ||
![]() |
5bfac5ec09 | ||
![]() |
dfffaace26 | ||
![]() |
1d5f628c7a | ||
![]() |
cb8a6f66fa | ||
![]() |
cb21c7c18d | ||
![]() |
0d104776bc | ||
![]() |
5f27757039 | ||
![]() |
532907219b | ||
![]() |
eeaba74553 | ||
![]() |
dd637582a4 | ||
![]() |
b0d12aeea1 | ||
![]() |
bdbd813455 | ||
![]() |
a6fac2b175 | ||
![]() |
5ce923ea90 | ||
![]() |
29f0508dc2 | ||
![]() |
3ffa59f0cd | ||
![]() |
790d6ef94c | ||
![]() |
7828f48b9a | ||
![]() |
768c71830b | ||
![]() |
ceb0564ebf | ||
![]() |
20f7eb7327 | ||
![]() |
441d5bd44d | ||
![]() |
9fa19df2ff | ||
![]() |
39f64f597e | ||
![]() |
160429eb24 | ||
![]() |
6516c64e67 | ||
![]() |
4c8a703084 | ||
![]() |
335210d788 | ||
![]() |
9b04e657db | ||
![]() |
f7311aa025 | ||
![]() |
fb24e55c8d | ||
![]() |
b58ca46a46 | ||
![]() |
76991cdcc4 | ||
![]() |
69c7cf783e | ||
![]() |
f751c3828e | ||
![]() |
5c65f9f9ad | ||
![]() |
81ae6709e4 | ||
![]() |
593a3d48fb | ||
![]() |
a8b90283d8 | ||
![]() |
80076f935d | ||
![]() |
9fbb3659a6 | ||
![]() |
fee446c28a | ||
![]() |
1d56f0b035 | ||
![]() |
34e8979d40 | ||
![]() |
2966a62429 | ||
![]() |
5983ccc55c | ||
![]() |
de382b704c | ||
![]() |
16dbbfabc6 | ||
![]() |
af8d04818d | ||
![]() |
ee19ef1aac | ||
![]() |
5e2d4e332a | ||
![]() |
c6c857dfff | ||
![]() |
8dbac20f8b | ||
![]() |
341fddb9aa | ||
![]() |
456824669f | ||
![]() |
62f3039d82 | ||
![]() |
be4c718859 | ||
![]() |
c2f9ed7c59 | ||
![]() |
bfac6607d1 | ||
![]() |
513066ba52 | ||
![]() |
316777f757 | ||
![]() |
246950159d | ||
![]() |
31d6a54b06 | ||
![]() |
5c3a6164bb | ||
![]() |
1652914d39 | ||
![]() |
618cfd9ec5 | ||
![]() |
f97cfe9916 | ||
![]() |
b9259a0238 | ||
![]() |
5abbe385c5 | ||
![]() |
e43dcded62 | ||
![]() |
887081fd71 | ||
![]() |
71ded24fce | ||
![]() |
1e2a9e8348 | ||
![]() |
64a3aa7092 | ||
![]() |
fda8dd4ce3 | ||
![]() |
1efabd27d8 | ||
![]() |
caa651e55b | ||
![]() |
b0a3891498 | ||
![]() |
2a9e3d84fd | ||
![]() |
a3dcac62f9 | ||
![]() |
6b535b11f8 | ||
![]() |
d9f09a7523 | ||
![]() |
159744e09e | ||
![]() |
c2637a76f7 | ||
![]() |
237edd75d1 | ||
![]() |
a34d5e3901 | ||
![]() |
1dd43a75f2 | ||
![]() |
1f5cbca509 | ||
![]() |
3749c11f21 | ||
![]() |
66cdb761dc | ||
![]() |
f0d9ad6a4e | ||
![]() |
03e317d052 | ||
![]() |
ba461e51a8 | ||
![]() |
80949521b6 | ||
![]() |
acbb8e9fd0 | ||
![]() |
90394a50df | ||
![]() |
5379794f16 | ||
![]() |
0a32321c85 | ||
![]() |
c9062599df | ||
![]() |
10a6e9b4ee | ||
![]() |
4b8ec44262 | ||
![]() |
bd74ed4bc0 | ||
![]() |
fc42f14448 | ||
![]() |
d01f296420 | ||
![]() |
27112e2ace | ||
![]() |
837930234f | ||
![]() |
e19aa3bbe0 | ||
![]() |
35b5c1ed56 | ||
![]() |
3e65e6c69a | ||
![]() |
3b3297d269 | ||
![]() |
fc0deb642a | ||
![]() |
9f2b2f51ff | ||
![]() |
c9d93ff685 | ||
![]() |
fa72990a63 | ||
![]() |
e5afb1c4ea | ||
![]() |
73ead5f328 | ||
![]() |
5c57b51378 | ||
![]() |
e25935ef21 | ||
![]() |
c7a52c3894 | ||
![]() |
01a4b4e82f | ||
![]() |
766866197b | ||
![]() |
9b5a3cbcd3 | ||
![]() |
d2ed3b9bec | ||
![]() |
99d2db42cd | ||
![]() |
7619507e6c | ||
![]() |
53a4689ed1 | ||
![]() |
0a82e6e792 | ||
![]() |
98855e4123 | ||
![]() |
71d9d64a02 | ||
![]() |
6a09d7c49b | ||
![]() |
46e50ba53f | ||
![]() |
f1e3ff2ed2 | ||
![]() |
7787fa8f29 | ||
![]() |
70902029f8 | ||
![]() |
4f9a56c884 | ||
![]() |
3715ba030b | ||
![]() |
2e49fd7b48 | ||
![]() |
06912b492f | ||
![]() |
442e58b07a | ||
![]() |
799f04efc0 | ||
![]() |
cc7dbeada6 | ||
![]() |
45d368e3a1 | ||
![]() |
628a94bad3 | ||
![]() |
0c93be97a9 | ||
![]() |
54eb6070fb | ||
![]() |
4dbf1c521e | ||
![]() |
0651716b96 | ||
![]() |
f30b8f6b3c | ||
![]() |
0992609bf4 | ||
![]() |
18c08f24ad | ||
![]() |
a7f53aea0e | ||
![]() |
c399905675 | ||
![]() |
5cb0c11feb | ||
![]() |
08b67e7aea | ||
![]() |
07ae8ec553 | ||
![]() |
04c3a43c17 | ||
![]() |
b632344596 | ||
![]() |
fb8ec79a52 | ||
![]() |
7dd16df846 | ||
![]() |
551e9c6111 | ||
![]() |
cc9f0b3f47 | ||
![]() |
d77c3abdc0 | ||
![]() |
dd37a4e04c | ||
![]() |
1f5c79bd17 | ||
![]() |
623570a117 | ||
![]() |
cdbc146e5d | ||
![]() |
7ae611256a | ||
![]() |
b62c47fede | ||
![]() |
99f497c3b8 | ||
![]() |
4f88c2489b | ||
![]() |
294ba1fca7 | ||
![]() |
be61b38a2c | ||
![]() |
f9797825ad | ||
![]() |
fd4b7d4588 | ||
![]() |
062cedc200 | ||
![]() |
79b9d0579d | ||
![]() |
ab31117bf3 | ||
![]() |
d31040f5d8 | ||
![]() |
52d19fa43d | ||
![]() |
8ca34f7098 | ||
![]() |
4c4099966a | ||
![]() |
86ac7f3a59 | ||
![]() |
9e400a7857 | ||
![]() |
d5278351da | ||
![]() |
36861595f1 | ||
![]() |
d604321f37 | ||
![]() |
964ab65497 | ||
![]() |
3b940b1c04 | ||
![]() |
a91e6a6bdf | ||
![]() |
8600620305 | ||
![]() |
96721f305f | ||
![]() |
2bf70d7d00 | ||
![]() |
1d8c170f48 | ||
![]() |
6009c7edb4 | ||
![]() |
e3f36c033e | ||
![]() |
d4eb0f1655 | ||
![]() |
5fca480921 | ||
![]() |
7051f897bc | ||
![]() |
2cb3015a28 | ||
![]() |
d0859a7d33 | ||
![]() |
e20ec00071 | ||
![]() |
150114d774 | ||
![]() |
89dfa5ea82 | ||
![]() |
61ebc629f6 | ||
![]() |
32f2da77f8 | ||
![]() |
bfca3f242a | ||
![]() |
3dfff2930a | ||
![]() |
027e0de48e | ||
![]() |
c811141a4f | ||
![]() |
871c0ee2a5 | ||
![]() |
b8a7741c61 | ||
![]() |
97aa930ad2 | ||
![]() |
2a5def10e7 | ||
![]() |
969834e037 | ||
![]() |
d73a44c504 | ||
![]() |
8aec092ab6 | ||
![]() |
4fa959ba45 | ||
![]() |
b6011b9353 | ||
![]() |
40a5005d94 | ||
![]() |
c5eba21ff6 | ||
![]() |
4891cfef56 | ||
![]() |
4395664547 | ||
![]() |
04d926af39 | ||
![]() |
f9a31c1abb | ||
![]() |
b43712d78d | ||
![]() |
01904a0f10 | ||
![]() |
dd875e7529 | ||
![]() |
f1dcf0f0b8 | ||
![]() |
a045d001bf | ||
![]() |
066c1022d0 | ||
![]() |
dca1c0f160 | ||
![]() |
2419bc3678 | ||
![]() |
c19b3ecd43 | ||
![]() |
ef1e91d838 | ||
![]() |
e5929225eb | ||
![]() |
59c192becc | ||
![]() |
a800816750 | ||
![]() |
99d9ab4e40 | ||
![]() |
f310ca1b74 | ||
![]() |
f763daa577 | ||
![]() |
607c3ae651 | ||
![]() |
970563e07b | ||
![]() |
e006045f59 | ||
![]() |
fff3645901 | ||
![]() |
a5383fd208 | ||
![]() |
5591832b50 | ||
![]() |
9ce3a2059f | ||
![]() |
69e6cf2c0c | ||
![]() |
28635124f9 | ||
![]() |
25b116048c | ||
![]() |
035be87a83 | ||
![]() |
e8bdbc45a9 | ||
![]() |
429caccefa | ||
![]() |
744ca1af7c | ||
![]() |
106f0d611f | ||
![]() |
d826416684 | ||
![]() |
24ba9eba46 | ||
![]() |
424c34225f | ||
![]() |
d781f3a11b | ||
![]() |
a80f9ed336 | ||
![]() |
92bbedfa5a | ||
![]() |
86710ed483 | ||
![]() |
da7eb9ac90 | ||
![]() |
c411043681 | ||
![]() |
93f8ee7e60 | ||
![]() |
0efc1f06f2 | ||
![]() |
81959804df | ||
![]() |
75b524ddc4 | ||
![]() |
f599c36272 | ||
![]() |
9bb64315f3 | ||
![]() |
575badc690 | ||
![]() |
4b91cfb7f9 | ||
![]() |
9ad9d64ac7 | ||
![]() |
5a2cfa2798 | ||
![]() |
eb24da7c82 | ||
![]() |
f93e261d75 | ||
![]() |
501f88ca86 | ||
![]() |
360effcb72 | ||
![]() |
eb9bd69405 | ||
![]() |
11b8210e36 | ||
![]() |
a57a842f7b | ||
![]() |
a8c253a2a5 | ||
![]() |
8b737aabd9 | ||
![]() |
0db4815f3d | ||
![]() |
139db58a66 | ||
![]() |
d23376b81e | ||
![]() |
99d90845b5 | ||
![]() |
ea0127e42b | ||
![]() |
c32fec7432 | ||
![]() |
8bd23dd457 | ||
![]() |
97a12c0169 | ||
![]() |
635916737b | ||
![]() |
65c50e4f01 | ||
![]() |
5cf18235e3 | ||
![]() |
b80f3fdec9 | ||
![]() |
0426be9280 | ||
![]() |
7c678659d4 | ||
![]() |
13fe9e83fa | ||
![]() |
4711f36a1f | ||
![]() |
01e2a51132 | ||
![]() |
a70a205ace | ||
![]() |
33625e2dd3 | ||
![]() |
0277218319 | ||
![]() |
18a8c727fa | ||
![]() |
80ad784a4e | ||
![]() |
ebadaa9660 | ||
![]() |
7bc51582f0 | ||
![]() |
11fb54c74e | ||
![]() |
913ac8b7e8 | ||
![]() |
2b9350ce76 | ||
![]() |
3b18f1b87f | ||
![]() |
c5c24c1989 | ||
![]() |
c3938d04f3 | ||
![]() |
f968713be8 | ||
![]() |
7b11284008 | ||
![]() |
f39c0d52ee | ||
![]() |
a3756a9600 | ||
![]() |
afa436fe8f | ||
![]() |
48b5ea9e59 | ||
![]() |
56974153f1 | ||
![]() |
9a2cd71571 | ||
![]() |
d1c6368283 | ||
![]() |
5c3268b8d4 | ||
![]() |
25af5ab7c6 | ||
![]() |
4d586b1446 | ||
![]() |
bb759d52c8 | ||
![]() |
9a2cf05c5f | ||
![]() |
c79d700d03 | ||
![]() |
482a3aebc9 | ||
![]() |
387f249363 | ||
![]() |
3d917d0b7e | ||
![]() |
824f3187ac | ||
![]() |
e3c27a483c | ||
![]() |
a33bb32874 | ||
![]() |
93d9d4b50a | ||
![]() |
2376a2c941 | ||
![]() |
b92702a312 | ||
![]() |
b11d5f6799 | ||
![]() |
072dce340e | ||
![]() |
cccb1a2c9e | ||
![]() |
063d9c47a4 | ||
![]() |
8d8d421286 | ||
![]() |
0ce57e5a39 | ||
![]() |
aebad04c0b | ||
![]() |
514d11d46f | ||
![]() |
96e46db272 | ||
![]() |
76f78877f6 | ||
![]() |
4ffa68b773 | ||
![]() |
8eaffee160 | ||
![]() |
557a622f71 | ||
![]() |
e9c6556296 | ||
![]() |
dce9d59dfe | ||
![]() |
d3e291b442 | ||
![]() |
d4686c0fb1 | ||
![]() |
ae9b247f47 | ||
![]() |
a59761d292 | ||
![]() |
030c87d142 | ||
![]() |
95ed3e9d46 | ||
![]() |
d0eaebe19f | ||
![]() |
9ecead2645 | ||
![]() |
98166dfa66 | ||
![]() |
5645be4e0f | ||
![]() |
9a7a205510 | ||
![]() |
7e3b8fd346 | ||
![]() |
3d6dcc9eee | ||
![]() |
4f6982fbc5 | ||
![]() |
00c144daeb | ||
![]() |
fddb05c845 | ||
![]() |
3b13e62d3f | ||
![]() |
9e7acd6f75 | ||
![]() |
7d9c043f1d | ||
![]() |
bafbeefb37 | ||
![]() |
54660300e9 | ||
![]() |
5dc40049be | ||
![]() |
1e46b4073f | ||
![]() |
29fc4af0fc | ||
![]() |
4030a2e253 | ||
![]() |
ea80cb751b | ||
![]() |
6aa61dbce7 | ||
![]() |
cdc9c99d40 | ||
![]() |
07f2931841 | ||
![]() |
616ad04131 | ||
![]() |
b966e58f9e | ||
![]() |
a546677b08 | ||
![]() |
5c3af1d3f6 | ||
![]() |
66b0b6feeb | ||
![]() |
7665a220a0 | ||
![]() |
4250af4dd9 | ||
![]() |
73252ccd25 | ||
![]() |
33bf78c369 | ||
![]() |
f33c2a48eb | ||
![]() |
96ded4e402 | ||
![]() |
44d5be6e7b | ||
![]() |
076124eb71 | ||
![]() |
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 |
@@ -49,7 +49,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
|
87
.clang-tidy
87
.clang-tidy
@@ -2,17 +2,29 @@
|
||||
Checks: >-
|
||||
*,
|
||||
-abseil-*,
|
||||
-altera-*,
|
||||
-android-*,
|
||||
-boost-*,
|
||||
-bugprone-macro-parentheses,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-signed-char-misuse,
|
||||
-cert-dcl50-cpp,
|
||||
-cert-err58-cpp,
|
||||
-clang-analyzer-core.CallAndMessage,
|
||||
-cert-oop57-cpp,
|
||||
-cert-str34-c,
|
||||
-clang-analyzer-optin.cplusplus.UninitializedObject,
|
||||
-clang-analyzer-osx.*,
|
||||
-clang-analyzer-security.*,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-c-copy-assignment-signature,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-clang-diagnostic-delete-abstract-non-virtual-dtor,
|
||||
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
|
||||
-clang-diagnostic-shadow-field,
|
||||
-clang-diagnostic-unused-const-variable,
|
||||
-clang-diagnostic-unused-parameter,
|
||||
-concurrency-*,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-init-variables,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-narrowing-conversions,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
@@ -24,42 +36,49 @@ Checks: >-
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-cppcoreguidelines-special-member-functions,
|
||||
-fuchsia-*,
|
||||
-fuchsia-default-arguments,
|
||||
-fuchsia-multiple-inheritance,
|
||||
-fuchsia-overloaded-operator,
|
||||
-fuchsia-statically-constructed-objects,
|
||||
-fuchsia-default-arguments-declarations,
|
||||
-fuchsia-default-arguments-calls,
|
||||
-google-build-using-namespace,
|
||||
-google-explicit-constructor,
|
||||
-google-readability-braces-around-statements,
|
||||
-google-readability-casting,
|
||||
-google-readability-namespace-comments,
|
||||
-google-readability-todo,
|
||||
-google-runtime-int,
|
||||
-google-runtime-references,
|
||||
-hicpp-*,
|
||||
-llvm-else-after-return,
|
||||
-llvm-header-guard,
|
||||
-llvm-include-order,
|
||||
-misc-unconventional-assign-operator,
|
||||
-llvm-qualified-auto,
|
||||
-llvmlibc-*,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-no-recursion,
|
||||
-misc-unused-parameters,
|
||||
-modernize-deprecated-headers,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-avoid-bind,
|
||||
-modernize-concat-nested-namespaces,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-use-auto,
|
||||
-modernize-use-default-member-init,
|
||||
-modernize-use-equals-default,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-use-nodiscard,
|
||||
-mpi-*,
|
||||
-objc-*,
|
||||
-performance-unnecessary-value-param,
|
||||
-readability-braces-around-statements,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-else-after-return,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-named-parameter,
|
||||
-readability-redundant-member-init,
|
||||
-warnings-as-errors,
|
||||
-zircon-*
|
||||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers,
|
||||
-readability-make-member-function-const,
|
||||
-readability-redundant-string-init,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-use-anyofallof,
|
||||
WarningsAsErrors: '*'
|
||||
HeaderFilterRegex: '^.*/src/esphome/.*'
|
||||
AnalyzeTemporaryDtors: false
|
||||
FormatStyle: google
|
||||
CheckOptions:
|
||||
@@ -67,9 +86,11 @@ CheckOptions:
|
||||
value: '1'
|
||||
- key: google-readability-function-size.StatementThreshold
|
||||
value: '800'
|
||||
- key: google-readability-namespace-comments.ShortNamespaceLines
|
||||
- key: google-runtime-int.TypeSuffix
|
||||
value: '_t'
|
||||
- key: llvm-namespace-comment.ShortNamespaceLines
|
||||
value: '10'
|
||||
- key: google-readability-namespace-comments.SpacesBeforeComments
|
||||
- key: llvm-namespace-comment.SpacesBeforeComments
|
||||
value: '2'
|
||||
- key: modernize-loop-convert.MaxCopySize
|
||||
value: '16'
|
||||
@@ -83,6 +104,12 @@ CheckOptions:
|
||||
value: llvm
|
||||
- key: modernize-use-nullptr.NullMacros
|
||||
value: 'NULL'
|
||||
- key: modernize-make-unique.MakeSmartPtrFunction
|
||||
value: 'make_unique'
|
||||
- key: modernize-make-unique.MakeSmartPtrFunctionHeader
|
||||
value: 'esphome/core/helpers.h'
|
||||
- key: readability-braces-around-statements.ShortStatementLines
|
||||
value: 2
|
||||
- key: readability-identifier-naming.LocalVariableCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassCase
|
||||
@@ -96,15 +123,19 @@ CheckOptions:
|
||||
- key: readability-identifier-naming.StaticConstantCase
|
||||
value: 'UPPER_CASE'
|
||||
- key: readability-identifier-naming.StaticVariableCase
|
||||
value: 'UPPER_CASE'
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.GlobalConstantCase
|
||||
value: 'UPPER_CASE'
|
||||
- key: readability-identifier-naming.ParameterCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMemberPrefix
|
||||
value: 'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED'
|
||||
- key: readability-identifier-naming.PrivateMethodPrefix
|
||||
value: 'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED'
|
||||
- key: readability-identifier-naming.PrivateMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMemberSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.PrivateMethodCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.PrivateMethodSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.ClassMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassMemberCase
|
||||
@@ -125,3 +156,5 @@ CheckOptions:
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.VirtualMethodSuffix
|
||||
value: ''
|
||||
- key: readability-qualified-auto.AddConstToQualified
|
||||
value: 0
|
||||
|
@@ -1,17 +1,29 @@
|
||||
{
|
||||
"name": "ESPHome Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "../docker/Dockerfile.dev",
|
||||
"postCreateCommand": "mkdir -p config && pip3 install -e .",
|
||||
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
|
||||
"image": "ghcr.io/esphome/esphome-lint:dev",
|
||||
"postCreateCommand": [
|
||||
"script/devcontainer-post-create"
|
||||
],
|
||||
"runArgs": [
|
||||
"--privileged",
|
||||
"-e",
|
||||
"ESPHOME_DASHBOARD_USE_PING=1"
|
||||
],
|
||||
"appPort": 6052,
|
||||
"extensions": [
|
||||
// python
|
||||
"ms-python.python",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"redhat.vscode-yaml"
|
||||
// yaml
|
||||
"redhat.vscode-yaml",
|
||||
// cpp
|
||||
"ms-vscode.cpptools",
|
||||
// editorconfig
|
||||
"editorconfig.editorconfig",
|
||||
],
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.languageServer": "Pylance",
|
||||
"python.pythonPath": "/usr/bin/python3",
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
@@ -19,7 +31,7 @@
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"yaml.customTags": [
|
||||
"!secret scalar",
|
||||
"!lambda scalar",
|
||||
@@ -27,6 +39,18 @@
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
]
|
||||
],
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/*.pyc": {
|
||||
"when": "$(basename).py"
|
||||
},
|
||||
"**/__pycache__": true
|
||||
},
|
||||
"files.associations": {
|
||||
"**/.vscode/*.json": "jsonc"
|
||||
},
|
||||
"C_Cpp.clang_format_path": "/usr/bin/clang-format-11",
|
||||
}
|
||||
}
|
||||
|
@@ -103,6 +103,10 @@ venv.bak/
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# PlatformIO
|
||||
.pio/
|
||||
|
||||
# ESPHome
|
||||
config/
|
||||
examples/
|
||||
Dockerfile
|
||||
|
@@ -7,7 +7,7 @@ insert_final_newline = true
|
||||
charset = utf-8
|
||||
|
||||
# python
|
||||
[*.{py}]
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
@@ -25,4 +25,9 @@ indent_size = 2
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
quote_type = single
|
||||
quote_type = double
|
||||
|
||||
# JSON
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize line endings to LF in the repository
|
||||
* text eol=lf
|
8
.github/FUNDING.yml
vendored
8
.github/FUNDING.yml
vendored
@@ -1,8 +1,4 @@
|
||||
---
|
||||
# These are supported funding model platforms
|
||||
|
||||
github:
|
||||
patreon: ottowinter
|
||||
open_collective:
|
||||
ko_fi:
|
||||
tidelift:
|
||||
custom: https://esphome.io/guides/supporters.html
|
||||
custom: https://www.nabucasa.com
|
||||
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Issue Tracker
|
||||
@@ -5,8 +6,10 @@ contact_links:
|
||||
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.
|
||||
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.
|
||||
|
||||
about: |
|
||||
Please view the FAQ for common questions and what
|
||||
to include in a bug report.
|
||||
|
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,25 +1,24 @@
|
||||
# What does this implement/fix?
|
||||
# What does this implement/fix?
|
||||
|
||||
Quick description
|
||||
<!-- Quick description and explanation of changes -->
|
||||
|
||||
## 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)
|
||||
- [ ] Other
|
||||
|
||||
**Related issue or feature (if applicable):** fixes <link to issue>
|
||||
|
||||
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>
|
||||
|
||||
# Test Environment
|
||||
|
||||
## Test Environment
|
||||
|
||||
- [ ] ESP32
|
||||
- [ ] ESP32 IDF
|
||||
- [ ] ESP8266
|
||||
- [ ] Windows
|
||||
- [ ] Mac OS
|
||||
- [ ] Linux
|
||||
- [ ] RP2040
|
||||
|
||||
## Example entry for `config.yaml`:
|
||||
<!--
|
||||
@@ -34,14 +33,9 @@ Quick description
|
||||
|
||||
```
|
||||
|
||||
# Explain your changes
|
||||
|
||||
Describe your changes here to communicate to the maintainers **why we should accept this pull request**.
|
||||
Very important to fill if no issue linked
|
||||
|
||||
## Checklist:
|
||||
- [ ] The code change is tested and works locally.
|
||||
- [ ] Tests have been added to verify that the new code works (under `tests/` folder).
|
||||
|
||||
|
||||
If user exposed functionality or configuration variables are added/changed:
|
||||
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
||||
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,9 +1,15 @@
|
||||
---
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: daily
|
||||
ignore:
|
||||
# Hypotehsis is only used for testing and is updated quite often
|
||||
- dependency-name: hypothesis
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
|
7
.github/issue-close-app.yml
vendored
7
.github/issue-close-app.yml
vendored
@@ -1,7 +0,0 @@
|
||||
comment: >-
|
||||
https://github.com/esphome/esphome/issues/430
|
||||
issueConfigs:
|
||||
- content:
|
||||
- "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
|
||||
|
||||
caseInsensitive: false
|
36
.github/lock.yml
vendored
36
.github/lock.yml
vendored
@@ -1,36 +0,0 @@
|
||||
# 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
59
.github/stale.yml
vendored
@@ -1,59 +0,0 @@
|
||||
# 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
|
69
.github/workflows/ci-docker.yml
vendored
69
.github/workflows/ci-docker.yml
vendored
@@ -1,17 +1,27 @@
|
||||
---
|
||||
name: CI for docker images
|
||||
|
||||
# Only run when docker paths change
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, master]
|
||||
branches: [dev, beta, release]
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
- "docker/**"
|
||||
- ".github/workflows/**"
|
||||
- "requirements*.txt"
|
||||
- "platformio.ini"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
- "docker/**"
|
||||
- ".github/workflows/**"
|
||||
- "requirements*.txt"
|
||||
- "platformio.ini"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
jobs:
|
||||
check-docker:
|
||||
@@ -21,35 +31,26 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.0.0"
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
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
|
||||
- name: Set TAG
|
||||
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}" \
|
||||
.
|
||||
echo "TAG=check" >> $GITHUB_ENV
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
|
293
.github/workflows/ci.yml
vendored
293
.github/workflows/ci.yml
vendored
@@ -1,160 +1,189 @@
|
||||
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
|
||||
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
|
||||
---
|
||||
name: CI
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
push:
|
||||
# On dev branch release-dev already performs CI checks
|
||||
# On other branches the `pull_request` trigger will be used
|
||||
branches: [beta, master]
|
||||
branches: [dev, beta, release]
|
||||
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
# yamllint disable-line rule:line-length
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-clang-format:
|
||||
ci:
|
||||
name: ${{ matrix.name }}
|
||||
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]
|
||||
include:
|
||||
- id: ci-custom
|
||||
name: Run script/ci-custom
|
||||
- id: lint-python
|
||||
name: Run script/lint-python
|
||||
- id: test
|
||||
file: tests/test1.yaml
|
||||
name: Test tests/test1.yaml
|
||||
pio_cache_key: test1
|
||||
- id: test
|
||||
file: tests/test2.yaml
|
||||
name: Test tests/test2.yaml
|
||||
pio_cache_key: test2
|
||||
- id: test
|
||||
file: tests/test3.yaml
|
||||
name: Test tests/test3.yaml
|
||||
pio_cache_key: test3
|
||||
- id: test
|
||||
file: tests/test4.yaml
|
||||
name: Test tests/test4.yaml
|
||||
pio_cache_key: test4
|
||||
- id: test
|
||||
file: tests/test5.yaml
|
||||
name: Test tests/test5.yaml
|
||||
pio_cache_key: test5
|
||||
- id: test
|
||||
file: tests/test6.yaml
|
||||
name: Test tests/test6.yaml
|
||||
pio_cache_key: test6
|
||||
- id: test
|
||||
file: tests/test7.yaml
|
||||
name: Test tests/test7.yaml
|
||||
pio_cache_key: test7
|
||||
- id: pytest
|
||||
name: Run pytest
|
||||
- id: clang-format
|
||||
name: Run script/clang-format
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP8266
|
||||
options: --environment esp8266-arduino-tidy --grep USE_ESP8266
|
||||
pio_cache_key: tidyesp8266
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 Arduino 1/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 Arduino 2/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 Arduino 3/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 Arduino 4/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 IDF
|
||||
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
||||
pio_cache_key: tidyesp32-idf
|
||||
- id: yamllint
|
||||
name: Run yamllint
|
||||
|
||||
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
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
id: python
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Cache virtualenv
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
path: .venv
|
||||
# yamllint disable-line rule:line-length
|
||||
key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
venv-${{ steps.python.outputs.python-version }}-
|
||||
|
||||
- name: Set up virtualenv
|
||||
# yamllint disable rule:line-length
|
||||
run: |
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -U pip
|
||||
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||
pip install -e .
|
||||
echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
|
||||
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
|
||||
# yamllint enable rule:line-length
|
||||
|
||||
# Use per check platformio cache because checks use different parts
|
||||
- name: Cache platformio
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.platformio
|
||||
# yamllint disable-line rule:line-length
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
if: matrix.id == 'test' || matrix.id == 'clang-tidy'
|
||||
|
||||
- name: Install clang tools
|
||||
run: |
|
||||
sudo apt-get install \
|
||||
clang-format-11 \
|
||||
clang-tidy-11
|
||||
if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
|
||||
|
||||
- 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/pytest.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
|
||||
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
|
||||
- name: Lint Custom
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
script/ci-custom.py
|
||||
script/build_codeowners.py --check
|
||||
if: matrix.id == 'ci-custom'
|
||||
|
||||
- name: Lint Python
|
||||
run: script/lint-python -a
|
||||
if: matrix.id == 'lint-python'
|
||||
|
||||
- run: esphome compile ${{ matrix.file }}
|
||||
if: matrix.id == 'test'
|
||||
env:
|
||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
||||
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
pytest -vv --tb=native tests
|
||||
if: matrix.id == 'pytest'
|
||||
|
||||
# Also run git-diff-index so that the step is marked as failed on
|
||||
# formatting errors, since clang-format doesn't do anything but
|
||||
# change files if -i is passed.
|
||||
- name: Run clang-format
|
||||
run: |
|
||||
script/clang-format -i
|
||||
git diff-index --quiet HEAD --
|
||||
if: matrix.id == 'clang-format'
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
script/clang-tidy --all-headers --fix ${{ matrix.options }}
|
||||
if: matrix.id == 'clang-tidy'
|
||||
env:
|
||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
||||
|
||||
- name: Run yamllint
|
||||
if: matrix.id == 'yamllint'
|
||||
uses: frenck/action-yamllint@v1.3.1
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
# yamllint disable-line rule:line-length
|
||||
if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
|
||||
|
29
.github/workflows/lock.yml
vendored
Normal file
29
.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Lock
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
with:
|
||||
pr-inactive-days: "1"
|
||||
pr-lock-reason: ""
|
||||
exclude-any-pr-labels: keep-open
|
||||
|
||||
issue-inactive-days: "7"
|
||||
issue-lock-reason: ""
|
||||
exclude-any-issue-labels: keep-open
|
2
.github/workflows/matchers/ci-custom.json
vendored
2
.github/workflows/matchers/ci-custom.json
vendored
@@ -4,7 +4,7 @@
|
||||
"owner": "ci-custom",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
|
2
.github/workflows/matchers/gcc.json
vendored
2
.github/workflows/matchers/gcc.json
vendored
@@ -5,7 +5,7 @@
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||
"regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
|
15
.github/workflows/matchers/lint-python.json
vendored
15
.github/workflows/matchers/lint-python.json
vendored
@@ -1,11 +1,22 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "black",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*): (Please format this file with the black formatter)",
|
||||
"file": 1,
|
||||
"message": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "flake8",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
|
||||
"regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"message": 3
|
||||
@@ -17,7 +28,7 @@
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
|
||||
"regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"message": 3
|
||||
|
19
.github/workflows/matchers/pytest.json
vendored
Normal file
19
.github/workflows/matchers/pytest.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "pytest",
|
||||
"fileLocation": "absolute",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$",
|
||||
"file": 1,
|
||||
"line": 2
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s+(.*)$",
|
||||
"message": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
246
.github/workflows/release-dev.yml
vendored
246
.github/workflows/release-dev.yml
vendored
@@ -1,246 +0,0 @@
|
||||
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
|
374
.github/workflows/release.yml
vendored
374
.github/workflows/release.yml
vendored
@@ -1,170 +1,53 @@
|
||||
---
|
||||
name: Publish Release
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
|
||||
|
||||
lint-clang-format:
|
||||
init:
|
||||
name: Initialize build
|
||||
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
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
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
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
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
|
||||
if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
else
|
||||
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
|
||||
today="$(date --utc '+%Y%m%d')"
|
||||
TAG="${TAG}${today}"
|
||||
BRANCH=${GITHUB_REF#refs/heads/}
|
||||
if [[ "$BRANCH" != "dev" ]]; then
|
||||
TAG="${TAG}-${BRANCH}"
|
||||
fi
|
||||
fi
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
# yamllint enable rule:line-length
|
||||
|
||||
deploy-pypi:
|
||||
name: Build and publish to PyPi
|
||||
if: github.repository == 'esphome/esphome'
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: "3.x"
|
||||
- name: Set up python environment
|
||||
run: |
|
||||
script/setup
|
||||
@@ -178,132 +61,95 @@ jobs:
|
||||
run: twine upload dist/*
|
||||
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
name: Build and publish ESPHome ${{ matrix.image.title}}
|
||||
if: github.repository == 'esphome/esphome'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
continue-on-error: ${{ matrix.image.title == 'lint' }}
|
||||
needs: [init]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
image:
|
||||
- title: "ha-addon"
|
||||
suffix: "hassio"
|
||||
target: "hassio"
|
||||
baseimg: "hassio"
|
||||
- title: "docker"
|
||||
suffix: ""
|
||||
target: "docker"
|
||||
baseimg: "docker"
|
||||
- title: "lint"
|
||||
suffix: "lint"
|
||||
target: "lint"
|
||||
baseimg: "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"
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
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
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
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}"
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Always publish to beta tag (also full releases)
|
||||
- name: Publish docker beta tag
|
||||
- name: Generate short tags
|
||||
id: tags
|
||||
run: |
|
||||
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
|
||||
docker push "${BUILD_TO}:beta"
|
||||
docker/generate_tags.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--suffix "${{ matrix.image.suffix }}"
|
||||
|
||||
- if: ${{ !github.event.release.prerelease }}
|
||||
name: Publish docker latest tag
|
||||
run: |
|
||||
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
|
||||
docker push "${BUILD_TO}:latest"
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
target: ${{ matrix.image.target }}
|
||||
push: true
|
||||
# yamllint disable rule:line-length
|
||||
cache-from: type=registry,ref=ghcr.io/${{ steps.tags.outputs.image }}:cache-${{ steps.tags.outputs.channel }}
|
||||
cache-to: type=registry,ref=ghcr.io/${{ steps.tags.outputs.image }}:cache-${{ steps.tags.outputs.channel }},mode=max
|
||||
# yamllint enable rule:line-length
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
build-args: |
|
||||
BASEIMGTYPE=${{ matrix.image.baseimg }}
|
||||
BUILD_VERSION=${{ needs.init.outputs.tag }}
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
deploy-ha-addon-repo:
|
||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||
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\"}}"
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
repo: "home-assistant-addon",
|
||||
workflow_id: "bump-version.yml",
|
||||
ref: "main",
|
||||
inputs: {
|
||||
version: "${{ github.event.release.tag_name }}",
|
||||
content: ${{ toJSON(github.event.release.body) }}
|
||||
}
|
||||
})
|
||||
|
51
.github/workflows/stale.yml
vendored
Normal file
51
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: Stale
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
remove-stale-when-updated: true
|
||||
stale-pr-label: "stale"
|
||||
exempt-pr-labels: "no-stale"
|
||||
stale-pr-message: >
|
||||
There hasn't been any activity on this pull request recently. This
|
||||
pull request has been automatically marked as stale because of that
|
||||
and will be closed if no further activity occurs within 7 days.
|
||||
Thank you for your contributions.
|
||||
|
||||
# Use stale to automatically close issues with a
|
||||
# reference to the issue tracker
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
days-before-issue-stale: 1
|
||||
days-before-issue-close: 1
|
||||
remove-stale-when-updated: true
|
||||
stale-issue-label: "stale"
|
||||
exempt-issue-labels: "not-stale"
|
||||
stale-issue-message: >
|
||||
https://github.com/esphome/esphome/issues/430
|
13
.gitignore
vendored
13
.gitignore
vendored
@@ -13,6 +13,9 @@ __pycache__/
|
||||
# Intellij Idea
|
||||
.idea
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
|
||||
# Hide some OS X stuff
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
@@ -74,6 +77,7 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
venv-*/
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
@@ -99,8 +103,7 @@ CMakeLists.txt
|
||||
.idea/**/dynamic.xml
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
cmake-build-*/
|
||||
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
@@ -120,4 +123,10 @@ config/
|
||||
tests/build/
|
||||
tests/.esphome/
|
||||
/.temp-clang-tidy.cpp
|
||||
/.temp/
|
||||
.pio/
|
||||
|
||||
sdkconfig.*
|
||||
!sdkconfig.defaults
|
||||
|
||||
.tests/
|
@@ -1,6 +0,0 @@
|
||||
ports:
|
||||
- port: 6052
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
|
||||
command: python -m esphome config dashboard
|
@@ -1,16 +1,17 @@
|
||||
---
|
||||
# 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
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.8.4
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
@@ -23,5 +24,10 @@ repos:
|
||||
- id: no-commit-to-branch
|
||||
args:
|
||||
- --branch=dev
|
||||
- --branch=master
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
|
35
.vscode/tasks.json
vendored
35
.vscode/tasks.json
vendored
@@ -1,11 +1,32 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "run",
|
||||
"type": "shell",
|
||||
"command": "python3 -m esphome dashboard config/",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "clang-tidy",
|
||||
"type": "shell",
|
||||
"command": "./script/clang-tidy",
|
||||
"problemMatcher": [
|
||||
{
|
||||
"label": "run",
|
||||
"type": "shell",
|
||||
"command": "python3 -m esphome config dashboard",
|
||||
"problemMatcher": []
|
||||
"owner": "clang-tidy",
|
||||
"fileLocation": "absolute",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
185
CODEOWNERS
185
CODEOWNERS
@@ -13,40 +13,124 @@ esphome/core/* @esphome/core
|
||||
# Integrations
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/adc128s102/* @DeerMaximum
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/airthings_ble/* @jeromelaban
|
||||
esphome/components/airthings_wave_mini/* @ncareau
|
||||
esphome/components/airthings_wave_plus/* @jeromelaban
|
||||
esphome/components/am43/* @buxtronix
|
||||
esphome/components/am43/cover/* @buxtronix
|
||||
esphome/components/analog_threshold/* @ianchi
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/anova/* @buxtronix
|
||||
esphome/components/api/* @OttoWinter
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/b_parasite/* @rbaron
|
||||
esphome/components/ballu/* @bazuchan
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
esphome/components/bedjet/* @jhansche
|
||||
esphome/components/bedjet/climate/* @jhansche
|
||||
esphome/components/bedjet/fan/* @jhansche
|
||||
esphome/components/bh1750/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/bl0939/* @ziceva
|
||||
esphome/components/bl0940/* @tobias-
|
||||
esphome/components/bl0942/* @dbuezas
|
||||
esphome/components/ble_client/* @buxtronix
|
||||
esphome/components/bluetooth_proxy/* @jesserockz
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/bmp3xx/* @martgras
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @MrEditor97
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/ccs811/* @habbie
|
||||
esphome/components/cd74hc4067/* @asoehlke
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/color_temperature/* @jesserockz
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/copy/* @OttoWinter
|
||||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/cse7761/* @berfenger
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/current_based/* @djwmarcx
|
||||
esphome/components/dac7678/* @NickB1
|
||||
esphome/components/daikin_brc/* @hagak
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/delonghi/* @grob6000
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/display_menu_base/* @numo68
|
||||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_client/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_can/* @Sympatron
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/ethernet_info/* @gtjadsonsantos
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/ezo_pmp/* @carlos-sarmiento
|
||||
esphome/components/factory_reset/* @anatoly-savchenkov
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/feedback/* @ianchi
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/graph/* @synco
|
||||
esphome/components/growatt_solar/* @leeuwte
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
esphome/components/heatpumpir/* @rob-deutsch
|
||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/improv_base/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/ina260/* @MrEditor97
|
||||
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/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @sebcaps
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/matrix_keypad/* @ssieb
|
||||
esphome/components/max31865/* @DAVe3283
|
||||
esphome/components/max44009/* @berfenger
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/max9611/* @mckaymatthew
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
esphome/components/mcp23017/* @jesserockz
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
||||
@@ -55,30 +139,97 @@ esphome/components/mcp23x08_base/* @jesserockz
|
||||
esphome/components/mcp23x17_base/* @jesserockz
|
||||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp3204/* @rsumner
|
||||
esphome/components/mcp4728/* @berfenger
|
||||
esphome/components/mcp47a1/* @jesserockz
|
||||
esphome/components/mcp9600/* @MrEditor97
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/midea_ac/* @dudanov
|
||||
esphome/components/midea_dongle/* @dudanov
|
||||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/media_player/* @jesserockz
|
||||
esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mlx90393/* @functionpointer
|
||||
esphome/components/modbus_controller/* @martgras
|
||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||
esphome/components/modbus_controller/number/* @martgras
|
||||
esphome/components/modbus_controller/output/* @martgras
|
||||
esphome/components/modbus_controller/select/* @martgras @stegm
|
||||
esphome/components/modbus_controller/sensor/* @martgras
|
||||
esphome/components/modbus_controller/switch/* @martgras
|
||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||
esphome/components/mopeka_ble/* @spbrogan
|
||||
esphome/components/mopeka_pro_check/* @spbrogan
|
||||
esphome/components/mpl3115a2/* @kbickar
|
||||
esphome/components/mpu6886/* @fabaff
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @senexcrenshaw
|
||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||
esphome/components/nextion/sensor/* @senexcrenshaw
|
||||
esphome/components/nextion/switch/* @senexcrenshaw
|
||||
esphome/components/nextion/text_sensor/* @senexcrenshaw
|
||||
esphome/components/nfc/* @jesserockz
|
||||
esphome/components/number/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pca9554/* @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pm1006/* @habbie
|
||||
esphome/components/pmsa003i/* @sjtrny
|
||||
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/preferences/* @esphome/core
|
||||
esphome/components/psram/* @esphome/core
|
||||
esphome/components/pulse_meter/* @cstaahl @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/qmp6988/* @andrewpc
|
||||
esphome/components/qr_code/* @wjtje
|
||||
esphome/components/radon_eye_ble/* @jeffeb3
|
||||
esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
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/rgbct/* @jesserockz
|
||||
esphome/components/rp2040/* @jesserockz
|
||||
esphome/components/rp2040_pwm/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
esphome/components/scd4x/* @martgras @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
esphome/components/selec_meter/* @sourabhjaiswal
|
||||
esphome/components/select/* @esphome/core
|
||||
esphome/components/sen5x/* @martgras
|
||||
esphome/components/sensirion_common/* @martgras
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sgp4x/* @SenexCrenshaw @martgras
|
||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sigma_delta_output/* @Cat-Ion
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm10bit_base/* @Cossid
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/sm2235/* @Cossid
|
||||
esphome/components/sm2335/* @Cossid
|
||||
esphome/components/sml/* @alengwenus
|
||||
esphome/components/smt100/* @piechade
|
||||
esphome/components/sn74hc165/* @jesserockz
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/sprinkler/* @kbx81
|
||||
esphome/components/sps30/* @martgras
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
esphome/components/ssd1325_base/* @kbx81
|
||||
@@ -92,23 +243,49 @@ esphome/components/ssd1351_base/* @kbx81
|
||||
esphome/components/ssd1351_spi/* @kbx81
|
||||
esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/st7920/* @marsjan155
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/tee501/* @Stock-M
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tlc5947/* @rnauber
|
||||
esphome/components/tm1621/* @Philippe12
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tm1638/* @skykingjwc
|
||||
esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tmp117/* @Azimath
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
esphome/components/toshiba/* @kbx81
|
||||
esphome/components/touchscreen/* @jesserockz
|
||||
esphome/components/tsl2591/* @wjcarpenter
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/number/* @frankiboy1
|
||||
esphome/components/tuya/select/* @bearpawmaxim
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/tuya/text_sensor/* @dentra
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/whynter/* @aeonsablaze
|
||||
esphome/components/wiegand/* @ssieb
|
||||
esphome/components/wl_134/* @hobbypunk90
|
||||
esphome/components/x9c/* @EtienneMD
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc303/* @drug123
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||
esphome/components/xpt2046/* @nielsnl68 @numo68
|
||||
|
@@ -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:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* 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
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- 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
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## 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.
|
||||
|
||||
|
@@ -1,15 +1,11 @@
|
||||
# Contributing to ESPHome
|
||||
|
||||
This python project is responsible for reading in YAML configuration files,
|
||||
converting them to C++ code. This code is then converted to a platformio project and compiled
|
||||
with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project.
|
||||
|
||||
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphomeyaml
|
||||
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome
|
||||
|
||||
Things to note when contributing:
|
||||
|
||||
- Please test your changes :)
|
||||
- If a new feature is added or an existing user-facing feature is changed, you should also
|
||||
- If a new feature is added or an existing user-facing feature is changed, you should also
|
||||
update the [docs](https://github.com/esphome/esphome-docs). See [contributing to esphome-docs](https://esphome.io/guides/contributing.html#contributing-to-esphomedocs)
|
||||
for more information.
|
||||
- Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files
|
||||
|
@@ -4,4 +4,5 @@ include requirements.txt
|
||||
include esphome/dashboard/templates/*.html
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||
recursive-include esphome *.cpp *.h *.tcc
|
||||
recursive-include esphome *.py.script
|
||||
recursive-include esphome LICENSE.txt
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# ESPHome [](https://travis-ci.org/esphome/esphome) [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
|
||||
# ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
|
||||
|
||||
[](https://esphome.io/)
|
||||
|
||||
|
@@ -1,15 +1,76 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0
|
||||
FROM ${BUILD_FROM}
|
||||
# Build these with the build.py script
|
||||
# Example:
|
||||
# python3 docker/build.py --tag dev --arch amd64 --build-type docker build
|
||||
|
||||
# One of "docker", "hassio"
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
# https://github.com/hassio-addons/addon-debian-base/releases
|
||||
FROM ghcr.io/hassio-addons/debian-base:6.2.0 AS base-hassio
|
||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
|
||||
FROM debian:bullseye-20221024-slim AS base-docker
|
||||
|
||||
FROM base-${BASEIMGTYPE} AS base
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3=3.9.2-3 \
|
||||
python3-pip=20.3.4-4+deb11u1 \
|
||||
python3-setuptools=52.0.0-4 \
|
||||
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
curl=7.74.0-1.3+deb11u5 \
|
||||
openssh-client=1:8.4p1-5+deb11u1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
ENV \
|
||||
# Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
|
||||
LANG=C.UTF-8 LC_ALL=C.UTF-8 \
|
||||
# Store globally installed pio libs in /piolibs
|
||||
PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
# Support legacy binaries on Debian multiarch system. There is no "correct" way
|
||||
# to do this, other than using properly built toolchains...
|
||||
# See: https://unix.stackexchange.com/questions/553743/correct-way-to-add-lib-ld-linux-so-3-in-debian
|
||||
RUN \
|
||||
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
|
||||
ln -s /lib/arm-linux-gnueabihf/ld-linux.so.3 /lib/ld-linux.so.3; \
|
||||
fi
|
||||
|
||||
RUN \
|
||||
# Ubuntu python3-pip is missing wheel
|
||||
pip3 install --no-cache-dir \
|
||||
wheel==0.37.1 \
|
||||
platformio==6.1.5 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_platformio_interval 1000000 \
|
||||
&& mkdir -p /piolibs
|
||||
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Then copy esphome and install
|
||||
COPY . .
|
||||
RUN pip3 install --no-cache-dir -e .
|
||||
|
||||
# ======================= docker-type image =======================
|
||||
FROM base AS docker
|
||||
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
|
||||
|
||||
# Settings for dashboard
|
||||
ENV USERNAME="" PASSWORD=""
|
||||
@@ -17,14 +78,77 @@ 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
|
||||
COPY docker/docker_entrypoint.sh /entrypoint.sh
|
||||
|
||||
# The directory the user should mount their configuration files to
|
||||
VOLUME /config
|
||||
WORKDIR /config
|
||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
|
||||
# Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome'
|
||||
# in every docker command twice
|
||||
ENTRYPOINT ["esphome"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
# When no arguments given, start the dashboard in the workdir
|
||||
CMD ["/config", "dashboard"]
|
||||
CMD ["dashboard", "/config"]
|
||||
|
||||
|
||||
|
||||
|
||||
# ======================= hassio-type image =======================
|
||||
FROM base AS hassio
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
nginx-light=1.18.0-6.1+deb11u3 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/ha-addon-rootfs/ /
|
||||
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
|
||||
|
||||
# Labels
|
||||
LABEL \
|
||||
io.hass.name="ESPHome" \
|
||||
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version="${BUILD_VERSION}"
|
||||
# io.hass.arch is inherited from addon-debian-base
|
||||
|
||||
|
||||
|
||||
|
||||
# ======================= lint-type image =======================
|
||||
FROM base AS lint
|
||||
|
||||
ENV \
|
||||
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
clang-format-11=1:11.0.1-2 \
|
||||
clang-tidy-11=1:11.0.1-2 \
|
||||
patch=2.7.6-7 \
|
||||
software-properties-common=0.96.20.2-2.1 \
|
||||
nano=5.4-2+deb11u2 \
|
||||
build-essential=12.9 \
|
||||
python3-dev=3.9.2-3 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
COPY requirements_test.txt /
|
||||
RUN pip3 install --no-cache-dir -r /requirements_test.txt
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
||||
|
@@ -1,13 +0,0 @@
|
||||
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,25 +0,0 @@
|
||||
ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/rootfs/ /
|
||||
|
||||
# Then copy esphome and install
|
||||
COPY . /opt/esphome/
|
||||
RUN pip3 install --no-cache-dir -e /opt/esphome
|
||||
|
||||
# Build arguments
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Labels
|
||||
LABEL \
|
||||
io.hass.name="ESPHome" \
|
||||
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version=${BUILD_VERSION}
|
@@ -1,9 +0,0 @@
|
||||
FROM esphome/esphome-lint-base:3.0.0
|
||||
|
||||
COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
192
docker/build.py
Executable file
192
docker/build.py
Executable file
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
from dataclasses import dataclass
|
||||
import subprocess
|
||||
import argparse
|
||||
from platform import machine
|
||||
import shlex
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
CHANNEL_DEV = "dev"
|
||||
CHANNEL_BETA = "beta"
|
||||
CHANNEL_RELEASE = "release"
|
||||
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
|
||||
|
||||
ARCH_AMD64 = "amd64"
|
||||
ARCH_ARMV7 = "armv7"
|
||||
ARCH_AARCH64 = "aarch64"
|
||||
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
|
||||
|
||||
TYPE_DOCKER = "docker"
|
||||
TYPE_HA_ADDON = "ha-addon"
|
||||
TYPE_LINT = "lint"
|
||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--tag",
|
||||
type=str,
|
||||
required=True,
|
||||
help="The main docker tag to push to. If a version number also adds latest and/or beta tag",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--arch", choices=ARCHS, required=False, help="The architecture to build for"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--build-type", choices=TYPES, required=True, help="The type of build to run"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true", help="Don't run any commands, just print them"
|
||||
)
|
||||
subparsers = parser.add_subparsers(
|
||||
help="Action to perform", dest="command", required=True
|
||||
)
|
||||
build_parser = subparsers.add_parser("build", help="Build the image")
|
||||
build_parser.add_argument("--push", help="Also push the images", action="store_true")
|
||||
build_parser.add_argument(
|
||||
"--load", help="Load the docker image locally", action="store_true"
|
||||
)
|
||||
manifest_parser = subparsers.add_parser(
|
||||
"manifest", help="Create a manifest from already pushed images"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DockerParams:
|
||||
build_to: str
|
||||
manifest_to: str
|
||||
baseimgtype: str
|
||||
platform: str
|
||||
target: str
|
||||
|
||||
@classmethod
|
||||
def for_type_arch(cls, build_type, arch):
|
||||
prefix = {
|
||||
TYPE_DOCKER: "esphome/esphome",
|
||||
TYPE_HA_ADDON: "esphome/esphome-hassio",
|
||||
TYPE_LINT: "esphome/esphome-lint",
|
||||
}[build_type]
|
||||
build_to = f"{prefix}-{arch}"
|
||||
baseimgtype = {
|
||||
TYPE_DOCKER: "docker",
|
||||
TYPE_HA_ADDON: "hassio",
|
||||
TYPE_LINT: "docker",
|
||||
}[build_type]
|
||||
platform = {
|
||||
ARCH_AMD64: "linux/amd64",
|
||||
ARCH_ARMV7: "linux/arm/v7",
|
||||
ARCH_AARCH64: "linux/arm64",
|
||||
}[arch]
|
||||
target = {
|
||||
TYPE_DOCKER: "docker",
|
||||
TYPE_HA_ADDON: "hassio",
|
||||
TYPE_LINT: "lint",
|
||||
}[build_type]
|
||||
return cls(
|
||||
build_to=build_to,
|
||||
manifest_to=prefix,
|
||||
baseimgtype=baseimgtype,
|
||||
platform=platform,
|
||||
target=target,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
def run_command(*cmd, ignore_error: bool = False):
|
||||
print(f"$ {shlex.join(list(cmd))}")
|
||||
if not args.dry_run:
|
||||
rc = subprocess.call(list(cmd))
|
||||
if rc != 0 and not ignore_error:
|
||||
print("Command failed")
|
||||
sys.exit(1)
|
||||
|
||||
# detect channel from tag
|
||||
match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag)
|
||||
major_minor_version = None
|
||||
if match is None:
|
||||
channel = CHANNEL_DEV
|
||||
elif match.group(2) is None:
|
||||
major_minor_version = match.group(1)
|
||||
channel = CHANNEL_RELEASE
|
||||
else:
|
||||
channel = CHANNEL_BETA
|
||||
|
||||
tags_to_push = [args.tag]
|
||||
if channel == CHANNEL_DEV:
|
||||
tags_to_push.append("dev")
|
||||
elif channel == CHANNEL_BETA:
|
||||
tags_to_push.append("beta")
|
||||
elif channel == CHANNEL_RELEASE:
|
||||
# Additionally push to beta
|
||||
tags_to_push.append("beta")
|
||||
tags_to_push.append("latest")
|
||||
|
||||
# Compatibility with HA tags
|
||||
if major_minor_version:
|
||||
tags_to_push.append("stable")
|
||||
tags_to_push.append(major_minor_version)
|
||||
|
||||
if args.command == "build":
|
||||
# 1. pull cache image
|
||||
params = DockerParams.for_type_arch(args.build_type, args.arch)
|
||||
cache_tag = {
|
||||
CHANNEL_DEV: "cache-dev",
|
||||
CHANNEL_BETA: "cache-beta",
|
||||
CHANNEL_RELEASE: "cache-latest",
|
||||
}[channel]
|
||||
cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
|
||||
|
||||
imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
|
||||
# 3. build
|
||||
cmd = [
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--build-arg",
|
||||
f"BASEIMGTYPE={params.baseimgtype}",
|
||||
"--build-arg",
|
||||
f"BUILD_VERSION={args.tag}",
|
||||
"--cache-from",
|
||||
f"type=registry,ref={cache_img}",
|
||||
"--file",
|
||||
"docker/Dockerfile",
|
||||
"--platform",
|
||||
params.platform,
|
||||
"--target",
|
||||
params.target,
|
||||
]
|
||||
for img in imgs:
|
||||
cmd += ["--tag", img]
|
||||
if args.push:
|
||||
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
|
||||
if args.load:
|
||||
cmd += ["--load"]
|
||||
|
||||
run_command(*cmd, ".")
|
||||
elif args.command == "manifest":
|
||||
manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
|
||||
|
||||
targets = [f"{manifest}:{tag}" for tag in tags_to_push]
|
||||
targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
|
||||
# 1. Create manifests
|
||||
for target in targets:
|
||||
cmd = ["docker", "manifest", "create", target]
|
||||
for arch in ARCHS:
|
||||
src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
|
||||
if target.startswith("ghcr.io"):
|
||||
src = f"ghcr.io/{src}"
|
||||
cmd.append(src)
|
||||
run_command(*cmd)
|
||||
# 2. Push manifests
|
||||
for target in targets:
|
||||
run_command("docker", "manifest", "push", target)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
24
docker/docker_entrypoint.sh
Executable file
24
docker/docker_entrypoint.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# If /cache is mounted, use that as PIO's coredir
|
||||
# otherwise use path in /config (so that PIO packages aren't downloaded on each compile)
|
||||
|
||||
if [[ -d /cache ]]; then
|
||||
pio_cache_base=/cache/platformio
|
||||
else
|
||||
pio_cache_base=/config/.esphome/platformio
|
||||
fi
|
||||
|
||||
if [[ ! -d "${pio_cache_base}" ]]; then
|
||||
echo "Creating cache directory ${pio_cache_base}"
|
||||
echo "You can change this behavior by mounting a directory to the container's /cache directory."
|
||||
mkdir -p "${pio_cache_base}"
|
||||
fi
|
||||
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
exec esphome "$@"
|
68
docker/generate_tags.py
Executable file
68
docker/generate_tags.py
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import os
|
||||
import argparse
|
||||
import json
|
||||
|
||||
CHANNEL_DEV = "dev"
|
||||
CHANNEL_BETA = "beta"
|
||||
CHANNEL_RELEASE = "release"
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--tag",
|
||||
type=str,
|
||||
required=True,
|
||||
help="The main docker tag to push to. If a version number also adds latest and/or beta tag",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--suffix",
|
||||
type=str,
|
||||
required=True,
|
||||
help="The suffix of the tag.",
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
# detect channel from tag
|
||||
match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag)
|
||||
major_minor_version = None
|
||||
if match is None:
|
||||
channel = CHANNEL_DEV
|
||||
elif match.group(2) is None:
|
||||
major_minor_version = match.group(1)
|
||||
channel = CHANNEL_RELEASE
|
||||
else:
|
||||
channel = CHANNEL_BETA
|
||||
|
||||
tags_to_push = [args.tag]
|
||||
if channel == CHANNEL_DEV:
|
||||
tags_to_push.append("dev")
|
||||
elif channel == CHANNEL_BETA:
|
||||
tags_to_push.append("beta")
|
||||
elif channel == CHANNEL_RELEASE:
|
||||
# Additionally push to beta
|
||||
tags_to_push.append("beta")
|
||||
tags_to_push.append("latest")
|
||||
|
||||
if major_minor_version:
|
||||
tags_to_push.append("stable")
|
||||
tags_to_push.append(major_minor_version)
|
||||
|
||||
suffix = f"-{args.suffix}" if args.suffix else ""
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "w") as f:
|
||||
print(f"channel={channel}", file=f)
|
||||
print(f"image=esphome/esphome{suffix}", file=f)
|
||||
full_tags = []
|
||||
|
||||
for tag in tags_to_push:
|
||||
full_tags += [f"ghcr.io/esphome/esphome{suffix}:{tag}"]
|
||||
full_tags += [f"esphome/esphome{suffix}:{tag}"]
|
||||
print(f"tags={','.join(full_tags)}", file=f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@@ -1,9 +1,9 @@
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_max_temp_file_size 0;
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_max_temp_file_size 0;
|
||||
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Connection $connection_upgrade;
|
@@ -1,5 +1,7 @@
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
|
||||
client_max_body_size 512m;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
@@ -0,0 +1,8 @@
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
3
docker/ha-addon-rootfs/etc/nginx/includes/upstream.conf
Normal file
3
docker/ha-addon-rootfs/etc/nginx/includes/upstream.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
upstream esphome {
|
||||
server unix:/var/run/esphome.sock;
|
||||
}
|
@@ -2,7 +2,6 @@ daemon off;
|
||||
user root;
|
||||
pid /var/run/nginx.pid;
|
||||
worker_processes 1;
|
||||
# Hass.io addon log
|
||||
error_log /proc/1/fd/1 error;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
@@ -10,24 +9,22 @@ events {
|
||||
|
||||
http {
|
||||
include /etc/nginx/includes/mime.types;
|
||||
access_log stdout;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
|
||||
access_log off;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
# Use Hass.io supervisor as resolver
|
||||
resolver 172.30.32.2;
|
||||
|
||||
upstream esphome {
|
||||
server unix:/var/run/esphome.sock;
|
||||
}
|
||||
|
||||
include /etc/nginx/includes/upstream.conf;
|
||||
include /etc/nginx/servers/*.conf;
|
||||
}
|
1
docker/ha-addon-rootfs/etc/nginx/servers/.gitkeep
Normal file
1
docker/ha-addon-rootfs/etc/nginx/servers/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
Without requirements or design, programming is the art of adding bugs to an empty text file. (Louis Srygley)
|
@@ -1,20 +1,26 @@
|
||||
server {
|
||||
listen %%port%% default_server ssl http2;
|
||||
{{ if not .ssl }}
|
||||
listen 6052 default_server;
|
||||
{{ else }}
|
||||
listen 6052 default_server ssl http2;
|
||||
{{ end }}
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
|
||||
{{ if .ssl }}
|
||||
include /etc/nginx/includes/ssl_params.conf;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /ssl/%%certfile%%;
|
||||
ssl_certificate_key /ssl/%%keyfile%%;
|
||||
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "";
|
||||
ssl_certificate /ssl/{{ .certfile }};
|
||||
ssl_certificate_key /ssl/{{ .keyfile }};
|
||||
|
||||
# Redirect http requests to https on the same port.
|
||||
# https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
|
||||
error_page 497 https://$http_host$request_uri;
|
||||
{{ end }}
|
||||
|
||||
# Clear Home Assistant Ingress header
|
||||
proxy_set_header X-HA-Ingress "";
|
||||
|
||||
location / {
|
||||
proxy_pass http://esphome;
|
@@ -1,14 +1,16 @@
|
||||
server {
|
||||
listen %%interface%%:%%port%% default_server;
|
||||
listen 127.0.0.1:{{ .port }} default_server;
|
||||
listen {{ .interface }}:{{ .port }} default_server;
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
# Set Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "YES";
|
||||
|
||||
# Set Home Assistant Ingress header
|
||||
proxy_set_header X-HA-Ingress "YES";
|
||||
|
||||
location / {
|
||||
# Only allow from Hass.io supervisor
|
||||
allow 172.30.32.2;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
|
||||
proxy_pass http://esphome;
|
32
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/discovery/run
Executable file
32
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/discovery/run
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Home Assistant Add-on: ESPHome
|
||||
# Sends discovery information to Home Assistant.
|
||||
# ==============================================================================
|
||||
declare config
|
||||
declare port
|
||||
|
||||
# We only disable it when disabled explicitly
|
||||
if bashio::config.false 'home_assistant_dashboard_integration';
|
||||
then
|
||||
bashio::log.info "Home Assistant discovery is disabled for this add-on."
|
||||
bashio::exit.ok
|
||||
fi
|
||||
|
||||
port=$(bashio::addon.ingress_port)
|
||||
|
||||
# Wait for NGINX to become available
|
||||
bashio::net.wait_for "${port}" "127.0.0.1" 300
|
||||
|
||||
config=$(\
|
||||
bashio::var.json \
|
||||
host "127.0.0.1" \
|
||||
port "^${port}" \
|
||||
)
|
||||
|
||||
if bashio::discovery "esphome" "${config}" > /dev/null; then
|
||||
bashio::log.info "Successfully send discovery information to Home Assistant."
|
||||
else
|
||||
bashio::log.error "Discovery message to Home Assistant failed!"
|
||||
fi
|
@@ -0,0 +1 @@
|
||||
oneshot
|
@@ -0,0 +1 @@
|
||||
/etc/s6-overlay/s6-rc.d/discovery/run
|
26
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/finish
Executable file
26
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/finish
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Home Assistant Community Add-on: ESPHome
|
||||
# Take down the S6 supervision tree when ESPHome dashboard fails
|
||||
# ==============================================================================
|
||||
declare exit_code
|
||||
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
|
||||
readonly exit_code_service="${1}"
|
||||
readonly exit_code_signal="${2}"
|
||||
|
||||
bashio::log.info \
|
||||
"Service ESPHome dashboard exited with code ${exit_code_service}" \
|
||||
"(by signal ${exit_code_signal})"
|
||||
|
||||
if [[ "${exit_code_service}" -eq 256 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
[[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
|
||||
elif [[ "${exit_code_service}" -ne 0 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
45
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/run
Executable file
45
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/run
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Runs the ESPHome dashboard
|
||||
# ==============================================================================
|
||||
readonly pio_cache_base=/data/cache/platformio
|
||||
|
||||
export ESPHOME_IS_HA_ADDON=true
|
||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
if bashio::config.true 'leave_front_door_open'; then
|
||||
export DISABLE_HA_AUTHENTICATION=true
|
||||
fi
|
||||
|
||||
if bashio::config.true 'streamer_mode'; then
|
||||
export ESPHOME_STREAMER_MODE=true
|
||||
fi
|
||||
|
||||
if bashio::config.true 'status_use_ping'; then
|
||||
export ESPHOME_DASHBOARD_USE_PING=true
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'relative_url'; then
|
||||
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'default_compile_process_limit'; then
|
||||
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
|
||||
else
|
||||
if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
|
||||
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1;
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "${pio_cache_base}"
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
|
@@ -0,0 +1 @@
|
||||
longrun
|
27
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file
27
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Configures NGINX for use with ESPHome
|
||||
# ==============================================================================
|
||||
mkdir -p /var/log/nginx
|
||||
|
||||
# Generate Ingress configuration
|
||||
bashio::var.json \
|
||||
interface "$(bashio::addon.ip_address)" \
|
||||
port "^$(bashio::addon.ingress_port)" \
|
||||
| tempio \
|
||||
-template /etc/nginx/templates/ingress.gtpl \
|
||||
-out /etc/nginx/servers/ingress.conf
|
||||
|
||||
# Generate direct access configuration, if enabled.
|
||||
if bashio::var.has_value "$(bashio::addon.port 6052)"; then
|
||||
bashio::config.require.ssl
|
||||
bashio::var.json \
|
||||
certfile "$(bashio::config 'certfile')" \
|
||||
keyfile "$(bashio::config 'keyfile')" \
|
||||
ssl "^$(bashio::config 'ssl')" \
|
||||
| tempio \
|
||||
-template /etc/nginx/templates/direct.gtpl \
|
||||
-out /etc/nginx/servers/direct.conf
|
||||
fi
|
@@ -0,0 +1 @@
|
||||
oneshot
|
@@ -0,0 +1 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-nginx/run
|
25
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/nginx/finish
Executable file
25
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/nginx/finish
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/command/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when NGINX fails
|
||||
# ==============================================================================
|
||||
declare exit_code
|
||||
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
|
||||
readonly exit_code_service="${1}"
|
||||
readonly exit_code_signal="${2}"
|
||||
|
||||
bashio::log.info \
|
||||
"Service NGINX exited with code ${exit_code_service}" \
|
||||
"(by signal ${exit_code_signal})"
|
||||
|
||||
if [[ "${exit_code_service}" -eq 256 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
[[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
|
||||
elif [[ "${exit_code_service}" -ne 0 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Runs the NGINX proxy
|
||||
# ==============================================================================
|
||||
|
||||
bashio::log.info "Waiting for dashboard to come up..."
|
||||
bashio::log.info "Waiting for ESPHome dashboard to come up..."
|
||||
|
||||
while [[ ! -S /var/run/esphome.sock ]]; do
|
||||
sleep 0.5
|
1
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/nginx/type
Normal file
1
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/nginx/type
Normal file
@@ -0,0 +1 @@
|
||||
longrun
|
@@ -3,18 +3,28 @@
|
||||
# all platformio libraries in the global storage
|
||||
|
||||
import configparser
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
|
||||
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:
|
||||
# Extract from every lib_deps key in all sections
|
||||
for section in config.sections():
|
||||
conf = config[section]
|
||||
if "lib_deps" not in conf:
|
||||
continue
|
||||
libs.append(m.group(1))
|
||||
for lib_dep in conf["lib_deps"].splitlines():
|
||||
if not lib_dep:
|
||||
# Empty line or comment
|
||||
continue
|
||||
if lib_dep.startswith("${"):
|
||||
# Extending from another section
|
||||
continue
|
||||
if "@" not in lib_dep:
|
||||
# No version pinned, this is an internal lib
|
||||
continue
|
||||
libs.append(lib_dep)
|
||||
|
||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
|
||||
|
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files check if all user configuration requirements are met
|
||||
# ==============================================================================
|
||||
|
||||
# Check SSL requirements, if enabled
|
||||
if bashio::config.true 'ssl'; then
|
||||
if ! bashio::config.has_value 'certfile'; then
|
||||
bashio::fatal 'SSL is enabled, but no certfile was specified.'
|
||||
bashio::exit.nok
|
||||
fi
|
||||
|
||||
if ! bashio::config.has_value 'keyfile'; then
|
||||
bashio::fatal 'SSL is enabled, but no keyfile was specified'
|
||||
bashio::exit.nok
|
||||
fi
|
||||
|
||||
|
||||
certfile="/ssl/$(bashio::config 'certfile')"
|
||||
keyfile="/ssl/$(bashio::config 'keyfile')"
|
||||
|
||||
if ! bashio::fs.file_exists "${certfile}"; then
|
||||
if ! bashio::fs.file_exists "${keyfile}"; then
|
||||
# Both files are missing, let's print a friendlier error message
|
||||
bashio::log.fatal 'You enabled encrypted connections using the "ssl": true option.'
|
||||
bashio::log.fatal "However, the SSL files '${certfile}' and '${keyfile}'"
|
||||
bashio::log.fatal "were not found. If you're using Hass.io on your local network and don't want"
|
||||
bashio::log.fatal 'to encrypt connections to the ESPHome dashboard, you can manually disable'
|
||||
bashio::log.fatal 'SSL by setting "ssl" to false."'
|
||||
bashio::exit.nok
|
||||
fi
|
||||
bashio::log.fatal "The configured certfile '${certfile}' was not found."
|
||||
bashio::exit.nok
|
||||
fi
|
||||
|
||||
if ! bashio::fs.file_exists "/ssl/$(bashio::config 'keyfile')"; then
|
||||
bashio::log.fatal "The configured keyfile '${keyfile}' was not found."
|
||||
bashio::exit.nok
|
||||
fi
|
||||
fi
|
@@ -1,34 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Configures NGINX for use with ESPHome
|
||||
# ==============================================================================
|
||||
|
||||
declare certfile
|
||||
declare keyfile
|
||||
declare direct_port
|
||||
declare ingress_interface
|
||||
declare ingress_port
|
||||
|
||||
mkdir -p /var/log/nginx
|
||||
|
||||
direct_port=$(bashio::addon.port 6052)
|
||||
if bashio::var.has_value "${direct_port}"; then
|
||||
if bashio::config.true 'ssl'; then
|
||||
certfile=$(bashio::config 'certfile')
|
||||
keyfile=$(bashio::config 'keyfile')
|
||||
|
||||
mv /etc/nginx/servers/direct-ssl.disabled /etc/nginx/servers/direct.conf
|
||||
sed -i "s/%%certfile%%/${certfile}/g" /etc/nginx/servers/direct.conf
|
||||
sed -i "s/%%keyfile%%/${keyfile}/g" /etc/nginx/servers/direct.conf
|
||||
else
|
||||
mv /etc/nginx/servers/direct.disabled /etc/nginx/servers/direct.conf
|
||||
fi
|
||||
|
||||
sed -i "s/%%port%%/${direct_port}/g" /etc/nginx/servers/direct.conf
|
||||
fi
|
||||
|
||||
ingress_port=$(bashio::addon.ingress_port)
|
||||
ingress_interface=$(bashio::addon.ip_address)
|
||||
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
|
||||
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
|
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files installs the user ESPHome version if specified
|
||||
# ==============================================================================
|
||||
|
||||
declare esphome_version
|
||||
|
||||
if bashio::config.has_value 'esphome_version'; then
|
||||
esphome_version=$(bashio::config 'esphome_version')
|
||||
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})..."
|
||||
pip3 install -U --no-cache-dir "${full_url}" \
|
||||
|| bashio::exit.nok "Failed installing esphome pinned version."
|
||||
fi
|
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files migrates the esphome config directory from the old path
|
||||
# ==============================================================================
|
||||
|
||||
if [[ ! -d /config/esphome && -d /config/esphomeyaml ]]; then
|
||||
echo "Moving config directory from /config/esphomeyaml to /config/esphome"
|
||||
mv /config/esphomeyaml /config/esphome
|
||||
mv /config/esphome/.esphomeyaml /config/esphome/.esphome
|
||||
fi
|
@@ -1,9 +0,0 @@
|
||||
ssl_protocols TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
|
||||
ssl_ecdh_curve secp384r1;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
@@ -1,12 +0,0 @@
|
||||
server {
|
||||
listen %%port%% default_server;
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "";
|
||||
|
||||
location / {
|
||||
proxy_pass http://esphome;
|
||||
}
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/execlineb -S0
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when ESPHome fails
|
||||
# ==============================================================================
|
||||
if -n { s6-test $# -ne 0 }
|
||||
if -n { s6-test ${1} -eq 256 }
|
||||
|
||||
s6-svscanctl -t /var/run/s6/services
|
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Runs the ESPHome dashboard
|
||||
# ==============================================================================
|
||||
|
||||
export ESPHOME_IS_HASSIO=true
|
||||
|
||||
if bashio::config.true 'leave_front_door_open'; then
|
||||
export DISABLE_HA_AUTHENTICATION=true
|
||||
fi
|
||||
|
||||
if bashio::config.true 'streamer_mode'; then
|
||||
export ESPHOME_STREAMER_MODE=true
|
||||
fi
|
||||
|
||||
if bashio::config.true 'status_use_ping'; then
|
||||
export ESPHOME_DASHBOARD_USE_PING=true
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'relative_url'; then
|
||||
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
|
||||
fi
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
exec esphome /config/esphome dashboard --socket /var/run/esphome.sock --hassio
|
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/execlineb -S0
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when NGINX fails
|
||||
# ==============================================================================
|
||||
if -n { s6-test $# -ne 0 }
|
||||
if -n { s6-test ${1} -eq 256 }
|
||||
|
||||
s6-svscanctl -t /var/run/s6/services
|
@@ -2,24 +2,34 @@ import argparse
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from esphome import const, writer, yaml_util
|
||||
import esphome.codegen as cg
|
||||
from esphome.config import iter_components, read_config, strip_default_ids
|
||||
from esphome.const import (
|
||||
ALLOWED_NAME_CHARS,
|
||||
CONF_BAUD_RATE,
|
||||
CONF_BROKER,
|
||||
CONF_DEASSERT_RTS_DTR,
|
||||
CONF_LOGGER,
|
||||
CONF_NAME,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_ESPHOME,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
CONF_SUBSTITUTIONS,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
SECRETS_FILES,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||
from esphome.helpers import color, indent
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
from esphome.helpers import indent
|
||||
from esphome.util import (
|
||||
run_external_command,
|
||||
run_external_process,
|
||||
@@ -27,6 +37,7 @@ from esphome.util import (
|
||||
list_yaml_files,
|
||||
get_serial_ports,
|
||||
)
|
||||
from esphome.log import color, setup_log, Fore
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -57,7 +68,7 @@ def choose_prompt(options):
|
||||
raise ValueError
|
||||
break
|
||||
except ValueError:
|
||||
safe_print(color("red", f"Invalid option: '{opt}'"))
|
||||
safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
|
||||
return options[opt - 1][1]
|
||||
|
||||
|
||||
@@ -70,7 +81,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
|
||||
if default == "OTA":
|
||||
return CORE.address
|
||||
if show_mqtt and "mqtt" in CORE.config:
|
||||
options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
|
||||
options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
|
||||
if default == "OTA":
|
||||
return "MQTT"
|
||||
if default is not None:
|
||||
@@ -94,47 +105,70 @@ def run_miniterm(config, port):
|
||||
|
||||
if CONF_LOGGER not in config:
|
||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||
return
|
||||
return 1
|
||||
baud_rate = config["logger"][CONF_BAUD_RATE]
|
||||
if baud_rate == 0:
|
||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
||||
return 1
|
||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||
|
||||
backtrace_state = False
|
||||
with serial.Serial(port, baudrate=baud_rate) as ser:
|
||||
while True:
|
||||
try:
|
||||
raw = ser.readline()
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return
|
||||
line = (
|
||||
raw.replace(b"\r", b"")
|
||||
.replace(b"\n", b"")
|
||||
.decode("utf8", "backslashreplace")
|
||||
)
|
||||
time = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
message = time + line
|
||||
safe_print(message)
|
||||
ser = serial.Serial()
|
||||
ser.baudrate = baud_rate
|
||||
ser.port = port
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
# We can't set to False by default since it leads to toggling and hence
|
||||
# ESP32 resets on some platforms.
|
||||
if config["logger"][CONF_DEASSERT_RTS_DTR]:
|
||||
ser.dtr = False
|
||||
ser.rts = False
|
||||
|
||||
tries = 0
|
||||
while tries < 5:
|
||||
try:
|
||||
with ser:
|
||||
while True:
|
||||
try:
|
||||
raw = ser.readline()
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return 0
|
||||
line = (
|
||||
raw.replace(b"\r", b"")
|
||||
.replace(b"\n", b"")
|
||||
.decode("utf8", "backslashreplace")
|
||||
)
|
||||
time_str = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
message = time_str + line
|
||||
safe_print(message)
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
except serial.SerialException:
|
||||
tries += 1
|
||||
time.sleep(1)
|
||||
if tries >= 5:
|
||||
_LOGGER.error("Could not connect to serial port %s", port)
|
||||
return 1
|
||||
|
||||
|
||||
def wrap_to_code(name, comp):
|
||||
coro = coroutine(comp.to_code)
|
||||
|
||||
@functools.wraps(comp.to_code)
|
||||
@coroutine_with_priority(coro.priority)
|
||||
def wrapped(conf):
|
||||
async def wrapped(conf):
|
||||
cg.add(cg.LineComment(f"{name}:"))
|
||||
if comp.config_schema is not None:
|
||||
conf_str = yaml_util.dump(conf)
|
||||
conf_str = conf_str.replace("//", "")
|
||||
# remove tailing \ to avoid multi-line comment warning
|
||||
conf_str = conf_str.replace("\\\n", "\n")
|
||||
cg.add(cg.LineComment(indent(conf_str)))
|
||||
yield coro(conf)
|
||||
await coro(conf)
|
||||
|
||||
if hasattr(coro, "priority"):
|
||||
wrapped.priority = coro.priority
|
||||
return wrapped
|
||||
|
||||
|
||||
@@ -166,16 +200,37 @@ def compile_program(args, config):
|
||||
from esphome import platformio_api
|
||||
|
||||
_LOGGER.info("Compiling app...")
|
||||
return platformio_api.run_compile(config, CORE.verbose)
|
||||
rc = platformio_api.run_compile(config, CORE.verbose)
|
||||
if rc != 0:
|
||||
return rc
|
||||
idedata = platformio_api.get_idedata(config)
|
||||
return 0 if idedata is not None else 1
|
||||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
path = CORE.firmware_bin
|
||||
from esphome import platformio_api
|
||||
|
||||
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||
"upload_speed", 460800
|
||||
)
|
||||
|
||||
def run_esptool(baud_rate):
|
||||
idedata = platformio_api.get_idedata(config)
|
||||
|
||||
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
|
||||
flash_images = [
|
||||
platformio_api.FlashImage(
|
||||
path=idedata.firmware_bin_path, offset=firmware_offset
|
||||
),
|
||||
*idedata.extra_flash_images,
|
||||
]
|
||||
|
||||
mcu = "esp8266"
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
|
||||
mcu = get_esp32_variant().lower()
|
||||
|
||||
cmd = [
|
||||
"esptool.py",
|
||||
"--before",
|
||||
@@ -184,20 +239,22 @@ def upload_using_esptool(config, port):
|
||||
"hard_reset",
|
||||
"--baud",
|
||||
str(baud_rate),
|
||||
"--chip",
|
||||
"esp8266",
|
||||
"--port",
|
||||
port,
|
||||
"--chip",
|
||||
mcu,
|
||||
"write_flash",
|
||||
"0x0",
|
||||
path,
|
||||
"-z",
|
||||
"--flash_size",
|
||||
"detect",
|
||||
]
|
||||
for img in flash_images:
|
||||
cmd += [img.offset, img.path]
|
||||
|
||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||
import esptool
|
||||
|
||||
# pylint: disable=protected-access
|
||||
return run_external_command(esptool._main, *cmd)
|
||||
return run_external_command(esptool.main, *cmd) # pylint: disable=no-member
|
||||
|
||||
return run_external_process(*cmd)
|
||||
|
||||
@@ -213,13 +270,21 @@ def upload_using_esptool(config, port):
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
# if upload is to a serial port use platformio, otherwise assume ota
|
||||
if get_port_type(host) == "SERIAL":
|
||||
from esphome import platformio_api
|
||||
|
||||
if CORE.is_esp8266:
|
||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||
return upload_using_esptool(config, host)
|
||||
return platformio_api.run_upload(config, CORE.verbose, host)
|
||||
|
||||
if CORE.target_platform in (PLATFORM_RP2040):
|
||||
from esphome import platformio_api
|
||||
|
||||
upload_args = ["-t", "upload"]
|
||||
if args.device is not None:
|
||||
upload_args += ["--upload-port", args.device]
|
||||
return platformio_api.run_platformio_cli_run(
|
||||
config, CORE.verbose, *upload_args
|
||||
)
|
||||
|
||||
return 1 # Unknown target platform
|
||||
|
||||
from esphome import espota2
|
||||
|
||||
@@ -231,7 +296,9 @@ def upload_program(config, args, host):
|
||||
|
||||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
password = ota_conf[CONF_PASSWORD]
|
||||
password = ota_conf.get(CONF_PASSWORD, "")
|
||||
if getattr(args, "file", None) is not None:
|
||||
return espota2.run_ota(host, remote_port, password, args.file)
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
|
||||
|
||||
@@ -239,10 +306,9 @@ def show_logs(config, args, port):
|
||||
if "logger" not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
if get_port_type(port) == "SERIAL":
|
||||
run_miniterm(config, port)
|
||||
return 0
|
||||
return run_miniterm(config, port)
|
||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||
from esphome.api.client import run_logs
|
||||
from esphome.components.api.client import run_logs
|
||||
|
||||
return run_logs(config, port)
|
||||
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
||||
@@ -263,57 +329,17 @@ def clean_mqtt(config, args):
|
||||
)
|
||||
|
||||
|
||||
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 = f"%(log_color)s{fmt}%(reset)s"
|
||||
datefmt = "%H:%M:%S"
|
||||
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
|
||||
try:
|
||||
import colorama
|
||||
|
||||
colorama.init(strip=True)
|
||||
|
||||
from colorlog import ColoredFormatter
|
||||
|
||||
logging.getLogger().handlers[0].setFormatter(
|
||||
ColoredFormatter(
|
||||
colorfmt,
|
||||
datefmt=datefmt,
|
||||
reset=True,
|
||||
log_colors={
|
||||
"DEBUG": "cyan",
|
||||
"INFO": "green",
|
||||
"WARNING": "yellow",
|
||||
"ERROR": "red",
|
||||
"CRITICAL": "red",
|
||||
},
|
||||
)
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def command_wizard(args):
|
||||
from esphome import wizard
|
||||
|
||||
return wizard.wizard(args.configuration[0])
|
||||
return wizard.wizard(args.configuration)
|
||||
|
||||
|
||||
def command_config(args, config):
|
||||
_LOGGER.info("Configuration is valid!")
|
||||
if not CORE.verbose:
|
||||
config = strip_default_ids(config)
|
||||
safe_print(yaml_util.dump(config))
|
||||
safe_print(yaml_util.dump(config, args.show_secrets))
|
||||
return 0
|
||||
|
||||
|
||||
@@ -322,7 +348,6 @@ def command_vscode(args):
|
||||
|
||||
logging.disable(logging.INFO)
|
||||
logging.disable(logging.WARNING)
|
||||
CORE.config_path = args.configuration[0]
|
||||
vscode.read_config(args)
|
||||
|
||||
|
||||
@@ -342,7 +367,7 @@ def command_compile(args, config):
|
||||
|
||||
def command_upload(args, config):
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
show_mqtt=False,
|
||||
@@ -357,7 +382,7 @@ def command_upload(args, config):
|
||||
|
||||
def command_logs(args, config):
|
||||
port = choose_upload_log_host(
|
||||
default=args.serial_port,
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
@@ -375,7 +400,7 @@ def command_run(args, config):
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
show_mqtt=False,
|
||||
@@ -388,7 +413,7 @@ def command_run(args, config):
|
||||
if args.no_logs:
|
||||
return 0
|
||||
port = choose_upload_log_host(
|
||||
default=args.upload_port,
|
||||
default=args.device,
|
||||
check_default=port,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
@@ -432,7 +457,7 @@ def command_update_all(args):
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration[0])
|
||||
files = list_yaml_files(args.configuration)
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
@@ -442,34 +467,141 @@ def command_update_all(args):
|
||||
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||
|
||||
for f in files:
|
||||
print("Updating {}".format(color("cyan", f)))
|
||||
print(f"Updating {color(Fore.CYAN, f)}")
|
||||
print("-" * twidth)
|
||||
print()
|
||||
rc = run_external_process(
|
||||
"esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA"
|
||||
"esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
|
||||
)
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color("bold_green", "SUCCESS"), f))
|
||||
print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
|
||||
success[f] = True
|
||||
else:
|
||||
print_bar("[{}] {}".format(color("bold_red", "ERROR"), f))
|
||||
print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
|
||||
success[f] = False
|
||||
|
||||
print()
|
||||
print()
|
||||
print()
|
||||
|
||||
print_bar("[{}]".format(color("bold_white", "SUMMARY")))
|
||||
print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
|
||||
failed = 0
|
||||
for f in files:
|
||||
if success[f]:
|
||||
print(" - {}: {}".format(f, color("green", "SUCCESS")))
|
||||
print(f" - {f}: {color(Fore.GREEN, 'SUCCESS')}")
|
||||
else:
|
||||
print(" - {}: {}".format(f, color("bold_red", "FAILED")))
|
||||
print(f" - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
|
||||
failed += 1
|
||||
return failed
|
||||
|
||||
|
||||
def command_idedata(args, config):
|
||||
from esphome import platformio_api
|
||||
import json
|
||||
|
||||
logging.disable(logging.INFO)
|
||||
logging.disable(logging.WARNING)
|
||||
|
||||
idedata = platformio_api.get_idedata(config)
|
||||
if idedata is None:
|
||||
return 1
|
||||
|
||||
print(json.dumps(idedata.raw, indent=2) + "\n")
|
||||
return 0
|
||||
|
||||
|
||||
def command_rename(args, config):
|
||||
for c in args.name:
|
||||
if c not in ALLOWED_NAME_CHARS:
|
||||
print(
|
||||
color(
|
||||
Fore.BOLD_RED,
|
||||
f"'{c}' is an invalid character for names. Valid characters are: "
|
||||
f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
# Load existing yaml file
|
||||
with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file:
|
||||
raw_contents = raw_file.read()
|
||||
|
||||
yaml = yaml_util.load_yaml(CORE.config_path)
|
||||
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
|
||||
print(
|
||||
color(Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed.")
|
||||
)
|
||||
return 1
|
||||
old_name = yaml[CONF_ESPHOME][CONF_NAME]
|
||||
match = re.match(r"^\$\{?([a-zA-Z0-9_]+)\}?$", old_name)
|
||||
if match is None:
|
||||
new_raw = re.sub(
|
||||
rf"name:\s+[\"']?{old_name}[\"']?",
|
||||
f'name: "{args.name}"',
|
||||
raw_contents,
|
||||
)
|
||||
else:
|
||||
old_name = yaml[CONF_SUBSTITUTIONS][match.group(1)]
|
||||
if (
|
||||
len(
|
||||
re.findall(
|
||||
rf"^\s+{match.group(1)}:\s+[\"']?{old_name}[\"']?",
|
||||
raw_contents,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
)
|
||||
> 1
|
||||
):
|
||||
print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename"))
|
||||
return 1
|
||||
|
||||
new_raw = re.sub(
|
||||
rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
|
||||
f'\\1: "{args.name}"',
|
||||
raw_contents,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
|
||||
new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
|
||||
print(
|
||||
f"Updating {color(Fore.CYAN, CORE.config_path)} to {color(Fore.CYAN, new_path)}"
|
||||
)
|
||||
print()
|
||||
|
||||
with open(new_path, mode="w", encoding="utf-8") as new_file:
|
||||
new_file.write(new_raw)
|
||||
|
||||
rc = run_external_process("esphome", "config", new_path)
|
||||
if rc != 0:
|
||||
print(color(Fore.BOLD_RED, "Rename failed. Reverting changes."))
|
||||
os.remove(new_path)
|
||||
return 1
|
||||
|
||||
cli_args = [
|
||||
"run",
|
||||
new_path,
|
||||
"--no-logs",
|
||||
"--device",
|
||||
CORE.address,
|
||||
]
|
||||
|
||||
if args.dashboard:
|
||||
cli_args.insert(0, "--dashboard")
|
||||
|
||||
try:
|
||||
rc = run_external_process("esphome", *cli_args)
|
||||
except KeyboardInterrupt:
|
||||
rc = 1
|
||||
if rc != 0:
|
||||
os.remove(new_path)
|
||||
return 1
|
||||
|
||||
os.remove(CORE.config_path)
|
||||
|
||||
print(color(Fore.BOLD_GREEN, "SUCCESS"))
|
||||
print()
|
||||
return 0
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
"wizard": command_wizard,
|
||||
"version": command_version,
|
||||
@@ -487,19 +619,23 @@ POST_CONFIG_ACTIONS = {
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||
"clean": command_clean,
|
||||
"idedata": command_idedata,
|
||||
"rename": command_rename,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}")
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", help="Enable verbose esphome logs.", action="store_true"
|
||||
options_parser = argparse.ArgumentParser(add_help=False)
|
||||
options_parser.add_argument(
|
||||
"-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q", "--quiet", help="Disable all esphome logs.", action="store_true"
|
||||
options_parser.add_argument(
|
||||
"-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
|
||||
)
|
||||
parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true")
|
||||
parser.add_argument(
|
||||
options_parser.add_argument(
|
||||
"--dashboard", help=argparse.SUPPRESS, action="store_true"
|
||||
)
|
||||
options_parser.add_argument(
|
||||
"-s",
|
||||
"--substitution",
|
||||
nargs=2,
|
||||
@@ -507,17 +643,38 @@ def parse_args(argv):
|
||||
help="Add a substitution",
|
||||
metavar=("key", "value"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"configuration", help="Your YAML configuration file.", nargs="*"
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description=f"ESPHome v{const.__version__}", parents=[options_parser]
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(help="Commands", dest="command")
|
||||
mqtt_options = argparse.ArgumentParser(add_help=False)
|
||||
mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.")
|
||||
mqtt_options.add_argument("--username", help="Manually set the MQTT username.")
|
||||
mqtt_options.add_argument("--password", help="Manually set the MQTT password.")
|
||||
mqtt_options.add_argument("--client-id", help="Manually set the MQTT client id.")
|
||||
|
||||
subparsers = parser.add_subparsers(
|
||||
help="Command to run:", dest="command", metavar="command"
|
||||
)
|
||||
subparsers.required = True
|
||||
subparsers.add_parser("config", help="Validate the configuration and spit it out.")
|
||||
|
||||
parser_config = subparsers.add_parser(
|
||||
"config", help="Validate the configuration and spit it out."
|
||||
)
|
||||
parser_config.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_config.add_argument(
|
||||
"--show-secrets", help="Show secrets in output.", action="store_true"
|
||||
)
|
||||
|
||||
parser_compile = subparsers.add_parser(
|
||||
"compile", help="Read the configuration and compile a program."
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_compile.add_argument(
|
||||
"--only-generate",
|
||||
help="Only generate source code, do not compile.",
|
||||
@@ -525,123 +682,244 @@ def parse_args(argv):
|
||||
)
|
||||
|
||||
parser_upload = subparsers.add_parser(
|
||||
"upload", help="Validate the configuration " "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.",
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--file",
|
||||
help="Manually specify the binary file to upload.",
|
||||
)
|
||||
|
||||
parser_logs = subparsers.add_parser(
|
||||
"logs", help="Validate the configuration " "and show all MQTT logs."
|
||||
"logs",
|
||||
help="Validate the configuration and show all logs.",
|
||||
parents=[mqtt_options],
|
||||
)
|
||||
parser_logs.add_argument("--topic", help="Manually set the topic to subscribe to.")
|
||||
parser_logs.add_argument("--username", help="Manually set the username.")
|
||||
parser_logs.add_argument("--password", help="Manually set the password.")
|
||||
parser_logs.add_argument("--client-id", help="Manually set the client id.")
|
||||
parser_logs.add_argument(
|
||||
"--serial-port",
|
||||
help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
"configuration", help="Your YAML configuration file.", nargs=1
|
||||
)
|
||||
parser_logs.add_argument(
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
)
|
||||
|
||||
parser_run = subparsers.add_parser(
|
||||
"run",
|
||||
help="Validate the configuration, create a binary, "
|
||||
"upload it, and start MQTT logs.",
|
||||
help="Validate the configuration, create a binary, upload it, and start logs.",
|
||||
parents=[mqtt_options],
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--upload-port",
|
||||
help="Manually specify the upload port/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.",
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--no-logs", help="Disable starting MQTT logs.", action="store_true"
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--topic", help="Manually set the topic to subscribe to for logs."
|
||||
"--no-logs", help="Disable starting logs.", action="store_true"
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--username", help="Manually set the MQTT username for logs."
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--password", help="Manually set the MQTT password for logs."
|
||||
)
|
||||
parser_run.add_argument("--client-id", help="Manually set the client id for logs.")
|
||||
|
||||
parser_clean = subparsers.add_parser(
|
||||
"clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages."
|
||||
"clean-mqtt",
|
||||
help="Helper to clear retained messages from an MQTT topic.",
|
||||
parents=[mqtt_options],
|
||||
)
|
||||
parser_clean.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_clean.add_argument("--topic", help="Manually set the topic to subscribe to.")
|
||||
parser_clean.add_argument("--username", help="Manually set the username.")
|
||||
parser_clean.add_argument("--password", help="Manually set the password.")
|
||||
parser_clean.add_argument("--client-id", help="Manually set the client id.")
|
||||
|
||||
subparsers.add_parser(
|
||||
parser_wizard = subparsers.add_parser(
|
||||
"wizard",
|
||||
help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.",
|
||||
help="A helpful setup wizard that will guide you through setting up ESPHome.",
|
||||
)
|
||||
parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
|
||||
|
||||
subparsers.add_parser(
|
||||
parser_fingerprint = subparsers.add_parser(
|
||||
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
|
||||
)
|
||||
parser_fingerprint.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
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.")
|
||||
parser_clean = subparsers.add_parser(
|
||||
"clean", help="Delete all temporary build files."
|
||||
)
|
||||
parser_clean.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
dashboard = subparsers.add_parser(
|
||||
parser_dashboard = subparsers.add_parser(
|
||||
"dashboard", help="Create a simple web server for a dashboard."
|
||||
)
|
||||
dashboard.add_argument(
|
||||
parser_dashboard.add_argument(
|
||||
"configuration", help="Your YAML configuration file directory."
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--port",
|
||||
help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int,
|
||||
default=6052,
|
||||
)
|
||||
dashboard.add_argument(
|
||||
parser_dashboard.add_argument(
|
||||
"--address",
|
||||
help="The address to bind to.",
|
||||
type=str,
|
||||
default="0.0.0.0",
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--username",
|
||||
help="The optional username to require " "for authentication.",
|
||||
help="The optional username to require for authentication.",
|
||||
type=str,
|
||||
default="",
|
||||
)
|
||||
dashboard.add_argument(
|
||||
parser_dashboard.add_argument(
|
||||
"--password",
|
||||
help="The optional password to require " "for authentication.",
|
||||
help="The optional password to require for authentication.",
|
||||
type=str,
|
||||
default="",
|
||||
)
|
||||
dashboard.add_argument(
|
||||
parser_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(
|
||||
parser_dashboard.add_argument(
|
||||
"--ha-addon", help=argparse.SUPPRESS, action="store_true"
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--socket", help="Make the dashboard serve under a unix socket", type=str
|
||||
)
|
||||
|
||||
vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS)
|
||||
vscode.add_argument("--ace", action="store_true")
|
||||
parser_vscode = subparsers.add_parser("vscode")
|
||||
parser_vscode.add_argument("configuration", help="Your YAML configuration file.")
|
||||
parser_vscode.add_argument("--ace", action="store_true")
|
||||
|
||||
subparsers.add_parser("update-all", help=argparse.SUPPRESS)
|
||||
parser_update = subparsers.add_parser("update-all")
|
||||
parser_update.add_argument(
|
||||
"configuration", help="Your YAML configuration file directories.", nargs="+"
|
||||
)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
parser_idedata = subparsers.add_parser("idedata")
|
||||
parser_idedata.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs=1
|
||||
)
|
||||
|
||||
parser_rename = subparsers.add_parser(
|
||||
"rename",
|
||||
help="Rename a device in YAML, compile the binary and upload it.",
|
||||
)
|
||||
parser_rename.add_argument(
|
||||
"configuration", help="Your YAML configuration file.", nargs=1
|
||||
)
|
||||
parser_rename.add_argument("name", help="The new name for the device.", type=str)
|
||||
|
||||
# Keep backward compatibility with the old command line format of
|
||||
# esphome <config> <command>.
|
||||
#
|
||||
# Unfortunately this can't be done by adding another configuration argument to the
|
||||
# main config parser, as argparse is greedy when parsing arguments, so in regular
|
||||
# usage it'll eat the command as the configuration argument and error out out
|
||||
# because it can't parse the configuration as a command.
|
||||
#
|
||||
# Instead, if parsing using the current format fails, construct an ad-hoc parser
|
||||
# that doesn't actually process the arguments, but parses them enough to let us
|
||||
# figure out if the old format is used. In that case, swap the command and
|
||||
# configuration in the arguments and retry with the normal parser (and raise
|
||||
# a deprecation warning).
|
||||
arguments = argv[1:]
|
||||
|
||||
# On Python 3.9+ we can simply set exit_on_error=False in the constructor
|
||||
def _raise(x):
|
||||
raise argparse.ArgumentError(None, x)
|
||||
|
||||
# First, try new-style parsing, but don't exit in case of failure
|
||||
try:
|
||||
# duplicate parser so that we can use the original one to raise errors later on
|
||||
current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
|
||||
current_parser.set_defaults(deprecated_argv_suggestion=None)
|
||||
current_parser.error = _raise
|
||||
return current_parser.parse_args(arguments)
|
||||
except argparse.ArgumentError:
|
||||
pass
|
||||
|
||||
# Second, try compat parsing and rearrange the command-line if it succeeds
|
||||
# Disable argparse's built-in help option and add it manually to prevent this
|
||||
# parser from printing the help messagefor the old format when invoked with -h.
|
||||
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
|
||||
compat_parser.add_argument("-h", "--help", action="store_true")
|
||||
compat_parser.add_argument("configuration", nargs="*")
|
||||
compat_parser.add_argument(
|
||||
"command",
|
||||
choices=[
|
||||
"config",
|
||||
"compile",
|
||||
"upload",
|
||||
"logs",
|
||||
"run",
|
||||
"clean-mqtt",
|
||||
"wizard",
|
||||
"mqtt-fingerprint",
|
||||
"version",
|
||||
"clean",
|
||||
"dashboard",
|
||||
"vscode",
|
||||
"update-all",
|
||||
],
|
||||
)
|
||||
|
||||
try:
|
||||
compat_parser.error = _raise
|
||||
result, unparsed = compat_parser.parse_known_args(argv[1:])
|
||||
last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
|
||||
unparsed = [
|
||||
"--device" if arg in ("--upload-port", "--serial-port") else arg
|
||||
for arg in unparsed
|
||||
]
|
||||
arguments = (
|
||||
arguments[0:last_option]
|
||||
+ [result.command]
|
||||
+ result.configuration
|
||||
+ unparsed
|
||||
)
|
||||
deprecated_argv_suggestion = arguments
|
||||
except argparse.ArgumentError:
|
||||
# old-style parsing failed, don't suggest any argument
|
||||
deprecated_argv_suggestion = None
|
||||
|
||||
# Finally, run the new-style parser again with the possibly swapped arguments,
|
||||
# and let it error out if the command is unparsable.
|
||||
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
|
||||
return parser.parse_args(arguments)
|
||||
|
||||
|
||||
def run_esphome(argv):
|
||||
args = parse_args(argv)
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
setup_log(args.verbose, args.quiet)
|
||||
if args.command != "version" and not args.configuration:
|
||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||
return 1
|
||||
setup_log(
|
||||
args.verbose,
|
||||
args.quiet,
|
||||
# Show timestamp for dashboard access logs
|
||||
args.command == "dashboard",
|
||||
)
|
||||
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
|
||||
_LOGGER.warning(
|
||||
"Calling ESPHome with the configuration before the command is deprecated "
|
||||
"and will be removed in the future. "
|
||||
)
|
||||
_LOGGER.warning("Please instead use:")
|
||||
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion))
|
||||
|
||||
if sys.version_info < (3, 6, 0):
|
||||
if sys.version_info < (3, 8, 0):
|
||||
_LOGGER.error(
|
||||
"You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
|
||||
"with this Python version. Please reinstall ESPHome with Python 3.6+"
|
||||
"You're running ESPHome with Python <3.8. ESPHome is no longer compatible "
|
||||
"with this Python version. Please reinstall ESPHome with Python 3.8+"
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -649,16 +927,20 @@ def run_esphome(argv):
|
||||
try:
|
||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
|
||||
for conf_path in args.configuration:
|
||||
if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||
continue
|
||||
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
config = read_config(dict(args.substitution) if args.substitution else {})
|
||||
if config is None:
|
||||
return 1
|
||||
return 2
|
||||
CORE.config = config
|
||||
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
@@ -667,7 +949,7 @@ def run_esphome(argv):
|
||||
try:
|
||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
_LOGGER.error(e, exc_info=args.verbose)
|
||||
return 1
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1,517 +0,0 @@
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from typing import Optional # noqa
|
||||
from google.protobuf import message # noqa
|
||||
|
||||
from esphome import const
|
||||
import esphome.api.api_pb2 as pb
|
||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import resolve_ip_address, indent, color
|
||||
from esphome.util import safe_print
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APIConnectionError(EsphomeError):
|
||||
pass
|
||||
|
||||
|
||||
MESSAGE_TYPE_TO_PROTO = {
|
||||
1: pb.HelloRequest,
|
||||
2: pb.HelloResponse,
|
||||
3: pb.ConnectRequest,
|
||||
4: pb.ConnectResponse,
|
||||
5: pb.DisconnectRequest,
|
||||
6: pb.DisconnectResponse,
|
||||
7: pb.PingRequest,
|
||||
8: pb.PingResponse,
|
||||
9: pb.DeviceInfoRequest,
|
||||
10: pb.DeviceInfoResponse,
|
||||
11: pb.ListEntitiesRequest,
|
||||
12: pb.ListEntitiesBinarySensorResponse,
|
||||
13: pb.ListEntitiesCoverResponse,
|
||||
14: pb.ListEntitiesFanResponse,
|
||||
15: pb.ListEntitiesLightResponse,
|
||||
16: pb.ListEntitiesSensorResponse,
|
||||
17: pb.ListEntitiesSwitchResponse,
|
||||
18: pb.ListEntitiesTextSensorResponse,
|
||||
19: pb.ListEntitiesDoneResponse,
|
||||
20: pb.SubscribeStatesRequest,
|
||||
21: pb.BinarySensorStateResponse,
|
||||
22: pb.CoverStateResponse,
|
||||
23: pb.FanStateResponse,
|
||||
24: pb.LightStateResponse,
|
||||
25: pb.SensorStateResponse,
|
||||
26: pb.SwitchStateResponse,
|
||||
27: pb.TextSensorStateResponse,
|
||||
28: pb.SubscribeLogsRequest,
|
||||
29: pb.SubscribeLogsResponse,
|
||||
30: pb.CoverCommandRequest,
|
||||
31: pb.FanCommandRequest,
|
||||
32: pb.LightCommandRequest,
|
||||
33: pb.SwitchCommandRequest,
|
||||
34: pb.SubscribeServiceCallsRequest,
|
||||
35: pb.ServiceCallResponse,
|
||||
36: pb.GetTimeRequest,
|
||||
37: pb.GetTimeResponse,
|
||||
}
|
||||
|
||||
|
||||
def _varuint_to_bytes(value):
|
||||
if value <= 0x7F:
|
||||
return bytes([value])
|
||||
|
||||
ret = bytes()
|
||||
while value:
|
||||
temp = value & 0x7F
|
||||
value >>= 7
|
||||
if value:
|
||||
ret += bytes([temp | 0x80])
|
||||
else:
|
||||
ret += bytes([temp])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _bytes_to_varuint(value):
|
||||
result = 0
|
||||
bitpos = 0
|
||||
for val in value:
|
||||
result |= (val & 0x7F) << bitpos
|
||||
bitpos += 7
|
||||
if (val & 0x80) == 0:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes,not-callable
|
||||
class APIClient(threading.Thread):
|
||||
def __init__(self, address, port, password):
|
||||
threading.Thread.__init__(self)
|
||||
self._address = address # type: str
|
||||
self._port = port # type: int
|
||||
self._password = password # type: Optional[str]
|
||||
self._socket = None # type: Optional[socket.socket]
|
||||
self._socket_open_event = threading.Event()
|
||||
self._socket_write_lock = threading.Lock()
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
self._message_handlers = []
|
||||
self._keepalive = 5
|
||||
self._ping_timer = None
|
||||
|
||||
self.on_disconnect = None
|
||||
self.on_connect = None
|
||||
self.on_login = None
|
||||
self.auto_reconnect = False
|
||||
self._running_event = threading.Event()
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
@property
|
||||
def stopped(self):
|
||||
return self._stop_event.is_set()
|
||||
|
||||
def _refresh_ping(self):
|
||||
if self._ping_timer is not None:
|
||||
self._ping_timer.cancel()
|
||||
self._ping_timer = None
|
||||
|
||||
def func():
|
||||
self._ping_timer = None
|
||||
|
||||
if self._connected:
|
||||
try:
|
||||
self.ping()
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error(err)
|
||||
else:
|
||||
self._refresh_ping()
|
||||
|
||||
self._ping_timer = threading.Timer(self._keepalive, func)
|
||||
self._ping_timer.start()
|
||||
|
||||
def _cancel_ping(self):
|
||||
if self._ping_timer is not None:
|
||||
self._ping_timer.cancel()
|
||||
self._ping_timer = None
|
||||
|
||||
def _close_socket(self):
|
||||
self._cancel_ping()
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
self._socket_open_event.clear()
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
self._message_handlers = []
|
||||
|
||||
def stop(self, force=False):
|
||||
if self.stopped:
|
||||
raise ValueError
|
||||
|
||||
if self._connected and not force:
|
||||
try:
|
||||
self.disconnect()
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
self._stop_event.set()
|
||||
if not force:
|
||||
self.join()
|
||||
|
||||
def connect(self):
|
||||
if not self._running_event.wait(0.1):
|
||||
raise APIConnectionError("You need to call start() first!")
|
||||
|
||||
if self._connected:
|
||||
self.disconnect(on_disconnect=False)
|
||||
|
||||
try:
|
||||
ip = resolve_ip_address(self._address)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.warning(
|
||||
"Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
self._address,
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)"
|
||||
)
|
||||
raise APIConnectionError(err) from err
|
||||
|
||||
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.settimeout(10.0)
|
||||
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
try:
|
||||
self._socket.connect((ip, self._port))
|
||||
except OSError as err:
|
||||
err = APIConnectionError(f"Error connecting to {ip}: {err}")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
self._socket.settimeout(0.1)
|
||||
|
||||
self._socket_open_event.set()
|
||||
|
||||
hello = pb.HelloRequest()
|
||||
hello.client_info = f"ESPHome v{const.__version__}"
|
||||
try:
|
||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
_LOGGER.debug(
|
||||
"Successfully connected to %s ('%s' API=%s.%s)",
|
||||
self._address,
|
||||
resp.server_info,
|
||||
resp.api_version_major,
|
||||
resp.api_version_minor,
|
||||
)
|
||||
self._connected = True
|
||||
self._refresh_ping()
|
||||
if self.on_connect is not None:
|
||||
self.on_connect()
|
||||
|
||||
def _check_connected(self):
|
||||
if not self._connected:
|
||||
err = APIConnectionError("Must be connected!")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
|
||||
def login(self):
|
||||
self._check_connected()
|
||||
if self._authenticated:
|
||||
raise APIConnectionError("Already logged in!")
|
||||
|
||||
connect = pb.ConnectRequest()
|
||||
if self._password is not None:
|
||||
connect.password = self._password
|
||||
resp = self._send_message_await_response(connect, pb.ConnectResponse)
|
||||
if resp.invalid_password:
|
||||
raise APIConnectionError("Invalid password!")
|
||||
|
||||
self._authenticated = True
|
||||
if self.on_login is not None:
|
||||
self.on_login()
|
||||
|
||||
def _fatal_error(self, err):
|
||||
was_connected = self._connected
|
||||
|
||||
self._close_socket()
|
||||
|
||||
if was_connected and self.on_disconnect is not None:
|
||||
self.on_disconnect(err)
|
||||
|
||||
def _write(self, data): # type: (bytes) -> None
|
||||
if self._socket is None:
|
||||
raise APIConnectionError("Socket closed")
|
||||
|
||||
# _LOGGER.debug("Write: %s", format_bytes(data))
|
||||
with self._socket_write_lock:
|
||||
try:
|
||||
self._socket.sendall(data)
|
||||
except OSError as err:
|
||||
err = APIConnectionError(f"Error while writing data: {err}")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
|
||||
def _send_message(self, msg):
|
||||
# type: (message.Message) -> None
|
||||
for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
|
||||
if isinstance(msg, klass):
|
||||
break
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
encoded = msg.SerializeToString()
|
||||
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
|
||||
req = bytes([0])
|
||||
req += _varuint_to_bytes(len(encoded))
|
||||
req += _varuint_to_bytes(message_type)
|
||||
req += encoded
|
||||
self._write(req)
|
||||
|
||||
def _send_message_await_response_complex(
|
||||
self, send_msg, do_append, do_stop, timeout=5
|
||||
):
|
||||
event = threading.Event()
|
||||
responses = []
|
||||
|
||||
def on_message(resp):
|
||||
if do_append(resp):
|
||||
responses.append(resp)
|
||||
if do_stop(resp):
|
||||
event.set()
|
||||
|
||||
self._message_handlers.append(on_message)
|
||||
self._send_message(send_msg)
|
||||
ret = event.wait(timeout)
|
||||
try:
|
||||
self._message_handlers.remove(on_message)
|
||||
except ValueError:
|
||||
pass
|
||||
if not ret:
|
||||
raise APIConnectionError("Timeout while waiting for message response!")
|
||||
return responses
|
||||
|
||||
def _send_message_await_response(self, send_msg, response_type, timeout=5):
|
||||
def is_response(msg):
|
||||
return isinstance(msg, response_type)
|
||||
|
||||
return self._send_message_await_response_complex(
|
||||
send_msg, is_response, is_response, timeout
|
||||
)[0]
|
||||
|
||||
def device_info(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(
|
||||
pb.DeviceInfoRequest(), pb.DeviceInfoResponse
|
||||
)
|
||||
|
||||
def ping(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
|
||||
|
||||
def disconnect(self, on_disconnect=True):
|
||||
self._check_connected()
|
||||
|
||||
try:
|
||||
self._send_message_await_response(
|
||||
pb.DisconnectRequest(), pb.DisconnectResponse
|
||||
)
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
if self.on_disconnect is not None and on_disconnect:
|
||||
self.on_disconnect(None)
|
||||
|
||||
def _check_authenticated(self):
|
||||
if not self._authenticated:
|
||||
raise APIConnectionError("Must login first!")
|
||||
|
||||
def subscribe_logs(self, on_log, log_level=7, dump_config=False):
|
||||
self._check_authenticated()
|
||||
|
||||
def on_msg(msg):
|
||||
if isinstance(msg, pb.SubscribeLogsResponse):
|
||||
on_log(msg)
|
||||
|
||||
self._message_handlers.append(on_msg)
|
||||
req = pb.SubscribeLogsRequest(dump_config=dump_config)
|
||||
req.level = log_level
|
||||
self._send_message(req)
|
||||
|
||||
def _recv(self, amount):
|
||||
ret = bytes()
|
||||
if amount == 0:
|
||||
return ret
|
||||
|
||||
while len(ret) < amount:
|
||||
if self.stopped:
|
||||
raise APIConnectionError("Stopped!")
|
||||
if not self._socket_open_event.is_set():
|
||||
raise APIConnectionError("No socket!")
|
||||
try:
|
||||
val = self._socket.recv(amount - len(ret))
|
||||
except AttributeError as err:
|
||||
raise APIConnectionError("Socket was closed") from err
|
||||
except socket.timeout:
|
||||
continue
|
||||
except OSError as err:
|
||||
raise APIConnectionError(f"Error while receiving data: {err}") from err
|
||||
ret += val
|
||||
return ret
|
||||
|
||||
def _recv_varint(self):
|
||||
raw = bytes()
|
||||
while not raw or raw[-1] & 0x80:
|
||||
raw += self._recv(1)
|
||||
return _bytes_to_varuint(raw)
|
||||
|
||||
def _run_once(self):
|
||||
if not self._socket_open_event.wait(0.1):
|
||||
return
|
||||
|
||||
# Preamble
|
||||
if self._recv(1)[0] != 0x00:
|
||||
raise APIConnectionError("Invalid preamble")
|
||||
|
||||
length = self._recv_varint()
|
||||
msg_type = self._recv_varint()
|
||||
|
||||
raw_msg = self._recv(length)
|
||||
if msg_type not in MESSAGE_TYPE_TO_PROTO:
|
||||
_LOGGER.debug("Skipping message type %s", msg_type)
|
||||
return
|
||||
|
||||
msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
|
||||
msg.ParseFromString(raw_msg)
|
||||
_LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
|
||||
for msg_handler in self._message_handlers[:]:
|
||||
msg_handler(msg)
|
||||
self._handle_internal_messages(msg)
|
||||
|
||||
def run(self):
|
||||
self._running_event.set()
|
||||
while not self.stopped:
|
||||
try:
|
||||
self._run_once()
|
||||
except APIConnectionError as err:
|
||||
if self.stopped:
|
||||
break
|
||||
if self._connected:
|
||||
_LOGGER.error("Error while reading incoming messages: %s", err)
|
||||
self._fatal_error(err)
|
||||
self._running_event.clear()
|
||||
|
||||
def _handle_internal_messages(self, msg):
|
||||
if isinstance(msg, pb.DisconnectRequest):
|
||||
self._send_message(pb.DisconnectResponse())
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
self._connected = False
|
||||
if self.on_disconnect is not None:
|
||||
self.on_disconnect(None)
|
||||
elif isinstance(msg, pb.PingRequest):
|
||||
self._send_message(pb.PingResponse())
|
||||
elif isinstance(msg, pb.GetTimeRequest):
|
||||
resp = pb.GetTimeResponse()
|
||||
resp.epoch_seconds = int(time.time())
|
||||
self._send_message(resp)
|
||||
|
||||
|
||||
def run_logs(config, address):
|
||||
conf = config["api"]
|
||||
port = conf[CONF_PORT]
|
||||
password = conf[CONF_PASSWORD]
|
||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||
|
||||
cli = APIClient(address, port, password)
|
||||
stopping = False
|
||||
retry_timer = []
|
||||
|
||||
has_connects = []
|
||||
|
||||
def try_connect(err, tries=0):
|
||||
if stopping:
|
||||
return
|
||||
|
||||
if err:
|
||||
_LOGGER.warning("Disconnected from API: %s", err)
|
||||
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
|
||||
error = None
|
||||
try:
|
||||
cli.connect()
|
||||
cli.login()
|
||||
except APIConnectionError as err2: # noqa
|
||||
error = err2
|
||||
|
||||
if error is None:
|
||||
_LOGGER.info("Successfully connected to %s", address)
|
||||
return
|
||||
|
||||
wait_time = int(min(1.5 ** min(tries, 100), 30))
|
||||
if not has_connects:
|
||||
_LOGGER.warning(
|
||||
"Initial connection failed. The ESP might not be connected "
|
||||
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error,
|
||||
wait_time,
|
||||
)
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error,
|
||||
wait_time,
|
||||
)
|
||||
timer = threading.Timer(
|
||||
wait_time, functools.partial(try_connect, None, tries + 1)
|
||||
)
|
||||
timer.start()
|
||||
retry_timer.append(timer)
|
||||
|
||||
def on_log(msg):
|
||||
time_ = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
text = msg.message
|
||||
if msg.send_failed:
|
||||
text = color(
|
||||
"white",
|
||||
"(Message skipped because it was too big to fit in "
|
||||
"TCP buffer - This is only cosmetic)",
|
||||
)
|
||||
safe_print(time_ + text)
|
||||
|
||||
def on_login():
|
||||
try:
|
||||
cli.subscribe_logs(on_log, dump_config=not has_connects)
|
||||
has_connects.append(True)
|
||||
except APIConnectionError:
|
||||
cli.disconnect()
|
||||
|
||||
cli.on_disconnect = try_connect
|
||||
cli.on_login = on_login
|
||||
cli.start()
|
||||
|
||||
try:
|
||||
try_connect(None)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
stopping = True
|
||||
cli.stop(True)
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
return 0
|
@@ -3,15 +3,16 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AUTOMATION_ID,
|
||||
CONF_CONDITION,
|
||||
CONF_COUNT,
|
||||
CONF_ELSE,
|
||||
CONF_ID,
|
||||
CONF_THEN,
|
||||
CONF_TIMEOUT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE_ID,
|
||||
CONF_TIME,
|
||||
)
|
||||
from esphome.core import coroutine
|
||||
from esphome.jsonschema import jschema_extractor
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.util import Registry
|
||||
|
||||
|
||||
@@ -22,11 +23,10 @@ def maybe_simple_id(*validators):
|
||||
def maybe_conf(conf, *validators):
|
||||
validator = cv.All(*validators)
|
||||
|
||||
@jschema_extractor("maybe")
|
||||
@schema_extractor("maybe")
|
||||
def validate(value):
|
||||
# pylint: disable=comparison-with-callable
|
||||
if value == jschema_extractor:
|
||||
return validator
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return (validator, conf)
|
||||
|
||||
if isinstance(value, dict):
|
||||
return validator(value)
|
||||
@@ -66,6 +66,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
@@ -109,11 +110,9 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
# This should only happen with invalid configs, but let's have a nice error message.
|
||||
return [schema(value)]
|
||||
|
||||
@jschema_extractor("automation")
|
||||
@schema_extractor("automation")
|
||||
def validator(value):
|
||||
# hack to get the schema
|
||||
# pylint: disable=comparison-with-callable
|
||||
if value == jschema_extractor:
|
||||
if value == SCHEMA_EXTRACT:
|
||||
return schema
|
||||
|
||||
value = validator_(value)
|
||||
@@ -142,27 +141,27 @@ NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
|
||||
|
||||
|
||||
@register_condition("and", AndCondition, validate_condition_list)
|
||||
def and_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
async def and_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition("or", OrCondition, validate_condition_list)
|
||||
def or_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
async def or_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition("not", NotCondition, validate_potentially_and_condition)
|
||||
def not_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
async def not_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = await build_condition(config, template_arg, args)
|
||||
return cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
|
||||
|
||||
@register_condition("lambda", LambdaCondition, cv.lambda_)
|
||||
def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
||||
async def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
lambda_ = await cg.process_lambda(config, args, return_type=bool)
|
||||
return cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_condition(
|
||||
@@ -177,26 +176,26 @@ def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
)
|
||||
def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(
|
||||
async def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = await build_condition(
|
||||
config[CONF_CONDITION], cg.TemplateArguments(), []
|
||||
)
|
||||
var = cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
yield cg.register_component(var, config)
|
||||
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||
await cg.register_component(var, config)
|
||||
templ = await cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||
cg.add(var.set_time(templ))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
@register_action(
|
||||
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
||||
)
|
||||
def delay_action_to_code(config, action_id, template_arg, args):
|
||||
async def delay_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_component(var, {})
|
||||
template_ = yield cg.templatable(config, args, cg.uint32)
|
||||
await cg.register_component(var, {})
|
||||
template_ = await cg.templatable(config, args, cg.uint32)
|
||||
cg.add(var.set_delay(template_))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
@register_action(
|
||||
@@ -211,16 +210,16 @@ def delay_action_to_code(config, action_id, template_arg, args):
|
||||
cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
|
||||
),
|
||||
)
|
||||
def if_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
async def if_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
if CONF_THEN in config:
|
||||
actions = yield build_action_list(config[CONF_THEN], template_arg, args)
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
if CONF_ELSE in config:
|
||||
actions = yield build_action_list(config[CONF_ELSE], template_arg, args)
|
||||
actions = await build_action_list(config[CONF_ELSE], template_arg, args)
|
||||
cg.add(var.add_else(actions))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
@register_action(
|
||||
@@ -233,37 +232,57 @@ def if_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)
|
||||
async def while_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
actions = yield build_action_list(config[CONF_THEN], template_arg, args)
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
yield var
|
||||
return var
|
||||
|
||||
|
||||
def validate_wait_until(value):
|
||||
schema = cv.Schema(
|
||||
@register_action(
|
||||
"repeat",
|
||||
RepeatAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}
|
||||
)
|
||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||
return schema(value)
|
||||
return validate_wait_until({CONF_CONDITION: value})
|
||||
),
|
||||
)
|
||||
async def repeat_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
|
||||
cg.add(var.set_count(count_template))
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
return var
|
||||
|
||||
|
||||
@register_action("wait_until", WaitUntilAction, validate_wait_until)
|
||||
def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
_validate_wait_until = cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_TIMEOUT): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
},
|
||||
key=CONF_CONDITION,
|
||||
)
|
||||
|
||||
|
||||
@register_action("wait_until", WaitUntilAction, _validate_wait_until)
|
||||
async def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
if CONF_TIMEOUT in config:
|
||||
template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
|
||||
cg.add(var.set_timeout_value(template_))
|
||||
await cg.register_component(var, {})
|
||||
return var
|
||||
|
||||
|
||||
@register_action("lambda", LambdaAction, cv.lambda_)
|
||||
def lambda_action_to_code(config, action_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
|
||||
yield cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
async def lambda_action_to_code(config, action_id, template_arg, args):
|
||||
lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
|
||||
return cg.new_Pvariable(action_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_action(
|
||||
@@ -275,54 +294,51 @@ def lambda_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])
|
||||
yield cg.new_Pvariable(action_id, template_arg, comp)
|
||||
async def component_update_action_to_code(config, action_id, template_arg, args):
|
||||
comp = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, comp)
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_action(full_config, template_arg, args):
|
||||
async def build_action(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
ACTION_REGISTRY, full_config
|
||||
)
|
||||
action_id = full_config[CONF_TYPE_ID]
|
||||
builder = registry_entry.coroutine_fun
|
||||
yield builder(config, action_id, template_arg, args)
|
||||
ret = await builder(config, action_id, template_arg, args)
|
||||
return ret
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_action_list(config, templ, arg_type):
|
||||
async def build_action_list(config, templ, arg_type):
|
||||
actions = []
|
||||
for conf in config:
|
||||
action = yield build_action(conf, templ, arg_type)
|
||||
action = await build_action(conf, templ, arg_type)
|
||||
actions.append(action)
|
||||
yield actions
|
||||
return actions
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_condition(full_config, template_arg, args):
|
||||
async def build_condition(full_config, template_arg, args):
|
||||
registry_entry, config = cg.extract_registry_entry_config(
|
||||
CONDITION_REGISTRY, full_config
|
||||
)
|
||||
action_id = full_config[CONF_TYPE_ID]
|
||||
builder = registry_entry.coroutine_fun
|
||||
yield builder(config, action_id, template_arg, args)
|
||||
ret = await builder(config, action_id, template_arg, args)
|
||||
return ret
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_condition_list(config, templ, args):
|
||||
async def build_condition_list(config, templ, args):
|
||||
conditions = []
|
||||
for conf in config:
|
||||
condition = yield build_condition(conf, templ, args)
|
||||
condition = await build_condition(conf, templ, args)
|
||||
conditions.append(condition)
|
||||
yield conditions
|
||||
return conditions
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_automation(trigger, args, config):
|
||||
async def build_automation(trigger, args, config):
|
||||
arg_types = [arg[0] for arg in args]
|
||||
templ = cg.TemplateArguments(*arg_types)
|
||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
|
||||
actions = yield build_action_list(config[CONF_THEN], templ, args)
|
||||
actions = await build_action_list(config[CONF_THEN], templ, args)
|
||||
cg.add(obj.add_actions(actions))
|
||||
yield obj
|
||||
return obj
|
||||
|
@@ -19,8 +19,10 @@ from esphome.cpp_generator import ( # noqa
|
||||
Statement,
|
||||
LineComment,
|
||||
progmem_array,
|
||||
static_const_array,
|
||||
statement,
|
||||
variable,
|
||||
with_local_variable,
|
||||
new_variable,
|
||||
Pvariable,
|
||||
new_Pvariable,
|
||||
@@ -29,6 +31,7 @@ from esphome.cpp_generator import ( # noqa
|
||||
add_library,
|
||||
add_build_flag,
|
||||
add_define,
|
||||
add_platformio_option,
|
||||
get_variable,
|
||||
get_variable_with_full_id,
|
||||
process_lambda,
|
||||
@@ -59,12 +62,15 @@ from esphome.cpp_types import ( # noqa
|
||||
uint8,
|
||||
uint16,
|
||||
uint32,
|
||||
uint64,
|
||||
int32,
|
||||
int64,
|
||||
size_t,
|
||||
const_char_ptr,
|
||||
NAN,
|
||||
esphome_ns,
|
||||
App,
|
||||
Nameable,
|
||||
EntityBase,
|
||||
Component,
|
||||
ComponentPtr,
|
||||
PollingComponent,
|
||||
@@ -72,8 +78,11 @@ from esphome.cpp_types import ( # noqa
|
||||
optional,
|
||||
arduino_json_ns,
|
||||
JsonObject,
|
||||
JsonObjectRef,
|
||||
JsonObjectConstRef,
|
||||
JsonObjectConst,
|
||||
Controller,
|
||||
GPIOPin,
|
||||
InternalGPIOPin,
|
||||
gpio_Flags,
|
||||
EntityCategory,
|
||||
Parented,
|
||||
)
|
||||
|
@@ -4,7 +4,7 @@
|
||||
namespace esphome {
|
||||
namespace a4988 {
|
||||
|
||||
static const char *TAG = "a4988.stepper";
|
||||
static const char *const TAG = "a4988.stepper";
|
||||
|
||||
void A4988::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up A4988...");
|
||||
@@ -46,6 +46,7 @@ void A4988::loop() {
|
||||
return;
|
||||
|
||||
this->dir_pin_->digital_write(dir == 1);
|
||||
delayMicroseconds(50);
|
||||
this->step_pin_->digital_write(true);
|
||||
delayMicroseconds(5);
|
||||
this->step_pin_->digital_write(false);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/stepper/stepper.h"
|
||||
|
||||
namespace esphome {
|
||||
|
@@ -18,16 +18,16 @@ CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield stepper.register_stepper(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await stepper.register_stepper(var, config)
|
||||
|
||||
step_pin = yield cg.gpio_pin_expression(config[CONF_STEP_PIN])
|
||||
step_pin = await cg.gpio_pin_expression(config[CONF_STEP_PIN])
|
||||
cg.add(var.set_step_pin(step_pin))
|
||||
dir_pin = yield cg.gpio_pin_expression(config[CONF_DIR_PIN])
|
||||
dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN])
|
||||
cg.add(var.set_dir_pin(dir_pin))
|
||||
|
||||
if CONF_SLEEP_PIN in config:
|
||||
sleep_pin = yield cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
|
||||
sleep_pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
|
||||
cg.add(var.set_sleep_pin(sleep_pin))
|
||||
|
@@ -1,28 +1,37 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "ac_dimmer.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cmath>
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
#include <core_esp8266_waveform.h>
|
||||
#endif
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
#include <esp32-hal-timer.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
|
||||
static const char *TAG = "ac_dimmer";
|
||||
static const char *const TAG = "ac_dimmer";
|
||||
|
||||
// Global array to store dimmer objects
|
||||
static AcDimmerDataStore *all_dimmers[32];
|
||||
static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
/// 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;
|
||||
/// However other factors like gate driver propagation time
|
||||
/// are also considered and a really low value is not important
|
||||
/// See also: https://github.com/esphome/issues/issues/1632
|
||||
static const uint32_t GATE_ENABLE_TIME = 50;
|
||||
|
||||
/// 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) {
|
||||
uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
// If no ZC signal received yet.
|
||||
if (this->crossed_zero_at == 0)
|
||||
return 0;
|
||||
@@ -34,19 +43,19 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
|
||||
if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
|
||||
this->enable_time_us = 0;
|
||||
this->gate_pin->digital_write(true);
|
||||
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);
|
||||
this->disable_time_us = std::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);
|
||||
this->gate_pin.digital_write(false);
|
||||
}
|
||||
|
||||
if (time_since_zc < this->enable_time_us)
|
||||
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) {
|
||||
} 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;
|
||||
}
|
||||
@@ -60,14 +69,15 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
}
|
||||
|
||||
/// Run timer interrupt code and return in how many µs the next event is expected
|
||||
uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
|
||||
uint32_t IRAM_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)
|
||||
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;
|
||||
@@ -77,7 +87,7 @@ uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
|
||||
}
|
||||
|
||||
/// GPIO interrupt routine, called when ZC pin triggers
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
void IRAM_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
|
||||
@@ -94,7 +104,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
|
||||
if (this->value == 65535) {
|
||||
// fully on, enable output immediately
|
||||
this->gate_pin->digital_write(true);
|
||||
this->gate_pin.digital_write(true);
|
||||
} else if (this->init_cycle) {
|
||||
// send a full cycle
|
||||
this->init_cycle = false;
|
||||
@@ -102,30 +112,31 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
this->disable_time_us = cycle_time_us;
|
||||
} else if (this->value == 0) {
|
||||
// fully off, disable output immediately
|
||||
this->gate_pin->digital_write(false);
|
||||
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);
|
||||
this->disable_time_us = std::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);
|
||||
this->enable_time_us = std::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);
|
||||
this->disable_time_us = std::max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
|
||||
} else {
|
||||
this->gate_pin->digital_write(false);
|
||||
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
|
||||
void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
// Attaching pin interrupts on the same pin will override the previous interrupt
|
||||
// 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*.
|
||||
@@ -138,11 +149,11 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_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(); }
|
||||
static hw_timer_t *dimmer_timer = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||
#endif
|
||||
|
||||
void AcDimmer::setup() {
|
||||
@@ -171,15 +182,16 @@ void AcDimmer::setup() {
|
||||
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);
|
||||
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_,
|
||||
gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_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
|
||||
#ifdef USE_ESP32
|
||||
// 80 Divider -> 1 count=1µs
|
||||
dimmer_timer = timerBegin(0, 80, true);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
|
||||
@@ -191,6 +203,7 @@ void AcDimmer::setup() {
|
||||
#endif
|
||||
}
|
||||
void AcDimmer::write_state(float state) {
|
||||
state = std::acos(1 - (2 * state)) / 3.14159; // RMS power compensation
|
||||
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_;
|
||||
@@ -202,12 +215,13 @@ void AcDimmer::dump_config() {
|
||||
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)
|
||||
if (method_ == DIM_METHOD_LEADING_PULSE) {
|
||||
ESP_LOGCONFIG(TAG, " Method: leading pulse");
|
||||
else if (method_ == DIM_METHOD_LEADING)
|
||||
} else if (method_ == DIM_METHOD_LEADING) {
|
||||
ESP_LOGCONFIG(TAG, " Method: leading");
|
||||
else
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Method: trailing");
|
||||
}
|
||||
|
||||
LOG_FLOAT_OUTPUT(this);
|
||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||
@@ -215,3 +229,5 @@ void AcDimmer::dump_config() {
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -11,11 +13,11 @@ enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TR
|
||||
|
||||
struct AcDimmerDataStore {
|
||||
/// Zero-cross pin
|
||||
ISRInternalGPIOPin *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;
|
||||
ISRInternalGPIOPin gate_pin;
|
||||
/// Value of the dimmer - 0 to 65535.
|
||||
uint16_t value;
|
||||
/// Minimum power for activation
|
||||
@@ -37,7 +39,7 @@ struct AcDimmerDataStore {
|
||||
|
||||
void gpio_intr();
|
||||
static void s_gpio_intr(AcDimmerDataStore *store);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
static void s_timer_intr();
|
||||
#endif
|
||||
};
|
||||
@@ -47,16 +49,16 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
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_gate_pin(InternalGPIOPin *gate_pin) { gate_pin_ = gate_pin; }
|
||||
void set_zero_cross_pin(InternalGPIOPin *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_;
|
||||
InternalGPIOPin *gate_pin_;
|
||||
InternalGPIOPin *zero_cross_pin_;
|
||||
AcDimmerDataStore store_;
|
||||
bool init_with_half_cycle_;
|
||||
DimMethod method_;
|
||||
@@ -64,3 +66,5 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
@@ -19,31 +19,34 @@ DIM_METHODS = {
|
||||
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)
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
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),
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
await 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)
|
||||
await output.register_output(var, config)
|
||||
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_GATE_PIN])
|
||||
pin = await 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])
|
||||
pin = await 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]))
|
||||
|
@@ -21,8 +21,7 @@ CONFIG_SCHEMA = cv.Schema({})
|
||||
"Adalight",
|
||||
{cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
|
||||
)
|
||||
def adalight_light_effect_to_code(config, effect_id):
|
||||
async 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
|
||||
await uart.register_uart_device(effect, config)
|
||||
return effect
|
||||
|
@@ -4,7 +4,7 @@
|
||||
namespace esphome {
|
||||
namespace adalight {
|
||||
|
||||
static const char *TAG = "adalight_light_effect";
|
||||
static const char *const TAG = "adalight_light_effect";
|
||||
|
||||
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
|
||||
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
|
||||
@@ -25,7 +25,7 @@ void AdalightLightEffect::stop() {
|
||||
AddressableLightEffect::stop();
|
||||
}
|
||||
|
||||
int AdalightLightEffect::get_frame_size_(int led_count) const {
|
||||
unsigned int AdalightLightEffect::get_frame_size_(int led_count) const {
|
||||
// 3 bytes: Ada
|
||||
// 2 bytes: LED count
|
||||
// 1 byte: checksum
|
||||
@@ -42,8 +42,9 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
|
||||
|
||||
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
|
||||
for (int led = it.size(); led-- > 0;) {
|
||||
it[led].set(COLOR_BLACK);
|
||||
it[led].set(Color::BLACK);
|
||||
}
|
||||
it.schedule_show();
|
||||
}
|
||||
|
||||
void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
|
||||
@@ -133,6 +134,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
|
||||
it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
|
||||
}
|
||||
|
||||
it.schedule_show();
|
||||
return CONSUMED;
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
|
||||
public:
|
||||
AdalightLightEffect(const std::string &name);
|
||||
|
||||
public:
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void apply(light::AddressableLight &it, const Color ¤t_color) override;
|
||||
@@ -25,12 +24,11 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
|
||||
CONSUMED,
|
||||
};
|
||||
|
||||
int get_frame_size_(int led_count) const;
|
||||
unsigned 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};
|
||||
|
@@ -1,92 +1,229 @@
|
||||
#include "adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
#include <Esp.h>
|
||||
ADC_MODE(ADC_VCC)
|
||||
#else
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#include <hardware/adc.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
static const char *TAG = "adc";
|
||||
static const char *const TAG = "adc";
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
|
||||
// 13bit for S2, and 12bit for all other esp32 variants
|
||||
#ifdef USE_ESP32
|
||||
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
|
||||
|
||||
#ifndef SOC_ADC_RTC_MAX_BITWIDTH
|
||||
#if USE_ESP32_VARIANT_ESP32S2
|
||||
static const int SOC_ADC_RTC_MAX_BITWIDTH = 13;
|
||||
#else
|
||||
static const int SOC_ADC_RTC_MAX_BITWIDTH = 12;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void ADCSensor::setup() {
|
||||
static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 bit) or 8191 (13 bit)
|
||||
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit)
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
extern "C"
|
||||
#endif
|
||||
void
|
||||
ADCSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||
#ifndef USE_ADC_SENSOR_VCC
|
||||
GPIOPin(this->pin_, INPUT).setup();
|
||||
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
|
||||
pin_->setup();
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
||||
#ifdef USE_ESP32
|
||||
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
|
||||
if (!autorange_) {
|
||||
adc1_config_channel_atten(channel_, attenuation_);
|
||||
}
|
||||
|
||||
// load characteristics for each attenuation
|
||||
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
|
||||
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
|
||||
1100, // default vref
|
||||
&cal_characteristics_[i]);
|
||||
switch (cal_value) {
|
||||
case ESP_ADC_CAL_VAL_EFUSE_VREF:
|
||||
ESP_LOGV(TAG, "Using eFuse Vref for calibration");
|
||||
break;
|
||||
case ESP_ADC_CAL_VAL_EFUSE_TP:
|
||||
ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
|
||||
break;
|
||||
case ESP_ADC_CAL_VAL_DEFAULT_VREF:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
adc_init();
|
||||
initialized = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
|
||||
}
|
||||
|
||||
void ADCSensor::dump_config() {
|
||||
LOG_SENSOR("", "ADC Sensor", this);
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
ESP_LOGCONFIG(TAG, " Pin: VCC");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||
switch (this->attenuation_) {
|
||||
case ADC_0db:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
|
||||
break;
|
||||
case ADC_2_5db:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
|
||||
break;
|
||||
case ADC_6db:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
|
||||
break;
|
||||
case ADC_11db:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
|
||||
break;
|
||||
#endif // USE_ESP8266
|
||||
|
||||
#ifdef USE_ESP32
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
if (autorange_) {
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: auto");
|
||||
} else {
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db");
|
||||
break;
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db");
|
||||
break;
|
||||
case ADC_ATTEN_DB_6:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 6db");
|
||||
break;
|
||||
case ADC_ATTEN_DB_11:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 11db");
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_RP2040
|
||||
if (this->is_temperature_) {
|
||||
ESP_LOGCONFIG(TAG, " Pin: Temperature");
|
||||
} else {
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
}
|
||||
#endif
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void ADCSensor::update() {
|
||||
float value_v = this->sample();
|
||||
ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
|
||||
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
float ADCSensor::sample() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
|
||||
switch (this->attenuation_) {
|
||||
case ADC_0db:
|
||||
value_v *= 1.1;
|
||||
break;
|
||||
case ADC_2_5db:
|
||||
value_v *= 1.5;
|
||||
break;
|
||||
case ADC_6db:
|
||||
value_v *= 2.2;
|
||||
break;
|
||||
case ADC_11db:
|
||||
value_v *= 3.9;
|
||||
break;
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#else
|
||||
int raw = analogRead(this->pin_->get_pin()); // NOLINT
|
||||
#endif
|
||||
if (output_raw_) {
|
||||
return raw;
|
||||
}
|
||||
return value_v;
|
||||
return raw / 1024.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
return ESP.getVcc() / 1024.0f;
|
||||
#else
|
||||
return analogRead(this->pin_) / 1024.0f; // NOLINT
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
float ADCSensor::sample() {
|
||||
if (!autorange_) {
|
||||
int raw = adc1_get_raw(channel_);
|
||||
if (raw == -1) {
|
||||
return NAN;
|
||||
}
|
||||
if (output_raw_) {
|
||||
return raw;
|
||||
}
|
||||
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
|
||||
return mv / 1000.0f;
|
||||
}
|
||||
|
||||
int raw11, raw6 = ADC_MAX, raw2 = ADC_MAX, raw0 = ADC_MAX;
|
||||
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
|
||||
raw11 = adc1_get_raw(channel_);
|
||||
if (raw11 < ADC_MAX) {
|
||||
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
|
||||
raw6 = adc1_get_raw(channel_);
|
||||
if (raw6 < ADC_MAX) {
|
||||
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
|
||||
raw2 = adc1_get_raw(channel_);
|
||||
if (raw2 < ADC_MAX) {
|
||||
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
|
||||
raw0 = adc1_get_raw(channel_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
|
||||
return NAN;
|
||||
}
|
||||
|
||||
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
|
||||
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
|
||||
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
|
||||
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
|
||||
|
||||
// Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
|
||||
uint32_t c11 = std::min(raw11, ADC_HALF);
|
||||
uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
|
||||
uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
|
||||
uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
|
||||
// max theoretical csum value is 4096*4 = 16384
|
||||
uint32_t csum = c11 + c6 + c2 + c0;
|
||||
|
||||
// each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32
|
||||
uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
|
||||
return mv_scaled / (float) (csum * 1000U);
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
float ADCSensor::sample() {
|
||||
if (this->is_temperature_) {
|
||||
adc_set_temp_sensor_enabled(true);
|
||||
delay(1);
|
||||
adc_select_input(4);
|
||||
} else {
|
||||
uint8_t pin = this->pin_->get_pin();
|
||||
adc_gpio_init(pin);
|
||||
adc_select_input(pin - 26);
|
||||
}
|
||||
|
||||
int raw = adc_read();
|
||||
if (this->is_temperature_) {
|
||||
adc_set_temp_sensor_enabled(false);
|
||||
}
|
||||
if (output_raw_) {
|
||||
return raw;
|
||||
}
|
||||
return raw * 3.3f / 4096.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||
#endif
|
||||
|
||||
|
@@ -1,19 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "driver/adc.h"
|
||||
#include <esp_adc_cal.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#ifdef USE_ESP32
|
||||
/// Set the attenuation for this pin. Only available on the ESP32.
|
||||
void set_attenuation(adc_attenuation_t attenuation);
|
||||
void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
|
||||
void set_channel(adc1_channel_t channel) { channel_ = channel; }
|
||||
void set_autorange(bool autorange) { autorange_ = autorange; }
|
||||
#endif
|
||||
|
||||
/// Update adc values.
|
||||
@@ -23,18 +30,31 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
void dump_config() override;
|
||||
/// `HARDWARE_LATE` setup priority.
|
||||
float get_setup_priority() const override;
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
|
||||
void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
|
||||
float sample() override;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ESP8266
|
||||
std::string unique_id() override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
uint8_t pin_;
|
||||
#ifdef USE_RP2040
|
||||
void set_is_temperature() { is_temperature_ = true; }
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
adc_attenuation_t attenuation_{ADC_0db};
|
||||
protected:
|
||||
InternalGPIOPin *pin_;
|
||||
bool output_raw_{false};
|
||||
|
||||
#ifdef USE_RP2040
|
||||
bool is_temperature_{false};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
|
||||
adc1_channel_t channel_{};
|
||||
bool autorange_{false};
|
||||
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@@ -4,29 +4,135 @@ from esphome import pins
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import (
|
||||
CONF_ATTENUATION,
|
||||
CONF_RAW,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_NUMBER,
|
||||
CONF_PIN,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
|
||||
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
|
||||
ATTENUATION_MODES = {
|
||||
"0db": cg.global_ns.ADC_0db,
|
||||
"2.5db": cg.global_ns.ADC_2_5db,
|
||||
"6db": cg.global_ns.ADC_6db,
|
||||
"11db": cg.global_ns.ADC_11db,
|
||||
"0db": cg.global_ns.ADC_ATTEN_DB_0,
|
||||
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
|
||||
"6db": cg.global_ns.ADC_ATTEN_DB_6,
|
||||
"11db": cg.global_ns.ADC_ATTEN_DB_11,
|
||||
"auto": "auto",
|
||||
}
|
||||
|
||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
|
||||
|
||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
|
||||
# pin to adc1 channel mapping
|
||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
VARIANT_ESP32: {
|
||||
36: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
37: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
38: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
39: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
32: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
33: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
34: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
35: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
},
|
||||
VARIANT_ESP32S2: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
6: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
7: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
8: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
9: adc1_channel_t.ADC1_CHANNEL_8,
|
||||
10: adc1_channel_t.ADC1_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32S3: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
6: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
7: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
8: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
9: adc1_channel_t.ADC1_CHANNEL_8,
|
||||
10: adc1_channel_t.ADC1_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32C3: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
VARIANT_ESP32H2: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def validate_adc_pin(value):
|
||||
vcc = str(value).upper()
|
||||
if vcc == "VCC":
|
||||
return cv.only_on_esp8266(vcc)
|
||||
return pins.analog_pin(value)
|
||||
if str(value).upper() == "VCC":
|
||||
return cv.only_on_esp8266("VCC")
|
||||
|
||||
if str(value).upper() == "TEMPERATURE":
|
||||
return cv.only_on_rp2040("TEMPERATURE")
|
||||
|
||||
if CORE.is_esp32:
|
||||
value = pins.internal_gpio_input_pin_number(value)
|
||||
variant = get_esp32_variant()
|
||||
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
|
||||
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
|
||||
|
||||
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
|
||||
raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
if CORE.is_esp8266:
|
||||
from esphome.components.esp8266.gpio import CONF_ANALOG
|
||||
|
||||
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
|
||||
value
|
||||
)
|
||||
|
||||
if value != 17: # A0
|
||||
raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
|
||||
return pins.gpio_pin_schema(
|
||||
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
|
||||
)(value)
|
||||
|
||||
if CORE.is_rp2040:
|
||||
value = pins.internal_gpio_input_pin_number(value)
|
||||
if value not in (26, 27, 28, 29):
|
||||
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_config(config):
|
||||
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
|
||||
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
|
||||
return config
|
||||
|
||||
|
||||
adc_ns = cg.esphome_ns.namespace("adc")
|
||||
@@ -34,30 +140,52 @@ ADCSensor = adc_ns.class_(
|
||||
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
ADCSensor,
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||
cv.Required(CONF_PIN): validate_adc_pin,
|
||||
cv.Optional(CONF_RAW, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
|
||||
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(cv.polling_component_schema("60s")),
|
||||
validate_config,
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
if config[CONF_PIN] == "VCC":
|
||||
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||
elif config[CONF_PIN] == "TEMPERATURE":
|
||||
cg.add(var.set_is_temperature())
|
||||
else:
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
|
||||
if CONF_RAW in config:
|
||||
cg.add(var.set_output_raw(config[CONF_RAW]))
|
||||
|
||||
if CONF_ATTENUATION in config:
|
||||
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
|
||||
if config[CONF_ATTENUATION] == "auto":
|
||||
cg.add(var.set_autorange(cg.global_ns.true))
|
||||
else:
|
||||
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
|
||||
|
||||
if CORE.is_esp32:
|
||||
variant = get_esp32_variant()
|
||||
pin_num = config[CONF_PIN][CONF_NUMBER]
|
||||
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
|
||||
cg.add(var.set_channel(chan))
|
||||
|
23
esphome/components/adc128s102/__init__.py
Normal file
23
esphome/components/adc128s102/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
MULTI_CONF = True
|
||||
CODEOWNERS = ["@DeerMaximum"]
|
||||
|
||||
adc128s102_ns = cg.esphome_ns.namespace("adc128s102")
|
||||
ADC128S102 = adc128s102_ns.class_("ADC128S102", cg.Component, spi.SPIDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADC128S102),
|
||||
}
|
||||
).extend(spi.spi_device_schema(cs_pin_required=True))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
35
esphome/components/adc128s102/adc128s102.cpp
Normal file
35
esphome/components/adc128s102/adc128s102.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "adc128s102.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc128s102 {
|
||||
|
||||
static const char *const TAG = "adc128s102";
|
||||
|
||||
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
void ADC128S102::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up adc128s102");
|
||||
this->spi_setup();
|
||||
}
|
||||
|
||||
void ADC128S102::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADC128S102:");
|
||||
LOG_PIN(" CS Pin:", this->cs_);
|
||||
}
|
||||
|
||||
uint16_t ADC128S102::read_data(uint8_t channel) {
|
||||
uint8_t control = channel << 3;
|
||||
|
||||
this->enable();
|
||||
uint8_t adc_primary_byte = this->transfer_byte(control);
|
||||
uint8_t adc_secondary_byte = this->transfer_byte(0x00);
|
||||
this->disable();
|
||||
|
||||
uint16_t digital_value = adc_primary_byte << 8 | adc_secondary_byte;
|
||||
|
||||
return digital_value;
|
||||
}
|
||||
|
||||
} // namespace adc128s102
|
||||
} // namespace esphome
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user