Compare commits
575 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ca241615d | ||
|
|
b8aa84002a | ||
|
|
10cc0b1d5b | ||
|
|
11d9c203c1 | ||
|
|
c9ab454c3c | ||
|
|
4a55692885 | ||
|
|
88c129e705 | ||
|
|
80b48f01fb | ||
|
|
642bc91a76 | ||
|
|
d69926ee56 | ||
|
|
4758403d44 | ||
|
|
4b0ec5c28a | ||
|
|
4b2a9e5e49 | ||
|
|
1449c51d49 | ||
|
|
a451705e0b | ||
|
|
2e6db39173 | ||
|
|
373f75253c | ||
|
|
724842084e | ||
|
|
8f3635b167 | ||
|
|
11605a36f7 | ||
|
|
533f81d625 | ||
|
|
aacb9e44e8 | ||
|
|
c6e3f1bca6 | ||
|
|
a933d4aeb6 | ||
|
|
caa5b20791 | ||
|
|
e2ad9ed746 | ||
|
|
32c0e7c2ae | ||
|
|
6c564c7b7f | ||
|
|
c81e3a3be4 | ||
|
|
6b1b9ef7ec | ||
|
|
c26a8b8718 | ||
|
|
4a89a475bd | ||
|
|
8cf15c7f5c | ||
|
|
adc76ca1b8 | ||
|
|
8f8892440c | ||
|
|
570843150d | ||
|
|
f3fc9e4142 | ||
|
|
075fcb77a8 | ||
|
|
e5899ff717 | ||
|
|
2fb3970027 | ||
|
|
1a4efa1b8c | ||
|
|
72f656ffef | ||
|
|
b60239d5e5 | ||
|
|
d02e280c3c | ||
|
|
6535b0966e | ||
|
|
82dbacbee5 | ||
|
|
2432901974 | ||
|
|
ebb5d58c14 | ||
|
|
605e365405 | ||
|
|
5ab995d8ca | ||
|
|
4248741b11 | ||
|
|
4b8ecc7634 | ||
|
|
25d04c759c | ||
|
|
b4ec84030e | ||
|
|
29e8761373 | ||
|
|
a04299c59e | ||
|
|
d7bf3c51d9 | ||
|
|
3ddf5a4ec7 | ||
|
|
f2d6817d8a | ||
|
|
31821e6309 | ||
|
|
7f507935b1 | ||
|
|
3b1ba27043 | ||
|
|
4b7c5aa05c | ||
|
|
5fca02c712 | ||
|
|
e4bbb56f6b | ||
|
|
96d30e28d4 | ||
|
|
41b73ff892 | ||
|
|
afc4e45fb0 | ||
|
|
8778ddd5c5 | ||
|
|
23f1798d20 | ||
|
|
6f3c126805 | ||
|
|
a9ae70cff1 | ||
|
|
d7a8c50c98 | ||
|
|
9e56318498 | ||
|
|
2570f2d6f2 | ||
|
|
359f54d3c1 | ||
|
|
68ce1b18c4 | ||
|
|
76d7802650 | ||
|
|
9be16916b7 | ||
|
|
0ced5509fc | ||
|
|
bd6b9ff1da | ||
|
|
edee28acf0 | ||
|
|
53e8b3ed3e | ||
|
|
6a17fe375e | ||
|
|
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 | ||
|
|
ac0b095941 | ||
|
|
cda9bad233 | ||
|
|
41db8a1264 | ||
|
|
e7e785fd60 | ||
|
|
300d3a1f46 | ||
|
|
356554c08d | ||
|
|
ced28ad006 | ||
|
|
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 |
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: ''
|
||||
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
|
||||
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
|
||||
94
.gitignore
vendored
94
.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,18 +70,46 @@ ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.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
|
||||
|
||||
439
.gitlab-ci.yml
439
.gitlab-ci.yml
@@ -2,176 +2,345 @@
|
||||
# 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: '1.8.3'
|
||||
TZ: UTC
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
.lint: &lint
|
||||
image: esphome/esphome-lint:latest
|
||||
stage: lint
|
||||
before_script:
|
||||
- script/setup
|
||||
tags:
|
||||
- python2.7
|
||||
- esphomeyaml-lint
|
||||
- docker
|
||||
|
||||
.test: &test
|
||||
image: esphome/esphome-lint:latest
|
||||
stage: test
|
||||
before_script:
|
||||
- pip install -e .
|
||||
- script/setup
|
||||
tags:
|
||||
- python2.7
|
||||
- esphomeyaml-test
|
||||
variables:
|
||||
TZ: UTC
|
||||
cache:
|
||||
paths:
|
||||
- tests/build
|
||||
- docker
|
||||
|
||||
.docker-builder: &docker-builder
|
||||
.docker-base: &docker-base
|
||||
image: esphome/esphome-base-builder
|
||||
before_script:
|
||||
- docker info
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
|
||||
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
|
||||
script:
|
||||
- docker run --rm --privileged hassioaddons/qemu-user-static:latest
|
||||
- TAG="${CI_COMMIT_TAG#v}"
|
||||
- TAG="${TAG:-${CI_COMMIT_SHA:0:7}}"
|
||||
- echo "Tag ${TAG}"
|
||||
|
||||
- |
|
||||
if [[ "${IS_HASSIO}" == "YES" ]]; then
|
||||
BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:${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:
|
||||
- hassio-builder
|
||||
- docker
|
||||
stage: deploy
|
||||
|
||||
flake8:
|
||||
lint-custom:
|
||||
<<: *lint
|
||||
script:
|
||||
- flake8 esphomeyaml
|
||||
- script/ci-custom.py
|
||||
|
||||
pylint:
|
||||
lint-python:
|
||||
<<: *lint
|
||||
script:
|
||||
- pylint esphomeyaml
|
||||
- script/lint-python
|
||||
|
||||
test:
|
||||
lint-tidy:
|
||||
<<: *lint
|
||||
script:
|
||||
- pio init --ide atom
|
||||
- |
|
||||
if ! patch -R -p0 -s -f --dry-run <script/.neopixelbus.patch; then
|
||||
patch -p0 < script/.neopixelbus.patch
|
||||
fi
|
||||
- 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:
|
||||
- esphomeyaml tests/test1.yaml compile
|
||||
- esphome tests/test1.yaml compile
|
||||
|
||||
.build-hassio: &build-hassio
|
||||
<<: *docker-builder
|
||||
stage: build
|
||||
test2:
|
||||
<<: *test
|
||||
script:
|
||||
- |
|
||||
hassio-builder.sh \
|
||||
-t . \
|
||||
-i ottowinter/esphomeyaml-hassio-${ADDON_ARCH} \
|
||||
-d "$CI_REGISTRY" \
|
||||
--${ADDON_ARCH}
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:dev" \
|
||||
"${CI_REGISTRY}/ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}"
|
||||
- docker push "${CI_REGISTRY}/ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}"
|
||||
- docker push "${CI_REGISTRY}/ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:dev"
|
||||
retry: 2
|
||||
- esphome tests/test2.yaml compile
|
||||
|
||||
# Generic deploy template
|
||||
.deploy: &deploy
|
||||
<<: *docker-builder
|
||||
test3:
|
||||
<<: *test
|
||||
script:
|
||||
- esphome tests/test3.yaml compile
|
||||
|
||||
.deploy-pypi: &deploy-pypi
|
||||
<<: *lint
|
||||
stage: deploy
|
||||
script:
|
||||
- version=${CI_COMMIT_TAG:1}
|
||||
- echo "Publishing version ${version}"
|
||||
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
|
||||
- docker pull "${CI_REGISTRY}/ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- |
|
||||
docker tag \
|
||||
"ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}" \
|
||||
"ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:latest"
|
||||
- docker push "ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- docker push "ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:latest"
|
||||
|
||||
# Build jobs
|
||||
build:normal:
|
||||
<<: *docker-builder
|
||||
stage: build
|
||||
script:
|
||||
- docker build -t "${CI_REGISTRY}/ottowinter/esphomeyaml:dev" .
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/ottowinter/esphomeyaml:dev" \
|
||||
"${CI_REGISTRY}/ottowinter/esphomeyaml:${CI_COMMIT_SHA}"
|
||||
- docker push "${CI_REGISTRY}/ottowinter/esphomeyaml:${CI_COMMIT_SHA}"
|
||||
- docker push "${CI_REGISTRY}/ottowinter/esphomeyaml:dev"
|
||||
|
||||
build:armhf:
|
||||
<<: *build-hassio
|
||||
variables:
|
||||
ADDON_ARCH: armhf
|
||||
|
||||
#build:aarch64:
|
||||
# <<: *build
|
||||
# variables:
|
||||
# ADDON_ARCH: aarch64
|
||||
|
||||
build:i386:
|
||||
<<: *build-hassio
|
||||
variables:
|
||||
ADDON_ARCH: i386
|
||||
|
||||
build:amd64:
|
||||
<<: *build-hassio
|
||||
variables:
|
||||
ADDON_ARCH: amd64
|
||||
|
||||
# Deploy jobs
|
||||
deploy:armhf:
|
||||
<<: *deploy
|
||||
variables:
|
||||
ADDON_ARCH: armhf
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?$/
|
||||
except:
|
||||
- /^(?!master).+@/
|
||||
|
||||
#deploy:aarch64:
|
||||
# <<: *deploy
|
||||
# variables:
|
||||
# ADDON_ARCH: aarch64
|
||||
# only:
|
||||
# - /^v\d+\.\d+\.\d+(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?$/
|
||||
# except:
|
||||
# - /^(?!master).+@/
|
||||
|
||||
deploy:i386:
|
||||
<<: *deploy
|
||||
variables:
|
||||
ADDON_ARCH: i386
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?$/
|
||||
except:
|
||||
- /^(?!master).+@/
|
||||
|
||||
deploy:amd64:
|
||||
<<: *deploy
|
||||
variables:
|
||||
ADDON_ARCH: amd64
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?$/
|
||||
except:
|
||||
- /^(?!master).+@/
|
||||
|
||||
deploy:pypi:
|
||||
stage: deploy
|
||||
before_script:
|
||||
- pip install -e .
|
||||
- pip install twine
|
||||
script:
|
||||
- python setup.py sdist
|
||||
- pip install twine wheel
|
||||
- python setup.py sdist bdist_wheel
|
||||
- twine upload dist/*
|
||||
tags:
|
||||
- python2.7
|
||||
- esphomeyaml-test
|
||||
|
||||
deploy-release:pypi:
|
||||
<<: *deploy-pypi
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+(?:(?:(?:\+|\.)?[a-zA-Z0-9]+)*)?$/
|
||||
- /^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
|
||||
63
.travis.yml
63
.travis.yml
@@ -1,10 +1,57 @@
|
||||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
- pip install tornado esptool flake8==3.5.0 pylint==1.8.4 tzlocal pillow
|
||||
script:
|
||||
- flake8 esphomeyaml
|
||||
- pylint esphomeyaml
|
||||
python: '2.7'
|
||||
install: script/setup
|
||||
cache:
|
||||
directories:
|
||||
- "~/.platformio"
|
||||
- "$TRAVIS_BUILD_DIR/.piolibdeps"
|
||||
- "$TRAVIS_BUILD_DIR/tests/build/test1/.piolibdeps"
|
||||
- "$TRAVIS_BUILD_DIR/tests/build/test2/.piolibdeps"
|
||||
- "$TRAVIS_BUILD_DIR/tests/build/test3/.piolibdeps"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: "2.7"
|
||||
env: TARGET=Lint2.7
|
||||
script:
|
||||
- script/ci-custom.py
|
||||
- flake8 esphome
|
||||
- pylint esphome
|
||||
- python: "3.5.3"
|
||||
env: TARGET=Lint3.5
|
||||
script:
|
||||
- script/ci-custom.py
|
||||
- flake8 esphome
|
||||
- pylint esphome
|
||||
- python: "2.7"
|
||||
env: TARGET=Test2.7
|
||||
script:
|
||||
- esphome tests/test1.yaml compile
|
||||
- esphome tests/test2.yaml compile
|
||||
- esphome tests/test3.yaml compile
|
||||
- env: TARGET=Cpp-Lint
|
||||
dist: trusty
|
||||
sudo: required
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty-7
|
||||
packages:
|
||||
- clang-tidy-7
|
||||
- clang-format-7
|
||||
before_script:
|
||||
- pio init --ide atom
|
||||
- |
|
||||
if ! patch -R -p0 -s -f --dry-run <script/.neopixelbus.patch; then
|
||||
patch -p0 < script/.neopixelbus.patch
|
||||
fi
|
||||
- clang-tidy-7 -version
|
||||
- clang-format-7 -version
|
||||
- clang-apply-replacements-7 -version
|
||||
script:
|
||||
- script/clang-tidy --all-headers -j 2 --fix
|
||||
- script/clang-format -i -j 2
|
||||
- script/ci-suggest-changes
|
||||
|
||||
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.
|
||||
26
Dockerfile
26
Dockerfile
@@ -1,26 +0,0 @@
|
||||
FROM python:2.7
|
||||
MAINTAINER Otto Winter <contact@otto-winter.com>
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python-pil \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
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 && \
|
||||
pip install --no-cache-dir tornado esptool
|
||||
|
||||
COPY docker/platformio.ini /usr/src/app/
|
||||
RUN platformio settings set enable_telemetry No && \
|
||||
platformio run -e espressif32 -e espressif8266; exit 0
|
||||
|
||||
COPY . .
|
||||
RUN pip install --no-cache-dir -e . && \
|
||||
pip install --no-cache-dir tzlocal
|
||||
|
||||
WORKDIR /config
|
||||
ENTRYPOINT ["esphomeyaml"]
|
||||
692
LICENSE
692
LICENSE
@@ -1,6 +1,17 @@
|
||||
MIT License
|
||||
# ESPHome License
|
||||
|
||||
Copyright (c) 2018 Otto Winter
|
||||
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
|
||||
@@ -19,3 +30,680 @@ 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>.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include esphomeyaml/dashboard/templates/index.html
|
||||
include esphomeyaml/dashboard/static/materialize-stepper.min.css
|
||||
include esphomeyaml/dashboard/static/materialize-stepper.min.js
|
||||
include esphome/dashboard/templates/*.html
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css
|
||||
recursive-include esphome *.cpp *.h *.tcc
|
||||
|
||||
39
README.md
39
README.md
@@ -1,38 +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://esphomelib.com/esphomeyaml/getting-started.html
|
||||
[](https://esphome.io/)
|
||||
|
||||
### Available Components: https://esphomelib.com/esphomeyaml/index.html
|
||||
**Documentation:** 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.
|
||||
For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues).
|
||||
|
||||
esphomeyaml will:
|
||||
|
||||
* 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).
|
||||
|
||||
9
docker/Dockerfile
Normal file
9
docker/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:1.8.3
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
COPY . .
|
||||
RUN pip2 install --no-cache-dir -e .
|
||||
|
||||
WORKDIR /config
|
||||
ENTRYPOINT ["esphome"]
|
||||
CMD ["/config", "dashboard"]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Dockerfile for aarch64 version of HassIO add-on
|
||||
FROM arm64v8/ubuntu:bionic
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python \
|
||||
python-pip \
|
||||
python-setuptools \
|
||||
python-pil \
|
||||
git \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
pip install --no-cache-dir --no-binary :all: platformio && \
|
||||
platformio settings set enable_telemetry No
|
||||
|
||||
COPY docker/platformio.ini /pio/platformio.ini
|
||||
RUN platformio run -d /pio; rm -rf /pio
|
||||
|
||||
COPY . .
|
||||
RUN pip install --no-cache-dir --no-binary :all: -e . && \
|
||||
pip install --no-cache-dir --no-binary :all: tzlocal
|
||||
|
||||
CMD ["esphomeyaml", "/config/esphomeyaml", "dashboard"]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Dockerfile for amd64 version of HassIO add-on
|
||||
FROM ubuntu:bionic
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python \
|
||||
python-pip \
|
||||
python-setuptools \
|
||||
python-pil \
|
||||
git \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
pip install --no-cache-dir --no-binary :all: platformio && \
|
||||
platformio settings set enable_telemetry No
|
||||
|
||||
COPY docker/platformio.ini /pio/platformio.ini
|
||||
RUN platformio run -d /pio; rm -rf /pio
|
||||
|
||||
COPY . .
|
||||
RUN pip install --no-cache-dir --no-binary :all: -e . && \
|
||||
pip install --no-cache-dir --no-binary :all: tzlocal
|
||||
|
||||
CMD ["esphomeyaml", "/config/esphomeyaml", "dashboard"]
|
||||
@@ -1,31 +0,0 @@
|
||||
# Dockerfile for armhf version of HassIO add-on
|
||||
FROM homeassistant/armhf-base:latest
|
||||
|
||||
RUN apk add --no-cache \
|
||||
python2 \
|
||||
python2-dev \
|
||||
py2-pip \
|
||||
git \
|
||||
gcc \
|
||||
openssh \
|
||||
libc6-compat \
|
||||
jpeg-dev \
|
||||
zlib-dev \
|
||||
freetype-dev \
|
||||
lcms2-dev \
|
||||
openjpeg-dev \
|
||||
tiff-dev \
|
||||
libc-dev \
|
||||
linux-headers \
|
||||
&& \
|
||||
pip install --no-cache-dir --no-binary :all: platformio && \
|
||||
platformio settings set enable_telemetry No
|
||||
|
||||
COPY docker/platformio-esp8266.ini /pio/platformio.ini
|
||||
RUN platformio run -d /pio; rm -rf /pio
|
||||
|
||||
COPY . .
|
||||
RUN pip install --no-cache-dir --no-binary :all: -e . && \
|
||||
pip install --no-cache-dir pillow tzlocal
|
||||
|
||||
CMD ["esphomeyaml", "/config/esphomeyaml", "dashboard"]
|
||||
@@ -1,32 +0,0 @@
|
||||
FROM multiarch/ubuntu-core:amd64-xenial
|
||||
|
||||
# setup locals
|
||||
RUN apt-get update && apt-get install -y \
|
||||
jq \
|
||||
git \
|
||||
python3-setuptools \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
ENV LANG C.UTF-8
|
||||
|
||||
# Install docker
|
||||
# https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/
|
||||
RUN apt-get update && apt-get install -y \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
curl \
|
||||
software-properties-common \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - \
|
||||
&& add-apt-repository "deb https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
|
||||
&& apt-get update && apt-get install -y docker-ce \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# setup arm binary support
|
||||
RUN apt-get update && apt-get install -y \
|
||||
qemu-user-static \
|
||||
binfmt-support \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY docker/hassio-builder.sh /usr/bin/
|
||||
|
||||
WORKDIR /data
|
||||
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 pip2 install --no-cache-dir -e /opt/esphome
|
||||
|
||||
# Build arguments
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Labels
|
||||
LABEL \
|
||||
io.hass.name="ESPHome" \
|
||||
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version=${BUILD_VERSION}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Dockerfile for i386 version of HassIO add-on
|
||||
FROM i386/ubuntu:bionic
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python \
|
||||
python-pip \
|
||||
python-setuptools \
|
||||
python-pil \
|
||||
git \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
pip install --no-cache-dir --no-binary :all: platformio && \
|
||||
platformio settings set enable_telemetry No
|
||||
|
||||
COPY docker/platformio.ini /pio/platformio.ini
|
||||
RUN platformio run -d /pio; rm -rf /pio
|
||||
|
||||
COPY . .
|
||||
RUN pip install --no-cache-dir --no-binary :all: -e . && \
|
||||
pip install --no-cache-dir --no-binary :all: tzlocal
|
||||
|
||||
CMD ["esphomeyaml", "/config/esphomeyaml", "dashboard"]
|
||||
@@ -1,6 +1,18 @@
|
||||
FROM python:2.7
|
||||
FROM esphome/esphome-base-amd64:1.8.3
|
||||
|
||||
COPY requirements.txt /requirements.txt
|
||||
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/*
|
||||
|
||||
RUN pip install -r /requirements.txt && \
|
||||
pip install flake8==3.5.0 pylint==1.8.4 tzlocal pillow
|
||||
COPY requirements_test.txt /requirements_test.txt
|
||||
RUN pip2 install --no-cache-dir wheel && pip2 install --no-cache-dir -r /requirements_test.txt
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
FROM ubuntu:bionic
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python \
|
||||
python-pip \
|
||||
python-setuptools \
|
||||
python-pil \
|
||||
git \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/*rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
pip install --no-cache-dir --no-binary :all: platformio && \
|
||||
platformio settings set enable_telemetry No
|
||||
|
||||
COPY docker/platformio.ini /pio/platformio.ini
|
||||
RUN platformio run -d /pio; rm -rf /pio
|
||||
|
||||
COPY requirements.txt /requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir -r /requirements.txt && \
|
||||
pip install --no-cache-dir tzlocal pillow
|
||||
@@ -1,318 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Based on Home Assistant's docker builder
|
||||
######################
|
||||
# Hass.io Build-env
|
||||
######################
|
||||
set -e
|
||||
|
||||
echo -- "$@"
|
||||
|
||||
#### Variable ####
|
||||
|
||||
DOCKER_TIMEOUT=20
|
||||
DOCKER_PID=-1
|
||||
DOCKER_HUB=""
|
||||
DOCKER_CACHE="true"
|
||||
DOCKER_LOCAL="false"
|
||||
TARGET=""
|
||||
IMAGE=""
|
||||
BUILD_LIST=()
|
||||
BUILD_TASKS=()
|
||||
|
||||
#### Misc functions ####
|
||||
|
||||
function print_help() {
|
||||
cat << EOF
|
||||
Hass.io build-env for ecosystem:
|
||||
docker run --rm homeassistant/{arch}-builder:latest [options]
|
||||
|
||||
Options:
|
||||
-h, --help
|
||||
Display this help and exit.
|
||||
|
||||
Repository / Data
|
||||
-t, --target <PATH_TO_BUILD>
|
||||
Set local folder or path inside repository for build.
|
||||
|
||||
Version/Image handling
|
||||
-i, --image <IMAGE_NAME>
|
||||
Overwrite image name of build / support {arch}
|
||||
|
||||
Architecture
|
||||
--armhf
|
||||
Build for arm.
|
||||
--amd64
|
||||
Build for intel/amd 64bit.
|
||||
--aarch64
|
||||
Build for arm 64bit.
|
||||
--i386
|
||||
Build for intel/amd 32bit.
|
||||
--all
|
||||
Build all architecture.
|
||||
|
||||
Build handling
|
||||
--no-cache
|
||||
Disable cache for the build (from latest).
|
||||
-d, --docker-hub <DOCKER_REPOSITORY>
|
||||
Set or overwrite the docker repository.
|
||||
|
||||
Use the host docker socket if mapped into container:
|
||||
/var/run/docker.sock
|
||||
|
||||
EOF
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
#### Docker functions ####
|
||||
|
||||
function start_docker() {
|
||||
local starttime
|
||||
local endtime
|
||||
|
||||
if [ -S "/var/run/docker.sock" ]; then
|
||||
echo "[INFO] Use host docker setup with '/var/run/docker.sock'"
|
||||
DOCKER_LOCAL="true"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[INFO] Starting docker."
|
||||
dockerd 2> /dev/null &
|
||||
DOCKER_PID=$!
|
||||
|
||||
echo "[INFO] Waiting for docker to initialize..."
|
||||
starttime="$(date +%s)"
|
||||
endtime="$(date +%s)"
|
||||
until docker info >/dev/null 2>&1; do
|
||||
if [ $((endtime - starttime)) -le ${DOCKER_TIMEOUT} ]; then
|
||||
sleep 1
|
||||
endtime=$(date +%s)
|
||||
else
|
||||
echo "[ERROR] Timeout while waiting for docker to come up"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "[INFO] Docker was initialized"
|
||||
}
|
||||
|
||||
|
||||
function stop_docker() {
|
||||
local starttime
|
||||
local endtime
|
||||
|
||||
if [ "$DOCKER_LOCAL" == "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[INFO] Stopping in container docker..."
|
||||
if [ "$DOCKER_PID" -gt 0 ] && kill -0 "$DOCKER_PID" 2> /dev/null; then
|
||||
starttime="$(date +%s)"
|
||||
endtime="$(date +%s)"
|
||||
|
||||
# Now wait for it to die
|
||||
kill "$DOCKER_PID"
|
||||
while kill -0 "$DOCKER_PID" 2> /dev/null; do
|
||||
if [ $((endtime - starttime)) -le ${DOCKER_TIMEOUT} ]; then
|
||||
sleep 1
|
||||
endtime=$(date +%s)
|
||||
else
|
||||
echo "[ERROR] Timeout while waiting for container docker to die"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "[WARN] Your host might have been left with unreleased resources"
|
||||
fi
|
||||
}
|
||||
|
||||
function run_build() {
|
||||
local build_dir=$1
|
||||
local repository=$2
|
||||
local image=$3
|
||||
local version=$4
|
||||
local build_arch=$5
|
||||
local docker_cli=("${!6}")
|
||||
|
||||
local push_images=()
|
||||
|
||||
# Overwrites
|
||||
if [ ! -z "$DOCKER_HUB" ]; then repository="$DOCKER_HUB"; fi
|
||||
if [ ! -z "$IMAGE" ]; then image="$IMAGE"; fi
|
||||
|
||||
# Init Cache
|
||||
if [ "$DOCKER_CACHE" == "true" ]; then
|
||||
echo "[INFO] Init cache for $repository/$image:$version"
|
||||
if docker pull "$repository/$image:latest" > /dev/null 2>&1; then
|
||||
docker_cli+=("--cache-from" "$repository/$image:latest")
|
||||
else
|
||||
docker_cli+=("--no-cache")
|
||||
echo "[WARN] No cache image found. Cache is disabled for build"
|
||||
fi
|
||||
else
|
||||
docker_cli+=("--no-cache")
|
||||
fi
|
||||
|
||||
# Build image
|
||||
echo "[INFO] Run build for $repository/$image:$version"
|
||||
docker build --pull -t "$repository/$image:$version" \
|
||||
--label "io.hass.version=$version" \
|
||||
--label "io.hass.arch=$build_arch" \
|
||||
-f "$TARGET/docker/Dockerfile.$build_arch" \
|
||||
"${docker_cli[@]}" \
|
||||
"$build_dir"
|
||||
|
||||
echo "[INFO] Finish build for $repository/$image:$version"
|
||||
docker tag "$repository/$image:$version" "$repository/$image:dev"
|
||||
}
|
||||
|
||||
|
||||
#### HassIO functions ####
|
||||
|
||||
function build_addon() {
|
||||
local build_arch=$1
|
||||
|
||||
local docker_cli=()
|
||||
local image=""
|
||||
local repository=""
|
||||
local raw_image=""
|
||||
local name=""
|
||||
local description=""
|
||||
local url=""
|
||||
local args=""
|
||||
|
||||
# Read addon config.json
|
||||
name="$(jq --raw-output '.name // empty' "$TARGET/esphomeyaml/config.json" | sed "s/'//g")"
|
||||
description="$(jq --raw-output '.description // empty' "$TARGET/esphomeyaml/config.json" | sed "s/'//g")"
|
||||
url="$(jq --raw-output '.url // empty' "$TARGET/esphomeyaml/config.json")"
|
||||
version="$(jq --raw-output '.version' "$TARGET/esphomeyaml/config.json")"
|
||||
raw_image="$(jq --raw-output '.image // empty' "$TARGET/esphomeyaml/config.json")"
|
||||
|
||||
# Read data from image
|
||||
if [ ! -z "$raw_image" ]; then
|
||||
repository="$(echo "$raw_image" | cut -f 1 -d '/')"
|
||||
image="$(echo "$raw_image" | cut -f 2 -d '/')"
|
||||
fi
|
||||
|
||||
# Set additional labels
|
||||
docker_cli+=("--label" "io.hass.name=$name")
|
||||
docker_cli+=("--label" "io.hass.description=$description")
|
||||
docker_cli+=("--label" "io.hass.type=addon")
|
||||
|
||||
if [ ! -z "$url" ]; then
|
||||
docker_cli+=("--label" "io.hass.url=$url")
|
||||
fi
|
||||
|
||||
# Start build
|
||||
run_build "$TARGET" "$repository" "$image" "$version" \
|
||||
"$build_arch" docker_cli[@]
|
||||
}
|
||||
|
||||
#### initialized cross-build ####
|
||||
|
||||
function init_crosscompile() {
|
||||
echo "[INFO] Setup crosscompiling feature"
|
||||
(
|
||||
mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
|
||||
update-binfmts --enable qemu-arm
|
||||
update-binfmts --enable qemu-aarch64
|
||||
) > /dev/null 2>&1 || echo "[WARN] Can't enable crosscompiling feature"
|
||||
}
|
||||
|
||||
|
||||
function clean_crosscompile() {
|
||||
echo "[INFO] Clean crosscompiling feature"
|
||||
if [ -f /proc/sys/fs/binfmt_misc ]; then
|
||||
umount /proc/sys/fs/binfmt_misc || true
|
||||
fi
|
||||
|
||||
(
|
||||
update-binfmts --disable qemu-arm
|
||||
update-binfmts --disable qemu-aarch64
|
||||
) > /dev/null 2>&1 || echo "[WARN] No crosscompiling feature found for cleanup"
|
||||
}
|
||||
|
||||
#### Error handling ####
|
||||
|
||||
function error_handling() {
|
||||
stop_docker
|
||||
clean_crosscompile
|
||||
|
||||
exit 1
|
||||
}
|
||||
trap 'error_handling' SIGINT SIGTERM
|
||||
|
||||
#### Parse arguments ####
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
key=$1
|
||||
case ${key} in
|
||||
-h|--help)
|
||||
print_help
|
||||
;;
|
||||
-t|--target)
|
||||
TARGET=$2
|
||||
shift
|
||||
;;
|
||||
-i|--image)
|
||||
IMAGE=$2
|
||||
shift
|
||||
;;
|
||||
--no-cache)
|
||||
DOCKER_CACHE="false"
|
||||
;;
|
||||
-d|--docker-hub)
|
||||
DOCKER_HUB=$2
|
||||
shift
|
||||
;;
|
||||
--armhf)
|
||||
BUILD_LIST+=("armhf")
|
||||
;;
|
||||
--amd64)
|
||||
BUILD_LIST+=("amd64")
|
||||
;;
|
||||
--i386)
|
||||
BUILD_LIST+=("i386")
|
||||
;;
|
||||
--aarch64)
|
||||
BUILD_LIST+=("aarch64")
|
||||
;;
|
||||
--all)
|
||||
BUILD_LIST=("armhf" "amd64" "i386" "aarch64")
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "[WARN] $0 : Argument '$1' unknown will be Ignoring"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Check if an architecture is available
|
||||
if [ "${#BUILD_LIST[@]}" -eq 0 ]; then
|
||||
echo "[ERROR] You need select an architecture for build!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
#### Main ####
|
||||
|
||||
mkdir -p /data
|
||||
|
||||
# Setup docker env
|
||||
init_crosscompile
|
||||
start_docker
|
||||
|
||||
# Select arch build
|
||||
for arch in "${BUILD_LIST[@]}"; do
|
||||
(build_addon "$arch") &
|
||||
BUILD_TASKS+=($!)
|
||||
done
|
||||
|
||||
# Wait until all build jobs are done
|
||||
wait "${BUILD_TASKS[@]}"
|
||||
|
||||
# Cleanup docker env
|
||||
clean_crosscompile
|
||||
stop_docker
|
||||
|
||||
exit 0
|
||||
@@ -1,7 +0,0 @@
|
||||
; This file allows the docker build file to install the required platformio
|
||||
; platforms
|
||||
|
||||
[env:espressif8266]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
@@ -1,12 +0,0 @@
|
||||
; This file allows the docker build file to install the required platformio
|
||||
; platforms
|
||||
|
||||
[env:espressif8266]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
|
||||
[env:espressif32]
|
||||
platform = espressif32
|
||||
board = nodemcu-32s
|
||||
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})..."
|
||||
pip2 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
|
||||
492
esphome/__main__.py
Normal file
492
esphome/__main__.py
Normal file
@@ -0,0 +1,492 @@
|
||||
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
|
||||
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
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_serial_ports():
|
||||
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
||||
from serial.tools.list_ports import comports
|
||||
result = []
|
||||
for port, desc, info in comports():
|
||||
if not port:
|
||||
continue
|
||||
if "VID:PID" in info:
|
||||
result.append((port, desc))
|
||||
result.sort(key=lambda x: x[0])
|
||||
return result
|
||||
|
||||
|
||||
def choose_prompt(options):
|
||||
if not options:
|
||||
raise ValueError
|
||||
|
||||
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')
|
||||
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, args.verbose)
|
||||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
path = CORE.firmware_bin
|
||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
||||
|
||||
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, args.verbose, host)
|
||||
|
||||
from esphome import espota2
|
||||
|
||||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
password = ota_conf[CONF_PASSWORD]
|
||||
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
return res
|
||||
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
def command_config(args, config):
|
||||
_LOGGER.info("Configuration is valid!")
|
||||
if not args.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
|
||||
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)
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
'wizard': command_wizard,
|
||||
'version': command_version,
|
||||
'dashboard': command_dashboard,
|
||||
'vscode': command_vscode,
|
||||
}
|
||||
|
||||
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(prog='esphome')
|
||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
||||
parser.add_argument('configuration', help='Your YAML configuration file.')
|
||||
|
||||
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("--password", help="The optional password to require for all requests.",
|
||||
type=str, default='')
|
||||
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
||||
action='store_true')
|
||||
dashboard.add_argument("--hassio",
|
||||
help=argparse.SUPPRESS,
|
||||
action="store_true")
|
||||
dashboard.add_argument("--socket",
|
||||
help="Make the dashboard serve under a unix socket", type=str)
|
||||
|
||||
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
||||
vscode.add_argument('--ace', action='store_true')
|
||||
|
||||
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 in PRE_CONFIG_ACTIONS:
|
||||
try:
|
||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
|
||||
CORE.config_path = args.configuration
|
||||
|
||||
config = read_config(args.verbose)
|
||||
if config is None:
|
||||
return 1
|
||||
CORE.config = config
|
||||
|
||||
if args.command in POST_CONFIG_ACTIONS:
|
||||
try:
|
||||
return POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
safe_print(u"Unknown command {}".format(args.command))
|
||||
return 1
|
||||
|
||||
|
||||
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())
|
||||
330
esphome/api/api.proto
Normal file
330
esphome/api/api.proto
Normal file
@@ -0,0 +1,330 @@
|
||||
syntax = "proto3";
|
||||
|
||||
// The Home Assistant protocol is structured as a simple
|
||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
||||
// First, a message in this protocol has a specific format:
|
||||
// * VarInt denoting the size of the message object. (type is not part of this)
|
||||
// * VarInt denoting the type of message.
|
||||
// * The message object encoded as a ProtoBuf message
|
||||
|
||||
// The connection is established in 4 steps:
|
||||
// * First, the client connects to the server and sends a "Hello Request" identifying itself
|
||||
// * The server responds with a "Hello Response" and selects the protocol version
|
||||
// * After receiving this message, the client attempts to authenticate itself using
|
||||
// the password and a "Connect Request"
|
||||
// * The server responds with a "Connect Response" and notifies of invalid password.
|
||||
// If anything in this initial process fails, the connection must immediately closed
|
||||
// by both sides and _no_ disconnection message is to be sent.
|
||||
|
||||
// Message sent at the beginning of each connection
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message HelloRequest {
|
||||
// Description of client (like User Agent)
|
||||
// For example "Home Assistant"
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message HelloResponse {
|
||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
||||
// for compatibility and if necessary adopt to an older API.
|
||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
||||
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
|
||||
uint32 api_version_major = 1;
|
||||
uint32 api_version_minor = 2;
|
||||
|
||||
// A string identifying the server (ESP); like client info this may be empty
|
||||
// and only exists for debugging/logging purposes.
|
||||
// For example "ESPHome v1.10.0 on ESP8266"
|
||||
string server_info = 3;
|
||||
}
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message ConnectRequest {
|
||||
// The password to log in with
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message ConnectResponse {
|
||||
bool invalid_password = 1;
|
||||
}
|
||||
|
||||
// Request to close the connection.
|
||||
// Can be sent by both the client and server
|
||||
message DisconnectRequest {
|
||||
// Do not close the connection before the acknowledgement arrives
|
||||
}
|
||||
|
||||
message DisconnectResponse {
|
||||
// Empty - Both parties are required to close the connection after this
|
||||
// message has been received.
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message PingResponse {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoResponse {
|
||||
bool uses_password = 1;
|
||||
|
||||
// The name of the node, given by "App.set_name()"
|
||||
string name = 2;
|
||||
|
||||
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
|
||||
string mac_address = 3;
|
||||
|
||||
// A string describing the ESPHome version. For example "1.10.0"
|
||||
string esphome_core_version = 4;
|
||||
|
||||
// A string describing the date of compilation, this is generated by the compiler
|
||||
// and therefore may not be in the same format all the time.
|
||||
// If the user isn't using esphome, this will also not be set.
|
||||
string compilation_time = 5;
|
||||
|
||||
// The model of the board. For example NodeMCU
|
||||
string model = 6;
|
||||
|
||||
bool has_deep_sleep = 7;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
}
|
||||
message ListEntitiesCoverResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool is_optimistic = 5;
|
||||
}
|
||||
message ListEntitiesFanResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
}
|
||||
message ListEntitiesLightResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_brightness = 5;
|
||||
bool supports_rgb = 6;
|
||||
bool supports_white_value = 7;
|
||||
bool supports_color_temperature = 8;
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
}
|
||||
message ListEntitiesSensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
}
|
||||
message ListEntitiesSwitchResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool optimistic = 6;
|
||||
}
|
||||
message ListEntitiesTextSensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
}
|
||||
message ListEntitiesDoneResponse {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message SubscribeStatesRequest {
|
||||
// Empty
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
message CoverStateResponse {
|
||||
fixed32 key = 1;
|
||||
enum CoverState {
|
||||
OPEN = 0;
|
||||
CLOSED = 1;
|
||||
}
|
||||
CoverState state = 2;
|
||||
}
|
||||
enum FanSpeed {
|
||||
LOW = 0;
|
||||
MEDIUM = 1;
|
||||
HIGH = 2;
|
||||
}
|
||||
message FanStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
}
|
||||
message LightStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
float white = 7;
|
||||
float color_temperature = 8;
|
||||
string effect = 9;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
float state = 2;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
message CoverCommandRequest {
|
||||
fixed32 key = 1;
|
||||
enum CoverCommand {
|
||||
OPEN = 0;
|
||||
CLOSE = 1;
|
||||
STOP = 2;
|
||||
}
|
||||
bool has_state = 2;
|
||||
CoverCommand command = 3;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
}
|
||||
message LightCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_rgb = 6;
|
||||
float red = 7;
|
||||
float green = 8;
|
||||
float blue = 9;
|
||||
bool has_white = 10;
|
||||
float white = 11;
|
||||
bool has_color_temperature = 12;
|
||||
float color_temperature = 13;
|
||||
bool has_transition_length = 14;
|
||||
uint32 transition_length = 15;
|
||||
bool has_flash_length = 16;
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19;
|
||||
}
|
||||
message SwitchCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
enum LogLevel {
|
||||
NONE = 0;
|
||||
ERROR = 1;
|
||||
WARN = 2;
|
||||
INFO = 3;
|
||||
DEBUG = 4;
|
||||
VERBOSE = 5;
|
||||
VERY_VERBOSE = 6;
|
||||
}
|
||||
|
||||
message SubscribeLogsRequest {
|
||||
LogLevel level = 1;
|
||||
bool dump_config = 2;
|
||||
}
|
||||
|
||||
message SubscribeLogsResponse {
|
||||
LogLevel level = 1;
|
||||
string tag = 2;
|
||||
string message = 3;
|
||||
bool send_failed = 4;
|
||||
}
|
||||
|
||||
message SubscribeServiceCallsRequest {
|
||||
|
||||
}
|
||||
|
||||
message ServiceCallResponse {
|
||||
string service = 1;
|
||||
map<string, string> data = 2;
|
||||
map<string, string> data_template = 3;
|
||||
map<string, string> variables = 4;
|
||||
}
|
||||
|
||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
||||
message SubscribeHomeAssistantStatesRequest {
|
||||
|
||||
}
|
||||
|
||||
message SubscribeHomeAssistantStateResponse {
|
||||
string entity_id = 1;
|
||||
}
|
||||
|
||||
message HomeAssistantStateResponse {
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
message GetTimeRequest {
|
||||
|
||||
}
|
||||
|
||||
message GetTimeResponse {
|
||||
fixed32 epoch_seconds = 1;
|
||||
}
|
||||
|
||||
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
496
esphome/api/client.py
Normal file
496
esphome/api/client.py
Normal file
@@ -0,0 +1,496 @@
|
||||
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, format_bytes
|
||||
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=1):
|
||||
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=1):
|
||||
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()
|
||||
|
||||
def _check_authenticated(self):
|
||||
if not self._authenticated:
|
||||
raise APIConnectionError("Must login first!")
|
||||
|
||||
def subscribe_logs(self, on_log, log_level=None, dump_config=False):
|
||||
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)
|
||||
if log_level is not None:
|
||||
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()
|
||||
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 = min(2**tries, 300)
|
||||
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/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]))
|
||||
21
esphome/components/ads1115/__init__.py
Normal file
21
esphome/components/ads1115/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
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)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||
}).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)
|
||||
164
esphome/components/ads1115/ads1115.cpp
Normal file
164
esphome/components/ads1115/ads1115.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
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::get_setup_priority() const { return setup_priority::DATA; }
|
||||
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
uint16_t config;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONFIG, &config)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
// Multiplexer
|
||||
// 0bxBBBxxxxxxxxxxxx
|
||||
config &= 0b1000111111111111;
|
||||
config |= (sensor->get_multiplexer() & 0b111) << 12;
|
||||
|
||||
// Gain
|
||||
// 0bxxxxBBBxxxxxxxxx
|
||||
config &= 0b1111000111111111;
|
||||
config |= (sensor->get_gain() & 0b111) << 9;
|
||||
// Start conversion
|
||||
config |= 0b1000000000000000;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
uint8_t ADS1115Sensor::get_multiplexer() const { return this->multiplexer_; }
|
||||
void ADS1115Sensor::set_multiplexer(ADS1115Multiplexer multiplexer) { this->multiplexer_ = multiplexer; }
|
||||
uint8_t ADS1115Sensor::get_gain() const { return this->gain_; }
|
||||
void ADS1115Sensor::set_gain(ADS1115Gain gain) { this->gain_ = gain; }
|
||||
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
|
||||
void ADS1115Sensor::update() {
|
||||
float v = this->parent_->request_measurement(this);
|
||||
if (!isnan(v)) {
|
||||
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
|
||||
this->publish_state(v);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
68
esphome/components/ads1115/ads1115.h
Normal file
68
esphome/components/ads1115/ads1115.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#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;
|
||||
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
float request_measurement(ADS1115Sensor *sensor);
|
||||
|
||||
protected:
|
||||
std::vector<ADS1115Sensor *> sensors_;
|
||||
};
|
||||
|
||||
/// 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);
|
||||
void set_gain(ADS1115Gain gain);
|
||||
|
||||
float sample() override;
|
||||
uint8_t get_multiplexer() const;
|
||||
uint8_t get_gain() const;
|
||||
|
||||
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))
|
||||
118
esphome/components/api/__init__.py
Normal file
118
esphome/components/api/__init__.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
||||
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
DEPENDENCIES = ['network']
|
||||
|
||||
api_ns = cg.esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
||||
KeyValuePair = api_ns.class_('KeyValuePair')
|
||||
TemplatableKeyValuePair = api_ns.class_('TemplatableKeyValuePair')
|
||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
||||
|
||||
UserService = api_ns.class_('UserService', automation.Trigger)
|
||||
ServiceTypeArgument = api_ns.class_('ServiceTypeArgument')
|
||||
ServiceArgType = api_ns.enum('ServiceArgType')
|
||||
SERVICE_ARG_TYPES = {
|
||||
'bool': ServiceArgType.SERVICE_ARG_TYPE_BOOL,
|
||||
'int': ServiceArgType.SERVICE_ARG_TYPE_INT,
|
||||
'float': ServiceArgType.SERVICE_ARG_TYPE_FLOAT,
|
||||
'string': ServiceArgType.SERVICE_ARG_TYPE_STRING,
|
||||
}
|
||||
SERVICE_ARG_NATIVE_TYPES = {
|
||||
'bool': bool,
|
||||
'int': cg.int32,
|
||||
'float': float,
|
||||
'string': cg.std_string,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='5min'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserService),
|
||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_TYPES, lower=True),
|
||||
}),
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@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_type_args = []
|
||||
for name, var_ in conf[CONF_VARIABLES].items():
|
||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||
template_args.append(native)
|
||||
func_args.append((native, name))
|
||||
service_type_args.append(ServiceTypeArgument(name, SERVICE_ARG_TYPES[var_]))
|
||||
templ = cg.TemplateArguments(*template_args)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
||||
conf[CONF_SERVICE], service_type_args)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
yield automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
cg.add_define('USE_API')
|
||||
if CORE.is_esp32:
|
||||
cg.add_library('AsyncTCP', '1.0.3')
|
||||
elif CORE.is_esp8266:
|
||||
cg.add_library('ESPAsyncTCP', '1.2.0')
|
||||
|
||||
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_SERVICE): cv.string,
|
||||
cv.Optional(CONF_DATA): cv.Schema({
|
||||
cv.string: cv.string,
|
||||
}),
|
||||
cv.Optional(CONF_DATA_TEMPLATE): cv.Schema({
|
||||
cv.string: cv.string,
|
||||
}),
|
||||
cv.Optional(CONF_VARIABLES): cv.Schema({
|
||||
cv.string: cv.returning_lambda,
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
@automation.register_action('homeassistant.service', HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv)
|
||||
cg.add(var.set_service(config[CONF_SERVICE]))
|
||||
if CONF_DATA in config:
|
||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA].items()]
|
||||
cg.add(var.set_data(datas))
|
||||
if CONF_DATA_TEMPLATE in config:
|
||||
datas = [KeyValuePair(k, v) for k, v in config[CONF_DATA_TEMPLATE].items()]
|
||||
cg.add(var.set_data_template(datas))
|
||||
if CONF_VARIABLES in config:
|
||||
datas = []
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
value_ = yield cg.process_lambda(value, [])
|
||||
datas.append(TemplatableKeyValuePair(key, value_))
|
||||
cg.add(var.set_variables(datas))
|
||||
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)
|
||||
506
esphome/components/api/api.proto
Normal file
506
esphome/components/api/api.proto
Normal file
@@ -0,0 +1,506 @@
|
||||
syntax = "proto3";
|
||||
|
||||
|
||||
// ==================== 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
|
||||
// ID: 1
|
||||
message HelloRequest {
|
||||
// Description of client (like User Agent)
|
||||
// For example "Home Assistant"
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
// ID: 2
|
||||
message HelloResponse {
|
||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
||||
// for compatibility and if necessary adopt to an older API.
|
||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
||||
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
|
||||
uint32 api_version_major = 1;
|
||||
uint32 api_version_minor = 2;
|
||||
|
||||
// A string identifying the server (ESP); like client info this may be empty
|
||||
// and only exists for debugging/logging purposes.
|
||||
// For example "ESPHome v1.10.0 on ESP8266"
|
||||
string server_info = 3;
|
||||
}
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
// ID: 3
|
||||
message ConnectRequest {
|
||||
// The password to log in with
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
// ID: 4
|
||||
message ConnectResponse {
|
||||
bool invalid_password = 1;
|
||||
}
|
||||
|
||||
// Request to close the connection.
|
||||
// Can be sent by both the client and server
|
||||
// ID: 5
|
||||
message DisconnectRequest {
|
||||
// Do not close the connection before the acknowledgement arrives
|
||||
}
|
||||
|
||||
// ID: 6
|
||||
message DisconnectResponse {
|
||||
// Empty - Both parties are required to close the connection after this
|
||||
// message has been received.
|
||||
}
|
||||
|
||||
// ID: 7
|
||||
message PingRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ID: 8
|
||||
message PingResponse {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ID: 9
|
||||
message DeviceInfoRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ID: 10
|
||||
message DeviceInfoResponse {
|
||||
bool uses_password = 1;
|
||||
|
||||
// The name of the node, given by "App.set_name()"
|
||||
string name = 2;
|
||||
|
||||
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
|
||||
string mac_address = 3;
|
||||
|
||||
// A string describing the ESPHome version. For example "1.10.0"
|
||||
string esphome_core_version = 4;
|
||||
|
||||
// A string describing the date of compilation, this is generated by the compiler
|
||||
// and therefore may not be in the same format all the time.
|
||||
// If the user isn't using ESPHome, this will also not be set.
|
||||
string compilation_time = 5;
|
||||
|
||||
// The model of the board. For example NodeMCU
|
||||
string model = 6;
|
||||
|
||||
bool has_deep_sleep = 7;
|
||||
}
|
||||
|
||||
// ID: 11
|
||||
message ListEntitiesRequest {
|
||||
// Empty
|
||||
}
|
||||
// ID: 19
|
||||
message ListEntitiesDoneResponse {
|
||||
// Empty
|
||||
}
|
||||
// ID: 20
|
||||
message SubscribeStatesRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ==================== BINARY SENSOR ====================
|
||||
// ID: 12
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
}
|
||||
// ID: 21
|
||||
message BinarySensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
// ==================== COVER ====================
|
||||
// ID: 13
|
||||
message ListEntitiesCoverResponse {
|
||||
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;
|
||||
}
|
||||
// ID: 22
|
||||
message CoverStateResponse {
|
||||
fixed32 key = 1;
|
||||
|
||||
// legacy: state has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
enum LegacyCoverState {
|
||||
OPEN = 0;
|
||||
CLOSED = 1;
|
||||
}
|
||||
LegacyCoverState legacy_state = 2;
|
||||
|
||||
float position = 3;
|
||||
float tilt = 4;
|
||||
enum CoverOperation {
|
||||
IDLE = 0;
|
||||
IS_OPENING = 1;
|
||||
IS_CLOSING = 2;
|
||||
}
|
||||
CoverOperation current_operation = 5;
|
||||
}
|
||||
// ID: 30
|
||||
message CoverCommandRequest {
|
||||
fixed32 key = 1;
|
||||
|
||||
// legacy: command has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
enum LegacyCoverCommand {
|
||||
OPEN = 0;
|
||||
CLOSE = 1;
|
||||
STOP = 2;
|
||||
}
|
||||
bool has_legacy_command = 2;
|
||||
LegacyCoverCommand legacy_command = 3;
|
||||
|
||||
bool has_position = 4;
|
||||
float position = 5;
|
||||
bool has_tilt = 6;
|
||||
float tilt = 7;
|
||||
bool stop = 8;
|
||||
}
|
||||
|
||||
// ==================== FAN ====================
|
||||
// ID: 14
|
||||
message ListEntitiesFanResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
}
|
||||
enum FanSpeed {
|
||||
LOW = 0;
|
||||
MEDIUM = 1;
|
||||
HIGH = 2;
|
||||
}
|
||||
// ID: 23
|
||||
message FanStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
}
|
||||
// ID: 31
|
||||
message FanCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
// ID: 15
|
||||
message ListEntitiesLightResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_brightness = 5;
|
||||
bool supports_rgb = 6;
|
||||
bool supports_white_value = 7;
|
||||
bool supports_color_temperature = 8;
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
}
|
||||
// ID: 24
|
||||
message LightStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
float white = 7;
|
||||
float color_temperature = 8;
|
||||
string effect = 9;
|
||||
}
|
||||
// ID: 32
|
||||
message LightCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_rgb = 6;
|
||||
float red = 7;
|
||||
float green = 8;
|
||||
float blue = 9;
|
||||
bool has_white = 10;
|
||||
float white = 11;
|
||||
bool has_color_temperature = 12;
|
||||
float color_temperature = 13;
|
||||
bool has_transition_length = 14;
|
||||
uint32 transition_length = 15;
|
||||
bool has_flash_length = 16;
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19;
|
||||
}
|
||||
|
||||
// ==================== SENSOR ====================
|
||||
// ID: 16
|
||||
message ListEntitiesSensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
}
|
||||
// ID: 25
|
||||
message SensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
float state = 2;
|
||||
}
|
||||
|
||||
// ==================== SWITCH ====================
|
||||
// ID: 17
|
||||
message ListEntitiesSwitchResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool assumed_state = 6;
|
||||
}
|
||||
// ID: 26
|
||||
message SwitchStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
// ID: 33
|
||||
message SwitchCommandRequest {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
// ==================== TEXT SENSOR ====================
|
||||
// ID: 18
|
||||
message ListEntitiesTextSensorResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
}
|
||||
// ID: 27
|
||||
message TextSensorStateResponse {
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
// ==================== SUBSCRIBE LOGS ====================
|
||||
enum LogLevel {
|
||||
NONE = 0;
|
||||
ERROR = 1;
|
||||
WARN = 2;
|
||||
INFO = 3;
|
||||
DEBUG = 4;
|
||||
VERBOSE = 5;
|
||||
VERY_VERBOSE = 6;
|
||||
}
|
||||
// ID: 28
|
||||
message SubscribeLogsRequest {
|
||||
LogLevel level = 1;
|
||||
bool dump_config = 2;
|
||||
}
|
||||
// ID: 29
|
||||
message SubscribeLogsResponse {
|
||||
LogLevel level = 1;
|
||||
string tag = 2;
|
||||
string message = 3;
|
||||
bool send_failed = 4;
|
||||
}
|
||||
|
||||
// ==================== HOMEASSISTANT.SERVICE ====================
|
||||
// ID: 34
|
||||
message SubscribeServiceCallsRequest {
|
||||
|
||||
}
|
||||
|
||||
// ID: 35
|
||||
message ServiceCallResponse {
|
||||
string service = 1;
|
||||
map<string, string> data = 2;
|
||||
map<string, string> data_template = 3;
|
||||
map<string, string> variables = 4;
|
||||
}
|
||||
|
||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
||||
// ID: 38
|
||||
message SubscribeHomeAssistantStatesRequest {
|
||||
|
||||
}
|
||||
|
||||
// ID: 39
|
||||
message SubscribeHomeAssistantStateResponse {
|
||||
string entity_id = 1;
|
||||
}
|
||||
|
||||
// ID: 40
|
||||
message HomeAssistantStateResponse {
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
// ==================== IMPORT TIME ====================
|
||||
// ID: 36
|
||||
message GetTimeRequest {
|
||||
|
||||
}
|
||||
|
||||
// ID: 37
|
||||
message GetTimeResponse {
|
||||
fixed32 epoch_seconds = 1;
|
||||
}
|
||||
|
||||
// ==================== USER-DEFINES SERVICES ====================
|
||||
message ListEntitiesServicesArgument {
|
||||
string name = 1;
|
||||
enum Type {
|
||||
BOOL = 0;
|
||||
INT = 1;
|
||||
FLOAT = 2;
|
||||
STRING = 3;
|
||||
}
|
||||
Type type = 2;
|
||||
}
|
||||
// ID: 41
|
||||
message ListEntitiesServicesResponse {
|
||||
string name = 1;
|
||||
fixed32 key = 2;
|
||||
repeated ListEntitiesServicesArgument args = 3;
|
||||
}
|
||||
message ExecuteServiceArgument {
|
||||
bool bool_ = 1;
|
||||
int32 int_ = 2;
|
||||
float float_ = 3;
|
||||
string string_ = 4;
|
||||
}
|
||||
// ID: 42
|
||||
message ExecuteServiceRequest {
|
||||
fixed32 key = 1;
|
||||
repeated ExecuteServiceArgument args = 2;
|
||||
}
|
||||
|
||||
// ==================== CAMERA ====================
|
||||
// ID: 43
|
||||
message ListEntitiesCameraResponse {
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
}
|
||||
|
||||
// ID: 44
|
||||
message CameraImageResponse {
|
||||
fixed32 key = 1;
|
||||
bytes data = 2;
|
||||
bool done = 3;
|
||||
}
|
||||
// ID: 45
|
||||
message CameraImageRequest {
|
||||
bool single = 1;
|
||||
bool stream = 2;
|
||||
}
|
||||
|
||||
// ==================== CLIMATE ====================
|
||||
enum ClimateMode {
|
||||
OFF = 0;
|
||||
AUTO = 1;
|
||||
COOL = 2;
|
||||
HEAT = 3;
|
||||
}
|
||||
// ID: 46
|
||||
message ListEntitiesClimateResponse {
|
||||
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;
|
||||
}
|
||||
// ID: 47
|
||||
message ClimateStateResponse {
|
||||
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;
|
||||
}
|
||||
// ID: 48
|
||||
message ClimateCommandRequest {
|
||||
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;
|
||||
}
|
||||
87
esphome/components/api/api_message.cpp
Normal file
87
esphome/components/api/api_message.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "api_message.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.message";
|
||||
|
||||
bool APIMessage::decode_varint(uint32_t field_id, uint32_t value) { return false; }
|
||||
bool APIMessage::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) { return false; }
|
||||
bool APIMessage::decode_32bit(uint32_t field_id, uint32_t value) { return false; }
|
||||
void APIMessage::encode(APIBuffer &buffer) {}
|
||||
void APIMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
uint32_t i = 0;
|
||||
bool error = false;
|
||||
while (i < length) {
|
||||
uint32_t consumed;
|
||||
auto res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid field start at %u", i);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t field_type = (*res) & 0b111;
|
||||
uint32_t field_id = (*res) >> 3;
|
||||
i += consumed;
|
||||
|
||||
switch (field_type) {
|
||||
case 0: { // VarInt
|
||||
res = proto_decode_varuint32(&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);
|
||||
}
|
||||
i += consumed;
|
||||
break;
|
||||
}
|
||||
case 2: { // Length-delimited
|
||||
res = proto_decode_varuint32(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
i += consumed;
|
||||
if (*res > length - i) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_length_delimited(field_id, &buffer[i], *res)) {
|
||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
|
||||
}
|
||||
i += *res;
|
||||
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, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
79
esphome/components/api/api_message.h
Normal file
79
esphome/components/api/api_message.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
enum class APIMessageType {
|
||||
HELLO_REQUEST = 1,
|
||||
HELLO_RESPONSE = 2,
|
||||
CONNECT_REQUEST = 3,
|
||||
CONNECT_RESPONSE = 4,
|
||||
DISCONNECT_REQUEST = 5,
|
||||
DISCONNECT_RESPONSE = 6,
|
||||
PING_REQUEST = 7,
|
||||
PING_RESPONSE = 8,
|
||||
DEVICE_INFO_REQUEST = 9,
|
||||
DEVICE_INFO_RESPONSE = 10,
|
||||
|
||||
LIST_ENTITIES_REQUEST = 11,
|
||||
LIST_ENTITIES_BINARY_SENSOR_RESPONSE = 12,
|
||||
LIST_ENTITIES_COVER_RESPONSE = 13,
|
||||
LIST_ENTITIES_FAN_RESPONSE = 14,
|
||||
LIST_ENTITIES_LIGHT_RESPONSE = 15,
|
||||
LIST_ENTITIES_SENSOR_RESPONSE = 16,
|
||||
LIST_ENTITIES_SWITCH_RESPONSE = 17,
|
||||
LIST_ENTITIES_TEXT_SENSOR_RESPONSE = 18,
|
||||
LIST_ENTITIES_SERVICE_RESPONSE = 41,
|
||||
LIST_ENTITIES_CAMERA_RESPONSE = 43,
|
||||
LIST_ENTITIES_CLIMATE_RESPONSE = 46,
|
||||
LIST_ENTITIES_DONE_RESPONSE = 19,
|
||||
|
||||
SUBSCRIBE_STATES_REQUEST = 20,
|
||||
BINARY_SENSOR_STATE_RESPONSE = 21,
|
||||
COVER_STATE_RESPONSE = 22,
|
||||
FAN_STATE_RESPONSE = 23,
|
||||
LIGHT_STATE_RESPONSE = 24,
|
||||
SENSOR_STATE_RESPONSE = 25,
|
||||
SWITCH_STATE_RESPONSE = 26,
|
||||
TEXT_SENSOR_STATE_RESPONSE = 27,
|
||||
CAMERA_IMAGE_RESPONSE = 44,
|
||||
CLIMATE_STATE_RESPONSE = 47,
|
||||
|
||||
SUBSCRIBE_LOGS_REQUEST = 28,
|
||||
SUBSCRIBE_LOGS_RESPONSE = 29,
|
||||
|
||||
COVER_COMMAND_REQUEST = 30,
|
||||
FAN_COMMAND_REQUEST = 31,
|
||||
LIGHT_COMMAND_REQUEST = 32,
|
||||
SWITCH_COMMAND_REQUEST = 33,
|
||||
CAMERA_IMAGE_REQUEST = 45,
|
||||
CLIMATE_COMMAND_REQUEST = 48,
|
||||
|
||||
SUBSCRIBE_SERVICE_CALLS_REQUEST = 34,
|
||||
SERVICE_CALL_RESPONSE = 35,
|
||||
GET_TIME_REQUEST = 36,
|
||||
GET_TIME_RESPONSE = 37,
|
||||
|
||||
SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST = 38,
|
||||
SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE = 39,
|
||||
HOME_ASSISTANT_STATE_RESPONSE = 40,
|
||||
|
||||
EXECUTE_SERVICE_REQUEST = 42,
|
||||
};
|
||||
|
||||
class APIMessage {
|
||||
public:
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
virtual bool decode_varint(uint32_t field_id, uint32_t value);
|
||||
virtual bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len);
|
||||
virtual bool decode_32bit(uint32_t field_id, uint32_t value);
|
||||
virtual APIMessageType message_type() const = 0;
|
||||
|
||||
virtual void encode(APIBuffer &buffer);
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
1202
esphome/components/api/api_server.cpp
Normal file
1202
esphome/components/api/api_server.cpp
Normal file
@@ -0,0 +1,1202 @@
|
||||
#include <utility>
|
||||
|
||||
#include "api_server.h"
|
||||
#include "basic_messages.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#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_service_call(ServiceCallResponse &call) {
|
||||
for (auto *client : this->clients_) {
|
||||
client->send_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();
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// APIConnection
|
||||
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
|
||||
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
|
||||
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
|
||||
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
|
||||
this);
|
||||
this->client_->onData([](void *s, AsyncClient *c, void *buf,
|
||||
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
|
||||
this);
|
||||
|
||||
this->send_buffer_.reserve(64);
|
||||
this->recv_buffer_.reserve(32);
|
||||
this->client_info_ = this->client_->remoteIP().toString().c_str();
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
APIConnection::~APIConnection() { delete this->client_; }
|
||||
void APIConnection::on_error_(int8_t error) {
|
||||
// disconnect will also be called, nothing to do here
|
||||
this->remove_ = true;
|
||||
}
|
||||
void APIConnection::on_disconnect_() {
|
||||
// delete self, generally unsafe but not in this case.
|
||||
this->remove_ = true;
|
||||
}
|
||||
void APIConnection::on_timeout_(uint32_t time) { this->disconnect_client(); }
|
||||
void APIConnection::on_data_(uint8_t *buf, size_t len) {
|
||||
if (len == 0 || buf == nullptr)
|
||||
return;
|
||||
|
||||
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
|
||||
// TODO: On ESP32, use queue to notify main thread of new data
|
||||
}
|
||||
void APIConnection::parse_recv_buffer_() {
|
||||
if (this->recv_buffer_.empty() || this->remove_)
|
||||
return;
|
||||
|
||||
while (!this->recv_buffer_.empty()) {
|
||||
if (this->recv_buffer_[0] != 0x00) {
|
||||
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
uint32_t i = 1;
|
||||
const uint32_t size = this->recv_buffer_.size();
|
||||
uint32_t msg_size = 0;
|
||||
while (i < size) {
|
||||
const uint8_t dat = this->recv_buffer_[i];
|
||||
msg_size |= (dat & 0x7F);
|
||||
// consume
|
||||
i += 1;
|
||||
if ((dat & 0x80) == 0x00) {
|
||||
break;
|
||||
} else {
|
||||
msg_size <<= 7;
|
||||
}
|
||||
}
|
||||
if (i == size)
|
||||
// not enough data there yet
|
||||
return;
|
||||
|
||||
uint32_t msg_type = 0;
|
||||
bool msg_type_done = false;
|
||||
while (i < size) {
|
||||
const uint8_t dat = this->recv_buffer_[i];
|
||||
msg_type |= (dat & 0x7F);
|
||||
// consume
|
||||
i += 1;
|
||||
if ((dat & 0x80) == 0x00) {
|
||||
msg_type_done = true;
|
||||
break;
|
||||
} else {
|
||||
msg_type <<= 7;
|
||||
}
|
||||
}
|
||||
if (!msg_type_done)
|
||||
// not enough data there yet
|
||||
return;
|
||||
|
||||
if (size - i < msg_size)
|
||||
// message body not fully received
|
||||
return;
|
||||
|
||||
// ESP_LOGVV(TAG, "RECV Message: Size=%u Type=%u", msg_size, msg_type);
|
||||
|
||||
if (!this->valid_rx_message_type_(msg_type)) {
|
||||
ESP_LOGE(TAG, "Not a valid message type: %u", msg_type);
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *msg = &this->recv_buffer_[i];
|
||||
this->read_message_(msg_size, msg_type, msg);
|
||||
if (this->remove_)
|
||||
return;
|
||||
// pop front
|
||||
uint32_t total = i + msg_size;
|
||||
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
|
||||
}
|
||||
}
|
||||
void APIConnection::read_message_(uint32_t size, uint32_t type, uint8_t *msg) {
|
||||
this->last_traffic_ = millis();
|
||||
|
||||
switch (static_cast<APIMessageType>(type)) {
|
||||
case APIMessageType::HELLO_REQUEST: {
|
||||
HelloRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_hello_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::HELLO_RESPONSE: {
|
||||
// Invalid
|
||||
break;
|
||||
}
|
||||
case APIMessageType::CONNECT_REQUEST: {
|
||||
ConnectRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_connect_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::CONNECT_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::DISCONNECT_REQUEST: {
|
||||
DisconnectRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_disconnect_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::DISCONNECT_RESPONSE: {
|
||||
DisconnectResponse req;
|
||||
req.decode(msg, size);
|
||||
this->on_disconnect_response_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::PING_REQUEST: {
|
||||
PingRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_ping_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::PING_RESPONSE: {
|
||||
PingResponse req;
|
||||
req.decode(msg, size);
|
||||
this->on_ping_response_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::DEVICE_INFO_REQUEST: {
|
||||
DeviceInfoRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_device_info_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::DEVICE_INFO_RESPONSE: {
|
||||
// Invalid
|
||||
break;
|
||||
}
|
||||
case APIMessageType::LIST_ENTITIES_REQUEST: {
|
||||
ListEntitiesRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_list_entities_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_COVER_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_FAN_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE:
|
||||
case APIMessageType::LIST_ENTITIES_DONE_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::SUBSCRIBE_STATES_REQUEST: {
|
||||
SubscribeStatesRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_subscribe_states_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::BINARY_SENSOR_STATE_RESPONSE:
|
||||
case APIMessageType::COVER_STATE_RESPONSE:
|
||||
case APIMessageType::FAN_STATE_RESPONSE:
|
||||
case APIMessageType::LIGHT_STATE_RESPONSE:
|
||||
case APIMessageType::SENSOR_STATE_RESPONSE:
|
||||
case APIMessageType::SWITCH_STATE_RESPONSE:
|
||||
case APIMessageType::TEXT_SENSOR_STATE_RESPONSE:
|
||||
case APIMessageType::CAMERA_IMAGE_RESPONSE:
|
||||
case APIMessageType::CLIMATE_STATE_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::SUBSCRIBE_LOGS_REQUEST: {
|
||||
SubscribeLogsRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_subscribe_logs_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType ::SUBSCRIBE_LOGS_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::COVER_COMMAND_REQUEST: {
|
||||
#ifdef USE_COVER
|
||||
CoverCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_cover_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::FAN_COMMAND_REQUEST: {
|
||||
#ifdef USE_FAN
|
||||
FanCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_fan_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::LIGHT_COMMAND_REQUEST: {
|
||||
#ifdef USE_LIGHT
|
||||
LightCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_light_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SWITCH_COMMAND_REQUEST: {
|
||||
#ifdef USE_SWITCH
|
||||
SwitchCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_switch_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::CLIMATE_COMMAND_REQUEST: {
|
||||
#ifdef USE_CLIMATE
|
||||
ClimateCommandRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_climate_command_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST: {
|
||||
SubscribeServiceCallsRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_subscribe_service_calls_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SERVICE_CALL_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::GET_TIME_REQUEST:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::GET_TIME_RESPONSE: {
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
homeassistant::GetTimeResponse req;
|
||||
req.decode(msg, size);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST: {
|
||||
SubscribeHomeAssistantStatesRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_subscribe_home_assistant_states_request_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE:
|
||||
// Invalid
|
||||
break;
|
||||
case APIMessageType::HOME_ASSISTANT_STATE_RESPONSE: {
|
||||
HomeAssistantStateResponse req;
|
||||
req.decode(msg, size);
|
||||
this->on_home_assistant_state_response_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::EXECUTE_SERVICE_REQUEST: {
|
||||
ExecuteServiceRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_execute_service_(req);
|
||||
break;
|
||||
}
|
||||
case APIMessageType::CAMERA_IMAGE_REQUEST: {
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CameraImageRequest req;
|
||||
req.decode(msg, size);
|
||||
this->on_camera_image_request_(req);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIConnection::on_hello_request_(const HelloRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_hello_request_(client_info='%s')", req.get_client_info().c_str());
|
||||
this->client_info_ = req.get_client_info() + " (" + this->client_->remoteIP().toString().c_str();
|
||||
this->client_info_ += ")";
|
||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// uint32 api_version_major = 1; -> 1
|
||||
buffer.encode_uint32(1, 1);
|
||||
// uint32 api_version_minor = 2; -> 1
|
||||
buffer.encode_uint32(2, 1);
|
||||
|
||||
// string server_info = 3;
|
||||
buffer.encode_string(3, App.get_name() + " (esphome v" ESPHOME_VERSION ")");
|
||||
bool success = this->send_buffer(APIMessageType::HELLO_RESPONSE);
|
||||
if (!success) {
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
|
||||
this->connection_state_ = ConnectionState::WAITING_FOR_CONNECT;
|
||||
}
|
||||
void APIConnection::on_connect_request_(const ConnectRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_connect_request_(password='%s')", req.get_password().c_str());
|
||||
bool correct = this->parent_->check_password(req.get_password());
|
||||
auto buffer = this->get_buffer();
|
||||
// bool invalid_password = 1;
|
||||
buffer.encode_bool(1, !correct);
|
||||
bool success = this->send_buffer(APIMessageType::CONNECT_RESPONSE);
|
||||
if (!success) {
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (correct) {
|
||||
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
this->send_time_request();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void APIConnection::on_disconnect_request_(const DisconnectRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_disconnect_request_");
|
||||
// remote initiated disconnect_client
|
||||
if (!this->send_empty_message(APIMessageType::DISCONNECT_RESPONSE)) {
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
this->disconnect_client();
|
||||
}
|
||||
void APIConnection::on_disconnect_response_(const DisconnectResponse &req) {
|
||||
ESP_LOGVV(TAG, "on_disconnect_response_");
|
||||
// we initiated disconnect_client
|
||||
this->disconnect_client();
|
||||
}
|
||||
void APIConnection::on_ping_request_(const PingRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_ping_request_");
|
||||
PingResponse resp;
|
||||
this->send_message(resp);
|
||||
}
|
||||
void APIConnection::on_ping_response_(const PingResponse &req) {
|
||||
ESP_LOGVV(TAG, "on_ping_response_");
|
||||
// we initiated ping
|
||||
this->sent_ping_ = false;
|
||||
}
|
||||
void APIConnection::on_device_info_request_(const DeviceInfoRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_device_info_request_");
|
||||
auto buffer = this->get_buffer();
|
||||
// bool uses_password = 1;
|
||||
buffer.encode_bool(1, this->parent_->uses_password());
|
||||
// string name = 2;
|
||||
buffer.encode_string(2, App.get_name());
|
||||
// string mac_address = 3;
|
||||
buffer.encode_string(3, get_mac_address_pretty());
|
||||
// string esphome_version = 4;
|
||||
buffer.encode_string(4, ESPHOME_VERSION);
|
||||
// string compilation_time = 5;
|
||||
buffer.encode_string(5, App.get_compilation_time());
|
||||
#ifdef ARDUINO_BOARD
|
||||
// string model = 6;
|
||||
buffer.encode_string(6, ARDUINO_BOARD);
|
||||
#endif
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
// bool has_deep_sleep = 7;
|
||||
buffer.encode_bool(7, deep_sleep::global_has_deep_sleep);
|
||||
#endif
|
||||
this->send_buffer(APIMessageType::DEVICE_INFO_RESPONSE);
|
||||
}
|
||||
void APIConnection::on_list_entities_request_(const ListEntitiesRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_list_entities_request_");
|
||||
this->list_entities_iterator_.begin();
|
||||
}
|
||||
void APIConnection::on_subscribe_states_request_(const SubscribeStatesRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_subscribe_states_request_");
|
||||
this->state_subscription_ = true;
|
||||
this->initial_state_iterator_.begin();
|
||||
}
|
||||
void APIConnection::on_subscribe_logs_request_(const SubscribeLogsRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_subscribe_logs_request_");
|
||||
this->log_subscription_ = req.get_level();
|
||||
if (req.get_dump_config()) {
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::fatal_error_() {
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
bool APIConnection::valid_rx_message_type_(uint32_t type) {
|
||||
switch (static_cast<APIMessageType>(type)) {
|
||||
case APIMessageType::HELLO_RESPONSE:
|
||||
case APIMessageType::CONNECT_RESPONSE:
|
||||
return false;
|
||||
case APIMessageType::HELLO_REQUEST:
|
||||
return this->connection_state_ == ConnectionState::WAITING_FOR_HELLO;
|
||||
case APIMessageType::CONNECT_REQUEST:
|
||||
return this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT;
|
||||
case APIMessageType::PING_REQUEST:
|
||||
case APIMessageType::PING_RESPONSE:
|
||||
case APIMessageType::DISCONNECT_REQUEST:
|
||||
case APIMessageType::DISCONNECT_RESPONSE:
|
||||
case APIMessageType::DEVICE_INFO_REQUEST:
|
||||
if (this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT)
|
||||
return true;
|
||||
default:
|
||||
return this->connection_state_ == ConnectionState::CONNECTED;
|
||||
}
|
||||
}
|
||||
bool APIConnection::send_message(APIMessage &msg) {
|
||||
this->send_buffer_.clear();
|
||||
APIBuffer buf(&this->send_buffer_);
|
||||
msg.encode(buf);
|
||||
return this->send_buffer(msg.message_type());
|
||||
}
|
||||
bool APIConnection::send_empty_message(APIMessageType type) {
|
||||
this->send_buffer_.clear();
|
||||
return this->send_buffer(type);
|
||||
}
|
||||
|
||||
void APIConnection::disconnect_client() {
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
void encode_varint(uint8_t *dat, uint8_t *len, uint32_t value) {
|
||||
if (value <= 0x7F) {
|
||||
*dat = value;
|
||||
(*len)++;
|
||||
return;
|
||||
}
|
||||
|
||||
while (value) {
|
||||
uint8_t temp = value & 0x7F;
|
||||
value >>= 7;
|
||||
if (value) {
|
||||
*dat = temp | 0x80;
|
||||
} else {
|
||||
*dat = temp;
|
||||
}
|
||||
dat++;
|
||||
(*len)++;
|
||||
}
|
||||
}
|
||||
|
||||
bool APIConnection::send_buffer(APIMessageType type) {
|
||||
uint8_t header[20];
|
||||
header[0] = 0x00;
|
||||
uint8_t header_len = 1;
|
||||
encode_varint(header + header_len, &header_len, this->send_buffer_.size());
|
||||
encode_varint(header + header_len, &header_len, static_cast<uint32_t>(type));
|
||||
|
||||
size_t needed_space = this->send_buffer_.size() + header_len;
|
||||
|
||||
if (needed_space > this->client_->space()) {
|
||||
delay(0);
|
||||
if (needed_space > this->client_->space()) {
|
||||
if (type != APIMessageType::SUBSCRIBE_LOGS_RESPONSE) {
|
||||
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
|
||||
}
|
||||
delay(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// char buffer[512];
|
||||
// uint32_t offset = 0;
|
||||
// for (int j = 0; j < header_len; j++) {
|
||||
// offset += snprintf(buffer + offset, 512 - offset, "0x%02X ", header[j]);
|
||||
// }
|
||||
// offset += snprintf(buffer + offset, 512 - offset, "| ");
|
||||
// for (auto &it : this->send_buffer_) {
|
||||
// int i = snprintf(buffer + offset, 512 - offset, "0x%02X ", it);
|
||||
// if (i <= 0)
|
||||
// break;
|
||||
// offset += i;
|
||||
// }
|
||||
// ESP_LOGVV(TAG, "SEND %s", buffer);
|
||||
|
||||
this->client_->add(reinterpret_cast<char *>(header), header_len);
|
||||
this->client_->add(reinterpret_cast<char *>(this->send_buffer_.data()), this->send_buffer_.size());
|
||||
return this->client_->send();
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (!network_is_connected()) {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->fatal_error_();
|
||||
return;
|
||||
}
|
||||
if (this->client_->disconnected()) {
|
||||
// failsafe for disconnect logic
|
||||
this->on_disconnect_();
|
||||
return;
|
||||
}
|
||||
this->parse_recv_buffer_();
|
||||
|
||||
this->list_entities_iterator_.advance();
|
||||
this->initial_state_iterator_.advance();
|
||||
|
||||
const uint32_t keepalive = 60000;
|
||||
if (this->sent_ping_) {
|
||||
if (millis() - this->last_traffic_ > (keepalive * 3) / 2) {
|
||||
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
this->disconnect_client();
|
||||
}
|
||||
} else if (millis() - this->last_traffic_ > keepalive) {
|
||||
this->sent_ping_ = true;
|
||||
this->send_ping_request();
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (this->image_reader_.available()) {
|
||||
uint32_t space = this->client_->space();
|
||||
// reserve 15 bytes for metadata, and at least 64 bytes of data
|
||||
if (space >= 15 + 64) {
|
||||
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
|
||||
// bytes data = 2;
|
||||
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
|
||||
// bool done = 3;
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
buffer.encode_bool(3, done);
|
||||
bool success = this->send_buffer(APIMessageType::CAMERA_IMAGE_RESPONSE);
|
||||
if (success) {
|
||||
this->image_reader_.consume_data(to_send);
|
||||
}
|
||||
if (success && done) {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, binary_sensor->get_object_id_hash());
|
||||
// bool state = 2;
|
||||
buffer.encode_bool(2, state);
|
||||
return this->send_buffer(APIMessageType::BINARY_SENSOR_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
auto traits = cover->get_traits();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, cover->get_object_id_hash());
|
||||
// enum LegacyCoverState {
|
||||
// OPEN = 0;
|
||||
// CLOSED = 1;
|
||||
// }
|
||||
// LegacyCoverState legacy_state = 2;
|
||||
uint32_t state = (cover->position == cover::COVER_OPEN) ? 0 : 1;
|
||||
buffer.encode_uint32(2, state);
|
||||
// float position = 3;
|
||||
buffer.encode_float(3, cover->position);
|
||||
if (traits.get_supports_tilt()) {
|
||||
// float tilt = 4;
|
||||
buffer.encode_float(4, cover->tilt);
|
||||
}
|
||||
// enum CoverCurrentOperation {
|
||||
// IDLE = 0;
|
||||
// IS_OPENING = 1;
|
||||
// IS_CLOSING = 2;
|
||||
// }
|
||||
// CoverCurrentOperation current_operation = 5;
|
||||
buffer.encode_uint32(5, cover->current_operation);
|
||||
return this->send_buffer(APIMessageType::COVER_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, fan->get_object_id_hash());
|
||||
// bool state = 2;
|
||||
buffer.encode_bool(2, fan->state);
|
||||
// bool oscillating = 3;
|
||||
if (fan->get_traits().supports_oscillation()) {
|
||||
buffer.encode_bool(3, fan->oscillating);
|
||||
}
|
||||
// enum FanSpeed {
|
||||
// LOW = 0;
|
||||
// MEDIUM = 1;
|
||||
// HIGH = 2;
|
||||
// }
|
||||
// FanSpeed speed = 4;
|
||||
if (fan->get_traits().supports_speed()) {
|
||||
buffer.encode_uint32(4, fan->speed);
|
||||
}
|
||||
return this->send_buffer(APIMessageType::FAN_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
bool APIConnection::send_light_state(light::LightState *light) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
auto traits = light->get_traits();
|
||||
auto values = light->remote_values;
|
||||
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, light->get_object_id_hash());
|
||||
// bool state = 2;
|
||||
buffer.encode_bool(2, values.get_state() != 0.0f);
|
||||
// float brightness = 3;
|
||||
if (traits.get_supports_brightness()) {
|
||||
buffer.encode_float(3, values.get_brightness());
|
||||
}
|
||||
if (traits.get_supports_rgb()) {
|
||||
// float red = 4;
|
||||
buffer.encode_float(4, values.get_red());
|
||||
// float green = 5;
|
||||
buffer.encode_float(5, values.get_green());
|
||||
// float blue = 6;
|
||||
buffer.encode_float(6, values.get_blue());
|
||||
}
|
||||
// float white = 7;
|
||||
if (traits.get_supports_rgb_white_value()) {
|
||||
buffer.encode_float(7, values.get_white());
|
||||
}
|
||||
// float color_temperature = 8;
|
||||
if (traits.get_supports_color_temperature()) {
|
||||
buffer.encode_float(8, values.get_color_temperature());
|
||||
}
|
||||
// string effect = 9;
|
||||
if (light->supports_effects()) {
|
||||
buffer.encode_string(9, light->get_effect_name());
|
||||
}
|
||||
return this->send_buffer(APIMessageType::LIGHT_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, sensor->get_object_id_hash());
|
||||
// float state = 2;
|
||||
buffer.encode_float(2, state);
|
||||
return this->send_buffer(APIMessageType::SENSOR_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, a_switch->get_object_id_hash());
|
||||
// bool state = 2;
|
||||
buffer.encode_bool(2, state);
|
||||
return this->send_buffer(APIMessageType::SWITCH_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, text_sensor->get_object_id_hash());
|
||||
// string state = 2;
|
||||
buffer.encode_string(2, state);
|
||||
return this->send_buffer(APIMessageType::TEXT_SENSOR_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
auto traits = climate->get_traits();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, climate->get_object_id_hash());
|
||||
// ClimateMode mode = 2;
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(climate->mode));
|
||||
// float current_temperature = 3;
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
buffer.encode_float(3, climate->current_temperature);
|
||||
}
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
// float target_temperature_low = 5;
|
||||
buffer.encode_float(5, climate->target_temperature_low);
|
||||
// float target_temperature_high = 6;
|
||||
buffer.encode_float(6, climate->target_temperature_high);
|
||||
} else {
|
||||
// float target_temperature = 4;
|
||||
buffer.encode_float(4, climate->target_temperature);
|
||||
}
|
||||
// bool away = 7;
|
||||
if (traits.get_supports_away()) {
|
||||
buffer.encode_bool(7, climate->away);
|
||||
}
|
||||
return this->send_buffer(APIMessageType::CLIMATE_STATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
|
||||
auto buffer = this->get_buffer();
|
||||
// LogLevel level = 1;
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
||||
// string tag = 2;
|
||||
// buffer.encode_string(2, tag, strlen(tag));
|
||||
// string message = 3;
|
||||
buffer.encode_string(3, line, strlen(line));
|
||||
bool success = this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE);
|
||||
|
||||
if (!success) {
|
||||
buffer = this->get_buffer();
|
||||
// bool send_failed = 4;
|
||||
buffer.encode_bool(4, true);
|
||||
return this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool APIConnection::send_disconnect_request() {
|
||||
DisconnectRequest req;
|
||||
return this->send_message(req);
|
||||
}
|
||||
bool APIConnection::send_ping_request() {
|
||||
ESP_LOGVV(TAG, "Sending ping...");
|
||||
PingRequest req;
|
||||
return this->send_message(req);
|
||||
}
|
||||
|
||||
#ifdef USE_COVER
|
||||
void APIConnection::on_cover_command_request_(const CoverCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_cover_command_request_");
|
||||
cover::Cover *cover = App.get_cover_by_key(req.get_key());
|
||||
if (cover == nullptr)
|
||||
return;
|
||||
|
||||
auto call = cover->make_call();
|
||||
if (req.get_legacy_command().has_value()) {
|
||||
auto cmd = *req.get_legacy_command();
|
||||
switch (cmd) {
|
||||
case LEGACY_COVER_COMMAND_OPEN:
|
||||
call.set_command_open();
|
||||
break;
|
||||
case LEGACY_COVER_COMMAND_CLOSE:
|
||||
call.set_command_close();
|
||||
break;
|
||||
case LEGACY_COVER_COMMAND_STOP:
|
||||
call.set_command_stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (req.get_position().has_value()) {
|
||||
auto pos = *req.get_position();
|
||||
call.set_position(pos);
|
||||
}
|
||||
if (req.get_tilt().has_value()) {
|
||||
auto tilt = *req.get_tilt();
|
||||
call.set_tilt(tilt);
|
||||
}
|
||||
if (req.get_stop()) {
|
||||
call.set_command_stop();
|
||||
}
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void APIConnection::on_fan_command_request_(const FanCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_fan_command_request_");
|
||||
fan::FanState *fan = App.get_fan_by_key(req.get_key());
|
||||
if (fan == nullptr)
|
||||
return;
|
||||
|
||||
auto call = fan->make_call();
|
||||
call.set_state(req.get_state());
|
||||
call.set_oscillating(req.get_oscillating());
|
||||
call.set_speed(req.get_speed());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void APIConnection::on_light_command_request_(const LightCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_light_command_request_");
|
||||
light::LightState *light = App.get_light_by_key(req.get_key());
|
||||
if (light == nullptr)
|
||||
return;
|
||||
|
||||
auto call = light->make_call();
|
||||
call.set_state(req.get_state());
|
||||
call.set_brightness(req.get_brightness());
|
||||
call.set_red(req.get_red());
|
||||
call.set_green(req.get_green());
|
||||
call.set_blue(req.get_blue());
|
||||
call.set_white(req.get_white());
|
||||
call.set_color_temperature(req.get_color_temperature());
|
||||
call.set_transition_length(req.get_transition_length());
|
||||
call.set_flash_length(req.get_flash_length());
|
||||
call.set_effect(req.get_effect());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void APIConnection::on_switch_command_request_(const SwitchCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_switch_command_request_");
|
||||
switch_::Switch *a_switch = App.get_switch_by_key(req.get_key());
|
||||
if (a_switch == nullptr || a_switch->is_internal())
|
||||
return;
|
||||
|
||||
if (req.get_state()) {
|
||||
a_switch->turn_on();
|
||||
} else {
|
||||
a_switch->turn_off();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void APIConnection::on_climate_command_request_(const ClimateCommandRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_climate_command_request_");
|
||||
climate::Climate *climate = App.get_climate_by_key(req.get_key());
|
||||
if (climate == nullptr)
|
||||
return;
|
||||
|
||||
auto call = climate->make_call();
|
||||
if (req.get_mode().has_value())
|
||||
call.set_mode(*req.get_mode());
|
||||
if (req.get_target_temperature().has_value())
|
||||
call.set_target_temperature(*req.get_target_temperature());
|
||||
if (req.get_target_temperature_low().has_value())
|
||||
call.set_target_temperature_low(*req.get_target_temperature_low());
|
||||
if (req.get_target_temperature_high().has_value())
|
||||
call.set_target_temperature_high(*req.get_target_temperature_high());
|
||||
if (req.get_away().has_value())
|
||||
call.set_away(*req.get_away());
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIConnection::on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req) {
|
||||
this->service_call_subscription_ = true;
|
||||
}
|
||||
void APIConnection::send_service_call(ServiceCallResponse &call) {
|
||||
if (!this->service_call_subscription_)
|
||||
return;
|
||||
|
||||
this->send_message(call);
|
||||
}
|
||||
void APIConnection::on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
auto buffer = this->get_buffer();
|
||||
// string entity_id = 1;
|
||||
buffer.encode_string(1, it.entity_id);
|
||||
this->send_buffer(APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE);
|
||||
}
|
||||
}
|
||||
void APIConnection::on_home_assistant_state_response_(const HomeAssistantStateResponse &req) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
if (it.entity_id == req.get_entity_id()) {
|
||||
it.callback(req.get_state());
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIConnection::on_execute_service_(const ExecuteServiceRequest &req) {
|
||||
ESP_LOGVV(TAG, "on_execute_service_");
|
||||
bool found = false;
|
||||
for (auto *service : this->parent_->get_user_services()) {
|
||||
if (service->execute_service(req)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGV(TAG, "Could not find matching service!");
|
||||
}
|
||||
}
|
||||
|
||||
APIBuffer APIConnection::get_buffer() {
|
||||
this->send_buffer_.clear();
|
||||
return {&this->send_buffer_};
|
||||
}
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIConnection::send_time_request() { this->send_empty_message(APIMessageType::GET_TIME_REQUEST); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
return;
|
||||
if (this->image_reader_.available())
|
||||
return;
|
||||
this->image_reader_.set_image(image);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::on_camera_image_request_(const CameraImageRequest &req) {
|
||||
if (esp32_camera::global_esp32_camera == nullptr)
|
||||
return;
|
||||
|
||||
ESP_LOGV(TAG, "on_camera_image_request_ stream=%s single=%s", YESNO(req.get_stream()), YESNO(req.get_single()));
|
||||
if (req.get_single()) {
|
||||
esp32_camera::global_esp32_camera->request_image();
|
||||
}
|
||||
if (req.get_stream()) {
|
||||
esp32_camera::global_esp32_camera->request_stream();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
242
esphome/components/api/api_server.h
Normal file
242
esphome/components/api/api_server.h
Normal file
@@ -0,0 +1,242 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "util.h"
|
||||
#include "api_message.h"
|
||||
#include "basic_messages.h"
|
||||
#include "list_entities.h"
|
||||
#include "subscribe_state.h"
|
||||
#include "subscribe_logs.h"
|
||||
#include "command_messages.h"
|
||||
#include "service_call_message.h"
|
||||
#include "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;
|
||||
|
||||
class APIConnection {
|
||||
public:
|
||||
APIConnection(AsyncClient *client, APIServer *parent);
|
||||
~APIConnection();
|
||||
|
||||
void disconnect_client();
|
||||
APIBuffer get_buffer();
|
||||
bool send_buffer(APIMessageType type);
|
||||
bool send_message(APIMessage &msg);
|
||||
bool send_empty_message(APIMessageType type);
|
||||
void loop();
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state(cover::Cover *cover);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state(fan::FanState *fan);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state(light::LightState *light);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state(sensor::Sensor *sensor, float state);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state(switch_::Switch *a_switch, bool state);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
bool send_disconnect_request();
|
||||
bool send_ping_request();
|
||||
void send_service_call(ServiceCallResponse &call);
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void send_time_request();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
friend APIServer;
|
||||
|
||||
void on_error_(int8_t error);
|
||||
void on_disconnect_();
|
||||
void on_timeout_(uint32_t time);
|
||||
void on_data_(uint8_t *buf, size_t len);
|
||||
void fatal_error_();
|
||||
bool valid_rx_message_type_(uint32_t msg_type);
|
||||
void read_message_(uint32_t size, uint32_t type, uint8_t *msg);
|
||||
void parse_recv_buffer_();
|
||||
|
||||
// request types
|
||||
void on_hello_request_(const HelloRequest &req);
|
||||
void on_connect_request_(const ConnectRequest &req);
|
||||
void on_disconnect_request_(const DisconnectRequest &req);
|
||||
void on_disconnect_response_(const DisconnectResponse &req);
|
||||
void on_ping_request_(const PingRequest &req);
|
||||
void on_ping_response_(const PingResponse &req);
|
||||
void on_device_info_request_(const DeviceInfoRequest &req);
|
||||
void on_list_entities_request_(const ListEntitiesRequest &req);
|
||||
void on_subscribe_states_request_(const SubscribeStatesRequest &req);
|
||||
void on_subscribe_logs_request_(const SubscribeLogsRequest &req);
|
||||
#ifdef USE_COVER
|
||||
void on_cover_command_request_(const CoverCommandRequest &req);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_command_request_(const FanCommandRequest &req);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_command_request_(const LightCommandRequest &req);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_command_request_(const SwitchCommandRequest &req);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_command_request_(const ClimateCommandRequest &req);
|
||||
#endif
|
||||
void on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req);
|
||||
void on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req);
|
||||
void on_home_assistant_state_response_(const HomeAssistantStateResponse &req);
|
||||
void on_execute_service_(const ExecuteServiceRequest &req);
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void on_camera_image_request_(const CameraImageRequest &req);
|
||||
#endif
|
||||
|
||||
enum class ConnectionState {
|
||||
WAITING_FOR_HELLO,
|
||||
WAITING_FOR_CONNECT,
|
||||
CONNECTED,
|
||||
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
|
||||
|
||||
bool remove_{false};
|
||||
|
||||
std::vector<uint8_t> send_buffer_;
|
||||
std::vector<uint8_t> recv_buffer_;
|
||||
|
||||
std::string client_info_;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
esp32_camera::CameraImageReader image_reader_;
|
||||
#endif
|
||||
|
||||
bool state_subscription_{false};
|
||||
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
AsyncClient *client_;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class HomeAssistantServiceCallAction;
|
||||
|
||||
class APIServer : public Component, public Controller {
|
||||
public:
|
||||
APIServer();
|
||||
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_service_call(ServiceCallResponse &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 HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit HomeAssistantServiceCallAction(APIServer *parent) : parent_(parent) {}
|
||||
void set_service(const std::string &service) { this->resp_.set_service(service); }
|
||||
void set_data(const std::vector<KeyValuePair> &data) { this->resp_.set_data(data); }
|
||||
void set_data_template(const std::vector<KeyValuePair> &data_template) {
|
||||
this->resp_.set_data_template(data_template);
|
||||
}
|
||||
void set_variables(const std::vector<TemplatableKeyValuePair> &variables) { this->resp_.set_variables(variables); }
|
||||
void play(Ts... x) override { this->parent_->send_service_call(this->resp_); }
|
||||
|
||||
protected:
|
||||
APIServer *parent_;
|
||||
ServiceCallResponse resp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
57
esphome/components/api/basic_messages.cpp
Normal file
57
esphome/components/api/basic_messages.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "basic_messages.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
// Hello
|
||||
bool HelloRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 1: // string client_info = 1;
|
||||
this->client_info_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const std::string &HelloRequest::get_client_info() const { return this->client_info_; }
|
||||
void HelloRequest::set_client_info(const std::string &client_info) { this->client_info_ = client_info; }
|
||||
APIMessageType HelloRequest::message_type() const { return APIMessageType::HELLO_REQUEST; }
|
||||
|
||||
// Connect
|
||||
bool ConnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 1: // string password = 1;
|
||||
this->password_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const std::string &ConnectRequest::get_password() const { return this->password_; }
|
||||
void ConnectRequest::set_password(const std::string &password) { this->password_ = password; }
|
||||
APIMessageType ConnectRequest::message_type() const { return APIMessageType::CONNECT_REQUEST; }
|
||||
|
||||
APIMessageType DeviceInfoRequest::message_type() const { return APIMessageType::DEVICE_INFO_REQUEST; }
|
||||
APIMessageType DisconnectRequest::message_type() const { return APIMessageType::DISCONNECT_REQUEST; }
|
||||
bool DisconnectRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 1: // string reason = 1;
|
||||
this->reason_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const std::string &DisconnectRequest::get_reason() const { return this->reason_; }
|
||||
void DisconnectRequest::set_reason(const std::string &reason) { this->reason_ = reason; }
|
||||
void DisconnectRequest::encode(APIBuffer &buffer) {
|
||||
// string reason = 1;
|
||||
buffer.encode_string(1, this->reason_);
|
||||
}
|
||||
APIMessageType DisconnectResponse::message_type() const { return APIMessageType::DISCONNECT_RESPONSE; }
|
||||
APIMessageType PingRequest::message_type() const { return APIMessageType::PING_REQUEST; }
|
||||
APIMessageType PingResponse::message_type() const { return APIMessageType::PING_RESPONSE; }
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
63
esphome/components/api/basic_messages.h
Normal file
63
esphome/components/api/basic_messages.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class HelloRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
const std::string &get_client_info() const;
|
||||
void set_client_info(const std::string &client_info);
|
||||
APIMessageType message_type() const override;
|
||||
|
||||
protected:
|
||||
std::string client_info_;
|
||||
};
|
||||
|
||||
class ConnectRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
const std::string &get_password() const;
|
||||
void set_password(const std::string &password);
|
||||
APIMessageType message_type() const override;
|
||||
|
||||
protected:
|
||||
std::string password_;
|
||||
};
|
||||
|
||||
class DeviceInfoRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class DisconnectRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
void encode(APIBuffer &buffer) override;
|
||||
APIMessageType message_type() const override;
|
||||
const std::string &get_reason() const;
|
||||
void set_reason(const std::string &reason);
|
||||
|
||||
protected:
|
||||
std::string reason_;
|
||||
};
|
||||
|
||||
class DisconnectResponse : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class PingRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class PingResponse : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
417
esphome/components/api/command_messages.cpp
Normal file
417
esphome/components/api/command_messages.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
#include "command_messages.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_COVER
|
||||
bool CoverCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool has_legacy_command = 2;
|
||||
this->has_legacy_command_ = value;
|
||||
return true;
|
||||
case 3:
|
||||
// enum LegacyCoverCommand {
|
||||
// OPEN = 0;
|
||||
// CLOSE = 1;
|
||||
// STOP = 2;
|
||||
// }
|
||||
// LegacyCoverCommand legacy_command_ = 3;
|
||||
this->legacy_command_ = static_cast<LegacyCoverCommand>(value);
|
||||
return true;
|
||||
case 4:
|
||||
// bool has_position = 4;
|
||||
this->has_position_ = value;
|
||||
return true;
|
||||
case 6:
|
||||
// bool has_tilt = 6;
|
||||
this->has_tilt_ = value;
|
||||
return true;
|
||||
case 8:
|
||||
// bool stop = 8;
|
||||
this->stop_ = value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool CoverCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
case 5:
|
||||
// float position = 5;
|
||||
this->position_ = as_float(value);
|
||||
return true;
|
||||
case 7:
|
||||
// float tilt = 7;
|
||||
this->tilt_ = as_float(value);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType CoverCommandRequest::message_type() const { return APIMessageType ::COVER_COMMAND_REQUEST; }
|
||||
uint32_t CoverCommandRequest::get_key() const { return this->key_; }
|
||||
optional<LegacyCoverCommand> CoverCommandRequest::get_legacy_command() const {
|
||||
if (!this->has_legacy_command_)
|
||||
return {};
|
||||
return this->legacy_command_;
|
||||
}
|
||||
optional<float> CoverCommandRequest::get_position() const {
|
||||
if (!this->has_position_)
|
||||
return {};
|
||||
return this->position_;
|
||||
}
|
||||
optional<float> CoverCommandRequest::get_tilt() const {
|
||||
if (!this->has_tilt_)
|
||||
return {};
|
||||
return this->tilt_;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
bool FanCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool has_state = 2;
|
||||
this->has_state_ = value;
|
||||
return true;
|
||||
case 3:
|
||||
// bool state = 3;
|
||||
this->state_ = value;
|
||||
return true;
|
||||
case 4:
|
||||
// bool has_speed = 4;
|
||||
this->has_speed_ = value;
|
||||
return true;
|
||||
case 5:
|
||||
// FanSpeed speed = 5;
|
||||
this->speed_ = static_cast<fan::FanSpeed>(value);
|
||||
return true;
|
||||
case 6:
|
||||
// bool has_oscillating = 6;
|
||||
this->has_oscillating_ = value;
|
||||
return true;
|
||||
case 7:
|
||||
// bool oscillating = 7;
|
||||
this->oscillating_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool FanCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType FanCommandRequest::message_type() const { return APIMessageType::FAN_COMMAND_REQUEST; }
|
||||
uint32_t FanCommandRequest::get_key() const { return this->key_; }
|
||||
optional<bool> FanCommandRequest::get_state() const {
|
||||
if (!this->has_state_)
|
||||
return {};
|
||||
return this->state_;
|
||||
}
|
||||
optional<fan::FanSpeed> FanCommandRequest::get_speed() const {
|
||||
if (!this->has_speed_)
|
||||
return {};
|
||||
return this->speed_;
|
||||
}
|
||||
optional<bool> FanCommandRequest::get_oscillating() const {
|
||||
if (!this->has_oscillating_)
|
||||
return {};
|
||||
return this->oscillating_;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
bool LightCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool has_state = 2;
|
||||
this->has_state_ = value;
|
||||
return true;
|
||||
case 3:
|
||||
// bool state = 3;
|
||||
this->state_ = value;
|
||||
return true;
|
||||
case 4:
|
||||
// bool has_brightness = 4;
|
||||
this->has_brightness_ = value;
|
||||
return true;
|
||||
case 6:
|
||||
// bool has_rgb = 6;
|
||||
this->has_rgb_ = value;
|
||||
return true;
|
||||
case 10:
|
||||
// bool has_white = 10;
|
||||
this->has_white_ = value;
|
||||
return true;
|
||||
case 12:
|
||||
// bool has_color_temperature = 12;
|
||||
this->has_color_temperature_ = value;
|
||||
return true;
|
||||
case 14:
|
||||
// bool has_transition_length = 14;
|
||||
this->has_transition_length_ = value;
|
||||
return true;
|
||||
case 15:
|
||||
// uint32 transition_length = 15;
|
||||
this->transition_length_ = value;
|
||||
return true;
|
||||
case 16:
|
||||
// bool has_flash_length = 16;
|
||||
this->has_flash_length_ = value;
|
||||
return true;
|
||||
case 17:
|
||||
// uint32 flash_length = 17;
|
||||
this->flash_length_ = value;
|
||||
return true;
|
||||
case 18:
|
||||
// bool has_effect = 18;
|
||||
this->has_effect_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LightCommandRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 19:
|
||||
// string effect = 19;
|
||||
this->effect_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LightCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
case 5:
|
||||
// float brightness = 5;
|
||||
this->brightness_ = as_float(value);
|
||||
return true;
|
||||
case 7:
|
||||
// float red = 7;
|
||||
this->red_ = as_float(value);
|
||||
return true;
|
||||
case 8:
|
||||
// float green = 8;
|
||||
this->green_ = as_float(value);
|
||||
return true;
|
||||
case 9:
|
||||
// float blue = 9;
|
||||
this->blue_ = as_float(value);
|
||||
return true;
|
||||
case 11:
|
||||
// float white = 11;
|
||||
this->white_ = as_float(value);
|
||||
return true;
|
||||
case 13:
|
||||
// float color_temperature = 13;
|
||||
this->color_temperature_ = as_float(value);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType LightCommandRequest::message_type() const { return APIMessageType::LIGHT_COMMAND_REQUEST; }
|
||||
uint32_t LightCommandRequest::get_key() const { return this->key_; }
|
||||
optional<bool> LightCommandRequest::get_state() const {
|
||||
if (!this->has_state_)
|
||||
return {};
|
||||
return this->state_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_brightness() const {
|
||||
if (!this->has_brightness_)
|
||||
return {};
|
||||
return this->brightness_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_red() const {
|
||||
if (!this->has_rgb_)
|
||||
return {};
|
||||
return this->red_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_green() const {
|
||||
if (!this->has_rgb_)
|
||||
return {};
|
||||
return this->green_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_blue() const {
|
||||
if (!this->has_rgb_)
|
||||
return {};
|
||||
return this->blue_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_white() const {
|
||||
if (!this->has_white_)
|
||||
return {};
|
||||
return this->white_;
|
||||
}
|
||||
optional<float> LightCommandRequest::get_color_temperature() const {
|
||||
if (!this->has_color_temperature_)
|
||||
return {};
|
||||
return this->color_temperature_;
|
||||
}
|
||||
optional<uint32_t> LightCommandRequest::get_transition_length() const {
|
||||
if (!this->has_transition_length_)
|
||||
return {};
|
||||
return this->transition_length_;
|
||||
}
|
||||
optional<uint32_t> LightCommandRequest::get_flash_length() const {
|
||||
if (!this->has_flash_length_)
|
||||
return {};
|
||||
return this->flash_length_;
|
||||
}
|
||||
optional<std::string> LightCommandRequest::get_effect() const {
|
||||
if (!this->has_effect_)
|
||||
return {};
|
||||
return this->effect_;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
bool SwitchCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool state = 2;
|
||||
this->state_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool SwitchCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType SwitchCommandRequest::message_type() const { return APIMessageType::SWITCH_COMMAND_REQUEST; }
|
||||
uint32_t SwitchCommandRequest::get_key() const { return this->key_; }
|
||||
bool SwitchCommandRequest::get_state() const { return this->state_; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool CameraImageRequest::get_single() const { return this->single_; }
|
||||
bool CameraImageRequest::get_stream() const { return this->stream_; }
|
||||
bool CameraImageRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// bool single = 1;
|
||||
this->single_ = value;
|
||||
return true;
|
||||
case 2:
|
||||
// bool stream = 2;
|
||||
this->stream_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType CameraImageRequest::message_type() const { return APIMessageType::CAMERA_IMAGE_REQUEST; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
// bool has_mode = 2;
|
||||
this->has_mode_ = value;
|
||||
return true;
|
||||
case 3:
|
||||
// ClimateMode mode = 3;
|
||||
this->mode_ = static_cast<climate::ClimateMode>(value);
|
||||
return true;
|
||||
case 4:
|
||||
// bool has_target_temperature = 4;
|
||||
this->has_target_temperature_ = value;
|
||||
return true;
|
||||
case 6:
|
||||
// bool has_target_temperature_low = 6;
|
||||
this->has_target_temperature_low_ = value;
|
||||
return true;
|
||||
case 8:
|
||||
// bool has_target_temperature_high = 8;
|
||||
this->has_target_temperature_high_ = value;
|
||||
return true;
|
||||
case 10:
|
||||
// bool has_away = 10;
|
||||
this->has_away_ = value;
|
||||
return true;
|
||||
case 11:
|
||||
// bool away = 11;
|
||||
this->away_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ClimateCommandRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
case 5:
|
||||
// float target_temperature = 5;
|
||||
this->target_temperature_ = as_float(value);
|
||||
return true;
|
||||
case 7:
|
||||
// float target_temperature_low = 7;
|
||||
this->target_temperature_low_ = as_float(value);
|
||||
return true;
|
||||
case 9:
|
||||
// float target_temperature_high = 9;
|
||||
this->target_temperature_high_ = as_float(value);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType ClimateCommandRequest::message_type() const { return APIMessageType::CLIMATE_COMMAND_REQUEST; }
|
||||
uint32_t ClimateCommandRequest::get_key() const { return this->key_; }
|
||||
optional<climate::ClimateMode> ClimateCommandRequest::get_mode() const {
|
||||
if (!this->has_mode_)
|
||||
return {};
|
||||
return this->mode_;
|
||||
}
|
||||
optional<float> ClimateCommandRequest::get_target_temperature() const {
|
||||
if (!this->has_target_temperature_)
|
||||
return {};
|
||||
return this->target_temperature_;
|
||||
}
|
||||
optional<float> ClimateCommandRequest::get_target_temperature_low() const {
|
||||
if (!this->has_target_temperature_low_)
|
||||
return {};
|
||||
return this->target_temperature_low_;
|
||||
}
|
||||
optional<float> ClimateCommandRequest::get_target_temperature_high() const {
|
||||
if (!this->has_target_temperature_high_)
|
||||
return {};
|
||||
return this->target_temperature_high_;
|
||||
}
|
||||
optional<bool> ClimateCommandRequest::get_away() const {
|
||||
if (!this->has_away_)
|
||||
return {};
|
||||
return this->away_;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
162
esphome/components/api/command_messages.h
Normal file
162
esphome/components/api/command_messages.h
Normal file
@@ -0,0 +1,162 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_COVER
|
||||
enum LegacyCoverCommand {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0,
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
||||
LEGACY_COVER_COMMAND_STOP = 2,
|
||||
};
|
||||
|
||||
class CoverCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
optional<LegacyCoverCommand> get_legacy_command() const;
|
||||
optional<float> get_position() const;
|
||||
optional<float> get_tilt() const;
|
||||
bool get_stop() const { return this->stop_; }
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool has_legacy_command_{false};
|
||||
LegacyCoverCommand legacy_command_{LEGACY_COVER_COMMAND_OPEN};
|
||||
bool has_position_{false};
|
||||
float position_{0.0f};
|
||||
bool has_tilt_{false};
|
||||
float tilt_{0.0f};
|
||||
bool stop_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
class FanCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
optional<bool> get_state() const;
|
||||
optional<fan::FanSpeed> get_speed() const;
|
||||
optional<bool> get_oscillating() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool has_state_{false};
|
||||
bool state_{false};
|
||||
bool has_speed_{false};
|
||||
fan::FanSpeed speed_{fan::FAN_SPEED_LOW};
|
||||
bool has_oscillating_{false};
|
||||
bool oscillating_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
class LightCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
optional<bool> get_state() const;
|
||||
optional<float> get_brightness() const;
|
||||
optional<float> get_red() const;
|
||||
optional<float> get_green() const;
|
||||
optional<float> get_blue() const;
|
||||
optional<float> get_white() const;
|
||||
optional<float> get_color_temperature() const;
|
||||
optional<uint32_t> get_transition_length() const;
|
||||
optional<uint32_t> get_flash_length() const;
|
||||
optional<std::string> get_effect() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool has_state_{false};
|
||||
bool state_{false};
|
||||
bool has_brightness_{false};
|
||||
float brightness_{0.0f};
|
||||
bool has_rgb_{false};
|
||||
float red_{0.0f};
|
||||
float green_{0.0f};
|
||||
float blue_{0.0f};
|
||||
bool has_white_{false};
|
||||
float white_{0.0f};
|
||||
bool has_color_temperature_{false};
|
||||
float color_temperature_{0.0f};
|
||||
bool has_transition_length_{false};
|
||||
uint32_t transition_length_{0};
|
||||
bool has_flash_length_{false};
|
||||
uint32_t flash_length_{0};
|
||||
bool has_effect_{false};
|
||||
std::string effect_{};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
class SwitchCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
bool get_state() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool state_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
class CameraImageRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool get_single() const;
|
||||
bool get_stream() const;
|
||||
APIMessageType message_type() const override;
|
||||
|
||||
protected:
|
||||
bool single_{false};
|
||||
bool stream_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
class ClimateCommandRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_key() const;
|
||||
optional<climate::ClimateMode> get_mode() const;
|
||||
optional<float> get_target_temperature() const;
|
||||
optional<float> get_target_temperature_low() const;
|
||||
optional<float> get_target_temperature_high() const;
|
||||
optional<bool> get_away() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_{0};
|
||||
bool has_mode_{false};
|
||||
climate::ClimateMode mode_{climate::CLIMATE_MODE_OFF};
|
||||
bool has_target_temperature_{false};
|
||||
float target_temperature_{0.0f};
|
||||
bool has_target_temperature_low_{false};
|
||||
float target_temperature_low_{0.0f};
|
||||
bool has_target_temperature_high_{false};
|
||||
float target_temperature_high_{0.0f};
|
||||
bool has_away_{false};
|
||||
bool away_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
190
esphome/components/api/list_entities.cpp
Normal file
190
esphome/components/api/list_entities.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
#include "list_entities.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(binary_sensor);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("binary_sensor", binary_sensor));
|
||||
// string device_class = 5;
|
||||
buffer.encode_string(5, binary_sensor->get_device_class());
|
||||
// bool is_status_binary_sensor = 6;
|
||||
buffer.encode_bool(6, binary_sensor->is_status_binary_sensor());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(cover);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("cover", cover));
|
||||
auto traits = cover->get_traits();
|
||||
|
||||
// bool assumed_state = 5;
|
||||
buffer.encode_bool(5, traits.get_is_assumed_state());
|
||||
// bool supports_position = 6;
|
||||
buffer.encode_bool(6, traits.get_supports_position());
|
||||
// bool supports_tilt = 7;
|
||||
buffer.encode_bool(7, traits.get_supports_tilt());
|
||||
// string device_class = 8;
|
||||
buffer.encode_string(8, cover->get_device_class());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_COVER_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool ListEntitiesIterator::on_fan(fan::FanState *fan) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(fan);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("fan", fan));
|
||||
// bool supports_oscillation = 5;
|
||||
buffer.encode_bool(5, fan->get_traits().supports_oscillation());
|
||||
// bool supports_speed = 6;
|
||||
buffer.encode_bool(6, fan->get_traits().supports_speed());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_FAN_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(light);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("light", light));
|
||||
// bool supports_brightness = 5;
|
||||
auto traits = light->get_traits();
|
||||
buffer.encode_bool(5, traits.get_supports_brightness());
|
||||
// bool supports_rgb = 6;
|
||||
buffer.encode_bool(6, traits.get_supports_rgb());
|
||||
// bool supports_white_value = 7;
|
||||
buffer.encode_bool(7, traits.get_supports_rgb_white_value());
|
||||
// bool supports_color_temperature = 8;
|
||||
buffer.encode_bool(8, traits.get_supports_color_temperature());
|
||||
if (traits.get_supports_color_temperature()) {
|
||||
// float min_mireds = 9;
|
||||
buffer.encode_float(9, traits.get_min_mireds());
|
||||
// float max_mireds = 10;
|
||||
buffer.encode_float(10, traits.get_max_mireds());
|
||||
}
|
||||
// repeated string effects = 11;
|
||||
if (light->supports_effects()) {
|
||||
buffer.encode_string(11, "None");
|
||||
for (auto *effect : light->get_effects()) {
|
||||
buffer.encode_string(11, effect->get_name());
|
||||
}
|
||||
}
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(sensor);
|
||||
// string unique_id = 4;
|
||||
std::string unique_id = sensor->unique_id();
|
||||
if (unique_id.empty())
|
||||
unique_id = get_default_unique_id("sensor", sensor);
|
||||
buffer.encode_string(4, unique_id);
|
||||
// string icon = 5;
|
||||
buffer.encode_string(5, sensor->get_icon());
|
||||
// string unit_of_measurement = 6;
|
||||
buffer.encode_string(6, sensor->get_unit_of_measurement());
|
||||
// int32 accuracy_decimals = 7;
|
||||
buffer.encode_int32(7, sensor->get_accuracy_decimals());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(a_switch);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("switch", a_switch));
|
||||
// string icon = 5;
|
||||
buffer.encode_string(5, a_switch->get_icon());
|
||||
// bool assumed_state = 6;
|
||||
buffer.encode_bool(6, a_switch->assumed_state());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(text_sensor);
|
||||
// string unique_id = 4;
|
||||
std::string unique_id = text_sensor->unique_id();
|
||||
if (unique_id.empty())
|
||||
unique_id = get_default_unique_id("text_sensor", text_sensor);
|
||||
buffer.encode_string(4, unique_id);
|
||||
// string icon = 5;
|
||||
buffer.encode_string(5, text_sensor->get_icon());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() {
|
||||
return this->client_->send_empty_message(APIMessageType::LIST_ENTITIES_DONE_RESPONSE);
|
||||
}
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
service->encode_list_service_response(buffer);
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(camera);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("camera", camera));
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
|
||||
auto buffer = this->client_->get_buffer();
|
||||
buffer.encode_nameable(climate);
|
||||
// string unique_id = 4;
|
||||
buffer.encode_string(4, get_default_unique_id("climate", climate));
|
||||
|
||||
auto traits = climate->get_traits();
|
||||
// bool supports_current_temperature = 5;
|
||||
buffer.encode_bool(5, traits.get_supports_current_temperature());
|
||||
// bool supports_two_point_target_temperature = 6;
|
||||
buffer.encode_bool(6, traits.get_supports_two_point_target_temperature());
|
||||
// repeated ClimateMode supported_modes = 7;
|
||||
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_HEAT}) {
|
||||
if (traits.supports_mode(mode))
|
||||
buffer.encode_uint32(7, mode, true);
|
||||
}
|
||||
|
||||
// float visual_min_temperature = 8;
|
||||
buffer.encode_float(8, traits.get_visual_min_temperature());
|
||||
// float visual_max_temperature = 9;
|
||||
buffer.encode_float(9, traits.get_visual_max_temperature());
|
||||
// float visual_temperature_step = 10;
|
||||
buffer.encode_float(10, traits.get_visual_temperature_step());
|
||||
// bool supports_away = 11;
|
||||
buffer.encode_bool(11, traits.get_supports_away());
|
||||
return this->client_->send_buffer(APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
APIMessageType ListEntitiesRequest::message_type() const { return APIMessageType::LIST_ENTITIES_REQUEST; }
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
57
esphome/components/api/list_entities.h
Normal file
57
esphome/components/api/list_entities.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class ListEntitiesRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
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"
|
||||
49
esphome/components/api/service_call_message.cpp
Normal file
49
esphome/components/api/service_call_message.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "service_call_message.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
APIMessageType SubscribeServiceCallsRequest::message_type() const {
|
||||
return APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST;
|
||||
}
|
||||
|
||||
APIMessageType ServiceCallResponse::message_type() const { return APIMessageType::SERVICE_CALL_RESPONSE; }
|
||||
void ServiceCallResponse::encode(APIBuffer &buffer) {
|
||||
// string service = 1;
|
||||
buffer.encode_string(1, this->service_);
|
||||
// map<string, string> data = 2;
|
||||
for (auto &it : this->data_) {
|
||||
auto nested = buffer.begin_nested(2);
|
||||
buffer.encode_string(1, it.key);
|
||||
buffer.encode_string(2, it.value);
|
||||
buffer.end_nested(nested);
|
||||
}
|
||||
// map<string, string> data_template = 3;
|
||||
for (auto &it : this->data_template_) {
|
||||
auto nested = buffer.begin_nested(3);
|
||||
buffer.encode_string(1, it.key);
|
||||
buffer.encode_string(2, it.value);
|
||||
buffer.end_nested(nested);
|
||||
}
|
||||
// map<string, string> variables = 4;
|
||||
for (auto &it : this->variables_) {
|
||||
auto nested = buffer.begin_nested(4);
|
||||
buffer.encode_string(1, it.key);
|
||||
buffer.encode_string(2, it.value());
|
||||
buffer.end_nested(nested);
|
||||
}
|
||||
}
|
||||
void ServiceCallResponse::set_service(const std::string &service) { this->service_ = service; }
|
||||
void ServiceCallResponse::set_data(const std::vector<KeyValuePair> &data) { this->data_ = data; }
|
||||
void ServiceCallResponse::set_data_template(const std::vector<KeyValuePair> &data_template) {
|
||||
this->data_template_ = data_template;
|
||||
}
|
||||
void ServiceCallResponse::set_variables(const std::vector<TemplatableKeyValuePair> &variables) {
|
||||
this->variables_ = variables;
|
||||
}
|
||||
|
||||
KeyValuePair::KeyValuePair(const std::string &key, const std::string &value) : key(key), value(value) {}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
53
esphome/components/api/service_call_message.h
Normal file
53
esphome/components/api/service_call_message.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class SubscribeServiceCallsRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class KeyValuePair {
|
||||
public:
|
||||
KeyValuePair(const std::string &key, const std::string &value);
|
||||
|
||||
std::string key;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
class TemplatableKeyValuePair {
|
||||
public:
|
||||
template<typename T> TemplatableKeyValuePair(std::string key, T func);
|
||||
|
||||
std::string key;
|
||||
std::function<std::string()> value;
|
||||
};
|
||||
template<typename T> TemplatableKeyValuePair::TemplatableKeyValuePair(std::string key, T func) : key(key) {
|
||||
this->value = [func]() -> std::string { return to_string(func()); };
|
||||
}
|
||||
|
||||
class ServiceCallResponse : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
|
||||
void encode(APIBuffer &buffer) override;
|
||||
|
||||
void set_service(const std::string &service);
|
||||
void set_data(const std::vector<KeyValuePair> &data);
|
||||
void set_data_template(const std::vector<KeyValuePair> &data_template);
|
||||
void set_variables(const std::vector<TemplatableKeyValuePair> &variables);
|
||||
|
||||
protected:
|
||||
std::string service_;
|
||||
std::vector<KeyValuePair> data_;
|
||||
std::vector<KeyValuePair> data_template_;
|
||||
std::vector<TemplatableKeyValuePair> variables_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
26
esphome/components/api/subscribe_logs.cpp
Normal file
26
esphome/components/api/subscribe_logs.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "subscribe_logs.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
APIMessageType SubscribeLogsRequest::message_type() const { return APIMessageType::SUBSCRIBE_LOGS_REQUEST; }
|
||||
bool SubscribeLogsRequest::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1: // LogLevel level = 1;
|
||||
this->level_ = value;
|
||||
return true;
|
||||
case 2: // bool dump_config = 2;
|
||||
this->dump_config_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
uint32_t SubscribeLogsRequest::get_level() const { return this->level_; }
|
||||
void SubscribeLogsRequest::set_level(uint32_t level) { this->level_ = level; }
|
||||
bool SubscribeLogsRequest::get_dump_config() const { return this->dump_config_; }
|
||||
void SubscribeLogsRequest::set_dump_config(bool dump_config) { this->dump_config_ = dump_config; }
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
24
esphome/components/api/subscribe_logs.h
Normal file
24
esphome/components/api/subscribe_logs.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class SubscribeLogsRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
uint32_t get_level() const;
|
||||
void set_level(uint32_t level);
|
||||
bool get_dump_config() const;
|
||||
void set_dump_config(bool dump_config);
|
||||
|
||||
protected:
|
||||
uint32_t level_{6};
|
||||
bool dump_config_{false};
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
77
esphome/components/api/subscribe_state.cpp
Normal file
77
esphome/components/api/subscribe_state.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "subscribe_state.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) {}
|
||||
|
||||
APIMessageType SubscribeStatesRequest::message_type() const { return APIMessageType::SUBSCRIBE_STATES_REQUEST; }
|
||||
|
||||
bool HomeAssistantStateResponse::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
// string entity_id = 1;
|
||||
this->entity_id_ = as_string(value, len);
|
||||
return true;
|
||||
case 2:
|
||||
// string state = 2;
|
||||
this->state_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType HomeAssistantStateResponse::message_type() const {
|
||||
return APIMessageType::HOME_ASSISTANT_STATE_RESPONSE;
|
||||
}
|
||||
const std::string &HomeAssistantStateResponse::get_entity_id() const { return this->entity_id_; }
|
||||
const std::string &HomeAssistantStateResponse::get_state() const { return this->state_; }
|
||||
APIMessageType SubscribeHomeAssistantStatesRequest::message_type() const {
|
||||
return APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
70
esphome/components/api/subscribe_state.h
Normal file
70
esphome/components/api/subscribe_state.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "util.h"
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class SubscribeStatesRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
class SubscribeHomeAssistantStatesRequest : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
};
|
||||
|
||||
class HomeAssistantStateResponse : public APIMessage {
|
||||
public:
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
APIMessageType message_type() const override;
|
||||
const std::string &get_entity_id() const;
|
||||
const std::string &get_state() const;
|
||||
|
||||
protected:
|
||||
std::string entity_id_;
|
||||
std::string state_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
#include "api_server.h"
|
||||
74
esphome/components/api/user_services.cpp
Normal file
74
esphome/components/api/user_services.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "user_services.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<> bool ExecuteServiceArgument::get_value<bool>() { return this->value_bool_; }
|
||||
template<> int ExecuteServiceArgument::get_value<int>() { return this->value_int_; }
|
||||
template<> float ExecuteServiceArgument::get_value<float>() { return this->value_float_; }
|
||||
template<> std::string ExecuteServiceArgument::get_value<std::string>() { return this->value_string_; }
|
||||
|
||||
APIMessageType ExecuteServiceArgument::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; }
|
||||
bool ExecuteServiceArgument::decode_varint(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1: // bool bool_ = 1;
|
||||
this->value_bool_ = value;
|
||||
return true;
|
||||
case 2: // int32 int_ = 2;
|
||||
this->value_int_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ExecuteServiceArgument::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 3: // float float_ = 3;
|
||||
this->value_float_ = as_float(value);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ExecuteServiceArgument::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 4: // string string_ = 4;
|
||||
this->value_string_ = as_string(value, len);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ExecuteServiceRequest::decode_32bit(uint32_t field_id, uint32_t value) {
|
||||
switch (field_id) {
|
||||
case 1: // fixed32 key = 1;
|
||||
this->key_ = value;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ExecuteServiceRequest::decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) {
|
||||
switch (field_id) {
|
||||
case 2: { // repeated ExecuteServiceArgument args = 2;
|
||||
ExecuteServiceArgument arg;
|
||||
arg.decode(value, len);
|
||||
this->args_.push_back(arg);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
APIMessageType ExecuteServiceRequest::message_type() const { return APIMessageType::EXECUTE_SERVICE_REQUEST; }
|
||||
const std::vector<ExecuteServiceArgument> &ExecuteServiceRequest::get_args() const { return this->args_; }
|
||||
uint32_t ExecuteServiceRequest::get_key() const { return this->key_; }
|
||||
|
||||
ServiceTypeArgument::ServiceTypeArgument(const std::string &name, ServiceArgType type) : name_(name), type_(type) {}
|
||||
const std::string &ServiceTypeArgument::get_name() const { return this->name_; }
|
||||
ServiceArgType ServiceTypeArgument::get_type() const { return this->type_; }
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
125
esphome/components/api/user_services.h
Normal file
125
esphome/components/api/user_services.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "api_message.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
enum ServiceArgType {
|
||||
SERVICE_ARG_TYPE_BOOL = 0,
|
||||
SERVICE_ARG_TYPE_INT = 1,
|
||||
SERVICE_ARG_TYPE_FLOAT = 2,
|
||||
SERVICE_ARG_TYPE_STRING = 3,
|
||||
};
|
||||
|
||||
class ServiceTypeArgument {
|
||||
public:
|
||||
ServiceTypeArgument(const std::string &name, ServiceArgType type);
|
||||
const std::string &get_name() const;
|
||||
ServiceArgType get_type() const;
|
||||
|
||||
protected:
|
||||
std::string name_;
|
||||
ServiceArgType type_;
|
||||
};
|
||||
|
||||
class ExecuteServiceArgument : public APIMessage {
|
||||
public:
|
||||
APIMessageType message_type() const override;
|
||||
template<typename T> T get_value();
|
||||
|
||||
bool decode_varint(uint32_t field_id, uint32_t value) override;
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
|
||||
protected:
|
||||
bool value_bool_{false};
|
||||
int value_int_{0};
|
||||
float value_float_{0.0f};
|
||||
std::string value_string_{};
|
||||
};
|
||||
|
||||
class ExecuteServiceRequest : public APIMessage {
|
||||
public:
|
||||
bool decode_length_delimited(uint32_t field_id, const uint8_t *value, size_t len) override;
|
||||
bool decode_32bit(uint32_t field_id, uint32_t value) override;
|
||||
APIMessageType message_type() const override;
|
||||
|
||||
uint32_t get_key() const;
|
||||
const std::vector<ExecuteServiceArgument> &get_args() const;
|
||||
|
||||
protected:
|
||||
uint32_t key_;
|
||||
std::vector<ExecuteServiceArgument> args_;
|
||||
};
|
||||
|
||||
class UserServiceDescriptor {
|
||||
public:
|
||||
virtual void encode_list_service_response(APIBuffer &buffer) = 0;
|
||||
|
||||
virtual bool execute_service(const ExecuteServiceRequest &req) = 0;
|
||||
};
|
||||
|
||||
template<typename... Ts> class UserService : public UserServiceDescriptor, public Trigger<Ts...> {
|
||||
public:
|
||||
UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args);
|
||||
|
||||
void encode_list_service_response(APIBuffer &buffer) override;
|
||||
|
||||
bool execute_service(const ExecuteServiceRequest &req) override;
|
||||
|
||||
protected:
|
||||
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>);
|
||||
|
||||
std::string name_;
|
||||
uint32_t key_{0};
|
||||
std::array<ServiceTypeArgument, sizeof...(Ts)> args_;
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
template<int... S>
|
||||
void UserService<Ts...>::execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
|
||||
this->trigger((args[S].get_value<Ts>())...);
|
||||
}
|
||||
template<typename... Ts> void UserService<Ts...>::encode_list_service_response(APIBuffer &buffer) {
|
||||
// string name = 1;
|
||||
buffer.encode_string(1, this->name_);
|
||||
// fixed32 key = 2;
|
||||
buffer.encode_fixed32(2, this->key_);
|
||||
|
||||
// repeated ListServicesArgument args = 3;
|
||||
for (auto &arg : this->args_) {
|
||||
auto nested = buffer.begin_nested(3);
|
||||
// string name = 1;
|
||||
buffer.encode_string(1, arg.get_name());
|
||||
// Type type = 2;
|
||||
buffer.encode_int32(2, arg.get_type());
|
||||
buffer.end_nested(nested);
|
||||
}
|
||||
}
|
||||
template<typename... Ts> bool UserService<Ts...>::execute_service(const ExecuteServiceRequest &req) {
|
||||
if (req.get_key() != this->key_)
|
||||
return false;
|
||||
|
||||
if (req.get_args().size() != this->args_.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->execute_(req.get_args(), typename gens<sizeof...(Ts)>::type());
|
||||
return true;
|
||||
}
|
||||
template<typename... Ts>
|
||||
UserService<Ts...>::UserService(const std::string &name, const std::array<ServiceTypeArgument, sizeof...(Ts)> &args)
|
||||
: name_(name), args_(args) {
|
||||
this->key_ = fnv1_hash(this->name_);
|
||||
}
|
||||
|
||||
template<> bool ExecuteServiceArgument::get_value<bool>();
|
||||
template<> int ExecuteServiceArgument::get_value<int>();
|
||||
template<> float ExecuteServiceArgument::get_value<float>();
|
||||
template<> std::string ExecuteServiceArgument::get_value<std::string>();
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
353
esphome/components/api/util.cpp
Normal file
353
esphome/components/api/util.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
#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 {
|
||||
|
||||
APIBuffer::APIBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
|
||||
size_t APIBuffer::get_length() const { return this->buffer_->size(); }
|
||||
void APIBuffer::write(uint8_t value) { this->buffer_->push_back(value); }
|
||||
void APIBuffer::encode_uint32(uint32_t field, uint32_t value, bool force) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field, 0);
|
||||
this->encode_varint_raw(value);
|
||||
}
|
||||
void APIBuffer::encode_int32(uint32_t field, int32_t value, bool force) {
|
||||
this->encode_uint32(field, static_cast<uint32_t>(value), force);
|
||||
}
|
||||
void APIBuffer::encode_bool(uint32_t field, bool value, bool force) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field, 0);
|
||||
this->write(0x01);
|
||||
}
|
||||
void APIBuffer::encode_string(uint32_t field, const std::string &value) {
|
||||
this->encode_string(field, value.data(), value.size());
|
||||
}
|
||||
void APIBuffer::encode_bytes(uint32_t field, const uint8_t *data, size_t len) {
|
||||
this->encode_string(field, reinterpret_cast<const char *>(data), len);
|
||||
}
|
||||
void APIBuffer::encode_string(uint32_t field, const char *string, size_t len) {
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field, 2);
|
||||
this->encode_varint_raw(len);
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t *>(string);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
this->write(data[i]);
|
||||
}
|
||||
}
|
||||
void APIBuffer::encode_fixed32(uint32_t field, uint32_t value, bool force) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field, 5);
|
||||
this->write((value >> 0) & 0xFF);
|
||||
this->write((value >> 8) & 0xFF);
|
||||
this->write((value >> 16) & 0xFF);
|
||||
this->write((value >> 24) & 0xFF);
|
||||
}
|
||||
void APIBuffer::encode_float(uint32_t field, float value, bool force) {
|
||||
if (value == 0.0f && !force)
|
||||
return;
|
||||
|
||||
union {
|
||||
float value_f;
|
||||
uint32_t value_raw;
|
||||
} val;
|
||||
val.value_f = value;
|
||||
this->encode_fixed32(field, val.value_raw);
|
||||
}
|
||||
void APIBuffer::encode_field_raw(uint32_t field, uint32_t type) {
|
||||
uint32_t val = (field << 3) | (type & 0b111);
|
||||
this->encode_varint_raw(val);
|
||||
}
|
||||
void APIBuffer::encode_varint_raw(uint32_t value) {
|
||||
if (value <= 0x7F) {
|
||||
this->write(value);
|
||||
return;
|
||||
}
|
||||
|
||||
while (value) {
|
||||
uint8_t temp = value & 0x7F;
|
||||
value >>= 7;
|
||||
if (value) {
|
||||
this->write(temp | 0x80);
|
||||
} else {
|
||||
this->write(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIBuffer::encode_sint32(uint32_t field, int32_t value, bool force) {
|
||||
if (value < 0)
|
||||
this->encode_uint32(field, ~(uint32_t(value) << 1), force);
|
||||
else
|
||||
this->encode_uint32(field, uint32_t(value) << 1, force);
|
||||
}
|
||||
void APIBuffer::encode_nameable(Nameable *nameable) {
|
||||
// string object_id = 1;
|
||||
this->encode_string(1, nameable->get_object_id());
|
||||
// fixed32 key = 2;
|
||||
this->encode_fixed32(2, nameable->get_object_id_hash());
|
||||
// string name = 3;
|
||||
this->encode_string(3, nameable->get_name());
|
||||
}
|
||||
size_t APIBuffer::begin_nested(uint32_t field) {
|
||||
this->encode_field_raw(field, 2);
|
||||
return this->buffer_->size();
|
||||
}
|
||||
void APIBuffer::end_nested(size_t begin_index) {
|
||||
const uint32_t nested_length = this->buffer_->size() - begin_index;
|
||||
// add varint
|
||||
std::vector<uint8_t> var;
|
||||
uint32_t val = nested_length;
|
||||
if (val <= 0x7F) {
|
||||
var.push_back(val);
|
||||
} else {
|
||||
while (val) {
|
||||
uint8_t temp = val & 0x7F;
|
||||
val >>= 7;
|
||||
if (val) {
|
||||
var.push_back(temp | 0x80);
|
||||
} else {
|
||||
var.push_back(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
this->buffer_->insert(this->buffer_->begin() + begin_index, var.begin(), var.end());
|
||||
}
|
||||
|
||||
optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed) {
|
||||
if (len == 0)
|
||||
return {};
|
||||
|
||||
uint32_t result = 0;
|
||||
uint8_t bitpos = 0;
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
uint8_t val = buf[i];
|
||||
result |= uint32_t(val & 0x7F) << bitpos;
|
||||
bitpos += 7;
|
||||
if ((val & 0x80) == 0) {
|
||||
if (consumed != nullptr) {
|
||||
*consumed = i + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string as_string(const uint8_t *value, size_t len) {
|
||||
return std::string(reinterpret_cast<const char *>(value), len);
|
||||
}
|
||||
|
||||
int32_t as_sint32(uint32_t val) {
|
||||
if (val & 1)
|
||||
return uint32_t(~(val >> 1));
|
||||
else
|
||||
return uint32_t(val >> 1);
|
||||
}
|
||||
|
||||
float as_float(uint32_t val) {
|
||||
static_assert(sizeof(uint32_t) == sizeof(float), "float must be 32bit long");
|
||||
union {
|
||||
uint32_t raw;
|
||||
float value;
|
||||
} x;
|
||||
x.raw = val;
|
||||
return x.value;
|
||||
}
|
||||
|
||||
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
|
||||
127
esphome/components/api/util.h
Normal file
127
esphome/components/api/util.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#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 APIBuffer {
|
||||
public:
|
||||
APIBuffer(std::vector<uint8_t> *buffer);
|
||||
|
||||
size_t get_length() const;
|
||||
void write(uint8_t value);
|
||||
|
||||
void encode_int32(uint32_t field, int32_t value, bool force = false);
|
||||
void encode_uint32(uint32_t field, uint32_t value, bool force = false);
|
||||
void encode_sint32(uint32_t field, int32_t value, bool force = false);
|
||||
void encode_bool(uint32_t field, bool value, bool force = false);
|
||||
void encode_string(uint32_t field, const std::string &value);
|
||||
void encode_string(uint32_t field, const char *string, size_t len);
|
||||
void encode_bytes(uint32_t field, const uint8_t *data, size_t len);
|
||||
void encode_fixed32(uint32_t field, uint32_t value, bool force = false);
|
||||
void encode_float(uint32_t field, float value, bool force = false);
|
||||
void encode_nameable(Nameable *nameable);
|
||||
|
||||
size_t begin_nested(uint32_t field);
|
||||
void end_nested(size_t begin_index);
|
||||
|
||||
void encode_field_raw(uint32_t field, uint32_t type);
|
||||
void encode_varint_raw(uint32_t value);
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
optional<uint32_t> proto_decode_varuint32(const uint8_t *buf, size_t len, uint32_t *consumed = nullptr);
|
||||
|
||||
std::string as_string(const uint8_t *value, size_t len);
|
||||
int32_t as_sint32(uint32_t val);
|
||||
float as_float(uint32_t val);
|
||||
|
||||
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
|
||||
0
esphome/components/bang_bang/__init__.py
Normal file
0
esphome/components/bang_bang/__init__.py
Normal file
156
esphome/components/bang_bang/bang_bang_climate.cpp
Normal file
156
esphome/components/bang_bang/bang_bang_climate.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "bang_bang_climate.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bang_bang {
|
||||
|
||||
static const char *TAG = "bang_bang.climate";
|
||||
|
||||
void BangBangClimate::setup() {
|
||||
this->sensor_->add_on_state_callback([this](float state) {
|
||||
this->current_temperature = state;
|
||||
// control may have changed, recompute
|
||||
this->compute_state_();
|
||||
// current temperature changed, publish state
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_temperature = this->sensor_->state;
|
||||
// restore set points
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
restore->to_call(this).perform();
|
||||
} else {
|
||||
// restore from defaults, change_away handles those for us
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
this->change_away_(false);
|
||||
}
|
||||
}
|
||||
void BangBangClimate::control(const climate::ClimateCall &call) {
|
||||
if (call.get_mode().has_value())
|
||||
this->mode = *call.get_mode();
|
||||
if (call.get_target_temperature_low().has_value())
|
||||
this->target_temperature_low = *call.get_target_temperature_low();
|
||||
if (call.get_target_temperature_high().has_value())
|
||||
this->target_temperature_high = *call.get_target_temperature_high();
|
||||
if (call.get_away().has_value())
|
||||
this->change_away_(*call.get_away());
|
||||
|
||||
this->compute_state_();
|
||||
this->publish_state();
|
||||
}
|
||||
climate::ClimateTraits BangBangClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
traits.set_supports_auto_mode(true);
|
||||
traits.set_supports_cool_mode(this->supports_cool_);
|
||||
traits.set_supports_heat_mode(this->supports_heat_);
|
||||
traits.set_supports_two_point_target_temperature(true);
|
||||
traits.set_supports_away(this->supports_away_);
|
||||
return traits;
|
||||
}
|
||||
void BangBangClimate::compute_state_() {
|
||||
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
||||
// in non-auto mode
|
||||
this->switch_to_mode_(this->mode);
|
||||
return;
|
||||
}
|
||||
|
||||
// auto mode, compute target mode
|
||||
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
|
||||
// if any control values are nan, go to OFF (idle) mode
|
||||
this->switch_to_mode_(climate::CLIMATE_MODE_OFF);
|
||||
return;
|
||||
}
|
||||
const bool too_cold = this->current_temperature < this->target_temperature_low;
|
||||
const bool too_hot = this->current_temperature > this->target_temperature_high;
|
||||
|
||||
climate::ClimateMode target_mode;
|
||||
if (too_cold) {
|
||||
// too cold -> enable heating if possible, else idle
|
||||
if (this->supports_heat_)
|
||||
target_mode = climate::CLIMATE_MODE_HEAT;
|
||||
else
|
||||
target_mode = climate::CLIMATE_MODE_OFF;
|
||||
} else if (too_hot) {
|
||||
// too hot -> enable cooling if possible, else idle
|
||||
if (this->supports_cool_)
|
||||
target_mode = climate::CLIMATE_MODE_COOL;
|
||||
else
|
||||
target_mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
// neither too hot nor too cold -> in range
|
||||
if (this->supports_cool_ && this->supports_heat_) {
|
||||
// if supports both ends, go to idle mode
|
||||
target_mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
// else use current mode and don't change (hysteresis)
|
||||
target_mode = this->internal_mode_;
|
||||
}
|
||||
}
|
||||
|
||||
this->switch_to_mode_(target_mode);
|
||||
}
|
||||
void BangBangClimate::switch_to_mode_(climate::ClimateMode mode) {
|
||||
if (mode == this->internal_mode_)
|
||||
// already in target mode
|
||||
return;
|
||||
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop();
|
||||
this->prev_trigger_ = nullptr;
|
||||
}
|
||||
Trigger<> *trig;
|
||||
switch (mode) {
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
trig = this->idle_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
trig = this->cool_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
trig = this->heat_trigger_;
|
||||
break;
|
||||
default:
|
||||
trig = nullptr;
|
||||
}
|
||||
if (trig != nullptr) {
|
||||
// trig should never be null, but still check so that we don't crash
|
||||
trig->trigger();
|
||||
this->internal_mode_ = mode;
|
||||
this->prev_trigger_ = trig;
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
void BangBangClimate::change_away_(bool away) {
|
||||
if (!away) {
|
||||
this->target_temperature_low = this->normal_config_.default_temperature_low;
|
||||
this->target_temperature_high = this->normal_config_.default_temperature_high;
|
||||
} else {
|
||||
this->target_temperature_low = this->away_config_.default_temperature_low;
|
||||
this->target_temperature_high = this->away_config_.default_temperature_high;
|
||||
}
|
||||
this->away = away;
|
||||
}
|
||||
void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) {
|
||||
this->normal_config_ = normal_config;
|
||||
}
|
||||
void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &away_config) {
|
||||
this->supports_away_ = true;
|
||||
this->away_config_ = away_config;
|
||||
}
|
||||
BangBangClimate::BangBangClimate()
|
||||
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
|
||||
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; }
|
||||
Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; }
|
||||
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; }
|
||||
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
|
||||
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig() = default;
|
||||
BangBangClimateTargetTempConfig::BangBangClimateTargetTempConfig(float default_temperature_low,
|
||||
float default_temperature_high)
|
||||
: default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
|
||||
|
||||
} // namespace bang_bang
|
||||
} // namespace esphome
|
||||
89
esphome/components/bang_bang/bang_bang_climate.h
Normal file
89
esphome/components/bang_bang/bang_bang_climate.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bang_bang {
|
||||
|
||||
struct BangBangClimateTargetTempConfig {
|
||||
public:
|
||||
BangBangClimateTargetTempConfig();
|
||||
BangBangClimateTargetTempConfig(float default_temperature_low, float default_temperature_high);
|
||||
|
||||
float default_temperature_low{NAN};
|
||||
float default_temperature_high{NAN};
|
||||
};
|
||||
|
||||
class BangBangClimate : public climate::Climate, public Component {
|
||||
public:
|
||||
BangBangClimate();
|
||||
void setup() override;
|
||||
|
||||
void set_sensor(sensor::Sensor *sensor);
|
||||
Trigger<> *get_idle_trigger() const;
|
||||
Trigger<> *get_cool_trigger() const;
|
||||
void set_supports_cool(bool supports_cool);
|
||||
Trigger<> *get_heat_trigger() const;
|
||||
void set_supports_heat(bool supports_heat);
|
||||
void set_normal_config(const BangBangClimateTargetTempConfig &normal_config);
|
||||
void set_away_config(const BangBangClimateTargetTempConfig &away_config);
|
||||
|
||||
protected:
|
||||
/// Override control to change settings of the climate device.
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
/// Change the away setting, will reset target temperatures to defaults.
|
||||
void change_away_(bool away);
|
||||
/// Return the traits of this controller.
|
||||
climate::ClimateTraits traits() override;
|
||||
|
||||
/// Re-compute the state of this climate controller.
|
||||
void compute_state_();
|
||||
|
||||
/// Switch the climate device to the given climate mode.
|
||||
void switch_to_mode_(climate::ClimateMode mode);
|
||||
|
||||
/// The sensor used for getting the current temperature
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
/** The trigger to call when the controller should switch to idle mode.
|
||||
*
|
||||
* In idle mode, the controller is assumed to have both heating and cooling disabled.
|
||||
*/
|
||||
Trigger<> *idle_trigger_;
|
||||
/** The trigger to call when the controller should switch to cooling mode.
|
||||
*/
|
||||
Trigger<> *cool_trigger_;
|
||||
/** Whether the controller supports cooling.
|
||||
*
|
||||
* A false value for this attribute means that the controller has no cooling action
|
||||
* (for example a thermostat, where only heating and not-heating is possible).
|
||||
*/
|
||||
bool supports_cool_{false};
|
||||
/** The trigger to call when the controller should switch to heating mode.
|
||||
*
|
||||
* A null value for this attribute means that the controller has no heating action
|
||||
* For example window blinds, where only cooling (blinds closed) and not-cooling
|
||||
* (blinds open) is possible.
|
||||
*/
|
||||
Trigger<> *heat_trigger_{nullptr};
|
||||
bool supports_heat_{false};
|
||||
/** A reference to the trigger that was previously active.
|
||||
*
|
||||
* This is so that the previous trigger can be stopped before enabling a new one.
|
||||
*/
|
||||
Trigger<> *prev_trigger_{nullptr};
|
||||
/** The climate mode that is currently active - for a `.mode = AUTO` this will
|
||||
* contain the actual mode the device
|
||||
*
|
||||
*/
|
||||
climate::ClimateMode internal_mode_{climate::CLIMATE_MODE_OFF};
|
||||
|
||||
BangBangClimateTargetTempConfig normal_config_{};
|
||||
bool supports_away_{false};
|
||||
BangBangClimateTargetTempConfig away_config_{};
|
||||
};
|
||||
|
||||
} // namespace bang_bang
|
||||
} // namespace esphome
|
||||
57
esphome/components/bang_bang/climate.py
Normal file
57
esphome/components/bang_bang/climate.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import climate, sensor
|
||||
from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \
|
||||
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, \
|
||||
CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR
|
||||
|
||||
bang_bang_ns = cg.esphome_ns.namespace('bang_bang')
|
||||
BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate, cg.Component)
|
||||
BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig')
|
||||
|
||||
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(BangBangClimate),
|
||||
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
||||
cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_AWAY_CONFIG): cv.Schema({
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
|
||||
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield climate.register_climate(var, config)
|
||||
|
||||
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_sensor(sens))
|
||||
|
||||
normal_config = BangBangClimateTargetTempConfig(
|
||||
config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
|
||||
config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
|
||||
)
|
||||
cg.add(var.set_normal_config(normal_config))
|
||||
|
||||
yield automation.build_automation(var.get_idle_trigger(), [], config[CONF_IDLE_ACTION])
|
||||
|
||||
if CONF_COOL_ACTION in config:
|
||||
yield automation.build_automation(var.get_cool_trigger(), [], config[CONF_COOL_ACTION])
|
||||
cg.add(var.set_supports_cool(True))
|
||||
if CONF_HEAT_ACTION in config:
|
||||
yield automation.build_automation(var.get_heat_trigger(), [], config[CONF_HEAT_ACTION])
|
||||
cg.add(var.set_supports_heat(True))
|
||||
|
||||
if CONF_AWAY_CONFIG in config:
|
||||
away = config[CONF_AWAY_CONFIG]
|
||||
away_config = BangBangClimateTargetTempConfig(
|
||||
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
|
||||
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
|
||||
)
|
||||
cg.add(var.set_away_config(away_config))
|
||||
0
esphome/components/bh1750/__init__.py
Normal file
0
esphome/components/bh1750/__init__.py
Normal file
78
esphome/components/bh1750/bh1750.cpp
Normal file
78
esphome/components/bh1750/bh1750.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "bh1750.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
|
||||
static const char *TAG = "bh1750.sensor";
|
||||
|
||||
static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001;
|
||||
|
||||
void BH1750Sensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str());
|
||||
if (!this->write_bytes(BH1750_COMMAND_POWER_ON, nullptr, 0)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
void BH1750Sensor::dump_config() {
|
||||
LOG_SENSOR("", "BH1750", this);
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with BH1750 failed!");
|
||||
}
|
||||
|
||||
const char *resolution_s;
|
||||
switch (this->resolution_) {
|
||||
case BH1750_RESOLUTION_0P5_LX:
|
||||
resolution_s = "0.5";
|
||||
break;
|
||||
case BH1750_RESOLUTION_1P0_LX:
|
||||
resolution_s = "1";
|
||||
break;
|
||||
case BH1750_RESOLUTION_4P0_LX:
|
||||
resolution_s = "4";
|
||||
break;
|
||||
default:
|
||||
resolution_s = "Unknown";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %s", resolution_s);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void BH1750Sensor::update() {
|
||||
if (!this->write_bytes(this->resolution_, nullptr, 0))
|
||||
return;
|
||||
|
||||
uint32_t wait = 0;
|
||||
// use max conversion times
|
||||
switch (this->resolution_) {
|
||||
case BH1750_RESOLUTION_0P5_LX:
|
||||
case BH1750_RESOLUTION_1P0_LX:
|
||||
wait = 180;
|
||||
break;
|
||||
case BH1750_RESOLUTION_4P0_LX:
|
||||
wait = 24;
|
||||
break;
|
||||
}
|
||||
|
||||
this->set_timeout("illuminance", wait, [this]() { this->read_data_(); });
|
||||
}
|
||||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void BH1750Sensor::read_data_() {
|
||||
uint16_t raw_value;
|
||||
if (!this->parent_->raw_receive_16(this->address_, &raw_value, 1)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
this->publish_state(lx);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; }
|
||||
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
46
esphome/components/bh1750/bh1750.h
Normal file
46
esphome/components/bh1750/bh1750.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
|
||||
/// Enum listing all resolutions that can be used with the BH1750
|
||||
enum BH1750Resolution {
|
||||
BH1750_RESOLUTION_4P0_LX = 0b00100011, // one-time low resolution mode
|
||||
BH1750_RESOLUTION_1P0_LX = 0b00100000, // one-time high resolution mode 1
|
||||
BH1750_RESOLUTION_0P5_LX = 0b00100001, // one-time high resolution mode 2
|
||||
};
|
||||
|
||||
/// This class implements support for the i2c-based BH1750 ambient light sensor.
|
||||
class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
/** Set the resolution of this sensor.
|
||||
*
|
||||
* Possible values are:
|
||||
*
|
||||
* - `BH1750_RESOLUTION_4P0_LX`
|
||||
* - `BH1750_RESOLUTION_1P0_LX`
|
||||
* - `BH1750_RESOLUTION_0P5_LX` (default)
|
||||
*
|
||||
* @param resolution The new resolution of the sensor.
|
||||
*/
|
||||
void set_resolution(BH1750Resolution resolution);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void read_data_();
|
||||
|
||||
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
|
||||
};
|
||||
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
30
esphome/components/bh1750/sensor.py
Normal file
30
esphome/components/bh1750/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_ID, CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
bh1750_ns = cg.esphome_ns.namespace('bh1750')
|
||||
BH1750Resolution = bh1750_ns.enum('BH1750Resolution')
|
||||
BH1750_RESOLUTIONS = {
|
||||
4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX,
|
||||
1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX,
|
||||
0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX,
|
||||
}
|
||||
|
||||
BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({
|
||||
cv.GenerateID(): cv.declare_id(BH1750Sensor),
|
||||
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
3
esphome/components/binary/__init__.py
Normal file
3
esphome/components/binary/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
binary_ns = cg.esphome_ns.namespace('binary')
|
||||
27
esphome/components/binary/fan/__init__.py
Normal file
27
esphome/components/binary/fan/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import fan, output
|
||||
from esphome.const import CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_OUTPUT_ID
|
||||
from .. import binary_ns
|
||||
|
||||
BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
|
||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
fan_ = yield fan.create_fan_state(config)
|
||||
cg.add(var.set_fan(fan_))
|
||||
output_ = yield cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(output_))
|
||||
|
||||
if CONF_OSCILLATION_OUTPUT in config:
|
||||
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
|
||||
cg.add(var.set_oscillation(oscillation_output))
|
||||
48
esphome/components/binary/fan/binary_fan.cpp
Normal file
48
esphome/components/binary/fan/binary_fan.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "binary_fan.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary {
|
||||
|
||||
static const char *TAG = "binary.fan";
|
||||
|
||||
void binary::BinaryFan::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str());
|
||||
if (this->fan_->get_traits().supports_oscillation()) {
|
||||
ESP_LOGCONFIG(TAG, " Oscillation: YES");
|
||||
}
|
||||
}
|
||||
void BinaryFan::setup() {
|
||||
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false);
|
||||
this->fan_->set_traits(traits);
|
||||
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
|
||||
}
|
||||
void BinaryFan::loop() {
|
||||
if (!this->next_update_) {
|
||||
return;
|
||||
}
|
||||
this->next_update_ = false;
|
||||
|
||||
{
|
||||
bool enable = this->fan_->state;
|
||||
if (enable)
|
||||
this->output_->turn_on();
|
||||
else
|
||||
this->output_->turn_off();
|
||||
ESP_LOGD(TAG, "Setting binary state: %s", ONOFF(enable));
|
||||
}
|
||||
|
||||
if (this->oscillating_ != nullptr) {
|
||||
bool enable = this->fan_->oscillating;
|
||||
if (enable) {
|
||||
this->oscillating_->turn_on();
|
||||
} else {
|
||||
this->oscillating_->turn_off();
|
||||
}
|
||||
ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable));
|
||||
}
|
||||
}
|
||||
float BinaryFan::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace binary
|
||||
} // namespace esphome
|
||||
28
esphome/components/binary/fan/binary_fan.h
Normal file
28
esphome/components/binary/fan/binary_fan.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/fan/fan_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary {
|
||||
|
||||
class BinaryFan : public Component {
|
||||
public:
|
||||
void set_fan(fan::FanState *fan) { fan_ = fan; }
|
||||
void set_output(output::BinaryOutput *output) { output_ = output; }
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
|
||||
|
||||
protected:
|
||||
fan::FanState *fan_;
|
||||
output::BinaryOutput *output_;
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
bool next_update_{true};
|
||||
};
|
||||
|
||||
} // namespace binary
|
||||
} // namespace esphome
|
||||
20
esphome/components/binary/light/__init__.py
Normal file
20
esphome/components/binary/light/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import light, output
|
||||
from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT
|
||||
from .. import binary_ns
|
||||
|
||||
BinaryLightOutput = binary_ns.class_('BinaryLightOutput', light.LightOutput)
|
||||
|
||||
CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput),
|
||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
yield light.register_light(var, config)
|
||||
|
||||
out = yield cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(out))
|
||||
32
esphome/components/binary/light/binary_light_output.h
Normal file
32
esphome/components/binary/light/binary_light_output.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary {
|
||||
|
||||
class BinaryLightOutput : public light::LightOutput {
|
||||
public:
|
||||
void set_output(output::BinaryOutput *output) { output_ = output; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(false);
|
||||
return traits;
|
||||
}
|
||||
void write_state(light::LightState *state) override {
|
||||
bool binary;
|
||||
state->current_values_as_binary(&binary);
|
||||
if (binary)
|
||||
this->output_->turn_on();
|
||||
else
|
||||
this->output_->turn_off();
|
||||
}
|
||||
|
||||
protected:
|
||||
output::BinaryOutput *output_;
|
||||
};
|
||||
|
||||
} // namespace binary
|
||||
} // namespace esphome
|
||||
308
esphome/components/binary_sensor/__init__.py
Normal file
308
esphome/components/binary_sensor/__init__.py
Normal file
@@ -0,0 +1,308 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
|
||||
CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, \
|
||||
CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \
|
||||
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
|
||||
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
from esphome.py_compat import string_types
|
||||
from esphome.util import Registry
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
|
||||
'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy',
|
||||
'opening', 'plug', 'power', 'presence', 'problem', 'safety', 'smoke',
|
||||
'sound', 'vibration', 'window'
|
||||
]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
binary_sensor_ns = cg.esphome_ns.namespace('binary_sensor')
|
||||
BinarySensor = binary_sensor_ns.class_('BinarySensor', cg.Nameable)
|
||||
BinarySensorPtr = BinarySensor.operator('ptr')
|
||||
|
||||
# Triggers
|
||||
PressTrigger = binary_sensor_ns.class_('PressTrigger', automation.Trigger.template())
|
||||
ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', automation.Trigger.template())
|
||||
ClickTrigger = binary_sensor_ns.class_('ClickTrigger', automation.Trigger.template())
|
||||
DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', automation.Trigger.template())
|
||||
MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', automation.Trigger.template(),
|
||||
cg.Component)
|
||||
MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent')
|
||||
StateTrigger = binary_sensor_ns.class_('StateTrigger', automation.Trigger.template(bool))
|
||||
BinarySensorPublishAction = binary_sensor_ns.class_('BinarySensorPublishAction', automation.Action)
|
||||
|
||||
# Condition
|
||||
BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition)
|
||||
|
||||
# Filters
|
||||
Filter = binary_sensor_ns.class_('Filter')
|
||||
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component)
|
||||
DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component)
|
||||
InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter)
|
||||
LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter)
|
||||
|
||||
FILTER_REGISTRY = Registry()
|
||||
validate_filters = cv.validate_registry('filter', FILTER_REGISTRY)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('invert', InvertFilter, {})
|
||||
def invert_filter_to_code(config, filter_id):
|
||||
yield cg.new_Pvariable(filter_id)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('delayed_on', DelayedOnFilter,
|
||||
cv.positive_time_period_milliseconds)
|
||||
def delayed_on_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('delayed_off', DelayedOffFilter, cv.positive_time_period_milliseconds)
|
||||
def delayed_off_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda)
|
||||
def lambda_filter_to_code(config, filter_id):
|
||||
lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool))
|
||||
yield cg.new_Pvariable(filter_id, lambda_)
|
||||
|
||||
|
||||
MULTI_CLICK_TIMING_SCHEMA = cv.Schema({
|
||||
cv.Optional(CONF_STATE): cv.boolean,
|
||||
cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds,
|
||||
})
|
||||
|
||||
|
||||
def parse_multi_click_timing_str(value):
|
||||
if not isinstance(value, string_types):
|
||||
return value
|
||||
|
||||
parts = value.lower().split(' ')
|
||||
if len(parts) != 5:
|
||||
raise cv.Invalid("Multi click timing grammar consists of exactly 5 words, not {}"
|
||||
"".format(len(parts)))
|
||||
try:
|
||||
state = cv.boolean(parts[0])
|
||||
except cv.Invalid:
|
||||
raise cv.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0]))
|
||||
|
||||
if parts[1] != 'for':
|
||||
raise cv.Invalid(u"Second word must be 'for', got {}".format(parts[1]))
|
||||
|
||||
if parts[2] == 'at':
|
||||
if parts[3] == 'least':
|
||||
key = CONF_MIN_LENGTH
|
||||
elif parts[3] == 'most':
|
||||
key = CONF_MAX_LENGTH
|
||||
else:
|
||||
raise cv.Invalid(u"Third word after at must either be 'least' or 'most', got {}"
|
||||
u"".format(parts[3]))
|
||||
try:
|
||||
length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err))
|
||||
return {
|
||||
CONF_STATE: state,
|
||||
key: str(length)
|
||||
}
|
||||
|
||||
if parts[3] != 'to':
|
||||
raise cv.Invalid("Multi click grammar: 4th word must be 'to'")
|
||||
|
||||
try:
|
||||
min_length = cv.positive_time_period_milliseconds(parts[2])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
|
||||
|
||||
try:
|
||||
max_length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
|
||||
|
||||
return {
|
||||
CONF_STATE: state,
|
||||
CONF_MIN_LENGTH: str(min_length),
|
||||
CONF_MAX_LENGTH: str(max_length)
|
||||
}
|
||||
|
||||
|
||||
def validate_multi_click_timing(value):
|
||||
if not isinstance(value, list):
|
||||
raise cv.Invalid("Timing option must be a *list* of times!")
|
||||
timings = []
|
||||
state = None
|
||||
for i, v_ in enumerate(value):
|
||||
v_ = MULTI_CLICK_TIMING_SCHEMA(v_)
|
||||
min_length = v_.get(CONF_MIN_LENGTH)
|
||||
max_length = v_.get(CONF_MAX_LENGTH)
|
||||
if min_length is None and max_length is None:
|
||||
raise cv.Invalid("At least one of min_length and max_length is required!")
|
||||
if min_length is None and max_length is not None:
|
||||
min_length = core.TimePeriodMilliseconds(milliseconds=0)
|
||||
|
||||
new_state = v_.get(CONF_STATE, not state)
|
||||
if new_state == state:
|
||||
raise cv.Invalid("Timings must have alternating state. Indices {} and {} have "
|
||||
"the same state {}".format(i, i + 1, state))
|
||||
if max_length is not None and max_length < min_length:
|
||||
raise cv.Invalid("Max length ({}) must be larger than min length ({})."
|
||||
"".format(max_length, min_length))
|
||||
|
||||
state = new_state
|
||||
tim = {
|
||||
CONF_STATE: new_state,
|
||||
CONF_MIN_LENGTH: min_length,
|
||||
}
|
||||
if max_length is not None:
|
||||
tim[CONF_MAX_LENGTH] = max_length
|
||||
timings.append(tim)
|
||||
return timings
|
||||
|
||||
|
||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space='_')
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTBinarySensorComponent),
|
||||
|
||||
cv.Optional(CONF_DEVICE_CLASS): device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
||||
}),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
||||
}),
|
||||
cv.Optional(CONF_ON_CLICK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
||||
cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
||||
cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
|
||||
cv.Required(CONF_TIMING): cv.All([parse_multi_click_timing_str],
|
||||
validate_multi_click_timing),
|
||||
cv.Optional(CONF_INVALID_COOLDOWN, default='1s'): cv.positive_time_period_milliseconds,
|
||||
}),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}),
|
||||
|
||||
cv.Optional(CONF_INVERTED): cv.invalid(
|
||||
"The inverted binary_sensor property has been replaced by the "
|
||||
"new 'invert' binary sensor filter. Please see "
|
||||
"https://esphome.io/components/binary_sensor/index.html."
|
||||
),
|
||||
})
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_binary_sensor_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(CONF_INTERNAL))
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
if CONF_INVERTED in config:
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
if CONF_FILTERS in config:
|
||||
filters = yield cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS])
|
||||
cg.add(var.add_filters(filters))
|
||||
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_RELEASE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_CLICK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var,
|
||||
conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_DOUBLE_CLICK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var,
|
||||
conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_MULTI_CLICK, []):
|
||||
timings = []
|
||||
for tim in conf[CONF_TIMING]:
|
||||
timings.append(cg.StructInitializer(
|
||||
MultiClickTriggerEvent,
|
||||
('state', tim[CONF_STATE]),
|
||||
('min_length', tim[CONF_MIN_LENGTH]),
|
||||
('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)),
|
||||
))
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
||||
if CONF_INVALID_COOLDOWN in conf:
|
||||
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
||||
yield cg.register_component(trigger, conf)
|
||||
yield automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
yield automation.build_automation(trigger, [(bool, 'x')], conf)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
yield mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_binary_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_binary_sensor(var))
|
||||
yield setup_binary_sensor_core_(var, config)
|
||||
|
||||
|
||||
@coroutine
|
||||
def new_binary_sensor(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
|
||||
yield register_binary_sensor(var, config)
|
||||
yield var
|
||||
|
||||
|
||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_id(BinarySensor),
|
||||
cv.Optional(CONF_FOR): cv.invalid("This option has been removed in 1.13, please use the "
|
||||
"'for' condition instead."),
|
||||
})
|
||||
|
||||
|
||||
@automation.register_condition('binary_sensor.is_on', BinarySensorCondition,
|
||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
||||
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition('binary_sensor.is_off', BinarySensorCondition,
|
||||
BINARY_SENSOR_CONDITION_SCHEMA)
|
||||
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_BINARY_SENSOR')
|
||||
cg.add_global(binary_sensor_ns.using)
|
||||
113
esphome/components/binary_sensor/automation.cpp
Normal file
113
esphome/components/binary_sensor/automation.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "automation.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary_sensor {
|
||||
|
||||
static const char *TAG = "binary_sensor.automation";
|
||||
|
||||
void binary_sensor::MultiClickTrigger::on_state_(bool state) {
|
||||
// Handle duplicate events
|
||||
if (state == this->last_state_) {
|
||||
return;
|
||||
}
|
||||
this->last_state_ = state;
|
||||
|
||||
// Cooldown: Do not immediately try matching after having invalid timing
|
||||
if (this->is_in_cooldown_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->at_index_.has_value()) {
|
||||
// Start matching
|
||||
MultiClickTriggerEvent evt = this->timing_[0];
|
||||
if (evt.state == state) {
|
||||
ESP_LOGV(TAG, "START min=%u max=%u", evt.min_length, evt.max_length);
|
||||
ESP_LOGV(TAG, "Multi Click: Starting multi click action!");
|
||||
this->at_index_ = 1;
|
||||
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
|
||||
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
||||
} else {
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
this->schedule_is_not_valid_(evt.max_length);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Multi Click: action not started because first level does not match!");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->is_valid_) {
|
||||
this->schedule_cooldown_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (*this->at_index_ == this->timing_.size()) {
|
||||
this->trigger_();
|
||||
return;
|
||||
}
|
||||
|
||||
MultiClickTriggerEvent evt = this->timing_[*this->at_index_];
|
||||
|
||||
if (evt.max_length != 4294967294UL) {
|
||||
ESP_LOGV(TAG, "A i=%u min=%u max=%u", *this->at_index_, evt.min_length, evt.max_length); // NOLINT
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
this->schedule_is_not_valid_(evt.max_length);
|
||||
} else if (*this->at_index_ + 1 != this->timing_.size()) {
|
||||
ESP_LOGV(TAG, "B i=%u min=%u", *this->at_index_, evt.min_length); // NOLINT
|
||||
this->cancel_timeout("is_not_valid");
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "C i=%u min=%u", *this->at_index_, evt.min_length); // NOLINT
|
||||
this->is_valid_ = false;
|
||||
this->cancel_timeout("is_not_valid");
|
||||
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
||||
}
|
||||
|
||||
*this->at_index_ = *this->at_index_ + 1;
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %u ms...", this->invalid_cooldown_);
|
||||
this->is_in_cooldown_ = true;
|
||||
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");
|
||||
this->is_in_cooldown_ = false;
|
||||
});
|
||||
this->at_index_.reset();
|
||||
this->cancel_timeout("trigger");
|
||||
this->cancel_timeout("is_valid");
|
||||
this->cancel_timeout("is_not_valid");
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
this->is_valid_ = false;
|
||||
this->set_timeout("is_valid", min_length, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||
this->is_valid_ = true;
|
||||
});
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||
this->set_timeout("is_not_valid", max_length, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
});
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::trigger_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
|
||||
this->at_index_.reset();
|
||||
this->cancel_timeout("trigger");
|
||||
this->cancel_timeout("is_valid");
|
||||
this->cancel_timeout("is_not_valid");
|
||||
this->trigger();
|
||||
}
|
||||
|
||||
bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) {
|
||||
if (max_length == 0) {
|
||||
return length >= min_length;
|
||||
} else {
|
||||
return length >= min_length && length <= max_length;
|
||||
}
|
||||
}
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
150
esphome/components/binary_sensor/automation.h
Normal file
150
esphome/components/binary_sensor/automation.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary_sensor {
|
||||
|
||||
struct MultiClickTriggerEvent {
|
||||
bool state;
|
||||
uint32_t min_length;
|
||||
uint32_t max_length;
|
||||
};
|
||||
|
||||
class PressTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit PressTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (state)
|
||||
this->trigger();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class ReleaseTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ReleaseTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (!state)
|
||||
this->trigger();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length);
|
||||
|
||||
class ClickTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ClickTrigger(BinarySensor *parent, uint32_t min_length, uint32_t max_length)
|
||||
: min_length_(min_length), max_length_(max_length) {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
if (state) {
|
||||
this->start_time_ = millis();
|
||||
} else {
|
||||
const uint32_t length = millis() - this->start_time_;
|
||||
if (match_interval(this->min_length_, this->max_length_, length))
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t start_time_{0}; /// The millis() time when the click started.
|
||||
uint32_t min_length_; /// Minimum length of click. 0 means no minimum.
|
||||
uint32_t max_length_; /// Maximum length of click. 0 means no maximum.
|
||||
};
|
||||
|
||||
class DoubleClickTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit DoubleClickTrigger(BinarySensor *parent, uint32_t min_length, uint32_t max_length)
|
||||
: min_length_(min_length), max_length_(max_length) {
|
||||
parent->add_on_state_callback([this](bool state) {
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (state && this->start_time_ != 0 && this->end_time_ != 0) {
|
||||
if (match_interval(this->min_length_, this->max_length_, this->end_time_ - this->start_time_) &&
|
||||
match_interval(this->min_length_, this->max_length_, now - this->end_time_)) {
|
||||
this->trigger();
|
||||
this->start_time_ = 0;
|
||||
this->end_time_ = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->start_time_ = this->end_time_;
|
||||
this->end_time_ = now;
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t start_time_{0};
|
||||
uint32_t end_time_{0};
|
||||
uint32_t min_length_; /// Minimum length of click. 0 means no minimum.
|
||||
uint32_t max_length_; /// Maximum length of click. 0 means no maximum.
|
||||
};
|
||||
|
||||
class MultiClickTrigger : public Trigger<>, public Component {
|
||||
public:
|
||||
explicit MultiClickTrigger(BinarySensor *parent, const std::vector<MultiClickTriggerEvent> &timing)
|
||||
: parent_(parent), timing_(timing) {}
|
||||
|
||||
void setup() override {
|
||||
this->last_state_ = this->parent_->state;
|
||||
auto f = std::bind(&MultiClickTrigger::on_state_, this, std::placeholders::_1);
|
||||
this->parent_->add_on_state_callback(f);
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void set_invalid_cooldown(uint32_t invalid_cooldown) { this->invalid_cooldown_ = invalid_cooldown; }
|
||||
|
||||
protected:
|
||||
void on_state_(bool state);
|
||||
void schedule_cooldown_();
|
||||
void schedule_is_valid_(uint32_t min_length);
|
||||
void schedule_is_not_valid_(uint32_t max_length);
|
||||
void trigger_();
|
||||
|
||||
BinarySensor *parent_;
|
||||
std::vector<MultiClickTriggerEvent> timing_;
|
||||
uint32_t invalid_cooldown_{1000};
|
||||
optional<size_t> at_index_{};
|
||||
bool last_state_{false};
|
||||
bool is_in_cooldown_{false};
|
||||
bool is_valid_{false};
|
||||
};
|
||||
|
||||
class StateTrigger : public Trigger<bool> {
|
||||
public:
|
||||
explicit StateTrigger(BinarySensor *parent) {
|
||||
parent->add_on_state_callback([this](bool state) { this->trigger(state); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> {
|
||||
public:
|
||||
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(Ts... x) override { return this->parent_->state == this->state_; }
|
||||
|
||||
protected:
|
||||
BinarySensor *parent_;
|
||||
bool state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
void play(Ts... x) override {
|
||||
auto val = this->state_.value(x...);
|
||||
this->sensor_->publish_state(val);
|
||||
}
|
||||
|
||||
protected:
|
||||
BinarySensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
71
esphome/components/binary_sensor/binary_sensor.cpp
Normal file
71
esphome/components/binary_sensor/binary_sensor.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "binary_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
static const char *TAG = "binary_sensor";
|
||||
|
||||
void BinarySensor::add_on_state_callback(std::function<void(bool)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void BinarySensor::publish_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, false);
|
||||
} else {
|
||||
this->filter_list_->input(state, false);
|
||||
}
|
||||
}
|
||||
void BinarySensor::publish_initial_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, true);
|
||||
} else {
|
||||
this->filter_list_->input(state, true);
|
||||
}
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), state ? "ON" : "OFF");
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
if (!is_initial) {
|
||||
this->state_callback_.call(state);
|
||||
}
|
||||
}
|
||||
std::string BinarySensor::device_class() { return ""; }
|
||||
BinarySensor::BinarySensor(const std::string &name) : Nameable(name), state(false) {}
|
||||
BinarySensor::BinarySensor() : BinarySensor("") {}
|
||||
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
std::string BinarySensor::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
return this->device_class();
|
||||
}
|
||||
void BinarySensor::add_filter(Filter *filter) {
|
||||
filter->parent_ = this;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->filter_list_ = filter;
|
||||
} else {
|
||||
Filter *last_filter = this->filter_list_;
|
||||
while (last_filter->next_ != nullptr)
|
||||
last_filter = last_filter->next_;
|
||||
last_filter->next_ = filter;
|
||||
}
|
||||
}
|
||||
void BinarySensor::add_filters(std::vector<Filter *> filters) {
|
||||
for (Filter *filter : filters) {
|
||||
this->add_filter(filter);
|
||||
}
|
||||
}
|
||||
bool BinarySensor::has_state() const { return this->has_state_; }
|
||||
uint32_t BinarySensor::hash_base() { return 1210250844UL; }
|
||||
bool BinarySensor::is_status_binary_sensor() const { return false; }
|
||||
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
90
esphome/components/binary_sensor/binary_sensor.h
Normal file
90
esphome/components/binary_sensor/binary_sensor.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/filter.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
|
||||
if (obj != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, prefix type " '%s'", obj->get_name().c_str()); \
|
||||
if (!obj->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, prefix " Device Class: '%s'", obj->get_device_class().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
/** Base class for all binary_sensor-type classes.
|
||||
*
|
||||
* This class includes a callback that components such as MQTT can subscribe to for state changes.
|
||||
* The sub classes should notify the front-end of new states via the publish_state() method which
|
||||
* handles inverted inputs for you.
|
||||
*/
|
||||
class BinarySensor : public Nameable {
|
||||
public:
|
||||
explicit BinarySensor();
|
||||
/** Construct a binary sensor with the specified name
|
||||
*
|
||||
* @param name Name of this binary sensor.
|
||||
*/
|
||||
explicit BinarySensor(const std::string &name);
|
||||
|
||||
/** Add a callback to be notified of state changes.
|
||||
*
|
||||
* @param callback The void(bool) callback.
|
||||
*/
|
||||
void add_on_state_callback(std::function<void(bool)> &&callback);
|
||||
|
||||
/** Publish a new state to the front-end.
|
||||
*
|
||||
* @param state The new state.
|
||||
*/
|
||||
void publish_state(bool state);
|
||||
|
||||
/** Publish the initial state, this will not make the callback manager send callbacks
|
||||
* and is meant only for the initial state on boot.
|
||||
*
|
||||
* @param state The new state.
|
||||
*/
|
||||
void publish_initial_state(bool state);
|
||||
|
||||
/// The current reported state of the binary sensor.
|
||||
bool state;
|
||||
|
||||
/// Manually set the Home Assistant device class (see binary_sensor::device_class)
|
||||
void set_device_class(const std::string &device_class);
|
||||
|
||||
/// Get the device class for this binary sensor, using the manual override if specified.
|
||||
std::string get_device_class();
|
||||
|
||||
void add_filter(Filter *filter);
|
||||
void add_filters(std::vector<Filter *> filters);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool state, bool is_initial);
|
||||
|
||||
/// Return whether this binary sensor has outputted a state.
|
||||
bool has_state() const;
|
||||
|
||||
virtual bool is_status_binary_sensor() const;
|
||||
|
||||
// ========== OVERRIDE METHODS ==========
|
||||
// (You'll only need this when creating your own custom binary sensor)
|
||||
/// Get the default device class for this sensor, or empty string for no default.
|
||||
virtual std::string device_class();
|
||||
|
||||
protected:
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void(bool)> state_callback_{};
|
||||
optional<std::string> device_class_{}; ///< Stores the override of the device class
|
||||
Filter *filter_list_{nullptr};
|
||||
bool has_state_{false};
|
||||
Deduplicator<bool> publish_dedup_;
|
||||
};
|
||||
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
68
esphome/components/binary_sensor/filter.cpp
Normal file
68
esphome/components/binary_sensor/filter.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "filter.h"
|
||||
#include "binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
static const char *TAG = "sensor.filter";
|
||||
|
||||
void Filter::output(bool value, bool is_initial) {
|
||||
if (!this->dedup_.next(value))
|
||||
return;
|
||||
|
||||
if (this->next_ == nullptr) {
|
||||
this->parent_->send_state_internal(value, is_initial);
|
||||
} else {
|
||||
this->next_->input(value, is_initial);
|
||||
}
|
||||
}
|
||||
void Filter::input(bool value, bool is_initial) {
|
||||
auto b = this->new_value(value, is_initial);
|
||||
if (b.has_value()) {
|
||||
this->output(*b, is_initial);
|
||||
}
|
||||
}
|
||||
DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {}
|
||||
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
if (value) {
|
||||
this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("ON");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {}
|
||||
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
if (!value) {
|
||||
this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("OFF");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
||||
|
||||
LambdaFilter::LambdaFilter(const std::function<optional<bool>(bool)> &f) : f_(f) {}
|
||||
|
||||
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
|
||||
|
||||
optional<bool> UniqueFilter::new_value(bool value, bool is_initial) {
|
||||
if (this->last_value_.has_value() && *this->last_value_ == value) {
|
||||
return {};
|
||||
} else {
|
||||
this->last_value_ = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
77
esphome/components/binary_sensor/filter.h
Normal file
77
esphome/components/binary_sensor/filter.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
class BinarySensor;
|
||||
|
||||
class Filter {
|
||||
public:
|
||||
virtual optional<bool> new_value(bool value, bool is_initial) = 0;
|
||||
|
||||
void input(bool value, bool is_initial);
|
||||
|
||||
void output(bool value, bool is_initial);
|
||||
|
||||
protected:
|
||||
friend BinarySensor;
|
||||
|
||||
Filter *next_{nullptr};
|
||||
BinarySensor *parent_{nullptr};
|
||||
Deduplicator<bool> dedup_;
|
||||
};
|
||||
|
||||
class DelayedOnFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit DelayedOnFilter(uint32_t delay);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
uint32_t delay_;
|
||||
};
|
||||
|
||||
class DelayedOffFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit DelayedOffFilter(uint32_t delay);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
uint32_t delay_;
|
||||
};
|
||||
|
||||
class InvertFilter : public Filter {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
};
|
||||
|
||||
class LambdaFilter : public Filter {
|
||||
public:
|
||||
explicit LambdaFilter(const std::function<optional<bool>(bool)> &f);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
|
||||
protected:
|
||||
std::function<optional<bool>(bool)> f_;
|
||||
};
|
||||
|
||||
class UniqueFilter : public Filter {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
|
||||
protected:
|
||||
optional<bool> last_value_{};
|
||||
};
|
||||
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
0
esphome/components/binary_sensor_map/__init__.py
Normal file
0
esphome/components/binary_sensor_map/__init__.py
Normal file
60
esphome/components/binary_sensor_map/binary_sensor_map.cpp
Normal file
60
esphome/components/binary_sensor_map/binary_sensor_map.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "binary_sensor_map.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary_sensor_map {
|
||||
|
||||
static const char *TAG = "binary_sensor_map";
|
||||
|
||||
void BinarySensorMap::dump_config() { LOG_SENSOR(" ", "binary_sensor_map", this); }
|
||||
|
||||
void BinarySensorMap::loop() {
|
||||
switch (this->sensor_type_) {
|
||||
case BINARY_SENSOR_MAP_TYPE_GROUP:
|
||||
this->process_group_();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BinarySensorMap::process_group_() {
|
||||
float total_current_value = 0.0;
|
||||
uint8_t num_active_sensors = 0;
|
||||
uint64_t mask = 0x00;
|
||||
// check all binary_sensors for its state. when active add its value to total_current_value.
|
||||
// create a bitmask for the binary_sensor status on all channels
|
||||
for (size_t i = 0; i < this->channels_.size(); i++) {
|
||||
auto bs = this->channels_[i];
|
||||
if (bs.binary_sensor->state) {
|
||||
num_active_sensors++;
|
||||
total_current_value += bs.sensor_value;
|
||||
mask |= 1 << i;
|
||||
}
|
||||
}
|
||||
// check if the sensor map was touched
|
||||
if (mask != 0ULL) {
|
||||
// did the bit_mask change or is it a new sensor touch
|
||||
if (this->last_mask_ != mask) {
|
||||
float publish_value = total_current_value / num_active_sensors;
|
||||
ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value);
|
||||
this->publish_state(publish_value);
|
||||
}
|
||||
} else if (this->last_mask_ != 0ULL) {
|
||||
// is this a new sensor release
|
||||
ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
|
||||
this->publish_state(NAN);
|
||||
}
|
||||
this->last_mask_ = mask;
|
||||
}
|
||||
|
||||
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
|
||||
BinarySensorMapChannel sensor_channel{
|
||||
.binary_sensor = sensor,
|
||||
.sensor_value = value,
|
||||
};
|
||||
this->channels_.push_back(sensor_channel);
|
||||
}
|
||||
|
||||
void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
|
||||
|
||||
} // namespace binary_sensor_map
|
||||
} // namespace esphome
|
||||
58
esphome/components/binary_sensor_map/binary_sensor_map.h
Normal file
58
esphome/components/binary_sensor_map/binary_sensor_map.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary_sensor_map {
|
||||
|
||||
enum BinarySensorMapType {
|
||||
BINARY_SENSOR_MAP_TYPE_GROUP,
|
||||
};
|
||||
|
||||
struct BinarySensorMapChannel {
|
||||
binary_sensor::BinarySensor *binary_sensor;
|
||||
float sensor_value;
|
||||
};
|
||||
|
||||
/** Class to group binary_sensors to one Sensor.
|
||||
*
|
||||
* Each binary sensor represents a float value in the group.
|
||||
*/
|
||||
class BinarySensorMap : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
/**
|
||||
* The loop checks all binary_sensor states
|
||||
* When the binary_sensor reports a true value for its state, then the float value it represents is added to the
|
||||
* total_current_value
|
||||
*
|
||||
* Only when the total_current_value changed and at least one sensor reports an active state we publish the sensors
|
||||
* average value. When the value changed and no sensors ar active we publish NAN.
|
||||
* */
|
||||
void loop() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
/** Add binary_sensors to the group.
|
||||
* Each binary_sensor represents a float value when its state is true
|
||||
*
|
||||
* @param *sensor The binary sensor.
|
||||
* @param value The value this binary_sensor represents
|
||||
*/
|
||||
void add_channel(binary_sensor::BinarySensor *sensor, float value);
|
||||
void set_sensor_type(BinarySensorMapType sensor_type);
|
||||
|
||||
protected:
|
||||
std::vector<BinarySensorMapChannel> channels_{};
|
||||
BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP};
|
||||
// this gives max 64 channels per binary_sensor_map
|
||||
uint64_t last_mask_{0x00};
|
||||
/**
|
||||
* methods to process the types of binary_sensor_maps
|
||||
* GROUP: process_group_() just map to a value
|
||||
* */
|
||||
void process_group_();
|
||||
};
|
||||
|
||||
} // namespace binary_sensor_map
|
||||
} // namespace esphome
|
||||
42
esphome/components/binary_sensor_map/sensor.py
Normal file
42
esphome/components/binary_sensor_map/sensor.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome.components import sensor, binary_sensor
|
||||
from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \
|
||||
ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR
|
||||
|
||||
DEPENDENCIES = ['binary_sensor']
|
||||
|
||||
binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map')
|
||||
BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor)
|
||||
SensorMapType = binary_sensor_map_ns.enum('SensorMapType')
|
||||
|
||||
CONF_GROUP = 'group'
|
||||
SENSOR_MAP_TYPES = {
|
||||
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
|
||||
}
|
||||
|
||||
entry = {
|
||||
cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Required(CONF_VALUE): cv.float_,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema({
|
||||
CONF_GROUP: sensor.sensor_schema(UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0).extend({
|
||||
cv.GenerateID(): cv.declare_id(BinarySensorMap),
|
||||
cv.Required(CONF_CHANNELS): cv.All(cv.ensure_list(entry), cv.Length(min=1)),
|
||||
}),
|
||||
}, lower=True)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
|
||||
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
|
||||
cg.add(var.set_sensor_type(constant))
|
||||
|
||||
for ch in config[CONF_CHANNELS]:
|
||||
input_var = yield cg.get_variable(ch[CONF_BINARY_SENSOR])
|
||||
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
|
||||
0
esphome/components/ble_presence/__init__.py
Normal file
0
esphome/components/ble_presence/__init__.py
Normal file
24
esphome/components/ble_presence/binary_sensor.py
Normal file
24
esphome/components/ble_presence/binary_sensor.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_ID
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
ble_presence_ns = cg.esphome_ns.namespace('ble_presence')
|
||||
BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor,
|
||||
cg.Component, esp32_ble_tracker.ESPBTDeviceListener)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
yield binary_sensor.register_binary_sensor(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
16
esphome/components/ble_presence/ble_presence_device.cpp
Normal file
16
esphome/components/ble_presence/ble_presence_device.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "ble_presence_device.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_presence {
|
||||
|
||||
static const char *TAG = "ble_presence";
|
||||
|
||||
void BLEPresenceDevice::dump_config() { LOG_BINARY_SENSOR("", "BLE Presence", this); }
|
||||
|
||||
} // namespace ble_presence
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
41
esphome/components/ble_presence/ble_presence_device.h
Normal file
41
esphome/components/ble_presence/ble_presence_device.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_presence {
|
||||
|
||||
class BLEPresenceDevice : public binary_sensor::BinarySensor,
|
||||
public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
public Component {
|
||||
public:
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
void on_scan_end() override {
|
||||
if (!this->found_)
|
||||
this->publish_state(false);
|
||||
this->found_ = false;
|
||||
}
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool found_{false};
|
||||
uint64_t address_;
|
||||
};
|
||||
|
||||
} // namespace ble_presence
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
0
esphome/components/ble_rssi/__init__.py
Normal file
0
esphome/components/ble_rssi/__init__.py
Normal file
16
esphome/components/ble_rssi/ble_rssi_sensor.cpp
Normal file
16
esphome/components/ble_rssi/ble_rssi_sensor.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "ble_rssi_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_rssi {
|
||||
|
||||
static const char *TAG = "ble_rssi";
|
||||
|
||||
void BLERSSISensor::dump_config() { LOG_SENSOR("", "BLE RSSI Sensor", this); }
|
||||
|
||||
} // namespace ble_rssi
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
39
esphome/components/ble_rssi/ble_rssi_sensor.h
Normal file
39
esphome/components/ble_rssi/ble_rssi_sensor.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_rssi {
|
||||
|
||||
class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
void on_scan_end() override {
|
||||
if (!this->found_)
|
||||
this->publish_state(NAN);
|
||||
this->found_ = false;
|
||||
}
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool found_{false};
|
||||
uint64_t address_;
|
||||
};
|
||||
|
||||
} // namespace ble_rssi
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
24
esphome/components/ble_rssi/sensor.py
Normal file
24
esphome/components/ble_rssi/sensor.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi')
|
||||
BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component,
|
||||
esp32_ble_tracker.ESPBTDeviceListener)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({
|
||||
cv.GenerateID(): cv.declare_id(BLERSSISensor),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
0
esphome/components/bme280/__init__.py
Normal file
0
esphome/components/bme280/__init__.py
Normal file
318
esphome/components/bme280/bme280.cpp
Normal file
318
esphome/components/bme280/bme280.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
#include "bme280.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme280 {
|
||||
|
||||
static const char *TAG = "bme280.sensor";
|
||||
|
||||
static const uint8_t BME280_REGISTER_DIG_T1 = 0x88;
|
||||
static const uint8_t BME280_REGISTER_DIG_T2 = 0x8A;
|
||||
static const uint8_t BME280_REGISTER_DIG_T3 = 0x8C;
|
||||
|
||||
static const uint8_t BME280_REGISTER_DIG_P1 = 0x8E;
|
||||
static const uint8_t BME280_REGISTER_DIG_P2 = 0x90;
|
||||
static const uint8_t BME280_REGISTER_DIG_P3 = 0x92;
|
||||
static const uint8_t BME280_REGISTER_DIG_P4 = 0x94;
|
||||
static const uint8_t BME280_REGISTER_DIG_P5 = 0x96;
|
||||
static const uint8_t BME280_REGISTER_DIG_P6 = 0x98;
|
||||
static const uint8_t BME280_REGISTER_DIG_P7 = 0x9A;
|
||||
static const uint8_t BME280_REGISTER_DIG_P8 = 0x9C;
|
||||
static const uint8_t BME280_REGISTER_DIG_P9 = 0x9E;
|
||||
|
||||
static const uint8_t BME280_REGISTER_DIG_H1 = 0xA1;
|
||||
static const uint8_t BME280_REGISTER_DIG_H2 = 0xE1;
|
||||
static const uint8_t BME280_REGISTER_DIG_H3 = 0xE3;
|
||||
static const uint8_t BME280_REGISTER_DIG_H4 = 0xE4;
|
||||
static const uint8_t BME280_REGISTER_DIG_H5 = 0xE5;
|
||||
static const uint8_t BME280_REGISTER_DIG_H6 = 0xE7;
|
||||
|
||||
static const uint8_t BME280_REGISTER_CHIPID = 0xD0;
|
||||
|
||||
static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
|
||||
static const uint8_t BME280_REGISTER_STATUS = 0xF3;
|
||||
static const uint8_t BME280_REGISTER_CONTROL = 0xF4;
|
||||
static const uint8_t BME280_REGISTER_CONFIG = 0xF5;
|
||||
static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7;
|
||||
static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
|
||||
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;
|
||||
|
||||
static const uint8_t BME280_MODE_FORCED = 0b01;
|
||||
|
||||
inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); }
|
||||
|
||||
static const char *oversampling_to_str(BME280Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case BME280_OVERSAMPLING_NONE:
|
||||
return "None";
|
||||
case BME280_OVERSAMPLING_1X:
|
||||
return "1x";
|
||||
case BME280_OVERSAMPLING_2X:
|
||||
return "2x";
|
||||
case BME280_OVERSAMPLING_4X:
|
||||
return "4x";
|
||||
case BME280_OVERSAMPLING_8X:
|
||||
return "8x";
|
||||
case BME280_OVERSAMPLING_16X:
|
||||
return "16x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *iir_filter_to_str(BME280IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case BME280_IIR_FILTER_OFF:
|
||||
return "OFF";
|
||||
case BME280_IIR_FILTER_2X:
|
||||
return "2x";
|
||||
case BME280_IIR_FILTER_4X:
|
||||
return "4x";
|
||||
case BME280_IIR_FILTER_8X:
|
||||
return "8x";
|
||||
case BME280_IIR_FILTER_16X:
|
||||
return "16x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void BME280Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME280...");
|
||||
uint8_t chip_id = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x60) {
|
||||
this->error_code_ = WRONG_CHIP_ID;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read calibration
|
||||
this->calibration_.t1 = read_u16_le_(BME280_REGISTER_DIG_T1);
|
||||
this->calibration_.t2 = read_s16_le_(BME280_REGISTER_DIG_T2);
|
||||
this->calibration_.t3 = read_s16_le_(BME280_REGISTER_DIG_T3);
|
||||
|
||||
this->calibration_.p1 = read_u16_le_(BME280_REGISTER_DIG_P1);
|
||||
this->calibration_.p2 = read_s16_le_(BME280_REGISTER_DIG_P2);
|
||||
this->calibration_.p3 = read_s16_le_(BME280_REGISTER_DIG_P3);
|
||||
this->calibration_.p4 = read_s16_le_(BME280_REGISTER_DIG_P4);
|
||||
this->calibration_.p5 = read_s16_le_(BME280_REGISTER_DIG_P5);
|
||||
this->calibration_.p6 = read_s16_le_(BME280_REGISTER_DIG_P6);
|
||||
this->calibration_.p7 = read_s16_le_(BME280_REGISTER_DIG_P7);
|
||||
this->calibration_.p8 = read_s16_le_(BME280_REGISTER_DIG_P8);
|
||||
this->calibration_.p9 = read_s16_le_(BME280_REGISTER_DIG_P9);
|
||||
|
||||
this->calibration_.h1 = read_u8_(BME280_REGISTER_DIG_H1);
|
||||
this->calibration_.h2 = read_s16_le_(BME280_REGISTER_DIG_H2);
|
||||
this->calibration_.h3 = read_u8_(BME280_REGISTER_DIG_H3);
|
||||
this->calibration_.h4 = read_u8_(BME280_REGISTER_DIG_H4) << 4 | (read_u8_(BME280_REGISTER_DIG_H4 + 1) & 0x0F);
|
||||
this->calibration_.h5 = read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4);
|
||||
this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6);
|
||||
|
||||
uint8_t humid_register = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
humid_register &= ~0b00000111;
|
||||
humid_register |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
void BME280Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BME280:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with BME280 failed!");
|
||||
break;
|
||||
case WRONG_CHIP_ID:
|
||||
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BMP280?");
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->temperature_oversampling_));
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->humidity_oversampling_));
|
||||
}
|
||||
float BME280Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
inline uint8_t oversampling_to_time(BME280Oversampling over_sampling) { return (1 << uint8_t(over_sampling)) >> 1; }
|
||||
|
||||
void BME280Component::update() {
|
||||
// Enable sensor
|
||||
ESP_LOGV(TAG, "Sending conversion request...");
|
||||
uint8_t meas_register = 0;
|
||||
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_register |= 0b01; // Forced mode
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float meas_time = 1;
|
||||
meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
|
||||
meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
|
||||
meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
|
||||
|
||||
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||
int32_t t_fine = 0;
|
||||
float temperature = this->read_temperature_(&t_fine);
|
||||
if (isnan(temperature)) {
|
||||
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
float pressure = this->read_pressure_(t_fine);
|
||||
float humidity = this->read_humidity_(t_fine);
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
float BME280Component::read_temperature_(int32_t *t_fine) {
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3))
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
// temperature was disabled
|
||||
return NAN;
|
||||
|
||||
const int32_t t1 = this->calibration_.t1;
|
||||
const int32_t t2 = this->calibration_.t2;
|
||||
const int32_t t3 = this->calibration_.t3;
|
||||
|
||||
int32_t var1 = (((adc >> 3) - (t1 << 1)) * t2) >> 11;
|
||||
int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
||||
*t_fine = var1 + var2;
|
||||
|
||||
float temperature = (*t_fine * 5 + 128) >> 8;
|
||||
return temperature / 100.0f;
|
||||
}
|
||||
|
||||
float BME280Component::read_pressure_(int32_t t_fine) {
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3))
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
// pressure was disabled
|
||||
return NAN;
|
||||
const int64_t p1 = this->calibration_.p1;
|
||||
const int64_t p2 = this->calibration_.p2;
|
||||
const int64_t p3 = this->calibration_.p3;
|
||||
const int64_t p4 = this->calibration_.p4;
|
||||
const int64_t p5 = this->calibration_.p5;
|
||||
const int64_t p6 = this->calibration_.p6;
|
||||
const int64_t p7 = this->calibration_.p7;
|
||||
const int64_t p8 = this->calibration_.p8;
|
||||
const int64_t p9 = this->calibration_.p9;
|
||||
|
||||
int64_t var1, var2, p;
|
||||
var1 = int64_t(t_fine) - 128000;
|
||||
var2 = var1 * var1 * p6;
|
||||
var2 = var2 + ((var1 * p5) << 17);
|
||||
var2 = var2 + (p4 << 35);
|
||||
var1 = ((var1 * var1 * p3) >> 8) + ((var1 * p2) << 12);
|
||||
var1 = ((int64_t(1) << 47) + var1) * p1 >> 33;
|
||||
|
||||
if (var1 == 0)
|
||||
return NAN;
|
||||
|
||||
p = 1048576 - adc;
|
||||
p = (((p << 31) - var2) * 3125) / var1;
|
||||
var1 = (p9 * (p >> 13) * (p >> 13)) >> 25;
|
||||
var2 = (p8 * p) >> 19;
|
||||
|
||||
p = ((p + var1 + var2) >> 8) + (p7 << 4);
|
||||
return (p / 256.0f) / 100.0f;
|
||||
}
|
||||
|
||||
float BME280Component::read_humidity_(int32_t t_fine) {
|
||||
uint16_t raw_adc;
|
||||
if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000)
|
||||
return NAN;
|
||||
|
||||
int32_t adc = raw_adc;
|
||||
|
||||
const int32_t h1 = this->calibration_.h1;
|
||||
const int32_t h2 = this->calibration_.h2;
|
||||
const int32_t h3 = this->calibration_.h3;
|
||||
const int32_t h4 = this->calibration_.h4;
|
||||
const int32_t h5 = this->calibration_.h5;
|
||||
const int32_t h6 = this->calibration_.h6;
|
||||
|
||||
int32_t v_x1_u32r = t_fine - 76800;
|
||||
|
||||
v_x1_u32r = ((((adc << 14) - (h4 << 20) - (h5 * v_x1_u32r)) + 16384) >> 15) *
|
||||
(((((((v_x1_u32r * h6) >> 10) * (((v_x1_u32r * h3) >> 11) + 32768)) >> 10) + 2097152) * h2 + 8192) >> 14);
|
||||
|
||||
v_x1_u32r = v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * h1) >> 4);
|
||||
|
||||
v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r;
|
||||
v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r;
|
||||
float h = v_x1_u32r >> 12;
|
||||
|
||||
return h / 1024.0f;
|
||||
}
|
||||
void BME280Component::set_temperature_oversampling(BME280Oversampling temperature_over_sampling) {
|
||||
this->temperature_oversampling_ = temperature_over_sampling;
|
||||
}
|
||||
void BME280Component::set_pressure_oversampling(BME280Oversampling pressure_over_sampling) {
|
||||
this->pressure_oversampling_ = pressure_over_sampling;
|
||||
}
|
||||
void BME280Component::set_humidity_oversampling(BME280Oversampling humidity_over_sampling) {
|
||||
this->humidity_oversampling_ = humidity_over_sampling;
|
||||
}
|
||||
void BME280Component::set_iir_filter(BME280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||
uint8_t BME280Component::read_u8_(uint8_t a_register) {
|
||||
uint8_t data = 0;
|
||||
this->read_byte(a_register, &data);
|
||||
return data;
|
||||
}
|
||||
uint16_t BME280Component::read_u16_le_(uint8_t a_register) {
|
||||
uint16_t data = 0;
|
||||
this->read_byte_16(a_register, &data);
|
||||
return (data >> 8) | (data << 8);
|
||||
}
|
||||
int16_t BME280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
||||
|
||||
} // namespace bme280
|
||||
} // namespace esphome
|
||||
110
esphome/components/bme280/bme280.h
Normal file
110
esphome/components/bme280/bme280.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme280 {
|
||||
|
||||
/// Internal struct storing the calibration values of an BME280.
|
||||
struct BME280CalibrationData {
|
||||
uint16_t t1; // 0x88 - 0x89
|
||||
int16_t t2; // 0x8A - 0x8B
|
||||
int16_t t3; // 0x8C - 0x8D
|
||||
|
||||
uint16_t p1; // 0x8E - 0x8F
|
||||
int16_t p2; // 0x90 - 0x91
|
||||
int16_t p3; // 0x92 - 0x93
|
||||
int16_t p4; // 0x94 - 0x95
|
||||
int16_t p5; // 0x96 - 0x97
|
||||
int16_t p6; // 0x98 - 0x99
|
||||
int16_t p7; // 0x9A - 0x9B
|
||||
int16_t p8; // 0x9C - 0x9D
|
||||
int16_t p9; // 0x9E - 0x9F
|
||||
|
||||
uint8_t h1; // 0xA1
|
||||
int16_t h2; // 0xE1 - 0xE2
|
||||
uint8_t h3; // 0xE3
|
||||
int16_t h4; // 0xE4 - 0xE5[3:0]
|
||||
int16_t h5; // 0xE5[7:4] - 0xE6
|
||||
int8_t h6; // 0xE7
|
||||
};
|
||||
|
||||
/** Enum listing all Oversampling values for the BME280.
|
||||
*
|
||||
* Oversampling basically means measuring a condition multiple times. Higher oversampling
|
||||
* values therefore increase the time required to read sensor values but increase accuracy.
|
||||
*/
|
||||
enum BME280Oversampling {
|
||||
BME280_OVERSAMPLING_NONE = 0b000,
|
||||
BME280_OVERSAMPLING_1X = 0b001,
|
||||
BME280_OVERSAMPLING_2X = 0b010,
|
||||
BME280_OVERSAMPLING_4X = 0b011,
|
||||
BME280_OVERSAMPLING_8X = 0b100,
|
||||
BME280_OVERSAMPLING_16X = 0b101,
|
||||
};
|
||||
|
||||
/** Enum listing all Infinite Impulse Filter values for the BME280.
|
||||
*
|
||||
* Higher values increase accuracy, but decrease response time.
|
||||
*/
|
||||
enum BME280IIRFilter {
|
||||
BME280_IIR_FILTER_OFF = 0b000,
|
||||
BME280_IIR_FILTER_2X = 0b001,
|
||||
BME280_IIR_FILTER_4X = 0b010,
|
||||
BME280_IIR_FILTER_8X = 0b011,
|
||||
BME280_IIR_FILTER_16X = 0b100,
|
||||
};
|
||||
|
||||
/// This class implements support for the BME280 Temperature+Pressure+Humidity i2c sensor.
|
||||
class BME280Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
/// Set the oversampling value for the temperature sensor. Default is 16x.
|
||||
void set_temperature_oversampling(BME280Oversampling temperature_over_sampling);
|
||||
/// Set the oversampling value for the pressure sensor. Default is 16x.
|
||||
void set_pressure_oversampling(BME280Oversampling pressure_over_sampling);
|
||||
/// Set the oversampling value for the humidity sensor. Default is 16x.
|
||||
void set_humidity_oversampling(BME280Oversampling humidity_over_sampling);
|
||||
/// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter.
|
||||
void set_iir_filter(BME280IIRFilter iir_filter);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
/// Read the temperature value and store the calculated ambient temperature in t_fine.
|
||||
float read_temperature_(int32_t *t_fine);
|
||||
/// Read the pressure value in hPa using the provided t_fine value.
|
||||
float read_pressure_(int32_t t_fine);
|
||||
/// Read the humidity value in % using the provided t_fine value.
|
||||
float read_humidity_(int32_t t_fine);
|
||||
uint8_t read_u8_(uint8_t a_register);
|
||||
uint16_t read_u16_le_(uint8_t a_register);
|
||||
int16_t read_s16_le_(uint8_t a_register);
|
||||
|
||||
BME280CalibrationData calibration_;
|
||||
BME280Oversampling temperature_oversampling_{BME280_OVERSAMPLING_16X};
|
||||
BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X};
|
||||
BME280Oversampling humidity_oversampling_{BME280_OVERSAMPLING_16X};
|
||||
BME280IIRFilter iir_filter_{BME280_IIR_FILTER_OFF};
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
WRONG_CHIP_ID,
|
||||
} error_code_{NONE};
|
||||
};
|
||||
|
||||
} // namespace bme280
|
||||
} // namespace esphome
|
||||
76
esphome/components/bme280/sensor.py
Normal file
76
esphome/components/bme280/sensor.py
Normal file
@@ -0,0 +1,76 @@
|
||||
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_IIR_FILTER, CONF_OVERSAMPLING, \
|
||||
CONF_PRESSURE, CONF_TEMPERATURE, ICON_THERMOMETER, \
|
||||
UNIT_CELSIUS, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
bme280_ns = cg.esphome_ns.namespace('bme280')
|
||||
BME280Oversampling = bme280_ns.enum('BME280Oversampling')
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
||||
'1X': BME280Oversampling.BME280_OVERSAMPLING_1X,
|
||||
'2X': BME280Oversampling.BME280_OVERSAMPLING_2X,
|
||||
'4X': BME280Oversampling.BME280_OVERSAMPLING_4X,
|
||||
'8X': BME280Oversampling.BME280_OVERSAMPLING_8X,
|
||||
'16X': BME280Oversampling.BME280_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BME280IIRFilter = bme280_ns.enum('BME280IIRFilter')
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
||||
'2X': BME280IIRFilter.BME280_IIR_FILTER_2X,
|
||||
'4X': BME280IIRFilter.BME280_IIR_FILTER_4X,
|
||||
'8X': BME280IIRFilter.BME280_IIR_FILTER_8X,
|
||||
'16X': BME280IIRFilter.BME280_IIR_FILTER_16X,
|
||||
}
|
||||
|
||||
BME280Component = bme280_ns.class_('BME280Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(BME280Component),
|
||||
cv.Optional(CONF_TEMPERATURE):
|
||||
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_PRESSURE):
|
||||
sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_HUMIDITY):
|
||||
sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77))
|
||||
|
||||
|
||||
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:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
conf = config[CONF_HUMIDITY]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
|
||||
0
esphome/components/bme680/__init__.py
Normal file
0
esphome/components/bme680/__init__.py
Normal file
483
esphome/components/bme680/bme680.cpp
Normal file
483
esphome/components/bme680/bme680.cpp
Normal file
@@ -0,0 +1,483 @@
|
||||
#include "bme680.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680 {
|
||||
|
||||
static const char *TAG = "bme680.sensor";
|
||||
|
||||
static const uint8_t BME680_REGISTER_COEFF1 = 0x89;
|
||||
static const uint8_t BME680_REGISTER_COEFF2 = 0xE1;
|
||||
|
||||
static const uint8_t BME680_REGISTER_CONFIG = 0x75;
|
||||
static const uint8_t BME680_REGISTER_CONTROL_MEAS = 0x74;
|
||||
static const uint8_t BME680_REGISTER_CONTROL_HUMIDITY = 0x72;
|
||||
static const uint8_t BME680_REGISTER_CONTROL_GAS1 = 0x71;
|
||||
static const uint8_t BME680_REGISTER_CONTROL_GAS0 = 0x70;
|
||||
static const uint8_t BME680_REGISTER_HEATER_HEAT0 = 0x5A;
|
||||
static const uint8_t BME680_REGISTER_HEATER_WAIT0 = 0x64;
|
||||
|
||||
static const uint8_t BME680_REGISTER_CHIPID = 0xD0;
|
||||
|
||||
static const uint8_t BME680_REGISTER_FIELD0 = 0x1D;
|
||||
|
||||
const float BME680_GAS_LOOKUP_TABLE_1[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -0.8,
|
||||
0.0, 0.0, -0.2, -0.5, 0.0, -1.0, 0.0, 0.0};
|
||||
|
||||
const float BME680_GAS_LOOKUP_TABLE_2[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8,
|
||||
-0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
|
||||
static const char *oversampling_to_str(BME680Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case BME680_OVERSAMPLING_NONE:
|
||||
return "None";
|
||||
case BME680_OVERSAMPLING_1X:
|
||||
return "1x";
|
||||
case BME680_OVERSAMPLING_2X:
|
||||
return "2x";
|
||||
case BME680_OVERSAMPLING_4X:
|
||||
return "4x";
|
||||
case BME680_OVERSAMPLING_8X:
|
||||
return "8x";
|
||||
case BME680_OVERSAMPLING_16X:
|
||||
return "16x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *iir_filter_to_str(BME680IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case BME680_IIR_FILTER_OFF:
|
||||
return "OFF";
|
||||
case BME680_IIR_FILTER_1X:
|
||||
return "1x";
|
||||
case BME680_IIR_FILTER_3X:
|
||||
return "3x";
|
||||
case BME680_IIR_FILTER_7X:
|
||||
return "7x";
|
||||
case BME680_IIR_FILTER_15X:
|
||||
return "15x";
|
||||
case BME680_IIR_FILTER_31X:
|
||||
return "31x";
|
||||
case BME680_IIR_FILTER_63X:
|
||||
return "63x";
|
||||
case BME680_IIR_FILTER_127X:
|
||||
return "127x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void BME680Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME680...");
|
||||
uint8_t chip_id;
|
||||
if (!this->read_byte(BME680_REGISTER_CHIPID, &chip_id) || chip_id != 0x61) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read calibration
|
||||
uint8_t cal1[25];
|
||||
if (!this->read_bytes(BME680_REGISTER_COEFF1, cal1, 25)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint8_t cal2[16];
|
||||
if (!this->read_bytes(BME680_REGISTER_COEFF2, cal2, 16)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->calibration_.t1 = cal2[9] << 8 | cal2[8];
|
||||
this->calibration_.t2 = cal1[2] << 8 | cal1[1];
|
||||
this->calibration_.t3 = cal1[3];
|
||||
|
||||
this->calibration_.h1 = cal2[2] << 4 | (cal2[1] & 0x0F);
|
||||
this->calibration_.h2 = cal2[0] << 4 | cal2[1];
|
||||
this->calibration_.h3 = cal2[3];
|
||||
this->calibration_.h4 = cal2[4];
|
||||
this->calibration_.h5 = cal2[5];
|
||||
this->calibration_.h6 = cal2[6];
|
||||
this->calibration_.h7 = cal2[7];
|
||||
|
||||
this->calibration_.p1 = cal1[6] << 8 | cal1[5];
|
||||
this->calibration_.p2 = cal1[8] << 8 | cal1[7];
|
||||
this->calibration_.p3 = cal1[9];
|
||||
this->calibration_.p4 = cal1[12] << 8 | cal1[11];
|
||||
this->calibration_.p5 = cal1[14] << 8 | cal1[13];
|
||||
this->calibration_.p6 = cal1[16];
|
||||
this->calibration_.p7 = cal1[15];
|
||||
this->calibration_.p8 = cal1[20] << 8 | cal1[19];
|
||||
this->calibration_.p9 = cal1[22] << 8 | cal1[21];
|
||||
this->calibration_.p10 = cal1[23];
|
||||
|
||||
this->calibration_.gh1 = cal2[14];
|
||||
this->calibration_.gh2 = cal2[12] << 8 | cal2[13];
|
||||
this->calibration_.gh3 = cal2[15];
|
||||
|
||||
if (!this->read_byte(0x02, &this->calibration_.res_heat_range)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->read_byte(0x00, &this->calibration_.res_heat_val)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->read_byte(0x04, &this->calibration_.range_sw_err)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->calibration_.ambient_temperature = 25; // prime ambient temperature
|
||||
|
||||
// Config register
|
||||
uint8_t config_register;
|
||||
if (!this->read_byte(BME680_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b00011100;
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BME680_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Humidity control register
|
||||
uint8_t hum_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_HUMIDITY, &hum_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
hum_control &= ~0b00000111;
|
||||
hum_control |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_HUMIDITY, hum_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Gas 1 control register
|
||||
uint8_t gas1_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_GAS1, &gas1_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
gas1_control &= ~0b00011111;
|
||||
gas1_control |= 1 << 4;
|
||||
gas1_control |= 0; // profile 0
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS1, gas1_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
const bool heat_off = this->heater_temperature_ == 0 || this->heater_duration_ == 0;
|
||||
|
||||
// Gas 0 control register
|
||||
uint8_t gas0_control;
|
||||
if (!this->read_byte(BME680_REGISTER_CONTROL_GAS0, &gas0_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
gas0_control &= ~0b00001000;
|
||||
gas0_control |= heat_off ? 0b100 : 0b000;
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!heat_off) {
|
||||
// Gas Heater Temperature
|
||||
uint8_t temperature = this->calc_heater_resistance_(this->heater_temperature_);
|
||||
if (!this->write_byte(BME680_REGISTER_HEATER_HEAT0, temperature)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Gas Heater Duration
|
||||
uint8_t duration = this->calc_heater_duration_(this->heater_duration_);
|
||||
|
||||
if (!this->write_byte(BME680_REGISTER_HEATER_WAIT0, duration)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BME680Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BME680:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with BME680 failed!");
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->temperature_oversampling_));
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->humidity_oversampling_));
|
||||
LOG_SENSOR(" ", "Gas Resistance", this->gas_resistance_sensor_);
|
||||
if (this->heater_duration_ == 0 || this->heater_temperature_ == 0) {
|
||||
ESP_LOGCONFIG(TAG, " Heater OFF");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Heater temperature=%u°C duration=%ums", this->heater_temperature_, this->heater_duration_);
|
||||
}
|
||||
}
|
||||
|
||||
float BME680Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void BME680Component::update() {
|
||||
uint8_t meas_control = 0; // No need to fetch, we're setting all fields
|
||||
meas_control |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_control |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_control |= 0b01; // forced mode
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_MEAS, meas_control)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout("data", this->calc_meas_duration_(), [this]() { this->read_data_(); });
|
||||
}
|
||||
|
||||
uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) {
|
||||
if (temperature < 200)
|
||||
temperature = 200;
|
||||
if (temperature > 400)
|
||||
temperature = 400;
|
||||
|
||||
const uint8_t ambient_temperature = this->calibration_.ambient_temperature;
|
||||
const int8_t gh1 = this->calibration_.gh1;
|
||||
const int16_t gh2 = this->calibration_.gh2;
|
||||
const int8_t gh3 = this->calibration_.gh3;
|
||||
const uint8_t res_heat_range = this->calibration_.res_heat_range;
|
||||
const uint8_t res_heat_val = this->calibration_.res_heat_val;
|
||||
|
||||
uint8_t heatr_res;
|
||||
int32_t var1;
|
||||
int32_t var2;
|
||||
int32_t var3;
|
||||
int32_t var4;
|
||||
int32_t var5;
|
||||
int32_t heatr_res_x100;
|
||||
|
||||
var1 = (((int32_t) ambient_temperature * gh3) / 1000) * 256;
|
||||
var2 = (gh1 + 784) * (((((gh2 + 154009) * temperature * 5) / 100) + 3276800) / 10);
|
||||
var3 = var1 + (var2 / 2);
|
||||
var4 = (var3 / (res_heat_range + 4));
|
||||
var5 = (131 * res_heat_val) + 65536;
|
||||
heatr_res_x100 = (int32_t)(((var4 / var5) - 250) * 34);
|
||||
heatr_res = (uint8_t)((heatr_res_x100 + 50) / 100);
|
||||
|
||||
return heatr_res;
|
||||
}
|
||||
uint8_t BME680Component::calc_heater_duration_(uint16_t duration) {
|
||||
uint8_t factor = 0;
|
||||
uint8_t duration_value;
|
||||
|
||||
if (duration >= 0xfc0) {
|
||||
duration_value = 0xff;
|
||||
} else {
|
||||
while (duration > 0x3F) {
|
||||
duration /= 4;
|
||||
factor += 1;
|
||||
}
|
||||
duration_value = duration + (factor * 64);
|
||||
}
|
||||
|
||||
return duration_value;
|
||||
}
|
||||
void BME680Component::read_data_() {
|
||||
uint8_t data[15];
|
||||
if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4);
|
||||
uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4);
|
||||
uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]);
|
||||
uint16_t raw_gas = (uint16_t(data[13]) << 2) | (uint16_t(14) >> 6);
|
||||
uint8_t gas_range = data[14] & 0x0F;
|
||||
|
||||
float temperature = this->calc_temperature_(raw_temperature);
|
||||
float pressure = this->calc_pressure_(raw_pressure);
|
||||
float humidity = this->calc_humidity_(raw_humidity);
|
||||
float gas_resistance = NAN;
|
||||
if (data[14] & 0x20) {
|
||||
gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure,
|
||||
humidity, gas_resistance);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
if (this->gas_resistance_sensor_ != nullptr)
|
||||
this->gas_resistance_sensor_->publish_state(gas_resistance);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
float BME680Component::calc_temperature_(uint32_t raw_temperature) {
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
float calc_temp = 0;
|
||||
float temp_adc = raw_temperature;
|
||||
|
||||
const float t1 = this->calibration_.t1;
|
||||
const float t2 = this->calibration_.t2;
|
||||
const float t3 = this->calibration_.t3;
|
||||
|
||||
/* calculate var1 data */
|
||||
var1 = ((temp_adc / 16384.0f) - (t1 / 1024.0f)) * t2;
|
||||
|
||||
/* calculate var2 data */
|
||||
var3 = (temp_adc / 131072.0f) - (t1 / 8192.0f);
|
||||
var2 = var3 * var3 * t3 * 16.0f;
|
||||
|
||||
/* t_fine value*/
|
||||
this->calibration_.tfine = (var1 + var2);
|
||||
|
||||
/* compensated temperature data*/
|
||||
calc_temp = ((this->calibration_.tfine) / 5120.0f);
|
||||
|
||||
return calc_temp;
|
||||
}
|
||||
float BME680Component::calc_pressure_(uint32_t raw_pressure) {
|
||||
const float tfine = this->calibration_.tfine;
|
||||
const float p1 = this->calibration_.p1;
|
||||
const float p2 = this->calibration_.p2;
|
||||
const float p3 = this->calibration_.p3;
|
||||
const float p4 = this->calibration_.p4;
|
||||
const float p5 = this->calibration_.p5;
|
||||
const float p6 = this->calibration_.p6;
|
||||
const float p7 = this->calibration_.p7;
|
||||
const float p8 = this->calibration_.p8;
|
||||
const float p9 = this->calibration_.p9;
|
||||
const float p10 = this->calibration_.p10;
|
||||
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
float var4 = 0;
|
||||
float calc_pres = 0;
|
||||
|
||||
var1 = (tfine / 2.0f) - 64000.0f;
|
||||
var2 = var1 * var1 * (p6 / 131072.0f);
|
||||
var2 = var2 + var1 * p5 * 2.0f;
|
||||
var2 = (var2 / 4.0f) + (p4 * 65536.0f);
|
||||
var1 = (((p3 * var1 * var1) / 16384.0f) + (p2 * var1)) / 524288.0f;
|
||||
var1 = (1.0f + (var1 / 32768.0f)) * p1;
|
||||
calc_pres = 1048576.0f - float(raw_pressure);
|
||||
|
||||
/* Avoid exception caused by division by zero */
|
||||
if (int(var1) != 0) {
|
||||
calc_pres = ((calc_pres - (var2 / 4096.0f)) * 6250.0f) / var1;
|
||||
var1 = (p9 * calc_pres * calc_pres) / 2147483648.0f;
|
||||
var2 = calc_pres * (p8 / 32768.0f);
|
||||
var4 = calc_pres / 256.0f;
|
||||
var3 = var4 * var4 * var4 * (p10 / 131072.0f);
|
||||
calc_pres = calc_pres + (var1 + var2 + var3 + (p7 * 128.0f)) / 16.0f;
|
||||
} else {
|
||||
calc_pres = 0;
|
||||
}
|
||||
|
||||
return calc_pres / 100.0f;
|
||||
}
|
||||
|
||||
float BME680Component::calc_humidity_(uint16_t raw_humidity) {
|
||||
const float tfine = this->calibration_.tfine;
|
||||
const float h1 = this->calibration_.h1;
|
||||
const float h2 = this->calibration_.h2;
|
||||
const float h3 = this->calibration_.h3;
|
||||
const float h4 = this->calibration_.h4;
|
||||
const float h5 = this->calibration_.h5;
|
||||
const float h6 = this->calibration_.h6;
|
||||
const float h7 = this->calibration_.h7;
|
||||
|
||||
float calc_hum = 0;
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
float var4 = 0;
|
||||
float temp_comp;
|
||||
|
||||
/* compensated temperature data*/
|
||||
temp_comp = tfine / 5120.0f;
|
||||
|
||||
var1 = float(raw_humidity) - (h1 * 16.0f + ((h3 / 2.0f) * temp_comp));
|
||||
var2 = var1 *
|
||||
(((h2 / 262144.0f) * (1.0f + ((h4 / 16384.0f) * temp_comp) + ((h5 / 1048576.0f) * temp_comp * temp_comp))));
|
||||
var3 = h6 / 16384.0f;
|
||||
var4 = h7 / 2097152.0f;
|
||||
|
||||
calc_hum = var2 + (var3 + var4 * temp_comp) * var2 * var2;
|
||||
|
||||
if (calc_hum > 100.0f)
|
||||
calc_hum = 100.0f;
|
||||
else if (calc_hum < 0.0f)
|
||||
calc_hum = 0.0f;
|
||||
|
||||
return calc_hum;
|
||||
}
|
||||
uint32_t BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) {
|
||||
float calc_gas_res;
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
const float range_sw_err = this->calibration_.range_sw_err;
|
||||
|
||||
var1 = 1340.0f + (5.0f * range_sw_err);
|
||||
var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f);
|
||||
var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f);
|
||||
|
||||
calc_gas_res = 1.0f / (var3 * 0.000000125f * float(1 << range) * (((float(raw_gas) - 512.0f) / var2) + 1.0f));
|
||||
|
||||
return static_cast<uint32_t>(calc_gas_res);
|
||||
}
|
||||
uint32_t BME680Component::calc_meas_duration_() {
|
||||
uint32_t tph_dur; // Calculate in us
|
||||
uint32_t meas_cycles;
|
||||
const uint8_t os_to_meas_cycles[6] = {0, 1, 2, 4, 8, 16};
|
||||
|
||||
meas_cycles = os_to_meas_cycles[this->temperature_oversampling_];
|
||||
meas_cycles += os_to_meas_cycles[this->pressure_oversampling_];
|
||||
meas_cycles += os_to_meas_cycles[this->humidity_oversampling_];
|
||||
|
||||
/* TPH measurement duration */
|
||||
tph_dur = meas_cycles * 1963u;
|
||||
tph_dur += 477 * 4; // TPH switching duration
|
||||
tph_dur += 477 * 5; // Gas measurement duration
|
||||
tph_dur += 500; // Get it to the closest whole number.
|
||||
tph_dur /= 1000; // Convert to ms
|
||||
|
||||
tph_dur += 1; // Wake up duration of 1ms
|
||||
|
||||
/* The remaining time should be used for heating */
|
||||
tph_dur += this->heater_duration_;
|
||||
|
||||
return tph_dur;
|
||||
}
|
||||
void BME680Component::set_temperature_oversampling(BME680Oversampling temperature_oversampling) {
|
||||
this->temperature_oversampling_ = temperature_oversampling;
|
||||
}
|
||||
void BME680Component::set_pressure_oversampling(BME680Oversampling pressure_oversampling) {
|
||||
this->pressure_oversampling_ = pressure_oversampling;
|
||||
}
|
||||
void BME680Component::set_humidity_oversampling(BME680Oversampling humidity_oversampling) {
|
||||
this->humidity_oversampling_ = humidity_oversampling;
|
||||
}
|
||||
void BME680Component::set_iir_filter(BME680IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||
void BME680Component::set_heater(uint16_t heater_temperature, uint16_t heater_duration) {
|
||||
this->heater_temperature_ = heater_temperature;
|
||||
this->heater_duration_ = heater_duration;
|
||||
}
|
||||
|
||||
} // namespace bme680
|
||||
} // namespace esphome
|
||||
139
esphome/components/bme680/bme680.h
Normal file
139
esphome/components/bme680/bme680.h
Normal file
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680 {
|
||||
|
||||
/// Enum listing all IIR Filter options for the BME680.
|
||||
enum BME680IIRFilter {
|
||||
BME680_IIR_FILTER_OFF = 0b000,
|
||||
BME680_IIR_FILTER_1X = 0b001,
|
||||
BME680_IIR_FILTER_3X = 0b010,
|
||||
BME680_IIR_FILTER_7X = 0b011,
|
||||
BME680_IIR_FILTER_15X = 0b100,
|
||||
BME680_IIR_FILTER_31X = 0b101,
|
||||
BME680_IIR_FILTER_63X = 0b110,
|
||||
BME680_IIR_FILTER_127X = 0b111,
|
||||
};
|
||||
|
||||
/// Enum listing all oversampling options for the BME680.
|
||||
enum BME680Oversampling {
|
||||
BME680_OVERSAMPLING_NONE = 0b000,
|
||||
BME680_OVERSAMPLING_1X = 0b001,
|
||||
BME680_OVERSAMPLING_2X = 0b010,
|
||||
BME680_OVERSAMPLING_4X = 0b011,
|
||||
BME680_OVERSAMPLING_8X = 0b100,
|
||||
BME680_OVERSAMPLING_16X = 0b101,
|
||||
};
|
||||
|
||||
/// Struct for storing calibration data for the BME680.
|
||||
struct BME680CalibrationData {
|
||||
uint16_t t1;
|
||||
uint16_t t2;
|
||||
uint8_t t3;
|
||||
|
||||
uint16_t p1;
|
||||
int16_t p2;
|
||||
int8_t p3;
|
||||
int16_t p4;
|
||||
int16_t p5;
|
||||
int8_t p6;
|
||||
int8_t p7;
|
||||
int16_t p8;
|
||||
int16_t p9;
|
||||
int8_t p10;
|
||||
|
||||
uint16_t h1;
|
||||
uint16_t h2;
|
||||
int8_t h3;
|
||||
int8_t h4;
|
||||
int8_t h5;
|
||||
uint8_t h6;
|
||||
int8_t h7;
|
||||
|
||||
int8_t gh1;
|
||||
int16_t gh2;
|
||||
int8_t gh3;
|
||||
|
||||
uint8_t res_heat_range;
|
||||
uint8_t res_heat_val;
|
||||
uint8_t range_sw_err;
|
||||
|
||||
float tfine;
|
||||
uint8_t ambient_temperature;
|
||||
};
|
||||
|
||||
class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
/// Set the temperature oversampling value. Defaults to 16X.
|
||||
void set_temperature_oversampling(BME680Oversampling temperature_oversampling);
|
||||
/// Set the pressure oversampling value. Defaults to 16X.
|
||||
void set_pressure_oversampling(BME680Oversampling pressure_oversampling);
|
||||
/// Set the humidity oversampling value. Defaults to 16X.
|
||||
void set_humidity_oversampling(BME680Oversampling humidity_oversampling);
|
||||
/// Set the IIR Filter value. Defaults to no IIR Filter.
|
||||
void set_iir_filter(BME680IIRFilter iir_filter);
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
void set_gas_resistance_sensor(sensor::Sensor *gas_resistance_sensor) {
|
||||
gas_resistance_sensor_ = gas_resistance_sensor;
|
||||
}
|
||||
|
||||
/** Set how the internal heater should operate.
|
||||
*
|
||||
* By default, the heater is off. If you want to to have more reliable
|
||||
* humidity and Gas Resistance values, you can however setup the heater
|
||||
* with this method.
|
||||
*
|
||||
* @param heater_temperature The temperature of the heater in °C.
|
||||
* @param heater_duration The duration in ms that the heater should turn on for when measuring.
|
||||
*/
|
||||
void set_heater(uint16_t heater_temperature, uint16_t heater_duration);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
/// Calculate the heater resistance value to send to the BME680 register.
|
||||
uint8_t calc_heater_resistance_(uint16_t temperature);
|
||||
/// Calculate the heater duration value to send to the BME680 register.
|
||||
uint8_t calc_heater_duration_(uint16_t duration);
|
||||
/// Read data from the BME680 and publish results.
|
||||
void read_data_();
|
||||
|
||||
/// Calculate the temperature in °C using the provided raw ADC value.
|
||||
float calc_temperature_(uint32_t raw_temperature);
|
||||
/// Calculate the pressure in hPa using the provided raw ADC value.
|
||||
float calc_pressure_(uint32_t raw_pressure);
|
||||
/// Calculate the relative humidity in % using the provided raw ADC value.
|
||||
float calc_humidity_(uint16_t raw_humidity);
|
||||
/// Calculate the gas resistance in Ω using the provided raw ADC value.
|
||||
uint32_t calc_gas_resistance_(uint16_t raw_gas, uint8_t range);
|
||||
/// Calculate how long the sensor will take until we can retrieve data.
|
||||
uint32_t calc_meas_duration_();
|
||||
|
||||
BME680CalibrationData calibration_;
|
||||
BME680Oversampling temperature_oversampling_{BME680_OVERSAMPLING_16X};
|
||||
BME680Oversampling pressure_oversampling_{BME680_OVERSAMPLING_16X};
|
||||
BME680Oversampling humidity_oversampling_{BME680_OVERSAMPLING_16X};
|
||||
BME680IIRFilter iir_filter_{BME680_IIR_FILTER_OFF};
|
||||
uint16_t heater_temperature_{320};
|
||||
uint16_t heater_duration_{150};
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *gas_resistance_sensor_;
|
||||
};
|
||||
|
||||
} // namespace bme680
|
||||
} // namespace esphome
|
||||
100
esphome/components/bme680/sensor.py
Normal file
100
esphome/components/bme680/sensor.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import core
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_DURATION, CONF_GAS_RESISTANCE, CONF_HEATER, \
|
||||
CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, CONF_PRESSURE, \
|
||||
CONF_TEMPERATURE, UNIT_OHM, ICON_GAS_CYLINDER, UNIT_CELSIUS, \
|
||||
ICON_THERMOMETER, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
bme680_ns = cg.esphome_ns.namespace('bme680')
|
||||
BME680Oversampling = bme680_ns.enum('BME680Oversampling')
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BME680Oversampling.BME680_OVERSAMPLING_NONE,
|
||||
'1X': BME680Oversampling.BME680_OVERSAMPLING_1X,
|
||||
'2X': BME680Oversampling.BME680_OVERSAMPLING_2X,
|
||||
'4X': BME680Oversampling.BME680_OVERSAMPLING_4X,
|
||||
'8X': BME680Oversampling.BME680_OVERSAMPLING_8X,
|
||||
'16X': BME680Oversampling.BME680_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BME680IIRFilter = bme680_ns.enum('BME680IIRFilter')
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BME680IIRFilter.BME680_IIR_FILTER_OFF,
|
||||
'1X': BME680IIRFilter.BME680_IIR_FILTER_1X,
|
||||
'3X': BME680IIRFilter.BME680_IIR_FILTER_3X,
|
||||
'7X': BME680IIRFilter.BME680_IIR_FILTER_7X,
|
||||
'15X': BME680IIRFilter.BME680_IIR_FILTER_15X,
|
||||
'31X': BME680IIRFilter.BME680_IIR_FILTER_31X,
|
||||
'63X': BME680IIRFilter.BME680_IIR_FILTER_63X,
|
||||
'127X': BME680IIRFilter.BME680_IIR_FILTER_127X,
|
||||
}
|
||||
|
||||
BME680Component = bme680_ns.class_('BME680Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(BME680Component),
|
||||
cv.Optional(CONF_TEMPERATURE):
|
||||
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_PRESSURE):
|
||||
sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_HUMIDITY):
|
||||
sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'):
|
||||
cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_GAS_RESISTANCE):
|
||||
sensor.sensor_schema(UNIT_OHM, ICON_GAS_CYLINDER, 1),
|
||||
cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True),
|
||||
cv.Optional(CONF_HEATER): cv.Any(None, cv.All(cv.Schema({
|
||||
cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range(min=200, max=400),
|
||||
cv.Optional(CONF_DURATION, default='150ms'): cv.All(
|
||||
cv.positive_time_period_milliseconds, cv.Range(max=core.TimePeriod(milliseconds=4032)))
|
||||
}), cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION))),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x76))
|
||||
|
||||
|
||||
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:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
conf = config[CONF_HUMIDITY]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_GAS_RESISTANCE in config:
|
||||
conf = config[CONF_GAS_RESISTANCE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_gas_resistance_sensor(sens))
|
||||
|
||||
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))
|
||||
|
||||
if CONF_HEATER in config:
|
||||
conf = config[CONF_HEATER]
|
||||
if not conf:
|
||||
cg.add(var.set_heater(0, 0))
|
||||
else:
|
||||
cg.add(var.set_heater(conf[CONF_TEMPERATURE], conf[CONF_DURATION]))
|
||||
0
esphome/components/bmp085/__init__.py
Normal file
0
esphome/components/bmp085/__init__.py
Normal file
138
esphome/components/bmp085/bmp085.cpp
Normal file
138
esphome/components/bmp085/bmp085.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "bmp085.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp085 {
|
||||
|
||||
static const char *TAG = "bmp085.sensor";
|
||||
|
||||
static const uint8_t BMP085_ADDRESS = 0x77;
|
||||
static const uint8_t BMP085_REGISTER_AC1_H = 0xAA;
|
||||
static const uint8_t BMP085_REGISTER_CONTROL = 0xF4;
|
||||
static const uint8_t BMP085_REGISTER_DATA_MSB = 0xF6;
|
||||
static const uint8_t BMP085_CONTROL_MODE_TEMPERATURE = 0x2E;
|
||||
static const uint8_t BMP085_CONTROL_MODE_PRESSURE_3 = 0xF4;
|
||||
|
||||
void BMP085Component::update() {
|
||||
if (!this->set_mode_(BMP085_CONTROL_MODE_TEMPERATURE))
|
||||
return;
|
||||
|
||||
this->set_timeout("temperature", 5, [this]() { this->read_temperature_(); });
|
||||
}
|
||||
void BMP085Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BMP085...");
|
||||
uint8_t data[22];
|
||||
if (!this->read_bytes(BMP085_REGISTER_AC1_H, data, 22)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load calibration
|
||||
this->calibration_.ac1 = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
||||
this->calibration_.ac2 = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
|
||||
this->calibration_.ac3 = ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
|
||||
this->calibration_.ac4 = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
|
||||
this->calibration_.ac5 = ((data[8] & 0xFF) << 8) | (data[9] & 0xFF);
|
||||
this->calibration_.ac6 = ((data[10] & 0xFF) << 8) | (data[11] & 0xFF);
|
||||
this->calibration_.b1 = ((data[12] & 0xFF) << 8) | (data[13] & 0xFF);
|
||||
this->calibration_.b2 = ((data[14] & 0xFF) << 8) | (data[15] & 0xFF);
|
||||
this->calibration_.mb = ((data[16] & 0xFF) << 8) | (data[17] & 0xFF);
|
||||
this->calibration_.mc = ((data[18] & 0xFF) << 8) | (data[19] & 0xFF);
|
||||
this->calibration_.md = ((data[20] & 0xFF) << 8) | (data[21] & 0xFF);
|
||||
}
|
||||
void BMP085Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BMP085:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Connection with BMP085 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_);
|
||||
}
|
||||
|
||||
void BMP085Component::read_temperature_() {
|
||||
uint8_t buffer[2];
|
||||
// 0xF6
|
||||
if (!this->read_bytes(BMP085_REGISTER_DATA_MSB, buffer, 2)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t ut = ((buffer[0] & 0xFF) << 8) | (buffer[1] & 0xFF);
|
||||
if (ut == 0) {
|
||||
ESP_LOGW(TAG, "Invalid temperature!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
double c5 = (pow(2, -15) / 160) * this->calibration_.ac5;
|
||||
double c6 = this->calibration_.ac6;
|
||||
double mc = (2048.0 / 25600.0) * this->calibration_.mc;
|
||||
double md = this->calibration_.md / 160.0;
|
||||
|
||||
double a = c5 * (ut - c6);
|
||||
float temp = a + (mc / (a + md));
|
||||
|
||||
this->calibration_.temp = temp;
|
||||
ESP_LOGD(TAG, "Got Temperature=%.1f °C", temp);
|
||||
if (this->temperature_ != nullptr)
|
||||
this->temperature_->publish_state(temp);
|
||||
this->status_clear_warning();
|
||||
|
||||
if (!this->set_mode_(BMP085_CONTROL_MODE_PRESSURE_3)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout("pressure", 26, [this]() { this->read_pressure_(); });
|
||||
}
|
||||
void BMP085Component::read_pressure_() {
|
||||
uint8_t buffer[3];
|
||||
if (!this->read_bytes(BMP085_REGISTER_DATA_MSB, buffer, 3)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t value = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1]) << 8) | uint32_t(buffer[0]);
|
||||
if ((value >> 5) == 0) {
|
||||
ESP_LOGW(TAG, "Invalid pressure!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
double c3 = 160.0 * pow(2.0, -15.0) * this->calibration_.ac3;
|
||||
double c4 = pow(10.0, -3) * pow(2.0, -15.0) * this->calibration_.ac4;
|
||||
double b1 = pow(160.0, 2.0) * pow(2.0, -30.0) * this->calibration_.b1;
|
||||
double x0 = this->calibration_.ac1;
|
||||
double x1 = 160.0 * pow(2.0, -13.0) * this->calibration_.ac2;
|
||||
double x2 = pow(160.0, 2.0) * pow(2.0, -25.0) * this->calibration_.b2;
|
||||
double y0 = c4 * pow(2.0, 15.0);
|
||||
double y1 = c4 * c3;
|
||||
double y2 = c4 * b1;
|
||||
double p0 = (3791.0 - 8.0) / 1600.0;
|
||||
double p1 = 1.0 - 7357.0 * pow(2, -20);
|
||||
double p2 = 3038.0 * 100.0 * pow(2, -36);
|
||||
|
||||
double p = value / 256.0;
|
||||
double s = this->calibration_.temp - 25.0;
|
||||
double x = (x2 * s * s) + (x1 * s) + x0;
|
||||
double y = (y2 * s * s) + (y1 * s) + y0;
|
||||
double z = (p - x) / y;
|
||||
float pressure = (p2 * z * z) + (p1 * z) + p0;
|
||||
|
||||
ESP_LOGD(TAG, "Got Pressure=%.1f hPa", pressure);
|
||||
|
||||
if (this->pressure_ != nullptr)
|
||||
this->pressure_->publish_state(pressure);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
bool BMP085Component::set_mode_(uint8_t mode) {
|
||||
ESP_LOGV(TAG, "Setting mode to 0x%02X...", mode);
|
||||
return this->write_byte(BMP085_REGISTER_CONTROL, mode);
|
||||
}
|
||||
float BMP085Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace bmp085
|
||||
} // namespace esphome
|
||||
45
esphome/components/bmp085/bmp085.h
Normal file
45
esphome/components/bmp085/bmp085.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp085 {
|
||||
|
||||
class BMP085Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
void set_pressure(sensor::Sensor *pressure) { pressure_ = pressure; }
|
||||
|
||||
/// Schedule temperature+pressure readings.
|
||||
void update() override;
|
||||
/// Setup the sensor and test for a connection.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
struct CalibrationData {
|
||||
int16_t ac1, ac2, ac3;
|
||||
uint16_t ac4, ac5, ac6;
|
||||
int16_t b1, b2;
|
||||
int16_t mb, mc, md;
|
||||
float temp;
|
||||
};
|
||||
|
||||
/// Internal method to read the temperature from the component after it has been scheduled.
|
||||
void read_temperature_();
|
||||
/// Internal method to read the pressure from the component after it has been scheduled.
|
||||
void read_pressure_();
|
||||
|
||||
bool set_mode_(uint8_t mode);
|
||||
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *pressure_{nullptr};
|
||||
CalibrationData calibration_;
|
||||
};
|
||||
|
||||
} // namespace bmp085
|
||||
} // namespace esphome
|
||||
32
esphome/components/bmp085/sensor.py
Normal file
32
esphome/components/bmp085/sensor.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
bmp085_ns = cg.esphome_ns.namespace('bmp085')
|
||||
BMP085Component = bmp085_ns.class_('BMP085Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(BMP085Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77))
|
||||
|
||||
|
||||
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:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure(sens))
|
||||
0
esphome/components/bmp280/__init__.py
Normal file
0
esphome/components/bmp280/__init__.py
Normal file
238
esphome/components/bmp280/bmp280.cpp
Normal file
238
esphome/components/bmp280/bmp280.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
#include "bmp280.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp280 {
|
||||
|
||||
static const char *TAG = "bmp280.sensor";
|
||||
|
||||
static const uint8_t BMP280_REGISTER_STATUS = 0xF3;
|
||||
static const uint8_t BMP280_REGISTER_CONTROL = 0xF4;
|
||||
static const uint8_t BMP280_REGISTER_CONFIG = 0xF5;
|
||||
static const uint8_t BMP280_REGISTER_PRESSUREDATA = 0xF7;
|
||||
static const uint8_t BMP280_REGISTER_TEMPDATA = 0xFA;
|
||||
|
||||
static const uint8_t BMP280_MODE_FORCED = 0b01;
|
||||
|
||||
inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); }
|
||||
|
||||
static const char *oversampling_to_str(BMP280Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case BMP280_OVERSAMPLING_NONE:
|
||||
return "None";
|
||||
case BMP280_OVERSAMPLING_1X:
|
||||
return "1x";
|
||||
case BMP280_OVERSAMPLING_2X:
|
||||
return "2x";
|
||||
case BMP280_OVERSAMPLING_4X:
|
||||
return "4x";
|
||||
case BMP280_OVERSAMPLING_8X:
|
||||
return "8x";
|
||||
case BMP280_OVERSAMPLING_16X:
|
||||
return "16x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *iir_filter_to_str(BMP280IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case BMP280_IIR_FILTER_OFF:
|
||||
return "OFF";
|
||||
case BMP280_IIR_FILTER_2X:
|
||||
return "2x";
|
||||
case BMP280_IIR_FILTER_4X:
|
||||
return "4x";
|
||||
case BMP280_IIR_FILTER_8X:
|
||||
return "8x";
|
||||
case BMP280_IIR_FILTER_16X:
|
||||
return "16x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void BMP280Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BMP280...");
|
||||
uint8_t chip_id = 0;
|
||||
if (!this->read_byte(0xD0, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x58) {
|
||||
this->error_code_ = WRONG_CHIP_ID;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read calibration
|
||||
this->calibration_.t1 = this->read_u16_le_(0x88);
|
||||
this->calibration_.t2 = this->read_s16_le_(0x8A);
|
||||
this->calibration_.t3 = this->read_s16_le_(0x8C);
|
||||
|
||||
this->calibration_.p1 = this->read_u16_le_(0x8E);
|
||||
this->calibration_.p2 = this->read_s16_le_(0x90);
|
||||
this->calibration_.p3 = this->read_s16_le_(0x92);
|
||||
this->calibration_.p4 = this->read_s16_le_(0x94);
|
||||
this->calibration_.p5 = this->read_s16_le_(0x96);
|
||||
this->calibration_.p6 = this->read_s16_le_(0x98);
|
||||
this->calibration_.p7 = this->read_s16_le_(0x9A);
|
||||
this->calibration_.p8 = this->read_s16_le_(0x9C);
|
||||
this->calibration_.p9 = this->read_s16_le_(0x9E);
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
void BMP280Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BMP280:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with BMP280 failed!");
|
||||
break;
|
||||
case WRONG_CHIP_ID:
|
||||
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?");
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->temperature_oversampling_));
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
|
||||
}
|
||||
float BMP280Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
inline uint8_t oversampling_to_time(BMP280Oversampling over_sampling) { return (1 << uint8_t(over_sampling)) >> 1; }
|
||||
|
||||
void BMP280Component::update() {
|
||||
// Enable sensor
|
||||
ESP_LOGV(TAG, "Sending conversion request...");
|
||||
uint8_t meas_register = 0;
|
||||
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_register |= 0b01; // Forced mode
|
||||
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_register)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float meas_time = 1;
|
||||
meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
|
||||
meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
|
||||
|
||||
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||
int32_t t_fine = 0;
|
||||
float temperature = this->read_temperature_(&t_fine);
|
||||
if (isnan(temperature)) {
|
||||
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values.");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
float pressure = this->read_pressure_(t_fine);
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
// temperature was disabled
|
||||
return NAN;
|
||||
|
||||
const int32_t t1 = this->calibration_.t1;
|
||||
const int32_t t2 = this->calibration_.t2;
|
||||
const int32_t t3 = this->calibration_.t3;
|
||||
|
||||
int32_t var1 = (((adc >> 3) - (t1 << 1)) * t2) >> 11;
|
||||
int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
||||
*t_fine = var1 + var2;
|
||||
|
||||
float temperature = (*t_fine * 5 + 128) >> 8;
|
||||
return temperature / 100.0f;
|
||||
}
|
||||
|
||||
float BMP280Component::read_pressure_(int32_t t_fine) {
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
// pressure was disabled
|
||||
return NAN;
|
||||
const int64_t p1 = this->calibration_.p1;
|
||||
const int64_t p2 = this->calibration_.p2;
|
||||
const int64_t p3 = this->calibration_.p3;
|
||||
const int64_t p4 = this->calibration_.p4;
|
||||
const int64_t p5 = this->calibration_.p5;
|
||||
const int64_t p6 = this->calibration_.p6;
|
||||
const int64_t p7 = this->calibration_.p7;
|
||||
const int64_t p8 = this->calibration_.p8;
|
||||
const int64_t p9 = this->calibration_.p9;
|
||||
|
||||
int64_t var1, var2, p;
|
||||
var1 = int64_t(t_fine) - 128000;
|
||||
var2 = var1 * var1 * p6;
|
||||
var2 = var2 + ((var1 * p5) << 17);
|
||||
var2 = var2 + (p4 << 35);
|
||||
var1 = ((var1 * var1 * p3) >> 8) + ((var1 * p2) << 12);
|
||||
var1 = ((int64_t(1) << 47) + var1) * p1 >> 33;
|
||||
|
||||
if (var1 == 0)
|
||||
return NAN;
|
||||
|
||||
p = 1048576 - adc;
|
||||
p = (((p << 31) - var2) * 3125) / var1;
|
||||
var1 = (p9 * (p >> 13) * (p >> 13)) >> 25;
|
||||
var2 = (p8 * p) >> 19;
|
||||
|
||||
p = ((p + var1 + var2) >> 8) + (p7 << 4);
|
||||
return (p / 256.0f) / 100.0f;
|
||||
}
|
||||
void BMP280Component::set_temperature_oversampling(BMP280Oversampling temperature_over_sampling) {
|
||||
this->temperature_oversampling_ = temperature_over_sampling;
|
||||
}
|
||||
void BMP280Component::set_pressure_oversampling(BMP280Oversampling pressure_over_sampling) {
|
||||
this->pressure_oversampling_ = pressure_over_sampling;
|
||||
}
|
||||
void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
|
||||
uint8_t data = 0;
|
||||
this->read_byte(a_register, &data);
|
||||
return data;
|
||||
}
|
||||
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
|
||||
uint16_t data = 0;
|
||||
this->read_byte_16(a_register, &data);
|
||||
return (data >> 8) | (data << 8);
|
||||
}
|
||||
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
||||
|
||||
} // namespace bmp280
|
||||
} // namespace esphome
|
||||
94
esphome/components/bmp280/bmp280.h
Normal file
94
esphome/components/bmp280/bmp280.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp280 {
|
||||
|
||||
/// Internal struct storing the calibration values of an BMP280.
|
||||
struct BMP280CalibrationData {
|
||||
uint16_t t1; // 0x88 - 0x89
|
||||
int16_t t2; // 0x8A - 0x8B
|
||||
int16_t t3; // 0x8C - 0x8D
|
||||
|
||||
uint16_t p1; // 0x8E - 0x8F
|
||||
int16_t p2; // 0x90 - 0x91
|
||||
int16_t p3; // 0x92 - 0x93
|
||||
int16_t p4; // 0x94 - 0x95
|
||||
int16_t p5; // 0x96 - 0x97
|
||||
int16_t p6; // 0x98 - 0x99
|
||||
int16_t p7; // 0x9A - 0x9B
|
||||
int16_t p8; // 0x9C - 0x9D
|
||||
int16_t p9; // 0x9E - 0x9F
|
||||
};
|
||||
|
||||
/** Enum listing all Oversampling values for the BMP280.
|
||||
*
|
||||
* Oversampling basically means measuring a condition multiple times. Higher oversampling
|
||||
* values therefore increase the time required to read sensor values but increase accuracy.
|
||||
*/
|
||||
enum BMP280Oversampling {
|
||||
BMP280_OVERSAMPLING_NONE = 0b000,
|
||||
BMP280_OVERSAMPLING_1X = 0b001,
|
||||
BMP280_OVERSAMPLING_2X = 0b010,
|
||||
BMP280_OVERSAMPLING_4X = 0b011,
|
||||
BMP280_OVERSAMPLING_8X = 0b100,
|
||||
BMP280_OVERSAMPLING_16X = 0b101,
|
||||
};
|
||||
|
||||
/** Enum listing all Infinite Impulse Filter values for the BMP280.
|
||||
*
|
||||
* Higher values increase accuracy, but decrease response time.
|
||||
*/
|
||||
enum BMP280IIRFilter {
|
||||
BMP280_IIR_FILTER_OFF = 0b000,
|
||||
BMP280_IIR_FILTER_2X = 0b001,
|
||||
BMP280_IIR_FILTER_4X = 0b010,
|
||||
BMP280_IIR_FILTER_8X = 0b011,
|
||||
BMP280_IIR_FILTER_16X = 0b100,
|
||||
};
|
||||
|
||||
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
|
||||
class BMP280Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
|
||||
/// Set the oversampling value for the temperature sensor. Default is 16x.
|
||||
void set_temperature_oversampling(BMP280Oversampling temperature_over_sampling);
|
||||
/// Set the oversampling value for the pressure sensor. Default is 16x.
|
||||
void set_pressure_oversampling(BMP280Oversampling pressure_over_sampling);
|
||||
/// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter.
|
||||
void set_iir_filter(BMP280IIRFilter iir_filter);
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
/// Read the temperature value and store the calculated ambient temperature in t_fine.
|
||||
float read_temperature_(int32_t *t_fine);
|
||||
/// Read the pressure value in hPa using the provided t_fine value.
|
||||
float read_pressure_(int32_t t_fine);
|
||||
uint8_t read_u8_(uint8_t a_register);
|
||||
uint16_t read_u16_le_(uint8_t a_register);
|
||||
int16_t read_s16_le_(uint8_t a_register);
|
||||
|
||||
BMP280CalibrationData calibration_;
|
||||
BMP280Oversampling temperature_oversampling_{BMP280_OVERSAMPLING_16X};
|
||||
BMP280Oversampling pressure_oversampling_{BMP280_OVERSAMPLING_16X};
|
||||
BMP280IIRFilter iir_filter_{BMP280_IIR_FILTER_OFF};
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
WRONG_CHIP_ID,
|
||||
} error_code_{NONE};
|
||||
};
|
||||
|
||||
} // namespace bmp280
|
||||
} // namespace esphome
|
||||
59
esphome/components/bmp280/sensor.py
Normal file
59
esphome/components/bmp280/sensor.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \
|
||||
UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL, \
|
||||
CONF_IIR_FILTER, CONF_OVERSAMPLING
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
bmp280_ns = cg.esphome_ns.namespace('bmp280')
|
||||
BMP280Oversampling = bmp280_ns.enum('BMP280Oversampling')
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
'NONE': BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
|
||||
'1X': BMP280Oversampling.BMP280_OVERSAMPLING_1X,
|
||||
'2X': BMP280Oversampling.BMP280_OVERSAMPLING_2X,
|
||||
'4X': BMP280Oversampling.BMP280_OVERSAMPLING_4X,
|
||||
'8X': BMP280Oversampling.BMP280_OVERSAMPLING_8X,
|
||||
'16X': BMP280Oversampling.BMP280_OVERSAMPLING_16X,
|
||||
}
|
||||
|
||||
BMP280IIRFilter = bmp280_ns.enum('BMP280IIRFilter')
|
||||
IIR_FILTER_OPTIONS = {
|
||||
'OFF': BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
|
||||
'2X': BMP280IIRFilter.BMP280_IIR_FILTER_2X,
|
||||
'4X': BMP280IIRFilter.BMP280_IIR_FILTER_4X,
|
||||
'8X': BMP280IIRFilter.BMP280_IIR_FILTER_8X,
|
||||
'16X': BMP280IIRFilter.BMP280_IIR_FILTER_16X,
|
||||
}
|
||||
|
||||
BMP280Component = bmp280_ns.class_('BMP280Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(BMP280Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
|
||||
cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True),
|
||||
}),
|
||||
cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77))
|
||||
|
||||
|
||||
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:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
0
esphome/components/ccs811/__init__.py
Normal file
0
esphome/components/ccs811/__init__.py
Normal file
147
esphome/components/ccs811/ccs811.cpp
Normal file
147
esphome/components/ccs811/ccs811.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "ccs811.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ccs811 {
|
||||
|
||||
static const char *TAG = "ccs811";
|
||||
|
||||
// based on
|
||||
// - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
|
||||
|
||||
#define CHECK_TRUE(f, error_code) \
|
||||
if (!(f)) { \
|
||||
this->mark_failed(); \
|
||||
this->error_code_ = (error_code); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICAITON_FAILED)
|
||||
|
||||
void CCS811Component::setup() {
|
||||
// page 9 programming guide - hwid is always 0x81
|
||||
uint8_t hw_id;
|
||||
CHECKED_IO(this->read_byte(0x20, &hw_id))
|
||||
CHECK_TRUE(hw_id == 0x81, INVALID_ID)
|
||||
|
||||
// software reset, page 3 - allowed to fail
|
||||
this->write_bytes(0xFF, {0x11, 0xE5, 0x72, 0x8A});
|
||||
delay(5);
|
||||
|
||||
// page 10, APP_START
|
||||
CHECK_TRUE(!this->status_has_error_(), SENSOR_REPORTED_ERROR)
|
||||
CHECK_TRUE(this->status_app_is_valid_(), APP_INVALID)
|
||||
CHECK_TRUE(this->write_bytes(0xF4, {}), APP_START_FAILED)
|
||||
// App setup, wait for it to load
|
||||
delay(1);
|
||||
|
||||
// set MEAS_MODE (page 5)
|
||||
uint8_t meas_mode = 0;
|
||||
uint32_t interval = this->get_update_interval();
|
||||
if (interval <= 1000)
|
||||
meas_mode = 1 << 4;
|
||||
else if (interval <= 10000)
|
||||
meas_mode = 2 << 4;
|
||||
else
|
||||
meas_mode = 3 << 4;
|
||||
|
||||
CHECKED_IO(this->write_byte(0x01, meas_mode))
|
||||
|
||||
if (this->baseline_.has_value()) {
|
||||
// baseline available, write to sensor
|
||||
this->write_bytes(0x11, decode_uint16(*this->baseline_));
|
||||
}
|
||||
}
|
||||
void CCS811Component::update() {
|
||||
if (!this->status_has_data_())
|
||||
this->status_set_warning();
|
||||
|
||||
// page 12 - alg result data
|
||||
auto alg_data = this->read_bytes<4>(0x02);
|
||||
if (!alg_data.has_value()) {
|
||||
ESP_LOGW(TAG, "Reading CCS811 data failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
auto res = *alg_data;
|
||||
uint16_t co2 = encode_uint16(res[0], res[1]);
|
||||
uint16_t tvoc = encode_uint16(res[2], res[3]);
|
||||
|
||||
// also print baseline
|
||||
auto baseline_data = this->read_bytes<2>(0x11);
|
||||
uint16_t baseline = 0;
|
||||
if (baseline_data.has_value()) {
|
||||
baseline = encode_uint16((*baseline_data)[0], (*baseline_data)[1]);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Got co2=%u ppm, tvoc=%u ppb, baseline=0x%04X", co2, tvoc, baseline);
|
||||
|
||||
if (this->co2_ != nullptr)
|
||||
this->co2_->publish_state(co2);
|
||||
if (this->tvoc_ != nullptr)
|
||||
this->tvoc_->publish_state(tvoc);
|
||||
|
||||
this->status_clear_warning();
|
||||
|
||||
this->send_env_data_();
|
||||
}
|
||||
void CCS811Component::send_env_data_() {
|
||||
if (this->humidity_ == nullptr && this->temperature_ == nullptr)
|
||||
return;
|
||||
|
||||
float humidity = NAN;
|
||||
if (this->humidity_ != nullptr)
|
||||
humidity = this->humidity_->state;
|
||||
if (isnan(humidity) || humidity < 0 || humidity > 100)
|
||||
humidity = 50;
|
||||
float temperature = NAN;
|
||||
if (this->temperature_ != nullptr)
|
||||
temperature = this->temperature_->state;
|
||||
if (isnan(temperature) || temperature < -25 || temperature > 50)
|
||||
temperature = 25;
|
||||
// temperature has a 25° offset to allow negative temperatures
|
||||
temperature += 25;
|
||||
|
||||
// only 0.5 fractions are supported (application note)
|
||||
auto hum_value = static_cast<uint8_t>(roundf(humidity * 2));
|
||||
auto temp_value = static_cast<uint8_t>(roundf(temperature * 2));
|
||||
this->write_bytes(0x05, {hum_value, 0x00, temp_value, 0x00});
|
||||
}
|
||||
void CCS811Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CCS811");
|
||||
LOG_I2C_DEVICE(this)
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
|
||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
|
||||
if (this->baseline_) {
|
||||
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Baseline: NOT SET");
|
||||
}
|
||||
if (this->is_failed()) {
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICAITON_FAILED:
|
||||
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
|
||||
break;
|
||||
case INVALID_ID:
|
||||
ESP_LOGW(TAG, "Sensor reported an invalid ID. Is this a CCS811?");
|
||||
break;
|
||||
case SENSOR_REPORTED_ERROR:
|
||||
ESP_LOGW(TAG, "Sensor reported internal error");
|
||||
break;
|
||||
case APP_INVALID:
|
||||
ESP_LOGW(TAG, "Sensor reported invalid APP installed.");
|
||||
break;
|
||||
case APP_START_FAILED:
|
||||
ESP_LOGW(TAG, "Sensor reported APP start failed.");
|
||||
break;
|
||||
case UNKNOWN:
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown setup error!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ccs811
|
||||
} // namespace esphome
|
||||
54
esphome/components/ccs811/ccs811.h
Normal file
54
esphome/components/ccs811/ccs811.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ccs811 {
|
||||
|
||||
class CCS811Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_co2(sensor::Sensor *co2) { co2_ = co2; }
|
||||
void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; }
|
||||
void set_baseline(uint16_t baseline) { baseline_ = baseline; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
|
||||
/// Setup the sensor and test for a connection.
|
||||
void setup() override;
|
||||
/// Schedule temperature+pressure readings.
|
||||
void update() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
optional<uint8_t> read_status_() { return this->read_byte(0x00); }
|
||||
bool status_has_error_() { return this->read_status_().value_or(1) & 1; }
|
||||
bool status_app_is_valid_() { return this->read_status_().value_or(0) & (1 << 4); }
|
||||
bool status_has_data_() { return this->read_status_().value_or(0) & (1 << 3); }
|
||||
void send_env_data_();
|
||||
|
||||
enum ErrorCode {
|
||||
UNKNOWN,
|
||||
COMMUNICAITON_FAILED,
|
||||
INVALID_ID,
|
||||
SENSOR_REPORTED_ERROR,
|
||||
APP_INVALID,
|
||||
APP_START_FAILED,
|
||||
} error_code_{UNKNOWN};
|
||||
|
||||
sensor::Sensor *co2_{nullptr};
|
||||
sensor::Sensor *tvoc_{nullptr};
|
||||
optional<uint16_t> baseline_{};
|
||||
/// Input sensor for humidity reading.
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
/// Input sensor for temperature reading.
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ccs811
|
||||
} // namespace esphome
|
||||
46
esphome/components/ccs811/sensor.py
Normal file
46
esphome/components/ccs811/sensor.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
|
||||
UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_PERIODIC_TABLE_CO2
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
ccs811_ns = cg.esphome_ns.namespace('ccs811')
|
||||
CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONF_ECO2 = 'eco2'
|
||||
CONF_TVOC = 'tvoc'
|
||||
CONF_BASELINE = 'baseline'
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CCS811Component),
|
||||
cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_PERIODIC_TABLE_CO2,
|
||||
0),
|
||||
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
|
||||
|
||||
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5A))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
sens = yield sensor.new_sensor(config[CONF_ECO2])
|
||||
cg.add(var.set_co2(sens))
|
||||
sens = yield sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
||||
|
||||
if CONF_BASELINE in config:
|
||||
cg.add(var.set_baseline(config[CONF_BASELINE]))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield cg.get_variable(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield cg.get_variable(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
105
esphome/components/climate/__init__.py
Normal file
105
esphome/components/climate/__init__.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \
|
||||
CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \
|
||||
CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \
|
||||
CONF_MQTT_ID, CONF_NAME
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
climate_ns = cg.esphome_ns.namespace('climate')
|
||||
|
||||
Climate = climate_ns.class_('Climate', cg.Nameable)
|
||||
ClimateCall = climate_ns.class_('ClimateCall')
|
||||
ClimateTraits = climate_ns.class_('ClimateTraits')
|
||||
|
||||
ClimateMode = climate_ns.enum('ClimateMode')
|
||||
CLIMATE_MODES = {
|
||||
'OFF': ClimateMode.CLIMATE_MODE_OFF,
|
||||
'AUTO': ClimateMode.CLIMATE_MODE_AUTO,
|
||||
'COOL': ClimateMode.CLIMATE_MODE_COOL,
|
||||
'HEAT': ClimateMode.CLIMATE_MODE_HEAT,
|
||||
}
|
||||
|
||||
validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
|
||||
|
||||
# Actions
|
||||
ControlAction = climate_ns.class_('ControlAction', automation.Action)
|
||||
|
||||
CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(Climate),
|
||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
cv.Optional(CONF_VISUAL, default={}): cv.Schema({
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
|
||||
}),
|
||||
# TODO: MQTT topic options
|
||||
})
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_climate_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
visual = config[CONF_VISUAL]
|
||||
if CONF_MIN_TEMPERATURE in visual:
|
||||
cg.add(var.set_visual_min_temperature_override(visual[CONF_MIN_TEMPERATURE]))
|
||||
if CONF_MAX_TEMPERATURE in visual:
|
||||
cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE]))
|
||||
if CONF_TEMPERATURE_STEP in visual:
|
||||
cg.add(var.set_visual_temperature_step_override(visual[CONF_TEMPERATURE_STEP]))
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
yield mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_climate(var))
|
||||
yield setup_climate_core_(var, config)
|
||||
|
||||
|
||||
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.use_id(Climate),
|
||||
cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
||||
})
|
||||
|
||||
|
||||
@automation.register_action('climate.control', ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA)
|
||||
def climate_control_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
if CONF_MODE in config:
|
||||
template_ = yield cg.templatable(config[CONF_MODE], args, ClimateMode)
|
||||
cg.add(var.set_mode(template_))
|
||||
if CONF_TARGET_TEMPERATURE in config:
|
||||
template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float)
|
||||
cg.add(var.set_target_temperature(template_))
|
||||
if CONF_TARGET_TEMPERATURE_LOW in config:
|
||||
template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_LOW], args, float)
|
||||
cg.add(var.set_target_temperature_low(template_))
|
||||
if CONF_TARGET_TEMPERATURE_HIGH in config:
|
||||
template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_HIGH], args, float)
|
||||
cg.add(var.set_target_temperature_high(template_))
|
||||
if CONF_AWAY in config:
|
||||
template_ = yield cg.templatable(config[CONF_AWAY], args, bool)
|
||||
cg.add(var.set_away(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_CLIMATE')
|
||||
cg.add_global(climate_ns.using)
|
||||
34
esphome/components/climate/automation.h
Normal file
34
esphome/components/climate/automation.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "climate.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ControlAction(Climate *climate) : climate_(climate) {}
|
||||
|
||||
TEMPLATABLE_VALUE(ClimateMode, mode)
|
||||
TEMPLATABLE_VALUE(float, target_temperature)
|
||||
TEMPLATABLE_VALUE(float, target_temperature_low)
|
||||
TEMPLATABLE_VALUE(float, target_temperature_high)
|
||||
TEMPLATABLE_VALUE(bool, away)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->climate_->make_call();
|
||||
call.set_mode(this->mode_.optional_value(x...));
|
||||
call.set_target_temperature(this->target_temperature_.optional_value(x...));
|
||||
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
|
||||
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
||||
call.set_away(this->away_.optional_value(x...));
|
||||
call.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
Climate *climate_;
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
254
esphome/components/climate/climate.cpp
Normal file
254
esphome/components/climate/climate.cpp
Normal file
@@ -0,0 +1,254 @@
|
||||
#include "climate.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
static const char *TAG = "climate";
|
||||
|
||||
void ClimateCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
this->validate_();
|
||||
if (this->mode_.has_value()) {
|
||||
const char *mode_s = climate_mode_to_string(*this->mode_);
|
||||
ESP_LOGD(TAG, " Mode: %s", mode_s);
|
||||
}
|
||||
if (this->target_temperature_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_);
|
||||
}
|
||||
if (this->target_temperature_low_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature Low: %.2f", *this->target_temperature_low_);
|
||||
}
|
||||
if (this->target_temperature_high_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
|
||||
}
|
||||
if (this->away_.has_value()) {
|
||||
ESP_LOGD(TAG, " Away Mode: %s", ONOFF(*this->away_));
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
void ClimateCall::validate_() {
|
||||
auto traits = this->parent_->get_traits();
|
||||
if (this->mode_.has_value()) {
|
||||
auto mode = *this->mode_;
|
||||
if (!traits.supports_mode(mode)) {
|
||||
ESP_LOGW(TAG, " Mode %s is not supported by this device!", climate_mode_to_string(mode));
|
||||
this->mode_.reset();
|
||||
}
|
||||
}
|
||||
if (this->target_temperature_.has_value()) {
|
||||
auto target = *this->target_temperature_;
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
ESP_LOGW(TAG, " Cannot set target temperature for climate device "
|
||||
"with two-point target temperature!");
|
||||
this->target_temperature_.reset();
|
||||
} else if (isnan(target)) {
|
||||
ESP_LOGW(TAG, " Target temperature must not be NAN!");
|
||||
this->target_temperature_.reset();
|
||||
}
|
||||
}
|
||||
if (this->target_temperature_low_.has_value() || this->target_temperature_high_.has_value()) {
|
||||
if (!traits.get_supports_two_point_target_temperature()) {
|
||||
ESP_LOGW(TAG, " Cannot set low/high target temperature for this device!");
|
||||
this->target_temperature_low_.reset();
|
||||
this->target_temperature_high_.reset();
|
||||
}
|
||||
}
|
||||
if (this->target_temperature_low_.has_value() && isnan(*this->target_temperature_low_)) {
|
||||
ESP_LOGW(TAG, " Target temperature low must not be NAN!");
|
||||
this->target_temperature_low_.reset();
|
||||
}
|
||||
if (this->target_temperature_high_.has_value() && isnan(*this->target_temperature_high_)) {
|
||||
ESP_LOGW(TAG, " Target temperature low must not be NAN!");
|
||||
this->target_temperature_high_.reset();
|
||||
}
|
||||
if (this->target_temperature_low_.has_value() && this->target_temperature_high_.has_value()) {
|
||||
float low = *this->target_temperature_low_;
|
||||
float high = *this->target_temperature_high_;
|
||||
if (low > high) {
|
||||
ESP_LOGW(TAG, " Target temperature low %.2f must be smaller than target temperature high %.2f!", low, high);
|
||||
this->target_temperature_low_.reset();
|
||||
this->target_temperature_high_.reset();
|
||||
}
|
||||
}
|
||||
if (this->away_.has_value()) {
|
||||
if (!traits.get_supports_away()) {
|
||||
ESP_LOGW(TAG, " Cannot set away mode for this device!");
|
||||
this->away_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
ClimateCall &ClimateCall::set_mode(ClimateMode mode) {
|
||||
this->mode_ = mode;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_mode(const std::string &mode) {
|
||||
if (str_equals_case_insensitive(mode, "OFF")) {
|
||||
this->set_mode(CLIMATE_MODE_OFF);
|
||||
} else if (str_equals_case_insensitive(mode, "AUTO")) {
|
||||
this->set_mode(CLIMATE_MODE_AUTO);
|
||||
} else if (str_equals_case_insensitive(mode, "COOL")) {
|
||||
this->set_mode(CLIMATE_MODE_COOL);
|
||||
} else if (str_equals_case_insensitive(mode, "HEAT")) {
|
||||
this->set_mode(CLIMATE_MODE_HEAT);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_temperature(float target_temperature) {
|
||||
this->target_temperature_ = target_temperature;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_temperature_low(float target_temperature_low) {
|
||||
this->target_temperature_low_ = target_temperature_low;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_temperature_high(float target_temperature_high) {
|
||||
this->target_temperature_high_ = target_temperature_high;
|
||||
return *this;
|
||||
}
|
||||
const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
|
||||
ClimateCall &ClimateCall::set_away(bool away) {
|
||||
this->away_ = away;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_away(optional<bool> away) {
|
||||
this->away_ = away;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
|
||||
this->target_temperature_high_ = target_temperature_high;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_temperature_low(optional<float> target_temperature_low) {
|
||||
this->target_temperature_low_ = target_temperature_low;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_temperature(optional<float> target_temperature) {
|
||||
this->target_temperature_ = target_temperature;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
|
||||
this->mode_ = mode;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||
this->rtc_ = global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash());
|
||||
ClimateDeviceRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
return recovered;
|
||||
}
|
||||
void Climate::save_state_() {
|
||||
ClimateDeviceRestoreState state{};
|
||||
// initialize as zero to prevent random data on stack triggering erase
|
||||
memset(&state, 0, sizeof(ClimateDeviceRestoreState));
|
||||
|
||||
state.mode = this->mode;
|
||||
auto traits = this->get_traits();
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
state.target_temperature_low = this->target_temperature_low;
|
||||
state.target_temperature_high = this->target_temperature_high;
|
||||
} else {
|
||||
state.target_temperature = this->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_away()) {
|
||||
state.away = this->away;
|
||||
}
|
||||
|
||||
this->rtc_.save(&state);
|
||||
}
|
||||
void Climate::publish_state() {
|
||||
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGD(TAG, " Mode: %s", climate_mode_to_string(this->mode));
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
||||
}
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
ESP_LOGD(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low,
|
||||
this->target_temperature_high);
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature);
|
||||
}
|
||||
if (traits.get_supports_away()) {
|
||||
ESP_LOGD(TAG, " Away: %s", ONOFF(this->away));
|
||||
}
|
||||
|
||||
// Send state to frontend
|
||||
this->state_callback_.call();
|
||||
// Save state
|
||||
this->save_state_();
|
||||
}
|
||||
uint32_t Climate::hash_base() { return 3104134496UL; }
|
||||
|
||||
ClimateTraits Climate::get_traits() {
|
||||
auto traits = this->traits();
|
||||
if (this->visual_min_temperature_override_.has_value()) {
|
||||
traits.set_visual_min_temperature(*this->visual_min_temperature_override_);
|
||||
}
|
||||
if (this->visual_max_temperature_override_.has_value()) {
|
||||
traits.set_visual_max_temperature(*this->visual_max_temperature_override_);
|
||||
}
|
||||
if (this->visual_temperature_step_override_.has_value()) {
|
||||
traits.set_visual_temperature_step(*this->visual_temperature_step_override_);
|
||||
}
|
||||
return traits;
|
||||
}
|
||||
|
||||
void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) {
|
||||
this->visual_min_temperature_override_ = visual_min_temperature_override;
|
||||
}
|
||||
void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) {
|
||||
this->visual_max_temperature_override_ = visual_max_temperature_override;
|
||||
}
|
||||
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
|
||||
this->visual_temperature_step_override_ = visual_temperature_step_override;
|
||||
}
|
||||
Climate::Climate(const std::string &name) : Nameable(name) {}
|
||||
Climate::Climate() : Climate("") {}
|
||||
ClimateCall Climate::make_call() { return ClimateCall(this); }
|
||||
|
||||
ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
||||
auto call = climate->make_call();
|
||||
auto traits = climate->get_traits();
|
||||
call.set_mode(this->mode);
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
call.set_target_temperature_low(this->target_temperature_low);
|
||||
call.set_target_temperature_high(this->target_temperature_high);
|
||||
} else {
|
||||
call.set_target_temperature(this->target_temperature);
|
||||
}
|
||||
if (traits.get_supports_away()) {
|
||||
call.set_away(this->away);
|
||||
}
|
||||
return call;
|
||||
}
|
||||
void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||
auto traits = climate->get_traits();
|
||||
climate->mode = this->mode;
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
climate->target_temperature_low = this->target_temperature_low;
|
||||
climate->target_temperature_high = this->target_temperature_high;
|
||||
} else {
|
||||
climate->target_temperature = this->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_away()) {
|
||||
climate->away = this->away;
|
||||
}
|
||||
climate->publish_state();
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
213
esphome/components/climate/climate.h
Normal file
213
esphome/components/climate/climate.h
Normal file
@@ -0,0 +1,213 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "climate_mode.h"
|
||||
#include "climate_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
class Climate;
|
||||
|
||||
/** This class is used to encode all control actions on a climate device.
|
||||
*
|
||||
* It is supposed to be used by all code that wishes to control a climate device (mqtt, api, lambda etc).
|
||||
* Create an instance of this class by calling `id(climate_device).make_call();`. Then set all attributes
|
||||
* with the `set_x` methods. Finally, to apply the changes call `.perform();`.
|
||||
*
|
||||
* The integration that implements the climate device receives this instance with the `control` method.
|
||||
* It should check all the properties it implements and apply them as needed. It should do so by
|
||||
* getting all properties it controls with the getter methods in this class. If the optional value is
|
||||
* set (check with `.has_value()`) that means the user wants to control this property. Get the value
|
||||
* of the optional with the star operator (`*call.get_mode()`) and apply it.
|
||||
*/
|
||||
class ClimateCall {
|
||||
public:
|
||||
explicit ClimateCall(Climate *parent) : parent_(parent) {}
|
||||
|
||||
/// Set the mode of the climate device.
|
||||
ClimateCall &set_mode(ClimateMode mode);
|
||||
/// Set the mode of the climate device.
|
||||
ClimateCall &set_mode(optional<ClimateMode> mode);
|
||||
/// Set the mode of the climate device based on a string.
|
||||
ClimateCall &set_mode(const std::string &mode);
|
||||
/// Set the target temperature of the climate device.
|
||||
ClimateCall &set_target_temperature(float target_temperature);
|
||||
/// Set the target temperature of the climate device.
|
||||
ClimateCall &set_target_temperature(optional<float> target_temperature);
|
||||
/** Set the low point target temperature of the climate device
|
||||
*
|
||||
* For climate devices with two point target temperature control
|
||||
*/
|
||||
ClimateCall &set_target_temperature_low(float target_temperature_low);
|
||||
/** Set the low point target temperature of the climate device
|
||||
*
|
||||
* For climate devices with two point target temperature control
|
||||
*/
|
||||
ClimateCall &set_target_temperature_low(optional<float> target_temperature_low);
|
||||
/** Set the high point target temperature of the climate device
|
||||
*
|
||||
* For climate devices with two point target temperature control
|
||||
*/
|
||||
ClimateCall &set_target_temperature_high(float target_temperature_high);
|
||||
/** Set the high point target temperature of the climate device
|
||||
*
|
||||
* For climate devices with two point target temperature control
|
||||
*/
|
||||
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
|
||||
ClimateCall &set_away(bool away);
|
||||
ClimateCall &set_away(optional<bool> away);
|
||||
|
||||
void perform();
|
||||
|
||||
const optional<ClimateMode> &get_mode() const;
|
||||
const optional<float> &get_target_temperature() const;
|
||||
const optional<float> &get_target_temperature_low() const;
|
||||
const optional<float> &get_target_temperature_high() const;
|
||||
const optional<bool> &get_away() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
Climate *const parent_;
|
||||
optional<ClimateMode> mode_;
|
||||
optional<float> target_temperature_;
|
||||
optional<float> target_temperature_low_;
|
||||
optional<float> target_temperature_high_;
|
||||
optional<bool> away_;
|
||||
};
|
||||
|
||||
/// Struct used to save the state of the climate device in restore memory.
|
||||
struct ClimateDeviceRestoreState {
|
||||
ClimateMode mode;
|
||||
bool away;
|
||||
union {
|
||||
float target_temperature;
|
||||
struct {
|
||||
float target_temperature_low;
|
||||
float target_temperature_high;
|
||||
};
|
||||
};
|
||||
|
||||
/// Convert this struct to a climate call that can be performed.
|
||||
ClimateCall to_call(Climate *climate);
|
||||
/// Apply these settings to the climate device.
|
||||
void apply(Climate *climate);
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* ClimateDevice - This is the base class for all climate integrations. Each integration
|
||||
* needs to extend this class and implement two functions:
|
||||
*
|
||||
* - get_traits() - return the static traits of the climate device
|
||||
* - control(ClimateDeviceCall call) - Apply the given changes from call.
|
||||
*
|
||||
* To write data to the frontend, the integration must first set the properties using
|
||||
* this->property = value; (for example this->current_temperature = 42.0;); then the integration
|
||||
* must call this->publish_state(); to send the entire state to the frontend.
|
||||
*
|
||||
* The entire state of the climate device is encoded in public properties of the base class (current_temperature,
|
||||
* mode etc). These are read-only for the user and rw for integrations. The reason these are public
|
||||
* is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...`
|
||||
*/
|
||||
class Climate : public Nameable {
|
||||
public:
|
||||
/// Construct a climate device with empty name (will be set later).
|
||||
Climate();
|
||||
/// Construct a climate device with a name.
|
||||
Climate(const std::string &name);
|
||||
|
||||
/// The active mode of the climate device.
|
||||
ClimateMode mode{CLIMATE_MODE_OFF};
|
||||
/// The current temperature of the climate device, as reported from the integration.
|
||||
float current_temperature{NAN};
|
||||
|
||||
union {
|
||||
/// The target temperature of the climate device.
|
||||
float target_temperature;
|
||||
struct {
|
||||
/// The minimum target temperature of the climate device, for climate devices with split target temperature.
|
||||
float target_temperature_low;
|
||||
/// The maximum target temperature of the climate device, for climate devices with split target temperature.
|
||||
float target_temperature_high;
|
||||
};
|
||||
};
|
||||
|
||||
/** Whether the climate device is in away mode.
|
||||
*
|
||||
* Away allows climate devices to have two different target temperature configs:
|
||||
* one for normal mode and one for away mode.
|
||||
*/
|
||||
bool away{false};
|
||||
|
||||
/** Add a callback for the climate device state, each time the state of the climate device is updated
|
||||
* (using publish_state), this callback will be called.
|
||||
*
|
||||
* @param callback The callback to call.
|
||||
*/
|
||||
void add_on_state_callback(std::function<void()> &&callback);
|
||||
|
||||
/** Make a climate device control call, this is used to control the climate device, see the ClimateCall description
|
||||
* for more info.
|
||||
* @return A new ClimateCall instance targeting this climate device.
|
||||
*/
|
||||
ClimateCall make_call();
|
||||
|
||||
/** Publish the state of the climate device, to be called from integrations.
|
||||
*
|
||||
* This will schedule the climate device to publish its state to all listeners and save the current state
|
||||
* to recover memory.
|
||||
*/
|
||||
void publish_state();
|
||||
|
||||
/** Get the traits of this climate device with all overrides applied.
|
||||
*
|
||||
* Traits are static data that encode the capabilities and static data for a climate device such as supported
|
||||
* modes, temperature range etc.
|
||||
*/
|
||||
ClimateTraits get_traits();
|
||||
|
||||
void set_visual_min_temperature_override(float visual_min_temperature_override);
|
||||
void set_visual_max_temperature_override(float visual_max_temperature_override);
|
||||
void set_visual_temperature_step_override(float visual_temperature_step_override);
|
||||
|
||||
protected:
|
||||
friend ClimateCall;
|
||||
|
||||
/** Get the default traits of this climate device.
|
||||
*
|
||||
* Traits are static data that encode the capabilities and static data for a climate device such as supported
|
||||
* modes, temperature range etc. Each integration must implement this method and the return value must
|
||||
* be constant during all of execution time.
|
||||
*/
|
||||
virtual ClimateTraits traits() = 0;
|
||||
|
||||
/** Control the climate device, this is a virtual method that each climate integration must implement.
|
||||
*
|
||||
* See more info in ClimateCall. The integration should check all of its values in this method and
|
||||
* set them accordingly. At the end of the call, the integration must call `publish_state()` to
|
||||
* notify the frontend of a changed state.
|
||||
*
|
||||
* @param call The ClimateCall instance encoding all attribute changes.
|
||||
*/
|
||||
virtual void control(const ClimateCall &call) = 0;
|
||||
/// Restore the state of the climate device, call this from your setup() method.
|
||||
optional<ClimateDeviceRestoreState> restore_state_();
|
||||
/** Internal method to save the state of the climate device to recover memory. This is automatically
|
||||
* called from publish_state()
|
||||
*/
|
||||
void save_state_();
|
||||
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
optional<float> visual_min_temperature_override_{};
|
||||
optional<float> visual_max_temperature_override_{};
|
||||
optional<float> visual_temperature_step_override_{};
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
22
esphome/components/climate/climate_mode.cpp
Normal file
22
esphome/components/climate/climate_mode.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "climate_mode.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
const char *climate_mode_to_string(ClimateMode mode) {
|
||||
switch (mode) {
|
||||
case CLIMATE_MODE_OFF:
|
||||
return "OFF";
|
||||
case CLIMATE_MODE_AUTO:
|
||||
return "AUTO";
|
||||
case CLIMATE_MODE_COOL:
|
||||
return "COOL";
|
||||
case CLIMATE_MODE_HEAT:
|
||||
return "HEAT";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
24
esphome/components/climate/climate_mode.h
Normal file
24
esphome/components/climate/climate_mode.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
/// Enum for all modes a climate device can be in.
|
||||
enum ClimateMode : uint8_t {
|
||||
/// The climate device is off (not in auto, heat or cool mode)
|
||||
CLIMATE_MODE_OFF = 0,
|
||||
/// The climate device is set to automatically change the heating/cooling cycle
|
||||
CLIMATE_MODE_AUTO = 1,
|
||||
/// The climate device is manually set to cool mode (not in auto mode!)
|
||||
CLIMATE_MODE_COOL = 2,
|
||||
/// The climate device is manually set to heat mode (not in auto mode!)
|
||||
CLIMATE_MODE_HEAT = 3,
|
||||
};
|
||||
|
||||
/// Convert the given ClimateMode to a human-readable string.
|
||||
const char *climate_mode_to_string(ClimateMode mode);
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
57
esphome/components/climate/climate_traits.cpp
Normal file
57
esphome/components/climate/climate_traits.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "climate_traits.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
bool ClimateTraits::supports_mode(ClimateMode mode) const {
|
||||
switch (mode) {
|
||||
case CLIMATE_MODE_OFF:
|
||||
return true;
|
||||
case CLIMATE_MODE_AUTO:
|
||||
return this->supports_auto_mode_;
|
||||
case CLIMATE_MODE_COOL:
|
||||
return this->supports_cool_mode_;
|
||||
case CLIMATE_MODE_HEAT:
|
||||
return this->supports_heat_mode_;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; }
|
||||
void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) {
|
||||
supports_current_temperature_ = supports_current_temperature;
|
||||
}
|
||||
bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; }
|
||||
void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) {
|
||||
supports_two_point_target_temperature_ = supports_two_point_target_temperature;
|
||||
}
|
||||
void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; }
|
||||
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
|
||||
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
|
||||
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
|
||||
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||
void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) {
|
||||
visual_min_temperature_ = visual_min_temperature;
|
||||
}
|
||||
float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; }
|
||||
void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) {
|
||||
visual_max_temperature_ = visual_max_temperature;
|
||||
}
|
||||
float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; }
|
||||
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
|
||||
// use printf %g to find number of digits based on temperature step
|
||||
char buf[32];
|
||||
sprintf(buf, "%.5g", this->visual_temperature_step_);
|
||||
std::string str{buf};
|
||||
size_t dot_pos = str.find('.');
|
||||
if (dot_pos == std::string::npos)
|
||||
return 0;
|
||||
|
||||
return str.length() - dot_pos - 1;
|
||||
}
|
||||
void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
|
||||
bool ClimateTraits::get_supports_away() const { return supports_away_; }
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
68
esphome/components/climate/climate_traits.h
Normal file
68
esphome/components/climate/climate_traits.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include "climate_mode.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
/** This class contains all static data for climate devices.
|
||||
*
|
||||
* All climate devices must support these features:
|
||||
* - OFF mode
|
||||
* - Target Temperature
|
||||
*
|
||||
* All other properties and modes are optional and the integration must mark
|
||||
* each of them as supported by setting the appropriate flag here.
|
||||
*
|
||||
* - supports current temperature - if the climate device supports reporting a current temperature
|
||||
* - supports two point target temperature - if the climate device's target temperature should be
|
||||
* split in target_temperature_low and target_temperature_high instead of just the single target_temperature
|
||||
* - supports modes:
|
||||
* - auto mode (automatic control)
|
||||
* - cool mode (lowers current temperature)
|
||||
* - heat mode (increases current temperature)
|
||||
* - supports away - away mode means that the climate device supports two different
|
||||
* target temperature settings: one target temp setting for "away" mode and one for non-away mode.
|
||||
*
|
||||
* This class also contains static data for the climate device display:
|
||||
* - visual min/max temperature - tells the frontend what range of temperatures the climate device
|
||||
* should display (gauge min/max values)
|
||||
* - temperature step - the step with which to increase/decrease target temperature.
|
||||
* This also affects with how many decimal places the temperature is shown
|
||||
*/
|
||||
class ClimateTraits {
|
||||
public:
|
||||
bool get_supports_current_temperature() const;
|
||||
void set_supports_current_temperature(bool supports_current_temperature);
|
||||
bool get_supports_two_point_target_temperature() const;
|
||||
void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature);
|
||||
void set_supports_auto_mode(bool supports_auto_mode);
|
||||
void set_supports_cool_mode(bool supports_cool_mode);
|
||||
void set_supports_heat_mode(bool supports_heat_mode);
|
||||
void set_supports_away(bool supports_away);
|
||||
bool get_supports_away() const;
|
||||
bool supports_mode(ClimateMode mode) const;
|
||||
|
||||
float get_visual_min_temperature() const;
|
||||
void set_visual_min_temperature(float visual_min_temperature);
|
||||
float get_visual_max_temperature() const;
|
||||
void set_visual_max_temperature(float visual_max_temperature);
|
||||
float get_visual_temperature_step() const;
|
||||
int8_t get_temperature_accuracy_decimals() const;
|
||||
void set_visual_temperature_step(float temperature_step);
|
||||
|
||||
protected:
|
||||
bool supports_current_temperature_{false};
|
||||
bool supports_two_point_target_temperature_{false};
|
||||
bool supports_auto_mode_{false};
|
||||
bool supports_cool_mode_{false};
|
||||
bool supports_heat_mode_{false};
|
||||
bool supports_away_{false};
|
||||
|
||||
float visual_min_temperature_{10};
|
||||
float visual_max_temperature_{30};
|
||||
float visual_temperature_step_{0.1};
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
0
esphome/components/coolix/__init__.py
Normal file
0
esphome/components/coolix/__init__.py
Normal file
36
esphome/components/coolix/climate.py
Normal file
36
esphome/components/coolix/climate.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate, remote_transmitter, sensor
|
||||
from esphome.const import CONF_ID, CONF_SENSOR
|
||||
|
||||
AUTO_LOAD = ['sensor']
|
||||
|
||||
coolix_ns = cg.esphome_ns.namespace('coolix')
|
||||
CoolixClimate = coolix_ns.class_('CoolixClimate', climate.Climate, cg.Component)
|
||||
|
||||
CONF_TRANSMITTER_ID = 'transmitter_id'
|
||||
CONF_SUPPORTS_HEAT = 'supports_heat'
|
||||
CONF_SUPPORTS_COOL = 'supports_cool'
|
||||
|
||||
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(CoolixClimate),
|
||||
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent),
|
||||
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||
}).extend(cv.COMPONENT_SCHEMA))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield climate.register_climate(var, config)
|
||||
|
||||
cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL]))
|
||||
cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT]))
|
||||
if CONF_SENSOR in config:
|
||||
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_sensor(sens))
|
||||
|
||||
transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID])
|
||||
cg.add(var.set_transmitter(transmitter))
|
||||
170
esphome/components/coolix/coolix.cpp
Normal file
170
esphome/components/coolix/coolix.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "coolix.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace coolix {
|
||||
|
||||
static const char *TAG = "coolix.climate";
|
||||
|
||||
const uint32_t COOLIX_OFF = 0xB27BE0;
|
||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
||||
const uint32_t COOLIX_DEFAULT_STATE = 0xB2BFC8;
|
||||
const uint32_t COOLIX_DEFAULT_STATE_AUTO_24_FAN = 0xB21F48;
|
||||
const uint8_t COOLIX_COOL = 0b00;
|
||||
const uint8_t COOLIX_DRY = 0b01;
|
||||
const uint8_t COOLIX_AUTO = 0b10;
|
||||
const uint8_t COOLIX_HEAT = 0b11;
|
||||
const uint8_t COOLIX_FAN = 4; // Synthetic.
|
||||
const uint32_t COOLIX_MODE_MASK = 0b000000000000000000001100; // 0xC
|
||||
|
||||
// Temperature
|
||||
const uint8_t COOLIX_TEMP_MIN = 17; // Celsius
|
||||
const uint8_t COOLIX_TEMP_MAX = 30; // Celsius
|
||||
const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
||||
const uint8_t COOLIX_FAN_TEMP_CODE = 0b1110; // Part of Fan Mode.
|
||||
const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
||||
const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||
0b0000, // 17C
|
||||
0b0001, // 18c
|
||||
0b0011, // 19C
|
||||
0b0010, // 20C
|
||||
0b0110, // 21C
|
||||
0b0111, // 22C
|
||||
0b0101, // 23C
|
||||
0b0100, // 24C
|
||||
0b1100, // 25C
|
||||
0b1101, // 26C
|
||||
0b1001, // 27C
|
||||
0b1000, // 28C
|
||||
0b1010, // 29C
|
||||
0b1011 // 30C
|
||||
};
|
||||
|
||||
// Constants
|
||||
// Pulse parms are *50-100 for the Mark and *50+100 for the space
|
||||
// First MARK is the one after the long gap
|
||||
// pulse parameters in usec
|
||||
const uint16_t COOLIX_TICK = 560; // Approximately 21 cycles at 38kHz
|
||||
const uint16_t COOLIX_BIT_MARK_TICKS = 1;
|
||||
const uint16_t COOLIX_BIT_MARK = COOLIX_BIT_MARK_TICKS * COOLIX_TICK;
|
||||
const uint16_t COOLIX_ONE_SPACE_TICKS = 3;
|
||||
const uint16_t COOLIX_ONE_SPACE = COOLIX_ONE_SPACE_TICKS * COOLIX_TICK;
|
||||
const uint16_t COOLIX_ZERO_SPACE_TICKS = 1;
|
||||
const uint16_t COOLIX_ZERO_SPACE = COOLIX_ZERO_SPACE_TICKS * COOLIX_TICK;
|
||||
const uint16_t COOLIX_HEADER_MARK_TICKS = 8;
|
||||
const uint16_t COOLIX_HEADER_MARK = COOLIX_HEADER_MARK_TICKS * COOLIX_TICK;
|
||||
const uint16_t COOLIX_HEADER_SPACE_TICKS = 8;
|
||||
const uint16_t COOLIX_HEADER_SPACE = COOLIX_HEADER_SPACE_TICKS * COOLIX_TICK;
|
||||
const uint16_t COOLIX_MIN_GAP_TICKS = COOLIX_HEADER_MARK_TICKS + COOLIX_ZERO_SPACE_TICKS;
|
||||
const uint16_t COOLIX_MIN_GAP = COOLIX_MIN_GAP_TICKS * COOLIX_TICK;
|
||||
|
||||
const uint16_t COOLIX_BITS = 24;
|
||||
|
||||
climate::ClimateTraits CoolixClimate::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
||||
traits.set_supports_auto_mode(true);
|
||||
traits.set_supports_cool_mode(this->supports_cool_);
|
||||
traits.set_supports_heat_mode(this->supports_heat_);
|
||||
traits.set_supports_two_point_target_temperature(false);
|
||||
traits.set_supports_away(false);
|
||||
traits.set_visual_min_temperature(17);
|
||||
traits.set_visual_max_temperature(30);
|
||||
traits.set_visual_temperature_step(1);
|
||||
return traits;
|
||||
}
|
||||
|
||||
void CoolixClimate::setup() {
|
||||
if (this->sensor_) {
|
||||
this->sensor_->add_on_state_callback([this](float state) {
|
||||
this->current_temperature = state;
|
||||
// current temperature changed, publish state
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_temperature = this->sensor_->state;
|
||||
} else
|
||||
this->current_temperature = NAN;
|
||||
// restore set points
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
restore->apply(this);
|
||||
} else {
|
||||
// restore from defaults
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
// initialize target temperature to some value so that it's not NAN
|
||||
this->target_temperature = roundf(this->current_temperature);
|
||||
}
|
||||
}
|
||||
|
||||
void CoolixClimate::control(const climate::ClimateCall &call) {
|
||||
if (call.get_mode().has_value())
|
||||
this->mode = *call.get_mode();
|
||||
if (call.get_target_temperature().has_value())
|
||||
this->target_temperature = *call.get_target_temperature();
|
||||
|
||||
this->transmit_state_();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void CoolixClimate::transmit_state_() {
|
||||
uint32_t remote_state;
|
||||
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state = (COOLIX_DEFAULT_STATE & ~COOLIX_MODE_MASK) | (COOLIX_COOL << 2);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state = (COOLIX_DEFAULT_STATE & ~COOLIX_MODE_MASK) | (COOLIX_HEAT << 2);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
remote_state = COOLIX_DEFAULT_STATE_AUTO_24_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
remote_state = COOLIX_OFF;
|
||||
break;
|
||||
}
|
||||
if (this->mode != climate::CLIMATE_MODE_OFF) {
|
||||
auto temp = (uint8_t) roundf(clamp(this->target_temperature, COOLIX_TEMP_MIN, COOLIX_TEMP_MAX));
|
||||
remote_state &= ~COOLIX_TEMP_MASK; // Clear the old temp.
|
||||
remote_state |= (COOLIX_TEMP_MAP[temp - COOLIX_TEMP_MIN] << 4);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Sending coolix code: %u", remote_state);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
|
||||
data->set_carrier_frequency(38000);
|
||||
uint16_t repeat = 1;
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
data->mark(COOLIX_HEADER_MARK);
|
||||
data->space(COOLIX_HEADER_SPACE);
|
||||
// Data
|
||||
// Break data into byte segments, starting at the Most Significant
|
||||
// Byte. Each byte then being sent normal, then followed inverted.
|
||||
for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) {
|
||||
// Grab a bytes worth of data.
|
||||
uint8_t segment = (remote_state >> (COOLIX_BITS - i)) & 0xFF;
|
||||
// Normal
|
||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
||||
data->mark(COOLIX_BIT_MARK);
|
||||
data->space((segment & mask) ? COOLIX_ONE_SPACE : COOLIX_ZERO_SPACE);
|
||||
}
|
||||
// Inverted
|
||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
||||
data->mark(COOLIX_BIT_MARK);
|
||||
data->space(!(segment & mask) ? COOLIX_ONE_SPACE : COOLIX_ZERO_SPACE);
|
||||
}
|
||||
}
|
||||
// Footer
|
||||
data->mark(COOLIX_BIT_MARK);
|
||||
data->space(COOLIX_MIN_GAP); // Pause before repeating
|
||||
}
|
||||
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
} // namespace coolix
|
||||
} // namespace esphome
|
||||
40
esphome/components/coolix/coolix.h
Normal file
40
esphome/components/coolix/coolix.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
#include "esphome/components/remote_transmitter/remote_transmitter.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace coolix {
|
||||
|
||||
class CoolixClimate : public climate::Climate, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
|
||||
this->transmitter_ = transmitter;
|
||||
}
|
||||
void set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
|
||||
void set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
|
||||
void set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
/// Override control to change settings of the climate device.
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
/// Return the traits of this controller.
|
||||
climate::ClimateTraits traits() override;
|
||||
|
||||
/// Transmit via IR the state of this climate controller.
|
||||
void transmit_state_();
|
||||
|
||||
bool supports_cool_{true};
|
||||
bool supports_heat_{true};
|
||||
|
||||
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace coolix
|
||||
} // namespace esphome
|
||||
130
esphome/components/cover/__init__.py
Normal file
130
esphome/components/cover/__init__.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id, Condition
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \
|
||||
CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, CONF_NAME
|
||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
'', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage',
|
||||
'shade', 'shutter', 'window'
|
||||
]
|
||||
|
||||
cover_ns = cg.esphome_ns.namespace('cover')
|
||||
|
||||
Cover = cover_ns.class_('Cover', cg.Nameable)
|
||||
|
||||
COVER_OPEN = cover_ns.COVER_OPEN
|
||||
COVER_CLOSED = cover_ns.COVER_CLOSED
|
||||
|
||||
COVER_STATES = {
|
||||
'OPEN': COVER_OPEN,
|
||||
'CLOSED': COVER_CLOSED,
|
||||
}
|
||||
validate_cover_state = cv.enum(COVER_STATES, upper=True)
|
||||
|
||||
CoverOperation = cover_ns.enum('CoverOperation')
|
||||
COVER_OPERATIONS = {
|
||||
'IDLE': CoverOperation.COVER_OPERATION_IDLE,
|
||||
'OPENING': CoverOperation.COVER_OPERATION_OPENING,
|
||||
'CLOSING': CoverOperation.COVER_OPERATION_CLOSING,
|
||||
}
|
||||
validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True)
|
||||
|
||||
# Actions
|
||||
OpenAction = cover_ns.class_('OpenAction', automation.Action)
|
||||
CloseAction = cover_ns.class_('CloseAction', automation.Action)
|
||||
StopAction = cover_ns.class_('StopAction', automation.Action)
|
||||
ControlAction = cover_ns.class_('ControlAction', automation.Action)
|
||||
CoverPublishAction = cover_ns.class_('CoverPublishAction', automation.Action)
|
||||
CoverIsOpenCondition = cover_ns.class_('CoverIsOpenCondition', Condition)
|
||||
CoverIsClosedCondition = cover_ns.class_('CoverIsClosedCondition', Condition)
|
||||
|
||||
COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(Cover),
|
||||
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTCoverComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
# TODO: MQTT topic options
|
||||
})
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_cover_core_(var, config):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
yield mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
@coroutine
|
||||
def register_cover(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_cover(var))
|
||||
yield setup_cover_core_(var, config)
|
||||
|
||||
|
||||
COVER_ACTION_SCHEMA = maybe_simple_id({
|
||||
cv.Required(CONF_ID): cv.use_id(Cover),
|
||||
})
|
||||
|
||||
|
||||
@automation.register_action('cover.open', OpenAction, COVER_ACTION_SCHEMA)
|
||||
def cover_open_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action('cover.close', CloseAction, COVER_ACTION_SCHEMA)
|
||||
def cover_close_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action('cover.stop', StopAction, COVER_ACTION_SCHEMA)
|
||||
def cover_stop_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.use_id(Cover),
|
||||
cv.Optional(CONF_STOP): cv.templatable(cv.boolean),
|
||||
cv.Exclusive(CONF_STATE, 'pos'): cv.templatable(validate_cover_state),
|
||||
cv.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_TILT): cv.templatable(cv.percentage),
|
||||
})
|
||||
|
||||
|
||||
@automation.register_action('cover.control', ControlAction, COVER_CONTROL_ACTION_SCHEMA)
|
||||
def cover_control_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
if CONF_STOP in config:
|
||||
template_ = yield cg.templatable(config[CONF_STOP], args, bool)
|
||||
cg.add(var.set_stop(template_))
|
||||
if CONF_STATE in config:
|
||||
template_ = yield cg.templatable(config[CONF_STATE], args, float)
|
||||
cg.add(var.set_position(template_))
|
||||
if CONF_POSITION in config:
|
||||
template_ = yield cg.templatable(config[CONF_POSITION], args, float)
|
||||
cg.add(var.set_position(template_))
|
||||
if CONF_TILT in config:
|
||||
template_ = yield cg.templatable(config[CONF_TILT], args, float)
|
||||
cg.add(var.set_tilt(template_))
|
||||
yield var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
def to_code(config):
|
||||
cg.add_define('USE_COVER')
|
||||
cg.add_global(cover_ns.using)
|
||||
102
esphome/components/cover/automation.h
Normal file
102
esphome/components/cover/automation.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "cover.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
template<typename... Ts> class OpenAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit OpenAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override { this->cover_->open(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CloseAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit CloseAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override { this->cover_->close(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class StopAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit StopAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override { this->cover_->stop(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ControlAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->cover_->make_call();
|
||||
if (this->stop_.has_value())
|
||||
call.set_stop(this->stop_.value(x...));
|
||||
if (this->position_.has_value())
|
||||
call.set_position(this->position_.value(x...));
|
||||
if (this->tilt_.has_value())
|
||||
call.set_tilt(this->tilt_.value(x...));
|
||||
call.perform();
|
||||
}
|
||||
|
||||
TEMPLATABLE_VALUE(bool, stop)
|
||||
TEMPLATABLE_VALUE(float, position)
|
||||
TEMPLATABLE_VALUE(float, tilt)
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
||||
public:
|
||||
CoverPublishAction(Cover *cover) : cover_(cover) {}
|
||||
void play(Ts... x) override {
|
||||
if (this->position_.has_value())
|
||||
this->cover_->position = this->position_.value(x...);
|
||||
if (this->tilt_.has_value())
|
||||
this->cover_->tilt = this->tilt_.value(x...);
|
||||
if (this->current_operation_.has_value())
|
||||
this->cover_->current_operation = this->current_operation_.value(x...);
|
||||
this->cover_->publish_state();
|
||||
}
|
||||
|
||||
TEMPLATABLE_VALUE(float, position)
|
||||
TEMPLATABLE_VALUE(float, tilt)
|
||||
TEMPLATABLE_VALUE(CoverOperation, current_operation)
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CoverIsOpenCondition : public Condition<Ts...> {
|
||||
public:
|
||||
CoverIsOpenCondition(Cover *cover) : cover_(cover) {}
|
||||
bool check(Ts... x) override { return this->cover_->is_fully_open(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
template<typename... Ts> class CoverIsClosedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
CoverIsClosedCondition(Cover *cover) : cover_(cover) {}
|
||||
bool check(Ts... x) override { return this->cover_->is_fully_closed(); }
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
214
esphome/components/cover/cover.cpp
Normal file
214
esphome/components/cover/cover.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
#include "cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
static const char *TAG = "cover";
|
||||
|
||||
const float COVER_OPEN = 1.0f;
|
||||
const float COVER_CLOSED = 0.0f;
|
||||
|
||||
const char *cover_command_to_str(float pos) {
|
||||
if (pos == COVER_OPEN) {
|
||||
return "OPEN";
|
||||
} else if (pos == COVER_CLOSED) {
|
||||
return "CLOSE";
|
||||
} else {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
const char *cover_operation_to_str(CoverOperation op) {
|
||||
switch (op) {
|
||||
case COVER_OPERATION_IDLE:
|
||||
return "IDLE";
|
||||
case COVER_OPERATION_OPENING:
|
||||
return "OPENING";
|
||||
case COVER_OPERATION_CLOSING:
|
||||
return "CLOSING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
Cover::Cover(const std::string &name) : Nameable(name), position{COVER_OPEN} {}
|
||||
|
||||
uint32_t Cover::hash_base() { return 1727367479UL; }
|
||||
|
||||
CoverCall::CoverCall(Cover *parent) : parent_(parent) {}
|
||||
CoverCall &CoverCall::set_command(const char *command) {
|
||||
if (strcasecmp(command, "OPEN") == 0) {
|
||||
this->set_command_open();
|
||||
} else if (strcasecmp(command, "CLOSE") == 0) {
|
||||
this->set_command_close();
|
||||
} else if (strcasecmp(command, "STOP") == 0) {
|
||||
this->set_command_stop();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_command_open() {
|
||||
this->position_ = COVER_OPEN;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_command_close() {
|
||||
this->position_ = COVER_CLOSED;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_command_stop() {
|
||||
this->stop_ = true;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_position(float position) {
|
||||
this->position_ = position;
|
||||
return *this;
|
||||
}
|
||||
CoverCall &CoverCall::set_tilt(float tilt) {
|
||||
this->tilt_ = tilt;
|
||||
return *this;
|
||||
}
|
||||
void CoverCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
auto traits = this->parent_->get_traits();
|
||||
this->validate_();
|
||||
if (this->stop_) {
|
||||
ESP_LOGD(TAG, " Command: STOP");
|
||||
}
|
||||
if (this->position_.has_value()) {
|
||||
if (traits.get_supports_position()) {
|
||||
ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f);
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Command: %s", cover_command_to_str(*this->position_));
|
||||
}
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
const optional<float> &CoverCall::get_position() const { return this->position_; }
|
||||
const optional<float> &CoverCall::get_tilt() const { return this->tilt_; }
|
||||
void CoverCall::validate_() {
|
||||
auto traits = this->parent_->get_traits();
|
||||
if (this->position_.has_value()) {
|
||||
auto pos = *this->position_;
|
||||
if (!traits.get_supports_position() && pos != COVER_OPEN && pos != COVER_CLOSED) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support setting position!", this->parent_->get_name().c_str());
|
||||
this->position_.reset();
|
||||
} else if (pos < 0.0f || pos > 1.0f) {
|
||||
ESP_LOGW(TAG, "'%s' - Position %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), pos);
|
||||
this->position_ = clamp(pos, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
auto tilt = *this->tilt_;
|
||||
if (!traits.get_supports_tilt()) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support tilt!", this->parent_->get_name().c_str());
|
||||
this->tilt_.reset();
|
||||
} else if (tilt < 0.0f || tilt > 1.0f) {
|
||||
ESP_LOGW(TAG, "'%s' - Tilt %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), tilt);
|
||||
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (this->stop_) {
|
||||
if (this->position_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set position when stopping a cover!");
|
||||
this->position_.reset();
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!");
|
||||
this->tilt_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
CoverCall &CoverCall::set_stop(bool stop) {
|
||||
this->stop_ = stop;
|
||||
return *this;
|
||||
}
|
||||
bool CoverCall::get_stop() const { return this->stop_; }
|
||||
void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; }
|
||||
CoverCall Cover::make_call() { return {this}; }
|
||||
void Cover::open() {
|
||||
auto call = this->make_call();
|
||||
call.set_command_open();
|
||||
call.perform();
|
||||
}
|
||||
void Cover::close() {
|
||||
auto call = this->make_call();
|
||||
call.set_command_close();
|
||||
call.perform();
|
||||
}
|
||||
void Cover::stop() {
|
||||
auto call = this->make_call();
|
||||
call.set_command_stop();
|
||||
call.perform();
|
||||
}
|
||||
void Cover::add_on_state_callback(std::function<void()> &&f) { this->state_callback_.add(std::move(f)); }
|
||||
void Cover::publish_state(bool save) {
|
||||
this->position = clamp(this->position, 0.0f, 1.0f);
|
||||
this->tilt = clamp(this->tilt, 0.0f, 1.0f);
|
||||
|
||||
ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
if (traits.get_supports_position()) {
|
||||
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
|
||||
} else {
|
||||
if (this->position == COVER_OPEN) {
|
||||
ESP_LOGD(TAG, " State: OPEN");
|
||||
} else if (this->position == COVER_CLOSED) {
|
||||
ESP_LOGD(TAG, " State: CLOSED");
|
||||
} else {
|
||||
ESP_LOGD(TAG, " State: UNKNOWN");
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_tilt()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f);
|
||||
}
|
||||
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
|
||||
|
||||
this->state_callback_.call();
|
||||
|
||||
if (save) {
|
||||
CoverRestoreState restore{};
|
||||
memset(&restore, 0, sizeof(restore));
|
||||
restore.position = this->position;
|
||||
if (traits.get_supports_tilt()) {
|
||||
restore.tilt = this->tilt;
|
||||
}
|
||||
this->rtc_.save(&restore);
|
||||
}
|
||||
}
|
||||
optional<CoverRestoreState> Cover::restore_state_() {
|
||||
this->rtc_ = global_preferences.make_preference<CoverRestoreState>(this->get_object_id_hash());
|
||||
CoverRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
return recovered;
|
||||
}
|
||||
Cover::Cover() : Cover("") {}
|
||||
std::string Cover::get_device_class() {
|
||||
if (this->device_class_override_.has_value())
|
||||
return *this->device_class_override_;
|
||||
return this->device_class();
|
||||
}
|
||||
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
|
||||
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
|
||||
std::string Cover::device_class() { return ""; }
|
||||
|
||||
CoverCall CoverRestoreState::to_call(Cover *cover) {
|
||||
auto call = cover->make_call();
|
||||
auto traits = cover->get_traits();
|
||||
call.set_position(this->position);
|
||||
if (traits.get_supports_tilt())
|
||||
call.set_tilt(this->tilt);
|
||||
return call;
|
||||
}
|
||||
void CoverRestoreState::apply(Cover *cover) {
|
||||
cover->position = this->position;
|
||||
cover->tilt = this->tilt;
|
||||
cover->publish_state();
|
||||
}
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
179
esphome/components/cover/cover.h
Normal file
179
esphome/components/cover/cover.h
Normal file
@@ -0,0 +1,179 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "cover_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
const extern float COVER_OPEN;
|
||||
const extern float COVER_CLOSED;
|
||||
|
||||
#define LOG_COVER(prefix, type, obj) \
|
||||
if (obj != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, prefix type " '%s'", obj->get_name().c_str()); \
|
||||
auto traits_ = obj->get_traits(); \
|
||||
if (traits_.get_is_assumed_state()) { \
|
||||
ESP_LOGCONFIG(TAG, prefix " Assumed State: YES"); \
|
||||
} \
|
||||
if (!obj->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, prefix " Device Class: '%s'", obj->get_device_class().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
class Cover;
|
||||
|
||||
class CoverCall {
|
||||
public:
|
||||
CoverCall(Cover *parent);
|
||||
|
||||
/// Set the command as a string, "STOP", "OPEN", "CLOSE".
|
||||
CoverCall &set_command(const char *command);
|
||||
/// Set the command to open the cover.
|
||||
CoverCall &set_command_open();
|
||||
/// Set the command to close the cover.
|
||||
CoverCall &set_command_close();
|
||||
/// Set the command to stop the cover.
|
||||
CoverCall &set_command_stop();
|
||||
/// Set the call to a certain target position.
|
||||
CoverCall &set_position(float position);
|
||||
/// Set the call to a certain target tilt.
|
||||
CoverCall &set_tilt(float tilt);
|
||||
/// Set whether this cover call should stop the cover.
|
||||
CoverCall &set_stop(bool stop);
|
||||
|
||||
/// Perform the cover call.
|
||||
void perform();
|
||||
|
||||
const optional<float> &get_position() const;
|
||||
bool get_stop() const;
|
||||
const optional<float> &get_tilt() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
Cover *parent_;
|
||||
bool stop_{false};
|
||||
optional<float> position_{};
|
||||
optional<float> tilt_{};
|
||||
};
|
||||
|
||||
/// Struct used to store the restored state of a cover
|
||||
struct CoverRestoreState {
|
||||
float position;
|
||||
float tilt;
|
||||
|
||||
/// Convert this struct to a cover call that can be performed.
|
||||
CoverCall to_call(Cover *cover);
|
||||
/// Apply these settings to the cover
|
||||
void apply(Cover *cover);
|
||||
} __attribute__((packed));
|
||||
|
||||
/// Enum encoding the current operation of a cover.
|
||||
enum CoverOperation : uint8_t {
|
||||
/// The cover is currently idle (not moving)
|
||||
COVER_OPERATION_IDLE = 0,
|
||||
/// The cover is currently opening.
|
||||
COVER_OPERATION_OPENING,
|
||||
/// The cover is currently closing.
|
||||
COVER_OPERATION_CLOSING,
|
||||
};
|
||||
|
||||
const char *cover_operation_to_str(CoverOperation op);
|
||||
|
||||
/** Base class for all cover devices.
|
||||
*
|
||||
* Covers currently have three properties:
|
||||
* - position - The current position of the cover from 0.0 (fully closed) to 1.0 (fully open).
|
||||
* For covers with only binary OPEN/CLOSED position this will always be either 0.0 or 1.0
|
||||
* - tilt - The tilt value of the cover from 0.0 (closed) to 1.0 (closed)
|
||||
* - current_operation - The operation the cover is currently performing, this can
|
||||
* be one of IDLE, OPENING and CLOSING.
|
||||
*
|
||||
* For users: All cover operations must be performed over the .make_call() interface.
|
||||
* To command a cover, use .make_call() to create a call object, set all properties
|
||||
* you wish to set, and activate the command with .perform().
|
||||
* For reading out the current values of the cover, use the public .position, .tilt etc
|
||||
* properties (these are read-only for users)
|
||||
*
|
||||
* For integrations: Integrations must implement two methods: control() and get_traits().
|
||||
* Control will be called with the arguments supplied by the user and should be used
|
||||
* to control all values of the cover. Also implement get_traits() to return what operations
|
||||
* the cover supports.
|
||||
*/
|
||||
class Cover : public Nameable {
|
||||
public:
|
||||
explicit Cover();
|
||||
explicit Cover(const std::string &name);
|
||||
|
||||
/// The current operation of the cover (idle, opening, closing).
|
||||
CoverOperation current_operation{COVER_OPERATION_IDLE};
|
||||
union {
|
||||
/** The position of the cover from 0.0 (fully closed) to 1.0 (fully open).
|
||||
*
|
||||
* For binary covers this is always equals to 0.0 or 1.0 (see also COVER_OPEN and
|
||||
* COVER_CLOSED constants).
|
||||
*/
|
||||
float position;
|
||||
ESPDEPRECATED("<cover>.state is deprecated, please use .position instead") float state;
|
||||
};
|
||||
/// The current tilt value of the cover from 0.0 to 1.0.
|
||||
float tilt{COVER_OPEN};
|
||||
|
||||
/// Construct a new cover call used to control the cover.
|
||||
CoverCall make_call();
|
||||
/** Open the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
void open();
|
||||
/** Close the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
void close();
|
||||
/** Stop the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
void add_on_state_callback(std::function<void()> &&f);
|
||||
|
||||
/** Publish the current state of the cover.
|
||||
*
|
||||
* First set the .position, .tilt, etc values and then call this method
|
||||
* to publish the state of the cover.
|
||||
*
|
||||
* @param save Whether to save the updated values in RTC area.
|
||||
*/
|
||||
void publish_state(bool save = true);
|
||||
|
||||
virtual CoverTraits get_traits() = 0;
|
||||
void set_device_class(const std::string &device_class);
|
||||
std::string get_device_class();
|
||||
|
||||
/// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0
|
||||
bool is_fully_open() const;
|
||||
/// Helper method to check if the cover is fully closed. Equivalent to comparing .position against 0.0
|
||||
bool is_fully_closed() const;
|
||||
|
||||
protected:
|
||||
friend CoverCall;
|
||||
|
||||
virtual void control(const CoverCall &call) = 0;
|
||||
virtual std::string device_class();
|
||||
|
||||
optional<CoverRestoreState> restore_state_();
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
optional<std::string> device_class_override_{};
|
||||
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
24
esphome/components/cover/cover_traits.h
Normal file
24
esphome/components/cover/cover_traits.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
|
||||
class CoverTraits {
|
||||
public:
|
||||
CoverTraits() = default;
|
||||
|
||||
bool get_is_assumed_state() const { return this->is_assumed_state_; }
|
||||
void set_is_assumed_state(bool is_assumed_state) { this->is_assumed_state_ = is_assumed_state; }
|
||||
bool get_supports_position() const { return this->supports_position_; }
|
||||
void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; }
|
||||
bool get_supports_tilt() const { return this->supports_tilt_; }
|
||||
void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
|
||||
|
||||
protected:
|
||||
bool is_assumed_state_{false};
|
||||
bool supports_position_{false};
|
||||
bool supports_tilt_{false};
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
0
esphome/components/cse7766/__init__.py
Normal file
0
esphome/components/cse7766/__init__.py
Normal file
178
esphome/components/cse7766/cse7766.cpp
Normal file
178
esphome/components/cse7766/cse7766.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "cse7766.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7766 {
|
||||
|
||||
static const char *TAG = "cse7766";
|
||||
|
||||
void CSE7766Component::loop() {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_transmission_ >= 500) {
|
||||
// last transmission too long ago. Reset RX index.
|
||||
this->raw_data_index_ = 0;
|
||||
}
|
||||
|
||||
if (this->available() == 0)
|
||||
return;
|
||||
|
||||
this->last_transmission_ = now;
|
||||
while (this->available() != 0) {
|
||||
this->read_byte(&this->raw_data_[this->raw_data_index_]);
|
||||
if (!this->check_byte_()) {
|
||||
this->raw_data_index_ = 0;
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
if (this->raw_data_index_ == 23) {
|
||||
this->parse_data_();
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
this->raw_data_index_ = (this->raw_data_index_ + 1) % 24;
|
||||
}
|
||||
}
|
||||
float CSE7766Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
bool CSE7766Component::check_byte_() {
|
||||
uint8_t index = this->raw_data_index_;
|
||||
uint8_t byte = this->raw_data_[index];
|
||||
if (index == 0) {
|
||||
return !((byte != 0x55) && ((byte & 0xF0) != 0xF0) && (byte != 0xAA));
|
||||
}
|
||||
|
||||
if (index == 1) {
|
||||
if (byte != 0x5A) {
|
||||
ESP_LOGV(TAG, "Invalid Header 2 Start: 0x%02X!", byte);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (index == 23) {
|
||||
uint8_t checksum = 0;
|
||||
for (uint8_t i = 2; i < 23; i++)
|
||||
checksum += this->raw_data_[i];
|
||||
|
||||
if (checksum != this->raw_data_[23]) {
|
||||
ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, this->raw_data_[23]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void CSE7766Component::parse_data_() {
|
||||
ESP_LOGVV(TAG, "CSE7766 Data: ");
|
||||
for (uint8_t i = 0; i < 23; i++) {
|
||||
ESP_LOGVV(TAG, " i=%u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i, BYTE_TO_BINARY(this->raw_data_[i]),
|
||||
this->raw_data_[i]);
|
||||
}
|
||||
|
||||
uint8_t header1 = this->raw_data_[0];
|
||||
if (header1 == 0xAA) {
|
||||
ESP_LOGW(TAG, "CSE7766 not calibrated!");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((header1 & 0xF0) == 0xF0 && ((header1 >> 0) & 1) == 1) {
|
||||
ESP_LOGW(TAG, "CSE7766 reports abnormal hardware: (0x%02X)", header1);
|
||||
ESP_LOGW(TAG, " Coefficient storage area is abnormal.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t voltage_calib = this->get_24_bit_uint_(2);
|
||||
uint32_t voltage_cycle = this->get_24_bit_uint_(5);
|
||||
uint32_t current_calib = this->get_24_bit_uint_(8);
|
||||
uint32_t current_cycle = this->get_24_bit_uint_(11);
|
||||
uint32_t power_calib = this->get_24_bit_uint_(14);
|
||||
uint32_t power_cycle = this->get_24_bit_uint_(17);
|
||||
|
||||
uint8_t adj = this->raw_data_[20];
|
||||
|
||||
bool power_ok = true;
|
||||
bool voltage_ok = true;
|
||||
bool current_ok = true;
|
||||
|
||||
if (header1 > 0xF0) {
|
||||
// ESP_LOGV(TAG, "CSE7766 reports abnormal hardware: (0x%02X)", byte);
|
||||
if ((header1 >> 3) & 1) {
|
||||
ESP_LOGV(TAG, " Voltage cycle exceeds range.");
|
||||
voltage_ok = false;
|
||||
}
|
||||
if ((header1 >> 2) & 1) {
|
||||
ESP_LOGV(TAG, " Current cycle exceeds range.");
|
||||
current_ok = false;
|
||||
}
|
||||
if ((header1 >> 1) & 1) {
|
||||
ESP_LOGV(TAG, " Power cycle exceeds range.");
|
||||
power_ok = false;
|
||||
}
|
||||
if ((header1 >> 0) & 1) {
|
||||
ESP_LOGV(TAG, " Coefficient storage area is abnormal.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((adj & 0x40) == 0x40 && voltage_ok && current_ok) {
|
||||
// voltage cycle of serial port outputted is a complete cycle;
|
||||
this->voltage_acc_ += voltage_calib / float(voltage_cycle);
|
||||
this->voltage_counts_ += 1;
|
||||
}
|
||||
|
||||
float power = 0;
|
||||
if ((adj & 0x10) == 0x10 && voltage_ok && current_ok && power_ok) {
|
||||
// power cycle of serial port outputted is a complete cycle;
|
||||
power = power_calib / float(power_cycle);
|
||||
this->power_acc_ += power;
|
||||
this->power_counts_ += 1;
|
||||
}
|
||||
|
||||
if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) {
|
||||
// indicates current cycle of serial port outputted is a complete cycle;
|
||||
this->current_acc_ += current_calib / float(current_cycle);
|
||||
this->current_counts_ += 1;
|
||||
}
|
||||
}
|
||||
void CSE7766Component::update() {
|
||||
float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0;
|
||||
float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0;
|
||||
float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0;
|
||||
|
||||
ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_,
|
||||
this->power_acc_);
|
||||
ESP_LOGV(TAG, "Got voltage_counts=%d current_counts=%d power_counts=%d", this->voltage_counts_, this->current_counts_,
|
||||
this->power_counts_);
|
||||
ESP_LOGD(TAG, "Got voltage=%.1fV current=%.1fA power=%.1fW", voltage, current, power);
|
||||
|
||||
if (this->voltage_sensor_ != nullptr)
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
if (this->current_sensor_ != nullptr)
|
||||
this->current_sensor_->publish_state(current);
|
||||
if (this->power_sensor_ != nullptr)
|
||||
this->power_sensor_->publish_state(power);
|
||||
|
||||
this->voltage_acc_ = 0.0f;
|
||||
this->current_acc_ = 0.0f;
|
||||
this->power_acc_ = 0.0f;
|
||||
this->voltage_counts_ = 0;
|
||||
this->power_counts_ = 0;
|
||||
this->current_counts_ = 0;
|
||||
}
|
||||
|
||||
uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) {
|
||||
return (uint32_t(this->raw_data_[start_index]) << 16) | (uint32_t(this->raw_data_[start_index + 1]) << 8) |
|
||||
uint32_t(this->raw_data_[start_index + 2]);
|
||||
}
|
||||
|
||||
void CSE7766Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CSE7766:");
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||
}
|
||||
|
||||
} // namespace cse7766
|
||||
} // namespace esphome
|
||||
41
esphome/components/cse7766/cse7766.h
Normal file
41
esphome/components/cse7766/cse7766.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7766 {
|
||||
|
||||
class CSE7766Component : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
bool check_byte_();
|
||||
void parse_data_();
|
||||
uint32_t get_24_bit_uint_(uint8_t start_index);
|
||||
|
||||
uint8_t raw_data_[24];
|
||||
uint8_t raw_data_index_{0};
|
||||
uint32_t last_transmission_{0};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
float voltage_acc_{0.0f};
|
||||
float current_acc_{0.0f};
|
||||
float power_acc_{0.0f};
|
||||
uint32_t voltage_counts_{0};
|
||||
uint32_t current_counts_{0};
|
||||
uint32_t power_counts_{0};
|
||||
};
|
||||
|
||||
} // namespace cse7766
|
||||
} // namespace esphome
|
||||
37
esphome/components/cse7766/sensor.py
Normal file
37
esphome/components/cse7766/sensor.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \
|
||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
cse7766_ns = cg.esphome_ns.namespace('cse7766')
|
||||
CSE7766Component = cse7766_ns.class_('CSE7766Component', cg.PollingComponent, uart.UARTDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CSE7766Component),
|
||||
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield uart.register_uart_device(var, config)
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
0
esphome/components/ct_clamp/__init__.py
Normal file
0
esphome/components/ct_clamp/__init__.py
Normal file
67
esphome/components/ct_clamp/ct_clamp_sensor.cpp
Normal file
67
esphome/components/ct_clamp/ct_clamp_sensor.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "ct_clamp_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace ct_clamp {
|
||||
|
||||
static const char *TAG = "ct_clamp";
|
||||
|
||||
void CTClampSensor::dump_config() {
|
||||
LOG_SENSOR("", "CT Clamp Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Sample Duration: %.2fs", this->sample_duration_ / 1e3f);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void CTClampSensor::update() {
|
||||
// Update only starts the sampling phase, in loop() the actual sampling is happening.
|
||||
|
||||
// Request a high loop() execution interval during sampling phase.
|
||||
this->high_freq_.start();
|
||||
|
||||
// Set timeout for ending sampling phase
|
||||
this->set_timeout("read", this->sample_duration_, [this]() {
|
||||
this->is_sampling_ = false;
|
||||
this->high_freq_.stop();
|
||||
|
||||
if (this->num_samples_ == 0) {
|
||||
// Shouldn't happen, but let's not crash if it does.
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
float raw = this->sample_sum_ / this->num_samples_;
|
||||
float irms = std::sqrt(raw);
|
||||
ESP_LOGD(TAG, "'%s' - Raw Value: %.2fA", this->name_.c_str(), irms);
|
||||
this->publish_state(irms);
|
||||
});
|
||||
|
||||
// Set sampling values
|
||||
this->is_sampling_ = true;
|
||||
this->num_samples_ = 0;
|
||||
this->sample_sum_ = 0.0f;
|
||||
}
|
||||
|
||||
void CTClampSensor::loop() {
|
||||
if (!this->is_sampling_)
|
||||
return;
|
||||
|
||||
// Perform a single sample
|
||||
float value = this->source_->sample();
|
||||
|
||||
// Adjust DC offset via low pass filter (exponential moving average)
|
||||
const float alpha = 0.001f;
|
||||
this->offset_ = this->offset_ * (1 - alpha) + value * alpha;
|
||||
|
||||
// Filtered value centered around the mid-point (0V)
|
||||
float filtered = value - this->offset_;
|
||||
|
||||
// IRMS is sqrt(∑v_i²)
|
||||
float sq = filtered * filtered;
|
||||
this->sample_sum_ += sq;
|
||||
this->num_samples_++;
|
||||
}
|
||||
|
||||
} // namespace ct_clamp
|
||||
} // namespace esphome
|
||||
46
esphome/components/ct_clamp/ct_clamp_sensor.h
Normal file
46
esphome/components/ct_clamp/ct_clamp_sensor.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ct_clamp {
|
||||
|
||||
class CTClampSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
void update() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void set_sample_duration(uint32_t sample_duration) { sample_duration_ = sample_duration; }
|
||||
void set_source(voltage_sampler::VoltageSampler *source) { source_ = source; }
|
||||
|
||||
protected:
|
||||
/// High Frequency loop() requester used during sampling phase.
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
|
||||
/// Duration in ms of the sampling phase.
|
||||
uint32_t sample_duration_;
|
||||
/// The sampling source to read values from.
|
||||
voltage_sampler::VoltageSampler *source_;
|
||||
|
||||
/** The DC offset of the circuit.
|
||||
*
|
||||
* Diagram: https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino
|
||||
*
|
||||
* This is automatically calculated with an exponential moving average/digital low pass filter.
|
||||
*
|
||||
* 0.5 is a good initial approximation to start with for most ESP8266 setups.
|
||||
*/
|
||||
float offset_ = 0.5f;
|
||||
|
||||
float sample_sum_ = 0.0f;
|
||||
uint32_t num_samples_ = 0;
|
||||
bool is_sampling_ = false;
|
||||
};
|
||||
|
||||
} // namespace ct_clamp
|
||||
} // namespace esphome
|
||||
27
esphome/components/ct_clamp/sensor.py
Normal file
27
esphome/components/ct_clamp/sensor.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE
|
||||
|
||||
AUTO_LOAD = ['voltage_sampler']
|
||||
|
||||
CONF_SAMPLE_DURATION = 'sample_duration'
|
||||
|
||||
ct_clamp_ns = cg.esphome_ns.namespace('ct_clamp')
|
||||
CTClampSensor = ct_clamp_ns.class_('CTClampSensor', sensor.Sensor, cg.PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2).extend({
|
||||
cv.GenerateID(): cv.declare_id(CTClampSensor),
|
||||
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
|
||||
cv.Optional(CONF_SAMPLE_DURATION, default='200ms'): cv.positive_time_period_milliseconds,
|
||||
}).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)
|
||||
|
||||
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_source(sens))
|
||||
cg.add(var.set_sample_duration(config[CONF_SAMPLE_DURATION]))
|
||||
3
esphome/components/custom/__init__.py
Normal file
3
esphome/components/custom/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
custom_ns = cg.esphome_ns.namespace('custom')
|
||||
24
esphome/components/custom/binary_sensor/__init__.py
Normal file
24
esphome/components/custom/binary_sensor/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA
|
||||
from .. import custom_ns
|
||||
|
||||
CustomBinarySensorConstructor = custom_ns.class_('CustomBinarySensorConstructor')
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
template_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr))
|
||||
|
||||
rhs = CustomBinarySensorConstructor(template_)
|
||||
custom = cg.variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_BINARY_SENSORS]):
|
||||
rhs = custom.Pget_binary_sensor(i)
|
||||
yield binary_sensor.register_binary_sensor(rhs, conf)
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "custom_binary_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
static const char *TAG = "custom.binary_sensor";
|
||||
|
||||
void CustomBinarySensorConstructor::dump_config() {
|
||||
for (auto *child : this->binary_sensors_) {
|
||||
LOG_BINARY_SENSOR("", "Custom Binary Sensor", child);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
class CustomBinarySensorConstructor : public Component {
|
||||
public:
|
||||
CustomBinarySensorConstructor(const std::function<std::vector<binary_sensor::BinarySensor *>()> &init) {
|
||||
this->binary_sensors_ = init();
|
||||
}
|
||||
|
||||
binary_sensor::BinarySensor *get_binary_sensor(int i) { return this->binary_sensors_[i]; }
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
std::vector<binary_sensor::BinarySensor *> binary_sensors_;
|
||||
};
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
26
esphome/components/custom/climate/__init__.py
Normal file
26
esphome/components/custom/climate/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA
|
||||
from .. import custom_ns
|
||||
|
||||
CustomClimateConstructor = custom_ns.class_('CustomClimateConstructor')
|
||||
CONF_CLIMATES = 'climates'
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomClimateConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
template_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [],
|
||||
return_type=cg.std_vector.template(climate.Climate.operator('ptr')))
|
||||
|
||||
rhs = CustomClimateConstructor(template_)
|
||||
custom = cg.variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_CLIMATES]):
|
||||
rhs = custom.Pget_climate(i)
|
||||
yield climate.register_climate(rhs, conf)
|
||||
20
esphome/components/custom/climate/custom_climate.h
Normal file
20
esphome/components/custom/climate/custom_climate.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
class CustomClimateConstructor {
|
||||
public:
|
||||
CustomClimateConstructor(const std::function<std::vector<climate::Climate *>()> &init) { this->climates_ = init(); }
|
||||
|
||||
climate::Climate *get_climate(int i) { return this->climates_[i]; }
|
||||
|
||||
protected:
|
||||
std::vector<climate::Climate *> climates_;
|
||||
};
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
26
esphome/components/custom/cover/__init__.py
Normal file
26
esphome/components/custom/cover/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import cover
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA
|
||||
from .. import custom_ns
|
||||
|
||||
CustomCoverConstructor = custom_ns.class_('CustomCoverConstructor')
|
||||
CONF_COVERS = 'covers'
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomCoverConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
template_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [],
|
||||
return_type=cg.std_vector.template(cover.Cover.operator('ptr')))
|
||||
|
||||
rhs = CustomCoverConstructor(template_)
|
||||
custom = cg.variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_COVERS]):
|
||||
rhs = custom.Pget_cover(i)
|
||||
yield cover.register_cover(rhs, conf)
|
||||
19
esphome/components/custom/cover/custom_cover.h
Normal file
19
esphome/components/custom/cover/custom_cover.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/cover/cover.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
class CustomCoverConstructor {
|
||||
public:
|
||||
CustomCoverConstructor(const std::function<std::vector<cover::Cover *>()> &init) { this->covers_ = init(); }
|
||||
|
||||
cover::Cover *get_cover(int i) { return this->covers_[i]; }
|
||||
|
||||
protected:
|
||||
std::vector<cover::Cover *> covers_;
|
||||
};
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
26
esphome/components/custom/light/__init__.py
Normal file
26
esphome/components/custom/light/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import light
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA
|
||||
from .. import custom_ns
|
||||
|
||||
CustomLightOutputConstructor = custom_ns.class_('CustomLightOutputConstructor')
|
||||
CONF_LIGHTS = 'lights'
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_LIGHTS): cv.ensure_list(light.RGB_LIGHT_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
template_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [],
|
||||
return_type=cg.std_vector.template(light.LightOutput.operator('ptr')))
|
||||
|
||||
rhs = CustomLightOutputConstructor(template_)
|
||||
custom = cg.variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_LIGHTS]):
|
||||
rhs = custom.Pget_light(i)
|
||||
yield light.register_light(rhs, conf)
|
||||
22
esphome/components/custom/light/custom_light_output.h
Normal file
22
esphome/components/custom/light/custom_light_output.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
class CustomLightOutputConstructor {
|
||||
public:
|
||||
CustomLightOutputConstructor(const std::function<std::vector<light::LightOutput *>()> &init) {
|
||||
this->outputs_ = init();
|
||||
}
|
||||
|
||||
light::LightOutput *get_light(int i) { return this->outputs_[i]; }
|
||||
|
||||
protected:
|
||||
std::vector<light::LightOutput *> outputs_;
|
||||
};
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
48
esphome/components/custom/output/__init__.py
Normal file
48
esphome/components/custom/output/__init__.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE
|
||||
from .. import custom_ns
|
||||
|
||||
CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor')
|
||||
CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor')
|
||||
|
||||
CONF_BINARY = 'binary'
|
||||
CONF_FLOAT = 'float'
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema({
|
||||
CONF_BINARY: cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_OUTPUTS):
|
||||
cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(output.BinaryOutput),
|
||||
})),
|
||||
}),
|
||||
CONF_FLOAT: cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_OUTPUTS):
|
||||
cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(output.FloatOutput),
|
||||
})),
|
||||
})
|
||||
}, lower=True)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
type = config[CONF_TYPE]
|
||||
if type == 'binary':
|
||||
ret_type = output.BinaryOutputPtr
|
||||
klass = CustomBinaryOutputConstructor
|
||||
else:
|
||||
ret_type = output.FloatOutputPtr
|
||||
klass = CustomFloatOutputConstructor
|
||||
template_ = yield cg.process_lambda(config[CONF_LAMBDA], [],
|
||||
return_type=cg.std_vector.template(ret_type))
|
||||
|
||||
rhs = klass(template_)
|
||||
custom = cg.variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_OUTPUTS]):
|
||||
out = cg.Pvariable(conf[CONF_ID], custom.get_output(i))
|
||||
yield output.register_output(out, conf)
|
||||
31
esphome/components/custom/output/custom_output.h
Normal file
31
esphome/components/custom/output/custom_output.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
class CustomBinaryOutputConstructor {
|
||||
public:
|
||||
CustomBinaryOutputConstructor(std::function<std::vector<output::BinaryOutput *>()> init) { this->outputs_ = init(); }
|
||||
|
||||
output::BinaryOutput *get_output(int i) { return this->outputs_[i]; }
|
||||
|
||||
protected:
|
||||
std::vector<output::BinaryOutput *> outputs_;
|
||||
};
|
||||
|
||||
class CustomFloatOutputConstructor {
|
||||
public:
|
||||
CustomFloatOutputConstructor(std::function<std::vector<output::FloatOutput *>()> init) { this->outputs_ = init(); }
|
||||
|
||||
output::FloatOutput *get_output(int i) { return this->outputs_[i]; }
|
||||
|
||||
protected:
|
||||
std::vector<output::FloatOutput *> outputs_;
|
||||
};
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
24
esphome/components/custom/sensor/__init__.py
Normal file
24
esphome/components/custom/sensor/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SENSORS
|
||||
from .. import custom_ns
|
||||
|
||||
CustomSensorConstructor = custom_ns.class_('CustomSensorConstructor')
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomSensorConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
template_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.std_vector.template(sensor.SensorPtr))
|
||||
|
||||
rhs = CustomSensorConstructor(template_)
|
||||
var = cg.variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_SENSORS]):
|
||||
sens = cg.Pvariable(conf[CONF_ID], var.get_sensor(i))
|
||||
yield sensor.register_sensor(sens, conf)
|
||||
16
esphome/components/custom/sensor/custom_sensor.cpp
Normal file
16
esphome/components/custom/sensor/custom_sensor.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "custom_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
static const char *TAG = "custom.sensor";
|
||||
|
||||
void CustomSensorConstructor::dump_config() {
|
||||
for (auto *child : this->sensors_) {
|
||||
LOG_SENSOR("", "Custom Sensor", child);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
22
esphome/components/custom/sensor/custom_sensor.h
Normal file
22
esphome/components/custom/sensor/custom_sensor.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
class CustomSensorConstructor : public Component {
|
||||
public:
|
||||
CustomSensorConstructor(const std::function<std::vector<sensor::Sensor *>()> &init) { this->sensors_ = init(); }
|
||||
|
||||
sensor::Sensor *get_sensor(int i) { return this->sensors_[i]; }
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
std::vector<sensor::Sensor *> sensors_;
|
||||
};
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
27
esphome/components/custom/switch/__init__.py
Normal file
27
esphome/components/custom/switch/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import switch
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_SWITCHES
|
||||
from .. import custom_ns
|
||||
|
||||
CustomSwitchConstructor = custom_ns.class_('CustomSwitchConstructor')
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomSwitchConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_SWITCHES):
|
||||
cv.ensure_list(switch.SWITCH_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(switch.Switch),
|
||||
})),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
template_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.std_vector.template(switch.SwitchPtr))
|
||||
|
||||
rhs = CustomSwitchConstructor(template_)
|
||||
var = cg.variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config[CONF_SWITCHES]):
|
||||
switch_ = cg.Pvariable(conf[CONF_ID], var.get_switch(i))
|
||||
yield switch.register_switch(switch_, conf)
|
||||
16
esphome/components/custom/switch/custom_switch.cpp
Normal file
16
esphome/components/custom/switch/custom_switch.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "custom_switch.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
static const char *TAG = "custom.switch";
|
||||
|
||||
void CustomSwitchConstructor::dump_config() {
|
||||
for (auto *child : this->switches_) {
|
||||
LOG_SWITCH("", "Custom Switch", child);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
22
esphome/components/custom/switch/custom_switch.h
Normal file
22
esphome/components/custom/switch/custom_switch.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
class CustomSwitchConstructor : public Component {
|
||||
public:
|
||||
CustomSwitchConstructor(std::function<std::vector<switch_::Switch *>()> init) { this->switches_ = init(); }
|
||||
|
||||
switch_::Switch *get_switch(int i) { return this->switches_[i]; }
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
std::vector<switch_::Switch *> switches_;
|
||||
};
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
28
esphome/components/custom/text_sensor/__init__.py
Normal file
28
esphome/components/custom/text_sensor/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_TEXT_SENSORS
|
||||
from .. import custom_ns
|
||||
|
||||
CustomTextSensorConstructor = custom_ns.class_('CustomTextSensorConstructor')
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_TEXT_SENSORS):
|
||||
cv.ensure_list(text_sensor.TEXT_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
})),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
template_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.std_vector.template(text_sensor.TextSensorPtr))
|
||||
|
||||
rhs = CustomTextSensorConstructor(template_)
|
||||
var = cg.variable(config[CONF_ID], rhs)
|
||||
|
||||
for i, conf in enumerate(config[CONF_TEXT_SENSORS]):
|
||||
text = cg.Pvariable(conf[CONF_ID], var.get_text_sensor(i))
|
||||
yield text_sensor.register_text_sensor(text, conf)
|
||||
16
esphome/components/custom/text_sensor/custom_text_sensor.cpp
Normal file
16
esphome/components/custom/text_sensor/custom_text_sensor.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "custom_text_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
static const char *TAG = "custom.text_sensor";
|
||||
|
||||
void CustomTextSensorConstructor::dump_config() {
|
||||
for (auto *child : this->text_sensors_) {
|
||||
LOG_TEXT_SENSOR("", "Custom Text Sensor", child);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
24
esphome/components/custom/text_sensor/custom_text_sensor.h
Normal file
24
esphome/components/custom/text_sensor/custom_text_sensor.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
class CustomTextSensorConstructor : public Component {
|
||||
public:
|
||||
CustomTextSensorConstructor(std::function<std::vector<text_sensor::TextSensor *>()> init) {
|
||||
this->text_sensors_ = init();
|
||||
}
|
||||
|
||||
text_sensor::TextSensor *get_text_sensor(int i) { return this->text_sensors_[i]; }
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
std::vector<text_sensor::TextSensor *> text_sensors_;
|
||||
};
|
||||
|
||||
} // namespace custom
|
||||
} // namespace esphome
|
||||
26
esphome/components/custom_component/__init__.py
Normal file
26
esphome/components/custom_component/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_COMPONENTS, CONF_ID, CONF_LAMBDA
|
||||
|
||||
custom_component_ns = cg.esphome_ns.namespace('custom_component')
|
||||
CustomComponentConstructor = custom_component_ns.class_('CustomComponentConstructor')
|
||||
|
||||
MULTI_CONF = True
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(CustomComponentConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_COMPONENTS): cv.ensure_list(cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(cg.Component)
|
||||
}).extend(cv.COMPONENT_SCHEMA)),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
template_ = yield cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.std_vector.template(cg.ComponentPtr))
|
||||
|
||||
rhs = CustomComponentConstructor(template_)
|
||||
var = cg.variable(config[CONF_ID], rhs)
|
||||
for i, conf in enumerate(config.get(CONF_COMPONENTS, [])):
|
||||
comp = cg.Pvariable(conf[CONF_ID], var.get_component(i))
|
||||
yield cg.register_component(comp, conf)
|
||||
26
esphome/components/custom_component/custom_component.h
Normal file
26
esphome/components/custom_component/custom_component.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace custom_component {
|
||||
|
||||
class CustomComponentConstructor {
|
||||
public:
|
||||
CustomComponentConstructor(const std::function<std::vector<Component *>()> &init) {
|
||||
this->components_ = init();
|
||||
|
||||
for (auto *comp : this->components_) {
|
||||
App.register_component(comp);
|
||||
}
|
||||
}
|
||||
|
||||
Component *get_component(int i) { return this->components_[i]; }
|
||||
|
||||
protected:
|
||||
std::vector<Component *> components_;
|
||||
};
|
||||
|
||||
} // namespace custom_component
|
||||
} // namespace esphome
|
||||
0
esphome/components/cwww/__init__.py
Normal file
0
esphome/components/cwww/__init__.py
Normal file
41
esphome/components/cwww/cwww_light_output.h
Normal file
41
esphome/components/cwww/cwww_light_output.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cwww {
|
||||
|
||||
class CWWWLightOutput : public light::LightOutput {
|
||||
public:
|
||||
void set_cold_white(output::FloatOutput *cold_white) { cold_white_ = cold_white; }
|
||||
void set_warm_white(output::FloatOutput *warm_white) { warm_white_ = warm_white; }
|
||||
void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; }
|
||||
void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(false);
|
||||
traits.set_supports_rgb_white_value(false);
|
||||
traits.set_supports_color_temperature(true);
|
||||
traits.set_min_mireds(this->cold_white_temperature_);
|
||||
traits.set_max_mireds(this->warm_white_temperature_);
|
||||
return traits;
|
||||
}
|
||||
void write_state(light::LightState *state) override {
|
||||
float cwhite, wwhite;
|
||||
state->current_values_as_cwww(&cwhite, &wwhite);
|
||||
this->cold_white_->set_level(cwhite);
|
||||
this->warm_white_->set_level(wwhite);
|
||||
}
|
||||
|
||||
protected:
|
||||
output::FloatOutput *cold_white_;
|
||||
output::FloatOutput *warm_white_;
|
||||
float cold_white_temperature_;
|
||||
float warm_white_temperature_;
|
||||
};
|
||||
|
||||
} // namespace cwww
|
||||
} // namespace esphome
|
||||
28
esphome/components/cwww/light.py
Normal file
28
esphome/components/cwww/light.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import light, output
|
||||
from esphome.const import CONF_OUTPUT_ID, CONF_COLD_WHITE, CONF_WARM_WHITE, \
|
||||
CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE
|
||||
|
||||
cwww_ns = cg.esphome_ns.namespace('cwww')
|
||||
CWWWLightOutput = cwww_ns.class_('CWWWLightOutput', light.LightOutput)
|
||||
|
||||
CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput),
|
||||
cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
yield light.register_light(var, config)
|
||||
cwhite = yield cg.get_variable(config[CONF_COLD_WHITE])
|
||||
cg.add(var.set_cold_white(cwhite))
|
||||
cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]))
|
||||
|
||||
wwhite = yield cg.get_variable(config[CONF_WARM_WHITE])
|
||||
cg.add(var.set_warm_white(wwhite))
|
||||
cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]))
|
||||
25
esphome/components/dallas/__init__.py
Normal file
25
esphome/components/dallas/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_PIN
|
||||
|
||||
MULTI_CONF = True
|
||||
AUTO_LOAD = ['sensor']
|
||||
|
||||
CONF_ONE_WIRE_ID = 'one_wire_id'
|
||||
dallas_ns = cg.esphome_ns.namespace('dallas')
|
||||
DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent)
|
||||
ESPOneWire = dallas_ns.class_('ESPOneWire')
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
||||
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
|
||||
}).extend(cv.polling_component_schema('60s'))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_PIN])
|
||||
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
|
||||
var = cg.new_Pvariable(config[CONF_ID], one_wire)
|
||||
yield cg.register_component(var, config)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user