Compare commits
1383 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c849b9002 | ||
|
|
6a0d4cb5a9 | ||
|
|
72002ce70e | ||
|
|
c67539cf5b | ||
|
|
dcd3d2084d | ||
|
|
585bb6dac8 | ||
|
|
cac3055261 | ||
|
|
6f7e6cc944 | ||
|
|
0a841fcc50 | ||
|
|
3c64c9b0e9 | ||
|
|
34df25da39 | ||
|
|
9586fb95d1 | ||
|
|
3ee6348e41 | ||
|
|
543f2c8152 | ||
|
|
16d11be213 | ||
|
|
e49b568fd4 | ||
|
|
22ab830ff3 | ||
|
|
095d3181cd | ||
|
|
9aa14a2e83 | ||
|
|
ac15ce576b | ||
|
|
498b59e998 | ||
|
|
63c420254a | ||
|
|
765e641d08 | ||
|
|
be16d10b7d | ||
|
|
4b808611e9 | ||
|
|
7afe202e20 | ||
|
|
039810eef3 | ||
|
|
ff43b45113 | ||
|
|
7cd4c3bdd3 | ||
|
|
c12c9e97c2 | ||
|
|
b3169deda7 | ||
|
|
0ea41e2f71 | ||
|
|
3afb564a48 | ||
|
|
7ff3f752e2 | ||
|
|
d821ead92a | ||
|
|
e42ce64127 | ||
|
|
9d2b0b4e03 | ||
|
|
b5e6ae0d69 | ||
|
|
08f1eac8b2 | ||
|
|
6ed3da33a2 | ||
|
|
a9a00f139b | ||
|
|
63d8071dbd | ||
|
|
d20caa9d60 | ||
|
|
7e40d4246c | ||
|
|
5a2b14cfa4 | ||
|
|
f2d218e5ad | ||
|
|
b493d5bba5 | ||
|
|
c9055f2aef | ||
|
|
9fed7cab5f | ||
|
|
eb5c4b7c4f | ||
|
|
2ab3534a4b | ||
|
|
9816e677a6 | ||
|
|
fc01a70b65 | ||
|
|
7221337442 | ||
|
|
051a1e4772 | ||
|
|
274741a9d5 | ||
|
|
25c01adf51 | ||
|
|
bf2d54c3ef | ||
|
|
49cb8fd9d3 | ||
|
|
e536316e3d | ||
|
|
a6c46eb8e5 | ||
|
|
1a270374e0 | ||
|
|
e73eafbd88 | ||
|
|
9fc3e05b76 | ||
|
|
31c604331c | ||
|
|
3fcdaaefe0 | ||
|
|
20dd744680 | ||
|
|
e4636b99f7 | ||
|
|
10e7abb579 | ||
|
|
d3f03b7acb | ||
|
|
7e53fc9d6a | ||
|
|
0059a6de46 | ||
|
|
22e1758d5b | ||
|
|
59cdc32970 | ||
|
|
adb51cf733 | ||
|
|
d97a9bf8e8 | ||
|
|
f034472e2a | ||
|
|
ed328d2df8 | ||
|
|
1520dc8755 | ||
|
|
bf601c3126 | ||
|
|
ab48e4a466 | ||
|
|
8ef0f5b047 | ||
|
|
2c14d134be | ||
|
|
9cd21bb5a0 | ||
|
|
bd061ac2ee | ||
|
|
dd3e821857 | ||
|
|
b38b7019ea | ||
|
|
2c71ee7853 | ||
|
|
540c62061d | ||
|
|
221ef07c8b | ||
|
|
29fc7ea154 | ||
|
|
7b157aeff1 | ||
|
|
e50644edee | ||
|
|
5f619e6f01 | ||
|
|
b266fb37a3 | ||
|
|
c9caf44c2e | ||
|
|
0c87a9ad2c | ||
|
|
c680b437f5 | ||
|
|
4988349677 | ||
|
|
e35d56defe | ||
|
|
5c86f332b2 | ||
|
|
002861f13b | ||
|
|
dbec3d7c50 | ||
|
|
89cde158d6 | ||
|
|
d7b76aadb2 | ||
|
|
e09fefd389 | ||
|
|
febc485da6 | ||
|
|
2ae709c2ba | ||
|
|
0ccfdd4711 | ||
|
|
0e59243b83 | ||
|
|
01bbd04a5a | ||
|
|
a3b2d384f5 | ||
|
|
50238f8d72 | ||
|
|
704470d606 | ||
|
|
f7e6195466 | ||
|
|
a0bb7c3ed0 | ||
|
|
e3a6c9a6cf | ||
|
|
99598d87a9 | ||
|
|
b5df50893b | ||
|
|
f46b3d15cd | ||
|
|
041b4ec66e | ||
|
|
703e9673c2 | ||
|
|
e7bd93b4b0 | ||
|
|
a401c71d3e | ||
|
|
5bae233334 | ||
|
|
c4edd3047f | ||
|
|
c50da1593a | ||
|
|
1d06426281 | ||
|
|
9c5b693dd5 | ||
|
|
5fecc70db1 | ||
|
|
ff24023b39 | ||
|
|
1a04e2d1b8 | ||
|
|
52c4dd0e35 | ||
|
|
ff90f6a440 | ||
|
|
e24d5c172f | ||
|
|
0918f452a0 | ||
|
|
839fe49e61 | ||
|
|
ff050d634a | ||
|
|
228670df78 | ||
|
|
cfe4638665 | ||
|
|
b7352b1345 | ||
|
|
32ae8fc2d0 | ||
|
|
86df4c1d8d | ||
|
|
dc4a88029c | ||
|
|
5da9b2ede7 | ||
|
|
29cfcfaf0f | ||
|
|
61bfd347ea | ||
|
|
b7436c0b22 | ||
|
|
8a45dfac5c | ||
|
|
69f5d8cd0f | ||
|
|
9a57e8fcb0 | ||
|
|
a9d75ca4f4 | ||
|
|
ccb6fc3010 | ||
|
|
4e9a05fe11 | ||
|
|
8a294e4134 | ||
|
|
aad9a539c1 | ||
|
|
fd6ac529fb | ||
|
|
009cea1abf | ||
|
|
4c3c14ec32 | ||
|
|
636c9db1e3 | ||
|
|
71f625bbd3 | ||
|
|
aea2e9a6bb | ||
|
|
3f6f3c14c4 | ||
|
|
b1d77b7c03 | ||
|
|
49233e4734 | ||
|
|
e2b5ecb78b | ||
|
|
351ce67eae | ||
|
|
44af5e439c | ||
|
|
dbc0d500d8 | ||
|
|
86736aa480 | ||
|
|
57eb05c0e3 | ||
|
|
f6fe6e6bff | ||
|
|
18560f9430 | ||
|
|
cb0ba647ed | ||
|
|
f9fceb7ffc | ||
|
|
2697c9465b | ||
|
|
8d204655be | ||
|
|
8414a22356 | ||
|
|
36e4a8b444 | ||
|
|
949c71dc97 | ||
|
|
a6f6b8da7f | ||
|
|
a9f123e864 | ||
|
|
a64a505817 | ||
|
|
352004221e | ||
|
|
fc6a3e31c2 | ||
|
|
a32b58fdf1 | ||
|
|
2d50ecbecf | ||
|
|
87f1ffec05 | ||
|
|
f0dfde9fa1 | ||
|
|
e36dc2d05e | ||
|
|
08c8fa2c90 | ||
|
|
fe6621357e | ||
|
|
4a0067a2c5 | ||
|
|
b270ff335d | ||
|
|
7d2fcf59fd | ||
|
|
d26c43103d | ||
|
|
389889ad70 | ||
|
|
0af73c7903 | ||
|
|
8aa73bba10 | ||
|
|
5396b05237 | ||
|
|
e898345ff1 | ||
|
|
f39bf9c1e5 | ||
|
|
9483a9c8c4 | ||
|
|
52639a0a7c | ||
|
|
a1e10f384e | ||
|
|
27d4b3b8ad | ||
|
|
6438bd84fb | ||
|
|
4b8e910e3f | ||
|
|
9c0e463698 | ||
|
|
2092939353 | ||
|
|
b9d55fd1ed | ||
|
|
c1748d586e | ||
|
|
4c55b9c58c | ||
|
|
312958c573 | ||
|
|
65a489ebdd | ||
|
|
bd392e2efc | ||
|
|
25ad33a377 | ||
|
|
abc83f6cb0 | ||
|
|
f61a82a568 | ||
|
|
1c3ed71d36 | ||
|
|
8215a018e9 | ||
|
|
bf3b678727 | ||
|
|
f6e3070dd8 | ||
|
|
4996967c79 | ||
|
|
5887fe8302 | ||
|
|
0fc8e8d483 | ||
|
|
55388724af | ||
|
|
32efa5d83e | ||
|
|
275c12150e | ||
|
|
62468198d6 | ||
|
|
43d5e7a8cc | ||
|
|
7524493c3c | ||
|
|
5759de079b | ||
|
|
f3d5d27c8f | ||
|
|
c030be4d3f | ||
|
|
50cf57affb | ||
|
|
25c62319a3 | ||
|
|
27abb1280a | ||
|
|
c73f0eee4c | ||
|
|
9208227d92 | ||
|
|
725e8c69f5 | ||
|
|
facd4f63ec | ||
|
|
2e1d14b8b1 | ||
|
|
e8272759be | ||
|
|
ebbfab608c | ||
|
|
a5e1f8fe19 | ||
|
|
1b2de953d0 | ||
|
|
df5d03b0bc | ||
|
|
11c1fe8827 | ||
|
|
f29622abe1 | ||
|
|
10e411f8c1 | ||
|
|
6405799cc2 | ||
|
|
0afa41d08a | ||
|
|
48f3dfe455 | ||
|
|
6f0bfb286a | ||
|
|
2e54d3f98d | ||
|
|
67b4dcf8ae | ||
|
|
ad91362571 | ||
|
|
76a3e75bc7 | ||
|
|
e88418a01b | ||
|
|
27e08d37ea | ||
|
|
e069687477 | ||
|
|
ef0e611e52 | ||
|
|
d58d0e89c7 | ||
|
|
dfbf225403 | ||
|
|
e962762046 | ||
|
|
8166d0de79 | ||
|
|
d9c33f19e2 | ||
|
|
0cc3902ffc | ||
|
|
4752096520 | ||
|
|
dcadcdf056 | ||
|
|
b6394b7c6d | ||
|
|
3ec9bcaed6 | ||
|
|
d5c59292c8 | ||
|
|
a20e71b32a | ||
|
|
1254ec2849 | ||
|
|
ca144bae90 | ||
|
|
850368b529 | ||
|
|
7bbdfc5553 | ||
|
|
ba73cb1b75 | ||
|
|
eca36cb868 | ||
|
|
2a14473e8c | ||
|
|
9880a425f4 | ||
|
|
13696b1ec4 | ||
|
|
764eb960c6 | ||
|
|
17b55fc23d | ||
|
|
e20c994b82 | ||
|
|
465cd3d1f9 | ||
|
|
412351fd1e | ||
|
|
5776e70d7c | ||
|
|
1ccc6e342c | ||
|
|
a53481e2da | ||
|
|
01b1b688b1 | ||
|
|
3d78248aaf | ||
|
|
cf703f6ac4 | ||
|
|
2012c769f6 | ||
|
|
c8a4eb426c | ||
|
|
351ecf9bd4 | ||
|
|
e6f42fa6f0 | ||
|
|
582ac4ac81 | ||
|
|
6e30bacae3 | ||
|
|
5d136a18af | ||
|
|
1a47e4b524 | ||
|
|
c296b4c348 | ||
|
|
c52cb7bbad | ||
|
|
1923e0312b | ||
|
|
c8998941a5 | ||
|
|
e41ea42074 | ||
|
|
ed5f207eba | ||
|
|
a76b8e745b | ||
|
|
68d29c5af5 | ||
|
|
c3acf08c02 | ||
|
|
ef9e6e4d6e | ||
|
|
f3158c8b24 | ||
|
|
ac4a179703 | ||
|
|
dd4ea51d1f | ||
|
|
45f6926a5a | ||
|
|
5ca4bac10a | ||
|
|
ec026ab3a8 | ||
|
|
0e5e559283 | ||
|
|
417a3cdf51 | ||
|
|
7fa98e288f | ||
|
|
c693c219f4 | ||
|
|
e5d4e12457 | ||
|
|
33212d1abf | ||
|
|
7a16f846eb | ||
|
|
6873f09f57 | ||
|
|
382793de9a | ||
|
|
d4e76185bd | ||
|
|
8566dd9100 | ||
|
|
f479eae714 | ||
|
|
31067530a5 | ||
|
|
6505c30c2b | ||
|
|
7487ffcbcb | ||
|
|
f79299c240 | ||
|
|
2f07225984 | ||
|
|
c99b2b59c2 | ||
|
|
78633c5768 | ||
|
|
c041cc483c | ||
|
|
83a12d980e | ||
|
|
491f7e96f0 | ||
|
|
bfb9cb6732 | ||
|
|
3c9712d683 | ||
|
|
ca4107d450 | ||
|
|
148f5d9418 | ||
|
|
51ab0f0b78 | ||
|
|
bf0cce4ad8 | ||
|
|
82d5d50d61 | ||
|
|
ecb1c77f8b | ||
|
|
f9a8629157 | ||
|
|
e2b655a6cc | ||
|
|
04e6f475b4 | ||
|
|
0082c5b459 | ||
|
|
3fd130869e | ||
|
|
d000440927 | ||
|
|
4fb750de43 | ||
|
|
9632ac0e02 | ||
|
|
35078fd52f | ||
|
|
0bb81e5b2d | ||
|
|
35a2258f12 | ||
|
|
b650704877 | ||
|
|
0c0dec2534 | ||
|
|
d1b051a6bd | ||
|
|
27204aa53c | ||
|
|
8aedac81a5 | ||
|
|
42007d03d4 | ||
|
|
821c1a8bbd | ||
|
|
bab562dc3a | ||
|
|
f63fd9696f | ||
|
|
cd7af19e7c | ||
|
|
64bd33a94e | ||
|
|
60e6366521 | ||
|
|
072b2c445c | ||
|
|
219fe41831 | ||
|
|
dcc8bb83af | ||
|
|
a8e3521f3c | ||
|
|
706dc6d116 | ||
|
|
84accb6df6 | ||
|
|
8421570b18 | ||
|
|
d355543ac9 | ||
|
|
3a597c5aa6 | ||
|
|
c7dddaded4 | ||
|
|
aae4b2ea5d | ||
|
|
310e2a0b20 | ||
|
|
0b04d143ac | ||
|
|
d1f4c2ab57 | ||
|
|
7e4d12f880 | ||
|
|
ecd65003d4 | ||
|
|
fed37b48a1 | ||
|
|
3fba3a5e2e | ||
|
|
9e7e8ab116 | ||
|
|
1bec1faf6d | ||
|
|
e64246f642 | ||
|
|
fb2b7ade41 | ||
|
|
4de44a99eb | ||
|
|
39fbf9c56f | ||
|
|
021055f0b8 | ||
|
|
c2e0ea97d8 | ||
|
|
153aadadae | ||
|
|
1319ff1129 | ||
|
|
7df72ddb96 | ||
|
|
1d9ce2afc5 | ||
|
|
53986279d7 | ||
|
|
26d6738abb | ||
|
|
7fa5cab8e3 | ||
|
|
4f2e2fa297 | ||
|
|
0473c4c79f | ||
|
|
a62b6548d2 | ||
|
|
da390d32f8 | ||
|
|
8b92456ded | ||
|
|
14e9375262 | ||
|
|
d49ee47018 | ||
|
|
af66753c1b | ||
|
|
39b35b79ba | ||
|
|
a2a83c5004 | ||
|
|
ba1222eae4 | ||
|
|
31ae337931 | ||
|
|
bab0ba9c0f | ||
|
|
c9e224e999 | ||
|
|
6123cb7c69 | ||
|
|
8040e3cf95 | ||
|
|
d447548893 | ||
|
|
269812e781 | ||
|
|
65f4d30fd0 | ||
|
|
c1dfed5c08 | ||
|
|
835079ad43 | ||
|
|
17fd9d5107 | ||
|
|
8613c02d5c | ||
|
|
dea6675c21 | ||
|
|
43cf3063e0 | ||
|
|
3b7a47fb90 | ||
|
|
79248e8b74 | ||
|
|
4620ad6124 | ||
|
|
25cdbacecc | ||
|
|
4ec636c08f | ||
|
|
4cb30a22ac | ||
|
|
c632b0e1d4 | ||
|
|
714d28a61a | ||
|
|
c60989a7be | ||
|
|
11b727fdf7 | ||
|
|
a1dfd355f7 | ||
|
|
fcb2cc2471 | ||
|
|
177617e6e3 | ||
|
|
e0b4226930 | ||
|
|
426e6a1b46 | ||
|
|
66083c5e97 | ||
|
|
aff4f1e9e2 | ||
|
|
3c68348868 | ||
|
|
7f2a6e7403 | ||
|
|
11069085e3 | ||
|
|
854d735ab3 | ||
|
|
a4ab52918b | ||
|
|
eb895d2095 | ||
|
|
67cbaabd99 | ||
|
|
4402a6eb4c | ||
|
|
6ae1efcf9f | ||
|
|
1d136ab0df | ||
|
|
7721049ed7 | ||
|
|
e6f21873c3 | ||
|
|
499903bd3d | ||
|
|
2d0d794a9d | ||
|
|
a55787f40c | ||
|
|
990a4d4774 | ||
|
|
6a60f01753 | ||
|
|
30ecb58e06 | ||
|
|
3b689ef39c | ||
|
|
170d52e0db | ||
|
|
92d93d658c | ||
|
|
d7a2816c58 | ||
|
|
a30d2f291c | ||
|
|
d33a158585 | ||
|
|
e21dbc4b60 | ||
|
|
8a754421fe | ||
|
|
a73fd55fc2 | ||
|
|
45630d74f3 | ||
|
|
a6d31f05ee | ||
|
|
05f9dede70 | ||
|
|
7c870556c6 | ||
|
|
420c860424 | ||
|
|
828e291538 | ||
|
|
eea78531a1 | ||
|
|
c8ccb06f11 | ||
|
|
f5b7cc81d8 | ||
|
|
ae784dc74c | ||
|
|
056c72d50d | ||
|
|
b5714cd70f | ||
|
|
74aca2137b | ||
|
|
d09dff3ae3 | ||
|
|
d280380c8d | ||
|
|
8a08d1fb5d | ||
|
|
ea652e3587 | ||
|
|
7a6df38515 | ||
|
|
bba6d6897d | ||
|
|
33c08812cc | ||
|
|
f68a3a9334 | ||
|
|
0698be3995 | ||
|
|
064589934c | ||
|
|
e9e92afc9e | ||
|
|
e86f2e993f | ||
|
|
d26cd85306 | ||
|
|
73f80a8ea1 | ||
|
|
b7dff4bbab | ||
|
|
31d964c16a | ||
|
|
7f895abc24 | ||
|
|
0f406c38eb | ||
|
|
6433da13a3 | ||
|
|
fa1adfd934 | ||
|
|
36ffef083b | ||
|
|
fe89dcdc08 | ||
|
|
be36eef939 | ||
|
|
6a0268b852 | ||
|
|
b7b23ffdb2 | ||
|
|
ad76709d00 | ||
|
|
53c231a7eb | ||
|
|
d44ce82aa1 | ||
|
|
a055de48e4 | ||
|
|
37b8d665fe | ||
|
|
dd7c8dabb1 | ||
|
|
e41a9875e3 | ||
|
|
c5c42c4338 | ||
|
|
531428b8b0 | ||
|
|
ea8068e001 | ||
|
|
7842a55c81 | ||
|
|
51d39862b1 | ||
|
|
bfea6ca79b | ||
|
|
6297395018 | ||
|
|
a5b49dbfa6 | ||
|
|
7c0d777173 | ||
|
|
74878276fc | ||
|
|
226e3b1dad | ||
|
|
7752794fc5 | ||
|
|
b3094d6a53 | ||
|
|
e3640e710f | ||
|
|
2ef64b55c5 | ||
|
|
7f6672bb37 | ||
|
|
68a3b31628 | ||
|
|
1b35855e68 | ||
|
|
1e1837000d | ||
|
|
e2d5257632 | ||
|
|
387c75793b | ||
|
|
4f3a74d08a | ||
|
|
072cd5b83e | ||
|
|
cfd42ea162 | ||
|
|
b55544b860 | ||
|
|
5becaebdda | ||
|
|
1814e4a46b | ||
|
|
4f8f59f705 | ||
|
|
aca306d120 | ||
|
|
694395ac91 | ||
|
|
092bca0d63 | ||
|
|
a386bb476f | ||
|
|
39a520f552 | ||
|
|
663f84f8b4 | ||
|
|
8677d47777 | ||
|
|
4f1a28d460 | ||
|
|
7b142525b4 | ||
|
|
7d4f279206 | ||
|
|
51233e1931 | ||
|
|
907c14aa98 | ||
|
|
fb055750df | ||
|
|
fad05d5a2e | ||
|
|
9580b13b9f | ||
|
|
367ae906c3 | ||
|
|
f8d98ac494 | ||
|
|
3e8fd48dc0 | ||
|
|
003326f2eb | ||
|
|
b5af3aa048 | ||
|
|
a919b015b4 | ||
|
|
f94e9b6b1e | ||
|
|
1ed8e63d59 | ||
|
|
d97bc95798 | ||
|
|
5c56f15c67 | ||
|
|
3fdb68cba8 | ||
|
|
85c46becdf | ||
|
|
0cbd373817 | ||
|
|
fdbc59a159 | ||
|
|
0db37bb55c | ||
|
|
8027facb39 | ||
|
|
2ff2750628 | ||
|
|
16c2dc2aaf | ||
|
|
eae5c17b87 | ||
|
|
a59cde91ad | ||
|
|
1f243ae37e | ||
|
|
603f82977e | ||
|
|
2d70422a6f | ||
|
|
e2c8b21195 | ||
|
|
7adaeacd0b | ||
|
|
dc2279b74f | ||
|
|
75275c4e93 | ||
|
|
65d3dc9cb8 | ||
|
|
66aa02fc34 | ||
|
|
be6b4ee47f | ||
|
|
90f909d2ea | ||
|
|
3aaa92fdff | ||
|
|
5efd076c08 | ||
|
|
09fd505f08 | ||
|
|
042ccde441 | ||
|
|
442030b6ca | ||
|
|
1df9ae53f8 | ||
|
|
5f535e9756 | ||
|
|
70faeb2fa8 | ||
|
|
469c0db981 | ||
|
|
440e428aa4 | ||
|
|
dde70c95a4 | ||
|
|
09d1846261 | ||
|
|
34d26a517d | ||
|
|
d24b88271c | ||
|
|
f22115792a | ||
|
|
82a30558e1 | ||
|
|
847fe5adca | ||
|
|
775b51c6a1 | ||
|
|
e0ad5a9009 | ||
|
|
1bf01a9081 | ||
|
|
6ae59bb43d | ||
|
|
adf2a463fd | ||
|
|
80aaf66963 | ||
|
|
560251ab2a | ||
|
|
864c5d8908 | ||
|
|
742c21506c | ||
|
|
a6faccb4d9 | ||
|
|
69fd3e8937 | ||
|
|
41233d7f25 | ||
|
|
07286d1d76 | ||
|
|
08148c5830 | ||
|
|
969bdb06ce | ||
|
|
b0bb692af4 | ||
|
|
7bf6fd316f | ||
|
|
c1f5e04d6c | ||
|
|
5a67e72389 | ||
|
|
91c9b11647 | ||
|
|
929600d7f7 | ||
|
|
163d0c55ab | ||
|
|
327ccb241e | ||
|
|
6b3c7b0854 | ||
|
|
681dcb51da | ||
|
|
576d5021fd | ||
|
|
6cd76f00ac | ||
|
|
6f63a62a8d | ||
|
|
8867a0fcfb | ||
|
|
bb2582717f | ||
|
|
d62ef35860 | ||
|
|
59c5956f93 | ||
|
|
e4f055597c | ||
|
|
4c49beb3c7 | ||
|
|
8ff742d9ab | ||
|
|
d63cd8b4cd | ||
|
|
42b4a166ec | ||
|
|
c27fd0f01a | ||
|
|
dcb4a0a81e | ||
|
|
17da9fddc3 | ||
|
|
31aa3c55ca | ||
|
|
eca3685ea0 | ||
|
|
bd216c5c63 | ||
|
|
31ff76427c | ||
|
|
3f0503c296 | ||
|
|
c18050bda0 | ||
|
|
6542be5588 | ||
|
|
9fb60b8015 | ||
|
|
1177b856a0 | ||
|
|
c0adaa8de8 | ||
|
|
ae8700447e | ||
|
|
d64a4ef2b4 | ||
|
|
2229aa6ccc | ||
|
|
872b468415 | ||
|
|
9f022a7433 | ||
|
|
85a958e300 | ||
|
|
0ee56195ae | ||
|
|
48f52db1d9 | ||
|
|
d2c7afeef0 | ||
|
|
644aec791e | ||
|
|
b70a0325c5 | ||
|
|
268387f829 | ||
|
|
b975caef1e | ||
|
|
54fe1c7d55 | ||
|
|
89c1274d56 | ||
|
|
f9ca3f1c27 | ||
|
|
26dbc30279 | ||
|
|
4bee316425 | ||
|
|
1e22b1e959 | ||
|
|
e077ad56bd | ||
|
|
f1e00f8c8e | ||
|
|
9a152e588e | ||
|
|
16f42a3d03 | ||
|
|
6c8d0f1852 | ||
|
|
b59cf6572b | ||
|
|
4fa11dfa68 | ||
|
|
352bdd9fb5 | ||
|
|
96ff9a162c | ||
|
|
af15a4e710 | ||
|
|
18426b71e4 | ||
|
|
7063aa6009 | ||
|
|
286ca07cc8 | ||
|
|
58b6311821 | ||
|
|
e553c0768e | ||
|
|
62d4b29662 | ||
|
|
0759140dc2 | ||
|
|
a943bc6c80 | ||
|
|
16bc60644d | ||
|
|
347393b864 | ||
|
|
bf6b11222a | ||
|
|
823ae7d1aa | ||
|
|
28031cfa3e | ||
|
|
292c2d0c53 | ||
|
|
869775ec7a | ||
|
|
783b179af7 | ||
|
|
28454ea4cd | ||
|
|
9f4b666ef0 | ||
|
|
80214640b1 | ||
|
|
4310c14497 | ||
|
|
aebb6d2fcc | ||
|
|
af35c9258e | ||
|
|
7a43231c43 | ||
|
|
1bf55c130b | ||
|
|
95a74a7f19 | ||
|
|
b78b28ea0e | ||
|
|
aed7b3fbb2 | ||
|
|
1ade7bcb2d | ||
|
|
21bbafb63d | ||
|
|
1cfc6ac3c6 | ||
|
|
c3aa834d80 | ||
|
|
68d0d045c0 | ||
|
|
72d6471ab8 | ||
|
|
22aecdfc6f | ||
|
|
ee0b6e835f | ||
|
|
8292024306 | ||
|
|
84cfcf2b4a | ||
|
|
ea762b7295 | ||
|
|
d51c0f13c0 | ||
|
|
6ceb975a3a | ||
|
|
9a40d6ef50 | ||
|
|
32195f77d9 | ||
|
|
578e5a0d7a | ||
|
|
1242f43769 | ||
|
|
3bb6430495 | ||
|
|
aae633277f | ||
|
|
996c50e8f2 | ||
|
|
95c883ae9b | ||
|
|
35a725fa1e | ||
|
|
78c1adafcd | ||
|
|
e15071228e | ||
|
|
ac48ff1fd6 | ||
|
|
428684bc1e | ||
|
|
81b7653c9c | ||
|
|
45736707bd | ||
|
|
cdfbe5b523 | ||
|
|
cdb9c59662 | ||
|
|
9c30f4cc68 | ||
|
|
acf3f6fb65 | ||
|
|
23f99908db | ||
|
|
a0046a2e55 | ||
|
|
e30512931b | ||
|
|
e207c6ad84 | ||
|
|
9d7f76773d | ||
|
|
5f2808ec2f | ||
|
|
be91cfb261 | ||
|
|
0eadda77b0 | ||
|
|
b2388b6fe7 | ||
|
|
1a763ae974 | ||
|
|
38dfab11b4 | ||
|
|
7c31592850 | ||
|
|
57bee74225 | ||
|
|
fa351cd37c | ||
|
|
68e7e5a51c | ||
|
|
4d31ad3bdc | ||
|
|
f4f1164b94 | ||
|
|
d4e0e1518a | ||
|
|
bd0be41064 | ||
|
|
4118a289a6 | ||
|
|
1d5f8d5a52 | ||
|
|
fd1dc24ac6 | ||
|
|
be1e4c0a1d | ||
|
|
c2028f7378 | ||
|
|
4b0f203049 | ||
|
|
23ff8178a0 | ||
|
|
93cfee8026 | ||
|
|
b6920025b2 | ||
|
|
fb29ac27a2 | ||
|
|
4c03cebef3 | ||
|
|
244c4be8cc | ||
|
|
9b28c732c6 | ||
|
|
5dfb33ebee | ||
|
|
2b30cde125 | ||
|
|
f9b3e61c0f | ||
|
|
83a92f03fc | ||
|
|
d27291b997 | ||
|
|
2c995cf145 | ||
|
|
2822fa4a40 | ||
|
|
ccf3da2a5a | ||
|
|
5348b36a7c | ||
|
|
947a6034e3 | ||
|
|
65d08beaa4 | ||
|
|
9770bc371b | ||
|
|
22f9f75914 | ||
|
|
54c9dd4173 | ||
|
|
0fc267dfc7 | ||
|
|
c5db457700 | ||
|
|
15a7d2ef75 | ||
|
|
071272a27f | ||
|
|
655327a8b1 | ||
|
|
15b87af8ed | ||
|
|
a0b3d861fe | ||
|
|
718c494013 | ||
|
|
5c9755ecc1 | ||
|
|
11e88019c2 | ||
|
|
a783637a7a | ||
|
|
7210ad7ed9 | ||
|
|
1876c21e3e | ||
|
|
6516a6ff7e | ||
|
|
85195436c1 | ||
|
|
c6512013bb | ||
|
|
81a070d03d | ||
|
|
0ef1d178d2 | ||
|
|
762f1b1fc9 | ||
|
|
7ad593d674 | ||
|
|
13522c8f19 | ||
|
|
d2938e82db | ||
|
|
f95d4ca106 | ||
|
|
486bafd009 | ||
|
|
341c99b4fa | ||
|
|
83095e8989 | ||
|
|
71ba4bc31c | ||
|
|
894ec07cc8 | ||
|
|
59091100e4 | ||
|
|
e5485ab650 | ||
|
|
6c493d10d2 | ||
|
|
840f599631 | ||
|
|
5a76e61b1e | ||
|
|
7b4366bfef | ||
|
|
8dee5c5fe8 | ||
|
|
b2e6d222cd | ||
|
|
2712c44004 | ||
|
|
82625a3080 | ||
|
|
49f9ad66db | ||
|
|
0dfab4d93c | ||
|
|
5cd7f23065 | ||
|
|
27453afa4e | ||
|
|
369d175694 | ||
|
|
fc465d6d93 | ||
|
|
904a0b26ea | ||
|
|
c13f132399 | ||
|
|
db968bc6b0 | ||
|
|
7abe8875bd | ||
|
|
dc9f304d94 | ||
|
|
1ca241615d | ||
|
|
b8aa84002a | ||
|
|
10cc0b1d5b | ||
|
|
11d9c203c1 | ||
|
|
c9ab454c3c | ||
|
|
4a55692885 | ||
|
|
88c129e705 | ||
|
|
a09bd80636 | ||
|
|
237ecb3adf | ||
|
|
9d65b77f13 | ||
|
|
97f2becc9e | ||
|
|
80b48f01fb | ||
|
|
f4160c363b | ||
|
|
4fee9cc039 | ||
|
|
36f47ade70 | ||
|
|
8db6f3129c | ||
|
|
75630a36f8 | ||
|
|
642bc91a76 | ||
|
|
d69926ee56 | ||
|
|
4758403d44 | ||
|
|
4b0ec5c28a | ||
|
|
4b2a9e5e49 | ||
|
|
1449c51d49 | ||
|
|
a451705e0b | ||
|
|
2e6db39173 | ||
|
|
373f75253c | ||
|
|
724842084e | ||
|
|
8f3635b167 | ||
|
|
11605a36f7 | ||
|
|
533f81d625 | ||
|
|
aacb9e44e8 | ||
|
|
c6e3f1bca6 | ||
|
|
a933d4aeb6 | ||
|
|
d2be58ba31 | ||
|
|
bbeb0461c4 | ||
|
|
14fd08e225 | ||
|
|
f99352f7e0 | ||
|
|
b51cbc4207 | ||
|
|
7a895adec9 | ||
|
|
4fe0c95ccb | ||
|
|
726b0e73d9 | ||
|
|
88ccd60a08 | ||
|
|
e6c16e9981 | ||
|
|
1bd408937a | ||
|
|
4d00dfd308 | ||
|
|
75326d2271 | ||
|
|
76fe2e4871 | ||
|
|
f977e9da2b | ||
|
|
16ae46e958 | ||
|
|
73eea154d5 | ||
|
|
0d36e66125 | ||
|
|
970838ed09 | ||
|
|
0a21816a5a | ||
|
|
30a542e763 | ||
|
|
ebe64e24f1 | ||
|
|
caa5b20791 | ||
|
|
e2ad9ed746 | ||
|
|
32c0e7c2ae | ||
|
|
6c564c7b7f | ||
|
|
c81e3a3be4 | ||
|
|
6b1b9ef7ec | ||
|
|
c26a8b8718 | ||
|
|
4a89a475bd | ||
|
|
c53483a3b2 | ||
|
|
fe24745815 | ||
|
|
b5e75793e1 | ||
|
|
734cc989de | ||
|
|
2642750466 | ||
|
|
ec9cc72320 | ||
|
|
c97a9d83c6 | ||
|
|
8cf15c7f5c | ||
|
|
adc76ca1b8 | ||
|
|
8f8892440c | ||
|
|
570843150d | ||
|
|
f3fc9e4142 | ||
|
|
075fcb77a8 | ||
|
|
e5899ff717 | ||
|
|
2fb3970027 | ||
|
|
1a4efa1b8c | ||
|
|
f31c1480f3 | ||
|
|
291d4be772 | ||
|
|
52584ec2be | ||
|
|
3bc08e5222 | ||
|
|
672f8d1719 | ||
|
|
420c8b49e2 | ||
|
|
f921997ee6 | ||
|
|
4e520d13dd | ||
|
|
72f656ffef | ||
|
|
b60239d5e5 | ||
|
|
d02e280c3c | ||
|
|
6535b0966e | ||
|
|
82dbacbee5 | ||
|
|
2432901974 | ||
|
|
ebb5d58c14 | ||
|
|
605e365405 | ||
|
|
2617e5092b | ||
|
|
d41ddf380c | ||
|
|
a72c3ea9d7 | ||
|
|
8be733efee | ||
|
|
b9609286ea | ||
|
|
2b186fdb0d | ||
|
|
3012fee013 | ||
|
|
01db114724 | ||
|
|
e05688d639 | ||
|
|
5ab995d8ca | ||
|
|
4248741b11 | ||
|
|
4b8ecc7634 | ||
|
|
25d04c759c | ||
|
|
b4ec84030e | ||
|
|
925b030718 | ||
|
|
9eba789c32 | ||
|
|
3e6ae4afda | ||
|
|
27abb38ecb | ||
|
|
29e8761373 | ||
|
|
a04299c59e | ||
|
|
d7bf3c51d9 | ||
|
|
3ddf5a4ec7 | ||
|
|
f2d6817d8a | ||
|
|
31821e6309 | ||
|
|
1ce257c721 | ||
|
|
8dd971b25e | ||
|
|
7f507935b1 | ||
|
|
3b1ba27043 | ||
|
|
4b7c5aa05c | ||
|
|
5fca02c712 | ||
|
|
31ddd3f668 | ||
|
|
f35f6d2348 | ||
|
|
02d34a0238 | ||
|
|
e4bbb56f6b | ||
|
|
96d30e28d4 | ||
|
|
41b73ff892 | ||
|
|
afc4e45fb0 | ||
|
|
8778ddd5c5 | ||
|
|
3089ffa8e7 | ||
|
|
15cb0e4ff3 | ||
|
|
2f3c6d5b58 | ||
|
|
ead5e8d855 | ||
|
|
fd9a9ecc63 | ||
|
|
bbb8ea7ec2 | ||
|
|
667ed94e29 | ||
|
|
23f1798d20 | ||
|
|
6f3c126805 | ||
|
|
a9ae70cff1 | ||
|
|
d7a8c50c98 | ||
|
|
9e56318498 | ||
|
|
2570f2d6f2 | ||
|
|
928df2dcd1 | ||
|
|
2decb8115c | ||
|
|
9d26c16471 | ||
|
|
5893506528 | ||
|
|
df0d33c3cd | ||
|
|
359f54d3c1 | ||
|
|
68ce1b18c4 | ||
|
|
61ba2e0f35 | ||
|
|
76d7802650 | ||
|
|
9fa1a334e6 | ||
|
|
9be16916b7 | ||
|
|
4a5365f6a0 | ||
|
|
0ced5509fc | ||
|
|
bd6b9ff1da | ||
|
|
edee28acf0 | ||
|
|
127efe9a52 | ||
|
|
53e8b3ed3e | ||
|
|
a23ebead68 | ||
|
|
f39d459555 | ||
|
|
6a17fe375e | ||
|
|
6e7d25ed42 | ||
|
|
a676ff23de | ||
|
|
85513476ce | ||
|
|
e62443933c | ||
|
|
3b48aa5911 | ||
|
|
b7daee533a | ||
|
|
7a14ab825e | ||
|
|
4323ca88c3 | ||
|
|
4bc3067725 | ||
|
|
28f2a7f99c | ||
|
|
72218171b3 | ||
|
|
0d9f5ef363 | ||
|
|
7b5c4359c6 | ||
|
|
72a80f559a | ||
|
|
dac624231f | ||
|
|
510e53de70 | ||
|
|
d8963ea25a | ||
|
|
9ed06444e1 | ||
|
|
df50b95e5a | ||
|
|
12ff280d3b | ||
|
|
16c2929bb4 | ||
|
|
422754ed63 | ||
|
|
aa7389432e | ||
|
|
bd45f6bd8e | ||
|
|
999c1a5357 | ||
|
|
a323679771 | ||
|
|
1fa4a2d256 | ||
|
|
8c73558165 | ||
|
|
ed61c1dd58 | ||
|
|
8be444a25e | ||
|
|
6306d44955 | ||
|
|
6fff2e5957 | ||
|
|
dd79e37933 | ||
|
|
a1b6a91642 | ||
|
|
60d67e5428 | ||
|
|
5bb963fa82 | ||
|
|
83fa51a580 | ||
|
|
0281914507 | ||
|
|
9231b80aa9 | ||
|
|
3e044db9f1 | ||
|
|
855e9367d4 | ||
|
|
db0dd6af09 | ||
|
|
b7afb8c887 | ||
|
|
02c9ada876 | ||
|
|
f811b1157c | ||
|
|
797aadaf26 | ||
|
|
f1a0e5a313 | ||
|
|
f2540bae23 | ||
|
|
a1a7448868 | ||
|
|
e373620393 | ||
|
|
23dcfe5075 | ||
|
|
7bab279c6a | ||
|
|
953d7f6193 | ||
|
|
1d6dd3aa5e | ||
|
|
9dd9e523ed | ||
|
|
66cbfca99c | ||
|
|
b0e6b48c50 | ||
|
|
bb1937ab88 | ||
|
|
86848b39db | ||
|
|
f2506bb23b | ||
|
|
3372ddc63d | ||
|
|
3fe9c20188 | ||
|
|
95428b4cfe | ||
|
|
968ff4b619 | ||
|
|
a07a835eb0 | ||
|
|
667457989e | ||
|
|
bedf1b7483 | ||
|
|
53c182ad37 | ||
|
|
ce45c81069 | ||
|
|
1ee85295f2 | ||
|
|
521c080989 | ||
|
|
64541e0e35 | ||
|
|
c3d4ef284d | ||
|
|
ebb5fadba1 | ||
|
|
c569d022ec | ||
|
|
58586ea840 | ||
|
|
27f431a027 | ||
|
|
595dfe7e24 | ||
|
|
766f6c045d | ||
|
|
0a0713f0e2 | ||
|
|
5e5b9f2205 | ||
|
|
e6ff3c287d | ||
|
|
295585379e | ||
|
|
c7609ba5e7 | ||
|
|
8e75980ebd | ||
|
|
6682c43dfa | ||
|
|
049807e3ab | ||
|
|
336f12b24d | ||
|
|
be5330b6ae | ||
|
|
e90829eef2 | ||
|
|
dc4c1bc225 | ||
|
|
a14d12b0c1 | ||
|
|
526a414743 | ||
|
|
7fd5634a51 | ||
|
|
cb9f36a153 | ||
|
|
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 | ||
|
|
5d5529ff15 | ||
|
|
db90a7a334 | ||
|
|
bb9c1faffa | ||
|
|
b4c4dc8cfb | ||
|
|
c3b2ab0085 | ||
|
|
f087e313d4 | ||
|
|
f88cf99b67 | ||
|
|
f394968bd0 | ||
|
|
70def85ba1 |
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: ''
|
||||
2
.coveragerc
Normal file
2
.coveragerc
Normal file
@@ -0,0 +1,2 @@
|
||||
[run]
|
||||
omit = esphome/components/*
|
||||
32
.devcontainer/devcontainer.json
Normal file
32
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "ESPHome Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "../docker/Dockerfile.dev",
|
||||
"postCreateCommand": "mkdir -p config && pip3 install -e .",
|
||||
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
|
||||
"appPort": 6052,
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"redhat.vscode-yaml"
|
||||
],
|
||||
"settings": {
|
||||
"python.pythonPath": "/usr/local/bin/python",
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"yaml.customTags": [
|
||||
"!secret scalar",
|
||||
"!lambda scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
]
|
||||
}
|
||||
}
|
||||
28
.editorconfig
Normal file
28
.editorconfig
Normal file
@@ -0,0 +1,28 @@
|
||||
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
|
||||
quote_type = single
|
||||
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github:
|
||||
patreon: ottowinter
|
||||
open_collective:
|
||||
ko_fi:
|
||||
tidelift:
|
||||
custom: https://esphome.io/guides/supporters.html
|
||||
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
<!-- Thanks for reporting a bug for this project. READ THIS FIRST:
|
||||
- Please make sure to submit issues in the right GitHub repository, if unsure just post it here:
|
||||
- esphomeyaml [here] - This is mostly for reporting bugs when compiling and when you get a long stack trace while compiling or if a configuration fails to validate.
|
||||
- esphomelib [https://github.com/OttoWinter/esphomelib] - Report bugs there if the ESP is crashing or a feature is not working as expected.
|
||||
- esphomedocs [https://github.com/OttoWinter/esphomedocs] - Report bugs there if the documentation is wrong/outdated.
|
||||
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks (```). Do not delete any text from this template!
|
||||
-->
|
||||
|
||||
**Operating environment (Hass.io/Docker/pip/etc.):**
|
||||
<!--
|
||||
Please provide details about your environment.
|
||||
-->
|
||||
|
||||
**ESP (ESP32/ESP8266/Board/Sonoff):**
|
||||
<!--
|
||||
Please provide details about which ESP you're using.
|
||||
-->
|
||||
|
||||
**Affected component:**
|
||||
<!--
|
||||
Please add the link to the documentation at https://esphomelib.com/esphomeyaml/index.html of the component in question.
|
||||
-->
|
||||
|
||||
|
||||
**Description of problem:**
|
||||
|
||||
|
||||
**Problem-relevant YAML-configuration entries:**
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
**Traceback (if applicable):**
|
||||
<!--
|
||||
Please copy the traceback here if compilation is failing. If possible, also connect to the ESP and copy its logs into the backticks.
|
||||
-->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
**Additional information:**
|
||||
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Issue Tracker
|
||||
url: https://github.com/esphome/issues
|
||||
about: Please create bug reports in the dedicated issue tracker.
|
||||
- name: Feature Request Tracker
|
||||
url: https://github.com/esphome/feature-requests
|
||||
about: Please create feature requests in the dedicated feature request tracker.
|
||||
- name: Frequently Asked Question
|
||||
url: https://esphome.io/guides/faq.html
|
||||
about: Please view the FAQ for common questions and what to include in a bug report.
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
<!-- READ THIS FIRST:
|
||||
- This is for feature requests only, if you want to have a certain new sensor/module supported, please use the "new integration" template.
|
||||
- Please be as descriptive as possible, especially use-cases that can otherwise not be solved boost the problem's priority.
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
<!--
|
||||
A clear and concise description of what the problem is.
|
||||
-->
|
||||
Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A description of what you want to happen.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the feature request here.
|
||||
20
.github/ISSUE_TEMPLATE/new-integration.md
vendored
20
.github/ISSUE_TEMPLATE/new-integration.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: New integration
|
||||
about: Suggest a new integration for esphomelib
|
||||
|
||||
---
|
||||
|
||||
<!-- READ THIS FIRST:
|
||||
- This is for new integrations (such as new sensors/modules) only, for new features within the environment please use the "feature request" template.
|
||||
- Do not delete anything from this template and fill out the form as precisely as possible.
|
||||
-->
|
||||
|
||||
**What new integration would you wish to have?**
|
||||
<!-- A name/description of the new integration/board. -->
|
||||
|
||||
**If possible, provide a link to an existing library for the integration:**
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Additional context**
|
||||
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,18 +3,11 @@
|
||||
|
||||
**Related issue (if applicable):** fixes <link to issue>
|
||||
|
||||
**Pull request in [esphomedocs](https://github.com/OttoWinter/esphomedocs) with documentation (if applicable):** OttoWinter/esphomedocs#<esphomedocs PR number goes here>
|
||||
**Pull request in [esphomelib](https://github.com/OttoWinter/esphomelib) with C++ framework changes (if applicable):** OttoWinter/esphomelib#<esphomelib PR number goes here>
|
||||
|
||||
## Example entry for YAML configuration (if applicable):
|
||||
```yaml
|
||||
|
||||
```
|
||||
**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).
|
||||
- [ ] Check this box if you have read, understand, comply, and agree with the [Code of Conduct](https://github.com/OttoWinter/esphomeyaml/blob/master/CODE_OF_CONDUCT.md).
|
||||
|
||||
If user exposed functionality or configuration variables are added/changed:
|
||||
- [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
|
||||
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
|
||||
|
||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
ignore:
|
||||
# Hypotehsis is only used for testing and is updated quite often
|
||||
- dependency-name: hypothesis
|
||||
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
|
||||
36
.github/lock.yml
vendored
Normal file
36
.github/lock.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 7
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- keep-open
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: false
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: false
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
59
.github/stale.yml
vendored
Normal file
59
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- not-stale
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 10
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: pulls
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
||||
55
.github/workflows/ci-docker.yml
vendored
Normal file
55
.github/workflows/ci-docker.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: CI for docker images
|
||||
|
||||
# Only run when docker paths change
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, master]
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
|
||||
jobs:
|
||||
check-docker:
|
||||
name: Build docker containers
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="2.6.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=ci" \
|
||||
--cache-from "${BUILD_TO}:dev" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
178
.github/workflows/ci.yml
vendored
Normal file
178
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
|
||||
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
# On dev branch release-dev already performs CI checks
|
||||
# On other branches the `pull_request` trigger will be used
|
||||
branches: [beta, master]
|
||||
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Cache platformio intermediary files (like libraries etc)
|
||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
||||
- name: Cache Platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: .pio
|
||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: |
|
||||
lint-cpp-pio-
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Cache platformio intermediary files (like libraries etc)
|
||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
||||
- name: Cache Platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: .pio
|
||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: |
|
||||
lint-cpp-pio-
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
16
.github/workflows/matchers/ci-custom.json
vendored
Normal file
16
.github/workflows/matchers/ci-custom.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "ci-custom",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
.github/workflows/matchers/clang-tidy.json
vendored
Normal file
17
.github/workflows/matchers/clang-tidy.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "clang-tidy",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.github/workflows/matchers/gcc.json
vendored
Normal file
18
.github/workflows/matchers/gcc.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "gcc",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
28
.github/workflows/matchers/lint-python.json
vendored
Normal file
28
.github/workflows/matchers/lint-python.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "flake8",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"message": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "pylint",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"message": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.github/workflows/matchers/python.json
vendored
Normal file
18
.github/workflows/matchers/python.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "python",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^\\s*File\\s\\\"(.*)\\\",\\sline\\s(\\d+),\\sin\\s(.*)$",
|
||||
"file": 1,
|
||||
"line": 2
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s*raise\\s(.*)\\(\\'(.*)\\'\\)$",
|
||||
"message": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
264
.github/workflows/release-dev.yml
vendored
Normal file
264
.github/workflows/release-dev.yml
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
name: Publish dev releases to docker hub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
|
||||
|
||||
lint-clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Cache platformio intermediary files (like libraries etc)
|
||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
||||
- name: Cache Platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: .pio
|
||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: |
|
||||
lint-cpp-pio-
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Cache platformio intermediary files (like libraries etc)
|
||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
||||
- name: Cache Platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: .pio
|
||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: |
|
||||
lint-cpp-pio-
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
# Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly
|
||||
build_type: ["docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_SHA:0:7}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="2.6.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=${TAG}" \
|
||||
--tag "${BUILD_TO}:${TAG}" \
|
||||
--tag "${BUILD_TO}:dev" \
|
||||
--cache-from "${BUILD_TO}:dev" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- run: |
|
||||
docker push "${BUILD_TO}:${TAG}"
|
||||
docker push "${BUILD_TO}:dev"
|
||||
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_SHA:0:7}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- name: "Create the manifest"
|
||||
run: |
|
||||
docker manifest create esphome/esphome:${TAG} \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:${TAG}
|
||||
|
||||
docker manifest create esphome/esphome:dev \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:dev
|
||||
327
.github/workflows/release.yml
vendored
Normal file
327
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
name: Publish Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
|
||||
|
||||
lint-clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Cache platformio intermediary files (like libraries etc)
|
||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
||||
- name: Cache Platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: .pio
|
||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: |
|
||||
lint-cpp-pio-
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Cache platformio intermediary files (like libraries etc)
|
||||
# Note: platformio platform versions should be cached via the esphome-lint image
|
||||
- name: Cache Platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: .pio
|
||||
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
||||
restore-keys: |
|
||||
lint-cpp-pio-
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core_config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome tests/${{ matrix.test }}.yaml compile
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
|
||||
deploy-pypi:
|
||||
name: Build and publish to PyPi
|
||||
if: github.repository == 'esphome/esphome'
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Set up python environment
|
||||
run: |
|
||||
script/setup
|
||||
pip install setuptools wheel twine
|
||||
- name: Build
|
||||
run: python setup.py sdist bdist_wheel
|
||||
- name: Upload
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
||||
run: twine upload dist/*
|
||||
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="2.6.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
|
||||
cache_tag="beta"
|
||||
else
|
||||
cache_tag="latest"
|
||||
fi
|
||||
|
||||
# Set env variables so these values don't need to be calculated again
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=${TAG}" \
|
||||
--tag "${BUILD_TO}:${TAG}" \
|
||||
--cache-from "${BUILD_TO}:${CACHE_TAG}" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- run: docker push "${BUILD_TO}:${TAG}"
|
||||
|
||||
# Always publish to beta tag (also full releases)
|
||||
- name: Publish docker beta tag
|
||||
run: |
|
||||
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
|
||||
docker push "${BUILD_TO}:beta"
|
||||
|
||||
- if: ${{ !github.event.release.prerelease }}
|
||||
name: Publish docker latest tag
|
||||
run: |
|
||||
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
|
||||
docker push "${BUILD_TO}:latest"
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- name: "Create the manifest"
|
||||
run: |
|
||||
docker manifest create esphome/esphome:${TAG} \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:${TAG}
|
||||
|
||||
- name: Publish docker beta tag
|
||||
run: |
|
||||
docker manifest create esphome/esphome:beta \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:beta
|
||||
|
||||
- name: Publish docker latest tag
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
run: |
|
||||
docker manifest create esphome/esphome:latest \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:latest
|
||||
|
||||
deploy-hassio-repo:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
curl \
|
||||
-u ":$TOKEN" \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
|
||||
-d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}"
|
||||
102
.gitignore
vendored
102
.gitignore
vendored
@@ -6,6 +6,22 @@ __pycache__/
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Hide sublime text stuff
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# Intellij Idea
|
||||
.idea
|
||||
|
||||
# Hide some OS X stuff
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
@@ -25,12 +41,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
|
||||
@@ -41,8 +51,10 @@ htmlcov/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
.esphome
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
cov.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
@@ -51,36 +63,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 +75,49 @@ ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.pio
|
||||
.vscode/
|
||||
!.vscode/tasks.json
|
||||
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
|
||||
.pio/
|
||||
|
||||
320
.gitlab-ci.yml
320
.gitlab-ci.yml
@@ -1,320 +0,0 @@
|
||||
---
|
||||
# Based on https://gitlab.com/hassio-addons/addon-node-red/blob/master/.gitlab-ci.yml
|
||||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
.lint: &lint
|
||||
stage: lint
|
||||
tags:
|
||||
- python2.7
|
||||
- esphomeyaml-lint
|
||||
|
||||
.test: &test
|
||||
stage: test
|
||||
before_script:
|
||||
- pip install -e .
|
||||
tags:
|
||||
- python2.7
|
||||
- esphomeyaml-test
|
||||
variables:
|
||||
TZ: UTC
|
||||
cache:
|
||||
paths:
|
||||
- tests/build
|
||||
|
||||
.docker-builder: &docker-builder
|
||||
before_script:
|
||||
- docker info
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
|
||||
services:
|
||||
- docker:dind
|
||||
tags:
|
||||
- hassio-builder
|
||||
|
||||
flake8:
|
||||
<<: *lint
|
||||
script:
|
||||
- flake8 esphomeyaml
|
||||
|
||||
pylint:
|
||||
<<: *lint
|
||||
script:
|
||||
- pylint esphomeyaml
|
||||
|
||||
test1:
|
||||
<<: *test
|
||||
script:
|
||||
- esphomeyaml tests/test1.yaml compile
|
||||
|
||||
test2:
|
||||
<<: *test
|
||||
script:
|
||||
- esphomeyaml tests/test2.yaml compile
|
||||
|
||||
.build-hassio: &build-hassio
|
||||
<<: *docker-builder
|
||||
stage: build
|
||||
script:
|
||||
- docker run --rm --privileged hassioaddons/qemu-user-static:latest
|
||||
- BUILD_FROM=homeassistant/${ADDON_ARCH}-base-ubuntu:latest
|
||||
- ADDON_VERSION="${CI_COMMIT_TAG#v}"
|
||||
- ADDON_VERSION="${ADDON_VERSION:-${CI_COMMIT_SHA:0:7}}"
|
||||
- ESPHOMELIB_VERSION="${ESPHOMELIB_VERSION:-dev}"
|
||||
- echo "Build from ${BUILD_FROM}"
|
||||
- echo "Add-on version ${ADDON_VERSION}"
|
||||
- echo "Esphomelib version ${ESPHOMELIB_VERSION}"
|
||||
- echo "Tag ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:dev"
|
||||
- echo "Tag ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}"
|
||||
- |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "ADDON_ARCH=${ADDON_ARCH}" \
|
||||
--build-arg "ADDON_VERSION=${ADDON_VERSION}" \
|
||||
--build-arg "ESPHOMELIB_VERSION=${ESPHOMELIB_VERSION}" \
|
||||
--tag "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:dev" \
|
||||
--tag "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
--file "docker/Dockerfile.hassio" \
|
||||
.
|
||||
- |
|
||||
if [ "${DO_PUSH:-true}" = true ]; then
|
||||
echo "Pushing to CI registry"
|
||||
docker push ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}
|
||||
docker push ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:dev
|
||||
fi
|
||||
|
||||
# Generic deploy template
|
||||
.deploy-release: &deploy-release
|
||||
<<: *docker-builder
|
||||
stage: deploy
|
||||
script:
|
||||
- version="${CI_COMMIT_TAG#v}"
|
||||
- echo "Publishing release version ${version}"
|
||||
- docker pull "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}"
|
||||
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
|
||||
|
||||
- echo "Tag ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- docker push "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
|
||||
- echo "Tag ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:latest"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:latest"
|
||||
- docker push "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:latest"
|
||||
|
||||
- echo "Tag ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
- docker push "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
|
||||
- echo "Tag ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- docker push "ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
|
||||
- echo "Tag ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:latest"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:latest"
|
||||
- docker push "ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:latest"
|
||||
|
||||
- echo "Tag ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
- docker push "ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
except:
|
||||
- /^(?!master).+@/
|
||||
|
||||
.deploy-beta: &deploy-beta
|
||||
<<: *docker-builder
|
||||
stage: deploy
|
||||
script:
|
||||
- version="${CI_COMMIT_TAG#v}"
|
||||
- echo "Publishing beta version ${version}"
|
||||
- docker pull "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}"
|
||||
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
|
||||
|
||||
- echo "Tag ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- docker push "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
|
||||
- echo "Tag ${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
- docker push "${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
|
||||
- echo "Tag ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
- docker push "ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:${version}"
|
||||
|
||||
- echo "Tag ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
- |
|
||||
docker tag \
|
||||
"${CI_REGISTRY}/esphomeyaml-hassio-${ADDON_ARCH}:${CI_COMMIT_SHA}" \
|
||||
"ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
- docker push "ottowinter/esphomeyaml-hassio-${ADDON_ARCH}:rc"
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+b\d+$/
|
||||
except:
|
||||
- /^(?!rc).+@/
|
||||
|
||||
# Build jobs
|
||||
build:normal:
|
||||
<<: *docker-builder
|
||||
stage: build
|
||||
script:
|
||||
- docker build -t "${CI_REGISTRY}/esphomeyaml:dev" .
|
||||
|
||||
.build-hassio-edge: &build-hassio-edge
|
||||
<<: *build-hassio
|
||||
except:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
- /^v\d+\.\d+\.\d+b\d+$/
|
||||
|
||||
.build-hassio-release: &build-hassio-release
|
||||
<<: *build-hassio
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
- /^v\d+\.\d+\.\d+b\d+$/
|
||||
|
||||
build:hassio-armhf-edge:
|
||||
<<: *build-hassio-edge
|
||||
variables:
|
||||
ADDON_ARCH: armhf
|
||||
DO_PUSH: "false"
|
||||
|
||||
build:hassio-armhf:
|
||||
<<: *build-hassio-release
|
||||
variables:
|
||||
ADDON_ARCH: armhf
|
||||
ESPHOMELIB_VERSION: "${CI_COMMIT_TAG}"
|
||||
|
||||
#build:hassio-aarch64-edge:
|
||||
# <<: *build-hassio-edge
|
||||
# variables:
|
||||
# ADDON_ARCH: aarch64
|
||||
# DO_PUSH: "false"
|
||||
|
||||
#build:hassio-aarch64:
|
||||
# <<: *build-hassio-release
|
||||
# variables:
|
||||
# ADDON_ARCH: aarch64
|
||||
# ESPHOMELIB_VERSION: "${CI_COMMIT_TAG}"
|
||||
|
||||
build:hassio-i386-edge:
|
||||
<<: *build-hassio-edge
|
||||
variables:
|
||||
ADDON_ARCH: i386
|
||||
DO_PUSH: "false"
|
||||
|
||||
build:hassio-i386:
|
||||
<<: *build-hassio-release
|
||||
variables:
|
||||
ADDON_ARCH: i386
|
||||
ESPHOMELIB_VERSION: "${CI_COMMIT_TAG}"
|
||||
|
||||
build:hassio-amd64-edge:
|
||||
<<: *build-hassio-edge
|
||||
variables:
|
||||
ADDON_ARCH: amd64
|
||||
DO_PUSH: "false"
|
||||
|
||||
build:hassio-amd64:
|
||||
<<: *build-hassio-release
|
||||
variables:
|
||||
ADDON_ARCH: amd64
|
||||
ESPHOMELIB_VERSION: "${CI_COMMIT_TAG}"
|
||||
|
||||
# Deploy jobs
|
||||
deploy-release:armhf:
|
||||
<<: *deploy-release
|
||||
variables:
|
||||
ADDON_ARCH: armhf
|
||||
|
||||
deploy-beta:armhf:
|
||||
<<: *deploy-beta
|
||||
variables:
|
||||
ADDON_ARCH: armhf
|
||||
|
||||
#deploy-release:aarch64:
|
||||
# <<: *deploy-release
|
||||
# variables:
|
||||
# ADDON_ARCH: aarch64
|
||||
#
|
||||
#deploy-beta:aarch64:
|
||||
# <<: *deploy-beta
|
||||
# variables:
|
||||
# ADDON_ARCH: aarch64
|
||||
|
||||
deploy-release:i386:
|
||||
<<: *deploy-release
|
||||
variables:
|
||||
ADDON_ARCH: i386
|
||||
|
||||
deploy-beta:i386:
|
||||
<<: *deploy-beta
|
||||
variables:
|
||||
ADDON_ARCH: i386
|
||||
|
||||
deploy-release:amd64:
|
||||
<<: *deploy-release
|
||||
variables:
|
||||
ADDON_ARCH: amd64
|
||||
|
||||
deploy-beta:amd64:
|
||||
<<: *deploy-beta
|
||||
variables:
|
||||
ADDON_ARCH: amd64
|
||||
|
||||
.deploy-pypi: &deploy-pypi
|
||||
stage: deploy
|
||||
before_script:
|
||||
- pip install -e .
|
||||
- pip install twine
|
||||
script:
|
||||
- python setup.py sdist
|
||||
- twine upload dist/*
|
||||
tags:
|
||||
- python2.7
|
||||
- esphomeyaml-test
|
||||
|
||||
deploy-release:pypi:
|
||||
<<: *deploy-pypi
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
except:
|
||||
- /^(?!master).+@/
|
||||
|
||||
deploy-beta:pypi:
|
||||
<<: *deploy-pypi
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+b\d+$/
|
||||
except:
|
||||
- /^(?!rc).+@/
|
||||
6
.gitpod.yml
Normal file
6
.gitpod.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
ports:
|
||||
- port: 6052
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
|
||||
command: python -m esphome config dashboard
|
||||
11
.pre-commit-config.yaml
Normal file
11
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- id: flake8
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,20 +0,0 @@
|
||||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
jobs:
|
||||
include:
|
||||
- name: "Lint"
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
- pip install flake8==3.5.0 pylint==1.9.3 tzlocal pillow
|
||||
script:
|
||||
- flake8 esphomeyaml
|
||||
- pylint esphomeyaml
|
||||
- name: "Test"
|
||||
install:
|
||||
- pip install -e .
|
||||
- pip install tzlocal pillow
|
||||
script:
|
||||
- esphomeyaml tests/test1.yaml compile
|
||||
- esphomeyaml tests/test2.yaml compile
|
||||
11
.vscode/tasks.json
vendored
Normal file
11
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "run",
|
||||
"type": "shell",
|
||||
"command": "python3 -m esphome config dashboard",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
98
CODEOWNERS
Normal file
98
CODEOWNERS
Normal file
@@ -0,0 +1,98 @@
|
||||
# This file is generated by script/build_codeowners.py
|
||||
# People marked here will be automatically requested for a review
|
||||
# when the code that they own is touched.
|
||||
#
|
||||
# Every time an issue is created with a label corresponding to an integration,
|
||||
# the integration's code owner is automatically notified.
|
||||
|
||||
# Core Code
|
||||
setup.py @esphome/core
|
||||
esphome/*.py @esphome/core
|
||||
esphome/core/* @esphome/core
|
||||
|
||||
# Integrations
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/api/* @OttoWinter
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw
|
||||
esphome/components/mcp23s17/* @SenexCrenshaw
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
esphome/components/ssd1325_base/* @kbx81
|
||||
esphome/components/ssd1325_spi/* @kbx81
|
||||
esphome/components/ssd1327_base/* @kbx81
|
||||
esphome/components/ssd1327_i2c/* @kbx81
|
||||
esphome/components/ssd1327_spi/* @kbx81
|
||||
esphome/components/ssd1331_base/* @kbx81
|
||||
esphome/components/ssd1331_spi/* @kbx81
|
||||
esphome/components/ssd1351_base/* @kbx81
|
||||
esphome/components/ssd1351_spi/* @kbx81
|
||||
esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
@@ -1,16 +1,16 @@
|
||||
# Contributing to esphomeyaml
|
||||
# Contributing to ESPHome
|
||||
|
||||
esphomeyaml is a part of esphomelib and is responsible for reading in YAML configuration files,
|
||||
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 [esphomelib](https://github.com/OttoWinter/esphomelib), the C++ framework behind the project.
|
||||
with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project.
|
||||
|
||||
For a detailed guide, please see https://esphomelib.com/esphomeyaml/guides/contributing.html#contributing-to-esphomeyaml
|
||||
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/OttoWinter/esphomedocs). See [contributing to esphomedocs](https://esphomelib.com/esphomeyaml/guides/contributing.html#contributing-to-esphomedocs)
|
||||
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.
|
||||
|
||||
29
Dockerfile
29
Dockerfile
@@ -1,29 +0,0 @@
|
||||
ARG BUILD_FROM=python:2.7
|
||||
FROM ${BUILD_FROM}
|
||||
MAINTAINER Otto Winter <contact@otto-winter.com>
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python-pil \
|
||||
git \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* && \
|
||||
pip install --no-cache-dir --no-binary :all: platformio && \
|
||||
platformio settings set enable_telemetry No && \
|
||||
platformio settings set check_libraries_interval 1000000 && \
|
||||
platformio settings set check_platformio_interval 1000000 && \
|
||||
platformio settings set check_platforms_interval 1000000
|
||||
|
||||
ENV ESPHOMEYAML_OTA_HOST_PORT=6123
|
||||
EXPOSE 6123
|
||||
VOLUME /config
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
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
|
||||
|
||||
WORKDIR /config
|
||||
ENTRYPOINT ["esphomeyaml"]
|
||||
CMD ["/config", "dashboard"]
|
||||
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,7 @@
|
||||
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 requirements.txt
|
||||
include esphome/dashboard/templates/*.html
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||
recursive-include esphome *.cpp *.h *.tcc
|
||||
recursive-include esphome LICENSE.txt
|
||||
|
||||
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/guides/getting_started_command_line.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](https://www.home-assistant.io).
|
||||
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](https://www.home-assistant.io):** Almost all [Home Assistant](https://www.home-assistant.io) 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).
|
||||
|
||||
24
docker/Dockerfile
Normal file
24
docker/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt /
|
||||
RUN pip3 install --no-cache-dir -r /requirements.txt
|
||||
|
||||
# Then copy esphome and install
|
||||
COPY . .
|
||||
RUN pip3 install --no-cache-dir -e .
|
||||
|
||||
# Settings for dashboard
|
||||
ENV USERNAME="" PASSWORD=""
|
||||
|
||||
# Expose the dashboard to Docker
|
||||
EXPOSE 6052
|
||||
|
||||
# The directory the user should mount their configuration files to
|
||||
WORKDIR /config
|
||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
|
||||
# in every docker command twice
|
||||
ENTRYPOINT ["esphome"]
|
||||
# When no arguments given, start the dashboard in the workdir
|
||||
CMD ["/config", "dashboard"]
|
||||
@@ -1,30 +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/*
|
||||
|
||||
WORKDIR /data
|
||||
13
docker/Dockerfile.dev
Normal file
13
docker/Dockerfile.dev
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM esphome/esphome-base-amd64:2.6.0
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3-wheel \
|
||||
net-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /workspaces
|
||||
ENV SHELL /bin/bash
|
||||
@@ -1,42 +1,23 @@
|
||||
# Dockerfile for HassIO add-on
|
||||
ARG BUILD_FROM=homeassistant/amd64-base-ubuntu:latest
|
||||
ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
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/* && \
|
||||
pip install --no-cache-dir --no-binary :all: platformio && \
|
||||
platformio settings set enable_telemetry No && \
|
||||
platformio settings set check_libraries_interval 1000000 && \
|
||||
platformio settings set check_platformio_interval 1000000 && \
|
||||
platformio settings set check_platforms_interval 1000000
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt /
|
||||
RUN pip3 install --no-cache-dir -r /requirements.txt
|
||||
|
||||
COPY docker/platformio.ini /pio/platformio.ini
|
||||
RUN platformio run -d /pio; rm -rf /pio
|
||||
# Copy root filesystem
|
||||
COPY docker/rootfs/ /
|
||||
|
||||
ARG ESPHOMELIB_VERSION="dev"
|
||||
RUN platformio lib -g install "https://github.com/OttoWinter/esphomelib.git#${ESPHOMELIB_VERSION}"
|
||||
# Then copy esphome and install
|
||||
COPY . /opt/esphome/
|
||||
RUN pip3 install --no-cache-dir -e /opt/esphome
|
||||
|
||||
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"]
|
||||
|
||||
# Build arugments
|
||||
ARG ADDON_ARCH
|
||||
ARG ADDON_VERSION
|
||||
# Build arguments
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Labels
|
||||
LABEL \
|
||||
io.hass.name="esphomeyaml" \
|
||||
io.hass.description="esphomeyaml HassIO add-on for intelligently managing all your ESP8266/ESP32 devices." \
|
||||
io.hass.arch="${ADDON_ARCH}" \
|
||||
io.hass.name="ESPHome" \
|
||||
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version="${ADDON_VERSION}" \
|
||||
io.hass.url="https://esphomelib.com/esphomeyaml/index.html" \
|
||||
maintainer="Otto Winter <contact@otto-winter.com>"
|
||||
io.hass.version=${BUILD_VERSION}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
FROM python:2.7
|
||||
FROM esphome/esphome-lint-base:2.6.0
|
||||
|
||||
COPY requirements.txt /requirements.txt
|
||||
COPY requirements.txt requirements_test.txt /
|
||||
RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt
|
||||
|
||||
RUN pip install -r /requirements.txt && \
|
||||
pip install flake8==3.5.0 pylint==1.9.3 tzlocal pillow
|
||||
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,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
|
||||
23
docker/rootfs/etc/cont-init.d/30-esphome.sh
Executable file
23
docker/rootfs/etc/cont-init.d/30-esphome.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files installs the user ESPHome version if specified
|
||||
# ==============================================================================
|
||||
|
||||
declare esphome_version
|
||||
|
||||
if bashio::config.has_value 'esphome_version'; then
|
||||
esphome_version=$(bashio::config 'esphome_version')
|
||||
if [[ $esphome_version == *":"* ]]; then
|
||||
IFS=':' read -r -a array <<< "$esphome_version"
|
||||
username=${array[0]}
|
||||
ref=${array[1]}
|
||||
else
|
||||
username="esphome"
|
||||
ref=$esphome_version
|
||||
fi
|
||||
full_url="https://github.com/${username}/esphome/archive/${ref}.zip"
|
||||
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
|
||||
pip3 install -U --no-cache-dir "${full_url}" \
|
||||
|| bashio::exit.nok "Failed installing esphome pinned version."
|
||||
fi
|
||||
11
docker/rootfs/etc/cont-init.d/40-migrate.sh
Executable file
11
docker/rootfs/etc/cont-init.d/40-migrate.sh
Executable 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
Normal file
33
docker/rootfs/etc/nginx/nginx.conf
Normal 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
|
||||
566
esphome/__main__.py
Normal file
566
esphome/__main__.py
Normal file
@@ -0,0 +1,566 @@
|
||||
import argparse
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from esphome import const, writer, yaml_util
|
||||
import esphome.codegen as cg
|
||||
from esphome.config import iter_components, read_config, strip_default_ids
|
||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
||||
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
|
||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||
from esphome.helpers import color, indent
|
||||
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \
|
||||
get_serial_ports
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def choose_prompt(options):
|
||||
if not options:
|
||||
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
||||
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
||||
"device is plugged in.")
|
||||
|
||||
if len(options) == 1:
|
||||
return options[0][1]
|
||||
|
||||
safe_print("Found multiple options, please choose one:")
|
||||
for i, (desc, _) in enumerate(options):
|
||||
safe_print(f" [{i+1}] {desc}")
|
||||
|
||||
while True:
|
||||
opt = 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', f"Invalid option: '{opt}'"))
|
||||
return options[opt - 1][1]
|
||||
|
||||
|
||||
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
||||
options = []
|
||||
for port in get_serial_ports():
|
||||
options.append((f"{port.path} ({port.description})", port.path))
|
||||
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
|
||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||
if default == 'OTA':
|
||||
return CORE.address
|
||||
if show_mqtt and 'mqtt' in CORE.config:
|
||||
options.append(("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
|
||||
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(f"{name}:"))
|
||||
if comp.config_schema is not None:
|
||||
conf_str = yaml_util.dump(conf)
|
||||
conf_str = conf_str.replace('//', '')
|
||||
cg.add(cg.LineComment(indent(conf_str)))
|
||||
yield coro(conf)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def write_cpp(config):
|
||||
generate_cpp_contents(config)
|
||||
return write_cpp_file()
|
||||
|
||||
|
||||
def generate_cpp_contents(config):
|
||||
_LOGGER.info("Generating C++ source...")
|
||||
|
||||
for name, component, conf in iter_components(CORE.config):
|
||||
if component.to_code is not None:
|
||||
coro = wrap_to_code(name, component)
|
||||
CORE.add_job(coro, conf)
|
||||
|
||||
CORE.flush_tasks()
|
||||
|
||||
|
||||
def write_cpp_file():
|
||||
writer.write_platformio_project()
|
||||
|
||||
code_s = indent(CORE.cpp_main_section)
|
||||
writer.write_cpp(code_s)
|
||||
return 0
|
||||
|
||||
|
||||
def compile_program(args, config):
|
||||
from esphome import platformio_api
|
||||
|
||||
_LOGGER.info("Compiling app...")
|
||||
return platformio_api.run_compile(config, CORE.verbose)
|
||||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
path = CORE.firmware_bin
|
||||
first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800)
|
||||
|
||||
def run_esptool(baud_rate):
|
||||
cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
|
||||
'--baud', str(baud_rate),
|
||||
'--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
|
||||
|
||||
if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
|
||||
import esptool
|
||||
# pylint: disable=protected-access
|
||||
return run_external_command(esptool._main, *cmd)
|
||||
|
||||
return run_external_process(*cmd)
|
||||
|
||||
rc = run_esptool(first_baudrate)
|
||||
if rc == 0 or first_baudrate == 115200:
|
||||
return rc
|
||||
# Try with 115200 baud rate, with some serial chips the faster baud rates do not work well
|
||||
_LOGGER.info("Upload with baud rate %s failed. Trying again with baud rate 115200.",
|
||||
first_baudrate)
|
||||
return run_esptool(115200)
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
# if upload is to a serial port use platformio, otherwise assume ota
|
||||
if get_port_type(host) == 'SERIAL':
|
||||
from esphome import platformio_api
|
||||
|
||||
if CORE.is_esp8266:
|
||||
return upload_using_esptool(config, host)
|
||||
return platformio_api.run_upload(config, CORE.verbose, host)
|
||||
|
||||
from esphome import espota2
|
||||
|
||||
if CONF_OTA not in config:
|
||||
raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: "
|
||||
"component")
|
||||
|
||||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
password = ota_conf[CONF_PASSWORD]
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
|
||||
|
||||
def show_logs(config, args, port):
|
||||
if 'logger' not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
if get_port_type(port) == 'SERIAL':
|
||||
run_miniterm(config, port)
|
||||
return 0
|
||||
if get_port_type(port) == 'NETWORK' and 'api' in config:
|
||||
from esphome.api.client import run_logs
|
||||
|
||||
return run_logs(config, port)
|
||||
if get_port_type(port) == 'MQTT' and 'mqtt' in config:
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
|
||||
|
||||
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||
|
||||
|
||||
def clean_mqtt(config, args):
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
|
||||
|
||||
|
||||
def setup_log(debug=False, quiet=False):
|
||||
if debug:
|
||||
log_level = logging.DEBUG
|
||||
CORE.verbose = True
|
||||
elif quiet:
|
||||
log_level = logging.CRITICAL
|
||||
else:
|
||||
log_level = logging.INFO
|
||||
logging.basicConfig(level=log_level)
|
||||
fmt = "%(levelname)s %(message)s"
|
||||
colorfmt = f"%(log_color)s{fmt}%(reset)s"
|
||||
datefmt = '%H:%M:%S'
|
||||
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
|
||||
try:
|
||||
import colorama
|
||||
colorama.init(strip=True)
|
||||
|
||||
from colorlog import ColoredFormatter
|
||||
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
|
||||
colorfmt,
|
||||
datefmt=datefmt,
|
||||
reset=True,
|
||||
log_colors={
|
||||
'DEBUG': 'cyan',
|
||||
'INFO': 'green',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red',
|
||||
}
|
||||
))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def command_wizard(args):
|
||||
from esphome import wizard
|
||||
|
||||
return wizard.wizard(args.configuration[0])
|
||||
|
||||
|
||||
def command_config(args, config):
|
||||
_LOGGER.info("Configuration is valid!")
|
||||
if not CORE.verbose:
|
||||
config = strip_default_ids(config)
|
||||
safe_print(yaml_util.dump(config))
|
||||
return 0
|
||||
|
||||
|
||||
def command_vscode(args):
|
||||
from esphome import vscode
|
||||
|
||||
CORE.config_path = args.configuration[0]
|
||||
vscode.read_config(args)
|
||||
|
||||
|
||||
def command_compile(args, config):
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
if args.only_generate:
|
||||
_LOGGER.info("Successfully generated source code.")
|
||||
return 0
|
||||
exit_code = compile_program(args, config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
return 0
|
||||
|
||||
|
||||
def command_upload(args, config):
|
||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||
show_ota=True, show_mqtt=False, show_api=False)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
return 0
|
||||
|
||||
|
||||
def command_logs(args, config):
|
||||
port = choose_upload_log_host(default=args.serial_port, check_default=None,
|
||||
show_ota=False, show_mqtt=True, show_api=True)
|
||||
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("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("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(f"Version: {const.__version__}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_clean(args, config):
|
||||
try:
|
||||
writer.clean_build()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Error deleting build files: %s", err)
|
||||
return 1
|
||||
_LOGGER.info("Done!")
|
||||
return 0
|
||||
|
||||
|
||||
def command_dashboard(args):
|
||||
from esphome.dashboard import dashboard
|
||||
|
||||
return dashboard.start_web_server(args)
|
||||
|
||||
|
||||
def command_update_all(args):
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration[0])
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
middle_text = f" {middle_text} "
|
||||
width = len(click.unstyle(middle_text))
|
||||
half_line = "=" * ((twidth - width) // 2)
|
||||
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||
|
||||
for f in files:
|
||||
print("Updating {}".format(color('cyan', f)))
|
||||
print('-' * twidth)
|
||||
print()
|
||||
rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port',
|
||||
'OTA')
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
|
||||
success[f] = True
|
||||
else:
|
||||
print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
|
||||
success[f] = False
|
||||
|
||||
print()
|
||||
print()
|
||||
print()
|
||||
|
||||
print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
|
||||
failed = 0
|
||||
for f in files:
|
||||
if success[f]:
|
||||
print(" - {}: {}".format(f, color('green', 'SUCCESS')))
|
||||
else:
|
||||
print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
|
||||
failed += 1
|
||||
return failed
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
'wizard': command_wizard,
|
||||
'version': command_version,
|
||||
'dashboard': command_dashboard,
|
||||
'vscode': command_vscode,
|
||||
'update-all': command_update_all,
|
||||
}
|
||||
|
||||
POST_CONFIG_ACTIONS = {
|
||||
'config': command_config,
|
||||
'compile': command_compile,
|
||||
'upload': command_upload,
|
||||
'logs': command_logs,
|
||||
'run': command_run,
|
||||
'clean-mqtt': command_clean_mqtt,
|
||||
'mqtt-fingerprint': command_mqtt_fingerprint,
|
||||
'clean': command_clean,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
|
||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
||||
action='store_true')
|
||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
||||
parser.add_argument('-s', '--substitution', nargs=2, action='append',
|
||||
help='Add a substitution', metavar=('key', 'value'))
|
||||
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
||||
|
||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||
subparsers.required = True
|
||||
subparsers.add_parser('config', help='Validate the configuration and spit it out.')
|
||||
|
||||
parser_compile = subparsers.add_parser('compile',
|
||||
help='Read the configuration and compile a program.')
|
||||
parser_compile.add_argument('--only-generate',
|
||||
help="Only generate source code, do not compile.",
|
||||
action='store_true')
|
||||
|
||||
parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
|
||||
'and upload the latest binary.')
|
||||
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
|
||||
parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
|
||||
'and show all MQTT logs.')
|
||||
parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
||||
parser_logs.add_argument('--username', help='Manually set the username.')
|
||||
parser_logs.add_argument('--password', help='Manually set the password.')
|
||||
parser_logs.add_argument('--client-id', help='Manually set the client id.')
|
||||
parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
|
||||
parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
|
||||
'upload it, and start MQTT logs.')
|
||||
parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
|
||||
"For example /dev/cu.SLAB_USBtoUART.")
|
||||
parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
|
||||
action='store_true')
|
||||
parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
|
||||
parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
|
||||
parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
|
||||
parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
|
||||
|
||||
parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
|
||||
"retain messages.")
|
||||
parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.')
|
||||
parser_clean.add_argument('--username', help='Manually set the username.')
|
||||
parser_clean.add_argument('--password', help='Manually set the password.')
|
||||
parser_clean.add_argument('--client-id', help='Manually set the client id.')
|
||||
|
||||
subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
|
||||
"you through setting up esphome.")
|
||||
|
||||
subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
|
||||
|
||||
subparsers.add_parser('version', help="Print the esphome version and exit.")
|
||||
|
||||
subparsers.add_parser('clean', help="Delete all temporary build files.")
|
||||
|
||||
dashboard = subparsers.add_parser('dashboard',
|
||||
help="Create a simple web server for a dashboard.")
|
||||
dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
|
||||
type=int, default=6052)
|
||||
dashboard.add_argument("--username", help="The optional username to require "
|
||||
"for authentication.",
|
||||
type=str, default='')
|
||||
dashboard.add_argument("--password", help="The optional password to require "
|
||||
"for authentication.",
|
||||
type=str, default='')
|
||||
dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
|
||||
action='store_true')
|
||||
dashboard.add_argument("--hassio",
|
||||
help=argparse.SUPPRESS,
|
||||
action="store_true")
|
||||
dashboard.add_argument("--socket",
|
||||
help="Make the dashboard serve under a unix socket", type=str)
|
||||
|
||||
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
||||
vscode.add_argument('--ace', action='store_true')
|
||||
|
||||
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
|
||||
|
||||
def run_esphome(argv):
|
||||
args = parse_args(argv)
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
setup_log(args.verbose, args.quiet)
|
||||
if args.command != 'version' and not args.configuration:
|
||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||
return 1
|
||||
|
||||
if sys.version_info < (3, 6, 0):
|
||||
_LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
|
||||
"with this Python version. Please reinstall ESPHome with Python 3.6+")
|
||||
return 1
|
||||
|
||||
if args.command in PRE_CONFIG_ACTIONS:
|
||||
try:
|
||||
return PRE_CONFIG_ACTIONS[args.command](args)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
|
||||
for conf_path in args.configuration:
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
config = read_config(dict(args.substitution) if args.substitution else {})
|
||||
if config is None:
|
||||
return 1
|
||||
CORE.config = config
|
||||
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
safe_print(f"Unknown command {args.command}")
|
||||
|
||||
try:
|
||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
if rc != 0:
|
||||
return rc
|
||||
|
||||
CORE.reset()
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
return run_esphome(sys.argv)
|
||||
except EsphomeError as e:
|
||||
_LOGGER.error(e)
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
2485
esphome/api/api_pb2.py
Normal file
2485
esphome/api/api_pb2.py
Normal file
File diff suppressed because one or more lines are too long
490
esphome/api/client.py
Normal file
490
esphome/api/client.py
Normal file
@@ -0,0 +1,490 @@
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from typing import Optional # noqa
|
||||
from google.protobuf import message # noqa
|
||||
|
||||
from esphome import const
|
||||
import esphome.api.api_pb2 as pb
|
||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.helpers import resolve_ip_address, indent, color
|
||||
from esphome.util import safe_print
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APIConnectionError(EsphomeError):
|
||||
pass
|
||||
|
||||
|
||||
MESSAGE_TYPE_TO_PROTO = {
|
||||
1: pb.HelloRequest,
|
||||
2: pb.HelloResponse,
|
||||
3: pb.ConnectRequest,
|
||||
4: pb.ConnectResponse,
|
||||
5: pb.DisconnectRequest,
|
||||
6: pb.DisconnectResponse,
|
||||
7: pb.PingRequest,
|
||||
8: pb.PingResponse,
|
||||
9: pb.DeviceInfoRequest,
|
||||
10: pb.DeviceInfoResponse,
|
||||
11: pb.ListEntitiesRequest,
|
||||
12: pb.ListEntitiesBinarySensorResponse,
|
||||
13: pb.ListEntitiesCoverResponse,
|
||||
14: pb.ListEntitiesFanResponse,
|
||||
15: pb.ListEntitiesLightResponse,
|
||||
16: pb.ListEntitiesSensorResponse,
|
||||
17: pb.ListEntitiesSwitchResponse,
|
||||
18: pb.ListEntitiesTextSensorResponse,
|
||||
19: pb.ListEntitiesDoneResponse,
|
||||
20: pb.SubscribeStatesRequest,
|
||||
21: pb.BinarySensorStateResponse,
|
||||
22: pb.CoverStateResponse,
|
||||
23: pb.FanStateResponse,
|
||||
24: pb.LightStateResponse,
|
||||
25: pb.SensorStateResponse,
|
||||
26: pb.SwitchStateResponse,
|
||||
27: pb.TextSensorStateResponse,
|
||||
28: pb.SubscribeLogsRequest,
|
||||
29: pb.SubscribeLogsResponse,
|
||||
30: pb.CoverCommandRequest,
|
||||
31: pb.FanCommandRequest,
|
||||
32: pb.LightCommandRequest,
|
||||
33: pb.SwitchCommandRequest,
|
||||
34: pb.SubscribeServiceCallsRequest,
|
||||
35: pb.ServiceCallResponse,
|
||||
36: pb.GetTimeRequest,
|
||||
37: pb.GetTimeResponse,
|
||||
}
|
||||
|
||||
|
||||
def _varuint_to_bytes(value):
|
||||
if value <= 0x7F:
|
||||
return bytes([value])
|
||||
|
||||
ret = bytes()
|
||||
while value:
|
||||
temp = value & 0x7F
|
||||
value >>= 7
|
||||
if value:
|
||||
ret += bytes([temp | 0x80])
|
||||
else:
|
||||
ret += bytes([temp])
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _bytes_to_varuint(value):
|
||||
result = 0
|
||||
bitpos = 0
|
||||
for val in value:
|
||||
result |= (val & 0x7F) << bitpos
|
||||
bitpos += 7
|
||||
if (val & 0x80) == 0:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes,not-callable
|
||||
class APIClient(threading.Thread):
|
||||
def __init__(self, address, port, password):
|
||||
threading.Thread.__init__(self)
|
||||
self._address = address # type: str
|
||||
self._port = port # type: int
|
||||
self._password = password # type: Optional[str]
|
||||
self._socket = None # type: Optional[socket.socket]
|
||||
self._socket_open_event = threading.Event()
|
||||
self._socket_write_lock = threading.Lock()
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
self._message_handlers = []
|
||||
self._keepalive = 5
|
||||
self._ping_timer = None
|
||||
|
||||
self.on_disconnect = None
|
||||
self.on_connect = None
|
||||
self.on_login = None
|
||||
self.auto_reconnect = False
|
||||
self._running_event = threading.Event()
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
@property
|
||||
def stopped(self):
|
||||
return self._stop_event.is_set()
|
||||
|
||||
def _refresh_ping(self):
|
||||
if self._ping_timer is not None:
|
||||
self._ping_timer.cancel()
|
||||
self._ping_timer = None
|
||||
|
||||
def func():
|
||||
self._ping_timer = None
|
||||
|
||||
if self._connected:
|
||||
try:
|
||||
self.ping()
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error(err)
|
||||
else:
|
||||
self._refresh_ping()
|
||||
|
||||
self._ping_timer = threading.Timer(self._keepalive, func)
|
||||
self._ping_timer.start()
|
||||
|
||||
def _cancel_ping(self):
|
||||
if self._ping_timer is not None:
|
||||
self._ping_timer.cancel()
|
||||
self._ping_timer = None
|
||||
|
||||
def _close_socket(self):
|
||||
self._cancel_ping()
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
self._socket_open_event.clear()
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
self._message_handlers = []
|
||||
|
||||
def stop(self, force=False):
|
||||
if self.stopped:
|
||||
raise ValueError
|
||||
|
||||
if self._connected and not force:
|
||||
try:
|
||||
self.disconnect()
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
self._stop_event.set()
|
||||
if not force:
|
||||
self.join()
|
||||
|
||||
def connect(self):
|
||||
if not self._running_event.wait(0.1):
|
||||
raise APIConnectionError("You need to call start() first!")
|
||||
|
||||
if self._connected:
|
||||
self.disconnect(on_disconnect=False)
|
||||
|
||||
try:
|
||||
ip = resolve_ip_address(self._address)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.warning("Error resolving IP address of %s. Is it connected to WiFi?",
|
||||
self._address)
|
||||
_LOGGER.warning("(If this error persists, please set a static IP address: "
|
||||
"https://esphome.io/components/wifi.html#manual-ips)")
|
||||
raise APIConnectionError(err) from err
|
||||
|
||||
_LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.settimeout(10.0)
|
||||
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
try:
|
||||
self._socket.connect((ip, self._port))
|
||||
except OSError as err:
|
||||
err = APIConnectionError(f"Error connecting to {ip}: {err}")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
self._socket.settimeout(0.1)
|
||||
|
||||
self._socket_open_event.set()
|
||||
|
||||
hello = pb.HelloRequest()
|
||||
hello.client_info = f'ESPHome v{const.__version__}'
|
||||
try:
|
||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||
except APIConnectionError as err:
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
_LOGGER.debug("Successfully connected to %s ('%s' API=%s.%s)", self._address,
|
||||
resp.server_info, resp.api_version_major, resp.api_version_minor)
|
||||
self._connected = True
|
||||
self._refresh_ping()
|
||||
if self.on_connect is not None:
|
||||
self.on_connect()
|
||||
|
||||
def _check_connected(self):
|
||||
if not self._connected:
|
||||
err = APIConnectionError("Must be connected!")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
|
||||
def login(self):
|
||||
self._check_connected()
|
||||
if self._authenticated:
|
||||
raise APIConnectionError("Already logged in!")
|
||||
|
||||
connect = pb.ConnectRequest()
|
||||
if self._password is not None:
|
||||
connect.password = self._password
|
||||
resp = self._send_message_await_response(connect, pb.ConnectResponse)
|
||||
if resp.invalid_password:
|
||||
raise APIConnectionError("Invalid password!")
|
||||
|
||||
self._authenticated = True
|
||||
if self.on_login is not None:
|
||||
self.on_login()
|
||||
|
||||
def _fatal_error(self, err):
|
||||
was_connected = self._connected
|
||||
|
||||
self._close_socket()
|
||||
|
||||
if was_connected and self.on_disconnect is not None:
|
||||
self.on_disconnect(err)
|
||||
|
||||
def _write(self, data): # type: (bytes) -> None
|
||||
if self._socket is None:
|
||||
raise APIConnectionError("Socket closed")
|
||||
|
||||
# _LOGGER.debug("Write: %s", format_bytes(data))
|
||||
with self._socket_write_lock:
|
||||
try:
|
||||
self._socket.sendall(data)
|
||||
except OSError as err:
|
||||
err = APIConnectionError(f"Error while writing data: {err}")
|
||||
self._fatal_error(err)
|
||||
raise err
|
||||
|
||||
def _send_message(self, msg):
|
||||
# type: (message.Message) -> None
|
||||
for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
|
||||
if isinstance(msg, klass):
|
||||
break
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
encoded = msg.SerializeToString()
|
||||
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
|
||||
req = bytes([0])
|
||||
req += _varuint_to_bytes(len(encoded))
|
||||
req += _varuint_to_bytes(message_type)
|
||||
req += encoded
|
||||
self._write(req)
|
||||
|
||||
def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5):
|
||||
event = threading.Event()
|
||||
responses = []
|
||||
|
||||
def on_message(resp):
|
||||
if do_append(resp):
|
||||
responses.append(resp)
|
||||
if do_stop(resp):
|
||||
event.set()
|
||||
|
||||
self._message_handlers.append(on_message)
|
||||
self._send_message(send_msg)
|
||||
ret = event.wait(timeout)
|
||||
try:
|
||||
self._message_handlers.remove(on_message)
|
||||
except ValueError:
|
||||
pass
|
||||
if not ret:
|
||||
raise APIConnectionError("Timeout while waiting for message response!")
|
||||
return responses
|
||||
|
||||
def _send_message_await_response(self, send_msg, response_type, timeout=5):
|
||||
def is_response(msg):
|
||||
return isinstance(msg, response_type)
|
||||
|
||||
return self._send_message_await_response_complex(send_msg, is_response, is_response,
|
||||
timeout)[0]
|
||||
|
||||
def device_info(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(pb.DeviceInfoRequest(), pb.DeviceInfoResponse)
|
||||
|
||||
def ping(self):
|
||||
self._check_connected()
|
||||
return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
|
||||
|
||||
def disconnect(self, on_disconnect=True):
|
||||
self._check_connected()
|
||||
|
||||
try:
|
||||
self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
|
||||
except APIConnectionError:
|
||||
pass
|
||||
self._close_socket()
|
||||
|
||||
if self.on_disconnect is not None and on_disconnect:
|
||||
self.on_disconnect(None)
|
||||
|
||||
def _check_authenticated(self):
|
||||
if not self._authenticated:
|
||||
raise APIConnectionError("Must login first!")
|
||||
|
||||
def subscribe_logs(self, on_log, log_level=7, dump_config=False):
|
||||
self._check_authenticated()
|
||||
|
||||
def on_msg(msg):
|
||||
if isinstance(msg, pb.SubscribeLogsResponse):
|
||||
on_log(msg)
|
||||
|
||||
self._message_handlers.append(on_msg)
|
||||
req = pb.SubscribeLogsRequest(dump_config=dump_config)
|
||||
req.level = log_level
|
||||
self._send_message(req)
|
||||
|
||||
def _recv(self, amount):
|
||||
ret = bytes()
|
||||
if amount == 0:
|
||||
return ret
|
||||
|
||||
while len(ret) < amount:
|
||||
if self.stopped:
|
||||
raise APIConnectionError("Stopped!")
|
||||
if not self._socket_open_event.is_set():
|
||||
raise APIConnectionError("No socket!")
|
||||
try:
|
||||
val = self._socket.recv(amount - len(ret))
|
||||
except AttributeError as err:
|
||||
raise APIConnectionError("Socket was closed") from err
|
||||
except socket.timeout:
|
||||
continue
|
||||
except OSError as err:
|
||||
raise APIConnectionError(f"Error while receiving data: {err}") from err
|
||||
ret += val
|
||||
return ret
|
||||
|
||||
def _recv_varint(self):
|
||||
raw = bytes()
|
||||
while not raw or raw[-1] & 0x80:
|
||||
raw += self._recv(1)
|
||||
return _bytes_to_varuint(raw)
|
||||
|
||||
def _run_once(self):
|
||||
if not self._socket_open_event.wait(0.1):
|
||||
return
|
||||
|
||||
# Preamble
|
||||
if self._recv(1)[0] != 0x00:
|
||||
raise APIConnectionError("Invalid preamble")
|
||||
|
||||
length = self._recv_varint()
|
||||
msg_type = self._recv_varint()
|
||||
|
||||
raw_msg = self._recv(length)
|
||||
if msg_type not in MESSAGE_TYPE_TO_PROTO:
|
||||
_LOGGER.debug("Skipping message type %s", msg_type)
|
||||
return
|
||||
|
||||
msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
|
||||
msg.ParseFromString(raw_msg)
|
||||
_LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
|
||||
for msg_handler in self._message_handlers[:]:
|
||||
msg_handler(msg)
|
||||
self._handle_internal_messages(msg)
|
||||
|
||||
def run(self):
|
||||
self._running_event.set()
|
||||
while not self.stopped:
|
||||
try:
|
||||
self._run_once()
|
||||
except APIConnectionError as err:
|
||||
if self.stopped:
|
||||
break
|
||||
if self._connected:
|
||||
_LOGGER.error("Error while reading incoming messages: %s", err)
|
||||
self._fatal_error(err)
|
||||
self._running_event.clear()
|
||||
|
||||
def _handle_internal_messages(self, msg):
|
||||
if isinstance(msg, pb.DisconnectRequest):
|
||||
self._send_message(pb.DisconnectResponse())
|
||||
if self._socket is not None:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
self._connected = False
|
||||
if self.on_disconnect is not None:
|
||||
self.on_disconnect(None)
|
||||
elif isinstance(msg, pb.PingRequest):
|
||||
self._send_message(pb.PingResponse())
|
||||
elif isinstance(msg, pb.GetTimeRequest):
|
||||
resp = pb.GetTimeResponse()
|
||||
resp.epoch_seconds = int(time.time())
|
||||
self._send_message(resp)
|
||||
|
||||
|
||||
def run_logs(config, address):
|
||||
conf = config['api']
|
||||
port = conf[CONF_PORT]
|
||||
password = conf[CONF_PASSWORD]
|
||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||
|
||||
cli = APIClient(address, port, password)
|
||||
stopping = False
|
||||
retry_timer = []
|
||||
|
||||
has_connects = []
|
||||
|
||||
def try_connect(err, tries=0):
|
||||
if stopping:
|
||||
return
|
||||
|
||||
if err:
|
||||
_LOGGER.warning("Disconnected from API: %s", err)
|
||||
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
|
||||
error = None
|
||||
try:
|
||||
cli.connect()
|
||||
cli.login()
|
||||
except APIConnectionError as err2: # noqa
|
||||
error = err2
|
||||
|
||||
if error is None:
|
||||
_LOGGER.info("Successfully connected to %s", address)
|
||||
return
|
||||
|
||||
wait_time = int(min(1.5**min(tries, 100), 30))
|
||||
if not has_connects:
|
||||
_LOGGER.warning("Initial connection failed. The ESP might not be connected "
|
||||
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||
error, wait_time)
|
||||
else:
|
||||
_LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||
error, wait_time)
|
||||
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
|
||||
timer.start()
|
||||
retry_timer.append(timer)
|
||||
|
||||
def on_log(msg):
|
||||
time_ = datetime.now().time().strftime('[%H:%M:%S]')
|
||||
text = msg.message
|
||||
if msg.send_failed:
|
||||
text = color('white', '(Message skipped because it was too big to fit in '
|
||||
'TCP buffer - This is only cosmetic)')
|
||||
safe_print(time_ + text)
|
||||
|
||||
def on_login():
|
||||
try:
|
||||
cli.subscribe_logs(on_log, dump_config=not has_connects)
|
||||
has_connects.append(True)
|
||||
except APIConnectionError:
|
||||
cli.disconnect()
|
||||
|
||||
cli.on_disconnect = try_connect
|
||||
cli.on_login = on_login
|
||||
cli.start()
|
||||
|
||||
try:
|
||||
try_connect(None)
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
stopping = True
|
||||
cli.stop(True)
|
||||
while retry_timer:
|
||||
retry_timer.pop(0).cancel()
|
||||
return 0
|
||||
271
esphome/automation.py
Normal file
271
esphome/automation.py
Normal file
@@ -0,0 +1,271 @@
|
||||
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):
|
||||
return maybe_conf(CONF_ID, *validators)
|
||||
|
||||
|
||||
def maybe_conf(conf, *validators):
|
||||
validator = cv.All(*validators)
|
||||
|
||||
def validate(value):
|
||||
if isinstance(value, dict):
|
||||
return validator(value)
|
||||
with cv.remove_prepend_path([conf]):
|
||||
return validator({conf: 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 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise err
|
||||
if '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_, int_, std_ns, std_string,
|
||||
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
|
||||
esphome_ns, App, Nameable, Component, ComponentPtr,
|
||||
PollingComponent, Application, optional, arduino_json_ns, JsonObject,
|
||||
JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin)
|
||||
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/ac_dimmer/__init__.py
Normal file
0
esphome/components/ac_dimmer/__init__.py
Normal file
217
esphome/components/ac_dimmer/ac_dimmer.cpp
Normal file
217
esphome/components/ac_dimmer/ac_dimmer.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "ac_dimmer.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include <core_esp8266_waveform.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
|
||||
static const char *TAG = "ac_dimmer";
|
||||
|
||||
// Global array to store dimmer objects
|
||||
static AcDimmerDataStore *all_dimmers[32];
|
||||
|
||||
/// Time in microseconds the gate should be held high
|
||||
/// 10µs should be long enough for most triacs
|
||||
/// For reference: BT136 datasheet says 2µs nominal (page 7)
|
||||
static uint32_t GATE_ENABLE_TIME = 10;
|
||||
|
||||
/// Function called from timer interrupt
|
||||
/// Input is current time in microseconds (micros())
|
||||
/// Returns when next "event" is expected in µs, or 0 if no such event known.
|
||||
uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
// If no ZC signal received yet.
|
||||
if (this->crossed_zero_at == 0)
|
||||
return 0;
|
||||
|
||||
uint32_t time_since_zc = now - this->crossed_zero_at;
|
||||
if (this->value == 65535 || this->value == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
|
||||
this->enable_time_us = 0;
|
||||
this->gate_pin->digital_write(true);
|
||||
// Prevent too short pulses
|
||||
this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
|
||||
}
|
||||
if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) {
|
||||
this->disable_time_us = 0;
|
||||
this->gate_pin->digital_write(false);
|
||||
}
|
||||
|
||||
if (time_since_zc < this->enable_time_us)
|
||||
// Next event is enable, return time until that event
|
||||
return this->enable_time_us - time_since_zc;
|
||||
else if (time_since_zc < disable_time_us) {
|
||||
// Next event is disable, return time until that event
|
||||
return this->disable_time_us - time_since_zc;
|
||||
}
|
||||
|
||||
if (time_since_zc >= this->cycle_time_us) {
|
||||
// Already past last cycle time, schedule next call shortly
|
||||
return 100;
|
||||
}
|
||||
|
||||
return this->cycle_time_us - time_since_zc;
|
||||
}
|
||||
|
||||
/// Run timer interrupt code and return in how many µs the next event is expected
|
||||
uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
|
||||
// run at least with 1kHz
|
||||
uint32_t min_dt_us = 1000;
|
||||
uint32_t now = micros();
|
||||
for (auto *dimmer : all_dimmers) {
|
||||
if (dimmer == nullptr)
|
||||
// no more dimmers
|
||||
break;
|
||||
uint32_t res = dimmer->timer_intr(now);
|
||||
if (res != 0 && res < min_dt_us)
|
||||
min_dt_us = res;
|
||||
}
|
||||
// return time until next timer1 interrupt in µs
|
||||
return min_dt_us;
|
||||
}
|
||||
|
||||
/// GPIO interrupt routine, called when ZC pin triggers
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
uint32_t prev_crossed = this->crossed_zero_at;
|
||||
|
||||
// 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms
|
||||
// in any case the cycle last at least 5ms
|
||||
this->crossed_zero_at = micros();
|
||||
uint32_t cycle_time = this->crossed_zero_at - prev_crossed;
|
||||
if (cycle_time > 5000) {
|
||||
this->cycle_time_us = cycle_time;
|
||||
} else {
|
||||
// Otherwise this is noise and this is 2nd (or 3rd...) fall in the same pulse
|
||||
// Consider this is the right fall edge and accumulate the cycle time instead
|
||||
this->cycle_time_us += cycle_time;
|
||||
}
|
||||
|
||||
if (this->value == 65535) {
|
||||
// fully on, enable output immediately
|
||||
this->gate_pin->digital_write(true);
|
||||
} else if (this->init_cycle) {
|
||||
// send a full cycle
|
||||
this->init_cycle = false;
|
||||
this->enable_time_us = 0;
|
||||
this->disable_time_us = cycle_time_us;
|
||||
} else if (this->value == 0) {
|
||||
// fully off, disable output immediately
|
||||
this->gate_pin->digital_write(false);
|
||||
} else {
|
||||
if (this->method == DIM_METHOD_TRAILING) {
|
||||
this->enable_time_us = 1; // cannot be 0
|
||||
this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
|
||||
} else {
|
||||
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
|
||||
// also take into account min_power
|
||||
auto min_us = this->cycle_time_us * this->min_power / 1000;
|
||||
this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
|
||||
if (this->method == DIM_METHOD_LEADING_PULSE) {
|
||||
// Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
|
||||
// this is for brightness near 99%
|
||||
this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
|
||||
} else {
|
||||
this->gate_pin->digital_write(false);
|
||||
this->disable_time_us = this->cycle_time_us;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||
// Attaching pin interrupts on the same pin will override the previous interupt
|
||||
// However, the user expects that multiple dimmers sharing the same ZC pin will work.
|
||||
// We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
|
||||
// if any of them are using the same ZC pin, and also trigger the interrupt for *them*.
|
||||
for (auto *dimmer : all_dimmers) {
|
||||
if (dimmer == nullptr)
|
||||
break;
|
||||
if (dimmer->zero_cross_pin_number == store->zero_cross_pin_number) {
|
||||
dimmer->gpio_intr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// ESP32 implementation, uses basically the same code but needs to wrap
|
||||
// timer_interrupt() function to auto-reschedule
|
||||
static hw_timer_t *dimmer_timer = nullptr;
|
||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||
#endif
|
||||
|
||||
void AcDimmer::setup() {
|
||||
// extend all_dimmers array with our dimmer
|
||||
|
||||
// Need to be sure the zero cross pin is setup only once, ESP8266 fails and ESP32 seems to fail silently
|
||||
auto setup_zero_cross_pin = true;
|
||||
|
||||
for (auto &all_dimmer : all_dimmers) {
|
||||
if (all_dimmer == nullptr) {
|
||||
all_dimmer = &this->store_;
|
||||
break;
|
||||
}
|
||||
if (all_dimmer->zero_cross_pin_number == this->zero_cross_pin_->get_pin()) {
|
||||
setup_zero_cross_pin = false;
|
||||
}
|
||||
}
|
||||
|
||||
this->gate_pin_->setup();
|
||||
this->store_.gate_pin = this->gate_pin_->to_isr();
|
||||
this->store_.zero_cross_pin_number = this->zero_cross_pin_->get_pin();
|
||||
this->store_.min_power = static_cast<uint16_t>(this->min_power_ * 1000);
|
||||
this->min_power_ = 0;
|
||||
this->store_.method = this->method_;
|
||||
|
||||
if (setup_zero_cross_pin) {
|
||||
this->zero_cross_pin_->setup();
|
||||
this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
|
||||
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING);
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
// Uses ESP8266 waveform (soft PWM) class
|
||||
// PWM and AcDimmer can even run at the same time this way
|
||||
setTimer1Callback(&timer_interrupt);
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// 80 Divider -> 1 count=1µs
|
||||
dimmer_timer = timerBegin(0, 80, true);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
|
||||
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
||||
// are not callable from ISR (placed in flash storage).
|
||||
// Here we just use an interrupt firing every 50 µs.
|
||||
timerAlarmWrite(dimmer_timer, 50, true);
|
||||
timerAlarmEnable(dimmer_timer);
|
||||
#endif
|
||||
}
|
||||
void AcDimmer::write_state(float state) {
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
if (new_value != 0 && this->store_.value == 0)
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
this->store_.value = new_value;
|
||||
}
|
||||
void AcDimmer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AcDimmer:");
|
||||
LOG_PIN(" Output Pin: ", this->gate_pin_);
|
||||
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f);
|
||||
ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
|
||||
if (method_ == DIM_METHOD_LEADING_PULSE)
|
||||
ESP_LOGCONFIG(TAG, " Method: leading pulse");
|
||||
else if (method_ == DIM_METHOD_LEADING)
|
||||
ESP_LOGCONFIG(TAG, " Method: leading");
|
||||
else
|
||||
ESP_LOGCONFIG(TAG, " Method: trailing");
|
||||
|
||||
LOG_FLOAT_OUTPUT(this);
|
||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||
}
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
66
esphome/components/ac_dimmer/ac_dimmer.h
Normal file
66
esphome/components/ac_dimmer/ac_dimmer.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ac_dimmer {
|
||||
|
||||
enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING };
|
||||
|
||||
struct AcDimmerDataStore {
|
||||
/// Zero-cross pin
|
||||
ISRInternalGPIOPin *zero_cross_pin;
|
||||
/// Zero-cross pin number - used to share ZC pin across multiple dimmers
|
||||
uint8_t zero_cross_pin_number;
|
||||
/// Output pin to write to
|
||||
ISRInternalGPIOPin *gate_pin;
|
||||
/// Value of the dimmer - 0 to 65535.
|
||||
uint16_t value;
|
||||
/// Minimum power for activation
|
||||
uint16_t min_power;
|
||||
/// Time between the last two ZC pulses
|
||||
uint32_t cycle_time_us;
|
||||
/// Time (in micros()) of last ZC signal
|
||||
uint32_t crossed_zero_at;
|
||||
/// Time since last ZC pulse to enable gate pin. 0 means not set.
|
||||
uint32_t enable_time_us;
|
||||
/// Time since last ZC pulse to disable gate pin. 0 means no disable.
|
||||
uint32_t disable_time_us;
|
||||
/// Set to send the first half ac cycle complete
|
||||
bool init_cycle;
|
||||
/// Dimmer method
|
||||
DimMethod method;
|
||||
|
||||
uint32_t timer_intr(uint32_t now);
|
||||
|
||||
void gpio_intr();
|
||||
static void s_gpio_intr(AcDimmerDataStore *store);
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static void s_timer_intr();
|
||||
#endif
|
||||
};
|
||||
|
||||
class AcDimmer : public output::FloatOutput, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; }
|
||||
void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
|
||||
void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
|
||||
void set_method(DimMethod method) { method_ = method; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
|
||||
GPIOPin *gate_pin_;
|
||||
GPIOPin *zero_cross_pin_;
|
||||
AcDimmerDataStore store_;
|
||||
bool init_with_half_cycle_;
|
||||
DimMethod method_;
|
||||
};
|
||||
|
||||
} // namespace ac_dimmer
|
||||
} // namespace esphome
|
||||
45
esphome/components/ac_dimmer/output.py
Normal file
45
esphome/components/ac_dimmer/output.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
||||
|
||||
CODEOWNERS = ['@glmnet']
|
||||
|
||||
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
|
||||
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
|
||||
|
||||
DimMethod = ac_dimmer_ns.enum('DimMethod')
|
||||
DIM_METHODS = {
|
||||
'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE,
|
||||
'LEADING': DimMethod.DIM_METHOD_LEADING,
|
||||
'TRAILING': DimMethod.DIM_METHOD_TRAILING,
|
||||
}
|
||||
|
||||
CONF_GATE_PIN = 'gate_pin'
|
||||
CONF_ZERO_CROSS_PIN = 'zero_cross_pin'
|
||||
CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle'
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({
|
||||
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
# override default min power to 10%
|
||||
if CONF_MIN_POWER not in config:
|
||||
config[CONF_MIN_POWER] = 0.1
|
||||
yield output.register_output(var, config)
|
||||
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_GATE_PIN])
|
||||
cg.add(var.set_gate_pin(pin))
|
||||
pin = yield cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
|
||||
cg.add(var.set_zero_cross_pin(pin))
|
||||
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
|
||||
cg.add(var.set_method(config[CONF_METHOD]))
|
||||
24
esphome/components/adalight/__init__.py
Normal file
24
esphome/components/adalight/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.components.light.types import AddressableLightEffect
|
||||
from esphome.components.light.effects import register_addressable_effect
|
||||
from esphome.const import CONF_NAME, CONF_UART_ID
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
|
||||
adalight_ns = cg.esphome_ns.namespace('adalight')
|
||||
AdalightLightEffect = adalight_ns.class_(
|
||||
'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({})
|
||||
|
||||
|
||||
@register_addressable_effect('adalight', AdalightLightEffect, "Adalight", {
|
||||
cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)
|
||||
})
|
||||
def adalight_light_effect_to_code(config, effect_id):
|
||||
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
|
||||
yield uart.register_uart_device(effect, config)
|
||||
|
||||
yield effect
|
||||
140
esphome/components/adalight/adalight_light_effect.cpp
Normal file
140
esphome/components/adalight/adalight_light_effect.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "adalight_light_effect.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adalight {
|
||||
|
||||
static const char *TAG = "adalight_light_effect";
|
||||
|
||||
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
|
||||
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
|
||||
|
||||
AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {}
|
||||
|
||||
void AdalightLightEffect::start() {
|
||||
AddressableLightEffect::start();
|
||||
|
||||
last_ack_ = 0;
|
||||
last_byte_ = 0;
|
||||
last_reset_ = 0;
|
||||
}
|
||||
|
||||
void AdalightLightEffect::stop() {
|
||||
frame_.resize(0);
|
||||
|
||||
AddressableLightEffect::stop();
|
||||
}
|
||||
|
||||
int AdalightLightEffect::get_frame_size_(int led_count) const {
|
||||
// 3 bytes: Ada
|
||||
// 2 bytes: LED count
|
||||
// 1 byte: checksum
|
||||
// 3 bytes per LED
|
||||
return 3 + 2 + 1 + led_count * 3;
|
||||
}
|
||||
|
||||
void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
|
||||
int buffer_capacity = get_frame_size_(it.size());
|
||||
|
||||
frame_.clear();
|
||||
frame_.reserve(buffer_capacity);
|
||||
}
|
||||
|
||||
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
|
||||
for (int led = it.size(); led-- > 0;) {
|
||||
it[led].set(light::ESPColor::BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) {
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) {
|
||||
ESP_LOGV(TAG, "Sending ACK");
|
||||
this->write_str("Ada\n");
|
||||
this->last_ack_ = now;
|
||||
}
|
||||
|
||||
if (!this->last_reset_) {
|
||||
ESP_LOGW(TAG, "Frame: Reset.");
|
||||
reset_frame_(it);
|
||||
blank_all_leds_(it);
|
||||
this->last_reset_ = now;
|
||||
}
|
||||
|
||||
if (!this->frame_.empty() && now - this->last_byte_ >= ADALIGHT_RECEIVE_TIMEOUT) {
|
||||
ESP_LOGW(TAG, "Frame: Receive timeout (size=%zu).", this->frame_.size());
|
||||
reset_frame_(it);
|
||||
blank_all_leds_(it);
|
||||
}
|
||||
|
||||
if (this->available() > 0) {
|
||||
ESP_LOGV(TAG, "Frame: Available (size=%d).", this->available());
|
||||
}
|
||||
|
||||
while (this->available() != 0) {
|
||||
uint8_t data;
|
||||
if (!this->read_byte(&data))
|
||||
break;
|
||||
this->frame_.push_back(data);
|
||||
this->last_byte_ = now;
|
||||
|
||||
switch (this->parse_frame_(it)) {
|
||||
case INVALID:
|
||||
ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=%d).", this->frame_.size(), this->frame_[0]);
|
||||
reset_frame_(it);
|
||||
break;
|
||||
|
||||
case PARTIAL:
|
||||
break;
|
||||
|
||||
case CONSUMED:
|
||||
ESP_LOGV(TAG, "Frame: Consumed (size=%zu).", this->frame_.size());
|
||||
reset_frame_(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableLight &it) {
|
||||
if (frame_.empty())
|
||||
return INVALID;
|
||||
|
||||
// Check header: `Ada`
|
||||
if (frame_[0] != 'A')
|
||||
return INVALID;
|
||||
if (frame_.size() > 1 && frame_[1] != 'd')
|
||||
return INVALID;
|
||||
if (frame_.size() > 2 && frame_[2] != 'a')
|
||||
return INVALID;
|
||||
|
||||
// 3 bytes: Count Hi, Count Lo, Checksum
|
||||
if (frame_.size() < 6)
|
||||
return PARTIAL;
|
||||
|
||||
// Check checksum
|
||||
uint16_t checksum = frame_[3] ^ frame_[4] ^ 0x55;
|
||||
if (checksum != frame_[5])
|
||||
return INVALID;
|
||||
|
||||
// Check if we received the full frame
|
||||
uint16_t led_count = (frame_[3] << 8) + frame_[4] + 1;
|
||||
auto buffer_size = get_frame_size_(led_count);
|
||||
if (frame_.size() < buffer_size)
|
||||
return PARTIAL;
|
||||
|
||||
// Apply lights
|
||||
auto accepted_led_count = std::min<int>(led_count, it.size());
|
||||
uint8_t *led_data = &frame_[6];
|
||||
|
||||
for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
|
||||
auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]);
|
||||
|
||||
it[led].set(light::ESPColor(led_data[0], led_data[1], led_data[2], white));
|
||||
}
|
||||
|
||||
return CONSUMED;
|
||||
}
|
||||
|
||||
} // namespace adalight
|
||||
} // namespace esphome
|
||||
41
esphome/components/adalight/adalight_light_effect.h
Normal file
41
esphome/components/adalight/adalight_light_effect.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/light/addressable_light_effect.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace adalight {
|
||||
|
||||
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
|
||||
public:
|
||||
AdalightLightEffect(const std::string &name);
|
||||
|
||||
public:
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override;
|
||||
|
||||
protected:
|
||||
enum Frame {
|
||||
INVALID,
|
||||
PARTIAL,
|
||||
CONSUMED,
|
||||
};
|
||||
|
||||
int get_frame_size_(int led_count) const;
|
||||
void reset_frame_(light::AddressableLight &it);
|
||||
void blank_all_leds_(light::AddressableLight &it);
|
||||
Frame parse_frame_(light::AddressableLight &it);
|
||||
|
||||
protected:
|
||||
uint32_t last_ack_{0};
|
||||
uint32_t last_byte_{0};
|
||||
uint32_t last_reset_{0};
|
||||
std::vector<uint8_t> frame_;
|
||||
};
|
||||
|
||||
} // namespace adalight
|
||||
} // namespace esphome
|
||||
1
esphome/components/adc/__init__.py
Normal file
1
esphome/components/adc/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
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; // NOLINT
|
||||
switch (this->attenuation_) {
|
||||
case ADC_0db:
|
||||
value_v *= 1.1;
|
||||
break;
|
||||
case ADC_2_5db:
|
||||
value_v *= 1.5;
|
||||
break;
|
||||
case ADC_6db:
|
||||
value_v *= 2.2;
|
||||
break;
|
||||
case ADC_11db:
|
||||
value_v *= 3.9;
|
||||
break;
|
||||
}
|
||||
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; // NOLINT
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||
#endif
|
||||
|
||||
} // namespace adc
|
||||
} // namespace esphome
|
||||
42
esphome/components/adc/adc_sensor.h
Normal file
42
esphome/components/adc/adc_sensor.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/esphal.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
/// Set the attenuation for this pin. Only available on the ESP32.
|
||||
void set_attenuation(adc_attenuation_t attenuation);
|
||||
#endif
|
||||
|
||||
/// Update adc values.
|
||||
void update() override;
|
||||
/// Setup ADc
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// `HARDWARE_LATE` setup priority.
|
||||
float get_setup_priority() const override;
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
float sample() override;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
std::string unique_id() override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
uint8_t pin_;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
adc_attenuation_t attenuation_{ADC_0db};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace adc
|
||||
} // namespace esphome
|
||||
48
esphome/components/adc/sensor.py
Normal file
48
esphome/components/adc/sensor.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT
|
||||
|
||||
|
||||
AUTO_LOAD = ['voltage_sampler']
|
||||
|
||||
ATTENUATION_MODES = {
|
||||
'0db': cg.global_ns.ADC_0db,
|
||||
'2.5db': cg.global_ns.ADC_2_5db,
|
||||
'6db': cg.global_ns.ADC_6db,
|
||||
'11db': cg.global_ns.ADC_11db,
|
||||
}
|
||||
|
||||
|
||||
def validate_adc_pin(value):
|
||||
vcc = str(value).upper()
|
||||
if vcc == 'VCC':
|
||||
return cv.only_on_esp8266(vcc)
|
||||
return pins.analog_pin(value)
|
||||
|
||||
|
||||
adc_ns = cg.esphome_ns.namespace('adc')
|
||||
ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({
|
||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||
cv.Required(CONF_PIN): validate_adc_pin,
|
||||
cv.SplitDefault(CONF_ATTENUATION, esp32='0db'):
|
||||
cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)),
|
||||
}).extend(cv.polling_component_schema('60s'))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield sensor.register_sensor(var, config)
|
||||
|
||||
if config[CONF_PIN] == 'VCC':
|
||||
cg.add_define('USE_ADC_SENSOR_VCC')
|
||||
else:
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
|
||||
if CONF_ATTENUATION in config:
|
||||
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
|
||||
0
esphome/components/ade7953/__init__.py
Normal file
0
esphome/components/ade7953/__init__.py
Normal file
54
esphome/components/ade7953/ade7953.cpp
Normal file
54
esphome/components/ade7953/ade7953.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "ade7953.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7953 {
|
||||
|
||||
static const char *TAG = "ade7953";
|
||||
|
||||
void ADE7953::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADE7953:");
|
||||
if (this->has_irq_) {
|
||||
ESP_LOGCONFIG(TAG, " IRQ Pin: GPIO%u", this->irq_pin_number_);
|
||||
}
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current A Sensor", this->current_a_sensor_);
|
||||
LOG_SENSOR(" ", "Current B Sensor", this->current_b_sensor_);
|
||||
LOG_SENSOR(" ", "Active Power A Sensor", this->active_power_a_sensor_);
|
||||
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
|
||||
}
|
||||
|
||||
#define ADE_PUBLISH_(name, factor) \
|
||||
if (name && this->name##_sensor_) { \
|
||||
float value = *name / factor; \
|
||||
this->name##_sensor_->publish_state(value); \
|
||||
}
|
||||
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
|
||||
|
||||
void ADE7953::update() {
|
||||
if (!this->is_setup_)
|
||||
return;
|
||||
|
||||
auto active_power_a = this->ade_read_<int32_t>(0x0312);
|
||||
ADE_PUBLISH(active_power_a, 154.0f);
|
||||
auto active_power_b = this->ade_read_<int32_t>(0x0313);
|
||||
ADE_PUBLISH(active_power_b, 154.0f);
|
||||
auto current_a = this->ade_read_<uint32_t>(0x031A);
|
||||
ADE_PUBLISH(current_a, 100000.0f);
|
||||
auto current_b = this->ade_read_<uint32_t>(0x031B);
|
||||
ADE_PUBLISH(current_b, 100000.0f);
|
||||
auto voltage = this->ade_read_<uint32_t>(0x031C);
|
||||
ADE_PUBLISH(voltage, 26000.0f);
|
||||
|
||||
// auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
|
||||
// auto apparent_power_b = this->ade_read_<int32_t>(0x0311);
|
||||
// auto reactive_power_a = this->ade_read_<int32_t>(0x0314);
|
||||
// auto reactive_power_b = this->ade_read_<int32_t>(0x0315);
|
||||
// auto power_factor_a = this->ade_read_<int16_t>(0x010A);
|
||||
// auto power_factor_b = this->ade_read_<int16_t>(0x010B);
|
||||
}
|
||||
|
||||
} // namespace ade7953
|
||||
} // namespace esphome
|
||||
79
esphome/components/ade7953/ade7953.h
Normal file
79
esphome/components/ade7953/ade7953.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7953 {
|
||||
|
||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
public:
|
||||
void set_irq_pin(uint8_t irq_pin) {
|
||||
has_irq_ = true;
|
||||
irq_pin_number_ = irq_pin;
|
||||
}
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
|
||||
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
|
||||
void set_active_power_a_sensor(sensor::Sensor *active_power_a_sensor) {
|
||||
active_power_a_sensor_ = active_power_a_sensor;
|
||||
}
|
||||
void set_active_power_b_sensor(sensor::Sensor *active_power_b_sensor) {
|
||||
active_power_b_sensor_ = active_power_b_sensor;
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
if (this->has_irq_) {
|
||||
auto pin = GPIOPin(this->irq_pin_number_, INPUT);
|
||||
this->irq_pin_ = &pin;
|
||||
this->irq_pin_->setup();
|
||||
}
|
||||
this->set_timeout(100, [this]() {
|
||||
this->ade_write_<uint8_t>(0x0010, 0x04);
|
||||
this->ade_write_<uint8_t>(0x00FE, 0xAD);
|
||||
this->ade_write_<uint16_t>(0x0120, 0x0030);
|
||||
this->is_setup_ = true;
|
||||
});
|
||||
}
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
template<typename T> bool ade_write_(uint16_t reg, T value) {
|
||||
std::vector<uint8_t> data;
|
||||
data.push_back(reg >> 8);
|
||||
data.push_back(reg >> 0);
|
||||
for (int i = sizeof(T) - 1; i >= 0; i--)
|
||||
data.push_back(value >> (i * 8));
|
||||
return this->write_bytes_raw(data);
|
||||
}
|
||||
template<typename T> optional<T> ade_read_(uint16_t reg) {
|
||||
uint8_t hi = reg >> 8;
|
||||
uint8_t lo = reg >> 0;
|
||||
if (!this->write_bytes_raw({hi, lo}))
|
||||
return {};
|
||||
auto ret = this->read_bytes_raw<sizeof(T)>();
|
||||
if (!ret.has_value())
|
||||
return {};
|
||||
T result = 0;
|
||||
for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--)
|
||||
result |= T((*ret)[i]) << (j * 8);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool has_irq_ = false;
|
||||
uint8_t irq_pin_number_;
|
||||
GPIOPin *irq_pin_{nullptr};
|
||||
bool is_setup_{false};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_a_sensor_{nullptr};
|
||||
sensor::Sensor *current_b_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_a_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_b_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ade7953
|
||||
} // namespace esphome
|
||||
44
esphome/components/ade7953/sensor.py
Normal file
44
esphome/components/ade7953/sensor.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, i2c
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_VOLTAGE, \
|
||||
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
ade7953_ns = cg.esphome_ns.namespace('ade7953')
|
||||
ADE7953 = ade7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONF_IRQ_PIN = 'irq_pin'
|
||||
CONF_CURRENT_A = 'current_a'
|
||||
CONF_CURRENT_B = 'current_b'
|
||||
CONF_ACTIVE_POWER_A = 'active_power_a'
|
||||
CONF_ACTIVE_POWER_B = 'active_power_b'
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ADE7953),
|
||||
cv.Optional(CONF_IRQ_PIN): pins.input_pin,
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
||||
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_IRQ_PIN in config:
|
||||
cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
|
||||
|
||||
for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
|
||||
CONF_ACTIVE_POWER_B]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f'set_{key}_sensor')(sens))
|
||||
25
esphome/components/ads1115/__init__.py
Normal file
25
esphome/components/ads1115/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['i2c']
|
||||
AUTO_LOAD = ['sensor', 'voltage_sampler']
|
||||
MULTI_CONF = True
|
||||
|
||||
ads1115_ns = cg.esphome_ns.namespace('ads1115')
|
||||
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
|
||||
|
||||
CONF_CONTINUOUS_MODE = 'continuous_mode'
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ADS1115Component),
|
||||
cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
|
||||
169
esphome/components/ads1115/ads1115.cpp
Normal file
169
esphome/components/ads1115/ads1115.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "ads1115.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115 {
|
||||
|
||||
static const char *TAG = "ads1115";
|
||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||
|
||||
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111;
|
||||
|
||||
void ADS1115Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
||||
uint16_t value;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t config = 0;
|
||||
// Clear single-shot bit
|
||||
// 0b0xxxxxxxxxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
// Setup multiplexer
|
||||
// 0bx000xxxxxxxxxxxx
|
||||
config |= ADS1115_MULTIPLEXER_P0_N1 << 12;
|
||||
|
||||
// Setup Gain
|
||||
// 0bxxxx000xxxxxxxxx
|
||||
config |= ADS1115_GAIN_6P144 << 9;
|
||||
|
||||
if (this->continuous_mode_) {
|
||||
// Set continuous mode
|
||||
// 0bxxxxxxx0xxxxxxxx
|
||||
config |= 0b0000000000000000;
|
||||
} else {
|
||||
// Set singleshot mode
|
||||
// 0bxxxxxxx1xxxxxxxx
|
||||
config |= 0b0000000100000000;
|
||||
}
|
||||
|
||||
// Set data rate - 860 samples per second (we're in singleshot mode)
|
||||
// 0bxxxxxxxx100xxxxx
|
||||
config |= ADS1115_DATA_RATE_860_SPS << 5;
|
||||
|
||||
// Set comparator mode - hysteresis
|
||||
// 0bxxxxxxxxxxx0xxxx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator polarity - active low
|
||||
// 0bxxxxxxxxxxxx0xxx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator latch enabled - false
|
||||
// 0bxxxxxxxxxxxxx0xx
|
||||
config |= 0b0000000000000000;
|
||||
|
||||
// Set comparator que mode - disabled
|
||||
// 0bxxxxxxxxxxxxxx11
|
||||
config |= 0b0000000000000011;
|
||||
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
this->set_interval(sensor->get_name(), sensor->update_interval(),
|
||||
[this, sensor] { this->request_measurement(sensor); });
|
||||
}
|
||||
}
|
||||
void ADS1115Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with ADS1115 failed!");
|
||||
}
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
LOG_SENSOR(" ", "Sensor", sensor);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", sensor->get_multiplexer());
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", sensor->get_gain());
|
||||
}
|
||||
}
|
||||
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
uint16_t config = this->prev_config_;
|
||||
// Multiplexer
|
||||
// 0bxBBBxxxxxxxxxxxx
|
||||
config &= 0b1000111111111111;
|
||||
config |= (sensor->get_multiplexer() & 0b111) << 12;
|
||||
|
||||
// Gain
|
||||
// 0bxxxxBBBxxxxxxxxx
|
||||
config &= 0b1111000111111111;
|
||||
config |= (sensor->get_gain() & 0b111) << 9;
|
||||
|
||||
if (!this->continuous_mode_) {
|
||||
// Start conversion
|
||||
config |= 0b1000000000000000;
|
||||
}
|
||||
|
||||
if (!this->continuous_mode_ || this->prev_config_ != config) {
|
||||
if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
|
||||
// about 1.6 ms with 860 samples per second
|
||||
delay(2);
|
||||
|
||||
uint32_t start = millis();
|
||||
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
||||
if (millis() - start > 100) {
|
||||
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t raw_conversion;
|
||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
auto signed_conversion = static_cast<int16_t>(raw_conversion);
|
||||
|
||||
float millivolts;
|
||||
switch (sensor->get_gain()) {
|
||||
case ADS1115_GAIN_6P144:
|
||||
millivolts = signed_conversion * 0.187500f;
|
||||
break;
|
||||
case ADS1115_GAIN_4P096:
|
||||
millivolts = signed_conversion * 0.125000f;
|
||||
break;
|
||||
case ADS1115_GAIN_2P048:
|
||||
millivolts = signed_conversion * 0.062500f;
|
||||
break;
|
||||
case ADS1115_GAIN_1P024:
|
||||
millivolts = signed_conversion * 0.031250f;
|
||||
break;
|
||||
case ADS1115_GAIN_0P512:
|
||||
millivolts = signed_conversion * 0.015625f;
|
||||
break;
|
||||
case ADS1115_GAIN_0P256:
|
||||
millivolts = signed_conversion * 0.007813f;
|
||||
break;
|
||||
default:
|
||||
millivolts = NAN;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return millivolts / 1e3f;
|
||||
}
|
||||
|
||||
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
|
||||
void ADS1115Sensor::update() {
|
||||
float v = this->parent_->request_measurement(this);
|
||||
if (!isnan(v)) {
|
||||
ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
|
||||
this->publish_state(v);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
71
esphome/components/ads1115/ads1115.h
Normal file
71
esphome/components/ads1115/ads1115.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115 {
|
||||
|
||||
enum ADS1115Multiplexer {
|
||||
ADS1115_MULTIPLEXER_P0_N1 = 0b000,
|
||||
ADS1115_MULTIPLEXER_P0_N3 = 0b001,
|
||||
ADS1115_MULTIPLEXER_P1_N3 = 0b010,
|
||||
ADS1115_MULTIPLEXER_P2_N3 = 0b011,
|
||||
ADS1115_MULTIPLEXER_P0_NG = 0b100,
|
||||
ADS1115_MULTIPLEXER_P1_NG = 0b101,
|
||||
ADS1115_MULTIPLEXER_P2_NG = 0b110,
|
||||
ADS1115_MULTIPLEXER_P3_NG = 0b111,
|
||||
};
|
||||
|
||||
enum ADS1115Gain {
|
||||
ADS1115_GAIN_6P144 = 0b000,
|
||||
ADS1115_GAIN_4P096 = 0b001,
|
||||
ADS1115_GAIN_2P048 = 0b010,
|
||||
ADS1115_GAIN_1P024 = 0b011,
|
||||
ADS1115_GAIN_0P512 = 0b100,
|
||||
ADS1115_GAIN_0P256 = 0b101,
|
||||
};
|
||||
|
||||
class ADS1115Sensor;
|
||||
|
||||
class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void register_sensor(ADS1115Sensor *obj) { this->sensors_.push_back(obj); }
|
||||
/// Set up the internal sensor array.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// HARDWARE_LATE setup priority
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
|
||||
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
float request_measurement(ADS1115Sensor *sensor);
|
||||
|
||||
protected:
|
||||
std::vector<ADS1115Sensor *> sensors_;
|
||||
uint16_t prev_config_{0};
|
||||
bool continuous_mode_;
|
||||
};
|
||||
|
||||
/// Internal holder class that is in instance of Sensor so that the hub can create individual sensors.
|
||||
class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
ADS1115Sensor(ADS1115Component *parent) : parent_(parent) {}
|
||||
void update() override;
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer) { multiplexer_ = multiplexer; }
|
||||
void set_gain(ADS1115Gain gain) { gain_ = gain; }
|
||||
|
||||
float sample() override;
|
||||
uint8_t get_multiplexer() const { return multiplexer_; }
|
||||
uint8_t get_gain() const { return gain_; }
|
||||
|
||||
protected:
|
||||
ADS1115Component *parent_;
|
||||
ADS1115Multiplexer multiplexer_;
|
||||
ADS1115Gain gain_;
|
||||
};
|
||||
|
||||
} // namespace ads1115
|
||||
} // namespace esphome
|
||||
62
esphome/components/ads1115/sensor.py
Normal file
62
esphome/components/ads1115/sensor.py
Normal file
@@ -0,0 +1,62 @@
|
||||
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 . 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 = f'{value:0.03f}'
|
||||
elif not isinstance(value, str):
|
||||
raise cv.Invalid(f'invalid gain "{value}"')
|
||||
|
||||
return cv.enum(GAIN)(value)
|
||||
|
||||
|
||||
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler)
|
||||
|
||||
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/aht10/__init__.py
Normal file
0
esphome/components/aht10/__init__.py
Normal file
127
esphome/components/aht10/aht10.cpp
Normal file
127
esphome/components/aht10/aht10.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
// Implementation based on:
|
||||
// - AHT10: https://github.com/Thinary/AHT10
|
||||
// - Official Datasheet (cn):
|
||||
// http://www.aosong.com/userfiles/files/media/aht10%E8%A7%84%E6%A0%BC%E4%B9%A6v1_1%EF%BC%8820191015%EF%BC%89.pdf
|
||||
// - Unofficial Translated Datasheet (en):
|
||||
// https://wiki.liutyi.info/download/attachments/30507639/Aosong_AHT10_en_draft_0c.pdf
|
||||
//
|
||||
// When configured for humidity, the log 'Components should block for at most 20-30ms in loop().' will be generated in
|
||||
// verbose mode. This is due to technical specs of the sensor and can not be avoided.
|
||||
//
|
||||
// According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost
|
||||
// immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best
|
||||
// results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time.
|
||||
|
||||
#include "aht10.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace aht10 {
|
||||
|
||||
static const char *TAG = "aht10";
|
||||
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
|
||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
static const uint8_t AHT10_ATTEMPS = 3; // safety margin, normally 3 attemps are enough: 3*30=90ms
|
||||
|
||||
void AHT10Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT10...");
|
||||
|
||||
if (!this->write_bytes(0, AHT10_CALIBRATE_CMD, sizeof(AHT10_CALIBRATE_CMD))) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint8_t data;
|
||||
if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
|
||||
ESP_LOGE(TAG, "AHT10 calibration failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "AHT10 calibrated");
|
||||
}
|
||||
|
||||
void AHT10Component::update() {
|
||||
if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
uint8_t data[6];
|
||||
uint8_t delay = AHT10_DEFAULT_DELAY;
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
delay = AHT10_HUMIDITY_DELAY;
|
||||
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
|
||||
if (!this->read_bytes(0, data, 6, delay)) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
|
||||
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
|
||||
// Unrealistic humidity (0x0)
|
||||
if (this->humidity_sensor_ == nullptr) {
|
||||
ESP_LOGVV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
|
||||
break;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
|
||||
if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// data is valid, we can break the loop
|
||||
ESP_LOGVV(TAG, "Answer at %6ld", millis());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((data[0] & 0x80) == 0x80) {
|
||||
ESP_LOGE(TAG, "Measurements reading timed-out!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
|
||||
|
||||
float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0;
|
||||
float humidity;
|
||||
if (raw_humidity == 0) { // unrealistic value
|
||||
humidity = NAN;
|
||||
} else {
|
||||
humidity = (float) raw_humidity * 100.0 / 1048576.0;
|
||||
}
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
if (isnan(humidity))
|
||||
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void AHT10Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AHT10:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
}
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
}
|
||||
|
||||
} // namespace aht10
|
||||
} // namespace esphome
|
||||
26
esphome/components/aht10/aht10.h
Normal file
26
esphome/components/aht10/aht10.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace aht10 {
|
||||
|
||||
class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
};
|
||||
|
||||
} // namespace aht10
|
||||
} // namespace esphome
|
||||
30
esphome/components/aht10/sensor.py
Normal file
30
esphome/components/aht10/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']
|
||||
|
||||
aht10_ns = cg.esphome_ns.namespace('aht10')
|
||||
AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(AHT10Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
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))
|
||||
94
esphome/components/animation/__init__.py
Normal file
94
esphome/components/animation/__init__.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import logging
|
||||
|
||||
from esphome import core
|
||||
from esphome.components import display, font
|
||||
import esphome.components.image as espImage
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['display']
|
||||
MULTI_CONF = True
|
||||
|
||||
Animation_ = display.display_ns.class_('Animation')
|
||||
|
||||
CONF_RAW_DATA_ID = 'raw_data_id'
|
||||
|
||||
ANIMATION_SCHEMA = cv.Schema({
|
||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||
cv.Required(CONF_FILE): cv.file_,
|
||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||
cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(espImage.IMAGE_TYPE, upper=True),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
|
||||
|
||||
CODEOWNERS = ['@syndlex']
|
||||
|
||||
|
||||
def to_code(config):
|
||||
from PIL import Image
|
||||
|
||||
path = CORE.relative_config_path(config[CONF_FILE])
|
||||
try:
|
||||
image = Image.open(path)
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(f"Could not load image file {path}: {e}")
|
||||
|
||||
width, height = image.size
|
||||
frames = image.n_frames
|
||||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
width, height = image.size
|
||||
else:
|
||||
if width > 500 or height > 500:
|
||||
_LOGGER.warning("The image you requested is very big. Please consider using"
|
||||
" the resize parameter.")
|
||||
|
||||
if config[CONF_TYPE] == 'GRAYSCALE':
|
||||
data = [0 for _ in range(height * width * frames)]
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('L', dither=Image.NONE)
|
||||
pixels = list(frame.getdata())
|
||||
for pix in pixels:
|
||||
data[pos] = pix
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == 'RGB24':
|
||||
data = [0 for _ in range(height * width * 3 * frames)]
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('RGB')
|
||||
pixels = list(frame.getdata())
|
||||
for pix in pixels:
|
||||
data[pos] = pix[0]
|
||||
pos += 1
|
||||
data[pos] = pix[1]
|
||||
pos += 1
|
||||
data[pos] = pix[2]
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == 'BINARY':
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range((height * width8 // 8) * frames)]
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert('1', dither=Image.NONE)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if frame.getpixel((x, y)):
|
||||
continue
|
||||
pos = x + y * width8 + (height * width8 * frameIndex)
|
||||
data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, frames,
|
||||
espImage.IMAGE_TYPE[config[CONF_TYPE]])
|
||||
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))
|
||||
160
esphome/components/api/__init__.py
Normal file
160
esphome/components/api/__init__.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
|
||||
CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT, \
|
||||
CONF_TAG
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
DEPENDENCIES = ['network']
|
||||
AUTO_LOAD = ['async_tcp']
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
|
||||
api_ns = cg.esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
|
||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
|
||||
|
||||
UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
|
||||
ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
|
||||
SERVICE_ARG_NATIVE_TYPES = {
|
||||
'bool': bool,
|
||||
'int': cg.int32,
|
||||
'float': float,
|
||||
'string': cg.std_string,
|
||||
'bool[]': cg.std_vector.template(bool),
|
||||
'int[]': cg.std_vector.template(cg.int32),
|
||||
'float[]': cg.std_vector.template(float),
|
||||
'string[]': cg.std_vector.template(cg.std_string),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(APIServer),
|
||||
cv.Optional(CONF_PORT, default=6053): cv.port,
|
||||
cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
|
||||
cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_SERVICES): automation.validate_automation({
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
|
||||
cv.Required(CONF_SERVICE): cv.valid_name,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
|
||||
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
|
||||
}),
|
||||
}),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@coroutine_with_priority(40.0)
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
for conf in config.get(CONF_SERVICES, []):
|
||||
template_args = []
|
||||
func_args = []
|
||||
service_arg_names = []
|
||||
for name, var_ in conf[CONF_VARIABLES].items():
|
||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||
template_args.append(native)
|
||||
func_args.append((native, name))
|
||||
service_arg_names.append(name)
|
||||
templ = cg.TemplateArguments(*template_args)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
|
||||
conf[CONF_SERVICE], service_arg_names)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
yield automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
cg.add_define('USE_API')
|
||||
cg.add_global(api_ns.using)
|
||||
|
||||
|
||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
|
||||
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}),
|
||||
})
|
||||
|
||||
|
||||
@automation.register_action('homeassistant.service', HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
|
||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||
templ = yield cg.templatable(config[CONF_SERVICE], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data(key, templ))
|
||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data_template(key, templ))
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_variable(key, templ))
|
||||
yield var
|
||||
|
||||
|
||||
def validate_homeassistant_event(value):
|
||||
value = cv.string(value)
|
||||
if not value.startswith('esphome.'):
|
||||
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
|
||||
"esphome. For example 'esphome.xyz'")
|
||||
return value
|
||||
|
||||
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_EVENT): validate_homeassistant_event,
|
||||
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
|
||||
cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
|
||||
})
|
||||
|
||||
|
||||
@automation.register_action('homeassistant.event', HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA)
|
||||
def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
templ = yield cg.templatable(config[CONF_EVENT], args, None)
|
||||
cg.add(var.set_service(templ))
|
||||
for key, value in config[CONF_DATA].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data(key, templ))
|
||||
for key, value in config[CONF_DATA_TEMPLATE].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_data_template(key, templ))
|
||||
for key, value in config[CONF_VARIABLES].items():
|
||||
templ = yield cg.templatable(value, args, None)
|
||||
cg.add(var.add_variable(key, templ))
|
||||
yield var
|
||||
|
||||
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value({
|
||||
cv.GenerateID(): cv.use_id(APIServer),
|
||||
cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
|
||||
}, key=CONF_TAG)
|
||||
|
||||
|
||||
@automation.register_action('homeassistant.tag_scanned', HomeAssistantServiceCallAction,
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA)
|
||||
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||
serv = yield cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
cg.add(var.set_service('esphome.tag_scanned'))
|
||||
templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string)
|
||||
cg.add(var.add_data('tag_id', templ))
|
||||
yield var
|
||||
|
||||
|
||||
@automation.register_condition('api.connected', APIConnectedCondition, {})
|
||||
def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
yield cg.new_Pvariable(condition_id, template_arg)
|
||||
752
esphome/components/api/api.proto
Normal file
752
esphome/components/api/api.proto
Normal file
@@ -0,0 +1,752 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "api_options.proto";
|
||||
|
||||
service APIConnection {
|
||||
rpc hello (HelloRequest) returns (HelloResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc connect (ConnectRequest) returns (ConnectResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc ping (PingRequest) returns (PingResponse) {
|
||||
option (needs_setup_connection) = false;
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc device_info (DeviceInfoRequest) returns (DeviceInfoResponse) {
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc list_entities (ListEntitiesRequest) returns (void) {}
|
||||
rpc subscribe_states (SubscribeStatesRequest) returns (void) {}
|
||||
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
||||
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
||||
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
||||
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
rpc execute_service (ExecuteServiceRequest) returns (void) {}
|
||||
|
||||
rpc cover_command (CoverCommandRequest) returns (void) {}
|
||||
rpc fan_command (FanCommandRequest) returns (void) {}
|
||||
rpc light_command (LightCommandRequest) returns (void) {}
|
||||
rpc switch_command (SwitchCommandRequest) returns (void) {}
|
||||
rpc camera_image (CameraImageRequest) returns (void) {}
|
||||
rpc climate_command (ClimateCommandRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
// ==================== BASE PACKETS ====================
|
||||
|
||||
// The Home Assistant protocol is structured as a simple
|
||||
// TCP socket with short binary messages encoded in the protocol buffers format
|
||||
// First, a message in this protocol has a specific format:
|
||||
// * VarInt denoting the size of the message object. (type is not part of this)
|
||||
// * VarInt denoting the type of message.
|
||||
// * The message object encoded as a ProtoBuf message
|
||||
|
||||
// The connection is established in 4 steps:
|
||||
// * First, the client connects to the server and sends a "Hello Request" identifying itself
|
||||
// * The server responds with a "Hello Response" and selects the protocol version
|
||||
// * After receiving this message, the client attempts to authenticate itself using
|
||||
// the password and a "Connect Request"
|
||||
// * The server responds with a "Connect Response" and notifies of invalid password.
|
||||
// If anything in this initial process fails, the connection must immediately closed
|
||||
// by both sides and _no_ disconnection message is to be sent.
|
||||
|
||||
// Message sent at the beginning of each connection
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message HelloRequest {
|
||||
option (id) = 1;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Description of client (like User Agent)
|
||||
// For example "Home Assistant"
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message HelloResponse {
|
||||
option (id) = 2;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
|
||||
// for compatibility and if necessary adopt to an older API.
|
||||
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
|
||||
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
|
||||
uint32 api_version_major = 1;
|
||||
uint32 api_version_minor = 2;
|
||||
|
||||
// A string identifying the server (ESP); like client info this may be empty
|
||||
// and only exists for debugging/logging purposes.
|
||||
// For example "ESPHome v1.10.0 on ESP8266"
|
||||
string server_info = 3;
|
||||
}
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
// Can only be sent by the client and only at the beginning of the connection
|
||||
message ConnectRequest {
|
||||
option (id) = 3;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
// The password to log in with
|
||||
string password = 1;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection. After this the connection is available for all traffic.
|
||||
// Can only be sent by the server and only at the beginning of the connection
|
||||
message ConnectResponse {
|
||||
option (id) = 4;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
bool invalid_password = 1;
|
||||
}
|
||||
|
||||
// Request to close the connection.
|
||||
// Can be sent by both the client and server
|
||||
message DisconnectRequest {
|
||||
option (id) = 5;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Do not close the connection before the acknowledgement arrives
|
||||
}
|
||||
|
||||
message DisconnectResponse {
|
||||
option (id) = 6;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
// Empty - Both parties are required to close the connection after this
|
||||
// message has been received.
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
option (id) = 7;
|
||||
option (source) = SOURCE_BOTH;
|
||||
// Empty
|
||||
}
|
||||
|
||||
message PingResponse {
|
||||
option (id) = 8;
|
||||
option (source) = SOURCE_BOTH;
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoRequest {
|
||||
option (id) = 9;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
|
||||
message DeviceInfoResponse {
|
||||
option (id) = 10;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
bool uses_password = 1;
|
||||
|
||||
// The name of the node, given by "App.set_name()"
|
||||
string name = 2;
|
||||
|
||||
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
|
||||
string mac_address = 3;
|
||||
|
||||
// A string describing the ESPHome version. For example "1.10.0"
|
||||
string esphome_version = 4;
|
||||
|
||||
// A string describing the date of compilation, this is generated by the compiler
|
||||
// and therefore may not be in the same format all the time.
|
||||
// If the user isn't using ESPHome, this will also not be set.
|
||||
string compilation_time = 5;
|
||||
|
||||
// The model of the board. For example NodeMCU
|
||||
string model = 6;
|
||||
|
||||
bool has_deep_sleep = 7;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
option (id) = 11;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
message ListEntitiesDoneResponse {
|
||||
option (id) = 19;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
// Empty
|
||||
}
|
||||
message SubscribeStatesRequest {
|
||||
option (id) = 20;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ==================== BINARY SENSOR ====================
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
option (id) = 12;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string device_class = 5;
|
||||
bool is_status_binary_sensor = 6;
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
// If the binary sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 3;
|
||||
}
|
||||
|
||||
// ==================== COVER ====================
|
||||
message ListEntitiesCoverResponse {
|
||||
option (id) = 13;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_COVER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool assumed_state = 5;
|
||||
bool supports_position = 6;
|
||||
bool supports_tilt = 7;
|
||||
string device_class = 8;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
LEGACY_COVER_STATE_OPEN = 0;
|
||||
LEGACY_COVER_STATE_CLOSED = 1;
|
||||
}
|
||||
enum CoverOperation {
|
||||
COVER_OPERATION_IDLE = 0;
|
||||
COVER_OPERATION_IS_OPENING = 1;
|
||||
COVER_OPERATION_IS_CLOSING = 2;
|
||||
}
|
||||
message CoverStateResponse {
|
||||
option (id) = 22;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
// legacy: state has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
LegacyCoverState legacy_state = 2;
|
||||
|
||||
float position = 3;
|
||||
float tilt = 4;
|
||||
CoverOperation current_operation = 5;
|
||||
}
|
||||
|
||||
enum LegacyCoverCommand {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0;
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1;
|
||||
LEGACY_COVER_COMMAND_STOP = 2;
|
||||
}
|
||||
message CoverCommandRequest {
|
||||
option (id) = 30;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
|
||||
// legacy: command has been removed in 1.13
|
||||
// clients/servers must still send/accept it until the next protocol change
|
||||
bool has_legacy_command = 2;
|
||||
LegacyCoverCommand legacy_command = 3;
|
||||
|
||||
bool has_position = 4;
|
||||
float position = 5;
|
||||
bool has_tilt = 6;
|
||||
float tilt = 7;
|
||||
bool stop = 8;
|
||||
}
|
||||
|
||||
// ==================== FAN ====================
|
||||
message ListEntitiesFanResponse {
|
||||
option (id) = 14;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_FAN";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
bool supports_direction = 7;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
FAN_SPEED_MEDIUM = 1;
|
||||
FAN_SPEED_HIGH = 2;
|
||||
}
|
||||
enum FanDirection {
|
||||
FAN_DIRECTION_FORWARD = 0;
|
||||
FAN_DIRECTION_REVERSE = 1;
|
||||
}
|
||||
message FanStateResponse {
|
||||
option (id) = 23;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
FanDirection direction = 5;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
option (id) = 31;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
bool has_direction = 8;
|
||||
FanDirection direction = 9;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
message ListEntitiesLightResponse {
|
||||
option (id) = 15;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_brightness = 5;
|
||||
bool supports_rgb = 6;
|
||||
bool supports_white_value = 7;
|
||||
bool supports_color_temperature = 8;
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
float white = 7;
|
||||
float color_temperature = 8;
|
||||
string effect = 9;
|
||||
}
|
||||
message LightCommandRequest {
|
||||
option (id) = 32;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_rgb = 6;
|
||||
float red = 7;
|
||||
float green = 8;
|
||||
float blue = 9;
|
||||
bool has_white = 10;
|
||||
float white = 11;
|
||||
bool has_color_temperature = 12;
|
||||
float color_temperature = 13;
|
||||
bool has_transition_length = 14;
|
||||
uint32 transition_length = 15;
|
||||
bool has_flash_length = 16;
|
||||
uint32 flash_length = 17;
|
||||
bool has_effect = 18;
|
||||
string effect = 19;
|
||||
}
|
||||
|
||||
// ==================== SENSOR ====================
|
||||
message ListEntitiesSensorResponse {
|
||||
option (id) = 16;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
string unit_of_measurement = 6;
|
||||
int32 accuracy_decimals = 7;
|
||||
bool force_update = 8;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
float state = 2;
|
||||
// If the sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 3;
|
||||
}
|
||||
|
||||
// ==================== SWITCH ====================
|
||||
message ListEntitiesSwitchResponse {
|
||||
option (id) = 17;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool assumed_state = 6;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
message SwitchCommandRequest {
|
||||
option (id) = 33;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
}
|
||||
|
||||
// ==================== TEXT SENSOR ====================
|
||||
message ListEntitiesTextSensorResponse {
|
||||
option (id) = 18;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
// If the text sensor does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 3;
|
||||
}
|
||||
|
||||
// ==================== SUBSCRIBE LOGS ====================
|
||||
enum LogLevel {
|
||||
LOG_LEVEL_NONE = 0;
|
||||
LOG_LEVEL_ERROR = 1;
|
||||
LOG_LEVEL_WARN = 2;
|
||||
LOG_LEVEL_INFO = 3;
|
||||
LOG_LEVEL_DEBUG = 4;
|
||||
LOG_LEVEL_VERBOSE = 5;
|
||||
LOG_LEVEL_VERY_VERBOSE = 6;
|
||||
}
|
||||
message SubscribeLogsRequest {
|
||||
option (id) = 28;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
LogLevel level = 1;
|
||||
bool dump_config = 2;
|
||||
}
|
||||
message SubscribeLogsResponse {
|
||||
option (id) = 29;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (log) = false;
|
||||
option (no_delay) = false;
|
||||
|
||||
LogLevel level = 1;
|
||||
string tag = 2;
|
||||
string message = 3;
|
||||
bool send_failed = 4;
|
||||
}
|
||||
|
||||
// ==================== HOMEASSISTANT.SERVICE ====================
|
||||
message SubscribeHomeassistantServicesRequest {
|
||||
option (id) = 34;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
}
|
||||
|
||||
message HomeassistantServiceMap {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message HomeassistantServiceResponse {
|
||||
option (id) = 35;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (no_delay) = true;
|
||||
|
||||
string service = 1;
|
||||
repeated HomeassistantServiceMap data = 2;
|
||||
repeated HomeassistantServiceMap data_template = 3;
|
||||
repeated HomeassistantServiceMap variables = 4;
|
||||
bool is_event = 5;
|
||||
}
|
||||
|
||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
|
||||
// 1. Client sends SubscribeHomeAssistantStatesRequest
|
||||
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
|
||||
// 3. Client sends HomeAssistantStateResponse for state changes.
|
||||
message SubscribeHomeAssistantStatesRequest {
|
||||
option (id) = 38;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
}
|
||||
|
||||
message SubscribeHomeAssistantStateResponse {
|
||||
option (id) = 39;
|
||||
option (source) = SOURCE_SERVER;
|
||||
string entity_id = 1;
|
||||
}
|
||||
|
||||
message HomeAssistantStateResponse {
|
||||
option (id) = 40;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
// ==================== IMPORT TIME ====================
|
||||
message GetTimeRequest {
|
||||
option (id) = 36;
|
||||
option (source) = SOURCE_BOTH;
|
||||
}
|
||||
|
||||
message GetTimeResponse {
|
||||
option (id) = 37;
|
||||
option (source) = SOURCE_BOTH;
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 epoch_seconds = 1;
|
||||
}
|
||||
|
||||
// ==================== USER-DEFINES SERVICES ====================
|
||||
enum ServiceArgType {
|
||||
SERVICE_ARG_TYPE_BOOL = 0;
|
||||
SERVICE_ARG_TYPE_INT = 1;
|
||||
SERVICE_ARG_TYPE_FLOAT = 2;
|
||||
SERVICE_ARG_TYPE_STRING = 3;
|
||||
SERVICE_ARG_TYPE_BOOL_ARRAY = 4;
|
||||
SERVICE_ARG_TYPE_INT_ARRAY = 5;
|
||||
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6;
|
||||
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
|
||||
}
|
||||
message ListEntitiesServicesArgument {
|
||||
string name = 1;
|
||||
ServiceArgType type = 2;
|
||||
}
|
||||
message ListEntitiesServicesResponse {
|
||||
option (id) = 41;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
string name = 1;
|
||||
fixed32 key = 2;
|
||||
repeated ListEntitiesServicesArgument args = 3;
|
||||
}
|
||||
message ExecuteServiceArgument {
|
||||
bool bool_ = 1;
|
||||
int32 legacy_int = 2;
|
||||
float float_ = 3;
|
||||
string string_ = 4;
|
||||
// ESPHome 1.14 (api v1.3) make int a signed value
|
||||
sint32 int_ = 5;
|
||||
repeated bool bool_array = 6 [packed=false];
|
||||
repeated sint32 int_array = 7 [packed=false];
|
||||
repeated float float_array = 8 [packed=false];
|
||||
repeated string string_array = 9;
|
||||
}
|
||||
message ExecuteServiceRequest {
|
||||
option (id) = 42;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
repeated ExecuteServiceArgument args = 2;
|
||||
}
|
||||
|
||||
// ==================== CAMERA ====================
|
||||
message ListEntitiesCameraResponse {
|
||||
option (id) = 43;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
}
|
||||
|
||||
message CameraImageResponse {
|
||||
option (id) = 44;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
|
||||
fixed32 key = 1;
|
||||
bytes data = 2;
|
||||
bool done = 3;
|
||||
}
|
||||
message CameraImageRequest {
|
||||
option (id) = 45;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
option (no_delay) = true;
|
||||
|
||||
bool single = 1;
|
||||
bool stream = 2;
|
||||
}
|
||||
|
||||
// ==================== CLIMATE ====================
|
||||
enum ClimateMode {
|
||||
CLIMATE_MODE_OFF = 0;
|
||||
CLIMATE_MODE_AUTO = 1;
|
||||
CLIMATE_MODE_COOL = 2;
|
||||
CLIMATE_MODE_HEAT = 3;
|
||||
CLIMATE_MODE_FAN_ONLY = 4;
|
||||
CLIMATE_MODE_DRY = 5;
|
||||
}
|
||||
enum ClimateFanMode {
|
||||
CLIMATE_FAN_ON = 0;
|
||||
CLIMATE_FAN_OFF = 1;
|
||||
CLIMATE_FAN_AUTO = 2;
|
||||
CLIMATE_FAN_LOW = 3;
|
||||
CLIMATE_FAN_MEDIUM = 4;
|
||||
CLIMATE_FAN_HIGH = 5;
|
||||
CLIMATE_FAN_MIDDLE = 6;
|
||||
CLIMATE_FAN_FOCUS = 7;
|
||||
CLIMATE_FAN_DIFFUSE = 8;
|
||||
}
|
||||
enum ClimateSwingMode {
|
||||
CLIMATE_SWING_OFF = 0;
|
||||
CLIMATE_SWING_BOTH = 1;
|
||||
CLIMATE_SWING_VERTICAL = 2;
|
||||
CLIMATE_SWING_HORIZONTAL = 3;
|
||||
}
|
||||
enum ClimateAction {
|
||||
CLIMATE_ACTION_OFF = 0;
|
||||
// values same as mode for readability
|
||||
CLIMATE_ACTION_COOLING = 2;
|
||||
CLIMATE_ACTION_HEATING = 3;
|
||||
CLIMATE_ACTION_IDLE = 4;
|
||||
CLIMATE_ACTION_DRYING = 5;
|
||||
CLIMATE_ACTION_FAN = 6;
|
||||
}
|
||||
message ListEntitiesClimateResponse {
|
||||
option (id) = 46;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_current_temperature = 5;
|
||||
bool supports_two_point_target_temperature = 6;
|
||||
repeated ClimateMode supported_modes = 7;
|
||||
float visual_min_temperature = 8;
|
||||
float visual_max_temperature = 9;
|
||||
float visual_temperature_step = 10;
|
||||
bool supports_away = 11;
|
||||
bool supports_action = 12;
|
||||
repeated ClimateFanMode supported_fan_modes = 13;
|
||||
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
ClimateMode mode = 2;
|
||||
float current_temperature = 3;
|
||||
float target_temperature = 4;
|
||||
float target_temperature_low = 5;
|
||||
float target_temperature_high = 6;
|
||||
bool away = 7;
|
||||
ClimateAction action = 8;
|
||||
ClimateFanMode fan_mode = 9;
|
||||
ClimateSwingMode swing_mode = 10;
|
||||
}
|
||||
message ClimateCommandRequest {
|
||||
option (id) = 48;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool has_mode = 2;
|
||||
ClimateMode mode = 3;
|
||||
bool has_target_temperature = 4;
|
||||
float target_temperature = 5;
|
||||
bool has_target_temperature_low = 6;
|
||||
float target_temperature_low = 7;
|
||||
bool has_target_temperature_high = 8;
|
||||
float target_temperature_high = 9;
|
||||
bool has_away = 10;
|
||||
bool away = 11;
|
||||
bool has_fan_mode = 12;
|
||||
ClimateFanMode fan_mode = 13;
|
||||
bool has_swing_mode = 14;
|
||||
ClimateSwingMode swing_mode = 15;
|
||||
}
|
||||
699
esphome/components/api/api_connection.cpp
Normal file
699
esphome/components/api/api_connection.cpp
Normal file
@@ -0,0 +1,699 @@
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.connection";
|
||||
|
||||
APIConnection::APIConnection(AsyncClient *client, APIServer *parent)
|
||||
: client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this);
|
||||
this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this);
|
||||
this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); },
|
||||
this);
|
||||
this->client_->onData([](void *s, AsyncClient *c, void *buf,
|
||||
size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast<uint8_t *>(buf), len); },
|
||||
this);
|
||||
|
||||
this->send_buffer_.reserve(64);
|
||||
this->recv_buffer_.reserve(32);
|
||||
this->client_info_ = this->client_->remoteIP().toString().c_str();
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
APIConnection::~APIConnection() { delete this->client_; }
|
||||
void APIConnection::on_error_(int8_t error) { this->remove_ = true; }
|
||||
void APIConnection::on_disconnect_() { this->remove_ = true; }
|
||||
void APIConnection::on_timeout_(uint32_t time) { this->on_fatal_error(); }
|
||||
void APIConnection::on_data_(uint8_t *buf, size_t len) {
|
||||
if (len == 0 || buf == nullptr)
|
||||
return;
|
||||
this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len);
|
||||
}
|
||||
void APIConnection::parse_recv_buffer_() {
|
||||
if (this->recv_buffer_.empty() || this->remove_)
|
||||
return;
|
||||
|
||||
while (!this->recv_buffer_.empty()) {
|
||||
if (this->recv_buffer_[0] != 0x00) {
|
||||
ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
uint32_t i = 1;
|
||||
const uint32_t size = this->recv_buffer_.size();
|
||||
uint32_t consumed;
|
||||
auto msg_size_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
|
||||
if (!msg_size_varint.has_value())
|
||||
// not enough data there yet
|
||||
return;
|
||||
i += consumed;
|
||||
uint32_t msg_size = msg_size_varint->as_uint32();
|
||||
|
||||
auto msg_type_varint = ProtoVarInt::parse(&this->recv_buffer_[i], size - i, &consumed);
|
||||
if (!msg_type_varint.has_value())
|
||||
// not enough data there yet
|
||||
return;
|
||||
i += consumed;
|
||||
uint32_t msg_type = msg_type_varint->as_uint32();
|
||||
|
||||
if (size - i < msg_size)
|
||||
// message body not fully received
|
||||
return;
|
||||
|
||||
uint8_t *msg = &this->recv_buffer_[i];
|
||||
this->read_message(msg_size, msg_type, msg);
|
||||
if (this->remove_)
|
||||
return;
|
||||
// pop front
|
||||
uint32_t total = i + msg_size;
|
||||
this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total);
|
||||
this->last_traffic_ = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::disconnect_client() {
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->remove_)
|
||||
return;
|
||||
|
||||
if (this->next_close_) {
|
||||
this->disconnect_client();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!network_is_connected()) {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
if (this->client_->disconnected()) {
|
||||
// failsafe for disconnect logic
|
||||
this->on_disconnect_();
|
||||
return;
|
||||
}
|
||||
this->parse_recv_buffer_();
|
||||
|
||||
this->list_entities_iterator_.advance();
|
||||
this->initial_state_iterator_.advance();
|
||||
|
||||
const uint32_t keepalive = 60000;
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (millis() - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
this->disconnect_client();
|
||||
}
|
||||
} else if (millis() - this->last_traffic_ > keepalive) {
|
||||
this->sent_ping_ = true;
|
||||
this->send_ping_request(PingRequest());
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (this->image_reader_.available()) {
|
||||
uint32_t space = this->client_->space();
|
||||
// reserve 15 bytes for metadata, and at least 64 bytes of data
|
||||
if (space >= 15 + 64) {
|
||||
uint32_t to_send = std::min(space - 15, this->image_reader_.available());
|
||||
auto buffer = this->create_buffer();
|
||||
// fixed32 key = 1;
|
||||
buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
|
||||
// bytes data = 2;
|
||||
buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
|
||||
// bool done = 3;
|
||||
bool done = this->image_reader_.available() == to_send;
|
||||
buffer.encode_bool(3, done);
|
||||
bool success = this->send_buffer(buffer, 44);
|
||||
|
||||
if (success) {
|
||||
this->image_reader_.consume_data(to_send);
|
||||
}
|
||||
if (success && done) {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, Nameable *nameable) {
|
||||
return App.get_name() + component_type + nameable->get_object_id();
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
BinarySensorStateResponse resp;
|
||||
resp.key = binary_sensor->get_object_id_hash();
|
||||
resp.state = state;
|
||||
resp.missing_state = !binary_sensor->has_state();
|
||||
return this->send_binary_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
|
||||
ListEntitiesBinarySensorResponse msg;
|
||||
msg.object_id = binary_sensor->get_object_id();
|
||||
msg.key = binary_sensor->get_object_id_hash();
|
||||
msg.name = binary_sensor->get_name();
|
||||
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
|
||||
msg.device_class = binary_sensor->get_device_class();
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
return this->send_list_entities_binary_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = cover->get_traits();
|
||||
CoverStateResponse resp{};
|
||||
resp.key = cover->get_object_id_hash();
|
||||
resp.legacy_state =
|
||||
(cover->position == cover::COVER_OPEN) ? enums::LEGACY_COVER_STATE_OPEN : enums::LEGACY_COVER_STATE_CLOSED;
|
||||
resp.position = cover->position;
|
||||
if (traits.get_supports_tilt())
|
||||
resp.tilt = cover->tilt;
|
||||
resp.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
|
||||
return this->send_cover_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
auto traits = cover->get_traits();
|
||||
ListEntitiesCoverResponse msg;
|
||||
msg.key = cover->get_object_id_hash();
|
||||
msg.object_id = cover->get_object_id();
|
||||
msg.name = cover->get_name();
|
||||
msg.unique_id = get_default_unique_id("cover", cover);
|
||||
msg.assumed_state = traits.get_is_assumed_state();
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
msg.supports_tilt = traits.get_supports_tilt();
|
||||
msg.device_class = cover->get_device_class();
|
||||
return this->send_list_entities_cover_response(msg);
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
cover::Cover *cover = App.get_cover_by_key(msg.key);
|
||||
if (cover == nullptr)
|
||||
return;
|
||||
|
||||
auto call = cover->make_call();
|
||||
if (msg.has_legacy_command) {
|
||||
switch (msg.legacy_command) {
|
||||
case enums::LEGACY_COVER_COMMAND_OPEN:
|
||||
call.set_command_open();
|
||||
break;
|
||||
case enums::LEGACY_COVER_COMMAND_CLOSE:
|
||||
call.set_command_close();
|
||||
break;
|
||||
case enums::LEGACY_COVER_COMMAND_STOP:
|
||||
call.set_command_stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (msg.has_position)
|
||||
call.set_position(msg.position);
|
||||
if (msg.has_tilt)
|
||||
call.set_tilt(msg.tilt);
|
||||
if (msg.stop)
|
||||
call.set_command_stop();
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = fan->get_traits();
|
||||
FanStateResponse resp{};
|
||||
resp.key = fan->get_object_id_hash();
|
||||
resp.state = fan->state;
|
||||
if (traits.supports_oscillation())
|
||||
resp.oscillating = fan->oscillating;
|
||||
if (traits.supports_speed())
|
||||
resp.speed = static_cast<enums::FanSpeed>(fan->speed);
|
||||
if (traits.supports_direction())
|
||||
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
return this->send_fan_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||
auto traits = fan->get_traits();
|
||||
ListEntitiesFanResponse msg;
|
||||
msg.key = fan->get_object_id_hash();
|
||||
msg.object_id = fan->get_object_id();
|
||||
msg.name = fan->get_name();
|
||||
msg.unique_id = get_default_unique_id("fan", fan);
|
||||
msg.supports_oscillation = traits.supports_oscillation();
|
||||
msg.supports_speed = traits.supports_speed();
|
||||
msg.supports_direction = traits.supports_direction();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
fan::FanState *fan = App.get_fan_by_key(msg.key);
|
||||
if (fan == nullptr)
|
||||
return;
|
||||
|
||||
auto call = fan->make_call();
|
||||
if (msg.has_state)
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_oscillating)
|
||||
call.set_oscillating(msg.oscillating);
|
||||
if (msg.has_speed)
|
||||
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
||||
if (msg.has_direction)
|
||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
bool APIConnection::send_light_state(light::LightState *light) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = light->get_traits();
|
||||
auto values = light->remote_values;
|
||||
LightStateResponse resp{};
|
||||
|
||||
resp.key = light->get_object_id_hash();
|
||||
resp.state = values.is_on();
|
||||
if (traits.get_supports_brightness())
|
||||
resp.brightness = values.get_brightness();
|
||||
if (traits.get_supports_rgb()) {
|
||||
resp.red = values.get_red();
|
||||
resp.green = values.get_green();
|
||||
resp.blue = values.get_blue();
|
||||
}
|
||||
if (traits.get_supports_rgb_white_value())
|
||||
resp.white = values.get_white();
|
||||
if (traits.get_supports_color_temperature())
|
||||
resp.color_temperature = values.get_color_temperature();
|
||||
if (light->supports_effects())
|
||||
resp.effect = light->get_effect_name();
|
||||
return this->send_light_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_light_info(light::LightState *light) {
|
||||
auto traits = light->get_traits();
|
||||
ListEntitiesLightResponse msg;
|
||||
msg.key = light->get_object_id_hash();
|
||||
msg.object_id = light->get_object_id();
|
||||
msg.name = light->get_name();
|
||||
msg.unique_id = get_default_unique_id("light", light);
|
||||
msg.supports_brightness = traits.get_supports_brightness();
|
||||
msg.supports_rgb = traits.get_supports_rgb();
|
||||
msg.supports_white_value = traits.get_supports_rgb_white_value();
|
||||
msg.supports_color_temperature = traits.get_supports_color_temperature();
|
||||
if (msg.supports_color_temperature) {
|
||||
msg.min_mireds = traits.get_min_mireds();
|
||||
msg.max_mireds = traits.get_max_mireds();
|
||||
}
|
||||
if (light->supports_effects()) {
|
||||
msg.effects.emplace_back("None");
|
||||
for (auto *effect : light->get_effects())
|
||||
msg.effects.push_back(effect->get_name());
|
||||
}
|
||||
return this->send_list_entities_light_response(msg);
|
||||
}
|
||||
void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
light::LightState *light = App.get_light_by_key(msg.key);
|
||||
if (light == nullptr)
|
||||
return;
|
||||
|
||||
auto call = light->make_call();
|
||||
if (msg.has_state)
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_brightness)
|
||||
call.set_brightness(msg.brightness);
|
||||
if (msg.has_rgb) {
|
||||
call.set_red(msg.red);
|
||||
call.set_green(msg.green);
|
||||
call.set_blue(msg.blue);
|
||||
}
|
||||
if (msg.has_white)
|
||||
call.set_white(msg.white);
|
||||
if (msg.has_color_temperature)
|
||||
call.set_color_temperature(msg.color_temperature);
|
||||
if (msg.has_transition_length)
|
||||
call.set_transition_length(msg.transition_length);
|
||||
if (msg.has_flash_length)
|
||||
call.set_flash_length(msg.flash_length);
|
||||
if (msg.has_effect)
|
||||
call.set_effect(msg.effect);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
SensorStateResponse resp{};
|
||||
resp.key = sensor->get_object_id_hash();
|
||||
resp.state = state;
|
||||
resp.missing_state = !sensor->has_state();
|
||||
return this->send_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
ListEntitiesSensorResponse msg;
|
||||
msg.key = sensor->get_object_id_hash();
|
||||
msg.object_id = sensor->get_object_id();
|
||||
msg.name = sensor->get_name();
|
||||
msg.unique_id = sensor->unique_id();
|
||||
if (msg.unique_id.empty())
|
||||
msg.unique_id = get_default_unique_id("sensor", sensor);
|
||||
msg.icon = sensor->get_icon();
|
||||
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||
msg.force_update = sensor->get_force_update();
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
SwitchStateResponse resp{};
|
||||
resp.key = a_switch->get_object_id_hash();
|
||||
resp.state = state;
|
||||
return this->send_switch_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
ListEntitiesSwitchResponse msg;
|
||||
msg.key = a_switch->get_object_id_hash();
|
||||
msg.object_id = a_switch->get_object_id();
|
||||
msg.name = a_switch->get_name();
|
||||
msg.unique_id = get_default_unique_id("switch", a_switch);
|
||||
msg.icon = a_switch->get_icon();
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
return this->send_list_entities_switch_response(msg);
|
||||
}
|
||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
|
||||
if (a_switch == nullptr)
|
||||
return;
|
||||
|
||||
if (msg.state)
|
||||
a_switch->turn_on();
|
||||
else
|
||||
a_switch->turn_off();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
TextSensorStateResponse resp{};
|
||||
resp.key = text_sensor->get_object_id_hash();
|
||||
resp.state = std::move(state);
|
||||
resp.missing_state = !text_sensor->has_state();
|
||||
return this->send_text_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
|
||||
ListEntitiesTextSensorResponse msg;
|
||||
msg.key = text_sensor->get_object_id_hash();
|
||||
msg.object_id = text_sensor->get_object_id();
|
||||
msg.name = text_sensor->get_name();
|
||||
msg.unique_id = text_sensor->unique_id();
|
||||
if (msg.unique_id.empty())
|
||||
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
|
||||
msg.icon = text_sensor->get_icon();
|
||||
return this->send_list_entities_text_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
auto traits = climate->get_traits();
|
||||
ClimateStateResponse resp{};
|
||||
resp.key = climate->get_object_id_hash();
|
||||
resp.mode = static_cast<enums::ClimateMode>(climate->mode);
|
||||
resp.action = static_cast<enums::ClimateAction>(climate->action);
|
||||
if (traits.get_supports_current_temperature())
|
||||
resp.current_temperature = climate->current_temperature;
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
resp.target_temperature_low = climate->target_temperature_low;
|
||||
resp.target_temperature_high = climate->target_temperature_high;
|
||||
} else {
|
||||
resp.target_temperature = climate->target_temperature;
|
||||
}
|
||||
if (traits.get_supports_away())
|
||||
resp.away = climate->away;
|
||||
if (traits.get_supports_fan_modes())
|
||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode);
|
||||
if (traits.get_supports_swing_modes())
|
||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||
return this->send_climate_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
auto traits = climate->get_traits();
|
||||
ListEntitiesClimateResponse msg;
|
||||
msg.key = climate->get_object_id_hash();
|
||||
msg.object_id = climate->get_object_id();
|
||||
msg.name = climate->get_name();
|
||||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) {
|
||||
if (traits.supports_mode(mode))
|
||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||
}
|
||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
||||
msg.supports_away = traits.get_supports_away();
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO,
|
||||
climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
|
||||
climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) {
|
||||
if (traits.supports_fan_mode(fan_mode))
|
||||
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
||||
}
|
||||
for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||
climate::CLIMATE_SWING_HORIZONTAL}) {
|
||||
if (traits.supports_swing_mode(swing_mode))
|
||||
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
|
||||
}
|
||||
return this->send_list_entities_climate_response(msg);
|
||||
}
|
||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
climate::Climate *climate = App.get_climate_by_key(msg.key);
|
||||
if (climate == nullptr)
|
||||
return;
|
||||
|
||||
auto call = climate->make_call();
|
||||
if (msg.has_mode)
|
||||
call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
|
||||
if (msg.has_target_temperature)
|
||||
call.set_target_temperature(msg.target_temperature);
|
||||
if (msg.has_target_temperature_low)
|
||||
call.set_target_temperature_low(msg.target_temperature_low);
|
||||
if (msg.has_target_temperature_high)
|
||||
call.set_target_temperature_high(msg.target_temperature_high);
|
||||
if (msg.has_away)
|
||||
call.set_away(msg.away);
|
||||
if (msg.has_fan_mode)
|
||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||
if (msg.has_swing_mode)
|
||||
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
return;
|
||||
if (this->image_reader_.available())
|
||||
return;
|
||||
this->image_reader_.set_image(image);
|
||||
}
|
||||
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
ListEntitiesCameraResponse msg;
|
||||
msg.key = camera->get_object_id_hash();
|
||||
msg.object_id = camera->get_object_id();
|
||||
msg.name = camera->get_name();
|
||||
msg.unique_id = get_default_unique_id("camera", camera);
|
||||
return this->send_list_entities_camera_response(msg);
|
||||
}
|
||||
void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||
if (esp32_camera::global_esp32_camera == nullptr)
|
||||
return;
|
||||
|
||||
if (msg.single)
|
||||
esp32_camera::global_esp32_camera->request_image();
|
||||
if (msg.stream)
|
||||
esp32_camera::global_esp32_camera->request_stream();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||
if (homeassistant::global_homeassistant_time != nullptr)
|
||||
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
|
||||
// Send raw so that we don't copy too much
|
||||
auto buffer = this->create_buffer();
|
||||
// LogLevel level = 1;
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(level));
|
||||
// string tag = 2;
|
||||
// buffer.encode_string(2, tag, strlen(tag));
|
||||
// string message = 3;
|
||||
buffer.encode_string(3, line, strlen(line));
|
||||
// SubscribeLogsResponse - 29
|
||||
bool success = this->send_buffer(buffer, 29);
|
||||
if (!success) {
|
||||
buffer = this->create_buffer();
|
||||
// bool send_failed = 4;
|
||||
buffer.encode_bool(4, true);
|
||||
return this->send_buffer(buffer, 29);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
this->client_info_ = msg.client_info + " (" + this->client_->remoteIP().toString().c_str();
|
||||
this->client_info_ += ")";
|
||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 3;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
}
|
||||
ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
||||
bool correct = this->parent_->check_password(msg.password);
|
||||
|
||||
ConnectResponse resp;
|
||||
// bool invalid_password = 1;
|
||||
resp.invalid_password = !correct;
|
||||
if (correct) {
|
||||
ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str());
|
||||
this->connection_state_ = ConnectionState::AUTHENTICATED;
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
this->send_time_request();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
DeviceInfoResponse resp{};
|
||||
resp.uses_password = this->parent_->uses_password();
|
||||
resp.name = App.get_name();
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
#ifdef ARDUINO_BOARD
|
||||
resp.model = ARDUINO_BOARD;
|
||||
#endif
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs())
|
||||
if (it.entity_id == msg.entity_id)
|
||||
it.callback(msg.state);
|
||||
}
|
||||
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
bool found = false;
|
||||
for (auto *service : this->parent_->get_user_services()) {
|
||||
if (service->execute_service(msg)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ESP_LOGV(TAG, "Could not find matching service!");
|
||||
}
|
||||
}
|
||||
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
if (!this->send_subscribe_home_assistant_state_response(resp)) {
|
||||
this->on_fatal_error();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
|
||||
if (this->remove_)
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> header;
|
||||
header.push_back(0x00);
|
||||
ProtoVarInt(buffer.get_buffer()->size()).encode(header);
|
||||
ProtoVarInt(message_type).encode(header);
|
||||
|
||||
size_t needed_space = buffer.get_buffer()->size() + header.size();
|
||||
|
||||
if (needed_space > this->client_->space()) {
|
||||
delay(0);
|
||||
if (needed_space > this->client_->space()) {
|
||||
// SubscribeLogsResponse
|
||||
if (message_type != 29) {
|
||||
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
|
||||
}
|
||||
delay(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->client_->add(reinterpret_cast<char *>(header.data()), header.size());
|
||||
this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size());
|
||||
bool ret = this->client_->send();
|
||||
return ret;
|
||||
}
|
||||
void APIConnection::on_unauthenticated_access() {
|
||||
ESP_LOGD(TAG, "'%s' tried to access without authentication.", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
}
|
||||
void APIConnection::on_no_setup_connection() {
|
||||
ESP_LOGD(TAG, "'%s' tried to access without full connection.", this->client_info_.c_str());
|
||||
this->on_fatal_error();
|
||||
}
|
||||
void APIConnection::on_fatal_error() {
|
||||
ESP_LOGV(TAG, "Error: Disconnecting %s", this->client_info_.c_str());
|
||||
this->client_->close();
|
||||
this->remove_ = true;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
172
esphome/components/api/api_connection.h
Normal file
172
esphome/components/api/api_connection.h
Normal file
@@ -0,0 +1,172 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIConnection : public APIServerConnection {
|
||||
public:
|
||||
APIConnection(AsyncClient *client, APIServer *parent);
|
||||
virtual ~APIConnection();
|
||||
|
||||
void disconnect_client();
|
||||
void loop();
|
||||
|
||||
bool send_list_info_done() {
|
||||
ListEntitiesDoneResponse resp;
|
||||
return this->send_list_entities_done_response(resp);
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
|
||||
bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state(cover::Cover *cover);
|
||||
bool send_cover_info(cover::Cover *cover);
|
||||
void cover_command(const CoverCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state(fan::FanState *fan);
|
||||
bool send_fan_info(fan::FanState *fan);
|
||||
void fan_command(const FanCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state(light::LightState *light);
|
||||
bool send_light_info(light::LightState *light);
|
||||
void light_command(const LightCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state(sensor::Sensor *sensor, float state);
|
||||
bool send_sensor_info(sensor::Sensor *sensor);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state(switch_::Switch *a_switch, bool state);
|
||||
bool send_switch_info(switch_::Switch *a_switch);
|
||||
void switch_command(const SwitchCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
|
||||
bool send_text_sensor_info(text_sensor::TextSensor *text_sensor);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
||||
bool send_camera_info(esp32_camera::ESP32Camera *camera);
|
||||
void camera_image(const CameraImageRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
bool send_climate_info(climate::Climate *climate);
|
||||
void climate_command(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
if (!this->service_call_subscription_)
|
||||
return;
|
||||
this->send_homeassistant_service_response(call);
|
||||
}
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void send_time_request() {
|
||||
GetTimeRequest req;
|
||||
this->send_get_time_request(req);
|
||||
}
|
||||
#endif
|
||||
|
||||
void on_disconnect_response(const DisconnectResponse &value) override {
|
||||
// we initiated disconnect_client
|
||||
this->next_close_ = true;
|
||||
}
|
||||
void on_ping_response(const PingResponse &value) override {
|
||||
// we initiated ping
|
||||
this->sent_ping_ = false;
|
||||
}
|
||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void on_get_time_response(const GetTimeResponse &value) override;
|
||||
#endif
|
||||
HelloResponse hello(const HelloRequest &msg) override;
|
||||
ConnectResponse connect(const ConnectRequest &msg) override;
|
||||
DisconnectResponse disconnect(const DisconnectRequest &msg) override {
|
||||
// remote initiated disconnect_client
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
}
|
||||
PingResponse ping(const PingRequest &msg) override { return {}; }
|
||||
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
||||
this->state_subscription_ = true;
|
||||
this->initial_state_iterator_.begin();
|
||||
}
|
||||
void subscribe_logs(const SubscribeLogsRequest &msg) override {
|
||||
this->log_subscription_ = msg.level;
|
||||
if (msg.dump_config)
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
|
||||
this->service_call_subscription_ = true;
|
||||
}
|
||||
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
GetTimeResponse get_time(const GetTimeRequest &msg) override {
|
||||
// TODO
|
||||
return {};
|
||||
}
|
||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
|
||||
bool is_connection_setup() override {
|
||||
return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
|
||||
}
|
||||
void on_fatal_error() override;
|
||||
void on_unauthenticated_access() override;
|
||||
void on_no_setup_connection() override;
|
||||
ProtoWriteBuffer create_buffer() override {
|
||||
this->send_buffer_.clear();
|
||||
return {&this->send_buffer_};
|
||||
}
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
|
||||
|
||||
protected:
|
||||
friend APIServer;
|
||||
|
||||
void on_error_(int8_t error);
|
||||
void on_disconnect_();
|
||||
void on_timeout_(uint32_t time);
|
||||
void on_data_(uint8_t *buf, size_t len);
|
||||
void parse_recv_buffer_();
|
||||
|
||||
enum class ConnectionState {
|
||||
WAITING_FOR_HELLO,
|
||||
CONNECTED,
|
||||
AUTHENTICATED,
|
||||
} connection_state_{ConnectionState::WAITING_FOR_HELLO};
|
||||
|
||||
bool remove_{false};
|
||||
|
||||
std::vector<uint8_t> send_buffer_;
|
||||
std::vector<uint8_t> recv_buffer_;
|
||||
|
||||
std::string client_info_;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
esp32_camera::CameraImageReader image_reader_;
|
||||
#endif
|
||||
|
||||
bool state_subscription_{false};
|
||||
int log_subscription_{ESPHOME_LOG_LEVEL_NONE};
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
bool current_nodelay_{false};
|
||||
bool next_close_{false};
|
||||
AsyncClient *client_;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
24
esphome/components/api/api_options.proto
Normal file
24
esphome/components/api/api_options.proto
Normal file
@@ -0,0 +1,24 @@
|
||||
syntax = "proto2";
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
|
||||
enum APISourceType {
|
||||
SOURCE_BOTH = 0;
|
||||
SOURCE_SERVER = 1;
|
||||
SOURCE_CLIENT = 2;
|
||||
}
|
||||
|
||||
message void {}
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
optional bool needs_setup_connection = 1038 [default=true];
|
||||
optional bool needs_authentication = 1039 [default=true];
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
optional uint32 id = 1036 [default=0];
|
||||
optional APISourceType source = 1037 [default=SOURCE_BOTH];
|
||||
optional string ifdef = 1038;
|
||||
optional bool log = 1039 [default=true];
|
||||
optional bool no_delay = 1040 [default=false];
|
||||
}
|
||||
2973
esphome/components/api/api_pb2.cpp
Normal file
2973
esphome/components/api/api_pb2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
745
esphome/components/api/api_pb2.h
Normal file
745
esphome/components/api/api_pb2.h
Normal file
@@ -0,0 +1,745 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See scripts/api_protobuf/api_protobuf.py
|
||||
#pragma once
|
||||
|
||||
#include "proto.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
namespace enums {
|
||||
|
||||
enum LegacyCoverState : uint32_t {
|
||||
LEGACY_COVER_STATE_OPEN = 0,
|
||||
LEGACY_COVER_STATE_CLOSED = 1,
|
||||
};
|
||||
enum CoverOperation : uint32_t {
|
||||
COVER_OPERATION_IDLE = 0,
|
||||
COVER_OPERATION_IS_OPENING = 1,
|
||||
COVER_OPERATION_IS_CLOSING = 2,
|
||||
};
|
||||
enum LegacyCoverCommand : uint32_t {
|
||||
LEGACY_COVER_COMMAND_OPEN = 0,
|
||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
||||
LEGACY_COVER_COMMAND_STOP = 2,
|
||||
};
|
||||
enum FanSpeed : uint32_t {
|
||||
FAN_SPEED_LOW = 0,
|
||||
FAN_SPEED_MEDIUM = 1,
|
||||
FAN_SPEED_HIGH = 2,
|
||||
};
|
||||
enum FanDirection : uint32_t {
|
||||
FAN_DIRECTION_FORWARD = 0,
|
||||
FAN_DIRECTION_REVERSE = 1,
|
||||
};
|
||||
enum LogLevel : uint32_t {
|
||||
LOG_LEVEL_NONE = 0,
|
||||
LOG_LEVEL_ERROR = 1,
|
||||
LOG_LEVEL_WARN = 2,
|
||||
LOG_LEVEL_INFO = 3,
|
||||
LOG_LEVEL_DEBUG = 4,
|
||||
LOG_LEVEL_VERBOSE = 5,
|
||||
LOG_LEVEL_VERY_VERBOSE = 6,
|
||||
};
|
||||
enum ServiceArgType : uint32_t {
|
||||
SERVICE_ARG_TYPE_BOOL = 0,
|
||||
SERVICE_ARG_TYPE_INT = 1,
|
||||
SERVICE_ARG_TYPE_FLOAT = 2,
|
||||
SERVICE_ARG_TYPE_STRING = 3,
|
||||
SERVICE_ARG_TYPE_BOOL_ARRAY = 4,
|
||||
SERVICE_ARG_TYPE_INT_ARRAY = 5,
|
||||
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6,
|
||||
SERVICE_ARG_TYPE_STRING_ARRAY = 7,
|
||||
};
|
||||
enum ClimateMode : uint32_t {
|
||||
CLIMATE_MODE_OFF = 0,
|
||||
CLIMATE_MODE_AUTO = 1,
|
||||
CLIMATE_MODE_COOL = 2,
|
||||
CLIMATE_MODE_HEAT = 3,
|
||||
CLIMATE_MODE_FAN_ONLY = 4,
|
||||
CLIMATE_MODE_DRY = 5,
|
||||
};
|
||||
enum ClimateFanMode : uint32_t {
|
||||
CLIMATE_FAN_ON = 0,
|
||||
CLIMATE_FAN_OFF = 1,
|
||||
CLIMATE_FAN_AUTO = 2,
|
||||
CLIMATE_FAN_LOW = 3,
|
||||
CLIMATE_FAN_MEDIUM = 4,
|
||||
CLIMATE_FAN_HIGH = 5,
|
||||
CLIMATE_FAN_MIDDLE = 6,
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
};
|
||||
enum ClimateSwingMode : uint32_t {
|
||||
CLIMATE_SWING_OFF = 0,
|
||||
CLIMATE_SWING_BOTH = 1,
|
||||
CLIMATE_SWING_VERTICAL = 2,
|
||||
CLIMATE_SWING_HORIZONTAL = 3,
|
||||
};
|
||||
enum ClimateAction : uint32_t {
|
||||
CLIMATE_ACTION_OFF = 0,
|
||||
CLIMATE_ACTION_COOLING = 2,
|
||||
CLIMATE_ACTION_HEATING = 3,
|
||||
CLIMATE_ACTION_IDLE = 4,
|
||||
CLIMATE_ACTION_DRYING = 5,
|
||||
CLIMATE_ACTION_FAN = 6,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string client_info{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HelloResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t api_version_major{0}; // NOLINT
|
||||
uint32_t api_version_minor{0}; // NOLINT
|
||||
std::string server_info{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ConnectRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string password{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ConnectResponse : public ProtoMessage {
|
||||
public:
|
||||
bool invalid_password{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DisconnectRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DisconnectResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class PingRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class PingResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DeviceInfoRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class DeviceInfoResponse : public ProtoMessage {
|
||||
public:
|
||||
bool uses_password{false}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string mac_address{}; // NOLINT
|
||||
std::string esphome_version{}; // NOLINT
|
||||
std::string compilation_time{}; // NOLINT
|
||||
std::string model{}; // NOLINT
|
||||
bool has_deep_sleep{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class ListEntitiesDoneResponse : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class SubscribeStatesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
bool is_status_binary_sensor{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BinarySensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool assumed_state{false}; // NOLINT
|
||||
bool supports_position{false}; // NOLINT
|
||||
bool supports_tilt{false}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CoverStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
enums::LegacyCoverState legacy_state{}; // NOLINT
|
||||
float position{0.0f}; // NOLINT
|
||||
float tilt{0.0f}; // NOLINT
|
||||
enums::CoverOperation current_operation{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CoverCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_legacy_command{false}; // NOLINT
|
||||
enums::LegacyCoverCommand legacy_command{}; // NOLINT
|
||||
bool has_position{false}; // NOLINT
|
||||
float position{0.0f}; // NOLINT
|
||||
bool has_tilt{false}; // NOLINT
|
||||
float tilt{0.0f}; // NOLINT
|
||||
bool stop{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesFanResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_oscillation{false}; // NOLINT
|
||||
bool supports_speed{false}; // NOLINT
|
||||
bool supports_direction{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_speed{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
bool has_oscillating{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
bool has_direction{false}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesLightResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_brightness{false}; // NOLINT
|
||||
bool supports_rgb{false}; // NOLINT
|
||||
bool supports_white_value{false}; // NOLINT
|
||||
bool supports_color_temperature{false}; // NOLINT
|
||||
float min_mireds{0.0f}; // NOLINT
|
||||
float max_mireds{0.0f}; // NOLINT
|
||||
std::vector<std::string> effects{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LightStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
float brightness{0.0f}; // NOLINT
|
||||
float red{0.0f}; // NOLINT
|
||||
float green{0.0f}; // NOLINT
|
||||
float blue{0.0f}; // NOLINT
|
||||
float white{0.0f}; // NOLINT
|
||||
float color_temperature{0.0f}; // NOLINT
|
||||
std::string effect{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LightCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_brightness{false}; // NOLINT
|
||||
float brightness{0.0f}; // NOLINT
|
||||
bool has_rgb{false}; // NOLINT
|
||||
float red{0.0f}; // NOLINT
|
||||
float green{0.0f}; // NOLINT
|
||||
float blue{0.0f}; // NOLINT
|
||||
bool has_white{false}; // NOLINT
|
||||
float white{0.0f}; // NOLINT
|
||||
bool has_color_temperature{false}; // NOLINT
|
||||
float color_temperature{0.0f}; // NOLINT
|
||||
bool has_transition_length{false}; // NOLINT
|
||||
uint32_t transition_length{0}; // NOLINT
|
||||
bool has_flash_length{false}; // NOLINT
|
||||
uint32_t flash_length{0}; // NOLINT
|
||||
bool has_effect{false}; // NOLINT
|
||||
std::string effect{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
std::string unit_of_measurement{}; // NOLINT
|
||||
int32_t accuracy_decimals{0}; // NOLINT
|
||||
bool force_update{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
float state{0.0f}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
bool assumed_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SwitchStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SwitchCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class TextSensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string state{}; // NOLINT
|
||||
bool missing_state{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeLogsRequest : public ProtoMessage {
|
||||
public:
|
||||
enums::LogLevel level{}; // NOLINT
|
||||
bool dump_config{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeLogsResponse : public ProtoMessage {
|
||||
public:
|
||||
enums::LogLevel level{}; // NOLINT
|
||||
std::string tag{}; // NOLINT
|
||||
std::string message{}; // NOLINT
|
||||
bool send_failed{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class HomeassistantServiceMap : public ProtoMessage {
|
||||
public:
|
||||
std::string key{}; // NOLINT
|
||||
std::string value{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HomeassistantServiceResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string service{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> data{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> data_template{}; // NOLINT
|
||||
std::vector<HomeassistantServiceMap> variables{}; // NOLINT
|
||||
bool is_event{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string entity_id{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class HomeAssistantStateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string entity_id{}; // NOLINT
|
||||
std::string state{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class GetTimeRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
};
|
||||
class GetTimeResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t epoch_seconds{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
};
|
||||
class ListEntitiesServicesArgument : public ProtoMessage {
|
||||
public:
|
||||
std::string name{}; // NOLINT
|
||||
enums::ServiceArgType type{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesServicesResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string name{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::vector<ListEntitiesServicesArgument> args{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ExecuteServiceArgument : public ProtoMessage {
|
||||
public:
|
||||
bool bool_{false}; // NOLINT
|
||||
int32_t legacy_int{0}; // NOLINT
|
||||
float float_{0.0f}; // NOLINT
|
||||
std::string string_{}; // NOLINT
|
||||
int32_t int_{0}; // NOLINT
|
||||
std::vector<bool> bool_array{}; // NOLINT
|
||||
std::vector<int32_t> int_array{}; // NOLINT
|
||||
std::vector<float> float_array{}; // NOLINT
|
||||
std::vector<std::string> string_array{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ExecuteServiceRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::vector<ExecuteServiceArgument> args{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesCameraResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class CameraImageResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string data{}; // NOLINT
|
||||
bool done{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CameraImageRequest : public ProtoMessage {
|
||||
public:
|
||||
bool single{false}; // NOLINT
|
||||
bool stream{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
std::string name{}; // NOLINT
|
||||
std::string unique_id{}; // NOLINT
|
||||
bool supports_current_temperature{false}; // NOLINT
|
||||
bool supports_two_point_target_temperature{false}; // NOLINT
|
||||
std::vector<enums::ClimateMode> supported_modes{}; // NOLINT
|
||||
float visual_min_temperature{0.0f}; // NOLINT
|
||||
float visual_max_temperature{0.0f}; // NOLINT
|
||||
float visual_temperature_step{0.0f}; // NOLINT
|
||||
bool supports_away{false}; // NOLINT
|
||||
bool supports_action{false}; // NOLINT
|
||||
std::vector<enums::ClimateFanMode> supported_fan_modes{}; // NOLINT
|
||||
std::vector<enums::ClimateSwingMode> supported_swing_modes{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
enums::ClimateMode mode{}; // NOLINT
|
||||
float current_temperature{0.0f}; // NOLINT
|
||||
float target_temperature{0.0f}; // NOLINT
|
||||
float target_temperature_low{0.0f}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
enums::ClimateAction action{}; // NOLINT
|
||||
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_mode{false}; // NOLINT
|
||||
enums::ClimateMode mode{}; // NOLINT
|
||||
bool has_target_temperature{false}; // NOLINT
|
||||
float target_temperature{0.0f}; // NOLINT
|
||||
bool has_target_temperature_low{false}; // NOLINT
|
||||
float target_temperature_low{0.0f}; // NOLINT
|
||||
bool has_target_temperature_high{false}; // NOLINT
|
||||
float target_temperature_high{0.0f}; // NOLINT
|
||||
bool has_away{false}; // NOLINT
|
||||
bool away{false}; // NOLINT
|
||||
bool has_fan_mode{false}; // NOLINT
|
||||
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||
bool has_swing_mode{false}; // NOLINT
|
||||
enums::ClimateSwingMode swing_mode{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
552
esphome/components/api/api_pb2_service.cpp
Normal file
552
esphome/components/api/api_pb2_service.cpp
Normal file
@@ -0,0 +1,552 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See scripts/api_protobuf/api_protobuf.py
|
||||
#include "api_pb2_service.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.service";
|
||||
|
||||
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<HelloResponse>(msg, 2);
|
||||
}
|
||||
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ConnectResponse>(msg, 4);
|
||||
}
|
||||
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
||||
return this->send_message_<DisconnectRequest>(msg, 5);
|
||||
}
|
||||
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<DisconnectResponse>(msg, 6);
|
||||
}
|
||||
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
||||
return this->send_message_<PingRequest>(msg, 7);
|
||||
}
|
||||
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<PingResponse>(msg, 8);
|
||||
}
|
||||
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
||||
}
|
||||
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<CoverStateResponse>(msg, 22);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<FanStateResponse>(msg, 23);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<LightStateResponse>(msg, 24);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<SensorStateResponse>(msg, 25);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<SwitchStateResponse>(msg, 26);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
||||
}
|
||||
#endif
|
||||
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
||||
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
||||
}
|
||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
||||
}
|
||||
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
||||
const SubscribeHomeAssistantStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
||||
}
|
||||
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
||||
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
||||
return this->send_message_<GetTimeRequest>(msg, 36);
|
||||
}
|
||||
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<GetTimeResponse>(msg, 37);
|
||||
}
|
||||
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
||||
}
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<CameraImageResponse>(msg, 44);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
||||
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
||||
return this->send_message_<ClimateStateResponse>(msg, 47);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
HelloRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
|
||||
this->on_hello_request(msg);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
ConnectRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
|
||||
this->on_connect_request(msg);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
DisconnectRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
|
||||
this->on_disconnect_request(msg);
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
DisconnectResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
|
||||
this->on_disconnect_response(msg);
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
PingRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
|
||||
this->on_ping_request(msg);
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
PingResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
|
||||
this->on_ping_response(msg);
|
||||
break;
|
||||
}
|
||||
case 9: {
|
||||
DeviceInfoRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
|
||||
this->on_device_info_request(msg);
|
||||
break;
|
||||
}
|
||||
case 11: {
|
||||
ListEntitiesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
|
||||
this->on_list_entities_request(msg);
|
||||
break;
|
||||
}
|
||||
case 20: {
|
||||
SubscribeStatesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_states_request(msg);
|
||||
break;
|
||||
}
|
||||
case 28: {
|
||||
SubscribeLogsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_logs_request(msg);
|
||||
break;
|
||||
}
|
||||
case 30: {
|
||||
#ifdef USE_COVER
|
||||
CoverCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
|
||||
this->on_cover_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 31: {
|
||||
#ifdef USE_FAN
|
||||
FanCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
|
||||
this->on_fan_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 32: {
|
||||
#ifdef USE_LIGHT
|
||||
LightCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
|
||||
this->on_light_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 33: {
|
||||
#ifdef USE_SWITCH
|
||||
SwitchCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
|
||||
this->on_switch_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
SubscribeHomeassistantServicesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_homeassistant_services_request(msg);
|
||||
break;
|
||||
}
|
||||
case 36: {
|
||||
GetTimeRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
|
||||
this->on_get_time_request(msg);
|
||||
break;
|
||||
}
|
||||
case 37: {
|
||||
GetTimeResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
|
||||
this->on_get_time_response(msg);
|
||||
break;
|
||||
}
|
||||
case 38: {
|
||||
SubscribeHomeAssistantStatesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
|
||||
this->on_subscribe_home_assistant_states_request(msg);
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
HomeAssistantStateResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
this->on_home_assistant_state_response(msg);
|
||||
break;
|
||||
}
|
||||
case 42: {
|
||||
ExecuteServiceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
|
||||
this->on_execute_service_request(msg);
|
||||
break;
|
||||
}
|
||||
case 45: {
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CameraImageRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
|
||||
this->on_camera_image_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 48: {
|
||||
#ifdef USE_CLIMATE
|
||||
ClimateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
|
||||
this->on_climate_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void APIServerConnection::on_hello_request(const HelloRequest &msg) {
|
||||
HelloResponse ret = this->hello(msg);
|
||||
if (!this->send_hello_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
|
||||
ConnectResponse ret = this->connect(msg);
|
||||
if (!this->send_connect_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
||||
DisconnectResponse ret = this->disconnect(msg);
|
||||
if (!this->send_disconnect_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_ping_request(const PingRequest &msg) {
|
||||
PingResponse ret = this->ping(msg);
|
||||
if (!this->send_ping_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
DeviceInfoResponse ret = this->device_info(msg);
|
||||
if (!this->send_device_info_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->list_entities(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_states(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_logs(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_homeassistant_services_request(
|
||||
const SubscribeHomeassistantServicesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_homeassistant_services(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_home_assistant_states(msg);
|
||||
}
|
||||
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
GetTimeResponse ret = this->get_time(msg);
|
||||
if (!this->send_get_time_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->execute_service(msg);
|
||||
}
|
||||
#ifdef USE_COVER
|
||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->cover_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->fan_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->light_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->switch_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->camera_image(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->climate_command(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
185
esphome/components/api/api_pb2_service.h
Normal file
185
esphome/components/api/api_pb2_service.h
Normal file
@@ -0,0 +1,185 @@
|
||||
// This file was automatically generated with a tool.
|
||||
// See scripts/api_protobuf/api_protobuf.py
|
||||
#pragma once
|
||||
|
||||
#include "api_pb2.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServerConnectionBase : public ProtoService {
|
||||
public:
|
||||
virtual void on_hello_request(const HelloRequest &value){};
|
||||
bool send_hello_response(const HelloResponse &msg);
|
||||
virtual void on_connect_request(const ConnectRequest &value){};
|
||||
bool send_connect_response(const ConnectResponse &msg);
|
||||
bool send_disconnect_request(const DisconnectRequest &msg);
|
||||
virtual void on_disconnect_request(const DisconnectRequest &value){};
|
||||
bool send_disconnect_response(const DisconnectResponse &msg);
|
||||
virtual void on_disconnect_response(const DisconnectResponse &value){};
|
||||
bool send_ping_request(const PingRequest &msg);
|
||||
virtual void on_ping_request(const PingRequest &value){};
|
||||
bool send_ping_response(const PingResponse &msg);
|
||||
virtual void on_ping_response(const PingResponse &value){};
|
||||
virtual void on_device_info_request(const DeviceInfoRequest &value){};
|
||||
bool send_device_info_response(const DeviceInfoResponse &msg);
|
||||
virtual void on_list_entities_request(const ListEntitiesRequest &value){};
|
||||
bool send_list_entities_done_response(const ListEntitiesDoneResponse &msg);
|
||||
virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){};
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state_response(const BinarySensorStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_list_entities_cover_response(const ListEntitiesCoverResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state_response(const CoverStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
virtual void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_list_entities_fan_response(const ListEntitiesFanResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state_response(const FanStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual void on_fan_command_request(const FanCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_list_entities_light_response(const ListEntitiesLightResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state_response(const LightStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual void on_light_command_request(const LightCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state_response(const SensorStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state_response(const SwitchStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state_response(const TextSensorStateResponse &msg);
|
||||
#endif
|
||||
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
|
||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
|
||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||
bool send_subscribe_home_assistant_state_response(const SubscribeHomeAssistantStateResponse &msg);
|
||||
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
bool send_get_time_request(const GetTimeRequest &msg);
|
||||
virtual void on_get_time_request(const GetTimeRequest &value){};
|
||||
bool send_get_time_response(const GetTimeResponse &msg);
|
||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||
bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg);
|
||||
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool send_list_entities_camera_response(const ListEntitiesCameraResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool send_camera_image_response(const CameraImageResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_list_entities_climate_response(const ListEntitiesClimateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state_response(const ClimateStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
class APIServerConnection : public APIServerConnectionBase {
|
||||
public:
|
||||
virtual HelloResponse hello(const HelloRequest &msg) = 0;
|
||||
virtual ConnectResponse connect(const ConnectRequest &msg) = 0;
|
||||
virtual DisconnectResponse disconnect(const DisconnectRequest &msg) = 0;
|
||||
virtual PingResponse ping(const PingRequest &msg) = 0;
|
||||
virtual DeviceInfoResponse device_info(const DeviceInfoRequest &msg) = 0;
|
||||
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
|
||||
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
|
||||
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
|
||||
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||
#ifdef USE_COVER
|
||||
virtual void cover_command(const CoverCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual void fan_command(const FanCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual void light_command(const LightCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual void camera_image(const CameraImageRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
void on_connect_request(const ConnectRequest &msg) override;
|
||||
void on_disconnect_request(const DisconnectRequest &msg) override;
|
||||
void on_ping_request(const PingRequest &msg) override;
|
||||
void on_device_info_request(const DeviceInfoRequest &msg) override;
|
||||
void on_list_entities_request(const ListEntitiesRequest &msg) override;
|
||||
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
|
||||
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||
#ifdef USE_COVER
|
||||
void on_cover_command_request(const CoverCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_command_request(const FanCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_command_request(const LightCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_command_request(const SwitchCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void on_camera_image_request(const CameraImageRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_command_request(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
239
esphome/components/api/api_server.cpp
Normal file
239
esphome/components/api/api_server.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "api_server.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api";
|
||||
|
||||
// APIServer
|
||||
void APIServer::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
|
||||
this->setup_controller();
|
||||
this->server_ = AsyncServer(this->port_);
|
||||
this->server_.setNoDelay(false);
|
||||
this->server_.begin();
|
||||
this->server_.onClient(
|
||||
[](void *s, AsyncClient *client) {
|
||||
if (client == nullptr)
|
||||
return;
|
||||
|
||||
// can't print here because in lwIP thread
|
||||
// ESP_LOGD(TAG, "New client connected from %s", client->remoteIP().toString().c_str());
|
||||
auto *a_this = (APIServer *) s;
|
||||
a_this->clients_.push_back(new APIConnection(client, a_this));
|
||||
},
|
||||
this);
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
||||
for (auto *c : this->clients_) {
|
||||
if (!c->remove_)
|
||||
c->send_log_message(level, tag, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
this->last_connected_ = millis();
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
||||
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
for (auto *c : this->clients_)
|
||||
if (!c->remove_)
|
||||
c->send_camera_state(image);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void APIServer::loop() {
|
||||
// Partition clients into remove and active
|
||||
auto new_end =
|
||||
std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; });
|
||||
// print disconnection messages
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it) {
|
||||
ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str());
|
||||
}
|
||||
// only then delete the pointers, otherwise log routine
|
||||
// would access freed memory
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it)
|
||||
delete *it;
|
||||
// resize vector
|
||||
this->clients_.erase(new_end, this->clients_.end());
|
||||
|
||||
for (auto *client : this->clients_) {
|
||||
client->loop();
|
||||
}
|
||||
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
const uint32_t now = millis();
|
||||
if (!this->is_connected()) {
|
||||
if (now - this->last_connected_ > this->reboot_timeout_) {
|
||||
ESP_LOGE(TAG, "No client connected to API. Rebooting...");
|
||||
App.reboot();
|
||||
}
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->last_connected_ = now;
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "API Server:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_);
|
||||
}
|
||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
||||
bool APIServer::check_password(const std::string &password) const {
|
||||
// depend only on input password length
|
||||
const char *a = this->password_.c_str();
|
||||
uint32_t len_a = this->password_.length();
|
||||
const char *b = password.c_str();
|
||||
uint32_t len_b = password.length();
|
||||
|
||||
// disable optimization with volatile
|
||||
volatile uint32_t length = len_b;
|
||||
volatile const char *left = nullptr;
|
||||
volatile const char *right = b;
|
||||
uint8_t result = 0;
|
||||
|
||||
if (len_a == length) {
|
||||
left = *((volatile const char **) &a);
|
||||
result = 0;
|
||||
}
|
||||
if (len_a != length) {
|
||||
left = b;
|
||||
result = 1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
result |= *left++ ^ *right++; // NOLINT
|
||||
}
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_binary_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
void APIServer::on_cover_update(cover::Cover *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_cover_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void APIServer::on_fan_update(fan::FanState *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_fan_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void APIServer::on_light_update(light::LightState *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_light_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_switch_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_text_sensor_state(obj, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void APIServer::on_climate_update(climate::Climate *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto *c : this->clients_)
|
||||
c->send_climate_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
APIServer *global_api_server = nullptr;
|
||||
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
for (auto *client : this->clients_) {
|
||||
client->send_homeassistant_service_call(call);
|
||||
}
|
||||
}
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f) {
|
||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||
.entity_id = std::move(entity_id),
|
||||
.callback = std::move(f),
|
||||
});
|
||||
}
|
||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
||||
return this->state_subs_;
|
||||
}
|
||||
uint16_t APIServer::get_port() const { return this->port_; }
|
||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto *client : this->clients_) {
|
||||
if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED)
|
||||
client->send_time_request();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
|
||||
void APIServer::on_shutdown() {
|
||||
for (auto *c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
100
esphome/components/api/api_server.h
Normal file
100
esphome/components/api/api_server.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "util.h"
|
||||
#include "list_entities.h"
|
||||
#include "subscribe_state.h"
|
||||
#include "homeassistant_service.h"
|
||||
#include "user_services.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServer : public Component, public Controller {
|
||||
public:
|
||||
APIServer();
|
||||
void setup() override;
|
||||
uint16_t get_port() const;
|
||||
float get_setup_priority() const override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void on_shutdown() override;
|
||||
bool check_password(const std::string &password) const;
|
||||
bool uses_password() const;
|
||||
void set_port(uint16_t port);
|
||||
void set_password(const std::string &password);
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
void handle_disconnect(APIConnection *conn);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void on_cover_update(cover::Cover *obj) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_update(fan::FanState *obj) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_update(light::LightState *obj) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void on_sensor_update(sensor::Sensor *obj, float state) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void on_switch_update(switch_::Switch *obj, bool state) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
#endif
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void request_time();
|
||||
#endif
|
||||
|
||||
bool is_connected() const;
|
||||
|
||||
struct HomeAssistantStateSubscription {
|
||||
std::string entity_id;
|
||||
std::function<void(std::string)> callback;
|
||||
};
|
||||
|
||||
void subscribe_home_assistant_state(std::string entity_id, std::function<void(std::string)> f);
|
||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
||||
|
||||
protected:
|
||||
AsyncServer server_{0};
|
||||
uint16_t port_{6053};
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t last_connected_{0};
|
||||
std::vector<APIConnection *> clients_;
|
||||
std::string password_;
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
};
|
||||
|
||||
extern APIServer *global_api_server;
|
||||
|
||||
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
||||
public:
|
||||
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
214
esphome/components/api/custom_api_device.h
Normal file
214
esphome/components/api/custom_api_device.h
Normal file
@@ -0,0 +1,214 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "user_services.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||
public:
|
||||
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||
void (T::*callback)(Ts...))
|
||||
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
|
||||
|
||||
protected:
|
||||
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT
|
||||
|
||||
T *obj_;
|
||||
void (T::*callback_)(Ts...);
|
||||
};
|
||||
|
||||
class CustomAPIDevice {
|
||||
public:
|
||||
/// Return if a client (such as Home Assistant) is connected to the native API.
|
||||
bool is_connected() const { return global_api_server->is_connected(); }
|
||||
|
||||
/** Register a custom native API service that will show up in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* register_service(&CustomNativeAPI::on_start_washer_cycle, "start_washer_cycle",
|
||||
* {"cycle_length"});
|
||||
* }
|
||||
*
|
||||
* void on_start_washer_cycle(int cycle_length) {
|
||||
* // Start washer cycle.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @tparam Ts The argument types for the service, automatically deduced from the function arguments.
|
||||
* @param callback The member function to call when the service is triggered.
|
||||
* @param name The name of the service to register.
|
||||
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
template<typename T, typename... Ts>
|
||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
/** Register a custom native API service that will show up in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* register_service(&CustomNativeAPI::on_hello_world, "hello_world");
|
||||
* }
|
||||
*
|
||||
* void on_hello_world() {
|
||||
* // Hello World service called.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the service is triggered.
|
||||
* @param name The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
|
||||
/** Subscribe to the state of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string state) {
|
||||
* // State of sensor.weather_forecast is `state`
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param entity_id The entity_id to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id) {
|
||||
auto f = std::bind(callback, (T *) this, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||
}
|
||||
|
||||
/** Subscribe to the state of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
* }
|
||||
*
|
||||
* void on_state_changed(std::string entity_id, std::string state) {
|
||||
* // State of `entity_id` is `state`
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @tparam T The class type creating the service, automatically deduced from the function pointer.
|
||||
* @param callback The member function to call when the entity state changes.
|
||||
* @param entity_id The entity_id to track.
|
||||
*/
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id) {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, f);
|
||||
}
|
||||
|
||||
/** Call a Home Assistant service from ESPHome.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* call_homeassistant_service("homeassistant.restart");
|
||||
* ```
|
||||
*
|
||||
* @param service_name The service to call.
|
||||
*/
|
||||
void call_homeassistant_service(const std::string &service_name) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Call a Home Assistant service from ESPHome.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* call_homeassistant_service("light.turn_on", {
|
||||
* {"entity_id", "light.my_light"},
|
||||
* {"brightness", "127"},
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param service_name The service to call.
|
||||
* @param data The data for the service call, mapping from string to string.
|
||||
*/
|
||||
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
for (auto &it : data) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.first;
|
||||
kv.value = it.second;
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Fire an ESPHome event in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* fire_homeassistant_event("esphome.something_happened");
|
||||
* ```
|
||||
*
|
||||
* @param event_name The event to fire.
|
||||
*/
|
||||
void fire_homeassistant_event(const std::string &event_name) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = event_name;
|
||||
resp.is_event = true;
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
/** Fire an ESPHome event in Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```cpp
|
||||
* fire_homeassistant_event("esphome.something_happened", {
|
||||
* {"my_value", "500"},
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param event_name The event to fire.
|
||||
* @param data The data for the event, mapping from string to string.
|
||||
*/
|
||||
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = service_name;
|
||||
resp.is_event = true;
|
||||
for (auto &it : data) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.first;
|
||||
kv.value = it.second;
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
67
esphome/components/api/homeassistant_service.h
Normal file
67
esphome/components/api/homeassistant_service.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<typename... Ts> class TemplatableKeyValuePair {
|
||||
public:
|
||||
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
|
||||
std::string key;
|
||||
TemplatableStringValue<Ts...> value;
|
||||
};
|
||||
|
||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
||||
|
||||
TEMPLATABLE_STRING_VALUE(service);
|
||||
template<typename T> void add_data(std::string key, T value) {
|
||||
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
template<typename T> void add_data_template(std::string key, T value) {
|
||||
this->data_template_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
template<typename T> void add_variable(std::string key, T value) {
|
||||
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
|
||||
void play(Ts... x) override {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = this->service_.value(x...);
|
||||
resp.is_event = this->is_event_;
|
||||
for (auto &it : this->data_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.data.push_back(kv);
|
||||
}
|
||||
for (auto &it : this->data_template_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.data_template.push_back(kv);
|
||||
}
|
||||
for (auto &it : this->variables_) {
|
||||
HomeassistantServiceMap kv;
|
||||
kv.key = it.key;
|
||||
kv.value = it.value.value(x...);
|
||||
resp.variables.push_back(kv);
|
||||
}
|
||||
this->parent_->send_homeassistant_service_call(resp);
|
||||
}
|
||||
|
||||
protected:
|
||||
APIServer *parent_;
|
||||
bool is_event_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
55
esphome/components/api/list_entities.cpp
Normal file
55
esphome/components/api/list_entities.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "list_entities.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_connection.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
return this->client_->send_binary_sensor_info(binary_sensor);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); }
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); }
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); }
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); }
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
return this->client_->send_text_sensor_info(text_sensor);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||
auto resp = service->encode_list_service_response();
|
||||
return this->client_->send_list_entities_services_response(resp);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
||||
return this->client_->send_camera_info(camera);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
52
esphome/components/api/list_entities.h
Normal file
52
esphome/components/api/list_entities.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIConnection;
|
||||
|
||||
class ListEntitiesIterator : public ComponentIterator {
|
||||
public:
|
||||
ListEntitiesIterator(APIServer *server, APIConnection *client);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::FanState *fan) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool on_sensor(sensor::Sensor *sensor) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool on_switch(switch_::Switch *a_switch) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
|
||||
#endif
|
||||
bool on_service(UserServiceDescriptor *service) override;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool on_camera(esp32_camera::ESP32Camera *camera) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
#include "api_server.h"
|
||||
90
esphome/components/api/proto.cpp
Normal file
90
esphome/components/api/proto.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "proto.h"
|
||||
#include "util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *TAG = "api.proto";
|
||||
|
||||
void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
uint32_t i = 0;
|
||||
bool error = false;
|
||||
while (i < length) {
|
||||
uint32_t consumed;
|
||||
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid field start at %u", i);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t field_type = (res->as_uint32()) & 0b111;
|
||||
uint32_t field_id = (res->as_uint32()) >> 3;
|
||||
i += consumed;
|
||||
|
||||
switch (field_type) {
|
||||
case 0: { // VarInt
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid VarInt at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_varint(field_id, *res)) {
|
||||
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32());
|
||||
}
|
||||
i += consumed;
|
||||
break;
|
||||
}
|
||||
case 2: { // Length-delimited
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
i += consumed;
|
||||
if (field_length > length - i) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
|
||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
|
||||
}
|
||||
i += field_length;
|
||||
break;
|
||||
}
|
||||
case 5: { // 32-bit
|
||||
if (length - i < 4) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
|
||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
|
||||
}
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGV(TAG, "Invalid field type at %u", i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (error) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ProtoMessage::dump() const {
|
||||
std::string out;
|
||||
this->dump_to(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
278
esphome/components/api/proto.h
Normal file
278
esphome/components/api/proto.h
Normal file
@@ -0,0 +1,278 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
|
||||
class ProtoVarInt {
|
||||
public:
|
||||
ProtoVarInt() : value_(0) {}
|
||||
explicit ProtoVarInt(uint64_t value) : value_(value) {}
|
||||
|
||||
static optional<ProtoVarInt> parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed) {
|
||||
if (consumed != nullptr)
|
||||
*consumed = 0;
|
||||
|
||||
if (len == 0)
|
||||
return {};
|
||||
|
||||
uint64_t result = 0;
|
||||
uint8_t bitpos = 0;
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
uint8_t val = buffer[i];
|
||||
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
|
||||
bitpos += 7;
|
||||
if ((val & 0x80) == 0) {
|
||||
if (consumed != nullptr)
|
||||
*consumed = i + 1;
|
||||
return ProtoVarInt(result);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t as_uint32() const { return this->value_; }
|
||||
uint64_t as_uint64() const { return this->value_; }
|
||||
bool as_bool() const { return this->value_; }
|
||||
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
|
||||
int32_t as_int32() const {
|
||||
// Not ZigZag encoded
|
||||
return static_cast<int32_t>(this->as_int64());
|
||||
}
|
||||
int64_t as_int64() const {
|
||||
// Not ZigZag encoded
|
||||
return static_cast<int64_t>(this->value_);
|
||||
}
|
||||
int32_t as_sint32() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1)
|
||||
return static_cast<int32_t>(~(this->value_ >> 1));
|
||||
else
|
||||
return static_cast<int32_t>(this->value_ >> 1);
|
||||
}
|
||||
int64_t as_sint64() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1)
|
||||
return static_cast<int64_t>(~(this->value_ >> 1));
|
||||
else
|
||||
return static_cast<int64_t>(this->value_ >> 1);
|
||||
}
|
||||
void encode(std::vector<uint8_t> &out) {
|
||||
uint32_t val = this->value_;
|
||||
if (val <= 0x7F) {
|
||||
out.push_back(val);
|
||||
return;
|
||||
}
|
||||
while (val) {
|
||||
uint8_t temp = val & 0x7F;
|
||||
val >>= 7;
|
||||
if (val) {
|
||||
out.push_back(temp | 0x80);
|
||||
} else {
|
||||
out.push_back(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
class ProtoLengthDelimited {
|
||||
public:
|
||||
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
|
||||
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
|
||||
template<class C> C as_message() const {
|
||||
auto msg = C();
|
||||
msg.decode(this->value_, this->length_);
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint8_t *const value_;
|
||||
const size_t length_;
|
||||
};
|
||||
|
||||
class Proto32Bit {
|
||||
public:
|
||||
explicit Proto32Bit(uint32_t value) : value_(value) {}
|
||||
uint32_t as_fixed32() const { return this->value_; }
|
||||
int32_t as_sfixed32() const { return static_cast<int32_t>(this->value_); }
|
||||
float as_float() const {
|
||||
union {
|
||||
uint32_t raw;
|
||||
float value;
|
||||
} s{};
|
||||
s.raw = this->value_;
|
||||
return s.value;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint32_t value_;
|
||||
};
|
||||
|
||||
class Proto64Bit {
|
||||
public:
|
||||
explicit Proto64Bit(uint64_t value) : value_(value) {}
|
||||
uint64_t as_fixed64() const { return this->value_; }
|
||||
int64_t as_sfixed64() const { return static_cast<int64_t>(this->value_); }
|
||||
double as_double() const {
|
||||
union {
|
||||
uint64_t raw;
|
||||
double value;
|
||||
} s{};
|
||||
s.raw = this->value_;
|
||||
return s.value;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint64_t value_;
|
||||
};
|
||||
|
||||
class ProtoWriteBuffer {
|
||||
public:
|
||||
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
|
||||
void write(uint8_t value) { this->buffer_->push_back(value); }
|
||||
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
|
||||
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) {
|
||||
uint32_t val = (field_id << 3) | (type & 0b111);
|
||||
this->encode_varint_raw(val);
|
||||
}
|
||||
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 2);
|
||||
this->encode_varint_raw(len);
|
||||
auto *data = reinterpret_cast<const uint8_t *>(string);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
this->write(data[i]);
|
||||
}
|
||||
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
||||
this->encode_string(field_id, value.data(), value.size());
|
||||
}
|
||||
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||
}
|
||||
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(value);
|
||||
}
|
||||
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(ProtoVarInt(value));
|
||||
}
|
||||
void encode_bool(uint32_t field_id, bool value, bool force = false) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->write(0x01);
|
||||
}
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 5);
|
||||
this->write((value >> 0) & 0xFF);
|
||||
this->write((value >> 8) & 0xFF);
|
||||
this->write((value >> 16) & 0xFF);
|
||||
this->write((value >> 24) & 0xFF);
|
||||
}
|
||||
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
|
||||
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||
}
|
||||
void encode_float(uint32_t field_id, float value, bool force = false) {
|
||||
if (value == 0.0f && !force)
|
||||
return;
|
||||
|
||||
union {
|
||||
float value;
|
||||
uint32_t raw;
|
||||
} val{};
|
||||
val.value = value;
|
||||
this->encode_fixed32(field_id, val.raw);
|
||||
}
|
||||
void encode_int32(uint32_t field_id, int32_t value, bool force = false) {
|
||||
if (value < 0) {
|
||||
// negative int32 is always 10 byte long
|
||||
this->encode_int64(field_id, value, force);
|
||||
return;
|
||||
}
|
||||
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||
}
|
||||
void encode_int64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
||||
}
|
||||
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
||||
uint32_t uvalue;
|
||||
if (value < 0)
|
||||
uvalue = ~(value << 1);
|
||||
else
|
||||
uvalue = value << 1;
|
||||
this->encode_uint32(field_id, uvalue, force);
|
||||
}
|
||||
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
|
||||
this->encode_field_raw(field_id, 2);
|
||||
size_t begin = this->buffer_->size();
|
||||
|
||||
value.encode(*this);
|
||||
|
||||
const uint32_t nested_length = this->buffer_->size() - begin;
|
||||
// add size varint
|
||||
std::vector<uint8_t> var;
|
||||
ProtoVarInt(nested_length).encode(var);
|
||||
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
|
||||
}
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual void encode(ProtoWriteBuffer buffer) const = 0;
|
||||
void decode(const uint8_t *buffer, size_t length);
|
||||
std::string dump() const;
|
||||
virtual void dump_to(std::string &out) const = 0;
|
||||
|
||||
protected:
|
||||
virtual bool decode_varint(uint32_t field_id, ProtoVarInt value) { return false; }
|
||||
virtual bool decode_length(uint32_t field_id, ProtoLengthDelimited value) { return false; }
|
||||
virtual bool decode_32bit(uint32_t field_id, Proto32Bit value) { return false; }
|
||||
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
|
||||
};
|
||||
|
||||
template<typename T> const char *proto_enum_to_string(T value);
|
||||
|
||||
class ProtoService {
|
||||
public:
|
||||
protected:
|
||||
virtual bool is_authenticated() = 0;
|
||||
virtual bool is_connection_setup() = 0;
|
||||
virtual void on_fatal_error() = 0;
|
||||
virtual void on_unauthenticated_access() = 0;
|
||||
virtual void on_no_setup_connection() = 0;
|
||||
virtual ProtoWriteBuffer create_buffer() = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
|
||||
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||
|
||||
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
||||
auto buffer = this->create_buffer();
|
||||
msg.encode(buffer);
|
||||
return this->send_buffer(buffer, message_type);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
44
esphome/components/api/subscribe_state.cpp
Normal file
44
esphome/components/api/subscribe_state.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "subscribe_state.h"
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
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) {
|
||||
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) {
|
||||
return this->client_->send_text_sensor_state(text_sensor, text_sensor->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
|
||||
#endif
|
||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
47
esphome/components/api/subscribe_state.h
Normal file
47
esphome/components/api/subscribe_state.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIConnection;
|
||||
|
||||
class InitialStateIterator : public ComponentIterator {
|
||||
public:
|
||||
InitialStateIterator(APIServer *server, APIConnection *client);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::FanState *fan) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool on_sensor(sensor::Sensor *sensor) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool on_switch(switch_::Switch *a_switch) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
#endif
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
#include "api_server.h"
|
||||
42
esphome/components/api/user_services.cpp
Normal file
42
esphome/components/api/user_services.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "user_services.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; }
|
||||
template<> int get_execute_arg_value<int>(const ExecuteServiceArgument &arg) {
|
||||
if (arg.legacy_int != 0)
|
||||
return arg.legacy_int;
|
||||
return arg.int_;
|
||||
}
|
||||
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
||||
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
||||
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.bool_array;
|
||||
}
|
||||
template<> std::vector<int> get_execute_arg_value<std::vector<int>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.int_array;
|
||||
}
|
||||
template<> std::vector<float> get_execute_arg_value<std::vector<float>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.float_array;
|
||||
}
|
||||
template<> std::vector<std::string> get_execute_arg_value<std::vector<std::string>>(const ExecuteServiceArgument &arg) {
|
||||
return arg.string_array;
|
||||
}
|
||||
|
||||
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<int>() { return enums::SERVICE_ARG_TYPE_INT; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<int>>() { return enums::SERVICE_ARG_TYPE_INT_ARRAY; }
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<float>>() {
|
||||
return enums::SERVICE_ARG_TYPE_FLOAT_ARRAY;
|
||||
}
|
||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>() {
|
||||
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
72
esphome/components/api/user_services.h
Normal file
72
esphome/components/api/user_services.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "api_pb2.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class UserServiceDescriptor {
|
||||
public:
|
||||
virtual ListEntitiesServicesResponse encode_list_service_response() = 0;
|
||||
|
||||
virtual bool execute_service(const ExecuteServiceRequest &req) = 0;
|
||||
};
|
||||
|
||||
template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
|
||||
|
||||
template<typename T> enums::ServiceArgType to_service_arg_type();
|
||||
|
||||
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
public:
|
||||
UserServiceBase(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||
: name_(name), arg_names_(arg_names) {
|
||||
this->key_ = fnv1_hash(this->name_);
|
||||
}
|
||||
|
||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||
ListEntitiesServicesResponse msg;
|
||||
msg.name = this->name_;
|
||||
msg.key = this->key_;
|
||||
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||
for (int i = 0; i < sizeof...(Ts); i++) {
|
||||
ListEntitiesServicesArgument arg;
|
||||
arg.type = arg_types[i];
|
||||
arg.name = this->arg_names_[i];
|
||||
msg.args.push_back(arg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
bool execute_service(const ExecuteServiceRequest &req) override {
|
||||
if (req.key != this->key_)
|
||||
return false;
|
||||
if (req.args.size() != this->arg_names_.size())
|
||||
return false;
|
||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void execute(Ts... x) = 0;
|
||||
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
std::string name_;
|
||||
uint32_t key_{0};
|
||||
std::array<std::string, sizeof...(Ts)> arg_names_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
|
||||
public:
|
||||
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
|
||||
: UserServiceBase<Ts...>(name, arg_names) {}
|
||||
|
||||
protected:
|
||||
void execute(Ts... x) override { this->trigger(x...); } // NOLINT
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
193
esphome/components/api/util.cpp
Normal file
193
esphome/components/api/util.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#include "util.h"
|
||||
#include "api_server.h"
|
||||
#include "user_services.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {}
|
||||
void ComponentIterator::begin() {
|
||||
this->state_ = IteratorState::BEGIN;
|
||||
this->at_ = 0;
|
||||
}
|
||||
void ComponentIterator::advance() {
|
||||
bool advance_platform = false;
|
||||
bool success = true;
|
||||
switch (this->state_) {
|
||||
case IteratorState::NONE:
|
||||
// not started
|
||||
return;
|
||||
case IteratorState::BEGIN:
|
||||
if (this->on_begin()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
case IteratorState::BINARY_SENSOR:
|
||||
if (this->at_ >= App.get_binary_sensors().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *binary_sensor = App.get_binary_sensors()[this->at_];
|
||||
if (binary_sensor->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_binary_sensor(binary_sensor);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
case IteratorState::COVER:
|
||||
if (this->at_ >= App.get_covers().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *cover = App.get_covers()[this->at_];
|
||||
if (cover->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_cover(cover);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
case IteratorState::FAN:
|
||||
if (this->at_ >= App.get_fans().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *fan = App.get_fans()[this->at_];
|
||||
if (fan->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_fan(fan);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
case IteratorState::LIGHT:
|
||||
if (this->at_ >= App.get_lights().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *light = App.get_lights()[this->at_];
|
||||
if (light->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_light(light);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
case IteratorState::SENSOR:
|
||||
if (this->at_ >= App.get_sensors().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *sensor = App.get_sensors()[this->at_];
|
||||
if (sensor->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_sensor(sensor);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
case IteratorState::SWITCH:
|
||||
if (this->at_ >= App.get_switches().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *a_switch = App.get_switches()[this->at_];
|
||||
if (a_switch->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_switch(a_switch);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
case IteratorState::TEXT_SENSOR:
|
||||
if (this->at_ >= App.get_text_sensors().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *text_sensor = App.get_text_sensors()[this->at_];
|
||||
if (text_sensor->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_text_sensor(text_sensor);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IteratorState ::SERVICE:
|
||||
if (this->at_ >= this->server_->get_user_services().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *service = this->server_->get_user_services()[this->at_];
|
||||
success = this->on_service(service);
|
||||
}
|
||||
break;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
case IteratorState::CAMERA:
|
||||
if (esp32_camera::global_esp32_camera == nullptr) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
if (esp32_camera::global_esp32_camera->is_internal()) {
|
||||
advance_platform = success = true;
|
||||
break;
|
||||
} else {
|
||||
advance_platform = success = this->on_camera(esp32_camera::global_esp32_camera);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
case IteratorState::CLIMATE:
|
||||
if (this->at_ >= App.get_climates().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *climate = App.get_climates()[this->at_];
|
||||
if (climate->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_climate(climate);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IteratorState::MAX:
|
||||
if (this->on_end()) {
|
||||
this->state_ = IteratorState::NONE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (advance_platform) {
|
||||
this->state_ = static_cast<IteratorState>(static_cast<uint32_t>(this->state_) + 1);
|
||||
this->at_ = 0;
|
||||
} else if (success) {
|
||||
this->at_++;
|
||||
}
|
||||
}
|
||||
bool ComponentIterator::on_end() { return true; }
|
||||
bool ComponentIterator::on_begin() { return true; }
|
||||
bool ComponentIterator::on_service(UserServiceDescriptor *service) { return true; }
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; }
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
93
esphome/components/api/util.h
Normal file
93
esphome/components/api/util.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
#include "esphome/components/esp32_camera/esp32_camera.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServer;
|
||||
class UserServiceDescriptor;
|
||||
|
||||
class ComponentIterator {
|
||||
public:
|
||||
ComponentIterator(APIServer *server);
|
||||
|
||||
void begin();
|
||||
void advance();
|
||||
virtual bool on_begin();
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
virtual bool on_cover(cover::Cover *cover) = 0;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual bool on_fan(fan::FanState *fan) = 0;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual bool on_light(light::LightState *light) = 0;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
virtual bool on_sensor(sensor::Sensor *sensor) = 0;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
virtual bool on_switch(switch_::Switch *a_switch) = 0;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
|
||||
#endif
|
||||
virtual bool on_service(UserServiceDescriptor *service);
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual bool on_camera(esp32_camera::ESP32Camera *camera);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
virtual bool on_climate(climate::Climate *climate) = 0;
|
||||
#endif
|
||||
virtual bool on_end();
|
||||
|
||||
protected:
|
||||
enum class IteratorState {
|
||||
NONE = 0,
|
||||
BEGIN,
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
BINARY_SENSOR,
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
COVER,
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
FAN,
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
LIGHT,
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
SENSOR,
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
SWITCH,
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
TEXT_SENSOR,
|
||||
#endif
|
||||
SERVICE,
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CAMERA,
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
CLIMATE,
|
||||
#endif
|
||||
MAX,
|
||||
} state_{IteratorState::NONE};
|
||||
size_t at_{0};
|
||||
|
||||
APIServer *server_;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
46
esphome/components/as3935/__init__.py
Normal file
46
esphome/components/as3935/__init__.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \
|
||||
CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \
|
||||
CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE
|
||||
from esphome.core import coroutine
|
||||
|
||||
AUTO_LOAD = ['sensor', 'binary_sensor']
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_AS3935_ID = 'as3935_id'
|
||||
|
||||
as3935_ns = cg.esphome_ns.namespace('as3935')
|
||||
AS3935 = as3935_ns.class_('AS3935Component', cg.Component)
|
||||
|
||||
CONF_IRQ_PIN = 'irq_pin'
|
||||
AS3935_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(AS3935),
|
||||
cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
|
||||
|
||||
cv.Optional(CONF_INDOOR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
|
||||
cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
|
||||
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
||||
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
|
||||
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
|
||||
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
||||
})
|
||||
|
||||
|
||||
@coroutine
|
||||
def setup_as3935(var, config):
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
irq_pin = yield cg.gpio_pin_expression(config[CONF_IRQ_PIN])
|
||||
cg.add(var.set_irq_pin(irq_pin))
|
||||
cg.add(var.set_indoor(config[CONF_INDOOR]))
|
||||
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))
|
||||
cg.add(var.set_watchdog_threshold(config[CONF_WATCHDOG_THRESHOLD]))
|
||||
cg.add(var.set_spike_rejection(config[CONF_SPIKE_REJECTION]))
|
||||
cg.add(var.set_lightning_threshold(config[CONF_LIGHTNING_THRESHOLD]))
|
||||
cg.add(var.set_mask_disturber(config[CONF_MASK_DISTURBER]))
|
||||
cg.add(var.set_div_ratio(config[CONF_DIV_RATIO]))
|
||||
cg.add(var.set_capacitance(config[CONF_CAPACITANCE]))
|
||||
226
esphome/components/as3935/as3935.cpp
Normal file
226
esphome/components/as3935/as3935.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#include "as3935.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935 {
|
||||
|
||||
static const char *TAG = "as3935";
|
||||
|
||||
void AS3935Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AS3935...");
|
||||
|
||||
this->irq_pin_->setup();
|
||||
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
|
||||
|
||||
// Write properties to sensor
|
||||
this->write_indoor(this->indoor_);
|
||||
this->write_noise_level(this->noise_level_);
|
||||
this->write_watchdog_threshold(this->watchdog_threshold_);
|
||||
this->write_spike_rejection(this->spike_rejection_);
|
||||
this->write_lightning_threshold(this->lightning_threshold_);
|
||||
this->write_mask_disturber(this->mask_disturber_);
|
||||
this->write_div_ratio(this->div_ratio_);
|
||||
this->write_capacitance(this->capacitance_);
|
||||
}
|
||||
|
||||
void AS3935Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AS3935:");
|
||||
LOG_PIN(" Interrupt Pin: ", this->irq_pin_);
|
||||
LOG_BINARY_SENSOR(" ", "Thunder alert", this->thunder_alert_binary_sensor_);
|
||||
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
|
||||
LOG_SENSOR(" ", "Lightning energy", this->energy_sensor_);
|
||||
}
|
||||
|
||||
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void AS3935Component::loop() {
|
||||
if (!this->irq_pin_->digital_read())
|
||||
return;
|
||||
|
||||
uint8_t int_value = this->read_interrupt_register_();
|
||||
if (int_value == NOISE_INT) {
|
||||
ESP_LOGI(TAG, "Noise was detected - try increasing the noise level value!");
|
||||
} else if (int_value == DISTURBER_INT) {
|
||||
ESP_LOGI(TAG, "Disturber was detected - try increasing the spike rejection value!");
|
||||
} else if (int_value == LIGHTNING_INT) {
|
||||
ESP_LOGI(TAG, "Lightning has been detected!");
|
||||
if (this->thunder_alert_binary_sensor_ != nullptr)
|
||||
this->thunder_alert_binary_sensor_->publish_state(true);
|
||||
uint8_t distance = this->get_distance_to_storm_();
|
||||
if (this->distance_sensor_ != nullptr)
|
||||
this->distance_sensor_->publish_state(distance);
|
||||
uint32_t energy = this->get_lightning_energy_();
|
||||
if (this->energy_sensor_ != nullptr)
|
||||
this->energy_sensor_->publish_state(energy);
|
||||
}
|
||||
this->thunder_alert_binary_sensor_->publish_state(false);
|
||||
}
|
||||
|
||||
void AS3935Component::write_indoor(bool indoor) {
|
||||
ESP_LOGV(TAG, "Setting indoor to %d", indoor);
|
||||
if (indoor)
|
||||
this->write_register(AFE_GAIN, GAIN_MASK, INDOOR, 1);
|
||||
else
|
||||
this->write_register(AFE_GAIN, GAIN_MASK, OUTDOOR, 1);
|
||||
}
|
||||
// REG0x01, bits[3:0], manufacturer default: 0010 (2).
|
||||
// This setting determines the threshold for events that trigger the
|
||||
// IRQ Pin.
|
||||
void AS3935Component::write_watchdog_threshold(uint8_t watchdog_threshold) {
|
||||
ESP_LOGV(TAG, "Setting watchdog sensitivity to %d", watchdog_threshold);
|
||||
if ((watchdog_threshold < 1) || (watchdog_threshold > 10)) // 10 is the max sensitivity setting
|
||||
return;
|
||||
this->write_register(THRESHOLD, THRESH_MASK, watchdog_threshold, 0);
|
||||
}
|
||||
|
||||
// REG0x01, bits [6:4], manufacturer default: 010 (2).
|
||||
// The noise floor level is compared to a known reference voltage. If this
|
||||
// level is exceeded the chip will issue an interrupt to the IRQ pin,
|
||||
// broadcasting that it can not operate properly due to noise (INT_NH).
|
||||
// Check datasheet for specific noise level tolerances when setting this register.
|
||||
void AS3935Component::write_noise_level(uint8_t noise_level) {
|
||||
ESP_LOGV(TAG, "Setting noise level to %d", noise_level);
|
||||
if ((noise_level < 1) || (noise_level > 7))
|
||||
return;
|
||||
|
||||
this->write_register(THRESHOLD, NOISE_FLOOR_MASK, noise_level, 4);
|
||||
}
|
||||
// REG0x02, bits [3:0], manufacturer default: 0010 (2).
|
||||
// This setting, like the watchdog threshold, can help determine between false
|
||||
// events and actual lightning. The shape of the spike is analyzed during the
|
||||
// chip's signal validation routine. Increasing this value increases robustness
|
||||
// at the cost of sensitivity to distant events.
|
||||
void AS3935Component::write_spike_rejection(uint8_t spike_rejection) {
|
||||
ESP_LOGV(TAG, "Setting spike rejection to %d", spike_rejection);
|
||||
if ((spike_rejection < 1) || (spike_rejection > 11))
|
||||
return;
|
||||
|
||||
this->write_register(LIGHTNING_REG, SPIKE_MASK, spike_rejection, 0);
|
||||
}
|
||||
// REG0x02, bits [5:4], manufacturer default: 0 (single lightning strike).
|
||||
// The number of lightning events before IRQ is set high. 15 minutes is The
|
||||
// window of time before the number of detected lightning events is reset.
|
||||
// The number of lightning strikes can be set to 1,5,9, or 16.
|
||||
void AS3935Component::write_lightning_threshold(uint8_t lightning_threshold) {
|
||||
ESP_LOGV(TAG, "Setting lightning threshold to %d", lightning_threshold);
|
||||
switch (lightning_threshold) {
|
||||
case 1:
|
||||
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 0, 4); // Demonstrative
|
||||
break;
|
||||
case 5:
|
||||
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 1, 4);
|
||||
break;
|
||||
case 9:
|
||||
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 1, 5);
|
||||
break;
|
||||
case 16:
|
||||
this->write_register(LIGHTNING_REG, ((1 << 5) | (1 << 4)), 3, 4);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
// REG0x03, bit [5], manufacturer default: 0.
|
||||
// This setting will return whether or not disturbers trigger the IRQ Pin.
|
||||
void AS3935Component::write_mask_disturber(bool enabled) {
|
||||
ESP_LOGV(TAG, "Setting mask disturber to %d", enabled);
|
||||
if (enabled) {
|
||||
this->write_register(INT_MASK_ANT, (1 << 5), 1, 5);
|
||||
} else {
|
||||
this->write_register(INT_MASK_ANT, (1 << 5), 0, 5);
|
||||
}
|
||||
}
|
||||
// REG0x03, bit [7:6], manufacturer default: 0 (16 division ratio).
|
||||
// The antenna is designed to resonate at 500kHz and so can be tuned with the
|
||||
// following setting. The accuracy of the antenna must be within 3.5 percent of
|
||||
// that value for proper signal validation and distance estimation.
|
||||
void AS3935Component::write_div_ratio(uint8_t div_ratio) {
|
||||
ESP_LOGV(TAG, "Setting div ratio to %d", div_ratio);
|
||||
switch (div_ratio) {
|
||||
case 16:
|
||||
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 0, 6);
|
||||
break;
|
||||
case 22:
|
||||
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 1, 6);
|
||||
break;
|
||||
case 64:
|
||||
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 1, 7);
|
||||
break;
|
||||
case 128:
|
||||
this->write_register(INT_MASK_ANT, ((1 << 7) | (1 << 6)), 3, 6);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
// REG0x08, bits [3:0], manufacturer default: 0.
|
||||
// This setting will add capacitance to the series RLC antenna on the product
|
||||
// to help tune its resonance. The datasheet specifies being within 3.5 percent
|
||||
// of 500kHz to get optimal lightning detection and distance sensing.
|
||||
// It's possible to add up to 120pF in steps of 8pF to the antenna.
|
||||
void AS3935Component::write_capacitance(uint8_t capacitance) {
|
||||
ESP_LOGV(TAG, "Setting tune cap to %d pF", capacitance * 8);
|
||||
this->write_register(FREQ_DISP_IRQ, CAP_MASK, capacitance, 0);
|
||||
}
|
||||
|
||||
// REG0x03, bits [3:0], manufacturer default: 0.
|
||||
// When there is an event that exceeds the watchdog threshold, the register is written
|
||||
// with the type of event. This consists of two messages: INT_D (disturber detected) and
|
||||
// INT_L (Lightning detected). A third interrupt INT_NH (noise level too HIGH)
|
||||
// indicates that the noise level has been exceeded and will persist until the
|
||||
// noise has ended. Events are active HIGH. There is a one second window of time to
|
||||
// read the interrupt register after lightning is detected, and 1.5 after
|
||||
// disturber.
|
||||
uint8_t AS3935Component::read_interrupt_register_() {
|
||||
// A 2ms delay is added to allow for the memory register to be populated
|
||||
// after the interrupt pin goes HIGH. See "Interrupt Management" in
|
||||
// datasheet.
|
||||
ESP_LOGV(TAG, "Calling read_interrupt_register_");
|
||||
delay(2);
|
||||
return this->read_register_(INT_MASK_ANT, INT_MASK);
|
||||
}
|
||||
|
||||
// REG0x02, bit [6], manufacturer default: 1.
|
||||
// This register clears the number of lightning strikes that has been read in
|
||||
// the last 15 minute block.
|
||||
void AS3935Component::clear_statistics_() {
|
||||
// Write high, then low, then high to clear.
|
||||
ESP_LOGV(TAG, "Calling clear_statistics_");
|
||||
this->write_register(LIGHTNING_REG, (1 << 6), 1, 6);
|
||||
this->write_register(LIGHTNING_REG, (1 << 6), 0, 6);
|
||||
this->write_register(LIGHTNING_REG, (1 << 6), 1, 6);
|
||||
}
|
||||
|
||||
// REG0x07, bit [5:0], manufacturer default: 0.
|
||||
// This register holds the distance to the front of the storm and not the
|
||||
// distance to a lightning strike.
|
||||
uint8_t AS3935Component::get_distance_to_storm_() {
|
||||
ESP_LOGV(TAG, "Calling get_distance_to_storm_");
|
||||
return this->read_register_(DISTANCE, DISTANCE_MASK);
|
||||
}
|
||||
|
||||
uint32_t AS3935Component::get_lightning_energy_() {
|
||||
ESP_LOGV(TAG, "Calling get_lightning_energy_");
|
||||
uint32_t pure_light = 0; // Variable for lightning energy which is just a pure number.
|
||||
uint32_t temp = 0;
|
||||
// Temp variable for lightning energy.
|
||||
temp = this->read_register_(ENERGY_LIGHT_MMSB, ENERGY_MASK);
|
||||
// Temporary Value is large enough to handle a shift of 16 bits.
|
||||
pure_light = temp << 16;
|
||||
temp = this->read_register(ENERGY_LIGHT_MSB);
|
||||
// Temporary value is large enough to handle a shift of 8 bits.
|
||||
pure_light |= temp << 8;
|
||||
// No shift here, directly OR'ed into pure_light variable.
|
||||
temp = this->read_register(ENERGY_LIGHT_LSB);
|
||||
pure_light |= temp;
|
||||
return pure_light;
|
||||
}
|
||||
|
||||
uint8_t AS3935Component::read_register_(uint8_t reg, uint8_t mask) {
|
||||
uint8_t value = this->read_register(reg);
|
||||
value &= (~mask);
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace as3935
|
||||
} // namespace esphome
|
||||
110
esphome/components/as3935/as3935.h
Normal file
110
esphome/components/as3935/as3935.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935 {
|
||||
|
||||
enum AS3935RegisterNames {
|
||||
AFE_GAIN = 0x00,
|
||||
THRESHOLD,
|
||||
LIGHTNING_REG,
|
||||
INT_MASK_ANT,
|
||||
ENERGY_LIGHT_LSB,
|
||||
ENERGY_LIGHT_MSB,
|
||||
ENERGY_LIGHT_MMSB,
|
||||
DISTANCE,
|
||||
FREQ_DISP_IRQ,
|
||||
CALIB_TRCO = 0x3A,
|
||||
CALIB_SRCO = 0x3B,
|
||||
DEFAULT_RESET = 0x3C,
|
||||
CALIB_RCO = 0x3D
|
||||
};
|
||||
|
||||
enum AS3935RegisterMasks {
|
||||
GAIN_MASK = 0x3E,
|
||||
SPIKE_MASK = 0xF,
|
||||
IO_MASK = 0xC1,
|
||||
DISTANCE_MASK = 0xC0,
|
||||
INT_MASK = 0xF0,
|
||||
THRESH_MASK = 0x0F,
|
||||
R_SPIKE_MASK = 0xF0,
|
||||
ENERGY_MASK = 0xF0,
|
||||
CAP_MASK = 0xF0,
|
||||
LIGHT_MASK = 0xCF,
|
||||
DISTURB_MASK = 0xDF,
|
||||
NOISE_FLOOR_MASK = 0x70,
|
||||
OSC_MASK = 0xE0,
|
||||
CALIB_MASK = 0x7F,
|
||||
DIV_MASK = 0x3F
|
||||
};
|
||||
|
||||
enum AS3935Values {
|
||||
AS3935_ADDR = 0x03,
|
||||
INDOOR = 0x12,
|
||||
OUTDOOR = 0xE,
|
||||
LIGHTNING_INT = 0x08,
|
||||
DISTURBER_INT = 0x04,
|
||||
NOISE_INT = 0x01
|
||||
};
|
||||
|
||||
class AS3935Component : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void loop() override;
|
||||
|
||||
void set_irq_pin(GPIOPin *irq_pin) { irq_pin_ = irq_pin; }
|
||||
void set_distance_sensor(sensor::Sensor *distance_sensor) { distance_sensor_ = distance_sensor; }
|
||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||
void set_thunder_alert_binary_sensor(binary_sensor::BinarySensor *thunder_alert_binary_sensor) {
|
||||
thunder_alert_binary_sensor_ = thunder_alert_binary_sensor;
|
||||
}
|
||||
void set_indoor(bool indoor) { indoor_ = indoor; }
|
||||
void write_indoor(bool indoor);
|
||||
void set_noise_level(uint8_t noise_level) { noise_level_ = noise_level; }
|
||||
void write_noise_level(uint8_t noise_level);
|
||||
void set_watchdog_threshold(uint8_t watchdog_threshold) { watchdog_threshold_ = watchdog_threshold; }
|
||||
void write_watchdog_threshold(uint8_t watchdog_threshold);
|
||||
void set_spike_rejection(uint8_t spike_rejection) { spike_rejection_ = spike_rejection; }
|
||||
void write_spike_rejection(uint8_t write_spike_rejection);
|
||||
void set_lightning_threshold(uint8_t lightning_threshold) { lightning_threshold_ = lightning_threshold; }
|
||||
void write_lightning_threshold(uint8_t lightning_threshold);
|
||||
void set_mask_disturber(bool mask_disturber) { mask_disturber_ = mask_disturber; }
|
||||
void write_mask_disturber(bool enabled);
|
||||
void set_div_ratio(uint8_t div_ratio) { div_ratio_ = div_ratio; }
|
||||
void write_div_ratio(uint8_t div_ratio);
|
||||
void set_capacitance(uint8_t capacitance) { capacitance_ = capacitance; }
|
||||
void write_capacitance(uint8_t capacitance);
|
||||
|
||||
protected:
|
||||
uint8_t read_interrupt_register_();
|
||||
void clear_statistics_();
|
||||
uint8_t get_distance_to_storm_();
|
||||
uint32_t get_lightning_energy_();
|
||||
|
||||
virtual uint8_t read_register(uint8_t reg) = 0;
|
||||
uint8_t read_register_(uint8_t reg, uint8_t mask);
|
||||
|
||||
virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0;
|
||||
|
||||
sensor::Sensor *distance_sensor_;
|
||||
sensor::Sensor *energy_sensor_;
|
||||
binary_sensor::BinarySensor *thunder_alert_binary_sensor_;
|
||||
GPIOPin *irq_pin_;
|
||||
|
||||
bool indoor_;
|
||||
uint8_t noise_level_;
|
||||
uint8_t watchdog_threshold_;
|
||||
uint8_t spike_rejection_;
|
||||
uint8_t lightning_threshold_;
|
||||
bool mask_disturber_;
|
||||
uint8_t div_ratio_;
|
||||
uint8_t capacitance_;
|
||||
};
|
||||
|
||||
} // namespace as3935
|
||||
} // namespace esphome
|
||||
16
esphome/components/as3935/binary_sensor.py
Normal file
16
esphome/components/as3935/binary_sensor.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from . import AS3935, CONF_AS3935_ID
|
||||
|
||||
DEPENDENCIES = ['as3935']
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
})
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
||||
var = yield binary_sensor.new_binary_sensor(config)
|
||||
cg.add(hub.set_thunder_alert_binary_sensor(var))
|
||||
30
esphome/components/as3935/sensor.py
Normal file
30
esphome/components/as3935/sensor.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \
|
||||
UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH
|
||||
from . import AS3935, CONF_AS3935_ID
|
||||
|
||||
DEPENDENCIES = ['as3935']
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
|
||||
cv.Optional(CONF_DISTANCE):
|
||||
sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1),
|
||||
cv.Optional(CONF_LIGHTNING_ENERGY):
|
||||
sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
hub = yield cg.get_variable(config[CONF_AS3935_ID])
|
||||
|
||||
if CONF_DISTANCE in config:
|
||||
conf = config[CONF_DISTANCE]
|
||||
distance_sensor = yield sensor.new_sensor(conf)
|
||||
cg.add(hub.set_distance_sensor(distance_sensor))
|
||||
|
||||
if CONF_LIGHTNING_ENERGY in config:
|
||||
conf = config[CONF_LIGHTNING_ENERGY]
|
||||
lightning_energy_sensor = yield sensor.new_sensor(conf)
|
||||
cg.add(hub.set_energy_sensor(lightning_energy_sensor))
|
||||
20
esphome/components/as3935_i2c/__init__.py
Normal file
20
esphome/components/as3935_i2c/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import as3935, i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['as3935']
|
||||
DEPENDENCIES = ['i2c']
|
||||
|
||||
as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c')
|
||||
I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(I2CAS3935),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03)))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield as3935.setup_as3935(var, config)
|
||||
yield i2c.register_i2c_device(var, config)
|
||||
40
esphome/components/as3935_i2c/as3935_i2c.cpp
Normal file
40
esphome/components/as3935_i2c/as3935_i2c.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "as3935_i2c.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935_i2c {
|
||||
|
||||
static const char *TAG = "as3935_i2c";
|
||||
|
||||
void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
|
||||
uint8_t write_reg;
|
||||
if (!this->read_byte(reg, &write_reg)) {
|
||||
this->mark_failed();
|
||||
ESP_LOGW(TAG, "read_byte failed - increase log level for more details!");
|
||||
return;
|
||||
}
|
||||
|
||||
write_reg &= (~mask);
|
||||
write_reg |= (bits << start_pos);
|
||||
|
||||
if (!this->write_byte(reg, write_reg)) {
|
||||
ESP_LOGW(TAG, "write_byte failed - increase log level for more details!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t I2CAS3935Component::read_register(uint8_t reg) {
|
||||
uint8_t value;
|
||||
if (!this->read_byte(reg, &value, 2)) {
|
||||
ESP_LOGW(TAG, "Read failed!");
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
void I2CAS3935Component::dump_config() {
|
||||
AS3935Component::dump_config();
|
||||
LOG_I2C_DEVICE(this);
|
||||
}
|
||||
|
||||
} // namespace as3935_i2c
|
||||
} // namespace esphome
|
||||
22
esphome/components/as3935_i2c/as3935_i2c.h
Normal file
22
esphome/components/as3935_i2c/as3935_i2c.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/as3935/as3935.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935_i2c {
|
||||
|
||||
class I2CAS3935Component : public as3935::AS3935Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) override;
|
||||
uint8_t read_register(uint8_t reg) override;
|
||||
};
|
||||
|
||||
} // namespace as3935_i2c
|
||||
} // namespace esphome
|
||||
20
esphome/components/as3935_spi/__init__.py
Normal file
20
esphome/components/as3935_spi/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import as3935, spi
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['as3935']
|
||||
DEPENDENCIES = ['spi']
|
||||
|
||||
as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
|
||||
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(SPIAS3935),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield as3935.setup_as3935(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
48
esphome/components/as3935_spi/as3935_spi.cpp
Normal file
48
esphome/components/as3935_spi/as3935_spi.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "as3935_spi.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935_spi {
|
||||
|
||||
static const char *TAG = "as3935_spi";
|
||||
|
||||
void SPIAS3935Component::setup() {
|
||||
ESP_LOGI(TAG, "SPIAS3935Component setup started!");
|
||||
this->spi_setup();
|
||||
ESP_LOGI(TAG, "SPI setup finished!");
|
||||
AS3935Component::setup();
|
||||
}
|
||||
|
||||
void SPIAS3935Component::dump_config() {
|
||||
AS3935Component::dump_config();
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
}
|
||||
|
||||
void SPIAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) {
|
||||
uint8_t write_reg = this->read_register(reg);
|
||||
|
||||
write_reg &= (~mask);
|
||||
write_reg |= (bits << start_pos);
|
||||
|
||||
this->enable();
|
||||
this->write_byte(reg);
|
||||
this->write_byte(write_reg);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
uint8_t SPIAS3935Component::read_register(uint8_t reg) {
|
||||
uint8_t value = 0;
|
||||
this->enable();
|
||||
this->write_byte(reg |= SPI_READ_M);
|
||||
value = this->read_byte();
|
||||
// According to datsheet, the chip select must be written HIGH, LOW, HIGH
|
||||
// to correctly end the READ command.
|
||||
this->cs_->digital_write(true);
|
||||
this->cs_->digital_write(false);
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "read_register_: %d", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace as3935_spi
|
||||
} // namespace esphome
|
||||
27
esphome/components/as3935_spi/as3935_spi.h
Normal file
27
esphome/components/as3935_spi/as3935_spi.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/as3935/as3935.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935_spi {
|
||||
|
||||
enum AS3935RegisterMasks { SPI_READ_M = 0x40 };
|
||||
|
||||
class SPIAS3935Component : public as3935::AS3935Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) override;
|
||||
uint8_t read_register(uint8_t reg) override;
|
||||
};
|
||||
|
||||
} // namespace as3935_spi
|
||||
} // namespace esphome
|
||||
15
esphome/components/async_tcp/__init__.py
Normal file
15
esphome/components/async_tcp/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Dummy integration to allow relying on AsyncTCP
|
||||
import esphome.codegen as cg
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
|
||||
|
||||
@coroutine_with_priority(200.0)
|
||||
def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
# https://github.com/OttoWinter/AsyncTCP/blob/master/library.json
|
||||
cg.add_library('AsyncTCP-esphome', '1.1.1')
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||
cg.add_library('ESPAsyncTCP-esphome', '1.2.3')
|
||||
0
esphome/components/atc_mithermometer/__init__.py
Normal file
0
esphome/components/atc_mithermometer/__init__.py
Normal file
137
esphome/components/atc_mithermometer/atc_mithermometer.cpp
Normal file
137
esphome/components/atc_mithermometer/atc_mithermometer.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "atc_mithermometer.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace atc_mithermometer {
|
||||
|
||||
static const char *TAG = "atc_mithermometer";
|
||||
|
||||
void ATCMiThermometer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ATC MiThermometer");
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_);
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != this->address_) {
|
||||
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||
|
||||
bool success = false;
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
auto res = parse_header(service_data);
|
||||
if (res->is_duplicate) {
|
||||
continue;
|
||||
}
|
||||
if (!(parse_message(service_data.data, *res))) {
|
||||
continue;
|
||||
}
|
||||
if (!(report_results(res, device.address_str()))) {
|
||||
continue;
|
||||
}
|
||||
if (res->temperature.has_value() && this->temperature_ != nullptr)
|
||||
this->temperature_->publish_state(*res->temperature);
|
||||
if (res->humidity.has_value() && this->humidity_ != nullptr)
|
||||
this->humidity_->publish_state(*res->humidity);
|
||||
if (res->battery_level.has_value() && this->battery_level_ != nullptr)
|
||||
this->battery_level_->publish_state(*res->battery_level);
|
||||
if (res->battery_voltage.has_value() && this->battery_voltage_ != nullptr)
|
||||
this->battery_voltage_->publish_state(*res->battery_voltage);
|
||||
success = true;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) {
|
||||
ParseResult result;
|
||||
if (!service_data.uuid.contains(0x1A, 0x18)) {
|
||||
ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto raw = service_data.data;
|
||||
|
||||
static uint8_t last_frame_count = 0;
|
||||
if (last_frame_count == raw[12]) {
|
||||
ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count));
|
||||
result.is_duplicate = true;
|
||||
return {};
|
||||
}
|
||||
last_frame_count = raw[12];
|
||||
result.is_duplicate = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseResult &result) {
|
||||
// Byte 0-5 mac in correct order
|
||||
// Byte 6-7 Temperature in uint16
|
||||
// Byte 8 Humidity in percent
|
||||
// Byte 9 Battery in percent
|
||||
// Byte 10-11 Battery in mV uint16_t
|
||||
// Byte 12 frame packet counter
|
||||
|
||||
const uint8_t *data = message.data();
|
||||
const int data_length = 13;
|
||||
|
||||
if (message.size() != data_length) {
|
||||
ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
|
||||
const int16_t temperature = uint16_t(data[7]) | (uint16_t(data[6]) << 8);
|
||||
result.temperature = temperature / 10.0f;
|
||||
|
||||
// humidity, 1 byte, 8-bit unsigned integer, 1.0 %
|
||||
result.humidity = data[8];
|
||||
|
||||
// battery, 1 byte, 8-bit unsigned integer, 1.0 %
|
||||
result.battery_level = data[9];
|
||||
|
||||
// battery, 2 bytes, 16-bit unsigned integer, 0.001 V
|
||||
const int16_t battery_voltage = uint16_t(data[11]) | (uint16_t(data[10]) << 8);
|
||||
result.battery_voltage = battery_voltage / 1.0e3f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ATCMiThermometer::report_results(const optional<ParseResult> &result, const std::string &address) {
|
||||
if (!result.has_value()) {
|
||||
ESP_LOGVV(TAG, "report_results(): no results available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str());
|
||||
|
||||
if (result->temperature.has_value()) {
|
||||
ESP_LOGD(TAG, " Temperature: %.1f °C", *result->temperature);
|
||||
}
|
||||
if (result->humidity.has_value()) {
|
||||
ESP_LOGD(TAG, " Humidity: %.0f %%", *result->humidity);
|
||||
}
|
||||
if (result->battery_level.has_value()) {
|
||||
ESP_LOGD(TAG, " Battery Level: %.0f %%", *result->battery_level);
|
||||
}
|
||||
if (result->battery_voltage.has_value()) {
|
||||
ESP_LOGD(TAG, " Battery Voltage: %.3f V", *result->battery_voltage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace atc_mithermometer
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
48
esphome/components/atc_mithermometer/atc_mithermometer.h
Normal file
48
esphome/components/atc_mithermometer/atc_mithermometer.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace atc_mithermometer {
|
||||
|
||||
struct ParseResult {
|
||||
optional<float> temperature;
|
||||
optional<float> humidity;
|
||||
optional<float> battery_level;
|
||||
optional<float> battery_voltage;
|
||||
bool is_duplicate;
|
||||
int raw_offset;
|
||||
};
|
||||
|
||||
class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
void set_address(uint64_t address) { address_ = address; };
|
||||
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
|
||||
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
|
||||
|
||||
protected:
|
||||
uint64_t address_;
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
sensor::Sensor *battery_level_{nullptr};
|
||||
sensor::Sensor *battery_voltage_{nullptr};
|
||||
|
||||
optional<ParseResult> parse_header(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool parse_message(const std::vector<uint8_t> &message, ParseResult &result);
|
||||
bool report_results(const optional<ParseResult> &result, const std::string &address);
|
||||
};
|
||||
|
||||
} // namespace atc_mithermometer
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
45
esphome/components/atc_mithermometer/sensor.py
Normal file
45
esphome/components/atc_mithermometer/sensor.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_BATTERY_LEVEL, CONF_BATTERY_VOLTAGE, CONF_MAC_ADDRESS, \
|
||||
CONF_HUMIDITY, CONF_TEMPERATURE, CONF_ID, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, \
|
||||
ICON_BATTERY, ICON_THERMOMETER, ICON_WATER_PERCENT
|
||||
|
||||
CODEOWNERS = ['@ahpohl']
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
atc_mithermometer_ns = cg.esphome_ns.namespace('atc_mithermometer')
|
||||
ATCMiThermometer = atc_mithermometer_ns.class_('ATCMiThermometer',
|
||||
esp32_ble_tracker.ESPBTDeviceListener,
|
||||
cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ATCMiThermometer),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_BATTERY, 3),
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery_level(sens))
|
||||
if CONF_BATTERY_VOLTAGE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
|
||||
cg.add(var.set_battery_voltage(sens))
|
||||
0
esphome/components/atm90e32/__init__.py
Normal file
0
esphome/components/atm90e32/__init__.py
Normal file
251
esphome/components/atm90e32/atm90e32.cpp
Normal file
251
esphome/components/atm90e32/atm90e32.cpp
Normal file
@@ -0,0 +1,251 @@
|
||||
#include "atm90e32.h"
|
||||
#include "atm90e32_reg.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace atm90e32 {
|
||||
|
||||
static const char *TAG = "atm90e32";
|
||||
|
||||
void ATM90E32Component::update() {
|
||||
if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->phase_[0].voltage_sensor_ != nullptr) {
|
||||
this->phase_[0].voltage_sensor_->publish_state(this->get_line_voltage_a_());
|
||||
}
|
||||
if (this->phase_[1].voltage_sensor_ != nullptr) {
|
||||
this->phase_[1].voltage_sensor_->publish_state(this->get_line_voltage_b_());
|
||||
}
|
||||
if (this->phase_[2].voltage_sensor_ != nullptr) {
|
||||
this->phase_[2].voltage_sensor_->publish_state(this->get_line_voltage_c_());
|
||||
}
|
||||
if (this->phase_[0].current_sensor_ != nullptr) {
|
||||
this->phase_[0].current_sensor_->publish_state(this->get_line_current_a_());
|
||||
}
|
||||
if (this->phase_[1].current_sensor_ != nullptr) {
|
||||
this->phase_[1].current_sensor_->publish_state(this->get_line_current_b_());
|
||||
}
|
||||
if (this->phase_[2].current_sensor_ != nullptr) {
|
||||
this->phase_[2].current_sensor_->publish_state(this->get_line_current_c_());
|
||||
}
|
||||
if (this->phase_[0].power_sensor_ != nullptr) {
|
||||
this->phase_[0].power_sensor_->publish_state(this->get_active_power_a_());
|
||||
}
|
||||
if (this->phase_[1].power_sensor_ != nullptr) {
|
||||
this->phase_[1].power_sensor_->publish_state(this->get_active_power_b_());
|
||||
}
|
||||
if (this->phase_[2].power_sensor_ != nullptr) {
|
||||
this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_());
|
||||
}
|
||||
if (this->phase_[0].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[0].reactive_power_sensor_->publish_state(this->get_reactive_power_a_());
|
||||
}
|
||||
if (this->phase_[1].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[1].reactive_power_sensor_->publish_state(this->get_reactive_power_b_());
|
||||
}
|
||||
if (this->phase_[2].reactive_power_sensor_ != nullptr) {
|
||||
this->phase_[2].reactive_power_sensor_->publish_state(this->get_reactive_power_c_());
|
||||
}
|
||||
if (this->phase_[0].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[0].power_factor_sensor_->publish_state(this->get_power_factor_a_());
|
||||
}
|
||||
if (this->phase_[1].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[1].power_factor_sensor_->publish_state(this->get_power_factor_b_());
|
||||
}
|
||||
if (this->phase_[2].power_factor_sensor_ != nullptr) {
|
||||
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
|
||||
}
|
||||
if (this->freq_sensor_ != nullptr) {
|
||||
this->freq_sensor_->publish_state(this->get_frequency_());
|
||||
}
|
||||
if (this->chip_temperature_sensor_ != nullptr) {
|
||||
this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_());
|
||||
}
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void ATM90E32Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
|
||||
this->spi_setup();
|
||||
|
||||
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||
if (line_freq_ == 60) {
|
||||
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
|
||||
}
|
||||
|
||||
if (current_phases_ == 2) {
|
||||
mmode0 |= 1 << 8; // sets 8th bit to 1, 3P3W
|
||||
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
||||
}
|
||||
|
||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||
this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering
|
||||
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x0001) {
|
||||
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000
|
||||
this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default)
|
||||
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // ZX2, ZX1, ZX0 pin config
|
||||
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
|
||||
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
||||
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
|
||||
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
|
||||
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
|
||||
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain
|
||||
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[1].ct_gain_); // B line current gain
|
||||
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[2].volt_gain_); // C Voltage rms gain
|
||||
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[2].ct_gain_); // C line current gain
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||
}
|
||||
|
||||
void ATM90E32Component::dump_config() {
|
||||
ESP_LOGCONFIG("", "ATM90E32:");
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with ATM90E32 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||
}
|
||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
uint8_t data[2];
|
||||
uint16_t output;
|
||||
|
||||
this->enable();
|
||||
delayMicroseconds(10);
|
||||
this->write_byte(addrh);
|
||||
this->write_byte(addrl);
|
||||
delayMicroseconds(4);
|
||||
this->read_array(data, 2);
|
||||
this->disable();
|
||||
|
||||
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
||||
ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||
uint16_t val_h = this->read16_(addr_h);
|
||||
uint16_t val_l = this->read16_(addr_l);
|
||||
int32_t val = (val_h << 16) | val_l;
|
||||
|
||||
ESP_LOGVV(TAG, "read32_ addr_h 0x%04X val_h 0x%04X addr_l 0x%04X val_l 0x%04X = %d", addr_h, val_h, addr_l, val_l,
|
||||
val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
|
||||
uint8_t addrh = (a_register >> 8) & 0x03;
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
|
||||
ESP_LOGVV(TAG, "write16_ 0x%04X val 0x%04X", a_register, val);
|
||||
this->enable();
|
||||
delayMicroseconds(10);
|
||||
this->write_byte(addrh);
|
||||
this->write_byte(addrl);
|
||||
delayMicroseconds(4);
|
||||
this->write_byte((val >> 8) & 0xff);
|
||||
this->write_byte(val & 0xFF);
|
||||
this->disable();
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_line_voltage_a_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSA);
|
||||
return (float) voltage / 100;
|
||||
}
|
||||
float ATM90E32Component::get_line_voltage_b_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSB);
|
||||
return (float) voltage / 100;
|
||||
}
|
||||
float ATM90E32Component::get_line_voltage_c_() {
|
||||
uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMSC);
|
||||
return (float) voltage / 100;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_a_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSA);
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_b_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSB);
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_line_current_c_() {
|
||||
uint16_t current = this->read16_(ATM90E32_REGISTER_IRMSC);
|
||||
return (float) current / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_a_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANA, ATM90E32_REGISTER_PMEANALSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_b_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANB, ATM90E32_REGISTER_PMEANBLSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_active_power_c_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_a_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANA, ATM90E32_REGISTER_QMEANALSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_b_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANB, ATM90E32_REGISTER_QMEANBLSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_reactive_power_c_() {
|
||||
int val = this->read32_(ATM90E32_REGISTER_QMEANC, ATM90E32_REGISTER_QMEANCLSB);
|
||||
return val * 0.00032f;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_a_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANA);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_b_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANB);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_power_factor_c_() {
|
||||
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
|
||||
return (float) pf / 1000;
|
||||
}
|
||||
float ATM90E32Component::get_frequency_() {
|
||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||
return (float) freq / 100;
|
||||
}
|
||||
float ATM90E32Component::get_chip_temperature_() {
|
||||
uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP);
|
||||
return (float) ctemp;
|
||||
}
|
||||
} // namespace atm90e32
|
||||
} // namespace esphome
|
||||
75
esphome/components/atm90e32/atm90e32.h
Normal file
75
esphome/components/atm90e32/atm90e32.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace atm90e32 {
|
||||
|
||||
class ATM90E32Component : public PollingComponent,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
|
||||
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
||||
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
||||
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
|
||||
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||
|
||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||
void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
|
||||
chip_temperature_sensor_ = chip_temperature_sensor;
|
||||
}
|
||||
void set_line_freq(int freq) { line_freq_ = freq; }
|
||||
void set_current_phases(int phases) { current_phases_ = phases; }
|
||||
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
||||
|
||||
protected:
|
||||
uint16_t read16_(uint16_t a_register);
|
||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||
void write16_(uint16_t a_register, uint16_t val);
|
||||
|
||||
float get_line_voltage_a_();
|
||||
float get_line_voltage_b_();
|
||||
float get_line_voltage_c_();
|
||||
float get_line_current_a_();
|
||||
float get_line_current_b_();
|
||||
float get_line_current_c_();
|
||||
float get_active_power_a_();
|
||||
float get_active_power_b_();
|
||||
float get_active_power_c_();
|
||||
float get_reactive_power_a_();
|
||||
float get_reactive_power_b_();
|
||||
float get_reactive_power_c_();
|
||||
float get_power_factor_a_();
|
||||
float get_power_factor_b_();
|
||||
float get_power_factor_c_();
|
||||
float get_frequency_();
|
||||
float get_chip_temperature_();
|
||||
|
||||
struct ATM90E32Phase {
|
||||
uint16_t volt_gain_{7305};
|
||||
uint16_t ct_gain_{27961};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
} phase_[3];
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||
uint16_t pga_gain_{0x15};
|
||||
int line_freq_{60};
|
||||
int current_phases_{3};
|
||||
};
|
||||
|
||||
} // namespace atm90e32
|
||||
} // namespace esphome
|
||||
253
esphome/components/atm90e32/atm90e32_reg.h
Normal file
253
esphome/components/atm90e32/atm90e32_reg.h
Normal file
@@ -0,0 +1,253 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace atm90e32 {
|
||||
|
||||
/* STATUS REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_METEREN = 0x00; // Metering Enable
|
||||
static const uint16_t ATM90E32_REGISTER_CHANNELMAPI = 0x01; // Current Channel Mapping Configuration
|
||||
static const uint16_t ATM90E32_REGISTER_CHANNELMAPU = 0x02; // Voltage Channel Mapping Configuration
|
||||
static const uint16_t ATM90E32_REGISTER_SAGPEAKDETCFG = 0x05; // Sag and Peak Detector Period Configuration
|
||||
static const uint16_t ATM90E32_REGISTER_OVTH = 0x06; // Over Voltage Threshold
|
||||
static const uint16_t ATM90E32_REGISTER_ZXCONFIG = 0x07; // Zero-Crossing Config
|
||||
static const uint16_t ATM90E32_REGISTER_SAGTH = 0x08; // Voltage Sag Th
|
||||
static const uint16_t ATM90E32_REGISTER_PHASELOSSTH = 0x09; // Voltage Phase Losing Th
|
||||
static const uint16_t ATM90E32_REGISTER_INWARNTH = 0x0A; // Neutral Current (Calculated) Warning Threshold
|
||||
static const uint16_t ATM90E32_REGISTER_OITH = 0x0B; // Over Current Threshold
|
||||
static const uint16_t ATM90E32_REGISTER_FREQLOTH = 0x0C; // Low Threshold for Frequency Detection
|
||||
static const uint16_t ATM90E32_REGISTER_FREQHITH = 0x0D; // High Threshold for Frequency Detection
|
||||
static const uint16_t ATM90E32_REGISTER_PMPWRCTRL = 0x0E; // Partial Measurement Mode Power Control
|
||||
static const uint16_t ATM90E32_REGISTER_IRQ0MERGECFG = 0x0F; // IRQ0 Merge Configuration
|
||||
|
||||
/* EMM STATUS REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_SOFTRESET = 0x70; // Software Reset
|
||||
static const uint16_t ATM90E32_REGISTER_EMMSTATE0 = 0x71; // EMM State 0
|
||||
static const uint16_t ATM90E32_REGISTER_EMMSTATE1 = 0x72; // EMM State 1
|
||||
static const uint16_t ATM90E32_REGISTER_EMMINTSTATE0 = 0x73; // EMM Interrupt Status 0
|
||||
static const uint16_t ATM90E32_REGISTER_EMMINTSTATE1 = 0x74; // EMM Interrupt Status 1
|
||||
static const uint16_t ATM90E32_REGISTER_EMMINTEN0 = 0x75; // EMM Interrupt Enable 0
|
||||
static const uint16_t ATM90E32_REGISTER_EMMINTEN1 = 0x76; // EMM Interrupt Enable 1
|
||||
static const uint16_t ATM90E32_REGISTER_LASTSPIDATA = 0x78; // Last Read/Write SPI Value
|
||||
static const uint16_t ATM90E32_REGISTER_CRCERRSTATUS = 0x79; // CRC Error Status
|
||||
static const uint16_t ATM90E32_REGISTER_CRCDIGEST = 0x7A; // CRC Digest
|
||||
static const uint16_t ATM90E32_REGISTER_CFGREGACCEN = 0x7F; // Configure Register Access Enable
|
||||
static const uint16_t ATM90E32_STATUS_S0_OIPHASEAST = 1 << 15; // Over current on phase A
|
||||
static const uint16_t ATM90E32_STATUS_S0_OIPHASEBST = 1 << 14; // Over current on phase B
|
||||
static const uint16_t ATM90E32_STATUS_S0_OIPHASECST = 1 << 13; // Over current on phase C
|
||||
static const uint16_t ATM90E32_STATUS_S0_OVPHASEAST = 1 << 12; // Over voltage on phase A
|
||||
static const uint16_t ATM90E32_STATUS_S0_OVPHASEBST = 1 << 11; // Over voltage on phase B
|
||||
static const uint16_t ATM90E32_STATUS_S0_OVPHASECST = 1 << 10; // Over voltage on phase C
|
||||
static const uint16_t ATM90E32_STATUS_S0_UREVWNST = 1 << 9; // Voltage Phase Sequence Error status
|
||||
static const uint16_t ATM90E32_STATUS_S0_IREVWNST = 1 << 8; // Current Phase Sequence Error status
|
||||
static const uint16_t ATM90E32_STATUS_S0_INOV0ST = 1 << 7; // Calculated N line current greater tha INWarnTh reg
|
||||
static const uint16_t ATM90E32_STATUS_S0_TQNOLOADST = 1 << 6; // All phase sum reactive power no-load condition status
|
||||
static const uint16_t ATM90E32_STATUS_S0_TPNOLOADST = 1 << 5; // All phase sum active power no-load condition status
|
||||
static const uint16_t ATM90E32_STATUS_S0_TASNOLOADST = 1 << 4; // All phase sum apparent power no-load status
|
||||
static const uint16_t ATM90E32_STATUS_S0_CF1REVST = 1 << 3; // Energy for CF1 Forward/Reverse status
|
||||
static const uint16_t ATM90E32_STATUS_S0_CF2REVST = 1 << 2; // Energy for CF2 Forward/Reverse status
|
||||
static const uint16_t ATM90E32_STATUS_S0_CF3REVST = 1 << 1; // Energy for CF3 Forward/Reverse status
|
||||
static const uint16_t ATM90E32_STATUS_S0_CF4REVST = 1 << 0; // Energy for CF4 Forward/Reverse status
|
||||
static const uint16_t ATM90E32_STATUS_S1_FREQHIST = 1 << 15; // Frequency is greater than the high threshold
|
||||
static const uint16_t ATM90E32_STATUS_S1_SAGPHASEAST = 1 << 14; // Voltage sag on phase A
|
||||
static const uint16_t ATM90E32_STATUS_S1_SAGPHASEBST = 1 << 13; // Voltage sag on phase B
|
||||
static const uint16_t ATM90E32_STATUS_S1_SAGPHASECST = 1 << 12; // Voltage sag on phase C
|
||||
static const uint16_t ATM90E32_STATUS_S1_FREQLOST = 1 << 11; // Frequency is lesser than the low threshold
|
||||
static const uint16_t ATM90E32_STATUS_S1_PHASELOSSAST = 1 << 10; // Phase loss in Phase A
|
||||
static const uint16_t ATM90E32_STATUS_S1_PHASELOSSBST = 1 << 9; // Phase loss in Phase B
|
||||
static const uint16_t ATM90E32_STATUS_S1_PHASELOSSCST = 1 << 8; // Phase loss in Phase C
|
||||
static const uint16_t ATM90E32_STATUS_S1_QEREGTPST = 1 << 7; // ReActive Energy register of sum (T) Positive Status
|
||||
static const uint16_t ATM90E32_STATUS_S1_QEREGAPST = 1 << 6; // ReActive Energy register of Channel A Positive Status
|
||||
static const uint16_t ATM90E32_STATUS_S1_QEREGBPST = 1 << 5; // ReActive Energy register of Channel B Positive Status
|
||||
static const uint16_t ATM90E32_STATUS_S1_QEREGCPST = 1 << 4; // ReActive Energy register of Channel C Positive Status
|
||||
static const uint16_t ATM90E32_STATUS_S1_PEREGTPST = 1 << 3; // Active Energy register of sum (T) Positive Status
|
||||
static const uint16_t ATM90E32_STATUS_S1_PEREGAPST = 1 << 2; // Active Energy register of Channel A Positive Status
|
||||
static const uint16_t ATM90E32_STATUS_S1_PEREGBPST = 1 << 1; // Active Energy register of Channel B Positive Status
|
||||
static const uint16_t ATM90E32_STATUS_S1_PEREGCPST = 1 << 0; // Active Energy register of Channel C Positive Status
|
||||
|
||||
/* LOW POWER MODE REGISTERS - NOT USED */
|
||||
static const uint16_t ATM90E32_REGISTER_DETECTCTRL = 0x10;
|
||||
static const uint16_t ATM90E32_REGISTER_DETECTTH1 = 0x11;
|
||||
static const uint16_t ATM90E32_REGISTER_DETECTTH2 = 0x12;
|
||||
static const uint16_t ATM90E32_REGISTER_DETECTTH3 = 0x13;
|
||||
static const uint16_t ATM90E32_REGISTER_PMOFFSETA = 0x14;
|
||||
static const uint16_t ATM90E32_REGISTER_PMOFFSETB = 0x15;
|
||||
static const uint16_t ATM90E32_REGISTER_PMOFFSETC = 0x16;
|
||||
static const uint16_t ATM90E32_REGISTER_PMPGA = 0x17;
|
||||
static const uint16_t ATM90E32_REGISTER_PMIRMSA = 0x18;
|
||||
static const uint16_t ATM90E32_REGISTER_PMIRMSB = 0x19;
|
||||
static const uint16_t ATM90E32_REGISTER_PMIRMSC = 0x1A;
|
||||
static const uint16_t ATM90E32_REGISTER_PMCONFIG = 0x10B;
|
||||
static const uint16_t ATM90E32_REGISTER_PMAVGSAMPLES = 0x1C;
|
||||
static const uint16_t ATM90E32_REGISTER_PMIRMSLSB = 0x1D;
|
||||
|
||||
/* CONFIGURATION REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_PLCONSTH = 0x31; // High Word of PL_Constant
|
||||
static const uint16_t ATM90E32_REGISTER_PLCONSTL = 0x32; // Low Word of PL_Constant
|
||||
static const uint16_t ATM90E32_REGISTER_MMODE0 = 0x33; // Metering Mode Config
|
||||
static const uint16_t ATM90E32_REGISTER_MMODE1 = 0x34; // PGA Gain Configuration for Current Channels
|
||||
static const uint16_t ATM90E32_REGISTER_PSTARTTH = 0x35; // Startup Power Th (P)
|
||||
static const uint16_t ATM90E32_REGISTER_QSTARTTH = 0x36; // Startup Power Th (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_SSTARTTH = 0x37; // Startup Power Th (S)
|
||||
static const uint16_t ATM90E32_REGISTER_PPHASETH = 0x38; // Startup Power Accum Th (P)
|
||||
static const uint16_t ATM90E32_REGISTER_QPHASETH = 0x39; // Startup Power Accum Th (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_SPHASETH = 0x3A; // Startup Power Accum Th (S)
|
||||
|
||||
/* CALIBRATION REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_POFFSETA = 0x41; // A Line Power Offset (P)
|
||||
static const uint16_t ATM90E32_REGISTER_QOFFSETA = 0x42; // A Line Power Offset (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_POFFSETB = 0x43; // B Line Power Offset (P)
|
||||
static const uint16_t ATM90E32_REGISTER_QOFFSETB = 0x44; // B Line Power Offset (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_POFFSETC = 0x45; // C Line Power Offset (P)
|
||||
static const uint16_t ATM90E32_REGISTER_QOFFSETC = 0x46; // C Line Power Offset (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_PQGAINA = 0x47; // A Line Calibration Gain
|
||||
static const uint16_t ATM90E32_REGISTER_PHIA = 0x48; // A Line Calibration Angle
|
||||
static const uint16_t ATM90E32_REGISTER_PQGAINB = 0x49; // B Line Calibration Gain
|
||||
static const uint16_t ATM90E32_REGISTER_PHIB = 0x4A; // B Line Calibration Angle
|
||||
static const uint16_t ATM90E32_REGISTER_PQGAINC = 0x4B; // C Line Calibration Gain
|
||||
static const uint16_t ATM90E32_REGISTER_PHIC = 0x4C; // C Line Calibration Angle
|
||||
|
||||
/* FUNDAMENTAL/HARMONIC ENERGY CALIBRATION REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_POFFSETAF = 0x51; // A Fund Power Offset (P)
|
||||
static const uint16_t ATM90E32_REGISTER_POFFSETBF = 0x52; // B Fund Power Offset (P)
|
||||
static const uint16_t ATM90E32_REGISTER_POFFSETCF = 0x53; // C Fund Power Offset (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PGAINAF = 0x54; // A Fund Power Gain (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PGAINBF = 0x55; // B Fund Power Gain (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PGAINCF = 0x56; // C Fund Power Gain (P)
|
||||
|
||||
/* MEASUREMENT CALIBRATION REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_UGAINA = 0x61; // A Voltage RMS Gain
|
||||
static const uint16_t ATM90E32_REGISTER_IGAINA = 0x62; // A Current RMS Gain
|
||||
static const uint16_t ATM90E32_REGISTER_UOFFSETA = 0x63; // A Voltage Offset
|
||||
static const uint16_t ATM90E32_REGISTER_IOFFSETA = 0x64; // A Current Offset
|
||||
static const uint16_t ATM90E32_REGISTER_UGAINB = 0x65; // B Voltage RMS Gain
|
||||
static const uint16_t ATM90E32_REGISTER_IGAINB = 0x66; // B Current RMS Gain
|
||||
static const uint16_t ATM90E32_REGISTER_UOFFSETB = 0x67; // B Voltage Offset
|
||||
static const uint16_t ATM90E32_REGISTER_IOFFSETB = 0x68; // B Current Offset
|
||||
static const uint16_t ATM90E32_REGISTER_UGAINC = 0x69; // C Voltage RMS Gain
|
||||
static const uint16_t ATM90E32_REGISTER_IGAINC = 0x6A; // C Current RMS Gain
|
||||
static const uint16_t ATM90E32_REGISTER_UOFFSETC = 0x6B; // C Voltage Offset
|
||||
static const uint16_t ATM90E32_REGISTER_IOFFSETC = 0x6C; // C Current Offset
|
||||
static const uint16_t ATM90E32_REGISTER_IOFFSETN = 0x6E; // N Current Offset
|
||||
|
||||
/* ENERGY REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYT = 0x80; // Total Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYA = 0x81; // A Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYB = 0x82; // B Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYC = 0x83; // C Forward Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYT = 0x84; // Total Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYA = 0x85; // A Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYB = 0x86; // B Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYC = 0x87; // C Reverse Active
|
||||
static const uint16_t ATM90E32_REGISTER_RPENERGYT = 0x88; // Total Forward Reactive
|
||||
static const uint16_t ATM90E32_REGISTER_RPENERGYA = 0x89; // A Forward Reactive
|
||||
static const uint16_t ATM90E32_REGISTER_RPENERGYB = 0x8A; // B Forward Reactive
|
||||
static const uint16_t ATM90E32_REGISTER_RPENERGYC = 0x8B; // C Forward Reactive
|
||||
static const uint16_t ATM90E32_REGISTER_RNENERGYT = 0x8C; // Total Reverse Reactive
|
||||
static const uint16_t ATM90E32_REGISTER_RNENERGYA = 0x8D; // A Reverse Reactive
|
||||
static const uint16_t ATM90E32_REGISTER_RNENERGYB = 0x8E; // B Reverse Reactive
|
||||
static const uint16_t ATM90E32_REGISTER_RNENERGYC = 0x8F; // C Reverse Reactive
|
||||
|
||||
static const uint16_t ATM90E32_REGISTER_SAENERGYT = 0x90; // Total Apparent Energy
|
||||
static const uint16_t ATM90E32_REGISTER_SENERGYA = 0x91; // A Apparent Energy
|
||||
static const uint16_t ATM90E32_REGISTER_SENERGYB = 0x92; // B Apparent Energy
|
||||
static const uint16_t ATM90E32_REGISTER_SENERGYC = 0x93; // C Apparent Energy
|
||||
|
||||
/* FUNDAMENTAL / HARMONIC ENERGY REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYTF = 0xA0; // Total Forward Fund. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYAF = 0xA1; // A Forward Fund. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYBF = 0xA2; // B Forward Fund. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYCF = 0xA3; // C Forward Fund. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYTF = 0xA4; // Total Reverse Fund Energy
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYAF = 0xA5; // A Reverse Fund. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYBF = 0xA6; // B Reverse Fund. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYCF = 0xA7; // C Reverse Fund. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYTH = 0xA8; // Total Forward Harm. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYAH = 0xA9; // A Forward Harm. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYBH = 0xAA; // B Forward Harm. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_APENERGYCH = 0xAB; // C Forward Harm. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYTH = 0xAC; // Total Reverse Harm. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYAH = 0xAD; // A Reverse Harm. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYBH = 0xAE; // B Reverse Harm. Energy
|
||||
static const uint16_t ATM90E32_REGISTER_ANENERGYCH = 0xAF; // C Reverse Harm. Energy
|
||||
|
||||
/* POWER & P.F. REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANT = 0xB0; // Total Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANA = 0xB1; // A Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANB = 0xB2; // B Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANC = 0xB3; // C Mean Power (P)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANT = 0xB4; // Total Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANA = 0xB5; // A Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6; // B Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANC = 0xB7; // C Mean Power (Q)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANT = 0xB8; // Total Mean Power (S)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANA = 0xB9; // A Mean Power (S)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANB = 0xBA; // B Mean Power (S)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANC = 0xBB; // C Mean Power (S)
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANT = 0xBC; // Mean Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANA = 0xBD; // A Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANB = 0xBE; // B Power Factor
|
||||
static const uint16_t ATM90E32_REGISTER_PFMEANC = 0xBF; // C Power Factor
|
||||
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTLSB = 0xC0; // Lower Word (Tot. Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANALSB = 0xC1; // Lower Word (A Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBLSB = 0xC2; // Lower Word (B Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCLSB = 0xC3; // Lower Word (C Act. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANTLSB = 0xC4; // Lower Word (Tot. React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANALSB = 0xC5; // Lower Word (A React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANBLSB = 0xC6; // Lower Word (B React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_QMEANCLSB = 0xC7; // Lower Word (C React. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_SAMEANTLSB = 0xC8; // Lower Word (Tot. App. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANALSB = 0xC9; // Lower Word (A App. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANBLSB = 0xCA; // Lower Word (B App. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_SMEANCLSB = 0xCB; // Lower Word (C App. Power)
|
||||
|
||||
/* FUND/HARM POWER & V/I RMS REGISTERS */
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTF = 0xD0; // Total Active Fund. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANAF = 0xD1; // A Active Fund. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBF = 0xD2; // B Active Fund. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCF = 0xD3; // C Active Fund. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTH = 0xD4; // Total Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANAH = 0xD5; // A Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBH = 0xD6; // B Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCH = 0xD7; // C Active Harm. Power
|
||||
static const uint16_t ATM90E32_REGISTER_URMSA = 0xD9; // A RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_URMSB = 0xDA; // B RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_URMSC = 0xDB; // C RMS Voltage
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSA = 0xDD; // A RMS Current
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSB = 0xDE; // B RMS Current
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSC = 0xDF; // C RMS Current
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSN = 0xD8; // Calculated N RMS Current
|
||||
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTFLSB = 0xE0; // Lower Word (Tot. Act. Fund. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANAFLSB = 0xE1; // Lower Word (A Act. Fund. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBFLSB = 0xE2; // Lower Word (B Act. Fund. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCFLSB = 0xE3; // Lower Word (C Act. Fund. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANTHLSB = 0xE4; // Lower Word (Tot. Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANAHLSB = 0xE5; // Lower Word (A Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANBHLSB = 0xE6; // Lower Word (B Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_PMEANCHLSB = 0xE7; // Lower Word (C Act. Harm. Power)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSALSB = 0xE9; // Lower Word (A RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSBLSB = 0xEA; // Lower Word (B RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_URMSCLSB = 0xEB; // Lower Word (C RMS Voltage)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSALSB = 0xED; // Lower Word (A RMS Current)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSBLSB = 0xEE; // Lower Word (B RMS Current)
|
||||
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current)
|
||||
|
||||
/* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/
|
||||
static const uint16_t ATM90E32_REGISTER_UPEAKA = 0xF1; // A Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_UPEAKB = 0xF2; // B Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_UPEAKC = 0xF3; // C Voltage Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKA = 0xF5; // A Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKB = 0xF6; // B Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_IPEAKC = 0xF7; // C Current Peak
|
||||
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_PANGLEC = 0xFB; // C Mean Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_TEMP = 0xFC; // Measured Temperature
|
||||
static const uint16_t ATM90E32_REGISTER_UANGLEA = 0xFD; // A Voltage Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_UANGLEB = 0xFE; // B Voltage Phase Angle
|
||||
static const uint16_t ATM90E32_REGISTER_UANGLEC = 0xFF; // C Voltage Phase Angle
|
||||
|
||||
} // namespace atm90e32
|
||||
} // namespace esphome
|
||||
95
esphome/components/atm90e32/sensor.py
Normal file
95
esphome/components/atm90e32/sensor.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, spi
|
||||
from esphome.const import \
|
||||
CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \
|
||||
ICON_FLASH, ICON_LIGHTBULB, ICON_CURRENT_AC, ICON_THERMOMETER, \
|
||||
UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE
|
||||
|
||||
CONF_PHASE_A = 'phase_a'
|
||||
CONF_PHASE_B = 'phase_b'
|
||||
CONF_PHASE_C = 'phase_c'
|
||||
|
||||
CONF_REACTIVE_POWER = 'reactive_power'
|
||||
CONF_LINE_FREQUENCY = 'line_frequency'
|
||||
CONF_CHIP_TEMPERATURE = 'chip_temperature'
|
||||
CONF_GAIN_PGA = 'gain_pga'
|
||||
CONF_CURRENT_PHASES = 'current_phases'
|
||||
CONF_GAIN_VOLTAGE = 'gain_voltage'
|
||||
CONF_GAIN_CT = 'gain_ct'
|
||||
LINE_FREQS = {
|
||||
'50HZ': 50,
|
||||
'60HZ': 60,
|
||||
}
|
||||
CURRENT_PHASES = {
|
||||
'2': 2,
|
||||
'3': 3,
|
||||
}
|
||||
PGA_GAINS = {
|
||||
'1X': 0x0,
|
||||
'2X': 0x15,
|
||||
'4X': 0x2A,
|
||||
}
|
||||
|
||||
atm90e32_ns = cg.esphome_ns.namespace('atm90e32')
|
||||
ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice)
|
||||
|
||||
ATM90E32_PHASE_SCHEMA = cv.Schema({
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 2),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE,
|
||||
ICON_LIGHTBULB, 2),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2),
|
||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(ATM90E32Component),
|
||||
cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1),
|
||||
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
||||
cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True),
|
||||
cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True),
|
||||
}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema())
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield spi.register_spi_device(var, config)
|
||||
|
||||
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||
if phase not in config:
|
||||
continue
|
||||
conf = config[phase]
|
||||
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
|
||||
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
|
||||
if CONF_VOLTAGE in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_VOLTAGE])
|
||||
cg.add(var.set_voltage_sensor(i, sens))
|
||||
if CONF_CURRENT in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_CURRENT])
|
||||
cg.add(var.set_current_sensor(i, sens))
|
||||
if CONF_POWER in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_POWER])
|
||||
cg.add(var.set_power_sensor(i, sens))
|
||||
if CONF_REACTIVE_POWER in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_REACTIVE_POWER])
|
||||
cg.add(var.set_reactive_power_sensor(i, sens))
|
||||
if CONF_POWER_FACTOR in conf:
|
||||
sens = yield sensor.new_sensor(conf[CONF_POWER_FACTOR])
|
||||
cg.add(var.set_power_factor_sensor(i, sens))
|
||||
if CONF_FREQUENCY in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
cg.add(var.set_freq_sensor(sens))
|
||||
if CONF_CHIP_TEMPERATURE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
|
||||
cg.add(var.set_chip_temperature_sensor(sens))
|
||||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
|
||||
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
||||
1
esphome/components/bang_bang/__init__.py
Normal file
1
esphome/components/bang_bang/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
175
esphome/components/bang_bang/bang_bang_climate.cpp
Normal file
175
esphome/components/bang_bang/bang_bang_climate.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#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_);
|
||||
traits.set_supports_action(true);
|
||||
return traits;
|
||||
}
|
||||
void BangBangClimate::compute_state_() {
|
||||
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
||||
// in non-auto mode, switch directly to appropriate action
|
||||
// - HEAT mode -> HEATING action
|
||||
// - COOL mode -> COOLING action
|
||||
// - OFF mode -> OFF action (not IDLE!)
|
||||
this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode));
|
||||
return;
|
||||
}
|
||||
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
|
||||
// if any control parameters are nan, go to OFF action (not IDLE!)
|
||||
this->switch_to_action_(climate::CLIMATE_ACTION_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::ClimateAction target_action;
|
||||
if (too_cold) {
|
||||
// too cold -> enable heating if possible, else idle
|
||||
if (this->supports_heat_)
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
else
|
||||
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||
} else if (too_hot) {
|
||||
// too hot -> enable cooling if possible, else idle
|
||||
if (this->supports_cool_)
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
else
|
||||
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||
} else {
|
||||
// neither too hot nor too cold -> in range
|
||||
if (this->supports_cool_ && this->supports_heat_) {
|
||||
// if supports both ends, go to idle action
|
||||
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||
} else {
|
||||
// else use current mode and don't change (hysteresis)
|
||||
target_action = this->action;
|
||||
}
|
||||
}
|
||||
|
||||
this->switch_to_action_(target_action);
|
||||
}
|
||||
void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||
if (action == this->action)
|
||||
// already in target mode
|
||||
return;
|
||||
|
||||
if ((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) ||
|
||||
(action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) {
|
||||
// switching from OFF to IDLE or vice-versa
|
||||
// these only have visual difference. OFF means user manually disabled,
|
||||
// IDLE means it's in auto mode but value is in target range.
|
||||
this->action = action;
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
this->prev_trigger_ = nullptr;
|
||||
}
|
||||
Trigger<> *trig;
|
||||
switch (action) {
|
||||
case climate::CLIMATE_ACTION_OFF:
|
||||
case climate::CLIMATE_ACTION_IDLE:
|
||||
trig = this->idle_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_ACTION_COOLING:
|
||||
trig = this->cool_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_ACTION_HEATING:
|
||||
trig = this->heat_trigger_;
|
||||
break;
|
||||
default:
|
||||
trig = nullptr;
|
||||
}
|
||||
assert(trig != nullptr);
|
||||
trig->trigger();
|
||||
this->action = action;
|
||||
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; }
|
||||
void BangBangClimate::dump_config() {
|
||||
LOG_CLIMATE("", "Bang Bang Climate", this);
|
||||
ESP_LOGCONFIG(TAG, " Supports HEAT: %s", YESNO(this->supports_heat_));
|
||||
ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_));
|
||||
ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_));
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature_low);
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high);
|
||||
}
|
||||
|
||||
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
|
||||
85
esphome/components/bang_bang/bang_bang_climate.h
Normal file
85
esphome/components/bang_bang/bang_bang_climate.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#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 dump_config() 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_action_(climate::ClimateAction action);
|
||||
|
||||
/// 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};
|
||||
|
||||
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
89
esphome/components/bh1750/bh1750.cpp
Normal file
89
esphome/components/bh1750/bh1750.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#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;
|
||||
static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits
|
||||
static const uint8_t BH1750_COMMAND_MT_REG_LO = 0b01100000; // last 5 bits
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint8_t mtreg_hi = (this->measurement_time_ >> 5) & 0b111;
|
||||
uint8_t mtreg_lo = (this->measurement_time_ >> 0) & 0b11111;
|
||||
this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0);
|
||||
this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0);
|
||||
}
|
||||
|
||||
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;
|
||||
lx *= 69.0f / this->measurement_time_;
|
||||
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
|
||||
48
esphome/components/bh1750/bh1750.h
Normal file
48
esphome/components/bh1750/bh1750.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#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);
|
||||
void set_measurement_time(uint8_t measurement_time) { measurement_time_ = measurement_time; }
|
||||
|
||||
// ========== 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};
|
||||
uint8_t measurement_time_;
|
||||
};
|
||||
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
33
esphome/components/bh1750/sensor.py
Normal file
33
esphome/components/bh1750/sensor.py
Normal file
@@ -0,0 +1,33 @@
|
||||
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)
|
||||
|
||||
CONF_MEASUREMENT_TIME = 'measurement_time'
|
||||
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),
|
||||
cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(min=31, max=254),
|
||||
}).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]))
|
||||
cg.add(var.set_measurement_time(config[CONF_MEASUREMENT_TIME]))
|
||||
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')
|
||||
33
esphome/components/binary/fan/__init__.py
Normal file
33
esphome/components/binary/fan/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import fan, output
|
||||
from esphome.const import CONF_DIRECTION_OUTPUT, 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_DIRECTION_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_oscillating(oscillation_output))
|
||||
|
||||
if CONF_DIRECTION_OUTPUT in config:
|
||||
direction_output = yield cg.get_variable(config[CONF_DIRECTION_OUTPUT])
|
||||
cg.add(var.set_direction(direction_output))
|
||||
61
esphome/components/binary/fan/binary_fan.cpp
Normal file
61
esphome/components/binary/fan/binary_fan.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#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");
|
||||
}
|
||||
if (this->fan_->get_traits().supports_direction()) {
|
||||
ESP_LOGCONFIG(TAG, " Direction: YES");
|
||||
}
|
||||
}
|
||||
void BinaryFan::setup() {
|
||||
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr);
|
||||
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));
|
||||
}
|
||||
|
||||
if (this->direction_ != nullptr) {
|
||||
bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE;
|
||||
if (enable) {
|
||||
this->direction_->turn_on();
|
||||
} else {
|
||||
this->direction_->turn_off();
|
||||
}
|
||||
ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable));
|
||||
}
|
||||
}
|
||||
float BinaryFan::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace binary
|
||||
} // namespace esphome
|
||||
30
esphome/components/binary/fan/binary_fan.h
Normal file
30
esphome/components/binary/fan/binary_fan.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#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; }
|
||||
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
|
||||
|
||||
protected:
|
||||
fan::FanState *fan_;
|
||||
output::BinaryOutput *output_;
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
output::BinaryOutput *direction_{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
|
||||
319
esphome/components/binary_sensor/__init__.py
Normal file
319
esphome/components/binary_sensor/__init__.py
Normal file
@@ -0,0 +1,319 @@
|
||||
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.util import Registry
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
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)
|
||||
BinarySensorInitiallyOff = binary_sensor_ns.class_('BinarySensorInitiallyOff', BinarySensor)
|
||||
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')
|
||||
DelayedOnOffFilter = binary_sensor_ns.class_('DelayedOnOffFilter', Filter, cg.Component)
|
||||
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_off', DelayedOnOffFilter,
|
||||
cv.positive_time_period_milliseconds)
|
||||
def delayed_on_off_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id, config)
|
||||
yield cg.register_component(var, {})
|
||||
yield var
|
||||
|
||||
|
||||
@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, str):
|
||||
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:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
|
||||
|
||||
if parts[1] != 'for':
|
||||
raise cv.Invalid("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("Third word after at must either be 'least' or 'most', got {}"
|
||||
"".format(parts[3]))
|
||||
try:
|
||||
length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {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(f"Multi Click Grammar Parsing minimum length failed: {err}")
|
||||
|
||||
try:
|
||||
max_length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {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(config[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
|
||||
151
esphome/components/binary_sensor/automation.h
Normal file
151
esphome/components/binary_sensor/automation.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#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
|
||||
75
esphome/components/binary_sensor/binary_sensor.cpp
Normal file
75
esphome/components/binary_sensor/binary_sensor.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#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) {
|
||||
if (is_initial) {
|
||||
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), ONOFF(state));
|
||||
}
|
||||
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
|
||||
95
esphome/components/binary_sensor/binary_sensor.h
Normal file
95
esphome/components/binary_sensor/binary_sensor.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#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, "%s%s '%s'", prefix, type, obj->get_name().c_str()); \
|
||||
if (!obj->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, 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.
|
||||
virtual 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_;
|
||||
};
|
||||
|
||||
class BinarySensorInitiallyOff : public BinarySensor {
|
||||
public:
|
||||
bool has_state() const override { return true; }
|
||||
};
|
||||
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
82
esphome/components/binary_sensor/filter.cpp
Normal file
82
esphome/components/binary_sensor/filter.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {}
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
|
||||
if (value) {
|
||||
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); });
|
||||
} else {
|
||||
this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
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
|
||||
89
esphome/components/binary_sensor/filter.h
Normal file
89
esphome/components/binary_sensor/filter.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#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 DelayedOnOffFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit DelayedOnOffFilter(uint32_t delay);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
uint32_t delay_;
|
||||
};
|
||||
|
||||
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
|
||||
41
esphome/components/binary_sensor_map/sensor.py
Normal file
41
esphome/components/binary_sensor_map/sensor.py
Normal file
@@ -0,0 +1,41 @@
|
||||
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, CONF_GROUP
|
||||
|
||||
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')
|
||||
|
||||
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
36
esphome/components/ble_presence/binary_sensor.py
Normal file
36
esphome/components/ble_presence/binary_sensor.py
Normal file
@@ -0,0 +1,36 @@
|
||||
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_SERVICE_UUID, 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 = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
||||
cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID))
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if CONF_MAC_ADDRESS in config:
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
if CONF_SERVICE_UUID in config:
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
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
|
||||
68
esphome/components/ble_presence/ble_presence_device.h
Normal file
68
esphome/components/ble_presence/ble_presence_device.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#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::BinarySensorInitiallyOff,
|
||||
public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
public Component {
|
||||
public:
|
||||
void set_address(uint64_t address) {
|
||||
this->by_address_ = true;
|
||||
this->address_ = address;
|
||||
}
|
||||
void set_service_uuid16(uint16_t uuid) {
|
||||
this->by_address_ = false;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid);
|
||||
}
|
||||
void set_service_uuid32(uint32_t uuid) {
|
||||
this->by_address_ = false;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid);
|
||||
}
|
||||
void set_service_uuid128(uint8_t *uuid) {
|
||||
this->by_address_ = false;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
|
||||
}
|
||||
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 (this->by_address_) {
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
if (this->uuid_ == uuid) {
|
||||
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};
|
||||
bool by_address_{false};
|
||||
uint64_t address_;
|
||||
esp32_ble_tracker::ESPBTUUID uuid_;
|
||||
};
|
||||
|
||||
} // 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
|
||||
66
esphome/components/ble_rssi/ble_rssi_sensor.h
Normal file
66
esphome/components/ble_rssi/ble_rssi_sensor.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#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) {
|
||||
this->by_address_ = true;
|
||||
this->address_ = address;
|
||||
}
|
||||
void set_service_uuid16(uint16_t uuid) {
|
||||
this->by_address_ = false;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid);
|
||||
}
|
||||
void set_service_uuid32(uint32_t uuid) {
|
||||
this->by_address_ = false;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid);
|
||||
}
|
||||
void set_service_uuid128(uint8_t *uuid) {
|
||||
this->by_address_ = false;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
|
||||
}
|
||||
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 (this->by_address_) {
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
if (this->uuid_ == uuid) {
|
||||
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};
|
||||
bool by_address_{false};
|
||||
uint64_t address_;
|
||||
esp32_ble_tracker::ESPBTUUID uuid_;
|
||||
};
|
||||
|
||||
} // namespace ble_rssi
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
36
esphome/components/ble_rssi/sensor.py
Normal file
36
esphome/components/ble_rssi/sensor.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_SERVICE_UUID, 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 = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({
|
||||
cv.GenerateID(): cv.declare_id(BLERSSISensor),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
||||
cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID))
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if CONF_MAC_ADDRESS in config:
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
if CONF_SERVICE_UUID in config:
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
0
esphome/components/ble_scanner/__init__.py
Normal file
0
esphome/components/ble_scanner/__init__.py
Normal file
16
esphome/components/ble_scanner/ble_scanner.cpp
Normal file
16
esphome/components/ble_scanner/ble_scanner.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "ble_scanner.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_scanner {
|
||||
|
||||
static const char *TAG = "ble_scanner";
|
||||
|
||||
void BLEScanner::dump_config() { LOG_TEXT_SENSOR("", "BLE Scanner", this); }
|
||||
|
||||
} // namespace ble_scanner
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
38
esphome/components/ble_scanner/ble_scanner.h
Normal file
38
esphome/components/ble_scanner/ble_scanner.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_scanner {
|
||||
|
||||
class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
this->publish_state("{\"timestamp\":" + to_string(::time(NULL)) +
|
||||
","
|
||||
"\"address\":\"" +
|
||||
device.address_str() +
|
||||
"\","
|
||||
"\"rssi\":" +
|
||||
to_string(device.get_rssi()) +
|
||||
","
|
||||
"\"name\":\"" +
|
||||
device.get_name() + "\"}");
|
||||
|
||||
return true;
|
||||
}
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
};
|
||||
|
||||
} // namespace ble_scanner
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
22
esphome/components/ble_scanner/text_sensor.py
Normal file
22
esphome/components/ble_scanner/text_sensor.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner')
|
||||
BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component,
|
||||
esp32_ble_tracker.ESPBTDeviceListener)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(BLEScanner),
|
||||
}).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 text_sensor.register_text_sensor(var, config)
|
||||
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, "BME280 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_));
|
||||
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 |= BME280_MODE_FORCED;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float meas_time = 1.5;
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user