mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +00:00
Compare commits
900 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42b4a166ec | ||
|
|
c27fd0f01a | ||
|
|
dcb4a0a81e | ||
|
|
17da9fddc3 | ||
|
|
31aa3c55ca | ||
|
|
eca3685ea0 | ||
|
|
bd216c5c63 | ||
|
|
31ff76427c | ||
|
|
2229aa6ccc | ||
|
|
872b468415 | ||
|
|
9f022a7433 | ||
|
|
85a958e300 | ||
|
|
0ee56195ae | ||
|
|
48f52db1d9 | ||
|
|
d2c7afeef0 | ||
|
|
644aec791e | ||
|
|
b70a0325c5 | ||
|
|
268387f829 | ||
|
|
b975caef1e | ||
|
|
54fe1c7d55 | ||
|
|
89c1274d56 | ||
|
|
f9ca3f1c27 | ||
|
|
26dbc30279 | ||
|
|
4bee316425 | ||
|
|
0759140dc2 | ||
|
|
a943bc6c80 | ||
|
|
347393b864 | ||
|
|
bf6b11222a | ||
|
|
823ae7d1aa | ||
|
|
28031cfa3e | ||
|
|
292c2d0c53 | ||
|
|
869775ec7a | ||
|
|
783b179af7 | ||
|
|
28454ea4cd | ||
|
|
9f4b666ef0 | ||
|
|
80214640b1 | ||
|
|
4310c14497 | ||
|
|
aebb6d2fcc | ||
|
|
af35c9258e | ||
|
|
7a43231c43 | ||
|
|
1bf55c130b | ||
|
|
95a74a7f19 | ||
|
|
b78b28ea0e | ||
|
|
aed7b3fbb2 | ||
|
|
1ade7bcb2d | ||
|
|
21bbafb63d | ||
|
|
1cfc6ac3c6 | ||
|
|
c3aa834d80 | ||
|
|
68d0d045c0 | ||
|
|
72d6471ab8 | ||
|
|
22aecdfc6f | ||
|
|
ee0b6e835f | ||
|
|
8292024306 | ||
|
|
84cfcf2b4a | ||
|
|
ea762b7295 | ||
|
|
d51c0f13c0 | ||
|
|
6ceb975a3a | ||
|
|
9a40d6ef50 | ||
|
|
32195f77d9 | ||
|
|
578e5a0d7a | ||
|
|
1242f43769 | ||
|
|
3bb6430495 | ||
|
|
aae633277f | ||
|
|
996c50e8f2 | ||
|
|
95c883ae9b | ||
|
|
35a725fa1e | ||
|
|
78c1adafcd | ||
|
|
e15071228e | ||
|
|
ac48ff1fd6 | ||
|
|
428684bc1e | ||
|
|
81b7653c9c | ||
|
|
45736707bd | ||
|
|
cdfbe5b523 | ||
|
|
cdb9c59662 | ||
|
|
9c30f4cc68 | ||
|
|
acf3f6fb65 | ||
|
|
23f99908db | ||
|
|
a0046a2e55 | ||
|
|
e30512931b | ||
|
|
e207c6ad84 | ||
|
|
9d7f76773d | ||
|
|
5f2808ec2f | ||
|
|
be91cfb261 | ||
|
|
0eadda77b0 | ||
|
|
b2388b6fe7 | ||
|
|
1a763ae974 | ||
|
|
38dfab11b4 | ||
|
|
7c31592850 | ||
|
|
57bee74225 | ||
|
|
fa351cd37c | ||
|
|
68e7e5a51c | ||
|
|
4d31ad3bdc | ||
|
|
f4f1164b94 | ||
|
|
d4e0e1518a | ||
|
|
bd0be41064 | ||
|
|
4118a289a6 | ||
|
|
1d5f8d5a52 | ||
|
|
fd1dc24ac6 | ||
|
|
be1e4c0a1d | ||
|
|
c2028f7378 | ||
|
|
4b0f203049 | ||
|
|
23ff8178a0 | ||
|
|
93cfee8026 | ||
|
|
b6920025b2 | ||
|
|
fb29ac27a2 | ||
|
|
4c03cebef3 | ||
|
|
244c4be8cc | ||
|
|
9b28c732c6 | ||
|
|
5dfb33ebee | ||
|
|
2b30cde125 | ||
|
|
f9b3e61c0f | ||
|
|
83a92f03fc | ||
|
|
d27291b997 | ||
|
|
2c995cf145 | ||
|
|
2822fa4a40 | ||
|
|
ccf3da2a5a | ||
|
|
5348b36a7c | ||
|
|
947a6034e3 | ||
|
|
65d08beaa4 | ||
|
|
9770bc371b | ||
|
|
22f9f75914 | ||
|
|
54c9dd4173 | ||
|
|
0fc267dfc7 | ||
|
|
c5db457700 | ||
|
|
15a7d2ef75 | ||
|
|
071272a27f | ||
|
|
655327a8b1 | ||
|
|
15b87af8ed | ||
|
|
a0b3d861fe | ||
|
|
718c494013 | ||
|
|
5c9755ecc1 | ||
|
|
11e88019c2 | ||
|
|
a783637a7a | ||
|
|
7210ad7ed9 | ||
|
|
1876c21e3e | ||
|
|
6516a6ff7e | ||
|
|
85195436c1 | ||
|
|
c6512013bb | ||
|
|
81a070d03d | ||
|
|
0ef1d178d2 | ||
|
|
762f1b1fc9 | ||
|
|
7ad593d674 | ||
|
|
13522c8f19 | ||
|
|
d2938e82db | ||
|
|
f95d4ca106 | ||
|
|
486bafd009 | ||
|
|
341c99b4fa | ||
|
|
83095e8989 | ||
|
|
71ba4bc31c | ||
|
|
894ec07cc8 | ||
|
|
59091100e4 | ||
|
|
e5485ab650 | ||
|
|
6c493d10d2 | ||
|
|
840f599631 | ||
|
|
5a76e61b1e | ||
|
|
7b4366bfef | ||
|
|
8dee5c5fe8 | ||
|
|
b2e6d222cd | ||
|
|
2712c44004 | ||
|
|
82625a3080 | ||
|
|
49f9ad66db | ||
|
|
0dfab4d93c | ||
|
|
5cd7f23065 | ||
|
|
27453afa4e | ||
|
|
369d175694 | ||
|
|
fc465d6d93 | ||
|
|
904a0b26ea | ||
|
|
c13f132399 | ||
|
|
db968bc6b0 | ||
|
|
7abe8875bd | ||
|
|
dc9f304d94 | ||
|
|
a09bd80636 | ||
|
|
237ecb3adf | ||
|
|
9d65b77f13 | ||
|
|
97f2becc9e | ||
|
|
f4160c363b | ||
|
|
4fee9cc039 | ||
|
|
36f47ade70 | ||
|
|
8db6f3129c | ||
|
|
75630a36f8 | ||
|
|
d2be58ba31 | ||
|
|
bbeb0461c4 | ||
|
|
14fd08e225 | ||
|
|
f99352f7e0 | ||
|
|
b51cbc4207 | ||
|
|
7a895adec9 | ||
|
|
4fe0c95ccb | ||
|
|
726b0e73d9 | ||
|
|
88ccd60a08 | ||
|
|
e6c16e9981 | ||
|
|
1bd408937a | ||
|
|
4d00dfd308 | ||
|
|
75326d2271 | ||
|
|
76fe2e4871 | ||
|
|
f977e9da2b | ||
|
|
16ae46e958 | ||
|
|
73eea154d5 | ||
|
|
0d36e66125 | ||
|
|
970838ed09 | ||
|
|
0a21816a5a | ||
|
|
30a542e763 | ||
|
|
ebe64e24f1 | ||
|
|
c53483a3b2 | ||
|
|
fe24745815 | ||
|
|
b5e75793e1 | ||
|
|
734cc989de | ||
|
|
2642750466 | ||
|
|
ec9cc72320 | ||
|
|
c97a9d83c6 | ||
|
|
f31c1480f3 | ||
|
|
291d4be772 | ||
|
|
52584ec2be | ||
|
|
3bc08e5222 | ||
|
|
672f8d1719 | ||
|
|
420c8b49e2 | ||
|
|
f921997ee6 | ||
|
|
4e520d13dd | ||
|
|
2617e5092b | ||
|
|
d41ddf380c | ||
|
|
a72c3ea9d7 | ||
|
|
8be733efee | ||
|
|
b9609286ea | ||
|
|
2b186fdb0d | ||
|
|
3012fee013 | ||
|
|
01db114724 | ||
|
|
e05688d639 | ||
|
|
925b030718 | ||
|
|
9eba789c32 | ||
|
|
3e6ae4afda | ||
|
|
27abb38ecb | ||
|
|
3ddf5a4ec7 | ||
|
|
f2d6817d8a | ||
|
|
31821e6309 | ||
|
|
1ce257c721 | ||
|
|
8dd971b25e | ||
|
|
7f507935b1 | ||
|
|
3b1ba27043 | ||
|
|
4b7c5aa05c | ||
|
|
5fca02c712 | ||
|
|
31ddd3f668 | ||
|
|
f35f6d2348 | ||
|
|
02d34a0238 | ||
|
|
e4bbb56f6b | ||
|
|
96d30e28d4 | ||
|
|
41b73ff892 | ||
|
|
afc4e45fb0 | ||
|
|
8778ddd5c5 | ||
|
|
3089ffa8e7 | ||
|
|
15cb0e4ff3 | ||
|
|
2f3c6d5b58 | ||
|
|
ead5e8d855 | ||
|
|
fd9a9ecc63 | ||
|
|
bbb8ea7ec2 | ||
|
|
667ed94e29 | ||
|
|
23f1798d20 | ||
|
|
6f3c126805 | ||
|
|
a9ae70cff1 | ||
|
|
d7a8c50c98 | ||
|
|
9e56318498 | ||
|
|
2570f2d6f2 | ||
|
|
928df2dcd1 | ||
|
|
2decb8115c | ||
|
|
9d26c16471 | ||
|
|
5893506528 | ||
|
|
df0d33c3cd | ||
|
|
359f54d3c1 | ||
|
|
68ce1b18c4 | ||
|
|
61ba2e0f35 | ||
|
|
76d7802650 | ||
|
|
9fa1a334e6 | ||
|
|
9be16916b7 | ||
|
|
4a5365f6a0 | ||
|
|
0ced5509fc | ||
|
|
bd6b9ff1da | ||
|
|
edee28acf0 | ||
|
|
127efe9a52 | ||
|
|
53e8b3ed3e | ||
|
|
a23ebead68 | ||
|
|
f39d459555 | ||
|
|
6a17fe375e | ||
|
|
6e7d25ed42 | ||
|
|
a676ff23de | ||
|
|
85513476ce | ||
|
|
e62443933c | ||
|
|
3b48aa5911 | ||
|
|
b7daee533a | ||
|
|
7a14ab825e | ||
|
|
4323ca88c3 | ||
|
|
4bc3067725 | ||
|
|
28f2a7f99c | ||
|
|
72218171b3 | ||
|
|
0d9f5ef363 | ||
|
|
7b5c4359c6 | ||
|
|
72a80f559a | ||
|
|
dac624231f | ||
|
|
510e53de70 | ||
|
|
d8963ea25a | ||
|
|
9ed06444e1 | ||
|
|
df50b95e5a | ||
|
|
12ff280d3b | ||
|
|
16c2929bb4 | ||
|
|
422754ed63 | ||
|
|
aa7389432e | ||
|
|
bd45f6bd8e | ||
|
|
999c1a5357 | ||
|
|
a323679771 | ||
|
|
1fa4a2d256 | ||
|
|
8c73558165 | ||
|
|
ed61c1dd58 | ||
|
|
8be444a25e | ||
|
|
6306d44955 | ||
|
|
6fff2e5957 | ||
|
|
dd79e37933 | ||
|
|
a1b6a91642 | ||
|
|
60d67e5428 | ||
|
|
5bb963fa82 | ||
|
|
83fa51a580 | ||
|
|
0281914507 | ||
|
|
9231b80aa9 | ||
|
|
3e044db9f1 | ||
|
|
855e9367d4 | ||
|
|
db0dd6af09 | ||
|
|
b7afb8c887 | ||
|
|
02c9ada876 | ||
|
|
f811b1157c | ||
|
|
797aadaf26 | ||
|
|
f1a0e5a313 | ||
|
|
f2540bae23 | ||
|
|
a1a7448868 | ||
|
|
e373620393 | ||
|
|
23dcfe5075 | ||
|
|
7bab279c6a | ||
|
|
953d7f6193 | ||
|
|
1d6dd3aa5e | ||
|
|
9dd9e523ed | ||
|
|
66cbfca99c | ||
|
|
b0e6b48c50 | ||
|
|
bb1937ab88 | ||
|
|
86848b39db | ||
|
|
f2506bb23b | ||
|
|
3372ddc63d | ||
|
|
3fe9c20188 | ||
|
|
95428b4cfe | ||
|
|
968ff4b619 | ||
|
|
a07a835eb0 | ||
|
|
667457989e | ||
|
|
bedf1b7483 | ||
|
|
53c182ad37 | ||
|
|
ce45c81069 | ||
|
|
1ee85295f2 | ||
|
|
521c080989 | ||
|
|
64541e0e35 | ||
|
|
c3d4ef284d | ||
|
|
ebb5fadba1 | ||
|
|
c569d022ec | ||
|
|
58586ea840 | ||
|
|
27f431a027 | ||
|
|
595dfe7e24 | ||
|
|
766f6c045d | ||
|
|
0a0713f0e2 | ||
|
|
5e5b9f2205 | ||
|
|
e6ff3c287d | ||
|
|
295585379e | ||
|
|
c7609ba5e7 | ||
|
|
8e75980ebd | ||
|
|
6682c43dfa | ||
|
|
049807e3ab | ||
|
|
336f12b24d | ||
|
|
be5330b6ae | ||
|
|
e90829eef2 | ||
|
|
dc4c1bc225 | ||
|
|
a14d12b0c1 | ||
|
|
526a414743 | ||
|
|
7fd5634a51 | ||
|
|
cb9f36a153 | ||
|
|
0272048899 | ||
|
|
5b0d30bc09 | ||
|
|
5859d4b01f | ||
|
|
1dab1314ff | ||
|
|
1f7e1daa73 | ||
|
|
d27b01f02c | ||
|
|
67c56ab97b | ||
|
|
6ccab2bef9 | ||
|
|
475aa4879c | ||
|
|
4452473735 | ||
|
|
e0a556e987 | ||
|
|
de51bdda5b | ||
|
|
386cd4f35f | ||
|
|
477311dac6 | ||
|
|
948adfd28c | ||
|
|
aebbd7673b | ||
|
|
3baaf6b7c4 | ||
|
|
7ad2d86929 | ||
|
|
4a14221e2b | ||
|
|
114ebf9fe1 | ||
|
|
0d7bb1286a | ||
|
|
92a4ee5652 | ||
|
|
9fea094b4e | ||
|
|
d332e491ad | ||
|
|
0ccfe33427 | ||
|
|
596c334fcb | ||
|
|
ca4450858f | ||
|
|
f3ec83fe31 | ||
|
|
c4ada8c9f0 | ||
|
|
23df5d8af7 | ||
|
|
289acade1e | ||
|
|
a99f99779a | ||
|
|
5c14ca030a | ||
|
|
0fc6a027a7 | ||
|
|
3c0d97ef69 | ||
|
|
5b4f98d414 | ||
|
|
7ebfcd3807 | ||
|
|
3ef0634dd2 | ||
|
|
0928c9739f | ||
|
|
e009f21a72 | ||
|
|
606c412616 | ||
|
|
975b5127d6 | ||
|
|
60c9ffef30 | ||
|
|
99861259d7 | ||
|
|
067ec30c56 | ||
|
|
5a102c2ab7 | ||
|
|
4b017e2096 | ||
|
|
8495ce96a3 | ||
|
|
55caf4f648 | ||
|
|
c123f0091d | ||
|
|
7c65d44976 | ||
|
|
5b8d12a80c | ||
|
|
3951a2b22a | ||
|
|
69a74a30e8 | ||
|
|
f3ee5b55e9 | ||
|
|
f6cc9f7caa | ||
|
|
a5d0ecdb13 | ||
|
|
71cbc9cfb0 | ||
|
|
88625c656d | ||
|
|
971b15ac67 | ||
|
|
a4edcc48ca | ||
|
|
1778dd4df9 | ||
|
|
311e837196 | ||
|
|
3b00cfd6c4 | ||
|
|
1c7ca4bc6f | ||
|
|
38e7b597d6 | ||
|
|
808ee19180 | ||
|
|
c2a0c22bd9 | ||
|
|
e785ad5401 | ||
|
|
bacddc3673 | ||
|
|
7dd0dabaf5 | ||
|
|
92f8b043ce | ||
|
|
2e5fd7e90d | ||
|
|
407c46cb03 | ||
|
|
12ce448f2d | ||
|
|
340530566c | ||
|
|
f4a55eafd7 | ||
|
|
fc8f270a9f | ||
|
|
e7ce8f7a13 | ||
|
|
3d1cce2a29 | ||
|
|
6be47488a4 | ||
|
|
5b94bb894e | ||
|
|
26aa399bea | ||
|
|
af0c213024 | ||
|
|
88e036ddb2 | ||
|
|
8012de3ba4 | ||
|
|
e2b1d5438d | ||
|
|
fe66f93a01 | ||
|
|
581dffe2d4 | ||
|
|
6f22006bf7 | ||
|
|
fe54700687 | ||
|
|
f0c0131ed1 | ||
|
|
52750d933b | ||
|
|
7e7a85abfd | ||
|
|
c1b8107aaf | ||
|
|
0bdcce609f | ||
|
|
6b702f8014 | ||
|
|
078ab26fd2 | ||
|
|
efe81e0856 | ||
|
|
41d637affe | ||
|
|
10cc11bab1 | ||
|
|
34ca5d6d8a | ||
|
|
b27c778cb7 | ||
|
|
f311a1bb30 | ||
|
|
7a450ed41c | ||
|
|
944f0169cb | ||
|
|
4da0e0c223 | ||
|
|
52c0b45f41 | ||
|
|
fcef3be5c7 | ||
|
|
ef7b936d60 | ||
|
|
e0eac6ba25 | ||
|
|
50af0da13f | ||
|
|
a8d87a1fee | ||
|
|
a66ed437d6 | ||
|
|
b95f53a7c7 | ||
|
|
d7eacb2a2f | ||
|
|
41ec337ba0 | ||
|
|
5e61014e98 | ||
|
|
0a4d49accb | ||
|
|
3d9301a0f7 | ||
|
|
1b8d242505 | ||
|
|
463ad6f94b | ||
|
|
e4e315f723 | ||
|
|
7fecec128f | ||
|
|
a15b647d13 | ||
|
|
feab956ea9 | ||
|
|
4b7a41922c | ||
|
|
61762bf299 | ||
|
|
6eb769e6d5 | ||
|
|
4c8bd7a70c | ||
|
|
d9cf91210e | ||
|
|
fcf5da66c0 | ||
|
|
b6edbf9d0b | ||
|
|
596f995af8 | ||
|
|
8ce176aaba | ||
|
|
f185ba7a21 | ||
|
|
7c28eca6de | ||
|
|
9fa95a9a21 | ||
|
|
6ccb68aaf1 | ||
|
|
6a76a3642e | ||
|
|
a50eb3579a | ||
|
|
4a74027848 | ||
|
|
98bdfc821e | ||
|
|
176c712eeb | ||
|
|
7b26ecc0dc | ||
|
|
92e909568c | ||
|
|
e3f7e0d14a | ||
|
|
5fcda34c45 | ||
|
|
bd23584073 | ||
|
|
6d6876ca39 | ||
|
|
9ebf6439b2 | ||
|
|
1404d39ffe | ||
|
|
7153c7a941 | ||
|
|
3437e23c8e | ||
|
|
eb39add6fc | ||
|
|
e13bffbb2f | ||
|
|
e347cb538d | ||
|
|
d799c03f0c | ||
|
|
c86675f644 | ||
|
|
a00fe09c66 | ||
|
|
e0fe0a2835 | ||
|
|
e1f48b5028 | ||
|
|
e4a2677de4 | ||
|
|
ab40156c16 | ||
|
|
804a1ed7b3 | ||
|
|
4de98fd782 | ||
|
|
d8fd5e9bb4 | ||
|
|
8424f9c34b | ||
|
|
70016d7641 | ||
|
|
7ced977f2a | ||
|
|
5ac9b8d545 | ||
|
|
a683108b5d | ||
|
|
54dd9e94ab | ||
|
|
4f5389998f | ||
|
|
8a61c21051 | ||
|
|
b71b14cd06 | ||
|
|
9b2b7dabd1 | ||
|
|
145e7b00ee | ||
|
|
07c80dfcda | ||
|
|
0e52c9a778 | ||
|
|
94bd179256 | ||
|
|
11c38ca4e8 | ||
|
|
dc71d11a21 | ||
|
|
86130b2954 | ||
|
|
c2787c1ce5 | ||
|
|
e81338b4f5 | ||
|
|
40ab2d5e5a | ||
|
|
7a703d10f4 | ||
|
|
e385c8435b | ||
|
|
4c5f908e3a | ||
|
|
13eca6012d | ||
|
|
27e1294630 | ||
|
|
cb3e3e024d | ||
|
|
79bdec32b8 | ||
|
|
c2fb71c41f | ||
|
|
c153dba5bc | ||
|
|
fa2c2917c1 | ||
|
|
7685b32ac0 | ||
|
|
2a06f4dbf4 | ||
|
|
49b618bb0c | ||
|
|
20ec3900be | ||
|
|
605be1a6ec | ||
|
|
33b67de32e | ||
|
|
5a1e806d14 | ||
|
|
cf73d777db | ||
|
|
47231977d7 | ||
|
|
d29b280d82 | ||
|
|
77514c0a06 | ||
|
|
1ffedb291c | ||
|
|
341a27e56b | ||
|
|
8f251848ef | ||
|
|
739b7bfc05 | ||
|
|
4046a16d85 | ||
|
|
01b49e8d59 | ||
|
|
52dbd35118 | ||
|
|
92439abeb4 | ||
|
|
9bce35e335 | ||
|
|
94cb7bf6bd | ||
|
|
cdf53cb3e8 | ||
|
|
30bd74cb6c | ||
|
|
e72ef9e061 | ||
|
|
3f4bba57f4 | ||
|
|
38c24fd100 | ||
|
|
c6ed88579f | ||
|
|
7f9462ebf3 | ||
|
|
4e44081fdf | ||
|
|
3cd1c2d723 | ||
|
|
907be3025c | ||
|
|
815da05b29 | ||
|
|
a2083fc901 | ||
|
|
06cb7497c8 | ||
|
|
1e12cba176 | ||
|
|
1d0c812e44 | ||
|
|
4948ad95b0 | ||
|
|
dd801636af | ||
|
|
a110911371 | ||
|
|
22fd4ec722 | ||
|
|
5db70bea3c | ||
|
|
00ff99cc7d | ||
|
|
a51eaa93b5 | ||
|
|
eddaee3a80 | ||
|
|
2b432ea829 | ||
|
|
09ab55b85d | ||
|
|
183e0f4d23 | ||
|
|
a8c17e5d05 | ||
|
|
2e65d6c02c | ||
|
|
07da8950c4 | ||
|
|
c206846816 | ||
|
|
6aac4191f8 | ||
|
|
5a7b66e207 | ||
|
|
da2821ab36 | ||
|
|
7556845079 | ||
|
|
e646f02e27 | ||
|
|
39ead80df3 | ||
|
|
71e221f6ec | ||
|
|
7c7032c59e | ||
|
|
2b88c987da | ||
|
|
3d49ffa134 | ||
|
|
5ed987adcd | ||
|
|
a463c59733 | ||
|
|
1726c4237b | ||
|
|
f2f869db89 | ||
|
|
e37e986276 | ||
|
|
f1241af91d | ||
|
|
7ae6777fd6 | ||
|
|
a169d37557 | ||
|
|
5d5529ff15 | ||
|
|
db90a7a334 | ||
|
|
bb9c1faffa | ||
|
|
be21aa786d | ||
|
|
9a881100e6 | ||
|
|
c2f88776c7 | ||
|
|
b4c4dc8cfb | ||
|
|
c3b2ab0085 | ||
|
|
846fcb8ccd | ||
|
|
f087e313d4 | ||
|
|
44495c919c | ||
|
|
d4ce7699d4 | ||
|
|
f88cf99b67 | ||
|
|
f394968bd0 | ||
|
|
318bb8b254 | ||
|
|
70def85ba1 | ||
|
|
1ad65516cf | ||
|
|
d4c7e6c634 | ||
|
|
9b07bb6608 | ||
|
|
f368255739 | ||
|
|
083c2fce05 | ||
|
|
c99d4e2815 | ||
|
|
39457f7b8c | ||
|
|
01aaf14078 | ||
|
|
9a939d2d27 | ||
|
|
7f91141df2 | ||
|
|
06eeed9ee9 | ||
|
|
5655b5fe10 | ||
|
|
15331edb78 | ||
|
|
4f375757a5 | ||
|
|
0dec7cfbf8 | ||
|
|
f51d301d53 | ||
|
|
c3b3ba4923 | ||
|
|
30e7797577 | ||
|
|
0e5cabadc1 | ||
|
|
58f4fa53d0 | ||
|
|
04dc848620 | ||
|
|
8203b8fcd3 | ||
|
|
0ad61f4a95 | ||
|
|
9fd4076ab8 | ||
|
|
a9e799cb06 | ||
|
|
3ec931ffa4 | ||
|
|
e3094d9689 | ||
|
|
3594779401 | ||
|
|
94978d0063 | ||
|
|
415e12309b | ||
|
|
aa5f887ff3 | ||
|
|
28561ea6a4 | ||
|
|
3b3ff4fea9 | ||
|
|
6b8125f5f2 | ||
|
|
ab43390983 | ||
|
|
611592170b | ||
|
|
8a58ff91c3 | ||
|
|
27b86d89b0 | ||
|
|
36da3b85d5 | ||
|
|
d7d3a4aa36 | ||
|
|
4e4ffc3a24 | ||
|
|
4dce7fa103 | ||
|
|
467ef9902f | ||
|
|
486174073b | ||
|
|
e9d9de448e | ||
|
|
08c16020c6 | ||
|
|
0ade9baf65 | ||
|
|
2fab7e73b9 | ||
|
|
af4e2bf61d | ||
|
|
6a2e9a8503 | ||
|
|
74fefea5bb | ||
|
|
21c22fe04a | ||
|
|
70206df8b5 | ||
|
|
15732ca465 | ||
|
|
8e0f4f93d4 | ||
|
|
361baea17f | ||
|
|
8bbfbc4cc1 | ||
|
|
8c5d12df51 | ||
|
|
25c66ed8ca | ||
|
|
7a55521807 | ||
|
|
d04de7baeb | ||
|
|
4aeb756388 | ||
|
|
92b6ed4180 | ||
|
|
9efd9f4fe8 | ||
|
|
34fc2147a4 | ||
|
|
629f2b128e | ||
|
|
81bc400340 | ||
|
|
75628b96a1 | ||
|
|
b1f7ed4fdc | ||
|
|
b8d7185d99 | ||
|
|
2d20a1c0fb | ||
|
|
820067ae5a | ||
|
|
db8313e0d5 | ||
|
|
27a77c685d | ||
|
|
e34365dc7c | ||
|
|
8d395e5338 | ||
|
|
6f54afec00 | ||
|
|
6a24145be6 | ||
|
|
4a2cdbf31c | ||
|
|
1d75ed1ff4 | ||
|
|
76b1c6f47b | ||
|
|
06371c9e2d | ||
|
|
a9c130dd50 | ||
|
|
1f82c1a483 | ||
|
|
cb28429231 | ||
|
|
f2cd2ec178 | ||
|
|
37360bb797 | ||
|
|
6c1dc0f2b3 | ||
|
|
f7671f0c90 | ||
|
|
639b97ccb2 | ||
|
|
0374b3a0b3 | ||
|
|
7ce753b76f | ||
|
|
05a1089ed2 | ||
|
|
71947bb6ac | ||
|
|
ef54e33b70 | ||
|
|
12f20fc3cf | ||
|
|
ffdcddc18e | ||
|
|
85d70eb5a0 | ||
|
|
ffb793177a | ||
|
|
d3f2fab88a | ||
|
|
0fa52d0ce6 | ||
|
|
cf264a2743 | ||
|
|
433b605bef | ||
|
|
6e60c6493a | ||
|
|
4e63bc96d5 | ||
|
|
ce4b339d16 | ||
|
|
ab6d293d0d | ||
|
|
5e5137960d | ||
|
|
8dba37846b | ||
|
|
5cd82d7c25 | ||
|
|
bc8354bad5 | ||
|
|
2abbe1bca3 | ||
|
|
74c70509c2 | ||
|
|
1576e1847e | ||
|
|
9ea9b4b102 | ||
|
|
490743c26e | ||
|
|
1c7bddd005 | ||
|
|
5c39f73fda | ||
|
|
2fc78a1b33 | ||
|
|
03249780fd | ||
|
|
5170a7cdf4 | ||
|
|
5680de79a9 | ||
|
|
61bd2a3a44 | ||
|
|
7802c63f5a | ||
|
|
94bef2a5a4 | ||
|
|
12c4b0788c | ||
|
|
b13cc3a4a5 | ||
|
|
c5d9bc5452 | ||
|
|
6c6d21a7ab | ||
|
|
9bb06782b2 | ||
|
|
a827b51887 | ||
|
|
2c30d80490 | ||
|
|
e0acdc3ae0 | ||
|
|
36a3f96011 | ||
|
|
6ae8e3495f | ||
|
|
6aa449115f | ||
|
|
0be29d27d5 | ||
|
|
68fa7489a2 | ||
|
|
d7699c93d6 | ||
|
|
a04438e924 | ||
|
|
e063f2aaea | ||
|
|
7b630bfb8b | ||
|
|
ec3366cce0 | ||
|
|
135117714b | ||
|
|
91e6304505 | ||
|
|
361a9da868 | ||
|
|
31d7656c07 | ||
|
|
d1a7751dc9 | ||
|
|
e47bcb9abb | ||
|
|
30b94f06b3 | ||
|
|
19dfdb77eb | ||
|
|
b6e7ad3589 | ||
|
|
1267680379 | ||
|
|
b5f6b32fad | ||
|
|
7068808d4f | ||
|
|
c82a44d541 | ||
|
|
e650473682 | ||
|
|
2ee0d4242d | ||
|
|
b2aecf29dc | ||
|
|
081c2f4e07 | ||
|
|
0c77228421 | ||
|
|
8a509c6551 | ||
|
|
d249820bcd | ||
|
|
f39cf52eae | ||
|
|
63fb252b46 | ||
|
|
2f98adca49 | ||
|
|
084fc00517 | ||
|
|
abeac23abd | ||
|
|
7264e3a4bf | ||
|
|
16eeb3af31 | ||
|
|
b799d2df7f | ||
|
|
11d55fec4f | ||
|
|
923a5990c7 | ||
|
|
a0a615c335 | ||
|
|
e9ced0cc3f | ||
|
|
1a92381994 | ||
|
|
a07a7a87a2 | ||
|
|
6a823f5777 | ||
|
|
419c5afe27 | ||
|
|
17798dee1e | ||
|
|
ee2c53585f | ||
|
|
2f05acfa5a | ||
|
|
f4d393a59e | ||
|
|
967aa53bad | ||
|
|
4f3f460105 | ||
|
|
6e85a741ae | ||
|
|
2db45898e2 | ||
|
|
eb62599a98 | ||
|
|
9f0737e5b9 | ||
|
|
7a393c1a3a | ||
|
|
4fa7bc196a | ||
|
|
65d0dd47f3 | ||
|
|
d88634b196 | ||
|
|
976627eb38 | ||
|
|
5b995c0692 | ||
|
|
2d4b475951 | ||
|
|
93d962dd43 | ||
|
|
2e7d8540fb | ||
|
|
677fe8bacf | ||
|
|
94d7ac4ef0 | ||
|
|
ebb6d0d464 | ||
|
|
e8fe653140 | ||
|
|
374ea7044c | ||
|
|
061798839d | ||
|
|
b9b09a1763 | ||
|
|
61b3ead2df | ||
|
|
48e42cf478 | ||
|
|
1a9ff55a61 | ||
|
|
e04285581c | ||
|
|
9af30061cb | ||
|
|
19929fafa5 | ||
|
|
3a9febaf85 | ||
|
|
ebb5991889 | ||
|
|
f18f8444c7 | ||
|
|
10607f2a51 | ||
|
|
d3ac5bfb27 | ||
|
|
4b9bb2b731 | ||
|
|
8639eb1b27 | ||
|
|
9979ee6ddf | ||
|
|
ee502a7aaa | ||
|
|
dc516f7537 | ||
|
|
44f2b582b5 | ||
|
|
262855ff62 | ||
|
|
eec163644d | ||
|
|
e65a4d50e5 | ||
|
|
a88aad2179 | ||
|
|
49736c8c6d | ||
|
|
7915e420f4 | ||
|
|
595aa5e92d | ||
|
|
ef1aa16627 | ||
|
|
3540b3fbb0 | ||
|
|
ac5ab33975 | ||
|
|
633d20d023 | ||
|
|
9e5548324b | ||
|
|
c31c5c4041 | ||
|
|
34605f19ee | ||
|
|
58e1b8454d | ||
|
|
0ab63dc4d4 | ||
|
|
51c856e65e | ||
|
|
de766a0100 | ||
|
|
d5b4971d81 | ||
|
|
8195981bc6 | ||
|
|
7f09d4f23d |
137
.clang-format
Normal file
137
.clang-format
Normal file
@@ -0,0 +1,137 @@
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 120
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 2000
|
||||
PointerAlignment: Right
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
||||
127
.clang-tidy
Normal file
127
.clang-tidy
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
Checks: >-
|
||||
*,
|
||||
-abseil-*,
|
||||
-android-*,
|
||||
-boost-*,
|
||||
-bugprone-macro-parentheses,
|
||||
-cert-dcl50-cpp,
|
||||
-cert-err58-cpp,
|
||||
-clang-analyzer-core.CallAndMessage,
|
||||
-clang-analyzer-osx.*,
|
||||
-clang-analyzer-security.*,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-c-copy-assignment-signature,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-pro-type-const-cast,
|
||||
-cppcoreguidelines-pro-type-cstyle-cast,
|
||||
-cppcoreguidelines-pro-type-member-init,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
-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,
|
||||
-google-build-using-namespace,
|
||||
-google-explicit-constructor,
|
||||
-google-readability-braces-around-statements,
|
||||
-google-readability-casting,
|
||||
-google-readability-todo,
|
||||
-google-runtime-int,
|
||||
-google-runtime-references,
|
||||
-hicpp-*,
|
||||
-llvm-header-guard,
|
||||
-llvm-include-order,
|
||||
-misc-unconventional-assign-operator,
|
||||
-misc-unused-parameters,
|
||||
-modernize-deprecated-headers,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-use-auto,
|
||||
-modernize-use-default-member-init,
|
||||
-modernize-use-equals-default,
|
||||
-mpi-*,
|
||||
-objc-*,
|
||||
-performance-unnecessary-value-param,
|
||||
-readability-braces-around-statements,
|
||||
-readability-else-after-return,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-named-parameter,
|
||||
-readability-redundant-member-init,
|
||||
-warnings-as-errors,
|
||||
-zircon-*
|
||||
WarningsAsErrors: '*'
|
||||
HeaderFilterRegex: '^.*/src/esphome/.*'
|
||||
AnalyzeTemporaryDtors: false
|
||||
FormatStyle: google
|
||||
CheckOptions:
|
||||
- key: google-readability-braces-around-statements.ShortStatementLines
|
||||
value: '1'
|
||||
- key: google-readability-function-size.StatementThreshold
|
||||
value: '800'
|
||||
- key: google-readability-namespace-comments.ShortNamespaceLines
|
||||
value: '10'
|
||||
- key: google-readability-namespace-comments.SpacesBeforeComments
|
||||
value: '2'
|
||||
- key: modernize-loop-convert.MaxCopySize
|
||||
value: '16'
|
||||
- key: modernize-loop-convert.MinConfidence
|
||||
value: reasonable
|
||||
- key: modernize-loop-convert.NamingStyle
|
||||
value: CamelCase
|
||||
- key: modernize-pass-by-value.IncludeStyle
|
||||
value: llvm
|
||||
- key: modernize-replace-auto-ptr.IncludeStyle
|
||||
value: llvm
|
||||
- key: modernize-use-nullptr.NullMacros
|
||||
value: 'NULL'
|
||||
- key: readability-identifier-naming.LocalVariableCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassCase
|
||||
value: 'CamelCase'
|
||||
- key: readability-identifier-naming.StructCase
|
||||
value: 'CamelCase'
|
||||
- key: readability-identifier-naming.EnumCase
|
||||
value: 'CamelCase'
|
||||
- key: readability-identifier-naming.EnumConstantCase
|
||||
value: 'UPPER_CASE'
|
||||
- key: readability-identifier-naming.StaticConstantCase
|
||||
value: 'UPPER_CASE'
|
||||
- key: readability-identifier-naming.StaticVariableCase
|
||||
value: 'UPPER_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.ClassMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ProtectedMemberCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ProtectedMemberSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.FunctionCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassMethodCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ProtectedMethodCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ProtectedMethodSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.VirtualMethodCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.VirtualMethodSuffix
|
||||
value: ''
|
||||
@@ -106,3 +106,5 @@ venv.bak/
|
||||
config/
|
||||
examples/
|
||||
Dockerfile
|
||||
.git/
|
||||
tests/build/
|
||||
|
||||
27
.editorconfig
Normal file
27
.editorconfig
Normal file
@@ -0,0 +1,27 @@
|
||||
root = true
|
||||
|
||||
# general
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
|
||||
# python
|
||||
[*.{py}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# C++
|
||||
[*.{cpp,h,tcc}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Web
|
||||
[*.{js,html,css}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# YAML
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github:
|
||||
patreon: ottowinter
|
||||
open_collective:
|
||||
ko_fi:
|
||||
tidelift:
|
||||
custom: https://esphome.io/guides/supporters.html
|
||||
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
## Description:
|
||||
|
||||
|
||||
**Related issue (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>
|
||||
|
||||
## 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).
|
||||
7
.github/issue-close-app.yml
vendored
Normal file
7
.github/issue-close-app.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
comment: >-
|
||||
https://github.com/esphome/esphome/issues/430
|
||||
issueConfigs:
|
||||
- content:
|
||||
- "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
|
||||
|
||||
caseInsensitive: false
|
||||
96
.gitignore
vendored
96
.gitignore
vendored
@@ -6,6 +6,19 @@ __pycache__/
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Hide sublime text stuff
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Hide some OS X stuff
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
@@ -25,12 +38,6 @@ wheels/
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
@@ -51,36 +58,9 @@ coverage.xml
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
@@ -90,17 +70,47 @@ ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.pio
|
||||
.vscode
|
||||
CMakeListsPrivate.txt
|
||||
CMakeLists.txt
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/dynamic.xml
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
/*.cbp
|
||||
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
||||
|
||||
config/
|
||||
tests/build/
|
||||
tests/.esphome/
|
||||
/.temp-clang-tidy.cpp
|
||||
|
||||
342
.gitlab-ci.yml
Normal file
342
.gitlab-ci.yml
Normal file
@@ -0,0 +1,342 @@
|
||||
---
|
||||
# Based on https://gitlab.com/hassio-addons/addon-node-red/blob/master/.gitlab-ci.yml
|
||||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_HOST: tcp://docker:2375/
|
||||
BASE_VERSION: '2.0.1'
|
||||
TZ: UTC
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- deploy
|
||||
|
||||
.lint: &lint
|
||||
image: esphome/esphome-lint:latest
|
||||
stage: lint
|
||||
before_script:
|
||||
- script/setup
|
||||
tags:
|
||||
- docker
|
||||
|
||||
.test: &test
|
||||
image: esphome/esphome-lint:latest
|
||||
stage: test
|
||||
before_script:
|
||||
- script/setup
|
||||
tags:
|
||||
- docker
|
||||
|
||||
.docker-base: &docker-base
|
||||
image: esphome/esphome-base-builder
|
||||
before_script:
|
||||
- docker info
|
||||
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
|
||||
script:
|
||||
- docker run --rm --privileged multiarch/qemu-user-static:4.1.0-1 --reset -p yes
|
||||
- TAG="${CI_COMMIT_TAG#v}"
|
||||
- TAG="${TAG:-${CI_COMMIT_SHA:0:7}}"
|
||||
- echo "Tag ${TAG}"
|
||||
|
||||
- |
|
||||
if [[ "${IS_HASSIO}" == "YES" ]]; then
|
||||
BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:${BASE_VERSION}
|
||||
BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH}
|
||||
DOCKERFILE=docker/Dockerfile.hassio
|
||||
else
|
||||
BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:${BASE_VERSION}
|
||||
if [[ "${BUILD_ARCH}" == "amd64" ]]; then
|
||||
BUILD_TO=esphome/esphome
|
||||
else
|
||||
BUILD_TO=esphome/esphome-${BUILD_ARCH}
|
||||
fi
|
||||
DOCKERFILE=docker/Dockerfile
|
||||
fi
|
||||
|
||||
- |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=${TAG}" \
|
||||
--tag "${BUILD_TO}:${TAG}" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- |
|
||||
if [[ "${RELEASE}" = "YES" ]]; then
|
||||
echo "Pushing to ${BUILD_TO}:${TAG}"
|
||||
docker push "${BUILD_TO}:${TAG}"
|
||||
fi
|
||||
- |
|
||||
if [[ "${LATEST}" = "YES" ]]; then
|
||||
echo "Pushing to :latest"
|
||||
docker tag ${BUILD_TO}:${TAG} ${BUILD_TO}:latest
|
||||
docker push ${BUILD_TO}:latest
|
||||
fi
|
||||
- |
|
||||
if [[ "${BETA}" = "YES" ]]; then
|
||||
echo "Pushing to :beta"
|
||||
docker tag \
|
||||
${BUILD_TO}:${TAG} \
|
||||
${BUILD_TO}:beta
|
||||
docker push ${BUILD_TO}:beta
|
||||
fi
|
||||
- |
|
||||
if [[ "${DEV}" = "YES" ]]; then
|
||||
echo "Pushing to :dev"
|
||||
docker tag \
|
||||
${BUILD_TO}:${TAG} \
|
||||
${BUILD_TO}:dev
|
||||
docker push ${BUILD_TO}:dev
|
||||
fi
|
||||
services:
|
||||
- docker:dind
|
||||
tags:
|
||||
- docker
|
||||
stage: deploy
|
||||
|
||||
lint-custom:
|
||||
<<: *lint
|
||||
script:
|
||||
- script/ci-custom.py
|
||||
|
||||
lint-python:
|
||||
<<: *lint
|
||||
script:
|
||||
- script/lint-python
|
||||
|
||||
lint-tidy:
|
||||
<<: *lint
|
||||
script:
|
||||
- pio init --ide atom
|
||||
- script/clang-tidy --all-headers --fix
|
||||
- script/ci-suggest-changes
|
||||
|
||||
lint-format:
|
||||
<<: *lint
|
||||
script:
|
||||
- script/clang-format -i
|
||||
- script/ci-suggest-changes
|
||||
|
||||
test1:
|
||||
<<: *test
|
||||
script:
|
||||
- esphome tests/test1.yaml compile
|
||||
|
||||
test2:
|
||||
<<: *test
|
||||
script:
|
||||
- esphome tests/test2.yaml compile
|
||||
|
||||
test3:
|
||||
<<: *test
|
||||
script:
|
||||
- esphome tests/test3.yaml compile
|
||||
|
||||
.deploy-pypi: &deploy-pypi
|
||||
<<: *lint
|
||||
stage: deploy
|
||||
script:
|
||||
- pip install twine wheel
|
||||
- python setup.py sdist bdist_wheel
|
||||
- twine upload dist/*
|
||||
|
||||
deploy-release:pypi:
|
||||
<<: *deploy-pypi
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
except:
|
||||
- /^(?!master).+@/
|
||||
|
||||
deploy-beta:pypi:
|
||||
<<: *deploy-pypi
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+b\d+$/
|
||||
except:
|
||||
- /^(?!rc).+@/
|
||||
|
||||
.latest: &latest
|
||||
<<: *docker-base
|
||||
only:
|
||||
- /^v([0-9\.]+)$/
|
||||
except:
|
||||
- branches
|
||||
|
||||
.beta: &beta
|
||||
<<: *docker-base
|
||||
only:
|
||||
- /^v([0-9\.]+b\d+)$/
|
||||
except:
|
||||
- branches
|
||||
|
||||
.dev: &dev
|
||||
<<: *docker-base
|
||||
only:
|
||||
- dev
|
||||
|
||||
aarch64-beta-docker:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: aarch64
|
||||
IS_HASSIO: "NO"
|
||||
RELEASE: "YES"
|
||||
aarch64-beta-hassio:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: aarch64
|
||||
IS_HASSIO: "YES"
|
||||
RELEASE: "YES"
|
||||
aarch64-dev-docker:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: aarch64
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "NO"
|
||||
aarch64-dev-hassio:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: aarch64
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "YES"
|
||||
aarch64-latest-docker:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: aarch64
|
||||
IS_HASSIO: "NO"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
aarch64-latest-hassio:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: aarch64
|
||||
IS_HASSIO: "YES"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
amd64-beta-docker:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: amd64
|
||||
IS_HASSIO: "NO"
|
||||
RELEASE: "YES"
|
||||
amd64-beta-hassio:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: amd64
|
||||
IS_HASSIO: "YES"
|
||||
RELEASE: "YES"
|
||||
amd64-dev-docker:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: amd64
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "NO"
|
||||
amd64-dev-hassio:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: amd64
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "YES"
|
||||
amd64-latest-docker:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: amd64
|
||||
IS_HASSIO: "NO"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
amd64-latest-hassio:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: amd64
|
||||
IS_HASSIO: "YES"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
armv7-beta-docker:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: armv7
|
||||
IS_HASSIO: "NO"
|
||||
RELEASE: "YES"
|
||||
armv7-beta-hassio:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: armv7
|
||||
IS_HASSIO: "YES"
|
||||
RELEASE: "YES"
|
||||
armv7-dev-docker:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: armv7
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "NO"
|
||||
armv7-dev-hassio:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: armv7
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "YES"
|
||||
armv7-latest-docker:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: armv7
|
||||
IS_HASSIO: "NO"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
armv7-latest-hassio:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: armv7
|
||||
IS_HASSIO: "YES"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
i386-beta-docker:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: i386
|
||||
IS_HASSIO: "NO"
|
||||
RELEASE: "YES"
|
||||
i386-beta-hassio:
|
||||
<<: *beta
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: i386
|
||||
IS_HASSIO: "YES"
|
||||
RELEASE: "YES"
|
||||
i386-dev-docker:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: i386
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "NO"
|
||||
i386-dev-hassio:
|
||||
<<: *dev
|
||||
variables:
|
||||
BUILD_ARCH: i386
|
||||
DEV: "YES"
|
||||
IS_HASSIO: "YES"
|
||||
i386-latest-docker:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: i386
|
||||
IS_HASSIO: "NO"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
i386-latest-hassio:
|
||||
<<: *latest
|
||||
variables:
|
||||
BETA: "YES"
|
||||
BUILD_ARCH: i386
|
||||
IS_HASSIO: "YES"
|
||||
LATEST: "YES"
|
||||
RELEASE: "YES"
|
||||
6
.gitpod.yml
Normal file
6
.gitpod.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
ports:
|
||||
- port: 6052
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- before: script/setup
|
||||
command: python -m esphome config dashboard
|
||||
49
.travis.yml
Normal file
49
.travis.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
sudo: false
|
||||
language: python
|
||||
python: '3.5'
|
||||
install: script/setup
|
||||
cache:
|
||||
directories:
|
||||
- "~/.platformio"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: "3.7"
|
||||
env: TARGET=Lint3.7
|
||||
script:
|
||||
- script/ci-custom.py
|
||||
- flake8 esphome
|
||||
- pylint esphome
|
||||
- python: "3.5"
|
||||
env: TARGET=Test3.5
|
||||
script:
|
||||
- esphome tests/test1.yaml compile
|
||||
- esphome tests/test2.yaml compile
|
||||
- esphome tests/test3.yaml compile
|
||||
- python: "2.7"
|
||||
env: TARGET=Test2.7
|
||||
script:
|
||||
- esphome tests/test1.yaml compile
|
||||
- esphome tests/test2.yaml compile
|
||||
- esphome tests/test3.yaml compile
|
||||
- env: TARGET=Cpp-Lint
|
||||
dist: trusty
|
||||
sudo: required
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty-7
|
||||
packages:
|
||||
- clang-tidy-7
|
||||
- clang-format-7
|
||||
before_script:
|
||||
- pio init --ide atom
|
||||
- clang-tidy-7 -version
|
||||
- clang-format-7 -version
|
||||
- clang-apply-replacements-7 -version
|
||||
script:
|
||||
- script/clang-tidy --all-headers -j 2 --fix
|
||||
- script/clang-format -i -j 2
|
||||
- script/ci-suggest-changes
|
||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
18
CONTRIBUTING.md
Normal file
18
CONTRIBUTING.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
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
|
||||
which checks if your new feature compiles correctly.
|
||||
- Sometimes I will let pull requests linger because I'm not 100% sure about them. Please feel free to ping
|
||||
me after some time.
|
||||
25
Dockerfile
25
Dockerfile
@@ -1,25 +0,0 @@
|
||||
FROM python:2.7
|
||||
MAINTAINER Otto Winter <contact@otto-winter.com>
|
||||
|
||||
ENV ESPHOMEYAML_OTA_HOST_PORT=6123
|
||||
EXPOSE 6123
|
||||
VOLUME /config
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY requirements.txt /usr/src/app/
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY docker/platformio.ini /usr/src/app/
|
||||
RUN platformio settings set enable_telemetry No && \
|
||||
platformio lib --global install esphomelib && \
|
||||
platformio run -e espressif32 -e espressif8266; exit 0
|
||||
|
||||
# Fix issue with static IP on ESP32: https://github.com/espressif/arduino-esp32/issues/1081
|
||||
RUN curl https://github.com/espressif/arduino-esp32/commit/144480637a718844b8f48f4392da8d4f622f2e5e.patch | \
|
||||
patch /root/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src/WiFiGeneric.cpp
|
||||
|
||||
COPY . .
|
||||
RUN pip install -e .
|
||||
|
||||
WORKDIR /config
|
||||
ENTRYPOINT ["esphomeyaml"]
|
||||
709
LICENSE
Normal file
709
LICENSE
Normal file
@@ -0,0 +1,709 @@
|
||||
# ESPHome License
|
||||
|
||||
Copyright (c) 2019 ESPHome
|
||||
|
||||
The ESPHome License is made up of two base licenses: MIT and the GNU GENERAL PUBLIC LICENSE.
|
||||
The C++/runtime codebase of the ESPHome project (file extensions .c, .cpp, .h, .hpp, .tcc, .ino) are
|
||||
published under the GPLv3 license. The python codebase and all other parts of this codebase are
|
||||
published under the MIT license.
|
||||
|
||||
Both MIT and GPLv3 licenses are attached to this document.
|
||||
|
||||
## MIT License
|
||||
|
||||
Copyright (c) 2019 ESPHome
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## GPLv3 License
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@@ -0,0 +1,6 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include esphome/dashboard/templates/*.html
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||
recursive-include esphome *.cpp *.h *.tcc
|
||||
recursive-include esphome LICENSE.txt
|
||||
38
README.md
38
README.md
@@ -1,37 +1,9 @@
|
||||
# esphomeyaml for [esphomelib](https://github.com/OttoWinter/esphomelib)
|
||||
# ESPHome [](https://travis-ci.org/esphome/esphome) [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
|
||||
|
||||
Getting Started Guide: https://esphomlib.com/esphomeyaml/getting-started.html
|
||||
Available Components: https://esphomelib.com/esphomeyaml/index.html
|
||||
[](https://esphome.io/)
|
||||
|
||||
esphomeyaml is the solution for your ESP8266/ESP32 projects with Home Assistant. It allows you to create **custom firmwares** for your microcontrollers with no programming experience required. All you need to know is the YAML configuration format which is also used by Home Assistant.
|
||||
**Documentation:** https://esphome.io/
|
||||
|
||||
esphomeyaml will:
|
||||
For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues).
|
||||
|
||||
* Read your configuration file and warn you about potential errors (like using the invalid pins.)
|
||||
* Create a custom C++ sketch file for you using esphomeyaml's powerful C++ generation engine.
|
||||
* Compile the sketch file for you using [platformio](http://platformio.org/).
|
||||
* Upload the binary to your ESP via Over the Air updates.
|
||||
* Automatically start remote logs via MQTT.
|
||||
|
||||
And all of that with a single command 🎉:
|
||||
|
||||
```bash
|
||||
esphomeyaml configuration.yaml run
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* **No programming experience required:** just edit YAML configuration
|
||||
files like you're used to with Home Assistant.
|
||||
* **Flexible:** Use [esphomelib](https://github.com/OttoWinter/esphomelib)'s powerful core to create custom sensors/outputs.
|
||||
* **Fast and efficient:** Written in C++ and keeps memory consumption to a minimum.
|
||||
* **Made for Home Assistant:** Almost all Home Assistant features are supported out of the box. Including RGB lights and many more.
|
||||
* **Easy reproducible configuration:** No need to go through a long setup process for every single node. Just copy a configuration file and run a single command.
|
||||
* **Smart Over The Air Updates:** esphomeyaml has OTA updates deeply integrated into the system. It even automatically enters a recovery mode if a boot loop is detected.
|
||||
* **Powerful logging engine:** View colorful logs and debug issues remotely.
|
||||
* **Open Source**
|
||||
* For me: Makes documenting esphomelib's features a lot easier.
|
||||
|
||||
## Special Thanks
|
||||
|
||||
Special Thanks to the Home Assistant project. Lots of the code base of esphomeyaml is based off of Home Assistant, for example the loading and config validation code.
|
||||
For feature requests, please see [feature requests](https://github.com/esphome/feature-requests/issues).
|
||||
|
||||
12
docker/Dockerfile
Normal file
12
docker/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.0.1
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
COPY . .
|
||||
RUN pip3 install --no-cache-dir -e .
|
||||
|
||||
ENV USERNAME=""
|
||||
ENV PASSWORD=""
|
||||
|
||||
WORKDIR /config
|
||||
ENTRYPOINT ["esphome"]
|
||||
CMD ["/config", "dashboard"]
|
||||
19
docker/Dockerfile.hassio
Normal file
19
docker/Dockerfile.hassio
Normal file
@@ -0,0 +1,19 @@
|
||||
ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/rootfs/ /
|
||||
COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
|
||||
COPY esphome /opt/esphome/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}
|
||||
20
docker/Dockerfile.lint
Normal file
20
docker/Dockerfile.lint
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM esphome/esphome-base-amd64:2.0.1
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
clang-format-7 \
|
||||
clang-tidy-7 \
|
||||
patch \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
COPY requirements_test.txt /requirements_test.txt
|
||||
RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
|
||||
|
||||
RUN ln -s /usr/bin/pip3 /usr/bin/pip && ln -f -s /usr/bin/python3 /usr/bin/python
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
||||
@@ -1,12 +0,0 @@
|
||||
; This file allows the docker build file to install the required platformio
|
||||
; platforms
|
||||
|
||||
[env:espressif32]
|
||||
platform = espressif32
|
||||
board = nodemcu-32s
|
||||
framework = arduino
|
||||
|
||||
[env:espressif8266]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
41
docker/rootfs/etc/cont-init.d/10-requirements.sh
Executable file
41
docker/rootfs/etc/cont-init.d/10-requirements.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/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
|
||||
34
docker/rootfs/etc/cont-init.d/20-nginx.sh
Executable file
34
docker/rootfs/etc/cont-init.d/20-nginx.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/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
|
||||
15
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file
15
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/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')
|
||||
full_url="https://github.com/esphome/esphome/archive/${esphome_version}.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
|
||||
11
docker/rootfs/etc/cont-init.d/40-migrate.sh
Normal file
11
docker/rootfs/etc/cont-init.d/40-migrate.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/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
|
||||
96
docker/rootfs/etc/nginx/includes/mime.types
Normal file
96
docker/rootfs/etc/nginx/includes/mime.types
Normal file
@@ -0,0 +1,96 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
16
docker/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
16
docker/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Authorization "";
|
||||
6
docker/rootfs/etc/nginx/includes/server_params.conf
Normal file
6
docker/rootfs/etc/nginx/includes/server_params.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
9
docker/rootfs/etc/nginx/includes/ssl_params.conf
Normal file
9
docker/rootfs/etc/nginx/includes/ssl_params.conf
Normal file
@@ -0,0 +1,9 @@
|
||||
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;
|
||||
33
docker/rootfs/etc/nginx/nginx.conf
Executable file
33
docker/rootfs/etc/nginx/nginx.conf
Executable file
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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/servers/*.conf;
|
||||
}
|
||||
22
docker/rootfs/etc/nginx/servers/direct-ssl.disabled
Normal file
22
docker/rootfs/etc/nginx/servers/direct-ssl.disabled
Normal file
@@ -0,0 +1,22 @@
|
||||
server {
|
||||
listen %%port%% default_server ssl http2;
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
include /etc/nginx/includes/ssl_params.conf;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /ssl/%%certfile%%;
|
||||
ssl_certificate_key /ssl/%%keyfile%%;
|
||||
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "";
|
||||
|
||||
# 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;
|
||||
|
||||
location / {
|
||||
proxy_pass http://esphome;
|
||||
}
|
||||
}
|
||||
12
docker/rootfs/etc/nginx/servers/direct.disabled
Normal file
12
docker/rootfs/etc/nginx/servers/direct.disabled
Normal file
@@ -0,0 +1,12 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
16
docker/rootfs/etc/nginx/servers/ingress.conf
Normal file
16
docker/rootfs/etc/nginx/servers/ingress.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
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";
|
||||
|
||||
location / {
|
||||
# Only allow from Hass.io supervisor
|
||||
allow 172.30.32.2;
|
||||
deny all;
|
||||
|
||||
proxy_pass http://esphome;
|
||||
}
|
||||
}
|
||||
9
docker/rootfs/etc/services.d/esphome/finish
Executable file
9
docker/rootfs/etc/services.d/esphome/finish
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/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
|
||||
26
docker/rootfs/etc/services.d/esphome/run
Executable file
26
docker/rootfs/etc/services.d/esphome/run
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/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
|
||||
9
docker/rootfs/etc/services.d/nginx/finish
Executable file
9
docker/rootfs/etc/services.d/nginx/finish
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/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
|
||||
14
docker/rootfs/etc/services.d/nginx/run
Executable file
14
docker/rootfs/etc/services.d/nginx/run
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Runs the NGINX proxy
|
||||
# ==============================================================================
|
||||
|
||||
bashio::log.info "Waiting for dashboard to come up..."
|
||||
|
||||
while [[ ! -S /var/run/esphome.sock ]]; do
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
bashio::log.info "Starting NGINX..."
|
||||
exec nginx
|
||||
559
esphome/__main__.py
Normal file
559
esphome/__main__.py
Normal file
@@ -0,0 +1,559 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from esphome import const, writer, yaml_util
|
||||
import esphome.codegen as cg
|
||||
from esphome.config import iter_components, read_config, strip_default_ids
|
||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
||||
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
|
||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||
from esphome.helpers import color, indent
|
||||
from esphome.py_compat import IS_PY2, safe_input
|
||||
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_serial_ports():
|
||||
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
||||
from serial.tools.list_ports import comports
|
||||
result = []
|
||||
for port, desc, info in comports(include_links=True):
|
||||
if not port:
|
||||
continue
|
||||
if "VID:PID" in info:
|
||||
result.append((port, desc))
|
||||
result.sort(key=lambda x: x[0])
|
||||
return result
|
||||
|
||||
|
||||
def choose_prompt(options):
|
||||
if not options:
|
||||
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
||||
"sections (ota, mqtt, ...) are in your configuration and/or the device "
|
||||
"is plugged in.")
|
||||
|
||||
if len(options) == 1:
|
||||
return options[0][1]
|
||||
|
||||
safe_print(u"Found multiple options, please choose one:")
|
||||
for i, (desc, _) in enumerate(options):
|
||||
safe_print(u" [{}] {}".format(i + 1, desc))
|
||||
|
||||
while True:
|
||||
opt = safe_input('(number): ')
|
||||
if opt in options:
|
||||
opt = options.index(opt)
|
||||
break
|
||||
try:
|
||||
opt = int(opt)
|
||||
if opt < 1 or opt > len(options):
|
||||
raise ValueError
|
||||
break
|
||||
except ValueError:
|
||||
safe_print(color('red', u"Invalid option: '{}'".format(opt)))
|
||||
return options[opt - 1][1]
|
||||
|
||||
|
||||
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
||||
options = []
|
||||
for res, desc in get_serial_ports():
|
||||
options.append((u"{} ({})".format(res, desc), res))
|
||||
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
|
||||
options.append((u"Over The Air ({})".format(CORE.address), CORE.address))
|
||||
if default == 'OTA':
|
||||
return CORE.address
|
||||
if show_mqtt and 'mqtt' in CORE.config:
|
||||
options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
||||
if default == 'OTA':
|
||||
return 'MQTT'
|
||||
if default is not None:
|
||||
return default
|
||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||
return check_default
|
||||
return choose_prompt(options)
|
||||
|
||||
|
||||
def get_port_type(port):
|
||||
if port.startswith('/') or port.startswith('COM'):
|
||||
return 'SERIAL'
|
||||
if port == 'MQTT':
|
||||
return 'MQTT'
|
||||
return 'NETWORK'
|
||||
|
||||
|
||||
def run_miniterm(config, port):
|
||||
import serial
|
||||
from esphome import platformio_api
|
||||
|
||||
if CONF_LOGGER not in config:
|
||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||
return
|
||||
baud_rate = config['logger'][CONF_BAUD_RATE]
|
||||
if baud_rate == 0:
|
||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||
|
||||
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
|
||||
if IS_PY2:
|
||||
line = raw.replace('\r', '').replace('\n', '')
|
||||
else:
|
||||
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8',
|
||||
'backslashreplace')
|
||||
time = datetime.now().time().strftime('[%H:%M:%S]')
|
||||
message = time + line
|
||||
safe_print(message)
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state)
|
||||
|
||||
|
||||
def wrap_to_code(name, comp):
|
||||
coro = coroutine(comp.to_code)
|
||||
|
||||
@functools.wraps(comp.to_code)
|
||||
@coroutine_with_priority(coro.priority)
|
||||
def wrapped(conf):
|
||||
cg.add(cg.LineComment(u"{}:".format(name)))
|
||||
if comp.config_schema is not None:
|
||||
conf_str = yaml_util.dump(conf)
|
||||
if IS_PY2:
|
||||
conf_str = conf_str.decode('utf-8')
|
||||
conf_str = conf_str.replace('//', '')
|
||||
cg.add(cg.LineComment(indent(conf_str)))
|
||||
yield coro(conf)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def write_cpp(config):
|
||||
_LOGGER.info("Generating C++ source...")
|
||||
|
||||
for name, component, conf in iter_components(CORE.config):
|
||||
if component.to_code is not None:
|
||||
coro = wrap_to_code(name, component)
|
||||
CORE.add_job(coro, conf)
|
||||
|
||||
CORE.flush_tasks()
|
||||
|
||||
writer.write_platformio_project()
|
||||
|
||||
code_s = indent(CORE.cpp_main_section)
|
||||
writer.write_cpp(code_s)
|
||||
return 0
|
||||
|
||||
|
||||
def compile_program(args, config):
|
||||
from esphome import platformio_api
|
||||
|
||||
_LOGGER.info("Compiling app...")
|
||||
return platformio_api.run_compile(config, CORE.verbose)
|
||||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
path = CORE.firmware_bin
|
||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
||||
'--baud', str(config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800)),
|
||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', 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_process(*cmd)
|
||||
|
||||
|
||||
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:
|
||||
return upload_using_esptool(config, host)
|
||||
return platformio_api.run_upload(config, CORE.verbose, host)
|
||||
|
||||
from esphome import espota2
|
||||
|
||||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
password = ota_conf[CONF_PASSWORD]
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
|
||||
|
||||
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
|
||||
if get_port_type(port) == 'NETWORK' and 'api' in config:
|
||||
from esphome.api.client import run_logs
|
||||
|
||||
return run_logs(config, port)
|
||||
if get_port_type(port) == 'MQTT' and 'mqtt' in config:
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
|
||||
|
||||
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||
|
||||
|
||||
def clean_mqtt(config, args):
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
|
||||
|
||||
|
||||
def setup_log(debug=False, quiet=False):
|
||||
if debug:
|
||||
log_level = logging.DEBUG
|
||||
CORE.verbose = True
|
||||
elif quiet:
|
||||
log_level = logging.CRITICAL
|
||||
else:
|
||||
log_level = logging.INFO
|
||||
logging.basicConfig(level=log_level)
|
||||
fmt = "%(levelname)s %(message)s"
|
||||
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
|
||||
datefmt = '%H:%M:%S'
|
||||
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
|
||||
try:
|
||||
from colorlog import ColoredFormatter
|
||||
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
|
||||
colorfmt,
|
||||
datefmt=datefmt,
|
||||
reset=True,
|
||||
log_colors={
|
||||
'DEBUG': 'cyan',
|
||||
'INFO': 'green',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red',
|
||||
}
|
||||
))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def command_wizard(args):
|
||||
from esphome import wizard
|
||||
|
||||
return wizard.wizard(args.configuration[0])
|
||||
|
||||
|
||||
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))
|
||||
return 0
|
||||
|
||||
|
||||
def command_vscode(args):
|
||||
from esphome import vscode
|
||||
|
||||
CORE.config_path = args.configuration[0]
|
||||
vscode.read_config(args)
|
||||
|
||||
|
||||
def command_compile(args, config):
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
if args.only_generate:
|
||||
_LOGGER.info(u"Successfully generated source code.")
|
||||
return 0
|
||||
exit_code = compile_program(args, config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully compiled program.")
|
||||
return 0
|
||||
|
||||
|
||||
def command_upload(args, config):
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=False)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully uploaded program.")
|
||||
return 0
|
||||
|
||||
|
||||
def command_logs(args, config):
|
||||
port = choose_upload_log_host(default=args.serial_port, check_default=None,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
return show_logs(config, args, port)
|
||||
|
||||
|
||||
def command_run(args, config):
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
exit_code = compile_program(args, config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully compiled program.")
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=True)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info(u"Successfully uploaded program.")
|
||||
if args.no_logs:
|
||||
return 0
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
return show_logs(config, args, port)
|
||||
|
||||
|
||||
def command_clean_mqtt(args, config):
|
||||
return clean_mqtt(config, args)
|
||||
|
||||
|
||||
def command_mqtt_fingerprint(args, config):
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.get_fingerprint(config)
|
||||
|
||||
|
||||
def command_version(args):
|
||||
safe_print(u"Version: {}".format(const.__version__))
|
||||
return 0
|
||||
|
||||
|
||||
def command_clean(args, config):
|
||||
try:
|
||||
writer.clean_build()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Error deleting build files: %s", err)
|
||||
return 1
|
||||
_LOGGER.info("Done!")
|
||||
return 0
|
||||
|
||||
|
||||
def command_dashboard(args):
|
||||
from esphome.dashboard import dashboard
|
||||
|
||||
return dashboard.start_web_server(args)
|
||||
|
||||
|
||||
def command_update_all(args):
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration[0])
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
middle_text = " {} ".format(middle_text)
|
||||
width = len(click.unstyle(middle_text))
|
||||
half_line = "=" * ((twidth - width) / 2)
|
||||
click.echo("%s%s%s" % (half_line, middle_text, half_line))
|
||||
|
||||
for f in files:
|
||||
print("Updating {}".format(color('cyan', f)))
|
||||
print('-' * twidth)
|
||||
print()
|
||||
rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs')
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
|
||||
success[f] = True
|
||||
else:
|
||||
print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
|
||||
success[f] = False
|
||||
|
||||
print()
|
||||
print()
|
||||
print()
|
||||
|
||||
print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
|
||||
failed = 0
|
||||
for f in files:
|
||||
if success[f]:
|
||||
print(" - {}: {}".format(f, color('green', 'SUCCESS')))
|
||||
else:
|
||||
print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
|
||||
failed += 1
|
||||
return failed
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
'wizard': command_wizard,
|
||||
'version': command_version,
|
||||
'dashboard': command_dashboard,
|
||||
'vscode': command_vscode,
|
||||
'update-all': command_update_all,
|
||||
}
|
||||
|
||||
POST_CONFIG_ACTIONS = {
|
||||
'config': command_config,
|
||||
'compile': command_compile,
|
||||
'upload': command_upload,
|
||||
'logs': command_logs,
|
||||
'run': command_run,
|
||||
'clean-mqtt': command_clean_mqtt,
|
||||
'mqtt-fingerprint': command_mqtt_fingerprint,
|
||||
'clean': command_clean,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(description='ESPHome v{}'.format(const.__version__))
|
||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
||||
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
||||
|
||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||
subparsers.required = True
|
||||
subparsers.add_parser('config', help='Validate the configuration and spit it out.')
|
||||
|
||||
parser_compile = subparsers.add_parser('compile',
|
||||
help='Read the configuration and compile a program.')
|
||||
parser_compile.add_argument('--only-generate',
|
||||
help="Only generate source code, do not compile.",
|
||||
action='store_true')
|
||||
|
||||
parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
|
||||
'and upload the latest binary.')
|
||||
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
|
||||
parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
|
||||
'and show all MQTT logs.')
|
||||
parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
||||
parser_logs.add_argument('--username', help='Manually set the username.')
|
||||
parser_logs.add_argument('--password', help='Manually set the password.')
|
||||
parser_logs.add_argument('--client-id', help='Manually set the client id.')
|
||||
parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
|
||||
parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
|
||||
'upload it, and start MQTT logs.')
|
||||
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
|
||||
action='store_true')
|
||||
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
||||
parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
|
||||
parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
|
||||
parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
|
||||
|
||||
parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
|
||||
"retain messages.")
|
||||
parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
||||
parser_clean.add_argument('--username', help='Manually set the username.')
|
||||
parser_clean.add_argument('--password', help='Manually set the password.')
|
||||
parser_clean.add_argument('--client-id', help='Manually set the client id.')
|
||||
|
||||
subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.")
|
||||
|
||||
subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
|
||||
|
||||
subparsers.add_parser('version', help="Print the esphome version and exit.")
|
||||
|
||||
subparsers.add_parser('clean', help="Delete all temporary build files.")
|
||||
|
||||
dashboard = subparsers.add_parser('dashboard',
|
||||
help="Create a simple web server for a dashboard.")
|
||||
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int, default=6052)
|
||||
dashboard.add_argument("--username", help="The optional username to require "
|
||||
"for authentication.",
|
||||
type=str, default='')
|
||||
dashboard.add_argument("--password", help="The optional password to require "
|
||||
"for authentication.",
|
||||
type=str, default='')
|
||||
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
||||
action='store_true')
|
||||
dashboard.add_argument("--hassio",
|
||||
help=argparse.SUPPRESS,
|
||||
action="store_true")
|
||||
dashboard.add_argument("--socket",
|
||||
help="Make the dashboard serve under a unix socket", type=str)
|
||||
|
||||
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
||||
vscode.add_argument('--ace', action='store_true')
|
||||
|
||||
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
|
||||
|
||||
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
|
||||
|
||||
if IS_PY2:
|
||||
_LOGGER.warning("You're using ESPHome with python 2. Support for python 2 is deprecated "
|
||||
"and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 "
|
||||
"or higher.")
|
||||
|
||||
if args.command in PRE_CONFIG_ACTIONS:
|
||||
try:
|
||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
|
||||
for conf_path in args.configuration:
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
config = read_config()
|
||||
if config is None:
|
||||
return 1
|
||||
CORE.config = config
|
||||
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(u"Unknown command {}".format(args.command))
|
||||
|
||||
try:
|
||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
CORE.reset()
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
return run_esphome(sys.argv)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
2485
esphome/api/api_pb2.py
Normal file
2485
esphome/api/api_pb2.py
Normal file
File diff suppressed because one or more lines are too long
495
esphome/api/client.py
Normal file
495
esphome/api/client.py
Normal file
@@ -0,0 +1,495 @@
|
||||
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.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte
|
||||
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 byte_to_bytes(value)
|
||||
|
||||
ret = bytes()
|
||||
while value:
|
||||
temp = value & 0x7F
|
||||
value >>= 7
|
||||
if value:
|
||||
ret += byte_to_bytes(temp | 0x80)
|
||||
else:
|
||||
ret += byte_to_bytes(temp)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _bytes_to_varuint(value):
|
||||
result = 0
|
||||
bitpos = 0
|
||||
for c in value:
|
||||
val = char_to_byte(c)
|
||||
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)
|
||||
|
||||
_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 socket.error as err:
|
||||
err = APIConnectionError("Error connecting to {}: {}".format(ip, err))
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
self._socket.settimeout(0.1)
|
||||
|
||||
self._socket_open_event.set()
|
||||
|
||||
hello = pb.HelloRequest()
|
||||
hello.client_info = 'ESPHome v{}'.format(const.__version__)
|
||||
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 socket.error as err:
|
||||
err = APIConnectionError("Error while writing data: {}".format(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(text_type(msg)))
|
||||
if IS_PY2:
|
||||
req = chr(0x00)
|
||||
else:
|
||||
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:
|
||||
raise APIConnectionError("Socket was closed")
|
||||
except socket.timeout:
|
||||
continue
|
||||
except socket.error as err:
|
||||
raise APIConnectionError("Error while receiving data: {}".format(err))
|
||||
ret += val
|
||||
return ret
|
||||
|
||||
def _recv_varint(self):
|
||||
raw = bytes()
|
||||
while not raw or char_to_byte(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 char_to_byte(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(u"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(u"Initial connection failed. The ESP might not be connected "
|
||||
u"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error, wait_time)
|
||||
else:
|
||||
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error, wait_time)
|
||||
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
|
||||
timer.start()
|
||||
retry_timer.append(timer)
|
||||
|
||||
def on_log(msg):
|
||||
time_ = datetime.now().time().strftime(u'[%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
|
||||
266
esphome/automation.py
Normal file
266
esphome/automation.py
Normal file
@@ -0,0 +1,266 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
|
||||
CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME
|
||||
from esphome.core import coroutine
|
||||
from esphome.util import Registry
|
||||
|
||||
|
||||
def maybe_simple_id(*validators):
|
||||
validator = cv.All(*validators)
|
||||
|
||||
def validate(value):
|
||||
if isinstance(value, dict):
|
||||
return validator(value)
|
||||
with cv.remove_prepend_path([CONF_ID]):
|
||||
return validator({CONF_ID: value})
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
def register_action(name, action_type, schema):
|
||||
return ACTION_REGISTRY.register(name, action_type, schema)
|
||||
|
||||
|
||||
def register_condition(name, condition_type, schema):
|
||||
return CONDITION_REGISTRY.register(name, condition_type, schema)
|
||||
|
||||
|
||||
Action = cg.esphome_ns.class_('Action')
|
||||
Trigger = cg.esphome_ns.class_('Trigger')
|
||||
ACTION_REGISTRY = Registry()
|
||||
Condition = cg.esphome_ns.class_('Condition')
|
||||
CONDITION_REGISTRY = Registry()
|
||||
validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY)
|
||||
validate_action_list = cv.validate_registry('action', ACTION_REGISTRY)
|
||||
validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY)
|
||||
validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY)
|
||||
|
||||
|
||||
def validate_potentially_and_condition(value):
|
||||
if isinstance(value, list):
|
||||
with cv.remove_prepend_path(['and']):
|
||||
return validate_condition({
|
||||
'and': value
|
||||
})
|
||||
return validate_condition(value)
|
||||
|
||||
|
||||
DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component)
|
||||
LambdaAction = cg.esphome_ns.class_('LambdaAction', Action)
|
||||
IfAction = cg.esphome_ns.class_('IfAction', Action)
|
||||
WhileAction = cg.esphome_ns.class_('WhileAction', Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
|
||||
Automation = cg.esphome_ns.class_('Automation')
|
||||
|
||||
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
|
||||
ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component)
|
||||
|
||||
|
||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
if extra_schema is None:
|
||||
extra_schema = {}
|
||||
if isinstance(extra_schema, cv.Schema):
|
||||
extra_schema = extra_schema.schema
|
||||
schema = AUTOMATION_SCHEMA.extend(extra_schema)
|
||||
|
||||
def validator_(value):
|
||||
if isinstance(value, list):
|
||||
# List of items, there are two possible options here, either a sequence of
|
||||
# actions (no then:) or a list of automations.
|
||||
try:
|
||||
# First try as a sequence of actions
|
||||
# If that succeeds, return immediately
|
||||
with cv.remove_prepend_path([CONF_THEN]):
|
||||
return [schema({CONF_THEN: value})]
|
||||
except cv.Invalid as err:
|
||||
# Next try as a sequence of automations
|
||||
try:
|
||||
return cv.Schema([schema])(value)
|
||||
except cv.Invalid as err2:
|
||||
if u'extra keys not allowed' in str(err2) and len(err2.path) == 2:
|
||||
raise err
|
||||
if u'Unable to find action' in str(err):
|
||||
raise err2
|
||||
raise cv.MultipleInvalid([err, err2])
|
||||
elif isinstance(value, dict):
|
||||
if CONF_THEN in value:
|
||||
return [schema(value)]
|
||||
with cv.remove_prepend_path([CONF_THEN]):
|
||||
return [schema({CONF_THEN: value})]
|
||||
# This should only happen with invalid configs, but let's have a nice error message.
|
||||
return [schema(value)]
|
||||
|
||||
def validator(value):
|
||||
value = validator_(value)
|
||||
if extra_validators is not None:
|
||||
value = cv.Schema([extra_validators])(value)
|
||||
if single:
|
||||
if len(value) != 1:
|
||||
raise cv.Invalid("Cannot have more than 1 automation for templates")
|
||||
return value[0]
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
AUTOMATION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
|
||||
cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
})
|
||||
|
||||
AndCondition = cg.esphome_ns.class_('AndCondition', Condition)
|
||||
OrCondition = cg.esphome_ns.class_('OrCondition', Condition)
|
||||
NotCondition = cg.esphome_ns.class_('NotCondition', Condition)
|
||||
|
||||
|
||||
@register_condition('and', AndCondition, validate_condition_list)
|
||||
def and_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition('or', OrCondition, validate_condition_list)
|
||||
def or_condition_to_code(config, condition_id, template_arg, args):
|
||||
conditions = yield build_condition_list(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, conditions)
|
||||
|
||||
|
||||
@register_condition('not', NotCondition, validate_potentially_and_condition)
|
||||
def not_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(config, template_arg, args)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
|
||||
|
||||
@register_condition('lambda', LambdaCondition, cv.lambda_)
|
||||
def lambda_condition_to_code(config, condition_id, template_arg, args):
|
||||
lambda_ = yield cg.process_lambda(config, args, return_type=bool)
|
||||
yield cg.new_Pvariable(condition_id, template_arg, lambda_)
|
||||
|
||||
|
||||
@register_condition('for', ForCondition, cv.Schema({
|
||||
cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
}).extend(cv.COMPONENT_SCHEMA))
|
||||
def for_condition_to_code(config, condition_id, template_arg, args):
|
||||
condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), [])
|
||||
var = cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
yield cg.register_component(var, config)
|
||||
templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
|
||||
cg.add(var.set_time(templ))
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds))
|
||||
def delay_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
yield cg.register_component(var, {})
|
||||
template_ = yield cg.templatable(config, args, cg.uint32)
|
||||
cg.add(var.set_delay(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('if', IfAction, cv.All({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_THEN): validate_action_list,
|
||||
cv.Optional(CONF_ELSE): validate_action_list,
|
||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE)))
|
||||
def if_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
if CONF_THEN in config:
|
||||
actions = yield 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)
|
||||
cg.add(var.add_else(actions))
|
||||
yield var
|
||||
|
||||
|
||||
@register_action('while', WhileAction, cv.Schema({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}))
|
||||
def while_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
actions = yield build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
yield var
|
||||
|
||||
|
||||
def validate_wait_until(value):
|
||||
schema = cv.Schema({
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
})
|
||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||
return schema(value)
|
||||
return validate_wait_until({CONF_CONDITION: value})
|
||||
|
||||
|
||||
@register_action('wait_until', WaitUntilAction, validate_wait_until)
|
||||
def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
yield cg.register_component(var, {})
|
||||
yield 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_)
|
||||
|
||||
|
||||
@register_action('component.update', UpdateComponentAction, maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
|
||||
}))
|
||||
def component_update_action_to_code(config, action_id, template_arg, args):
|
||||
comp = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, comp)
|
||||
|
||||
|
||||
@coroutine
|
||||
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)
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_action_list(config, templ, arg_type):
|
||||
actions = []
|
||||
for conf in config:
|
||||
action = yield build_action(conf, templ, arg_type)
|
||||
actions.append(action)
|
||||
yield actions
|
||||
|
||||
|
||||
@coroutine
|
||||
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)
|
||||
|
||||
|
||||
@coroutine
|
||||
def build_condition_list(config, templ, args):
|
||||
conditions = []
|
||||
for conf in config:
|
||||
condition = yield build_condition(conf, templ, args)
|
||||
conditions.append(condition)
|
||||
yield conditions
|
||||
|
||||
|
||||
@coroutine
|
||||
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)
|
||||
cg.add(obj.add_actions(actions))
|
||||
yield obj
|
||||
26
esphome/codegen.py
Normal file
26
esphome/codegen.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Base file for all codegen-related imports
|
||||
# All integrations should have a line in the import section like this
|
||||
#
|
||||
# >>> import esphome.codegen as cg
|
||||
#
|
||||
# Integrations should specifically *NOT* import directly from the
|
||||
# other helper modules (cpp_generator etc) directly if they don't
|
||||
# want to break suddenly due to a rename (this file will get backports for features).
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from esphome.cpp_generator import ( # noqa
|
||||
Expression, RawExpression, RawStatement, TemplateArguments,
|
||||
StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment,
|
||||
progmem_array, statement, variable, Pvariable, new_Pvariable,
|
||||
add, add_global, add_library, add_build_flag, add_define,
|
||||
get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj,
|
||||
MockObjClass)
|
||||
from esphome.cpp_helpers import ( # noqa
|
||||
gpio_pin_expression, register_component, build_registry_entry,
|
||||
build_registry_list, extract_registry_entry_config, register_parented)
|
||||
from esphome.cpp_types import ( # noqa
|
||||
global_ns, void, nullptr, float_, double, bool_, std_ns, std_string,
|
||||
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
|
||||
esphome_ns, App, Nameable, Component, ComponentPtr,
|
||||
PollingComponent, Application, optional, arduino_json_ns, JsonObject,
|
||||
JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin)
|
||||
0
esphome/components/__init__.py
Normal file
0
esphome/components/__init__.py
Normal file
0
esphome/components/a4988/__init__.py
Normal file
0
esphome/components/a4988/__init__.py
Normal file
49
esphome/components/a4988/a4988.cpp
Normal file
49
esphome/components/a4988/a4988.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "a4988.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace a4988 {
|
||||
|
||||
static const char *TAG = "a4988.stepper";
|
||||
|
||||
void A4988::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up A4988...");
|
||||
if (this->sleep_pin_ != nullptr) {
|
||||
this->sleep_pin_->setup();
|
||||
this->sleep_pin_->digital_write(false);
|
||||
}
|
||||
this->step_pin_->setup();
|
||||
this->step_pin_->digital_write(false);
|
||||
this->dir_pin_->setup();
|
||||
this->dir_pin_->digital_write(false);
|
||||
}
|
||||
void A4988::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "A4988:");
|
||||
LOG_PIN(" Step Pin: ", this->step_pin_);
|
||||
LOG_PIN(" Dir Pin: ", this->dir_pin_);
|
||||
LOG_PIN(" Sleep Pin: ", this->sleep_pin_);
|
||||
LOG_STEPPER(this);
|
||||
}
|
||||
void A4988::loop() {
|
||||
bool at_target = this->has_reached_target();
|
||||
if (this->sleep_pin_ != nullptr) {
|
||||
this->sleep_pin_->digital_write(!at_target);
|
||||
}
|
||||
if (at_target) {
|
||||
this->high_freq_.stop();
|
||||
} else {
|
||||
this->high_freq_.start();
|
||||
}
|
||||
|
||||
int32_t dir = this->should_step_();
|
||||
if (dir == 0)
|
||||
return;
|
||||
|
||||
this->dir_pin_->digital_write(dir == 1);
|
||||
this->step_pin_->digital_write(true);
|
||||
delayMicroseconds(5);
|
||||
this->step_pin_->digital_write(false);
|
||||
}
|
||||
|
||||
} // namespace a4988
|
||||
} // namespace esphome
|
||||
28
esphome/components/a4988/a4988.h
Normal file
28
esphome/components/a4988/a4988.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/components/stepper/stepper.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace a4988 {
|
||||
|
||||
class A4988 : public stepper::Stepper, public Component {
|
||||
public:
|
||||
void set_step_pin(GPIOPin *step_pin) { step_pin_ = step_pin; }
|
||||
void set_dir_pin(GPIOPin *dir_pin) { dir_pin_ = dir_pin; }
|
||||
void set_sleep_pin(GPIOPin *sleep_pin) { this->sleep_pin_ = sleep_pin; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
protected:
|
||||
GPIOPin *step_pin_;
|
||||
GPIOPin *dir_pin_;
|
||||
GPIOPin *sleep_pin_{nullptr};
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
};
|
||||
|
||||
} // namespace a4988
|
||||
} // namespace esphome
|
||||
31
esphome/components/a4988/stepper.py
Normal file
31
esphome/components/a4988/stepper.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from esphome import pins
|
||||
from esphome.components import stepper
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
|
||||
|
||||
|
||||
a4988_ns = cg.esphome_ns.namespace('a4988')
|
||||
A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({
|
||||
cv.Required(CONF_ID): cv.declare_id(A4988),
|
||||
cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield stepper.register_stepper(var, config)
|
||||
|
||||
step_pin = yield 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])
|
||||
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])
|
||||
cg.add(var.set_sleep_pin(sleep_pin))
|
||||
0
esphome/components/adc/__init__.py
Normal file
0
esphome/components/adc/__init__.py
Normal file
92
esphome/components/adc/adc_sensor.cpp
Normal file
92
esphome/components/adc/adc_sensor.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
ADC_MODE(ADC_VCC)
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
static const char *TAG = "adc";
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
|
||||
#endif
|
||||
|
||||
void ADCSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||
GPIOPin(this->pin_, INPUT).setup();
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
||||
#endif
|
||||
}
|
||||
void ADCSensor::dump_config() {
|
||||
LOG_SENSOR("", "ADC Sensor", this);
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
ESP_LOGCONFIG(TAG, " Pin: VCC");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->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
|
||||
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);
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
float ADCSensor::sample() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
float value_v = analogRead(this->pin_) / 4095.0f;
|
||||
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;
|
||||
}
|
||||
return value_v;
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
return ESP.getVcc() / 1024.0f;
|
||||
#else
|
||||
return analogRead(this->pin_) / 1024.0f;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||
#endif
|
||||
|
||||
} // namespace adc
|
||||
} // namespace esphome
|
||||
42
esphome/components/adc/adc_sensor.h
Normal file
42
esphome/components/adc/adc_sensor.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
/// Set the attenuation for this pin. Only available on the ESP32.
|
||||
void set_attenuation(adc_attenuation_t attenuation);
|
||||
#endif
|
||||
|
||||
/// Update adc values.
|
||||
void update() override;
|
||||
/// Setup ADc
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// `HARDWARE_LATE` setup priority.
|
||||
float get_setup_priority() const override;
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
float sample() override;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
std::string unique_id() override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
uint8_t pin_;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
adc_attenuation_t attenuation_{ADC_0db};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace adc
|
||||
} // namespace esphome
|
||||
48
esphome/components/adc/sensor.py
Normal file
48
esphome/components/adc/sensor.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT
|
||||
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
|
||||
def validate_adc_pin(value):
|
||||
vcc = str(value).upper()
|
||||
if vcc == 'VCC':
|
||||
return cv.only_on_esp8266(vcc)
|
||||
return pins.analog_pin(value)
|
||||
|
||||
|
||||
adc_ns = cg.esphome_ns.namespace('adc')
|
||||
ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({
|
||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||
cv.Required(CONF_PIN): validate_adc_pin,
|
||||
cv.SplitDefault(CONF_ATTENUATION, esp32='0db'):
|
||||
cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)),
|
||||
}).extend(cv.polling_component_schema('60s'))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
|
||||
if config[CONF_PIN] == 'VCC':
|
||||
cg.add_define('USE_ADC_SENSOR_VCC')
|
||||
else:
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
|
||||
if CONF_ATTENUATION in config:
|
||||
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
|
||||
0
esphome/components/ade7953/__init__.py
Normal file
0
esphome/components/ade7953/__init__.py
Normal file
51
esphome/components/ade7953/ade7953.cpp
Normal file
51
esphome/components/ade7953/ade7953.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "ade7953.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7953 {
|
||||
|
||||
static const char *TAG = "ade7953";
|
||||
|
||||
void ADE7953::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADE7953:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current A Sensor", this->current_a_sensor_);
|
||||
LOG_SENSOR(" ", "Current B Sensor", this->current_b_sensor_);
|
||||
LOG_SENSOR(" ", "Active Power A Sensor", this->active_power_a_sensor_);
|
||||
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
|
||||
}
|
||||
|
||||
#define ADE_PUBLISH_(name, factor) \
|
||||
if (name) { \
|
||||
float value = *name / factor; \
|
||||
this->name##_sensor_->publish_state(value); \
|
||||
}
|
||||
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
|
||||
|
||||
void ADE7953::update() {
|
||||
if (!this->is_setup_)
|
||||
return;
|
||||
|
||||
auto active_power_a = this->ade_read_<int32_t>(0x0312);
|
||||
ADE_PUBLISH(active_power_a, 154.0f);
|
||||
auto active_power_b = this->ade_read_<int32_t>(0x0313);
|
||||
ADE_PUBLISH(active_power_b, 154.0f);
|
||||
auto current_a = this->ade_read_<uint32_t>(0x031A);
|
||||
ADE_PUBLISH(current_a, 100000.0f);
|
||||
auto current_b = this->ade_read_<uint32_t>(0x031B);
|
||||
ADE_PUBLISH(current_b, 100000.0f);
|
||||
auto voltage = this->ade_read_<uint32_t>(0x031C);
|
||||
ADE_PUBLISH(voltage, 26000.0f);
|
||||
|
||||
// auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
|
||||
// auto apparent_power_b = this->ade_read_<int32_t>(0x0311);
|
||||
// auto reactive_power_a = this->ade_read_<int32_t>(0x0314);
|
||||
// auto reactive_power_b = this->ade_read_<int32_t>(0x0315);
|
||||
// auto power_factor_a = this->ade_read_<int16_t>(0x010A);
|
||||
// auto power_factor_b = this->ade_read_<int16_t>(0x010B);
|
||||
}
|
||||
|
||||
} // namespace ade7953
|
||||
} // namespace esphome
|
||||
67
esphome/components/ade7953/ade7953.h
Normal file
67
esphome/components/ade7953/ade7953.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7953 {
|
||||
|
||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
|
||||
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
|
||||
void set_active_power_a_sensor(sensor::Sensor *active_power_a_sensor) {
|
||||
active_power_a_sensor_ = active_power_a_sensor;
|
||||
}
|
||||
void set_active_power_b_sensor(sensor::Sensor *active_power_b_sensor) {
|
||||
active_power_b_sensor_ = active_power_b_sensor;
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
this->set_timeout(100, [this]() {
|
||||
this->ade_write_<uint8_t>(0x0010, 0x04);
|
||||
this->ade_write_<uint8_t>(0x00FE, 0xAD);
|
||||
this->ade_write_<uint16_t>(0x0120, 0x0030);
|
||||
this->is_setup_ = true;
|
||||
});
|
||||
}
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
template<typename T> bool ade_write_(uint16_t reg, T value) {
|
||||
std::vector<uint8_t> data;
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
for (int i = sizeof(T) - 1; i >= 0; i--)
|
||||
data.push_back(value >> (i * 8));
|
||||
return this->write_bytes_raw(data);
|
||||
}
|
||||
template<typename T> optional<T> ade_read_(uint16_t reg) {
|
||||
uint8_t hi = reg >> 8;
|
||||
uint8_t lo = reg >> 0;
|
||||
if (!this->write_bytes_raw({hi, lo}))
|
||||
return {};
|
||||
auto ret = this->read_bytes_raw<sizeof(T)>();
|
||||
if (!ret.has_value())
|
||||
return {};
|
||||
T result = 0;
|
||||
for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--)
|
||||
result |= T((*ret)[i]) << (j * 8);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_setup_{false};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_a_sensor_{nullptr};
|
||||
sensor::Sensor *current_b_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_a_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_b_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ade7953
|
||||
} // namespace esphome
|
||||
39
esphome/components/ade7953/sensor.py
Normal file
39
esphome/components/ade7953/sensor.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, i2c
|
||||
from esphome.const import CONF_ID, CONF_VOLTAGE, \
|
||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
ace7953_ns = cg.esphome_ns.namespace('ade7953')
|
||||
ADE7953 = ace7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONF_CURRENT_A = 'current_a'
|
||||
CONF_CURRENT_B = 'current_b'
|
||||
CONF_ACTIVE_POWER_A = 'active_power_a'
|
||||
CONF_ACTIVE_POWER_B = 'active_power_b'
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
|
||||
CONF_ACTIVE_POWER_B]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens))
|
||||
25
esphome/components/ads1115/__init__.py
Normal file
25
esphome/components/ads1115/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
AUTO_LOAD = ['sensor', 'voltage_sampler']
|
||||
MULTI_CONF = True
|
||||
|
||||
ads1115_ns = cg.esphome_ns.namespace('ads1115')
|
||||
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
|
||||
|
||||
CONF_CONTINUOUS_MODE = 'continuous_mode'
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
||||
169
esphome/components/ads1115/ads1115.cpp
Normal file
169
esphome/components/ads1115/ads1115.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "ads1115.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115 {
|
||||
|
||||
static const char *TAG = "ads1115";
|
||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||
|
||||
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111;
|
||||
|
||||
void ADS1115Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
||||
uint16_t value;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t config = 0;
|
||||
// Clear single-shot bit
|
||||
// 0b0xxxxxxxxxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
// Setup multiplexer
|
||||
// 0bx000xxxxxxxxxxxx
|
||||
config |= ADS1115_MULTIPLEXER_P0_N1 << 12;
|
||||
|
||||
// Setup Gain
|
||||
// 0bxxxx000xxxxxxxxx
|
||||
config |= ADS1115_GAIN_6P144 << 9;
|
||||
|
||||
if (this->continuous_mode_) {
|
||||
// Set continuous mode
|
||||
// 0bxxxxxxx0xxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
} else {
|
||||
// Set singleshot mode
|
||||
// 0bxxxxxxx1xxxxxxxx
|
||||
config |= 0b0000000100000000;
|
||||
}
|
||||
|
||||
// Set data rate - 860 samples per second (we're in singleshot mode)
|
||||
// 0bxxxxxxxx100xxxxx
|
||||
config |= ADS1115_DATA_RATE_860_SPS << 5;
|
||||
|
||||
// Set comparator mode - hysteresis
|
||||
// 0bxxxxxxxxxxx0xxxx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator polarity - active low
|
||||
// 0bxxxxxxxxxxxx0xxx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator latch enabled - false
|
||||
// 0bxxxxxxxxxxxxx0xx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator que mode - disabled
|
||||
// 0bxxxxxxxxxxxxxx11
|
||||
config |= 0b0000000000000011;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
this->set_interval(sensor->get_name(), sensor->update_interval(),
|
||||
[this, sensor] { this->request_measurement(sensor); });
|
||||
}
|
||||
}
|
||||
void ADS1115Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with ADS1115 failed!");
|
||||
}
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
LOG_SENSOR(" ", "Sensor", sensor);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", sensor->get_multiplexer());
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", sensor->get_gain());
|
||||
}
|
||||
}
|
||||
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
uint16_t config = this->prev_config_;
|
||||
// Multiplexer
|
||||
// 0bxBBBxxxxxxxxxxxx
|
||||
config &= 0b1000111111111111;
|
||||
config |= (sensor->get_multiplexer() & 0b111) << 12;
|
||||
|
||||
// Gain
|
||||
// 0bxxxxBBBxxxxxxxxx
|
||||
config &= 0b1111000111111111;
|
||||
config |= (sensor->get_gain() & 0b111) << 9;
|
||||
|
||||
if (!this->continuous_mode_) {
|
||||
// Start conversion
|
||||
config |= 0b1000000000000000;
|
||||
}
|
||||
|
||||
if (!this->continuous_mode_ || this->prev_config_ != config) {
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
|
||||
// about 1.6 ms with 860 samples per second
|
||||
delay(2);
|
||||
|
||||
uint32_t start = millis();
|
||||
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
||||
if (millis() - start > 100) {
|
||||
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t raw_conversion;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
auto signed_conversion = static_cast<int16_t>(raw_conversion);
|
||||
|
||||
float millivolts;
|
||||
switch (sensor->get_gain()) {
|
||||
case ADS1115_GAIN_6P144:
|
||||
millivolts = signed_conversion * 0.187500f;
|
||||
break;
|
||||
case ADS1115_GAIN_4P096:
|
||||
millivolts = signed_conversion * 0.125000f;
|
||||
break;
|
||||
case ADS1115_GAIN_2P048:
|
||||
millivolts = signed_conversion * 0.062500f;
|
||||
break;
|
||||
case ADS1115_GAIN_1P024:
|
||||
millivolts = signed_conversion * 0.031250f;
|
||||
break;
|
||||
case ADS1115_GAIN_0P512:
|
||||
millivolts = signed_conversion * 0.015625f;
|
||||
break;
|
||||
case ADS1115_GAIN_0P256:
|
||||
millivolts = signed_conversion * 0.007813f;
|
||||
break;
|
||||
default:
|
||||
millivolts = NAN;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return millivolts / 1e3f;
|
||||
}
|
||||
|
||||
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
|
||||
void ADS1115Sensor::update() {
|
||||
float v = this->parent_->request_measurement(this);
|
||||
if (!isnan(v)) {
|
||||
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
|
||||
this->publish_state(v);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
71
esphome/components/ads1115/ads1115.h
Normal file
71
esphome/components/ads1115/ads1115.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115 {
|
||||
|
||||
enum ADS1115Multiplexer {
|
||||
ADS1115_MULTIPLEXER_P0_N1 = 0b000,
|
||||
ADS1115_MULTIPLEXER_P0_N3 = 0b001,
|
||||
ADS1115_MULTIPLEXER_P1_N3 = 0b010,
|
||||
ADS1115_MULTIPLEXER_P2_N3 = 0b011,
|
||||
ADS1115_MULTIPLEXER_P0_NG = 0b100,
|
||||
ADS1115_MULTIPLEXER_P1_NG = 0b101,
|
||||
ADS1115_MULTIPLEXER_P2_NG = 0b110,
|
||||
ADS1115_MULTIPLEXER_P3_NG = 0b111,
|
||||
};
|
||||
|
||||
enum ADS1115Gain {
|
||||
ADS1115_GAIN_6P144 = 0b000,
|
||||
ADS1115_GAIN_4P096 = 0b001,
|
||||
ADS1115_GAIN_2P048 = 0b010,
|
||||
ADS1115_GAIN_1P024 = 0b011,
|
||||
ADS1115_GAIN_0P512 = 0b100,
|
||||
ADS1115_GAIN_0P256 = 0b101,
|
||||
};
|
||||
|
||||
class ADS1115Sensor;
|
||||
|
||||
class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void register_sensor(ADS1115Sensor *obj) { this->sensors_.push_back(obj); }
|
||||
/// Set up the internal sensor array.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// HARDWARE_LATE setup priority
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
|
||||
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
float request_measurement(ADS1115Sensor *sensor);
|
||||
|
||||
protected:
|
||||
std::vector<ADS1115Sensor *> sensors_;
|
||||
uint16_t prev_config_{0};
|
||||
bool continuous_mode_;
|
||||
};
|
||||
|
||||
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||
class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
ADS1115Sensor(ADS1115Component *parent) : parent_(parent) {}
|
||||
void update() override;
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer) { multiplexer_ = multiplexer; }
|
||||
void set_gain(ADS1115Gain gain) { gain_ = gain; }
|
||||
|
||||
float sample() override;
|
||||
uint8_t get_multiplexer() const { return multiplexer_; }
|
||||
uint8_t get_gain() const { return gain_; }
|
||||
|
||||
protected:
|
||||
ADS1115Component *parent_;
|
||||
ADS1115Multiplexer multiplexer_;
|
||||
ADS1115Gain gain_;
|
||||
};
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
63
esphome/components/ads1115/sensor.py
Normal file
63
esphome/components/ads1115/sensor.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
|
||||
from esphome.py_compat import string_types
|
||||
from . import ads1115_ns, ADS1115Component
|
||||
|
||||
DEPENDENCIES = ['ads1115']
|
||||
|
||||
ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
|
||||
MUX = {
|
||||
'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
|
||||
'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
|
||||
'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
|
||||
'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
|
||||
'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
|
||||
'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
|
||||
'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
|
||||
'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
|
||||
}
|
||||
|
||||
ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
|
||||
GAIN = {
|
||||
'6.144': ADS1115Gain.ADS1115_GAIN_6P144,
|
||||
'4.096': ADS1115Gain.ADS1115_GAIN_4P096,
|
||||
'2.048': ADS1115Gain.ADS1115_GAIN_2P048,
|
||||
'1.024': ADS1115Gain.ADS1115_GAIN_1P024,
|
||||
'0.512': ADS1115Gain.ADS1115_GAIN_0P512,
|
||||
'0.256': ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
}
|
||||
|
||||
|
||||
def validate_gain(value):
|
||||
if isinstance(value, float):
|
||||
value = u'{:0.03f}'.format(value)
|
||||
elif not isinstance(value, string_types):
|
||||
raise cv.Invalid('invalid gain "{}"'.format(value))
|
||||
|
||||
return cv.enum(GAIN)(value)
|
||||
|
||||
|
||||
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler)
|
||||
|
||||
CONF_ADS1115_ID = 'ads1115_id'
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Sensor),
|
||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'),
|
||||
cv.Required(CONF_GAIN): validate_gain,
|
||||
}).extend(cv.polling_component_schema('60s'))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
paren = yield cg.get_variable(config[CONF_ADS1115_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], paren)
|
||||
yield sensor.register_sensor(var, config)
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
|
||||
cg.add(paren.register_sensor(var))
|
||||
0
esphome/components/am2320/__init__.py
Normal file
0
esphome/components/am2320/__init__.py
Normal file
107
esphome/components/am2320/am2320.cpp
Normal file
107
esphome/components/am2320/am2320.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// Implementation based on:
|
||||
// - ESPEasy: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P034_DHT12.ino
|
||||
// - DHT12_sensor_library: https://github.com/xreef/DHT12_sensor_library/blob/master/DHT12.cpp
|
||||
// - Arduino - AM2320: https://github.com/EngDial/AM2320/blob/master/src/AM2320.cpp
|
||||
|
||||
#include "am2320.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am2320 {
|
||||
|
||||
static const char *TAG = "am2320";
|
||||
|
||||
// ---=== Calc CRC16 ===---
|
||||
uint16_t crc_16(uint8_t *ptr, uint8_t length) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
uint8_t i;
|
||||
//------------------------------
|
||||
while (length--) {
|
||||
crc ^= *ptr++;
|
||||
for (i = 0; i < 8; i++)
|
||||
if ((crc & 0x01) != 0) {
|
||||
crc >>= 1;
|
||||
crc ^= 0xA001;
|
||||
} else
|
||||
crc >>= 1;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void AM2320Component::update() {
|
||||
uint8_t data[8];
|
||||
data[0] = 0;
|
||||
data[1] = 4;
|
||||
if (!this->read_data_(data)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0;
|
||||
temperature = (data[4] & 0x80) ? -temperature : temperature;
|
||||
float humidity = ((data[2] << 8) + data[3]) / 10.0;
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
void AM2320Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AM2320...");
|
||||
uint8_t data[8];
|
||||
data[0] = 0;
|
||||
data[1] = 4;
|
||||
if (!this->read_data_(data)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
void AM2320Component::dump_config() {
|
||||
ESP_LOGD(TAG, "AM2320:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with AM2320 failed!");
|
||||
}
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
}
|
||||
float AM2320Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) {
|
||||
if (!this->write_bytes(a_register, data, 2)) {
|
||||
ESP_LOGW(TAG, "Writing bytes for AM2320 failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (conversion > 0)
|
||||
delay(conversion);
|
||||
return this->parent_->raw_receive(this->address_, data, len);
|
||||
}
|
||||
|
||||
bool AM2320Component::read_data_(uint8_t *data) {
|
||||
// Wake up
|
||||
this->write_bytes(0, data, 0);
|
||||
|
||||
// Write instruction 3, 2 bytes, get 8 bytes back (2 preamble, 2 bytes temperature, 2 bytes humidity, 2 bytes CRC)
|
||||
if (!this->read_bytes_(3, data, 8, 2)) {
|
||||
ESP_LOGW(TAG, "Updating AM2320 failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t checksum;
|
||||
|
||||
checksum = data[7] << 8;
|
||||
checksum += data[6];
|
||||
|
||||
if (crc_16(data, 6) != checksum) {
|
||||
ESP_LOGW(TAG, "AM2320 Checksum invalid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace am2320
|
||||
} // namespace esphome
|
||||
29
esphome/components/am2320/am2320.h
Normal file
29
esphome/components/am2320/am2320.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am2320 {
|
||||
|
||||
class AM2320Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
protected:
|
||||
bool read_data_(uint8_t *data);
|
||||
bool read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0);
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
};
|
||||
|
||||
} // namespace am2320
|
||||
} // namespace esphome
|
||||
30
esphome/components/am2320/sensor.py
Normal file
30
esphome/components/am2320/sensor.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
am2320_ns = cg.esphome_ns.namespace('am2320')
|
||||
AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(AM2320Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
23
esphome/components/apds9960/__init__.py
Normal file
23
esphome/components/apds9960/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_APDS9960_ID = 'apds9960_id'
|
||||
|
||||
apds9960_nds = cg.esphome_ns.namespace('apds9960')
|
||||
APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(APDS9960),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
374
esphome/components/apds9960/apds9960.cpp
Normal file
374
esphome/components/apds9960/apds9960.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
#include "apds9960.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace apds9960 {
|
||||
|
||||
static const char *TAG = "apds9960";
|
||||
|
||||
#define APDS9960_ERROR_CHECK(func) \
|
||||
if (!func) { \
|
||||
this->mark_failed(); \
|
||||
return; \
|
||||
}
|
||||
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
|
||||
|
||||
void APDS9960::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up APDS9960...");
|
||||
uint8_t id;
|
||||
if (!this->read_byte(0x92, &id)) { // ID register
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != 0xAB && id != 0x9C) { // APDS9960 all should have one of these IDs
|
||||
this->error_code_ = WRONG_ID;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// ATime (ADC integration time, 2.78ms increments, 0x81) -> 0xDB (103ms)
|
||||
APDS9960_WRITE_BYTE(0x81, 0xDB);
|
||||
// WTime (Wait time, 0x83) -> 0xF6 (27ms)
|
||||
APDS9960_WRITE_BYTE(0x83, 0xF6);
|
||||
// PPulse (0x8E) -> 0x87 (16us, 8 pulses)
|
||||
APDS9960_WRITE_BYTE(0x8E, 0x87);
|
||||
// POffset UR (0x9D) -> 0 (no offset)
|
||||
APDS9960_WRITE_BYTE(0x9D, 0x00);
|
||||
// POffset DL (0x9E) -> 0 (no offset)
|
||||
APDS9960_WRITE_BYTE(0x9E, 0x00);
|
||||
// Config 1 (0x8D) -> 0x60 (no wtime factor)
|
||||
APDS9960_WRITE_BYTE(0x8D, 0x60);
|
||||
|
||||
// Control (0x8F) ->
|
||||
uint8_t val = 0;
|
||||
APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val));
|
||||
val &= 0b00111111;
|
||||
uint8_t led_drive = 0; // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
||||
val |= (led_drive & 0b11) << 6;
|
||||
|
||||
val &= 0b11110011;
|
||||
uint8_t proximity_gain = 2; // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 4 -> 8X
|
||||
val |= (proximity_gain & 0b11) << 2;
|
||||
|
||||
val &= 0b11111100;
|
||||
uint8_t ambient_gain = 1; // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x
|
||||
val |= (ambient_gain & 0b11) << 0;
|
||||
APDS9960_WRITE_BYTE(0x8F, val);
|
||||
|
||||
// Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt)
|
||||
APDS9960_WRITE_BYTE(0x8C, 0x11);
|
||||
// Config 2 (0x90) -> 0x01 (no saturation interrupts or LED boost)
|
||||
APDS9960_WRITE_BYTE(0x90, 0x01);
|
||||
// Config 3 (0x9F) -> 0x00 (enable all photodiodes, no SAI)
|
||||
APDS9960_WRITE_BYTE(0x9F, 0x00);
|
||||
// GPenTh (0xA0, gesture enter threshold) -> 0x28 (also 0x32)
|
||||
APDS9960_WRITE_BYTE(0xA0, 0x28);
|
||||
// GPexTh (0xA1, gesture exit threshold) -> 0x1E
|
||||
APDS9960_WRITE_BYTE(0xA1, 0x1E);
|
||||
|
||||
// GConf 1 (0xA2, gesture config 1) -> 0x40 (4 gesture events for interrupt (GFIFO 3), 1 for exit)
|
||||
APDS9960_WRITE_BYTE(0xA2, 0x40);
|
||||
|
||||
// GConf 2 (0xA3, gesture config 2) ->
|
||||
APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val));
|
||||
val &= 0b10011111;
|
||||
uint8_t gesture_gain = 2; // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x
|
||||
val |= (gesture_gain & 0b11) << 5;
|
||||
|
||||
val &= 0b11100111;
|
||||
uint8_t gesture_led_drive = 0; // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
||||
val |= (gesture_led_drive & 0b11) << 3;
|
||||
|
||||
val &= 0b11111000;
|
||||
// gesture wait time
|
||||
// 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms
|
||||
// 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms
|
||||
uint8_t gesture_wait_time = 1; // gesture wait time
|
||||
val |= (gesture_wait_time & 0b111) << 0;
|
||||
APDS9960_WRITE_BYTE(0xA3, val);
|
||||
|
||||
// GOffsetU (0xA4) -> 0x00 (no offset)
|
||||
APDS9960_WRITE_BYTE(0xA4, 0x00);
|
||||
// GOffsetD (0xA5) -> 0x00 (no offset)
|
||||
APDS9960_WRITE_BYTE(0xA5, 0x00);
|
||||
// GOffsetL (0xA7) -> 0x00 (no offset)
|
||||
APDS9960_WRITE_BYTE(0xA7, 0x00);
|
||||
// GOffsetR (0xA9) -> 0x00 (no offset)
|
||||
APDS9960_WRITE_BYTE(0xA9, 0x00);
|
||||
// GPulse (0xA6) -> 0xC9 (32 µs, 10 pulses)
|
||||
APDS9960_WRITE_BYTE(0xA6, 0xC9);
|
||||
|
||||
// GConf 3 (0xAA, gesture config 3) -> 0x00 (all photodiodes active during gesture, all gesture dimensions enabled)
|
||||
// 0x00 -> all dimensions, 0x01 -> up down, 0x02 -> left right
|
||||
APDS9960_WRITE_BYTE(0xAA, 0x00);
|
||||
|
||||
// Enable (0x80) ->
|
||||
val = 0;
|
||||
val |= (0b1) << 0; // power on
|
||||
val |= (this->is_color_enabled_() & 0b1) << 1;
|
||||
val |= (this->is_proximity_enabled_() & 0b1) << 2;
|
||||
val |= 0b0 << 3; // wait timer disabled
|
||||
val |= 0b0 << 4; // color interrupt disabled
|
||||
val |= 0b0 << 5; // proximity interrupt disabled
|
||||
val |= (this->is_gesture_enabled_() & 0b1) << 6; // proximity is required for gestures
|
||||
APDS9960_WRITE_BYTE(0x80, val);
|
||||
}
|
||||
bool APDS9960::is_color_enabled_() const {
|
||||
return this->red_channel_ != nullptr || this->green_channel_ != nullptr || this->blue_channel_ != nullptr ||
|
||||
this->clear_channel_ != nullptr;
|
||||
}
|
||||
|
||||
void APDS9960::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "APDS9960:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->is_failed()) {
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with APDS9960 failed!");
|
||||
break;
|
||||
case WRONG_ID:
|
||||
ESP_LOGE(TAG, "APDS9960 has invalid id!");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Setting up APDS9960 registers failed!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define APDS9960_WARNING_CHECK(func, warning) \
|
||||
if (!(func)) { \
|
||||
ESP_LOGW(TAG, warning); \
|
||||
this->status_set_warning(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
void APDS9960::update() {
|
||||
uint8_t status;
|
||||
APDS9960_WARNING_CHECK(this->read_byte(0x93, &status), "Reading status bit failed.");
|
||||
this->status_clear_warning();
|
||||
|
||||
this->read_color_data_(status);
|
||||
this->read_proximity_data_(status);
|
||||
}
|
||||
|
||||
void APDS9960::loop() { this->read_gesture_data_(); }
|
||||
|
||||
void APDS9960::read_color_data_(uint8_t status) {
|
||||
if (!this->is_color_enabled_())
|
||||
return;
|
||||
|
||||
if ((status & 0x01) == 0x00) {
|
||||
// color data not ready yet.
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t raw[8];
|
||||
APDS9960_WARNING_CHECK(this->read_bytes(0x94, raw, 8), "Reading color values failed.");
|
||||
|
||||
uint16_t uint_clear = (uint16_t(raw[1]) << 8) | raw[0];
|
||||
uint16_t uint_red = (uint16_t(raw[3]) << 8) | raw[2];
|
||||
uint16_t uint_green = (uint16_t(raw[5]) << 8) | raw[4];
|
||||
uint16_t uint_blue = (uint16_t(raw[7]) << 8) | raw[6];
|
||||
|
||||
float clear_perc = (uint_clear / float(UINT16_MAX)) * 100.0f;
|
||||
float red_perc = (uint_red / float(UINT16_MAX)) * 100.0f;
|
||||
float green_perc = (uint_green / float(UINT16_MAX)) * 100.0f;
|
||||
float blue_perc = (uint_blue / float(UINT16_MAX)) * 100.0f;
|
||||
|
||||
ESP_LOGD(TAG, "Got clear=%.1f%% red=%.1f%% green=%.1f%% blue=%.1f%%", clear_perc, red_perc, green_perc, blue_perc);
|
||||
if (this->clear_channel_ != nullptr)
|
||||
this->clear_channel_->publish_state(clear_perc);
|
||||
if (this->red_channel_ != nullptr)
|
||||
this->red_channel_->publish_state(red_perc);
|
||||
if (this->green_channel_ != nullptr)
|
||||
this->green_channel_->publish_state(green_perc);
|
||||
if (this->blue_channel_ != nullptr)
|
||||
this->blue_channel_->publish_state(blue_perc);
|
||||
}
|
||||
void APDS9960::read_proximity_data_(uint8_t status) {
|
||||
if (this->proximity_ == nullptr)
|
||||
return;
|
||||
|
||||
if ((status & 0b10) == 0x00) {
|
||||
// proximity data not ready yet.
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t prox;
|
||||
APDS9960_WARNING_CHECK(this->read_byte(0x9C, &prox), "Reading proximity values failed.");
|
||||
|
||||
float prox_perc = (prox / float(UINT8_MAX)) * 100.0f;
|
||||
ESP_LOGD(TAG, "Got proximity=%.1f%%", prox_perc);
|
||||
this->proximity_->publish_state(prox_perc);
|
||||
}
|
||||
void APDS9960::read_gesture_data_() {
|
||||
if (!this->is_gesture_enabled_())
|
||||
return;
|
||||
|
||||
uint8_t status;
|
||||
APDS9960_WARNING_CHECK(this->read_byte(0xAF, &status), "Reading gesture status failed.");
|
||||
|
||||
if ((status & 0b01) == 0) {
|
||||
// GVALID is false
|
||||
return;
|
||||
}
|
||||
|
||||
if ((status & 0b10) == 0b10) {
|
||||
ESP_LOGV(TAG, "FIFO buffer has filled to capacity!");
|
||||
}
|
||||
|
||||
uint8_t fifo_level;
|
||||
APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed.");
|
||||
if (fifo_level == 0)
|
||||
// no data to process
|
||||
return;
|
||||
|
||||
APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.")
|
||||
|
||||
uint8_t buf[128];
|
||||
for (uint8_t pos = 0; pos < fifo_level * 4; pos += 32) {
|
||||
// The ESP's i2c driver has a limited buffer size.
|
||||
// This way of retrieving the data should be wrong according to the datasheet
|
||||
// but it seems to work.
|
||||
uint8_t read = std::min(32, fifo_level * 4 - pos);
|
||||
APDS9960_WARNING_CHECK(this->read_bytes(0xFC + pos, buf + pos, read), "Reading FIFO buffer failed.");
|
||||
}
|
||||
|
||||
if (millis() - this->gesture_start_ > 500) {
|
||||
this->gesture_up_started_ = false;
|
||||
this->gesture_down_started_ = false;
|
||||
this->gesture_left_started_ = false;
|
||||
this->gesture_right_started_ = false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < fifo_level * 4; i += 4) {
|
||||
const int up = buf[i + 0]; // NOLINT
|
||||
const int down = buf[i + 1];
|
||||
const int left = buf[i + 2];
|
||||
const int right = buf[i + 3];
|
||||
this->process_dataset_(up, down, left, right);
|
||||
}
|
||||
}
|
||||
void APDS9960::report_gesture_(int gesture) {
|
||||
binary_sensor::BinarySensor *bin;
|
||||
switch (gesture) {
|
||||
case 1:
|
||||
bin = this->up_direction_;
|
||||
this->gesture_up_started_ = false;
|
||||
this->gesture_down_started_ = false;
|
||||
ESP_LOGD(TAG, "Got gesture UP");
|
||||
break;
|
||||
case 2:
|
||||
bin = this->down_direction_;
|
||||
this->gesture_up_started_ = false;
|
||||
this->gesture_down_started_ = false;
|
||||
ESP_LOGD(TAG, "Got gesture DOWN");
|
||||
break;
|
||||
case 3:
|
||||
bin = this->left_direction_;
|
||||
this->gesture_left_started_ = false;
|
||||
this->gesture_right_started_ = false;
|
||||
ESP_LOGD(TAG, "Got gesture LEFT");
|
||||
break;
|
||||
case 4:
|
||||
bin = this->right_direction_;
|
||||
this->gesture_left_started_ = false;
|
||||
this->gesture_right_started_ = false;
|
||||
ESP_LOGD(TAG, "Got gesture RIGHT");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (bin != nullptr) {
|
||||
bin->publish_state(true);
|
||||
bin->publish_state(false);
|
||||
}
|
||||
}
|
||||
void APDS9960::process_dataset_(int up, int down, int left, int right) {
|
||||
/* Algorithm: (see Figure 11 in datasheet)
|
||||
*
|
||||
* Observation: When a gesture is started, we will see a short amount of time where
|
||||
* the photodiode in the direction of the motion has a much higher count value
|
||||
* than where the gesture originates.
|
||||
*
|
||||
* In this algorithm we continually check the difference between the count values of opposing
|
||||
* directions. For example in the down/up direction we continually look at the difference of the
|
||||
* up count and down count. When DOWN gesture begins, this difference will be positive with a
|
||||
* high magnitude for a short amount of time (magic value here is the difference is at least 13).
|
||||
*
|
||||
* If we see such a pattern, we store that we saw the first part of a gesture (the leading edge).
|
||||
* After that some time can pass during which the difference is zero again (though the count values
|
||||
* are not zero). At the end of a gesture, we will see this difference go into the opposite direction
|
||||
* for a short period of time.
|
||||
*
|
||||
* If a gesture is not ended within 500 milliseconds, we consider the initial trailing edge invalid
|
||||
* and reset the state.
|
||||
*
|
||||
* This algorithm does work, but not too well. Some good signal processing algorithms could
|
||||
* probably improve this a lot, especially since the incoming signal has such a characteristic
|
||||
* and quite noise-free pattern.
|
||||
*/
|
||||
const int up_down_delta = up - down;
|
||||
const int left_right_delta = left - right;
|
||||
const bool up_down_significant = abs(up_down_delta) > 13;
|
||||
const bool left_right_significant = abs(left_right_delta) > 13;
|
||||
|
||||
if (up_down_significant) {
|
||||
if (up_down_delta < 0) {
|
||||
if (this->gesture_up_started_) {
|
||||
// trailing edge of gesture up
|
||||
this->report_gesture_(1); // UP
|
||||
} else {
|
||||
// leading edge of gesture down
|
||||
this->gesture_down_started_ = true;
|
||||
this->gesture_start_ = millis();
|
||||
}
|
||||
} else {
|
||||
if (this->gesture_down_started_) {
|
||||
// trailing edge of gesture down
|
||||
this->report_gesture_(2); // DOWN
|
||||
} else {
|
||||
// leading edge of gesture up
|
||||
this->gesture_up_started_ = true;
|
||||
this->gesture_start_ = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left_right_significant) {
|
||||
if (left_right_delta < 0) {
|
||||
if (this->gesture_left_started_) {
|
||||
// trailing edge of gesture left
|
||||
this->report_gesture_(3); // LEFT
|
||||
} else {
|
||||
// leading edge of gesture right
|
||||
this->gesture_right_started_ = true;
|
||||
this->gesture_start_ = millis();
|
||||
}
|
||||
} else {
|
||||
if (this->gesture_right_started_) {
|
||||
// trailing edge of gesture right
|
||||
this->report_gesture_(4); // RIGHT
|
||||
} else {
|
||||
// leading edge of gesture left
|
||||
this->gesture_left_started_ = true;
|
||||
this->gesture_start_ = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
float APDS9960::get_setup_priority() const { return setup_priority::DATA; }
|
||||
bool APDS9960::is_proximity_enabled_() const { return this->proximity_ != nullptr || this->is_gesture_enabled_(); }
|
||||
bool APDS9960::is_gesture_enabled_() const {
|
||||
return this->up_direction_ != nullptr || this->left_direction_ != nullptr || this->down_direction_ != nullptr ||
|
||||
this->right_direction_ != nullptr;
|
||||
}
|
||||
|
||||
} // namespace apds9960
|
||||
} // namespace esphome
|
||||
61
esphome/components/apds9960/apds9960.h
Normal file
61
esphome/components/apds9960/apds9960.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace apds9960 {
|
||||
|
||||
class APDS9960 : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
|
||||
void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; }
|
||||
void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; }
|
||||
void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; }
|
||||
void set_clear_channel(sensor::Sensor *clear_channel) { clear_channel_ = clear_channel; }
|
||||
void set_up_direction(binary_sensor::BinarySensor *up_direction) { up_direction_ = up_direction; }
|
||||
void set_right_direction(binary_sensor::BinarySensor *right_direction) { right_direction_ = right_direction; }
|
||||
void set_down_direction(binary_sensor::BinarySensor *down_direction) { down_direction_ = down_direction; }
|
||||
void set_left_direction(binary_sensor::BinarySensor *left_direction) { left_direction_ = left_direction; }
|
||||
void set_proximity(sensor::Sensor *proximity) { proximity_ = proximity; }
|
||||
|
||||
protected:
|
||||
bool is_color_enabled_() const;
|
||||
bool is_proximity_enabled_() const;
|
||||
bool is_gesture_enabled_() const;
|
||||
void read_color_data_(uint8_t status);
|
||||
void read_proximity_data_(uint8_t status);
|
||||
void read_gesture_data_();
|
||||
void report_gesture_(int gesture);
|
||||
void process_dataset_(int up, int down, int left, int right);
|
||||
|
||||
sensor::Sensor *red_channel_{nullptr};
|
||||
sensor::Sensor *green_channel_{nullptr};
|
||||
sensor::Sensor *blue_channel_{nullptr};
|
||||
sensor::Sensor *clear_channel_{nullptr};
|
||||
binary_sensor::BinarySensor *up_direction_{nullptr};
|
||||
binary_sensor::BinarySensor *right_direction_{nullptr};
|
||||
binary_sensor::BinarySensor *down_direction_{nullptr};
|
||||
binary_sensor::BinarySensor *left_direction_{nullptr};
|
||||
sensor::Sensor *proximity_{nullptr};
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
WRONG_ID,
|
||||
} error_code_{NONE};
|
||||
bool gesture_up_started_{false};
|
||||
bool gesture_down_started_{false};
|
||||
bool gesture_left_started_{false};
|
||||
bool gesture_right_started_{false};
|
||||
uint32_t gesture_start_{0};
|
||||
};
|
||||
|
||||
} // namespace apds9960
|
||||
} // namespace esphome
|
||||
27
esphome/components/apds9960/binary_sensor.py
Normal file
27
esphome/components/apds9960/binary_sensor.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING
|
||||
from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ['apds9960']
|
||||
|
||||
DIRECTIONS = {
|
||||
'UP': 'set_up_direction',
|
||||
'DOWN': 'set_down_direction',
|
||||
'LEFT': 'set_left_direction',
|
||||
'RIGHT': 'set_right_direction',
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class,
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
|
||||
var = yield binary_sensor.new_binary_sensor(config)
|
||||
func = getattr(hub, DIRECTIONS[config[CONF_DIRECTION]])
|
||||
cg.add(func(var))
|
||||
27
esphome/components/apds9960/sensor.py
Normal file
27
esphome/components/apds9960/sensor.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB
|
||||
from . import APDS9960, CONF_APDS9960_ID
|
||||
|
||||
DEPENDENCIES = ['apds9960']
|
||||
|
||||
TYPES = {
|
||||
'CLEAR': 'set_clear_channel',
|
||||
'RED': 'set_red_channel',
|
||||
'GREEN': 'set_green_channel',
|
||||
'BLUE': 'set_blue_channel',
|
||||
'PROXIMITY': 'set_proximity',
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({
|
||||
cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
|
||||
cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_APDS9960_ID])
|
||||
var = yield sensor.new_sensor(config)
|
||||
func = getattr(hub, TYPES[config[CONF_TYPE]])
|
||||
cg.add(func(var))
|
||||
141
esphome/components/api/__init__.py
Normal file
141
esphome/components/api/__init__.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
||||
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
DEPENDENCIES = ['network']
|
||||
AUTO_LOAD = ['async_tcp']
|
||||
|
||||
api_ns = cg.esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
||||
|
||||
UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
|
||||
ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
|
||||
SERVICE_ARG_NATIVE_TYPES = {
|
||||
'bool': bool,
|
||||
'int': cg.int32,
|
||||
'float': float,
|
||||
'string': cg.std_string,
|
||||
'bool[]': cg.std_vector.template(bool),
|
||||
'int[]': cg.std_vector.template(cg.int32),
|
||||
'float[]': cg.std_vector.template(float),
|
||||
'string[]': cg.std_vector.template(cg.std_string),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
|
||||
}),
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
for conf in config.get(CONF_SERVICES, []):
|
||||
template_args = []
|
||||
func_args = []
|
||||
service_arg_names = []
|
||||
for name, var_ in conf[CONF_VARIABLES].items():
|
||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||
template_args.append(native)
|
||||
func_args.append((native, name))
|
||||
service_arg_names.append(name)
|
||||
templ = cg.TemplateArguments(*template_args)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
||||
conf[CONF_SERVICE], service_arg_names)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
yield automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
cg.add_define('USE_API')
|
||||
cg.add_global(api_ns.using)
|
||||
|
||||
|
||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string)})
|
||||
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
||||
})
|
||||
|
||||
|
||||
@automation.register_action('homeassistant.service', HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||
templ = yield cg.templatable(config[CONF_SERVICE], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data(key, templ))
|
||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data_template(key, templ))
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_variable(key, templ))
|
||||
yield var
|
||||
|
||||
|
||||
def validate_homeassistant_event(value):
|
||||
value = cv.string(value)
|
||||
if not value.startswith(u'esphome.'):
|
||||
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
|
||||
"esphome. For example 'esphome.xyz'")
|
||||
return value
|
||||
|
||||
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
||||
})
|
||||
|
||||
|
||||
@automation.register_action('homeassistant.event', HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA)
|
||||
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
templ = yield cg.templatable(config[CONF_EVENT], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data(key, templ))
|
||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data_template(key, templ))
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_variable(key, templ))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_condition('api.connected', APIConnectedCondition, {})
|
||||
def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
yield cg.new_Pvariable(condition_id, template_arg)
|
||||
705
esphome/components/api/api.proto
Normal file
705
esphome/components/api/api.proto
Normal file
@@ -0,0 +1,705 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "api_options.proto";
|
||||
|
||||
service APIConnection {
|
||||
rpc hello (HelloRequest) returns (HelloResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc connect (ConnectRequest) returns (ConnectResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc ping (PingRequest) returns (PingResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc device_info (DeviceInfoRequest) returns (DeviceInfoResponse) {
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc list_entities (ListEntitiesRequest) returns (void) {}
|
||||
rpc subscribe_states (SubscribeStatesRequest) returns (void) {}
|
||||
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
||||
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
||||
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
||||
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
||||
|
||||
rpc cover_command (CoverCommandRequest) returns (void) {}
|
||||
rpc fan_command (FanCommandRequest) returns (void) {}
|
||||
rpc light_command (LightCommandRequest) returns (void) {}
|
||||
rpc switch_command (SwitchCommandRequest) returns (void) {}
|
||||
rpc camera_image (CameraImageRequest) returns (void) {}
|
||||
rpc climate_command (ClimateCommandRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
// ==================== BASE PACKETS ====================
|
||||
|
||||
// The Home Assistant protocol is structured as a simple
|
||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
||||
// First, a message in this protocol has a specific format:
|
||||
// * VarInt denoting the size of the message object. (type is not part of this)
|
||||
// * VarInt denoting the type of message.
|
||||
// * The message object encoded as a ProtoBuf message
|
||||
|
||||
// The connection is established in 4 steps:
|
||||
// * First, the client connects to the server and sends a "Hello Request" identifying itself
|
||||
// * The server responds with a "Hello Response" and selects the protocol version
|
||||
// * After receiving this message, the client attempts to authenticate itself using
|
||||
// the password and a "Connect Request"
|
||||
// * The server responds with a "Connect Response" and notifies of invalid password.
|
||||
// If anything in this initial process fails, the connection must immediately closed
|
||||
// by both sides and _no_ disconnection message is to be sent.
|
||||
|
||||
// Message sent at the beginning of each connection
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message HelloRequest {
|
||||
option (id) = 1;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Description of client (like User Agent)
|
||||
// For example "Home Assistant"
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message HelloResponse {
|
||||
option (id) = 2;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
||||
// for compatibility and if necessary adopt to an older API.
|
||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
||||
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
|
||||
uint32 api_version_major = 1;
|
||||
uint32 api_version_minor = 2;
|
||||
|
||||
// A string identifying the server (ESP); like client info this may be empty
|
||||
// and only exists for debugging/logging purposes.
|
||||
// For example "ESPHome v1.10.0 on ESP8266"
|
||||
string server_info = 3;
|
||||
}
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message ConnectRequest {
|
||||
option (id) = 3;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
// The password to log in with
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message ConnectResponse {
|
||||
option (id) = 4;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
bool invalid_password = 1;
|
||||
}
|
||||
|
||||
// Request to close the connection.
|
||||
// Can be sent by both the client and server
|
||||
message DisconnectRequest {
|
||||
option (id) = 5;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Do not close the connection before the acknowledgement arrives
|
||||
}
|
||||
|
||||
message DisconnectResponse {
|
||||
option (id) = 6;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Empty - Both parties are required to close the connection after this
|
||||
// message has been received.
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
option (id) = 7;
|
||||
option (source) = SOURCE_BOTH;
|
||||
// Empty
|
||||
}
|
||||
|
||||
message PingResponse {
|
||||
option (id) = 8;
|
||||
option (source) = SOURCE_BOTH;
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoRequest {
|
||||
option (id) = 9;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoResponse {
|
||||
option (id) = 10;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
bool uses_password = 1;
|
||||
|
||||
// The name of the node, given by "App.set_name()"
|
||||
string name = 2;
|
||||
|
||||
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
|
||||
string mac_address = 3;
|
||||
|
||||
// A string describing the ESPHome version. For example "1.10.0"
|
||||
string esphome_version = 4;
|
||||
|
||||
// A string describing the date of compilation, this is generated by the compiler
|
||||
// and therefore may not be in the same format all the time.
|
||||
// If the user isn't using ESPHome, this will also not be set.
|
||||
string compilation_time = 5;
|
||||
|
||||
// The model of the board. For example NodeMCU
|
||||
string model = 6;
|
||||
|
||||
bool has_deep_sleep = 7;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
option (id) = 11;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
message ListEntitiesDoneResponse {
|
||||
option (id) = 19;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
// Empty
|
||||
}
|
||||
message SubscribeStatesRequest {
|
||||
option (id) = 20;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ==================== BINARY SENSOR ====================
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
option (id) = 12;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
// ==================== COVER ====================
|
||||
message ListEntitiesCoverResponse {
|
||||
option (id) = 13;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_COVER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool assumed_state = 5;
|
||||
bool supports_position = 6;
|
||||
bool supports_tilt = 7;
|
||||
string device_class = 8;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
LEGACY_COVER_STATE_OPEN = 0;
|
||||
LEGACY_COVER_STATE_CLOSED = 1;
|
||||
}
|
||||
enum CoverOperation {
|
||||
COVER_OPERATION_IDLE = 0;
|
||||
COVER_OPERATION_IS_OPENING = 1;
|
||||
COVER_OPERATION_IS_CLOSING = 2;
|
||||
}
|
||||
message CoverStateResponse {
|
||||
option (id) = 22;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
// legacy: state has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
LegacyCoverState legacy_state = 2;
|
||||
|
||||
float position = 3;
|
||||
float tilt = 4;
|
||||
CoverOperation current_operation = 5;
|
||||
}
|
||||
|
||||
enum LegacyCoverCommand {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0;
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1;
|
||||
LEGACY_COVER_COMMAND_STOP = 2;
|
||||
}
|
||||
message CoverCommandRequest {
|
||||
option (id) = 30;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
|
||||
// legacy: command has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
bool has_legacy_command = 2;
|
||||
LegacyCoverCommand legacy_command = 3;
|
||||
|
||||
bool has_position = 4;
|
||||
float position = 5;
|
||||
bool has_tilt = 6;
|
||||
float tilt = 7;
|
||||
bool stop = 8;
|
||||
}
|
||||
|
||||
// ==================== FAN ====================
|
||||
message ListEntitiesFanResponse {
|
||||
option (id) = 14;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_FAN";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
FAN_SPEED_MEDIUM = 1;
|
||||
FAN_SPEED_HIGH = 2;
|
||||
}
|
||||
message FanStateResponse {
|
||||
option (id) = 23;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
option (id) = 31;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
message ListEntitiesLightResponse {
|
||||
option (id) = 15;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_brightness = 5;
|
||||
bool supports_rgb = 6;
|
||||
bool supports_white_value = 7;
|
||||
bool supports_color_temperature = 8;
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
float white = 7;
|
||||
float color_temperature = 8;
|
||||
string effect = 9;
|
||||
}
|
||||
message LightCommandRequest {
|
||||
option (id) = 32;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_rgb = 6;
|
||||
float red = 7;
|
||||
float green = 8;
|
||||
float blue = 9;
|
||||
bool has_white = 10;
|
||||
float white = 11;
|
||||
bool has_color_temperature = 12;
|
||||
float color_temperature = 13;
|
||||
bool has_transition_length = 14;
|
||||
uint32 transition_length = 15;
|
||||
bool has_flash_length = 16;
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19;
|
||||
}
|
||||
|
||||
// ==================== SENSOR ====================
|
||||
message ListEntitiesSensorResponse {
|
||||
option (id) = 16;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
bool force_update = 8;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
float state = 2;
|
||||
}
|
||||
|
||||
// ==================== SWITCH ====================
|
||||
message ListEntitiesSwitchResponse {
|
||||
option (id) = 17;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool assumed_state = 6;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
message SwitchCommandRequest {
|
||||
option (id) = 33;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
// ==================== TEXT SENSOR ====================
|
||||
message ListEntitiesTextSensorResponse {
|
||||
option (id) = 18;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
// ==================== SUBSCRIBE LOGS ====================
|
||||
enum LogLevel {
|
||||
LOG_LEVEL_NONE = 0;
|
||||
LOG_LEVEL_ERROR = 1;
|
||||
LOG_LEVEL_WARN = 2;
|
||||
LOG_LEVEL_INFO = 3;
|
||||
LOG_LEVEL_DEBUG = 4;
|
||||
LOG_LEVEL_VERBOSE = 5;
|
||||
LOG_LEVEL_VERY_VERBOSE = 6;
|
||||
}
|
||||
message SubscribeLogsRequest {
|
||||
option (id) = 28;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
LogLevel level = 1;
|
||||
bool dump_config = 2;
|
||||
}
|
||||
message SubscribeLogsResponse {
|
||||
option (id) = 29;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (log) = false;
|
||||
option (no_delay) = false;
|
||||
|
||||
LogLevel level = 1;
|
||||
string tag = 2;
|
||||
string message = 3;
|
||||
bool send_failed = 4;
|
||||
}
|
||||
|
||||
// ==================== HOMEASSISTANT.SERVICE ====================
|
||||
message SubscribeHomeassistantServicesRequest {
|
||||
option (id) = 34;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
}
|
||||
|
||||
message HomeassistantServiceMap {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message HomeassistantServiceResponse {
|
||||
option (id) = 35;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
string service = 1;
|
||||
repeated HomeassistantServiceMap data = 2;
|
||||
repeated HomeassistantServiceMap data_template = 3;
|
||||
repeated HomeassistantServiceMap variables = 4;
|
||||
bool is_event = 5;
|
||||
}
|
||||
|
||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
||||
message SubscribeHomeAssistantStatesRequest {
|
||||
option (id) = 38;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
}
|
||||
|
||||
message SubscribeHomeAssistantStateResponse {
|
||||
option (id) = 39;
|
||||
option (source) = SOURCE_SERVER;
|
||||
string entity_id = 1;
|
||||
}
|
||||
|
||||
message HomeAssistantStateResponse {
|
||||
option (id) = 40;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
// ==================== IMPORT TIME ====================
|
||||
message GetTimeRequest {
|
||||
option (id) = 36;
|
||||
option (source) = SOURCE_BOTH;
|
||||
}
|
||||
|
||||
message GetTimeResponse {
|
||||
option (id) = 37;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 epoch_seconds = 1;
|
||||
}
|
||||
|
||||
// ==================== USER-DEFINES SERVICES ====================
|
||||
enum ServiceArgType {
|
||||
SERVICE_ARG_TYPE_BOOL = 0;
|
||||
SERVICE_ARG_TYPE_INT = 1;
|
||||
SERVICE_ARG_TYPE_FLOAT = 2;
|
||||
SERVICE_ARG_TYPE_STRING = 3;
|
||||
SERVICE_ARG_TYPE_BOOL_ARRAY = 4;
|
||||
SERVICE_ARG_TYPE_INT_ARRAY = 5;
|
||||
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6;
|
||||
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
|
||||
}
|
||||
message ListEntitiesServicesArgument {
|
||||
string name = 1;
|
||||
ServiceArgType type = 2;
|
||||
}
|
||||
message ListEntitiesServicesResponse {
|
||||
option (id) = 41;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
string name = 1;
|
||||
fixed32 key = 2;
|
||||
repeated ListEntitiesServicesArgument args = 3;
|
||||
}
|
||||
message ExecuteServiceArgument {
|
||||
bool bool_ = 1;
|
||||
int32 legacy_int = 2;
|
||||
float float_ = 3;
|
||||
string string_ = 4;
|
||||
// ESPHome 1.14 (api v1.3) make int a signed value
|
||||
sint32 int_ = 5;
|
||||
repeated bool bool_array = 6 [packed=false];
|
||||
repeated sint32 int_array = 7 [packed=false];
|
||||
repeated float float_array = 8 [packed=false];
|
||||
repeated string string_array = 9;
|
||||
}
|
||||
message ExecuteServiceRequest {
|
||||
option (id) = 42;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
repeated ExecuteServiceArgument args = 2;
|
||||
}
|
||||
|
||||
// ==================== CAMERA ====================
|
||||
message ListEntitiesCameraResponse {
|
||||
option (id) = 43;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
}
|
||||
|
||||
message CameraImageResponse {
|
||||
option (id) = 44;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
|
||||
fixed32 key = 1;
|
||||
bytes data = 2;
|
||||
bool done = 3;
|
||||
}
|
||||
message CameraImageRequest {
|
||||
option (id) = 45;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
option (no_delay) = true;
|
||||
|
||||
bool single = 1;
|
||||
bool stream = 2;
|
||||
}
|
||||
|
||||
// ==================== CLIMATE ====================
|
||||
enum ClimateMode {
|
||||
CLIMATE_MODE_OFF = 0;
|
||||
CLIMATE_MODE_AUTO = 1;
|
||||
CLIMATE_MODE_COOL = 2;
|
||||
CLIMATE_MODE_HEAT = 3;
|
||||
}
|
||||
enum ClimateAction {
|
||||
CLIMATE_ACTION_OFF = 0;
|
||||
// values same as mode for readability
|
||||
CLIMATE_ACTION_COOLING = 2;
|
||||
CLIMATE_ACTION_HEATING = 3;
|
||||
}
|
||||
message ListEntitiesClimateResponse {
|
||||
option (id) = 46;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_current_temperature = 5;
|
||||
bool supports_two_point_target_temperature = 6;
|
||||
repeated ClimateMode supported_modes = 7;
|
||||
float visual_min_temperature = 8;
|
||||
float visual_max_temperature = 9;
|
||||
float visual_temperature_step = 10;
|
||||
bool supports_away = 11;
|
||||
bool supports_action = 12;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
ClimateMode mode = 2;
|
||||
float current_temperature = 3;
|
||||
float target_temperature = 4;
|
||||
float target_temperature_low = 5;
|
||||
float target_temperature_high = 6;
|
||||
bool away = 7;
|
||||
ClimateAction action = 8;
|
||||
}
|
||||
message ClimateCommandRequest {
|
||||
option (id) = 48;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_mode = 2;
|
||||
ClimateMode mode = 3;
|
||||
bool has_target_temperature = 4;
|
||||
float target_temperature = 5;
|
||||
bool has_target_temperature_low = 6;
|
||||
float target_temperature_low = 7;
|
||||
bool has_target_temperature_high = 8;
|
||||
float target_temperature_high = 9;
|
||||
bool has_away = 10;
|
||||
bool away = 11;
|
||||
}
|
||||
673
esphome/components/api/api_connection.cpp
Normal file
673
esphome/components/api/api_connection.cpp
Normal file
@@ -0,0 +1,673 @@
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.connection";
|
||||
|
||||
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
|
||||
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
|
||||
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
|
||||
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
|
||||
this);
|
||||
this->client_->onData([](void *s, AsyncClient *c, void *buf,
|
||||
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
|
||||
this);
|
||||
|
||||
this->send_buffer_.reserve(64);
|
||||
this->recv_buffer_.reserve(32);
|
||||
this->client_info_ = this->client_->remoteIP().toString().c_str();
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
APIConnection::~APIConnection() { delete this->client_; }
|
||||
void APIConnection::on_error_(int8_t error) { this->remove_ = true; }
|
||||
void APIConnection::on_disconnect_() { this->remove_ = true; }
|
||||
void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); }
|
||||
void APIConnection::on_data_(uint8_t *buf, size_t len) {
|
||||
if (len == 0 || buf == nullptr)
|
||||
return;
|
||||
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
|
||||
}
|
||||
void APIConnection::parse_recv_buffer_() {
|
||||
if (this->recv_buffer_.empty() || this->remove_)
|
||||
return;
|
||||
|
||||
while (!this->recv_buffer_.empty()) {
|
||||
if (this->recv_buffer_[0] != 0x00) {
|
||||
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
uint32_t i = 1;
|
||||
const uint32_t size = this->recv_buffer_.size();
|
||||
uint32_t consumed;
|
||||
auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
|
||||
if (!msg_size_varint.has_value())
|
||||
// not enough data there yet
|
||||
return;
|
||||
i += consumed;
|
||||
uint32_t msg_size = msg_size_varint->as_uint32();
|
||||
|
||||
auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
|
||||
if (!msg_type_varint.has_value())
|
||||
// not enough data there yet
|
||||
return;
|
||||
i += consumed;
|
||||
uint32_t msg_type = msg_type_varint->as_uint32();
|
||||
|
||||
if (size - i < msg_size)
|
||||
// message body not fully received
|
||||
return;
|
||||
|
||||
uint8_t *msg = &this->recv_buffer_[i];
|
||||
this->read_message(msg_size, msg_type, msg);
|
||||
if (this->remove_)
|
||||
return;
|
||||
// pop front
|
||||
uint32_t total = i + msg_size;
|
||||
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::disconnect_client() {
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->remove_)
|
||||
return;
|
||||
|
||||
if (this->next_close_) {
|
||||
this->disconnect_client();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!network_is_connected()) {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
if (this->client_->disconnected()) {
|
||||
// failsafe for disconnect logic
|
||||
this->on_disconnect_();
|
||||
return;
|
||||
}
|
||||
this->parse_recv_buffer_();
|
||||
|
||||
this->list_entities_iterator_.advance();
|
||||
this->initial_state_iterator_.advance();
|
||||
|
||||
const uint32_t keepalive = 60000;
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (millis() - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
this->disconnect_client();
|
||||
}
|
||||
} else if (millis() - this->last_traffic_ > keepalive) {
|
||||
this->sent_ping_ = true;
|
||||
this->send_ping_request(PingRequest());
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (this->image_reader_.available()) {
|
||||
uint32_t space = this->client_->space();
|
||||
// reserve 15 bytes for metadata, and at least 64 bytes of data
|
||||
if (space >= 15 + 64) {
|
||||
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
|
||||
auto buffer = this->create_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
|
||||
// bytes data = 2;
|
||||
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
|
||||
// bool done = 3;
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
buffer.encode_bool(3, done);
|
||||
this->set_nodelay(false);
|
||||
bool success = this->send_buffer(buffer, 44);
|
||||
|
||||
if (success) {
|
||||
this->image_reader_.consume_data(to_send);
|
||||
}
|
||||
if (success && done) {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
BinarySensorStateResponse resp;
|
||||
resp.key = binary_sensor->get_object_id_hash();
|
||||
resp.state = state;
|
||||
return this->send_binary_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
|
||||
ListEntitiesBinarySensorResponse msg;
|
||||
msg.object_id = binary_sensor->get_object_id();
|
||||
msg.key = binary_sensor->get_object_id_hash();
|
||||
msg.name = binary_sensor->get_name();
|
||||
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
|
||||
msg.device_class = binary_sensor->get_device_class();
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
return this->send_list_entities_binary_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = cover->get_traits();
|
||||
CoverStateResponse resp{};
|
||||
resp.key = cover->get_object_id_hash();
|
||||
resp.legacy_state = (cover->position == cover::COVER_OPEN) ? LEGACY_COVER_STATE_OPEN : LEGACY_COVER_STATE_CLOSED;
|
||||
resp.position = cover->position;
|
||||
if (traits.get_supports_tilt())
|
||||
resp.tilt = cover->tilt;
|
||||
resp.current_operation = static_cast<EnumCoverOperation>(cover->current_operation);
|
||||
return this->send_cover_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
auto traits = cover->get_traits();
|
||||
ListEntitiesCoverResponse msg;
|
||||
msg.key = cover->get_object_id_hash();
|
||||
msg.object_id = cover->get_object_id();
|
||||
msg.name = cover->get_name();
|
||||
msg.unique_id = get_default_unique_id("cover", cover);
|
||||
msg.assumed_state = traits.get_is_assumed_state();
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
msg.supports_tilt = traits.get_supports_tilt();
|
||||
msg.device_class = cover->get_device_class();
|
||||
return this->send_list_entities_cover_response(msg);
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
cover::Cover *cover = App.get_cover_by_key(msg.key);
|
||||
if (cover == nullptr)
|
||||
return;
|
||||
|
||||
auto call = cover->make_call();
|
||||
if (msg.has_legacy_command) {
|
||||
switch (msg.legacy_command) {
|
||||
case LEGACY_COVER_COMMAND_OPEN:
|
||||
call.set_command_open();
|
||||
break;
|
||||
case LEGACY_COVER_COMMAND_CLOSE:
|
||||
call.set_command_close();
|
||||
break;
|
||||
case LEGACY_COVER_COMMAND_STOP:
|
||||
call.set_command_stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (msg.has_position)
|
||||
call.set_position(msg.position);
|
||||
if (msg.has_tilt)
|
||||
call.set_tilt(msg.tilt);
|
||||
if (msg.stop)
|
||||
call.set_command_stop();
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = fan->get_traits();
|
||||
FanStateResponse resp{};
|
||||
resp.key = fan->get_object_id_hash();
|
||||
resp.state = fan->state;
|
||||
if (traits.supports_oscillation())
|
||||
resp.oscillating = fan->oscillating;
|
||||
if (traits.supports_speed())
|
||||
resp.speed = static_cast<EnumFanSpeed>(fan->speed);
|
||||
return this->send_fan_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||
auto traits = fan->get_traits();
|
||||
ListEntitiesFanResponse msg;
|
||||
msg.key = fan->get_object_id_hash();
|
||||
msg.object_id = fan->get_object_id();
|
||||
msg.name = fan->get_name();
|
||||
msg.unique_id = get_default_unique_id("fan", fan);
|
||||
msg.supports_oscillation = traits.supports_oscillation();
|
||||
msg.supports_speed = traits.supports_speed();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
fan::FanState *fan = App.get_fan_by_key(msg.key);
|
||||
if (fan == nullptr)
|
||||
return;
|
||||
|
||||
auto call = fan->make_call();
|
||||
if (msg.has_state)
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_oscillating)
|
||||
call.set_oscillating(msg.oscillating);
|
||||
if (msg.has_speed)
|
||||
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
bool APIConnection::send_light_state(light::LightState *light) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = light->get_traits();
|
||||
auto values = light->remote_values;
|
||||
LightStateResponse resp{};
|
||||
|
||||
resp.key = light->get_object_id_hash();
|
||||
resp.state = values.is_on();
|
||||
if (traits.get_supports_brightness())
|
||||
resp.brightness = values.get_brightness();
|
||||
if (traits.get_supports_rgb()) {
|
||||
resp.red = values.get_red();
|
||||
resp.green = values.get_green();
|
||||
resp.blue = values.get_blue();
|
||||
}
|
||||
if (traits.get_supports_rgb_white_value())
|
||||
resp.white = values.get_white();
|
||||
if (traits.get_supports_color_temperature())
|
||||
resp.color_temperature = values.get_color_temperature();
|
||||
if (light->supports_effects())
|
||||
resp.effect = light->get_effect_name();
|
||||
return this->send_light_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_light_info(light::LightState *light) {
|
||||
auto traits = light->get_traits();
|
||||
ListEntitiesLightResponse msg;
|
||||
msg.key = light->get_object_id_hash();
|
||||
msg.object_id = light->get_object_id();
|
||||
msg.name = light->get_name();
|
||||
msg.unique_id = get_default_unique_id("light", light);
|
||||
msg.supports_brightness = traits.get_supports_brightness();
|
||||
msg.supports_rgb = traits.get_supports_rgb();
|
||||
msg.supports_white_value = traits.get_supports_rgb_white_value();
|
||||
msg.supports_color_temperature = traits.get_supports_color_temperature();
|
||||
if (msg.supports_color_temperature) {
|
||||
msg.min_mireds = traits.get_min_mireds();
|
||||
msg.max_mireds = traits.get_max_mireds();
|
||||
}
|
||||
if (light->supports_effects()) {
|
||||
msg.effects.emplace_back("None");
|
||||
for (auto *effect : light->get_effects())
|
||||
msg.effects.push_back(effect->get_name());
|
||||
}
|
||||
return this->send_list_entities_light_response(msg);
|
||||
}
|
||||
void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
light::LightState *light = App.get_light_by_key(msg.key);
|
||||
if (light == nullptr)
|
||||
return;
|
||||
|
||||
auto call = light->make_call();
|
||||
if (msg.has_state)
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_brightness)
|
||||
call.set_brightness(msg.brightness);
|
||||
if (msg.has_rgb) {
|
||||
call.set_red(msg.red);
|
||||
call.set_green(msg.green);
|
||||
call.set_blue(msg.blue);
|
||||
}
|
||||
if (msg.has_white)
|
||||
call.set_white(msg.white);
|
||||
if (msg.has_color_temperature)
|
||||
call.set_color_temperature(msg.color_temperature);
|
||||
if (msg.has_transition_length)
|
||||
call.set_transition_length(msg.transition_length);
|
||||
if (msg.has_flash_length)
|
||||
call.set_flash_length(msg.flash_length);
|
||||
if (msg.has_effect)
|
||||
call.set_effect(msg.effect);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
SensorStateResponse resp{};
|
||||
resp.key = sensor->get_object_id_hash();
|
||||
resp.state = state;
|
||||
return this->send_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
ListEntitiesSensorResponse msg;
|
||||
msg.key = sensor->get_object_id_hash();
|
||||
msg.object_id = sensor->get_object_id();
|
||||
msg.name = sensor->get_name();
|
||||
msg.unique_id = sensor->unique_id();
|
||||
if (msg.unique_id.empty())
|
||||
msg.unique_id = get_default_unique_id("sensor", sensor);
|
||||
msg.icon = sensor->get_icon();
|
||||
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
SwitchStateResponse resp{};
|
||||
resp.key = a_switch->get_object_id_hash();
|
||||
resp.state = state;
|
||||
return this->send_switch_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
ListEntitiesSwitchResponse msg;
|
||||
msg.key = a_switch->get_object_id_hash();
|
||||
msg.object_id = a_switch->get_object_id();
|
||||
msg.name = a_switch->get_name();
|
||||
msg.unique_id = get_default_unique_id("switch", a_switch);
|
||||
msg.icon = a_switch->get_icon();
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
return this->send_list_entities_switch_response(msg);
|
||||
}
|
||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
|
||||
if (a_switch == nullptr)
|
||||
return;
|
||||
|
||||
if (msg.state)
|
||||
a_switch->turn_on();
|
||||
else
|
||||
a_switch->turn_off();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
TextSensorStateResponse resp{};
|
||||
resp.key = text_sensor->get_object_id_hash();
|
||||
resp.state = std::move(state);
|
||||
return this->send_text_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
|
||||
ListEntitiesTextSensorResponse msg;
|
||||
msg.key = text_sensor->get_object_id_hash();
|
||||
msg.object_id = text_sensor->get_object_id();
|
||||
msg.name = text_sensor->get_name();
|
||||
msg.unique_id = text_sensor->unique_id();
|
||||
if (msg.unique_id.empty())
|
||||
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
|
||||
msg.icon = text_sensor->get_icon();
|
||||
return this->send_list_entities_text_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = climate->get_traits();
|
||||
ClimateStateResponse resp{};
|
||||
resp.key = climate->get_object_id_hash();
|
||||
resp.mode = static_cast<EnumClimateMode>(climate->mode);
|
||||
resp.action = static_cast<EnumClimateAction>(climate->action);
|
||||
if (traits.get_supports_current_temperature())
|
||||
resp.current_temperature = climate->current_temperature;
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
resp.target_temperature_low = climate->target_temperature_low;
|
||||
resp.target_temperature_high = climate->target_temperature_high;
|
||||
} else {
|
||||
resp.target_temperature = climate->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_away())
|
||||
resp.away = climate->away;
|
||||
return this->send_climate_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
auto traits = climate->get_traits();
|
||||
ListEntitiesClimateResponse msg;
|
||||
msg.key = climate->get_object_id_hash();
|
||||
msg.object_id = climate->get_object_id();
|
||||
msg.name = climate->get_name();
|
||||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_HEAT}) {
|
||||
if (traits.supports_mode(mode))
|
||||
msg.supported_modes.push_back(static_cast<EnumClimateMode>(mode));
|
||||
}
|
||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
||||
msg.supports_away = traits.get_supports_away();
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
return this->send_list_entities_climate_response(msg);
|
||||
}
|
||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
climate::Climate *climate = App.get_climate_by_key(msg.key);
|
||||
if (climate == nullptr)
|
||||
return;
|
||||
|
||||
auto call = climate->make_call();
|
||||
if (msg.has_mode)
|
||||
call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
|
||||
if (msg.has_target_temperature)
|
||||
call.set_target_temperature(msg.target_temperature);
|
||||
if (msg.has_target_temperature_low)
|
||||
call.set_target_temperature_low(msg.target_temperature_low);
|
||||
if (msg.has_target_temperature_high)
|
||||
call.set_target_temperature_high(msg.target_temperature_high);
|
||||
if (msg.has_away)
|
||||
call.set_away(msg.away);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
return;
|
||||
if (this->image_reader_.available())
|
||||
return;
|
||||
this->image_reader_.set_image(image);
|
||||
}
|
||||
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
ListEntitiesCameraResponse msg;
|
||||
msg.key = camera->get_object_id_hash();
|
||||
msg.object_id = camera->get_object_id();
|
||||
msg.name = camera->get_name();
|
||||
msg.unique_id = get_default_unique_id("camera", camera);
|
||||
return this->send_list_entities_camera_response(msg);
|
||||
}
|
||||
void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||
if (esp32_camera::global_esp32_camera == nullptr)
|
||||
return;
|
||||
|
||||
if (msg.single)
|
||||
esp32_camera::global_esp32_camera->request_image();
|
||||
if (msg.stream)
|
||||
esp32_camera::global_esp32_camera->request_stream();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||
if (homeassistant::global_homeassistant_time != nullptr)
|
||||
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
|
||||
this->set_nodelay(false);
|
||||
|
||||
// Send raw so that we don't copy too much
|
||||
auto buffer = this->create_buffer();
|
||||
// LogLevel level = 1;
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
||||
// string tag = 2;
|
||||
// buffer.encode_string(2, tag, strlen(tag));
|
||||
// string message = 3;
|
||||
buffer.encode_string(3, line, strlen(line));
|
||||
// SubscribeLogsResponse - 29
|
||||
bool success = this->send_buffer(buffer, 29);
|
||||
if (!success) {
|
||||
buffer = this->create_buffer();
|
||||
// bool send_failed = 4;
|
||||
buffer.encode_bool(4, true);
|
||||
return this->send_buffer(buffer, 29);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str();
|
||||
this->client_info_ += ")";
|
||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 3;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
}
|
||||
ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
||||
bool correct = this->parent_->check_password(msg.password);
|
||||
|
||||
ConnectResponse resp;
|
||||
// bool invalid_password = 1;
|
||||
resp.invalid_password = !correct;
|
||||
if (correct) {
|
||||
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
||||
this->connection_state_ = ConnectionState::AUTHENTICATED;
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
this->send_time_request();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
DeviceInfoResponse resp{};
|
||||
resp.uses_password = this->parent_->uses_password();
|
||||
resp.name = App.get_name();
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
#ifdef ARDUINO_BOARD
|
||||
resp.model = ARDUINO_BOARD;
|
||||
#endif
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs())
|
||||
if (it.entity_id == msg.entity_id)
|
||||
it.callback(msg.state);
|
||||
}
|
||||
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
bool found = false;
|
||||
for (auto *service : this->parent_->get_user_services()) {
|
||||
if (service->execute_service(msg)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGV(TAG, "Could not find matching service!");
|
||||
}
|
||||
}
|
||||
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
|
||||
if (this->remove_)
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> header;
|
||||
header.push_back(0x00);
|
||||
ProtoVarInt(buffer.get_buffer()->size()).encode(header);
|
||||
ProtoVarInt(message_type).encode(header);
|
||||
|
||||
size_t needed_space = buffer.get_buffer()->size() + header.size();
|
||||
|
||||
if (needed_space > this->client_->space()) {
|
||||
delay(0);
|
||||
if (needed_space > this->client_->space()) {
|
||||
// SubscribeLogsResponse
|
||||
if (message_type != 29) {
|
||||
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
|
||||
}
|
||||
delay(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->client_->add(reinterpret_cast<char *>(header.data()), header.size());
|
||||
this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size());
|
||||
bool ret = this->client_->send();
|
||||
return ret;
|
||||
}
|
||||
void APIConnection::on_unauthenticated_access() {
|
||||
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
}
|
||||
void APIConnection::on_no_setup_connection() {
|
||||
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
}
|
||||
void APIConnection::on_fatal_error() {
|
||||
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
178
esphome/components/api/api_connection.h
Normal file
178
esphome/components/api/api_connection.h
Normal file
@@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIConnection : public APIServerConnection {
|
||||
public:
|
||||
APIConnection(AsyncClient *client, APIServer *parent);
|
||||
virtual ~APIConnection();
|
||||
|
||||
void disconnect_client();
|
||||
void loop();
|
||||
|
||||
bool send_list_info_done() {
|
||||
ListEntitiesDoneResponse resp;
|
||||
return this->send_list_entities_done_response(resp);
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
|
||||
bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state(cover::Cover *cover);
|
||||
bool send_cover_info(cover::Cover *cover);
|
||||
void cover_command(const CoverCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state(fan::FanState *fan);
|
||||
bool send_fan_info(fan::FanState *fan);
|
||||
void fan_command(const FanCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state(light::LightState *light);
|
||||
bool send_light_info(light::LightState *light);
|
||||
void light_command(const LightCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state(sensor::Sensor *sensor, float state);
|
||||
bool send_sensor_info(sensor::Sensor *sensor);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state(switch_::Switch *a_switch, bool state);
|
||||
bool send_switch_info(switch_::Switch *a_switch);
|
||||
void switch_command(const SwitchCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
|
||||
bool send_text_sensor_info(text_sensor::TextSensor *text_sensor);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
||||
bool send_camera_info(esp32_camera::ESP32Camera *camera);
|
||||
void camera_image(const CameraImageRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
bool send_climate_info(climate::Climate *climate);
|
||||
void climate_command(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
if (!this->service_call_subscription_)
|
||||
return;
|
||||
this->send_homeassistant_service_response(call);
|
||||
}
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void send_time_request() {
|
||||
GetTimeRequest req;
|
||||
this->send_get_time_request(req);
|
||||
}
|
||||
#endif
|
||||
|
||||
void on_disconnect_response(const DisconnectResponse &value) override {
|
||||
// we initiated disconnect_client
|
||||
this->next_close_ = true;
|
||||
}
|
||||
void on_ping_response(const PingResponse &value) override {
|
||||
// we initiated ping
|
||||
this->sent_ping_ = false;
|
||||
}
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void on_get_time_response(const GetTimeResponse &value) override;
|
||||
#endif
|
||||
HelloResponse hello(const HelloRequest &msg) override;
|
||||
ConnectResponse connect(const ConnectRequest &msg) override;
|
||||
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
|
||||
// remote initiated disconnect_client
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
}
|
||||
PingResponse ping(const PingRequest &msg) override { return {}; }
|
||||
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
||||
this->state_subscription_ = true;
|
||||
this->initial_state_iterator_.begin();
|
||||
}
|
||||
void subscribe_logs(const SubscribeLogsRequest &msg) override {
|
||||
this->log_subscription_ = msg.level;
|
||||
if (msg.dump_config)
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
|
||||
this->service_call_subscription_ = true;
|
||||
}
|
||||
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
GetTimeResponse get_time(const GetTimeRequest &msg) override {
|
||||
// TODO
|
||||
return {};
|
||||
}
|
||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
|
||||
bool is_connection_setup() override {
|
||||
return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
|
||||
}
|
||||
void on_fatal_error() override;
|
||||
void on_unauthenticated_access() override;
|
||||
void on_no_setup_connection() override;
|
||||
ProtoWriteBuffer create_buffer() override {
|
||||
this->send_buffer_.clear();
|
||||
return {&this->send_buffer_};
|
||||
}
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
|
||||
|
||||
protected:
|
||||
friend APIServer;
|
||||
|
||||
void on_error_(int8_t error);
|
||||
void on_disconnect_();
|
||||
void on_timeout_(uint32_t time);
|
||||
void on_data_(uint8_t *buf, size_t len);
|
||||
void parse_recv_buffer_();
|
||||
void set_nodelay(bool nodelay) override {
|
||||
if (nodelay == this->current_nodelay_)
|
||||
return;
|
||||
this->client_->setNoDelay(nodelay);
|
||||
this->current_nodelay_ = nodelay;
|
||||
}
|
||||
|
||||
enum class ConnectionState {
|
||||
WAITING_FOR_HELLO,
|
||||
CONNECTED,
|
||||
AUTHENTICATED,
|
||||
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
|
||||
|
||||
bool remove_{false};
|
||||
|
||||
std::vector<uint8_t> send_buffer_;
|
||||
std::vector<uint8_t> recv_buffer_;
|
||||
|
||||
std::string client_info_;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
esp32_camera::CameraImageReader image_reader_;
|
||||
#endif
|
||||
|
||||
bool state_subscription_{false};
|
||||
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
bool current_nodelay_{false};
|
||||
bool next_close_{false};
|
||||
AsyncClient *client_;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
24
esphome/components/api/api_options.proto
Normal file
24
esphome/components/api/api_options.proto
Normal file
@@ -0,0 +1,24 @@
|
||||
syntax = "proto2";
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
|
||||
enum APISourceType {
|
||||
SOURCE_BOTH = 0;
|
||||
SOURCE_SERVER = 1;
|
||||
SOURCE_CLIENT = 2;
|
||||
}
|
||||
|
||||
message void {}
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
optional bool needs_setup_connection = 1038 [default=true];
|
||||
optional bool needs_authentication = 1039 [default=true];
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
optional uint32 id = 1036 [default=0];
|
||||
optional APISourceType source = 1037 [default=SOURCE_BOTH];
|
||||
optional string ifdef = 1038;
|
||||
optional bool log = 1039 [default=true];
|
||||
optional bool no_delay = 1040 [default=false];
|
||||
}
|
||||
2758
esphome/components/api/api_pb2.cpp
Normal file
2758
esphome/components/api/api_pb2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
695
esphome/components/api/api_pb2.h
Normal file
695
esphome/components/api/api_pb2.h
Normal file
@@ -0,0 +1,695 @@
|
||||
#pragma once
|
||||
|
||||
#include "proto.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
enum EnumLegacyCoverState : uint32_t {
|
||||
LEGACY_COVER_STATE_OPEN = 0,
|
||||
LEGACY_COVER_STATE_CLOSED = 1,
|
||||
};
|
||||
enum EnumCoverOperation : uint32_t {
|
||||
COVER_OPERATION_IDLE = 0,
|
||||
COVER_OPERATION_IS_OPENING = 1,
|
||||
COVER_OPERATION_IS_CLOSING = 2,
|
||||
};
|
||||
enum EnumLegacyCoverCommand : uint32_t {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0,
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
||||
LEGACY_COVER_COMMAND_STOP = 2,
|
||||
};
|
||||
enum EnumFanSpeed : uint32_t {
|
||||
FAN_SPEED_LOW = 0,
|
||||
FAN_SPEED_MEDIUM = 1,
|
||||
FAN_SPEED_HIGH = 2,
|
||||
};
|
||||
enum EnumLogLevel : uint32_t {
|
||||
LOG_LEVEL_NONE = 0,
|
||||
LOG_LEVEL_ERROR = 1,
|
||||
LOG_LEVEL_WARN = 2,
|
||||
LOG_LEVEL_INFO = 3,
|
||||
LOG_LEVEL_DEBUG = 4,
|
||||
LOG_LEVEL_VERBOSE = 5,
|
||||
LOG_LEVEL_VERY_VERBOSE = 6,
|
||||
};
|
||||
enum EnumServiceArgType : uint32_t {
|
||||
SERVICE_ARG_TYPE_BOOL = 0,
|
||||
SERVICE_ARG_TYPE_INT = 1,
|
||||
SERVICE_ARG_TYPE_FLOAT = 2,
|
||||
SERVICE_ARG_TYPE_STRING = 3,
|
||||
SERVICE_ARG_TYPE_BOOL_ARRAY = 4,
|
||||
SERVICE_ARG_TYPE_INT_ARRAY = 5,
|
||||
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6,
|
||||
SERVICE_ARG_TYPE_STRING_ARRAY = 7,
|
||||
};
|
||||
enum EnumClimateMode : uint32_t {
|
||||
CLIMATE_MODE_OFF = 0,
|
||||
CLIMATE_MODE_AUTO = 1,
|
||||
CLIMATE_MODE_COOL = 2,
|
||||
CLIMATE_MODE_HEAT = 3,
|
||||
};
|
||||
enum EnumClimateAction : uint32_t {
|
||||
CLIMATE_ACTION_OFF = 0,
|
||||
CLIMATE_ACTION_COOLING = 2,
|
||||
CLIMATE_ACTION_HEATING = 3,
|
||||
};
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string client_info{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HelloResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t api_version_major{0}; // NOLINT
|
||||
uint32_t api_version_minor{0}; // NOLINT
|
||||
std::string server_info{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ConnectRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string password{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ConnectResponse : public ProtoMessage {
|
||||
public:
|
||||
bool invalid_password{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DisconnectRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DisconnectResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class PingRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class PingResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DeviceInfoRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DeviceInfoResponse : public ProtoMessage {
|
||||
public:
|
||||
bool uses_password{false}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string mac_address{}; // NOLINT
|
||||
std::string esphome_version{}; // NOLINT
|
||||
std::string compilation_time{}; // NOLINT
|
||||
std::string model{}; // NOLINT
|
||||
bool has_deep_sleep{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class ListEntitiesDoneResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class SubscribeStatesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
bool is_status_binary_sensor{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BinarySensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool assumed_state{false}; // NOLINT
|
||||
bool supports_position{false}; // NOLINT
|
||||
bool supports_tilt{false}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CoverStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
EnumLegacyCoverState legacy_state{}; // NOLINT
|
||||
float position{0.0f}; // NOLINT
|
||||
float tilt{0.0f}; // NOLINT
|
||||
EnumCoverOperation current_operation{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CoverCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_legacy_command{false}; // NOLINT
|
||||
EnumLegacyCoverCommand legacy_command{}; // NOLINT
|
||||
bool has_position{false}; // NOLINT
|
||||
float position{0.0f}; // NOLINT
|
||||
bool has_tilt{false}; // NOLINT
|
||||
float tilt{0.0f}; // NOLINT
|
||||
bool stop{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesFanResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_oscillation{false}; // NOLINT
|
||||
bool supports_speed{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
EnumFanSpeed speed{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_speed{false}; // NOLINT
|
||||
EnumFanSpeed speed{}; // NOLINT
|
||||
bool has_oscillating{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesLightResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_brightness{false}; // NOLINT
|
||||
bool supports_rgb{false}; // NOLINT
|
||||
bool supports_white_value{false}; // NOLINT
|
||||
bool supports_color_temperature{false}; // NOLINT
|
||||
float min_mireds{0.0f}; // NOLINT
|
||||
float max_mireds{0.0f}; // NOLINT
|
||||
std::vector<std::string> effects{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LightStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
float brightness{0.0f}; // NOLINT
|
||||
float red{0.0f}; // NOLINT
|
||||
float green{0.0f}; // NOLINT
|
||||
float blue{0.0f}; // NOLINT
|
||||
float white{0.0f}; // NOLINT
|
||||
float color_temperature{0.0f}; // NOLINT
|
||||
std::string effect{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LightCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_brightness{false}; // NOLINT
|
||||
float brightness{0.0f}; // NOLINT
|
||||
bool has_rgb{false}; // NOLINT
|
||||
float red{0.0f}; // NOLINT
|
||||
float green{0.0f}; // NOLINT
|
||||
float blue{0.0f}; // NOLINT
|
||||
bool has_white{false}; // NOLINT
|
||||
float white{0.0f}; // NOLINT
|
||||
bool has_color_temperature{false}; // NOLINT
|
||||
float color_temperature{0.0f}; // NOLINT
|
||||
bool has_transition_length{false}; // NOLINT
|
||||
uint32_t transition_length{0}; // NOLINT
|
||||
bool has_flash_length{false}; // NOLINT
|
||||
uint32_t flash_length{0}; // NOLINT
|
||||
bool has_effect{false}; // NOLINT
|
||||
std::string effect{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
std::string unit_of_measurement{}; // NOLINT
|
||||
int32_t accuracy_decimals{0}; // NOLINT
|
||||
bool force_update{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
float state{0.0f}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
};
|
||||
class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
bool assumed_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SwitchStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SwitchCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class TextSensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string state{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class SubscribeLogsRequest : public ProtoMessage {
|
||||
public:
|
||||
EnumLogLevel level{}; // NOLINT
|
||||
bool dump_config{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeLogsResponse : public ProtoMessage {
|
||||
public:
|
||||
EnumLogLevel level{}; // NOLINT
|
||||
std::string tag{}; // NOLINT
|
||||
std::string message{}; // NOLINT
|
||||
bool send_failed{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class HomeassistantServiceMap : public ProtoMessage {
|
||||
public:
|
||||
std::string key{}; // NOLINT
|
||||
std::string value{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HomeassistantServiceResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string service{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> data{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
|
||||
bool is_event{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string entity_id{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HomeAssistantStateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string entity_id{}; // NOLINT
|
||||
std::string state{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class GetTimeRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class GetTimeResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t epoch_seconds{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
};
|
||||
class ListEntitiesServicesArgument : public ProtoMessage {
|
||||
public:
|
||||
std::string name{}; // NOLINT
|
||||
EnumServiceArgType type{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesServicesResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string name{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ExecuteServiceArgument : public ProtoMessage {
|
||||
public:
|
||||
bool bool_{false}; // NOLINT
|
||||
int32_t legacy_int{0}; // NOLINT
|
||||
float float_{0.0f}; // NOLINT
|
||||
std::string string_{}; // NOLINT
|
||||
int32_t int_{0}; // NOLINT
|
||||
std::vector<bool> bool_array{}; // NOLINT
|
||||
std::vector<int32_t> int_array{}; // NOLINT
|
||||
std::vector<float> float_array{}; // NOLINT
|
||||
std::vector<std::string> string_array{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ExecuteServiceRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::vector<ExecuteServiceArgument> args{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesCameraResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class CameraImageResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string data{}; // NOLINT
|
||||
bool done{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CameraImageRequest : public ProtoMessage {
|
||||
public:
|
||||
bool single{false}; // NOLINT
|
||||
bool stream{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_current_temperature{false}; // NOLINT
|
||||
bool supports_two_point_target_temperature{false}; // NOLINT
|
||||
std::vector<EnumClimateMode> supported_modes{}; // NOLINT
|
||||
float visual_min_temperature{0.0f}; // NOLINT
|
||||
float visual_max_temperature{0.0f}; // NOLINT
|
||||
float visual_temperature_step{0.0f}; // NOLINT
|
||||
bool supports_away{false}; // NOLINT
|
||||
bool supports_action{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
EnumClimateMode mode{}; // NOLINT
|
||||
float current_temperature{0.0f}; // NOLINT
|
||||
float target_temperature{0.0f}; // NOLINT
|
||||
float target_temperature_low{0.0f}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
EnumClimateAction action{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_mode{false}; // NOLINT
|
||||
EnumClimateMode mode{}; // NOLINT
|
||||
bool has_target_temperature{false}; // NOLINT
|
||||
float target_temperature{0.0f}; // NOLINT
|
||||
bool has_target_temperature_low{false}; // NOLINT
|
||||
float target_temperature_low{0.0f}; // NOLINT
|
||||
bool has_target_temperature_high{false}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool has_away{false}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
582
esphome/components/api/api_pb2_service.cpp
Normal file
582
esphome/components/api/api_pb2_service.cpp
Normal file
@@ -0,0 +1,582 @@
|
||||
#include "api_pb2_service.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.service";
|
||||
|
||||
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<HelloResponse>(msg, 2);
|
||||
}
|
||||
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<ConnectResponse>(msg, 4);
|
||||
}
|
||||
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<DisconnectRequest>(msg, 5);
|
||||
}
|
||||
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<DisconnectResponse>(msg, 6);
|
||||
}
|
||||
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<PingRequest>(msg, 7);
|
||||
}
|
||||
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<PingResponse>(msg, 8);
|
||||
}
|
||||
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
||||
}
|
||||
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<CoverStateResponse>(msg, 22);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<FanStateResponse>(msg, 23);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<LightStateResponse>(msg, 24);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<SensorStateResponse>(msg, 25);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<SwitchStateResponse>(msg, 26);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
||||
}
|
||||
#endif
|
||||
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
||||
}
|
||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
||||
}
|
||||
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
||||
const SubscribeHomeAssistantStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
||||
}
|
||||
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<GetTimeRequest>(msg, 36);
|
||||
}
|
||||
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<GetTimeResponse>(msg, 37);
|
||||
}
|
||||
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
||||
}
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<CameraImageResponse>(msg, 44);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(false);
|
||||
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
||||
this->set_nodelay(true);
|
||||
return this->send_message_<ClimateStateResponse>(msg, 47);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
HelloRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
|
||||
this->on_hello_request(msg);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
ConnectRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
|
||||
this->on_connect_request(msg);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
DisconnectRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
|
||||
this->on_disconnect_request(msg);
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
DisconnectResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
|
||||
this->on_disconnect_response(msg);
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
PingRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
|
||||
this->on_ping_request(msg);
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
PingResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
|
||||
this->on_ping_response(msg);
|
||||
break;
|
||||
}
|
||||
case 9: {
|
||||
DeviceInfoRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
|
||||
this->on_device_info_request(msg);
|
||||
break;
|
||||
}
|
||||
case 11: {
|
||||
ListEntitiesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
|
||||
this->on_list_entities_request(msg);
|
||||
break;
|
||||
}
|
||||
case 20: {
|
||||
SubscribeStatesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_states_request(msg);
|
||||
break;
|
||||
}
|
||||
case 28: {
|
||||
SubscribeLogsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_logs_request(msg);
|
||||
break;
|
||||
}
|
||||
case 30: {
|
||||
#ifdef USE_COVER
|
||||
CoverCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
|
||||
this->on_cover_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 31: {
|
||||
#ifdef USE_FAN
|
||||
FanCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
|
||||
this->on_fan_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 32: {
|
||||
#ifdef USE_LIGHT
|
||||
LightCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
|
||||
this->on_light_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 33: {
|
||||
#ifdef USE_SWITCH
|
||||
SwitchCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
|
||||
this->on_switch_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
SubscribeHomeassistantServicesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_homeassistant_services_request(msg);
|
||||
break;
|
||||
}
|
||||
case 36: {
|
||||
GetTimeRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
|
||||
this->on_get_time_request(msg);
|
||||
break;
|
||||
}
|
||||
case 37: {
|
||||
GetTimeResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
|
||||
this->on_get_time_response(msg);
|
||||
break;
|
||||
}
|
||||
case 38: {
|
||||
SubscribeHomeAssistantStatesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_home_assistant_states_request(msg);
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
HomeAssistantStateResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
this->on_home_assistant_state_response(msg);
|
||||
break;
|
||||
}
|
||||
case 42: {
|
||||
ExecuteServiceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
|
||||
this->on_execute_service_request(msg);
|
||||
break;
|
||||
}
|
||||
case 45: {
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CameraImageRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
|
||||
this->on_camera_image_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 48: {
|
||||
#ifdef USE_CLIMATE
|
||||
ClimateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
|
||||
this->on_climate_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void APIServerConnection::on_hello_request(const HelloRequest &msg) {
|
||||
HelloResponse ret = this->hello(msg);
|
||||
if (!this->send_hello_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
|
||||
ConnectResponse ret = this->connect(msg);
|
||||
if (!this->send_connect_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
||||
DisconnectResponse ret = this->disconnect(msg);
|
||||
if (!this->send_disconnect_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_ping_request(const PingRequest &msg) {
|
||||
PingResponse ret = this->ping(msg);
|
||||
if (!this->send_ping_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
DeviceInfoResponse ret = this->device_info(msg);
|
||||
if (!this->send_device_info_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->list_entities(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_states(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_logs(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_homeassistant_services_request(
|
||||
const SubscribeHomeassistantServicesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_homeassistant_services(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_home_assistant_states(msg);
|
||||
}
|
||||
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
GetTimeResponse ret = this->get_time(msg);
|
||||
if (!this->send_get_time_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->execute_service(msg);
|
||||
}
|
||||
#ifdef USE_COVER
|
||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->cover_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->fan_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->light_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->switch_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->camera_image(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->climate_command(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
183
esphome/components/api/api_pb2_service.h
Normal file
183
esphome/components/api/api_pb2_service.h
Normal file
@@ -0,0 +1,183 @@
|
||||
#pragma once
|
||||
|
||||
#include "api_pb2.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServerConnectionBase : public ProtoService {
|
||||
public:
|
||||
virtual void on_hello_request(const HelloRequest &value){};
|
||||
bool send_hello_response(const HelloResponse &msg);
|
||||
virtual void on_connect_request(const ConnectRequest &value){};
|
||||
bool send_connect_response(const ConnectResponse &msg);
|
||||
bool send_disconnect_request(const DisconnectRequest &msg);
|
||||
virtual void on_disconnect_request(const DisconnectRequest &value){};
|
||||
bool send_disconnect_response(const DisconnectResponse &msg);
|
||||
virtual void on_disconnect_response(const DisconnectResponse &value){};
|
||||
bool send_ping_request(const PingRequest &msg);
|
||||
virtual void on_ping_request(const PingRequest &value){};
|
||||
bool send_ping_response(const PingResponse &msg);
|
||||
virtual void on_ping_response(const PingResponse &value){};
|
||||
virtual void on_device_info_request(const DeviceInfoRequest &value){};
|
||||
bool send_device_info_response(const DeviceInfoResponse &msg);
|
||||
virtual void on_list_entities_request(const ListEntitiesRequest &value){};
|
||||
bool send_list_entities_done_response(const ListEntitiesDoneResponse &msg);
|
||||
virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){};
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state_response(const BinarySensorStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_list_entities_cover_response(const ListEntitiesCoverResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state_response(const CoverStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
virtual void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_list_entities_fan_response(const ListEntitiesFanResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state_response(const FanStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual void on_fan_command_request(const FanCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_list_entities_light_response(const ListEntitiesLightResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state_response(const LightStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual void on_light_command_request(const LightCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state_response(const SensorStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state_response(const SwitchStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state_response(const TextSensorStateResponse &msg);
|
||||
#endif
|
||||
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
|
||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
|
||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||
bool send_subscribe_home_assistant_state_response(const SubscribeHomeAssistantStateResponse &msg);
|
||||
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
bool send_get_time_request(const GetTimeRequest &msg);
|
||||
virtual void on_get_time_request(const GetTimeRequest &value){};
|
||||
bool send_get_time_response(const GetTimeResponse &msg);
|
||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||
bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg);
|
||||
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool send_list_entities_camera_response(const ListEntitiesCameraResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool send_camera_image_response(const CameraImageResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_list_entities_climate_response(const ListEntitiesClimateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state_response(const ClimateStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
class APIServerConnection : public APIServerConnectionBase {
|
||||
public:
|
||||
virtual HelloResponse hello(const HelloRequest &msg) = 0;
|
||||
virtual ConnectResponse connect(const ConnectRequest &msg) = 0;
|
||||
virtual DisconnectResponse disconnect(const DisconnectRequest &msg) = 0;
|
||||
virtual PingResponse ping(const PingRequest &msg) = 0;
|
||||
virtual DeviceInfoResponse device_info(const DeviceInfoRequest &msg) = 0;
|
||||
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
|
||||
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
|
||||
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
|
||||
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||
#ifdef USE_COVER
|
||||
virtual void cover_command(const CoverCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual void fan_command(const FanCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual void light_command(const LightCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual void camera_image(const CameraImageRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
void on_connect_request(const ConnectRequest &msg) override;
|
||||
void on_disconnect_request(const DisconnectRequest &msg) override;
|
||||
void on_ping_request(const PingRequest &msg) override;
|
||||
void on_device_info_request(const DeviceInfoRequest &msg) override;
|
||||
void on_list_entities_request(const ListEntitiesRequest &msg) override;
|
||||
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
|
||||
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||
#ifdef USE_COVER
|
||||
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_command_request(const FanCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_command_request(const LightCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_command_request(const SwitchCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void on_camera_image_request(const CameraImageRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
239
esphome/components/api/api_server.cpp
Normal file
239
esphome/components/api/api_server.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "api_server.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api";
|
||||
|
||||
// APIServer
|
||||
void APIServer::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
|
||||
this->setup_controller();
|
||||
this->server_ = AsyncServer(this->port_);
|
||||
this->server_.setNoDelay(false);
|
||||
this->server_.begin();
|
||||
this->server_.onClient(
|
||||
[](void *s, AsyncClient *client) {
|
||||
if (client == nullptr)
|
||||
return;
|
||||
|
||||
// can't print here because in lwIP thread
|
||||
// ESP_LOGD(TAG, "New client connected from %s", client->remoteIP().toString().c_str());
|
||||
auto *a_this = (APIServer *) s;
|
||||
a_this->clients_.push_back(new APIConnection(client, a_this));
|
||||
},
|
||||
this);
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
||||
for (auto *c : this->clients_) {
|
||||
if (!c->remove_)
|
||||
c->send_log_message(level, tag, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
this->last_connected_ = millis();
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
||||
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
for (auto *c : this->clients_)
|
||||
if (!c->remove_)
|
||||
c->send_camera_state(image);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void APIServer::loop() {
|
||||
// Partition clients into remove and active
|
||||
auto new_end =
|
||||
std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; });
|
||||
// print disconnection messages
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it) {
|
||||
ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str());
|
||||
}
|
||||
// only then delete the pointers, otherwise log routine
|
||||
// would access freed memory
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it)
|
||||
delete *it;
|
||||
// resize vector
|
||||
this->clients_.erase(new_end, this->clients_.end());
|
||||
|
||||
for (auto *client : this->clients_) {
|
||||
client->loop();
|
||||
}
|
||||
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
const uint32_t now = millis();
|
||||
if (!this->is_connected()) {
|
||||
if (now - this->last_connected_ > this->reboot_timeout_) {
|
||||
ESP_LOGE(TAG, "No client connected to API. Rebooting...");
|
||||
App.reboot();
|
||||
}
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->last_connected_ = now;
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "API Server:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_);
|
||||
}
|
||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
||||
bool APIServer::check_password(const std::string &password) const {
|
||||
// depend only on input password length
|
||||
const char *a = this->password_.c_str();
|
||||
uint32_t len_a = this->password_.length();
|
||||
const char *b = password.c_str();
|
||||
uint32_t len_b = password.length();
|
||||
|
||||
// disable optimization with volatile
|
||||
volatile uint32_t length = len_b;
|
||||
volatile const char *left = nullptr;
|
||||
volatile const char *right = b;
|
||||
uint8_t result = 0;
|
||||
|
||||
if (len_a == length) {
|
||||
left = *((volatile const char **) &a);
|
||||
result = 0;
|
||||
}
|
||||
if (len_a != length) {
|
||||
left = b;
|
||||
result = 1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
result |= *left++ ^ *right++; // NOLINT
|
||||
}
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_binary_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
void APIServer::on_cover_update(cover::Cover *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_cover_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void APIServer::on_fan_update(fan::FanState *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_fan_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void APIServer::on_light_update(light::LightState *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_light_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_switch_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_text_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void APIServer::on_climate_update(climate::Climate *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_climate_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
APIServer *global_api_server = nullptr;
|
||||
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
for (auto *client : this->clients_) {
|
||||
client->send_homeassistant_service_call(call);
|
||||
}
|
||||
}
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.callback = std::move(f),
|
||||
});
|
||||
}
|
||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
||||
return this->state_subs_;
|
||||
}
|
||||
uint16_t APIServer::get_port() const { return this->port_; }
|
||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto *client : this->clients_) {
|
||||
if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED)
|
||||
client->send_time_request();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
void APIServer::on_shutdown() {
|
||||
for (auto *c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
100
esphome/components/api/api_server.h
Normal file
100
esphome/components/api/api_server.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "util.h"
|
||||
#include "list_entities.h"
|
||||
#include "subscribe_state.h"
|
||||
#include "homeassistant_service.h"
|
||||
#include "user_services.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServer : public Component, public Controller {
|
||||
public:
|
||||
APIServer();
|
||||
void setup() override;
|
||||
uint16_t get_port() const;
|
||||
float get_setup_priority() const override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void on_shutdown() override;
|
||||
bool check_password(const std::string &password) const;
|
||||
bool uses_password() const;
|
||||
void set_port(uint16_t port);
|
||||
void set_password(const std::string &password);
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
void handle_disconnect(APIConnection *conn);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void on_cover_update(cover::Cover *obj) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_update(fan::FanState *obj) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_update(light::LightState *obj) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void on_sensor_update(sensor::Sensor *obj, float state) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_update(switch_::Switch *obj, bool state) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
#endif
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void request_time();
|
||||
#endif
|
||||
|
||||
bool is_connected() const;
|
||||
|
||||
struct HomeAssistantStateSubscription {
|
||||
std::string entity_id;
|
||||
std::function<void(std::string)> callback;
|
||||
};
|
||||
|
||||
void subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f);
|
||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
||||
|
||||
protected:
|
||||
AsyncServer server_{0};
|
||||
uint16_t port_{6053};
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t last_connected_{0};
|
||||
std::vector<APIConnection *> clients_;
|
||||
std::string password_;
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
};
|
||||
|
||||
extern APIServer *global_api_server;
|
||||
|
||||
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
214
esphome/components/api/custom_api_device.h
Normal file
214
esphome/components/api/custom_api_device.h
Normal file
@@ -0,0 +1,214 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "user_services.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||
public:
|
||||
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||
void (T::*callback)(Ts...))
|
||||
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
||||
|
||||
protected:
|
||||
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
||||
|
||||
T *obj_;
|
||||
void (T::*callback_)(Ts...);
|
||||
};
|
||||
|
||||
class CustomAPIDevice {
|
||||
public:
|
||||
/// Return if a client (such as Home Assistant) is connected to the native API.
|
||||
bool is_connected() const { return global_api_server->is_connected(); }
|
||||
|
||||
/** Register a custom native API service that will show up in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* register_service(&CustomNativeAPI::on_start_washer_cycle, "start_washer_cycle",
|
||||
* {"cycle_length"});
|
||||
* }
|
||||
*
|
||||
* void on_start_washer_cycle(int cycle_length) {
|
||||
* // Start washer cycle.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @tparam Ts The argument types for the service, automatically deduced from the function arguments.
|
||||
* @param callback The member function to call when the service is triggered.
|
||||
* @param name The name of the service to register.
|
||||
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
template<typename T, typename... Ts>
|
||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
/** Register a custom native API service that will show up in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* register_service(&CustomNativeAPI::on_hello_world, "hello_world");
|
||||
* }
|
||||
*
|
||||
* void on_hello_world() {
|
||||
* // Hello World service called.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the service is triggered.
|
||||
* @param name The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
/** Subscribe to the state of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string state) {
|
||||
* // State of sensor.weather_forecast is `state`
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param entity_id The entity_id to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||
}
|
||||
|
||||
/** Subscribe to the state of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string entity_id, std::string state) {
|
||||
* // State of `entity_id` is `state`
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param entity_id The entity_id to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||
}
|
||||
|
||||
/** Call a Home Assistant service from ESPHome.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* call_homeassistant_service("homeassistant.restart");
|
||||
* ```
|
||||
*
|
||||
* @param service_name The service to call.
|
||||
*/
|
||||
void call_homeassistant_service(const std::string &service_name) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Call a Home Assistant service from ESPHome.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* call_homeassistant_service("light.turn_on", {
|
||||
* {"entity_id", "light.my_light"},
|
||||
* {"brightness", "127"},
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param service_name The service to call.
|
||||
* @param data The data for the service call, mapping from string to string.
|
||||
*/
|
||||
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
for (auto &it : data) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.first;
|
||||
kv.value = it.second;
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Fire an ESPHome event in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* fire_homeassistant_event("esphome.something_happened");
|
||||
* ```
|
||||
*
|
||||
* @param event_name The event to fire.
|
||||
*/
|
||||
void fire_homeassistant_event(const std::string &event_name) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = event_name;
|
||||
resp.is_event = true;
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Fire an ESPHome event in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* fire_homeassistant_event("esphome.something_happened", {
|
||||
* {"my_value", "500"},
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param event_name The event to fire.
|
||||
* @param data The data for the event, mapping from string to string.
|
||||
*/
|
||||
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
resp.is_event = true;
|
||||
for (auto &it : data) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.first;
|
||||
kv.value = it.second;
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
66
esphome/components/api/homeassistant_service.h
Normal file
66
esphome/components/api/homeassistant_service.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<typename... Ts> class TemplatableKeyValuePair {
|
||||
public:
|
||||
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
|
||||
std::string key;
|
||||
TemplatableStringValue<Ts...> value;
|
||||
};
|
||||
|
||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
||||
|
||||
TEMPLATABLE_STRING_VALUE(service);
|
||||
template<typename T> void add_data(std::string key, T value) {
|
||||
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
template<typename T> void add_data_template(std::string key, T value) {
|
||||
this->data_template_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
template<typename T> void add_variable(std::string key, T value) {
|
||||
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
void play(Ts... x) override {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = this->service_.value(x...);
|
||||
resp.is_event = this->is_event_;
|
||||
for (auto &it : this->data_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
for (auto &it : this->data_template_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.data_template.push_back(kv);
|
||||
}
|
||||
for (auto &it : this->variables_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.variables.push_back(kv);
|
||||
}
|
||||
this->parent_->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
protected:
|
||||
APIServer *parent_;
|
||||
bool is_event_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
55
esphome/components/api/list_entities.cpp
Normal file
55
esphome/components/api/list_entities.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "list_entities.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_connection.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
return this->client_->send_binary_sensor_info(binary_sensor);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); }
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); }
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); }
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); }
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
return this->client_->send_text_sensor_info(text_sensor);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||
auto resp = service->encode_list_service_response();
|
||||
return this->client_->send_list_entities_services_response(resp);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
||||
return this->client_->send_camera_info(camera);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
52
esphome/components/api/list_entities.h
Normal file
52
esphome/components/api/list_entities.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIConnection;
|
||||
|
||||
class ListEntitiesIterator : public ComponentIterator {
|
||||
public:
|
||||
ListEntitiesIterator(APIServer *server, APIConnection *client);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::FanState *fan) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool on_sensor(sensor::Sensor *sensor) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool on_switch(switch_::Switch *a_switch) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
|
||||
#endif
|
||||
bool on_service(UserServiceDescriptor *service) override;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool on_camera(esp32_camera::ESP32Camera *camera) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
#include "api_server.h"
|
||||
91
esphome/components/api/proto.cpp
Normal file
91
esphome/components/api/proto.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "proto.h"
|
||||
#include "util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.proto";
|
||||
|
||||
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
uint32_t i = 0;
|
||||
bool error = false;
|
||||
while (i < length) {
|
||||
uint32_t consumed;
|
||||
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid field start at %u", i);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t field_type = (res->as_uint32()) & 0b111;
|
||||
uint32_t field_id = (res->as_uint32()) >> 3;
|
||||
i += consumed;
|
||||
|
||||
switch (field_type) {
|
||||
case 0: { // VarInt
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid VarInt at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_varint(field_id, *res)) {
|
||||
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32());
|
||||
}
|
||||
i += consumed;
|
||||
break;
|
||||
}
|
||||
case 2: { // Length-delimited
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
i += consumed;
|
||||
if (field_length > length - i) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
|
||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
|
||||
}
|
||||
i += field_length;
|
||||
break;
|
||||
}
|
||||
case 5: { // 32-bit
|
||||
if (length - i < 4) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
|
||||
(uint32_t(buffer[i + 3]) << 24);
|
||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
|
||||
}
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGV(TAG, "Invalid field type at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (error) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ProtoMessage::dump() const {
|
||||
std::string out;
|
||||
this->dump_to(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
279
esphome/components/api/proto.h
Normal file
279
esphome/components/api/proto.h
Normal file
@@ -0,0 +1,279 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
|
||||
class ProtoVarInt {
|
||||
public:
|
||||
ProtoVarInt() : value_(0) {}
|
||||
explicit ProtoVarInt(uint64_t value) : value_(value) {}
|
||||
|
||||
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
|
||||
if (consumed != nullptr)
|
||||
*consumed = 0;
|
||||
|
||||
if (len == 0)
|
||||
return {};
|
||||
|
||||
uint64_t result = 0;
|
||||
uint8_t bitpos = 0;
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
uint8_t val = buffer[i];
|
||||
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
|
||||
bitpos += 7;
|
||||
if ((val & 0x80) == 0) {
|
||||
if (consumed != nullptr)
|
||||
*consumed = i + 1;
|
||||
return ProtoVarInt(result);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t as_uint32() const { return this->value_; }
|
||||
uint64_t as_uint64() const { return this->value_; }
|
||||
bool as_bool() const { return this->value_; }
|
||||
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
|
||||
int32_t as_int32() const {
|
||||
// Not ZigZag encoded
|
||||
return static_cast<int32_t>(this->as_int64());
|
||||
}
|
||||
int64_t as_int64() const {
|
||||
// Not ZigZag encoded
|
||||
return static_cast<int64_t>(this->value_);
|
||||
}
|
||||
int32_t as_sint32() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1)
|
||||
return static_cast<int32_t>(~(this->value_ >> 1));
|
||||
else
|
||||
return static_cast<int32_t>(this->value_ >> 1);
|
||||
}
|
||||
int64_t as_sint64() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1)
|
||||
return static_cast<int64_t>(~(this->value_ >> 1));
|
||||
else
|
||||
return static_cast<int64_t>(this->value_ >> 1);
|
||||
}
|
||||
void encode(std::vector<uint8_t> &out) {
|
||||
uint32_t val = this->value_;
|
||||
if (val <= 0x7F) {
|
||||
out.push_back(val);
|
||||
return;
|
||||
}
|
||||
while (val) {
|
||||
uint8_t temp = val & 0x7F;
|
||||
val >>= 7;
|
||||
if (val) {
|
||||
out.push_back(temp | 0x80);
|
||||
} else {
|
||||
out.push_back(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
class ProtoLengthDelimited {
|
||||
public:
|
||||
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
||||
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
||||
template<class C> C as_message() const {
|
||||
auto msg = C();
|
||||
msg.decode(this->value_, this->length_);
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint8_t *const value_;
|
||||
const size_t length_;
|
||||
};
|
||||
|
||||
class Proto32Bit {
|
||||
public:
|
||||
explicit Proto32Bit(uint32_t value) : value_(value) {}
|
||||
uint32_t as_fixed32() const { return this->value_; }
|
||||
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
|
||||
float as_float() const {
|
||||
union {
|
||||
uint32_t raw;
|
||||
float value;
|
||||
} s{};
|
||||
s.raw = this->value_;
|
||||
return s.value;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint32_t value_;
|
||||
};
|
||||
|
||||
class Proto64Bit {
|
||||
public:
|
||||
explicit Proto64Bit(uint64_t value) : value_(value) {}
|
||||
uint64_t as_fixed64() const { return this->value_; }
|
||||
int64_t as_sfixed64() const { return static_cast<int64_t>(this->value_); }
|
||||
double as_double() const {
|
||||
union {
|
||||
uint64_t raw;
|
||||
double value;
|
||||
} s{};
|
||||
s.raw = this->value_;
|
||||
return s.value;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint64_t value_;
|
||||
};
|
||||
|
||||
class ProtoWriteBuffer {
|
||||
public:
|
||||
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
|
||||
void write(uint8_t value) { this->buffer_->push_back(value); }
|
||||
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
|
||||
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) {
|
||||
uint32_t val = (field_id << 3) | (type & 0b111);
|
||||
this->encode_varint_raw(val);
|
||||
}
|
||||
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 2);
|
||||
this->encode_varint_raw(len);
|
||||
auto *data = reinterpret_cast<const uint8_t *>(string);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
this->write(data[i]);
|
||||
}
|
||||
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
||||
this->encode_string(field_id, value.data(), value.size());
|
||||
}
|
||||
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||
}
|
||||
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(value);
|
||||
}
|
||||
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(ProtoVarInt(value));
|
||||
}
|
||||
void encode_bool(uint32_t field_id, bool value, bool force = false) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->write(0x01);
|
||||
}
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 5);
|
||||
this->write((value >> 0) & 0xFF);
|
||||
this->write((value >> 8) & 0xFF);
|
||||
this->write((value >> 16) & 0xFF);
|
||||
this->write((value >> 24) & 0xFF);
|
||||
}
|
||||
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
|
||||
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||
}
|
||||
void encode_float(uint32_t field_id, float value, bool force = false) {
|
||||
if (value == 0.0f && !force)
|
||||
return;
|
||||
|
||||
union {
|
||||
float value;
|
||||
uint32_t raw;
|
||||
} val{};
|
||||
val.value = value;
|
||||
this->encode_fixed32(field_id, val.raw);
|
||||
}
|
||||
void encode_int32(uint32_t field_id, int32_t value, bool force = false) {
|
||||
if (value < 0) {
|
||||
// negative int32 is always 10 byte long
|
||||
this->encode_int64(field_id, value, force);
|
||||
return;
|
||||
}
|
||||
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||
}
|
||||
void encode_int64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
||||
}
|
||||
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
||||
uint32_t uvalue;
|
||||
if (value < 0)
|
||||
uvalue = ~(value << 1);
|
||||
else
|
||||
uvalue = value << 1;
|
||||
this->encode_uint32(field_id, uvalue, force);
|
||||
}
|
||||
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
|
||||
this->encode_field_raw(field_id, 2);
|
||||
size_t begin = this->buffer_->size();
|
||||
|
||||
value.encode(*this);
|
||||
|
||||
const uint32_t nested_length = this->buffer_->size() - begin;
|
||||
// add size varint
|
||||
std::vector<uint8_t> var;
|
||||
ProtoVarInt(nested_length).encode(var);
|
||||
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
|
||||
}
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual void encode(ProtoWriteBuffer buffer) const = 0;
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
std::string dump() const;
|
||||
virtual void dump_to(std::string &out) const = 0;
|
||||
|
||||
protected:
|
||||
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; }
|
||||
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
|
||||
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
|
||||
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
|
||||
};
|
||||
|
||||
template<typename T> const char *proto_enum_to_string(T value);
|
||||
|
||||
class ProtoService {
|
||||
public:
|
||||
protected:
|
||||
virtual bool is_authenticated() = 0;
|
||||
virtual bool is_connection_setup() = 0;
|
||||
virtual void on_fatal_error() = 0;
|
||||
virtual void on_unauthenticated_access() = 0;
|
||||
virtual void on_no_setup_connection() = 0;
|
||||
virtual ProtoWriteBuffer create_buffer() = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
|
||||
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||
virtual void set_nodelay(bool nodelay) = 0;
|
||||
|
||||
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
||||
auto buffer = this->create_buffer();
|
||||
msg.encode(buffer);
|
||||
return this->send_buffer(buffer, message_type);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
53
esphome/components/api/subscribe_state.cpp
Normal file
53
esphome/components/api/subscribe_state.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "subscribe_state.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
if (!binary_sensor->has_state())
|
||||
return true;
|
||||
|
||||
return this->client_->send_binary_sensor_state(binary_sensor, binary_sensor->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); }
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool InitialStateIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_state(fan); }
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); }
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) {
|
||||
if (!sensor->has_state())
|
||||
return true;
|
||||
|
||||
return this->client_->send_sensor_state(sensor, sensor->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool InitialStateIterator::on_switch(switch_::Switch *a_switch) {
|
||||
return this->client_->send_switch_state(a_switch, a_switch->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
if (!text_sensor->has_state())
|
||||
return true;
|
||||
|
||||
return this->client_->send_text_sensor_state(text_sensor, text_sensor->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
|
||||
#endif
|
||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
47
esphome/components/api/subscribe_state.h
Normal file
47
esphome/components/api/subscribe_state.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIConnection;
|
||||
|
||||
class InitialStateIterator : public ComponentIterator {
|
||||
public:
|
||||
InitialStateIterator(APIServer *server, APIConnection *client);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::FanState *fan) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool on_sensor(sensor::Sensor *sensor) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool on_switch(switch_::Switch *a_switch) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
#endif
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
#include "api_server.h"
|
||||
38
esphome/components/api/user_services.cpp
Normal file
38
esphome/components/api/user_services.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "user_services.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; }
|
||||
template<> int get_execute_arg_value<int>(const ExecuteServiceArgument &arg) {
|
||||
if (arg.legacy_int != 0)
|
||||
return arg.legacy_int;
|
||||
return arg.int_;
|
||||
}
|
||||
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
||||
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
||||
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.bool_array;
|
||||
}
|
||||
template<> std::vector<int> get_execute_arg_value<std::vector<int>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.int_array;
|
||||
}
|
||||
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.float_array;
|
||||
}
|
||||
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.string_array;
|
||||
}
|
||||
|
||||
template<> EnumServiceArgType to_service_arg_type<bool>() { return SERVICE_ARG_TYPE_BOOL; }
|
||||
template<> EnumServiceArgType to_service_arg_type<int>() { return SERVICE_ARG_TYPE_INT; }
|
||||
template<> EnumServiceArgType to_service_arg_type<float>() { return SERVICE_ARG_TYPE_FLOAT; }
|
||||
template<> EnumServiceArgType to_service_arg_type<std::string>() { return SERVICE_ARG_TYPE_STRING; }
|
||||
template<> EnumServiceArgType to_service_arg_type<std::vector<bool>>() { return SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
||||
template<> EnumServiceArgType to_service_arg_type<std::vector<int>>() { return SERVICE_ARG_TYPE_INT_ARRAY; }
|
||||
template<> EnumServiceArgType to_service_arg_type<std::vector<float>>() { return SERVICE_ARG_TYPE_FLOAT_ARRAY; }
|
||||
template<> EnumServiceArgType to_service_arg_type<std::vector<std::string>>() { return SERVICE_ARG_TYPE_STRING_ARRAY; }
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
72
esphome/components/api/user_services.h
Normal file
72
esphome/components/api/user_services.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "api_pb2.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class UserServiceDescriptor {
|
||||
public:
|
||||
virtual ListEntitiesServicesResponse encode_list_service_response() = 0;
|
||||
|
||||
virtual bool execute_service(const ExecuteServiceRequest &req) = 0;
|
||||
};
|
||||
|
||||
template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
|
||||
|
||||
template<typename T> EnumServiceArgType to_service_arg_type();
|
||||
|
||||
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
public:
|
||||
UserServiceBase(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||
: name_(name), arg_names_(arg_names) {
|
||||
this->key_ = fnv1_hash(this->name_);
|
||||
}
|
||||
|
||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||
ListEntitiesServicesResponse msg;
|
||||
msg.name = this->name_;
|
||||
msg.key = this->key_;
|
||||
std::array<EnumServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||
for (int i = 0; i < sizeof...(Ts); i++) {
|
||||
ListEntitiesServicesArgument arg;
|
||||
arg.type = arg_types[i];
|
||||
arg.name = this->arg_names_[i];
|
||||
msg.args.push_back(arg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
bool execute_service(const ExecuteServiceRequest &req) override {
|
||||
if (req.key != this->key_)
|
||||
return false;
|
||||
if (req.args.size() != this->arg_names_.size())
|
||||
return false;
|
||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void execute(Ts... x) = 0;
|
||||
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
std::string name_;
|
||||
uint32_t key_{0};
|
||||
std::array<std::string, sizeof...(Ts)> arg_names_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
||||
public:
|
||||
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||
: UserServiceBase<Ts...>(name, arg_names) {}
|
||||
|
||||
protected:
|
||||
void execute(Ts... x) override { this->trigger(x...); } // NOLINT
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
193
esphome/components/api/util.cpp
Normal file
193
esphome/components/api/util.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#include "util.h"
|
||||
#include "api_server.h"
|
||||
#include "user_services.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {}
|
||||
void ComponentIterator::begin() {
|
||||
this->state_ = IteratorState::BEGIN;
|
||||
this->at_ = 0;
|
||||
}
|
||||
void ComponentIterator::advance() {
|
||||
bool advance_platform = false;
|
||||
bool success = true;
|
||||
switch (this->state_) {
|
||||
case IteratorState::NONE:
|
||||
// not started
|
||||
return;
|
||||
case IteratorState::BEGIN:
|
||||
if (this->on_begin()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
case IteratorState::BINARY_SENSOR:
|
||||
if (this->at_ >= App.get_binary_sensors().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *binary_sensor = App.get_binary_sensors()[this->at_];
|
||||
if (binary_sensor->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_binary_sensor(binary_sensor);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
case IteratorState::COVER:
|
||||
if (this->at_ >= App.get_covers().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *cover = App.get_covers()[this->at_];
|
||||
if (cover->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_cover(cover);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
case IteratorState::FAN:
|
||||
if (this->at_ >= App.get_fans().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *fan = App.get_fans()[this->at_];
|
||||
if (fan->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_fan(fan);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
case IteratorState::LIGHT:
|
||||
if (this->at_ >= App.get_lights().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *light = App.get_lights()[this->at_];
|
||||
if (light->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_light(light);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
case IteratorState::SENSOR:
|
||||
if (this->at_ >= App.get_sensors().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *sensor = App.get_sensors()[this->at_];
|
||||
if (sensor->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_sensor(sensor);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
case IteratorState::SWITCH:
|
||||
if (this->at_ >= App.get_switches().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *a_switch = App.get_switches()[this->at_];
|
||||
if (a_switch->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_switch(a_switch);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
case IteratorState::TEXT_SENSOR:
|
||||
if (this->at_ >= App.get_text_sensors().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *text_sensor = App.get_text_sensors()[this->at_];
|
||||
if (text_sensor->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_text_sensor(text_sensor);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IteratorState ::SERVICE:
|
||||
if (this->at_ >= this->server_->get_user_services().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *service = this->server_->get_user_services()[this->at_];
|
||||
success = this->on_service(service);
|
||||
}
|
||||
break;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
case IteratorState::CAMERA:
|
||||
if (esp32_camera::global_esp32_camera == nullptr) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
if (esp32_camera::global_esp32_camera->is_internal()) {
|
||||
advance_platform = success = true;
|
||||
break;
|
||||
} else {
|
||||
advance_platform = success = this->on_camera(esp32_camera::global_esp32_camera);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
case IteratorState::CLIMATE:
|
||||
if (this->at_ >= App.get_climates().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *climate = App.get_climates()[this->at_];
|
||||
if (climate->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_climate(climate);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IteratorState::MAX:
|
||||
if (this->on_end()) {
|
||||
this->state_ = IteratorState::NONE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (advance_platform) {
|
||||
this->state_ = static_cast<IteratorState>(static_cast<uint32_t>(this->state_) + 1);
|
||||
this->at_ = 0;
|
||||
} else if (success) {
|
||||
this->at_++;
|
||||
}
|
||||
}
|
||||
bool ComponentIterator::on_end() { return true; }
|
||||
bool ComponentIterator::on_begin() { return true; }
|
||||
bool ComponentIterator::on_service(UserServiceDescriptor *service) { return true; }
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; }
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
93
esphome/components/api/util.h
Normal file
93
esphome/components/api/util.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
#include "esphome/components/esp32_camera/esp32_camera.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServer;
|
||||
class UserServiceDescriptor;
|
||||
|
||||
class ComponentIterator {
|
||||
public:
|
||||
ComponentIterator(APIServer *server);
|
||||
|
||||
void begin();
|
||||
void advance();
|
||||
virtual bool on_begin();
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
virtual bool on_cover(cover::Cover *cover) = 0;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual bool on_fan(fan::FanState *fan) = 0;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual bool on_light(light::LightState *light) = 0;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
virtual bool on_sensor(sensor::Sensor *sensor) = 0;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
virtual bool on_switch(switch_::Switch *a_switch) = 0;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
|
||||
#endif
|
||||
virtual bool on_service(UserServiceDescriptor *service);
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual bool on_camera(esp32_camera::ESP32Camera *camera);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
virtual bool on_climate(climate::Climate *climate) = 0;
|
||||
#endif
|
||||
virtual bool on_end();
|
||||
|
||||
protected:
|
||||
enum class IteratorState {
|
||||
NONE = 0,
|
||||
BEGIN,
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
BINARY_SENSOR,
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
COVER,
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
FAN,
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
LIGHT,
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
SENSOR,
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
SWITCH,
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
TEXT_SENSOR,
|
||||
#endif
|
||||
SERVICE,
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CAMERA,
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
CLIMATE,
|
||||
#endif
|
||||
MAX,
|
||||
} state_{IteratorState::NONE};
|
||||
size_t at_{0};
|
||||
|
||||
APIServer *server_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
47
esphome/components/as3935/__init__.py
Normal file
47
esphome/components/as3935/__init__.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_PIN, CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \
|
||||
CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \
|
||||
CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE
|
||||
from esphome.core import coroutine
|
||||
|
||||
|
||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_AS3935_ID = 'as3935_id'
|
||||
|
||||
as3935_ns = cg.esphome_ns.namespace('as3935')
|
||||
AS3935 = as3935_ns.class_('AS3935Component', cg.Component)
|
||||
|
||||
AS3935_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(AS3935),
|
||||
cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema,
|
||||
pins.validate_has_interrupt),
|
||||
|
||||
cv.Optional(CONF_INDOOR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
|
||||
cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
|
||||
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
||||
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
|
||||
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 22, 64, 128, int=True),
|
||||
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
||||
})
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_as3935(var, config):
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
cg.add(var.set_indoor(config[CONF_INDOOR]))
|
||||
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))
|
||||
cg.add(var.set_watchdog_threshold(config[CONF_WATCHDOG_THRESHOLD]))
|
||||
cg.add(var.set_spike_rejection(config[CONF_SPIKE_REJECTION]))
|
||||
cg.add(var.set_lightning_threshold(config[CONF_LIGHTNING_THRESHOLD]))
|
||||
cg.add(var.set_mask_disturber(config[CONF_MASK_DISTURBER]))
|
||||
cg.add(var.set_div_ratio(config[CONF_DIV_RATIO]))
|
||||
cg.add(var.set_capacitance(config[CONF_CAPACITANCE]))
|
||||
228
esphome/components/as3935/as3935.cpp
Normal file
228
esphome/components/as3935/as3935.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "as3935.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935 {
|
||||
|
||||
static const char *TAG = "as3935";
|
||||
|
||||
void AS3935Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AS3935...");
|
||||
|
||||
this->pin_->setup();
|
||||
this->store_.pin = this->pin_->to_isr();
|
||||
LOG_PIN(" Interrupt Pin: ", this->pin_);
|
||||
this->pin_->attach_interrupt(AS3935ComponentStore::gpio_intr, &this->store_, RISING);
|
||||
|
||||
// Write properties to sensor
|
||||
this->write_indoor(this->indoor_);
|
||||
this->write_noise_level(this->noise_level_);
|
||||
this->write_watchdog_threshold(this->watchdog_threshold_);
|
||||
this->write_spike_rejection(this->spike_rejection_);
|
||||
this->write_lightning_threshold(this->lightning_threshold_);
|
||||
this->write_mask_disturber(this->mask_disturber_);
|
||||
this->write_div_ratio(this->div_ratio_);
|
||||
this->write_capacitance(this->capacitance_);
|
||||
}
|
||||
|
||||
void AS3935Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AS3935:");
|
||||
LOG_PIN(" Interrupt Pin: ", this->pin_);
|
||||
}
|
||||
|
||||
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void AS3935Component::loop() {
|
||||
if (!this->store_.interrupt)
|
||||
return;
|
||||
|
||||
uint8_t int_value = this->read_interrupt_register_();
|
||||
if (int_value == NOISE_INT) {
|
||||
ESP_LOGI(TAG, "Noise was detected - try increasing the noise level value!");
|
||||
} else if (int_value == DISTURBER_INT) {
|
||||
ESP_LOGI(TAG, "Disturber was detected - try increasing the spike rejection value!");
|
||||
} else if (int_value == LIGHTNING_INT) {
|
||||
ESP_LOGI(TAG, "Lightning has been detected!");
|
||||
if (this->thunder_alert_binary_sensor_ != nullptr)
|
||||
this->thunder_alert_binary_sensor_->publish_state(true);
|
||||
uint8_t distance = this->get_distance_to_storm_();
|
||||
if (this->distance_sensor_ != nullptr)
|
||||
this->distance_sensor_->publish_state(distance);
|
||||
uint32_t energy = this->get_lightning_energy_();
|
||||
if (this->energy_sensor_ != nullptr)
|
||||
this->energy_sensor_->publish_state(energy);
|
||||
}
|
||||
this->thunder_alert_binary_sensor_->publish_state(false);
|
||||
this->store_.interrupt = false;
|
||||
}
|
||||
|
||||
void AS3935Component::write_indoor(bool indoor) {
|
||||
ESP_LOGV(TAG, "Setting indoor to %d", indoor);
|
||||
if (indoor)
|
||||
this->write_register(AFE_GAIN, GAIN_MASK, INDOOR, 1);
|
||||
else
|
||||
this->write_register(AFE_GAIN, GAIN_MASK, OUTDOOR, 1);
|
||||
}
|
||||
// REG0x01, bits[3:0], manufacturer default: 0010 (2).
|
||||
// This setting determines the threshold for events that trigger the
|
||||
// IRQ Pin.
|
||||
void AS3935Component::write_watchdog_threshold(uint8_t watchdog_threshold) {
|
||||
ESP_LOGV(TAG, "Setting watchdog sensitivity to %d", watchdog_threshold);
|
||||
if ((watchdog_threshold < 1) || (watchdog_threshold > 10)) // 10 is the max sensitivity setting
|
||||
return;
|
||||
this->write_register(THRESHOLD, THRESH_MASK, watchdog_threshold, 0);
|
||||
}
|
||||
|
||||
// REG0x01, bits [6:4], manufacturer default: 010 (2).
|
||||
// The noise floor level is compared to a known reference voltage. If this
|
||||
// level is exceeded the chip will issue an interrupt to the IRQ pin,
|
||||
// broadcasting that it can not operate properly due to noise (INT_NH).
|
||||
// Check datasheet for specific noise level tolerances when setting this register.
|
||||
void AS3935Component::write_noise_level(uint8_t noise_level) {
|
||||
ESP_LOGV(TAG, "Setting noise level to %d", noise_level);
|
||||
if ((noise_level < 1) || (noise_level > 7))
|
||||
return;
|
||||
|
||||
this->write_register(THRESHOLD, NOISE_FLOOR_MASK, noise_level, 4);
|
||||
}
|
||||
// REG0x02, bits [3:0], manufacturer default: 0010 (2).
|
||||
// This setting, like the watchdog threshold, can help determine between false
|
||||
// events and actual lightning. The shape of the spike is analyzed during the
|
||||
// chip's signal validation routine. Increasing this value increases robustness
|
||||
// at the cost of sensitivity to distant events.
|
||||
void AS3935Component::write_spike_rejection(uint8_t spike_rejection) {
|
||||
ESP_LOGV(TAG, "Setting spike rejection to %d", spike_rejection);
|
||||
if ((spike_rejection < 1) || (spike_rejection > 11))
|
||||
return;
|
||||
|
||||
this->write_register(LIGHTNING_REG, SPIKE_MASK, spike_rejection, 0);
|
||||
}
|
||||
// REG0x02, bits [5:4], manufacturer default: 0 (single lightning strike).
|
||||
// The number of lightning events before IRQ is set high. 15 minutes is The
|
||||
// window of time before the number of detected lightning events is reset.
|
||||
// The number of lightning strikes can be set to 1,5,9, or 16.
|
||||
void AS3935Component::write_lightning_threshold(uint8_t lightning_threshold) {
|
||||
ESP_LOGV(TAG, "Setting lightning threshold to %d", lightning_threshold);
|
||||
switch (lightning_threshold) {
|
||||
case 1:
|
||||
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 0, 4); // Demonstrative
|
||||
break;
|
||||
case 5:
|
||||
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 1, 4);
|
||||
break;
|
||||
case 9:
|
||||
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 1, 5);
|
||||
break;
|
||||
case 16:
|
||||
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 3, 4);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
// REG0x03, bit [5], manufacturer default: 0.
|
||||
// This setting will return whether or not disturbers trigger the IRQ Pin.
|
||||
void AS3935Component::write_mask_disturber(bool enabled) {
|
||||
ESP_LOGV(TAG, "Setting mask disturber to %d", enabled);
|
||||
if (enabled) {
|
||||
this->write_register(INT_MASK_ANT, (1 << 5), 1, 5);
|
||||
} else {
|
||||
this->write_register(INT_MASK_ANT, (1 << 5), 0, 5);
|
||||
}
|
||||
}
|
||||
// REG0x03, bit [7:6], manufacturer default: 0 (16 division ratio).
|
||||
// The antenna is designed to resonate at 500kHz and so can be tuned with the
|
||||
// following setting. The accuracy of the antenna must be within 3.5 percent of
|
||||
// that value for proper signal validation and distance estimation.
|
||||
void AS3935Component::write_div_ratio(uint8_t div_ratio) {
|
||||
ESP_LOGV(TAG, "Setting div ratio to %d", div_ratio);
|
||||
switch (div_ratio) {
|
||||
case 16:
|
||||
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 0, 6);
|
||||
break;
|
||||
case 22:
|
||||
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 1, 6);
|
||||
break;
|
||||
case 64:
|
||||
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 1, 7);
|
||||
break;
|
||||
case 128:
|
||||
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 3, 6);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
// REG0x08, bits [3:0], manufacturer default: 0.
|
||||
// This setting will add capacitance to the series RLC antenna on the product
|
||||
// to help tune its resonance. The datasheet specifies being within 3.5 percent
|
||||
// of 500kHz to get optimal lightning detection and distance sensing.
|
||||
// It's possible to add up to 120pF in steps of 8pF to the antenna.
|
||||
void AS3935Component::write_capacitance(uint8_t capacitance) {
|
||||
ESP_LOGV(TAG, "Setting tune cap to %d pF", capacitance * 8);
|
||||
this->write_register(FREQ_DISP_IRQ, CAP_MASK, capacitance, 0);
|
||||
}
|
||||
|
||||
// REG0x03, bits [3:0], manufacturer default: 0.
|
||||
// When there is an event that exceeds the watchdog threshold, the register is written
|
||||
// with the type of event. This consists of two messages: INT_D (disturber detected) and
|
||||
// INT_L (Lightning detected). A third interrupt INT_NH (noise level too HIGH)
|
||||
// indicates that the noise level has been exceeded and will persist until the
|
||||
// noise has ended. Events are active HIGH. There is a one second window of time to
|
||||
// read the interrupt register after lightning is detected, and 1.5 after
|
||||
// disturber.
|
||||
uint8_t AS3935Component::read_interrupt_register_() {
|
||||
// A 2ms delay is added to allow for the memory register to be populated
|
||||
// after the interrupt pin goes HIGH. See "Interrupt Management" in
|
||||
// datasheet.
|
||||
ESP_LOGV(TAG, "Calling read_interrupt_register_");
|
||||
delay(2);
|
||||
return this->read_register_(INT_MASK_ANT, INT_MASK);
|
||||
}
|
||||
|
||||
// REG0x02, bit [6], manufacturer default: 1.
|
||||
// This register clears the number of lightning strikes that has been read in
|
||||
// the last 15 minute block.
|
||||
void AS3935Component::clear_statistics_() {
|
||||
// Write high, then low, then high to clear.
|
||||
ESP_LOGV(TAG, "Calling clear_statistics_");
|
||||
this->write_register(LIGHTNING_REG, (1 << 6), 1, 6);
|
||||
this->write_register(LIGHTNING_REG, (1 << 6), 0, 6);
|
||||
this->write_register(LIGHTNING_REG, (1 << 6), 1, 6);
|
||||
}
|
||||
|
||||
// REG0x07, bit [5:0], manufacturer default: 0.
|
||||
// This register holds the distance to the front of the storm and not the
|
||||
// distance to a lightning strike.
|
||||
uint8_t AS3935Component::get_distance_to_storm_() {
|
||||
ESP_LOGV(TAG, "Calling get_distance_to_storm_");
|
||||
return this->read_register_(DISTANCE, DISTANCE_MASK);
|
||||
}
|
||||
|
||||
uint32_t AS3935Component::get_lightning_energy_() {
|
||||
ESP_LOGV(TAG, "Calling get_lightning_energy_");
|
||||
uint32_t pure_light = 0; // Variable for lightning energy which is just a pure number.
|
||||
uint32_t temp = 0;
|
||||
// Temp variable for lightning energy.
|
||||
temp = this->read_register_(ENERGY_LIGHT_MMSB, ENERGY_MASK);
|
||||
// Temporary Value is large enough to handle a shift of 16 bits.
|
||||
pure_light = temp << 16;
|
||||
temp = this->read_register(ENERGY_LIGHT_MSB);
|
||||
// Temporary value is large enough to handle a shift of 8 bits.
|
||||
pure_light |= temp << 8;
|
||||
// No shift here, directly OR'ed into pure_light variable.
|
||||
temp = this->read_register(ENERGY_LIGHT_LSB);
|
||||
pure_light |= temp;
|
||||
return pure_light;
|
||||
}
|
||||
|
||||
uint8_t AS3935Component::read_register_(uint8_t reg, uint8_t mask) {
|
||||
uint8_t value = this->read_register(reg);
|
||||
value &= (~mask);
|
||||
return value;
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR AS3935ComponentStore::gpio_intr(AS3935ComponentStore *arg) { arg->interrupt = true; }
|
||||
|
||||
} // namespace as3935
|
||||
} // namespace esphome
|
||||
119
esphome/components/as3935/as3935.h
Normal file
119
esphome/components/as3935/as3935.h
Normal file
@@ -0,0 +1,119 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935 {
|
||||
|
||||
enum AS3935RegisterNames {
|
||||
AFE_GAIN = 0x00,
|
||||
THRESHOLD,
|
||||
LIGHTNING_REG,
|
||||
INT_MASK_ANT,
|
||||
ENERGY_LIGHT_LSB,
|
||||
ENERGY_LIGHT_MSB,
|
||||
ENERGY_LIGHT_MMSB,
|
||||
DISTANCE,
|
||||
FREQ_DISP_IRQ,
|
||||
CALIB_TRCO = 0x3A,
|
||||
CALIB_SRCO = 0x3B,
|
||||
DEFAULT_RESET = 0x3C,
|
||||
CALIB_RCO = 0x3D
|
||||
};
|
||||
|
||||
enum AS3935RegisterMasks {
|
||||
GAIN_MASK = 0x3E,
|
||||
SPIKE_MASK = 0xF,
|
||||
IO_MASK = 0xC1,
|
||||
DISTANCE_MASK = 0xC0,
|
||||
INT_MASK = 0xF0,
|
||||
THRESH_MASK = 0x0F,
|
||||
R_SPIKE_MASK = 0xF0,
|
||||
ENERGY_MASK = 0xF0,
|
||||
CAP_MASK = 0xF0,
|
||||
LIGHT_MASK = 0xCF,
|
||||
DISTURB_MASK = 0xDF,
|
||||
NOISE_FLOOR_MASK = 0x70,
|
||||
OSC_MASK = 0xE0,
|
||||
CALIB_MASK = 0x7F,
|
||||
DIV_MASK = 0x3F
|
||||
};
|
||||
|
||||
enum AS3935Values {
|
||||
AS3935_ADDR = 0x03,
|
||||
INDOOR = 0x12,
|
||||
OUTDOOR = 0xE,
|
||||
LIGHTNING_INT = 0x08,
|
||||
DISTURBER_INT = 0x04,
|
||||
NOISE_INT = 0x01
|
||||
};
|
||||
|
||||
/// Store data in a class that doesn't use multiple-inheritance (vtables in flash)
|
||||
struct AS3935ComponentStore {
|
||||
volatile bool interrupt;
|
||||
|
||||
ISRInternalGPIOPin *pin;
|
||||
static void gpio_intr(AS3935ComponentStore *arg);
|
||||
};
|
||||
|
||||
class AS3935Component : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void loop() override;
|
||||
|
||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||
void set_distance_sensor(sensor::Sensor *distance_sensor) { distance_sensor_ = distance_sensor; }
|
||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||
void set_thunder_alert_binary_sensor(binary_sensor::BinarySensor *thunder_alert_binary_sensor) {
|
||||
thunder_alert_binary_sensor_ = thunder_alert_binary_sensor;
|
||||
}
|
||||
void set_indoor(bool indoor) { indoor_ = indoor; }
|
||||
void write_indoor(bool indoor);
|
||||
void set_noise_level(uint8_t noise_level) { noise_level_ = noise_level; }
|
||||
void write_noise_level(uint8_t noise_level);
|
||||
void set_watchdog_threshold(uint8_t watchdog_threshold) { watchdog_threshold_ = watchdog_threshold; }
|
||||
void write_watchdog_threshold(uint8_t watchdog_threshold);
|
||||
void set_spike_rejection(uint8_t spike_rejection) { spike_rejection_ = spike_rejection; }
|
||||
void write_spike_rejection(uint8_t write_spike_rejection);
|
||||
void set_lightning_threshold(uint8_t lightning_threshold) { lightning_threshold_ = lightning_threshold; }
|
||||
void write_lightning_threshold(uint8_t lightning_threshold);
|
||||
void set_mask_disturber(bool mask_disturber) { mask_disturber_ = mask_disturber; }
|
||||
void write_mask_disturber(bool enabled);
|
||||
void set_div_ratio(uint8_t div_ratio) { div_ratio_ = div_ratio; }
|
||||
void write_div_ratio(uint8_t div_ratio);
|
||||
void set_capacitance(uint8_t capacitance) { capacitance_ = capacitance; }
|
||||
void write_capacitance(uint8_t capacitance);
|
||||
|
||||
protected:
|
||||
uint8_t read_interrupt_register_();
|
||||
void clear_statistics_();
|
||||
uint8_t get_distance_to_storm_();
|
||||
uint32_t get_lightning_energy_();
|
||||
|
||||
virtual uint8_t read_register(uint8_t reg) = 0;
|
||||
uint8_t read_register_(uint8_t reg, uint8_t mask);
|
||||
|
||||
virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0;
|
||||
|
||||
sensor::Sensor *distance_sensor_;
|
||||
sensor::Sensor *energy_sensor_;
|
||||
binary_sensor::BinarySensor *thunder_alert_binary_sensor_;
|
||||
GPIOPin *pin_;
|
||||
AS3935ComponentStore store_;
|
||||
|
||||
bool indoor_;
|
||||
uint8_t noise_level_;
|
||||
uint8_t watchdog_threshold_;
|
||||
uint8_t spike_rejection_;
|
||||
uint8_t lightning_threshold_;
|
||||
bool mask_disturber_;
|
||||
uint8_t div_ratio_;
|
||||
uint8_t capacitance_;
|
||||
};
|
||||
|
||||
} // namespace as3935
|
||||
} // namespace esphome
|
||||
16
esphome/components/as3935/binary_sensor.py
Normal file
16
esphome/components/as3935/binary_sensor.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from . import AS3935, CONF_AS3935_ID
|
||||
|
||||
DEPENDENCIES = ['as3935']
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
||||
var = yield binary_sensor.new_binary_sensor(config)
|
||||
cg.add(hub.set_thunder_alert_binary_sensor(var))
|
||||
30
esphome/components/as3935/sensor.py
Normal file
30
esphome/components/as3935/sensor.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \
|
||||
UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH
|
||||
from . import AS3935, CONF_AS3935_ID
|
||||
|
||||
DEPENDENCIES = ['as3935']
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
cv.Optional(CONF_DISTANCE):
|
||||
sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1),
|
||||
cv.Optional(CONF_LIGHTNING_ENERGY):
|
||||
sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
||||
|
||||
if CONF_DISTANCE in config:
|
||||
conf = config[CONF_DISTANCE]
|
||||
distance_sensor = yield sensor.new_sensor(conf)
|
||||
cg.add(hub.set_distance_sensor(distance_sensor))
|
||||
|
||||
if CONF_LIGHTNING_ENERGY in config:
|
||||
conf = config[CONF_LIGHTNING_ENERGY]
|
||||
lightning_energy_sensor = yield sensor.new_sensor(conf)
|
||||
cg.add(hub.set_distance_sensor(lightning_energy_sensor))
|
||||
20
esphome/components/as3935_i2c/__init__.py
Normal file
20
esphome/components/as3935_i2c/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import as3935, i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['as3935']
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c')
|
||||
I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03)))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield as3935.setup_as3935(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
36
esphome/components/as3935_i2c/as3935_i2c.cpp
Normal file
36
esphome/components/as3935_i2c/as3935_i2c.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "as3935_i2c.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935_i2c {
|
||||
|
||||
static const char *TAG = "as3935_i2c";
|
||||
|
||||
void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
|
||||
uint8_t write_reg;
|
||||
if (!this->read_byte(reg, &write_reg)) {
|
||||
this->mark_failed();
|
||||
ESP_LOGW(TAG, "read_byte failed - increase log level for more details!");
|
||||
return;
|
||||
}
|
||||
|
||||
write_reg &= (~mask);
|
||||
write_reg |= (bits << start_pos);
|
||||
|
||||
if (!this->write_byte(reg, write_reg)) {
|
||||
ESP_LOGW(TAG, "write_byte failed - increase log level for more details!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t I2CAS3935Component::read_register(uint8_t reg) {
|
||||
uint8_t value;
|
||||
if (!this->read_byte(reg, &value, 2)) {
|
||||
ESP_LOGW(TAG, "Read failed!");
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace as3935_i2c
|
||||
} // 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