Compare commits
440 Commits
shutdown/b
...
2022.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10b4adb8e6 | ||
|
|
83b7181bcb | ||
|
|
8886b7e141 | ||
|
|
7dcc4d030b | ||
|
|
b9398897c1 | ||
|
|
657b1c60ae | ||
|
|
dc54b17778 | ||
|
|
1fb214165b | ||
|
|
81b2fd78f5 | ||
|
|
69002fb1e6 | ||
|
|
75332a752d | ||
|
|
b528f48417 | ||
|
|
ec7a79049a | ||
|
|
6ddad6b299 | ||
|
|
16dc7762f9 | ||
|
|
dc0ed8857f | ||
|
|
bb6b77bd98 | ||
|
|
dcc80f9032 | ||
|
|
dd554bcdf4 | ||
|
|
f376a39e55 | ||
|
|
8dcc9d6b66 | ||
|
|
a576c9f21f | ||
|
|
71a438e2cb | ||
|
|
272d6f2a8b | ||
|
|
5c22065135 | ||
|
|
e7dd6c52ac | ||
|
|
3bf042dce9 | ||
|
|
09ed1aed93 | ||
|
|
53d3718028 | ||
|
|
2b5dce5232 | ||
|
|
9ad84150aa | ||
|
|
64f798d4b2 | ||
|
|
f43e04e15a | ||
|
|
88d72f8c9a | ||
|
|
9826726a72 | ||
|
|
c66d0550e8 | ||
|
|
4aeacfd16e | ||
|
|
58fa63ad88 | ||
|
|
94f944dc9c | ||
|
|
116ddbdd01 | ||
|
|
1c0697b5d4 | ||
|
|
434ca47ea0 | ||
|
|
397ef72b16 | ||
|
|
7ca9245735 | ||
|
|
69856286e8 | ||
|
|
ad43d6a5bc | ||
|
|
1e5004f495 | ||
|
|
253161d3d0 | ||
|
|
ab47e201c7 | ||
|
|
42984fa72a | ||
|
|
e7864a28a1 | ||
|
|
21803607e7 | ||
|
|
c0523590b4 | ||
|
|
c7f091ab10 | ||
|
|
7479e0aada | ||
|
|
62b366a5ec | ||
|
|
2b39988707 | ||
|
|
f9e7291050 | ||
|
|
4de642ff28 | ||
|
|
0384efcfc2 | ||
|
|
bf91443f38 | ||
|
|
4a5970b4af | ||
|
|
e3fd68c849 | ||
|
|
df0de2fc2d | ||
|
|
0c3568fad5 | ||
|
|
976f5d91ed | ||
|
|
0f3d4d9a47 | ||
|
|
ad1f4429c9 | ||
|
|
7590d5eacb | ||
|
|
c5974b8833 | ||
|
|
511c8de6f3 | ||
|
|
a718ac7ee0 | ||
|
|
ef832becf1 | ||
|
|
3a62455948 | ||
|
|
297824e2d7 | ||
|
|
d92f297bc0 | ||
|
|
7a0827e3d0 | ||
|
|
ef256a64b8 | ||
|
|
1de941e837 | ||
|
|
28b65cb810 | ||
|
|
6ff3942e8b | ||
|
|
ef5d959788 | ||
|
|
5bbee1a1fe | ||
|
|
6a2c58fcc0 | ||
|
|
bdb9546ca3 | ||
|
|
46af4cad6e | ||
|
|
76a238912b | ||
|
|
4e6bdb31ac | ||
|
|
80d03a631e | ||
|
|
6b27f2d2cf | ||
|
|
7cb6729fa7 | ||
|
|
2f46267994 | ||
|
|
cdda648360 | ||
|
|
f2d677d51a | ||
|
|
c2ee0f0864 | ||
|
|
2a84db7f85 | ||
|
|
8187a4bce9 | ||
|
|
97681d142e | ||
|
|
b2430097f2 | ||
|
|
7da12a878f | ||
|
|
a31700e16f | ||
|
|
7854522792 | ||
|
|
a6a9ebfde2 | ||
|
|
c6cbe2748e | ||
|
|
f9a7f00843 | ||
|
|
f0b183a552 | ||
|
|
338ada5c9f | ||
|
|
ef88f9923f | ||
|
|
6f8c7d9ec4 | ||
|
|
ec769ccf72 | ||
|
|
045952939e | ||
|
|
1c51cac5ba | ||
|
|
ea11462e1e | ||
|
|
62f9736b1d | ||
|
|
1f8a1f0046 | ||
|
|
172507acb5 | ||
|
|
434ab65c16 | ||
|
|
909a526967 | ||
|
|
cd6f4fb93f | ||
|
|
c19458696e | ||
|
|
cb5f793ede | ||
|
|
318b930e9f | ||
|
|
9296a078a7 | ||
|
|
5dc776e55f | ||
|
|
72d60f30f7 | ||
|
|
869743a742 | ||
|
|
7b03e07908 | ||
|
|
348f880e15 | ||
|
|
737188ae50 | ||
|
|
db21731b14 | ||
|
|
cdb4fa2487 | ||
|
|
514204f0d4 | ||
|
|
ead597d0fb | ||
|
|
afbf989715 | ||
|
|
01b62a16c3 | ||
|
|
c5eba04517 | ||
|
|
282313ab52 | ||
|
|
d274545e77 | ||
|
|
45ac577c4d | ||
|
|
09402fdb22 | ||
|
|
89e7448007 | ||
|
|
1ea6f957bc | ||
|
|
f44fca0a4b | ||
|
|
52d2f62a57 | ||
|
|
d3fda37615 | ||
|
|
cbe3092404 | ||
|
|
6dfe3039d0 | ||
|
|
d6009453df | ||
|
|
2a8668ea60 | ||
|
|
cc0d433621 | ||
|
|
c81323ef91 | ||
|
|
1fe89fb364 | ||
|
|
961c27f1c2 | ||
|
|
fe4a14e6cc | ||
|
|
ee58ad1ac0 | ||
|
|
c0ff899812 | ||
|
|
d9c938de33 | ||
|
|
56547b3d50 | ||
|
|
5026bc7a78 | ||
|
|
27364ee72c | ||
|
|
ece71a0228 | ||
|
|
073828235f | ||
|
|
41bcc8c0f4 | ||
|
|
a0ea2aae6e | ||
|
|
f34b46a621 | ||
|
|
7217a4f7a4 | ||
|
|
6383eca54a | ||
|
|
e55bd1e559 | ||
|
|
9e8b701dea | ||
|
|
a4431abea8 | ||
|
|
5844c1767b | ||
|
|
9a70bfa471 | ||
|
|
b406c6403c | ||
|
|
499625f266 | ||
|
|
6b773553fc | ||
|
|
15fe049a99 | ||
|
|
e4555f6997 | ||
|
|
470071e0b0 | ||
|
|
ea1be8e7bf | ||
|
|
84a830195f | ||
|
|
e62c3e00c1 | ||
|
|
07e790f900 | ||
|
|
640142fc0c | ||
|
|
5c339d4597 | ||
|
|
a4931f5d78 | ||
|
|
5e1e543b06 | ||
|
|
df929f9445 | ||
|
|
d8e719d1c4 | ||
|
|
3067e482fc | ||
|
|
ed5930e934 | ||
|
|
ffea3597f4 | ||
|
|
193d3e0206 | ||
|
|
c8f4fbb7dd | ||
|
|
c855bc31b4 | ||
|
|
b924b179ab | ||
|
|
3df0fee3de | ||
|
|
b601560e81 | ||
|
|
e5775cf812 | ||
|
|
26dd1f8532 | ||
|
|
5143a5b5c5 | ||
|
|
15ce27992e | ||
|
|
dbc2812022 | ||
|
|
dce3713f12 | ||
|
|
f849d45bb6 | ||
|
|
8ad06fb9ea | ||
|
|
9124d9d6e6 | ||
|
|
45ebe51e4f | ||
|
|
407661d56b | ||
|
|
998d4229af | ||
|
|
a02d2e2e11 | ||
|
|
72fa68849f | ||
|
|
33f17f75a0 | ||
|
|
23edb18d7e | ||
|
|
07ff3a853f | ||
|
|
2cf36bdb46 | ||
|
|
50848c2f4d | ||
|
|
d32633b3c7 | ||
|
|
b37739eec2 | ||
|
|
28f87dc804 | ||
|
|
41879e41e6 | ||
|
|
fc0a6546a2 | ||
|
|
ffd4280d6c | ||
|
|
f859b346a6 | ||
|
|
cb0677cafe | ||
|
|
c6956527d1 | ||
|
|
72c6bfaa50 | ||
|
|
7927b5f624 | ||
|
|
b7aad39daf | ||
|
|
f48de6dd43 | ||
|
|
79d73d8f8b | ||
|
|
cc5947467f | ||
|
|
e152f128c8 | ||
|
|
99bd808ebe | ||
|
|
beb5f3dc9d | ||
|
|
f5c3b3446f | ||
|
|
db3b955b0f | ||
|
|
f431c7402f | ||
|
|
5516f65971 | ||
|
|
9471df0a1b | ||
|
|
6d39f64be7 | ||
|
|
4907e6f6d7 | ||
|
|
1ccee86705 | ||
|
|
542fb2175b | ||
|
|
6ec9cfb044 | ||
|
|
66e0ff8392 | ||
|
|
b89d0a9a73 | ||
|
|
4bb779d9a5 | ||
|
|
386a5b6362 | ||
|
|
e32a999cd0 | ||
|
|
bfbc6a4bad | ||
|
|
8c9e0e552d | ||
|
|
8aaf9fd83f | ||
|
|
08057720b8 | ||
|
|
bfaa648837 | ||
|
|
d504daef91 | ||
|
|
b8d3ef2f49 | ||
|
|
3bf6320030 | ||
|
|
708b928c73 | ||
|
|
649366ff44 | ||
|
|
e5c9e87fad | ||
|
|
f3d9d707b6 | ||
|
|
090e10730c | ||
|
|
fbc84861c7 | ||
|
|
e763469af8 | ||
|
|
3c0c514e44 | ||
|
|
ed5e2dd332 | ||
|
|
09b7c6f550 | ||
|
|
df315a1f51 | ||
|
|
7ee4bb621c | ||
|
|
24874f4c3c | ||
|
|
c128880033 | ||
|
|
a66e94a0b0 | ||
|
|
56870ed4a8 | ||
|
|
3ac720df47 | ||
|
|
1bc757ad06 | ||
|
|
f72abc6f3d | ||
|
|
5ac88de985 | ||
|
|
0826b367d6 | ||
|
|
329bf861d6 | ||
|
|
9dcd3d18a0 | ||
|
|
db66cd88b6 | ||
|
|
86c205fe43 | ||
|
|
c6414138c7 | ||
|
|
36b355eb82 | ||
|
|
7be9291b13 | ||
|
|
ea9e75039b | ||
|
|
a5fb036011 | ||
|
|
e55506f9db | ||
|
|
50ec1d0445 | ||
|
|
3d5e1d8d91 | ||
|
|
db2128a344 | ||
|
|
21db43db06 | ||
|
|
5009b3029f | ||
|
|
57a029189c | ||
|
|
0cb715bb76 | ||
|
|
7d03823afd | ||
|
|
8e1c9f5042 | ||
|
|
980b7cda8f | ||
|
|
3a72dd5cb6 | ||
|
|
3178243811 | ||
|
|
d30e2f2a4f | ||
|
|
6226dae05c | ||
|
|
9c6a475a6e | ||
|
|
8294d10d5b | ||
|
|
67558bec47 | ||
|
|
84873d4074 | ||
|
|
58a0b28a39 | ||
|
|
b37d3a66cc | ||
|
|
7e495a5e27 | ||
|
|
c41547fd4a | ||
|
|
0d47d41c85 | ||
|
|
41a3a17456 | ||
|
|
cbbafbcca2 | ||
|
|
c75566b374 | ||
|
|
7279f1fcc1 | ||
|
|
d7432f7c10 | ||
|
|
b0a0a153f3 | ||
|
|
024632dbd0 | ||
|
|
0a545a28b9 | ||
|
|
0f2df59998 | ||
|
|
29a7d32f77 | ||
|
|
687a7e9b2f | ||
|
|
09e8782318 | ||
|
|
f2aea02210 | ||
|
|
194f922312 | ||
|
|
fea3c48098 | ||
|
|
c2f57baec2 | ||
|
|
f4a140e126 | ||
|
|
ab506b09fe | ||
|
|
87e1cdeedb | ||
|
|
81a36146ef | ||
|
|
7fa4a68a27 | ||
|
|
f1c5e2ef81 | ||
|
|
b526155cce | ||
|
|
62c3f301e7 | ||
|
|
38cb988809 | ||
|
|
b976ac54c8 | ||
|
|
78026e766f | ||
|
|
b4cd8d21a5 | ||
|
|
7552893311 | ||
|
|
21c896d8f8 | ||
|
|
4b7fe202ec | ||
|
|
9f4519210f | ||
|
|
b0506afa5b | ||
|
|
8cbb379898 | ||
|
|
b226215593 | ||
|
|
19970729a9 | ||
|
|
d2ebfd2833 | ||
|
|
bd782fc828 | ||
|
|
23560e608c | ||
|
|
f1377b560e | ||
|
|
72108684ea | ||
|
|
c6adaaea97 | ||
|
|
91999a38ca | ||
|
|
b34eed125d | ||
|
|
2abe09529a | ||
|
|
9aaaf4dd4b | ||
|
|
cbfbcf7f1b | ||
|
|
c7651dc40d | ||
|
|
eda1c471ad | ||
|
|
c7ef18fbc4 | ||
|
|
901ec918b1 | ||
|
|
6bdae55ee1 | ||
|
|
dfb96e4b7f | ||
|
|
ff2c316b18 | ||
|
|
5be52f71f9 | ||
|
|
42873dd37c | ||
|
|
f93e7d4e3a | ||
|
|
bbcd523967 | ||
|
|
68cbe58d00 | ||
|
|
115bca98f1 | ||
|
|
ed0b34b2fe | ||
|
|
ab34401421 | ||
|
|
eed0c18d65 | ||
|
|
e5a38ce748 | ||
|
|
7d9d9fcf36 | ||
|
|
f0aba6ceb2 | ||
|
|
ab07ee57c6 | ||
|
|
eae3d72a4d | ||
|
|
7b8d826704 | ||
|
|
e7baa42e63 | ||
|
|
2f32833a22 | ||
|
|
f6935a4b4b | ||
|
|
332c9e891b | ||
|
|
b91ee4847f | ||
|
|
625463d871 | ||
|
|
6433a01e07 | ||
|
|
56cc31e8e7 | ||
|
|
3af297aa76 | ||
|
|
996ec59d28 | ||
|
|
95593eeeab | ||
|
|
dad244fb7a | ||
|
|
adb5d27d95 | ||
|
|
8456a8cecb | ||
|
|
b9f66373c1 | ||
|
|
9ac365feef | ||
|
|
43bbd58a44 | ||
|
|
7feffa64f3 | ||
|
|
ea0977abb4 | ||
|
|
4c83dc7c28 | ||
|
|
e10ab1da78 | ||
|
|
1b0e60374b | ||
|
|
3a760fbb44 | ||
|
|
6ef57a2973 | ||
|
|
3e9c7f2e9f | ||
|
|
430598b7a1 | ||
|
|
91611b09b4 | ||
|
|
ecd115851f | ||
|
|
4a1e50fed1 | ||
|
|
d6d037047b | ||
|
|
b5734c2b20 | ||
|
|
723fb7eaac | ||
|
|
63a9acaa19 | ||
|
|
0524f8c677 | ||
|
|
70b62f272e | ||
|
|
f0089b7940 | ||
|
|
4b44280d53 | ||
|
|
f045382d20 | ||
|
|
db3fa1ade7 | ||
|
|
f83950fd75 | ||
|
|
4dd1bf920d | ||
|
|
98755f3621 | ||
|
|
c3a8a044b9 | ||
|
|
15b5ea43a7 | ||
|
|
ec683fc227 | ||
|
|
d4e65eb82a | ||
|
|
10c6601b0a | ||
|
|
73940bc1bd | ||
|
|
9b7fb829f9 | ||
|
|
c51d8c9021 | ||
|
|
d8a6dfe5ce | ||
|
|
5f7cef0b06 | ||
|
|
48ff2ffc68 | ||
|
|
b3b9ccd314 | ||
|
|
e63c7b483b | ||
|
|
f57980b069 | ||
|
|
7006aa0d2a | ||
|
|
8051c1ca99 | ||
|
|
a779592414 | ||
|
|
112215848d |
10
.clang-tidy
10
.clang-tidy
@@ -68,8 +68,6 @@ Checks: >-
|
||||
-modernize-use-nodiscard,
|
||||
-mpi-*,
|
||||
-objc-*,
|
||||
-readability-braces-around-statements,
|
||||
-readability-const-return-type,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-else-after-return,
|
||||
-readability-function-cognitive-complexity,
|
||||
@@ -77,10 +75,6 @@ Checks: >-
|
||||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers,
|
||||
-readability-make-member-function-const,
|
||||
-readability-named-parameter,
|
||||
-readability-qualified-auto,
|
||||
-readability-redundant-access-specifiers,
|
||||
-readability-redundant-member-init,
|
||||
-readability-redundant-string-init,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-use-anyofallof,
|
||||
@@ -114,6 +108,8 @@ CheckOptions:
|
||||
value: 'make_unique'
|
||||
- key: modernize-make-unique.MakeSmartPtrFunctionHeader
|
||||
value: 'esphome/core/helpers.h'
|
||||
- key: readability-braces-around-statements.ShortStatementLines
|
||||
value: 2
|
||||
- key: readability-identifier-naming.LocalVariableCase
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.ClassCase
|
||||
@@ -160,3 +156,5 @@ CheckOptions:
|
||||
value: 'lower_case'
|
||||
- key: readability-identifier-naming.VirtualMethodSuffix
|
||||
value: ''
|
||||
- key: readability-qualified-auto.AddConstToQualified
|
||||
value: 0
|
||||
|
||||
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@@ -51,26 +51,26 @@ jobs:
|
||||
name: Run script/clang-format
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP8266
|
||||
options: --environment esp8266-tidy --grep USE_ESP8266
|
||||
options: --environment esp8266-arduino-tidy --grep USE_ESP8266
|
||||
pio_cache_key: tidyesp8266
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 1/4
|
||||
options: --environment esp32-tidy --split-num 4 --split-at 1
|
||||
name: Run script/clang-tidy for ESP32 Arduino 1/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 2/4
|
||||
options: --environment esp32-tidy --split-num 4 --split-at 2
|
||||
name: Run script/clang-tidy for ESP32 Arduino 2/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 3/4
|
||||
options: --environment esp32-tidy --split-num 4 --split-at 3
|
||||
name: Run script/clang-tidy for ESP32 Arduino 3/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 4/4
|
||||
options: --environment esp32-tidy --split-num 4 --split-at 4
|
||||
name: Run script/clang-tidy for ESP32 Arduino 4/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 esp-idf
|
||||
name: Run script/clang-tidy for ESP32 IDF
|
||||
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
||||
pio_cache_key: tidyesp32-idf
|
||||
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -137,18 +137,18 @@ jobs:
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
manifest
|
||||
|
||||
deploy-hassio-repo:
|
||||
deploy-ha-addon-repo:
|
||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
|
||||
TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
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 \
|
||||
https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
|
||||
-d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"
|
||||
|
||||
@@ -3,4 +3,4 @@ ports:
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
|
||||
command: python -m esphome config dashboard
|
||||
command: python -m esphome dashboard config
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 20.8b1
|
||||
rev: 22.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@@ -10,7 +10,7 @@ repos:
|
||||
- --quiet
|
||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.8.4
|
||||
rev: 4.0.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
|
||||
28
CODEOWNERS
28
CODEOWNERS
@@ -28,13 +28,16 @@ esphome/components/b_parasite/* @rbaron
|
||||
esphome/components/ballu/* @bazuchan
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/bl0940/* @tobias-
|
||||
esphome/components/ble_client/* @buxtronix
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/bmp3xx/* @martgras
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @MrEditor97
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/ccs811/* @habbie
|
||||
esphome/components/cd74hc4067/* @asoehlke
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/color_temperature/* @jesserockz
|
||||
@@ -51,10 +54,12 @@ esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_can/* @Sympatron
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
@@ -65,6 +70,7 @@ esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/graph/* @synco
|
||||
esphome/components/growatt_solar/* @leeuwte
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
@@ -74,16 +80,21 @@ esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/ina260/* @MrEditor97
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate6/* @jesserockz
|
||||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/max9611/* @mckaymatthew
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
esphome/components/mcp23017/* @jesserockz
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
||||
@@ -92,15 +103,20 @@ esphome/components/mcp23x08_base/* @jesserockz
|
||||
esphome/components/mcp23x17_base/* @jesserockz
|
||||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp3204/* @rsumner
|
||||
esphome/components/mcp47a1/* @jesserockz
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mlx90393/* @functionpointer
|
||||
esphome/components/modbus_controller/* @martgras
|
||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||
esphome/components/modbus_controller/number/* @martgras
|
||||
esphome/components/modbus_controller/output/* @martgras
|
||||
esphome/components/modbus_controller/select/* @martgras @stegm
|
||||
esphome/components/modbus_controller/sensor/* @martgras
|
||||
esphome/components/modbus_controller/switch/* @martgras
|
||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||
@@ -123,8 +139,12 @@ esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
||||
esphome/components/power_supply/* @esphome/core
|
||||
esphome/components/preferences/* @esphome/core
|
||||
esphome/components/psram/* @esphome/core
|
||||
esphome/components/pulse_meter/* @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/qr_code/* @wjtje
|
||||
esphome/components/radon_eye_ble/* @jeffeb3
|
||||
esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
@@ -132,7 +152,7 @@ esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rgbct/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @paulmonigatti
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
esphome/components/scd4x/* @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
@@ -142,7 +162,7 @@ esphome/components/select/* @esphome/core
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/socket/* @esphome/core
|
||||
@@ -176,17 +196,21 @@ esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tmp117/* @Azimath
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
esphome/components/toshiba/* @kbx81
|
||||
esphome/components/touchscreen/* @jesserockz
|
||||
esphome/components/tsl2591/* @wjcarpenter
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/number/* @frankiboy1
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/tuya/text_sensor/* @dentra
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc303/* @drug123
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xpt2046/* @numo68
|
||||
|
||||
@@ -4,4 +4,5 @@ include requirements.txt
|
||||
include esphome/dashboard/templates/*.html
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||
recursive-include esphome *.cpp *.h *.tcc
|
||||
recursive-include esphome *.py.script
|
||||
recursive-include esphome LICENSE.txt
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
# One of "docker", "hassio"
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
|
||||
FROM debian:bullseye-20211011-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20211011-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20211011-slim AS base-docker-armv7
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7
|
||||
FROM debian:bullseye-20220125-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20220125-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20220125-slim AS base-docker-armv7
|
||||
|
||||
# Use TARGETARCH/TARGETVARIANT defined by docker
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
@@ -23,11 +23,11 @@ RUN \
|
||||
python3=3.9.2-3 \
|
||||
python3-pip=20.3.4-4 \
|
||||
python3-setuptools=52.0.0-4 \
|
||||
python3-pil=8.1.2+dfsg-0.3 \
|
||||
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
curl=7.74.0-1.3+b1 \
|
||||
curl=7.74.0-1.3+deb11u1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
@@ -42,8 +42,8 @@ ENV \
|
||||
RUN \
|
||||
# Ubuntu python3-pip is missing wheel
|
||||
pip3 install --no-cache-dir \
|
||||
wheel==0.36.2 \
|
||||
platformio==5.2.2 \
|
||||
wheel==0.37.1 \
|
||||
platformio==5.2.5 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_libraries_interval 1000000 \
|
||||
@@ -64,7 +64,7 @@ RUN \
|
||||
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN pip3 install --no-cache-dir -e /esphome
|
||||
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
|
||||
|
||||
# Settings for dashboard
|
||||
ENV USERNAME="" PASSWORD=""
|
||||
@@ -102,7 +102,7 @@ RUN \
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/hassio-rootfs/ /
|
||||
COPY docker/ha-addon-rootfs/ /
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
@@ -112,7 +112,7 @@ RUN \
|
||||
|
||||
# Copy esphome and install
|
||||
COPY . /esphome
|
||||
RUN pip3 install --no-cache-dir -e /esphome
|
||||
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
|
||||
|
||||
# Labels
|
||||
LABEL \
|
||||
|
||||
@@ -32,6 +32,7 @@ parser.add_argument("--dry-run", action="store_true", help="Don't run any comman
|
||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
|
||||
build_parser = subparsers.add_parser("build", help="Build the image")
|
||||
build_parser.add_argument("--push", help="Also push the images", action="store_true")
|
||||
build_parser.add_argument("--load", help="Load the docker image locally", action="store_true")
|
||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
|
||||
|
||||
|
||||
@@ -132,6 +133,8 @@ def main():
|
||||
cmd += ["--tag", img]
|
||||
if args.push:
|
||||
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
|
||||
if args.load:
|
||||
cmd += ["--load"]
|
||||
|
||||
run_command(*cmd, ".")
|
||||
elif args.command == "manifest":
|
||||
|
||||
@@ -10,7 +10,7 @@ server {
|
||||
ssl_certificate_key /ssl/%%keyfile%%;
|
||||
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "";
|
||||
proxy_set_header X-HA-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/
|
||||
@@ -4,7 +4,7 @@ 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 "";
|
||||
proxy_set_header X-HA-Ingress "";
|
||||
|
||||
location / {
|
||||
proxy_pass http://esphome;
|
||||
@@ -3,8 +3,8 @@ server {
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
# Set Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "YES";
|
||||
# Set Home Assistant Ingress header
|
||||
proxy_set_header X-HA-Ingress "YES";
|
||||
|
||||
location / {
|
||||
# Only allow from Hass.io supervisor
|
||||
@@ -4,7 +4,7 @@
|
||||
# Runs the ESPHome dashboard
|
||||
# ==============================================================================
|
||||
|
||||
export ESPHOME_IS_HASSIO=true
|
||||
export ESPHOME_IS_HA_ADDON=true
|
||||
|
||||
if bashio::config.true 'leave_front_door_open'; then
|
||||
export DISABLE_HA_AUTHENTICATION=true
|
||||
@@ -32,4 +32,4 @@ export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
|
||||
@@ -661,7 +661,7 @@ def parse_args(argv):
|
||||
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--hassio", help=argparse.SUPPRESS, action="store_true"
|
||||
"--ha-addon", help=argparse.SUPPRESS, action="store_true"
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--socket", help="Make the dashboard serve under a unix socket", type=str
|
||||
|
||||
@@ -63,6 +63,7 @@ from esphome.cpp_types import ( # noqa
|
||||
uint32,
|
||||
uint64,
|
||||
int32,
|
||||
int64,
|
||||
const_char_ptr,
|
||||
NAN,
|
||||
esphome_ns,
|
||||
@@ -75,11 +76,11 @@ from esphome.cpp_types import ( # noqa
|
||||
optional,
|
||||
arduino_json_ns,
|
||||
JsonObject,
|
||||
JsonObjectRef,
|
||||
JsonObjectConstRef,
|
||||
JsonObjectConst,
|
||||
Controller,
|
||||
GPIOPin,
|
||||
InternalGPIOPin,
|
||||
gpio_Flags,
|
||||
EntityCategory,
|
||||
Parented,
|
||||
)
|
||||
|
||||
@@ -52,10 +52,10 @@ uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||
this->gate_pin.digital_write(false);
|
||||
}
|
||||
|
||||
if (time_since_zc < this->enable_time_us)
|
||||
if (time_since_zc < this->enable_time_us) {
|
||||
// Next event is enable, return time until that event
|
||||
return this->enable_time_us - time_since_zc;
|
||||
else if (time_since_zc < disable_time_us) {
|
||||
} else if (time_since_zc < disable_time_us) {
|
||||
// Next event is disable, return time until that event
|
||||
return this->disable_time_us - time_since_zc;
|
||||
}
|
||||
@@ -74,9 +74,10 @@ uint32_t IRAM_ATTR HOT timer_interrupt() {
|
||||
uint32_t min_dt_us = 1000;
|
||||
uint32_t now = micros();
|
||||
for (auto *dimmer : all_dimmers) {
|
||||
if (dimmer == nullptr)
|
||||
if (dimmer == nullptr) {
|
||||
// no more dimmers
|
||||
break;
|
||||
}
|
||||
uint32_t res = dimmer->timer_intr(now);
|
||||
if (res != 0 && res < min_dt_us)
|
||||
min_dt_us = res;
|
||||
@@ -212,12 +213,13 @@ void AcDimmer::dump_config() {
|
||||
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f);
|
||||
ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
|
||||
if (method_ == DIM_METHOD_LEADING_PULSE)
|
||||
if (method_ == DIM_METHOD_LEADING_PULSE) {
|
||||
ESP_LOGCONFIG(TAG, " Method: leading pulse");
|
||||
else if (method_ == DIM_METHOD_LEADING)
|
||||
} else if (method_ == DIM_METHOD_LEADING) {
|
||||
ESP_LOGCONFIG(TAG, " Method: leading");
|
||||
else
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Method: trailing");
|
||||
}
|
||||
|
||||
LOG_FLOAT_OUTPUT(this);
|
||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||
|
||||
@@ -13,7 +13,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
|
||||
public:
|
||||
AdalightLightEffect(const std::string &name);
|
||||
|
||||
public:
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void apply(light::AddressableLight &it, const Color ¤t_color) override;
|
||||
@@ -30,7 +29,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
|
||||
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};
|
||||
|
||||
@@ -15,6 +15,11 @@ namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
// 13 bits for S3 / 12 bit for all other esp32 variants
|
||||
// create a const to avoid the repated cast to enum
|
||||
#ifdef USE_ESP32
|
||||
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
|
||||
#endif
|
||||
|
||||
void ADCSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||
@@ -23,14 +28,14 @@ void ADCSensor::setup() {
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
|
||||
if (!autorange_) {
|
||||
adc1_config_channel_atten(channel_, attenuation_);
|
||||
}
|
||||
|
||||
// load characteristics for each attenuation
|
||||
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
|
||||
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12,
|
||||
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
|
||||
1100, // default vref
|
||||
&cal_characteristics_[i]);
|
||||
switch (cal_value) {
|
||||
@@ -65,9 +70,9 @@ void ADCSensor::dump_config() {
|
||||
|
||||
#ifdef USE_ESP32
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
if (autorange_)
|
||||
if (autorange_) {
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: auto");
|
||||
else
|
||||
} else {
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
|
||||
@@ -84,6 +89,7 @@ void ADCSensor::dump_config() {
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // USE_ESP32
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle_ = 0;
|
||||
auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
|
||||
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
|
||||
sensors_data_characteristic_uuid_.to_string().c_str());
|
||||
@@ -56,7 +56,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
}
|
||||
|
||||
void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
|
||||
auto value = (WaveMiniReadings *) raw_value;
|
||||
auto *value = (WaveMiniReadings *) raw_value;
|
||||
|
||||
if (sizeof(WaveMiniReadings) <= value_len) {
|
||||
this->humidity_sensor_->publish_state(value->humidity / 100.0f);
|
||||
|
||||
@@ -24,7 +24,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle_ = 0;
|
||||
auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
|
||||
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
|
||||
sensors_data_characteristic_uuid_.to_string().c_str());
|
||||
@@ -56,7 +56,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
|
||||
auto value = (WavePlusReadings *) raw_value;
|
||||
auto *value = (WavePlusReadings *) raw_value;
|
||||
|
||||
if (sizeof(WavePlusReadings) <= value_len) {
|
||||
ESP_LOGD(TAG, "version = %d", value->version);
|
||||
|
||||
@@ -19,12 +19,14 @@ uint16_t crc_16(uint8_t *ptr, uint8_t length) {
|
||||
//------------------------------
|
||||
while (length--) {
|
||||
crc ^= *ptr++;
|
||||
for (i = 0; i < 8; i++)
|
||||
for (i = 0; i < 8; i++) {
|
||||
if ((crc & 0x01) != 0) {
|
||||
crc >>= 1;
|
||||
crc ^= 0xA001;
|
||||
} else
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
|
||||
auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
|
||||
if (chr == nullptr) {
|
||||
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
|
||||
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
|
||||
@@ -75,13 +75,14 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
||||
|
||||
if (this->current_sensor_ > 0) {
|
||||
if (this->illuminance_ != nullptr) {
|
||||
auto packet = this->encoder_->get_light_level_request();
|
||||
auto *packet = this->encoder_->get_light_level_request();
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
|
||||
status);
|
||||
}
|
||||
}
|
||||
this->current_sensor_ = 0;
|
||||
}
|
||||
@@ -99,7 +100,7 @@ void Am43::update() {
|
||||
}
|
||||
if (this->current_sensor_ == 0) {
|
||||
if (this->battery_ != nullptr) {
|
||||
auto packet = this->encoder_->get_battery_level_request();
|
||||
auto *packet = this->encoder_->get_battery_level_request();
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
|
||||
@@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN
|
||||
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
AUTO_LOAD = ["am43"]
|
||||
AUTO_LOAD = ["am43", "sensor"]
|
||||
|
||||
CONF_INVERT_POSITION = "invert_position"
|
||||
|
||||
|
||||
@@ -25,15 +25,16 @@ void Am43Component::setup() {
|
||||
|
||||
void Am43Component::loop() {
|
||||
if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
|
||||
auto packet = this->encoder_->get_send_pin_request(this->pin_);
|
||||
auto *packet = this->encoder_->get_send_pin_request(this->pin_);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
|
||||
if (status)
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
|
||||
else
|
||||
} else {
|
||||
this->logged_in_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ void Am43Component::control(const CoverCall &call) {
|
||||
return;
|
||||
}
|
||||
if (call.get_stop()) {
|
||||
auto packet = this->encoder_->get_stop_request();
|
||||
auto *packet = this->encoder_->get_stop_request();
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
@@ -63,7 +64,7 @@ void Am43Component::control(const CoverCall &call) {
|
||||
|
||||
if (this->invert_position_)
|
||||
pos = 1 - pos;
|
||||
auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
|
||||
auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
@@ -80,7 +81,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
|
||||
auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
|
||||
if (chr == nullptr) {
|
||||
if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
|
||||
ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str());
|
||||
@@ -120,7 +121,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
if (this->decoder_->has_pin_response()) {
|
||||
if (this->decoder_->pin_ok_) {
|
||||
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
|
||||
auto packet = this->encoder_->get_position_request();
|
||||
auto *packet = this->encoder_->get_position_request();
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
|
||||
@@ -76,6 +76,8 @@ async def to_code(config):
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
frame = image.convert("RGB")
|
||||
if CONF_RESIZE in config:
|
||||
frame = frame.resize([width, height])
|
||||
|
||||
@@ -40,7 +40,7 @@ void Anova::control(const ClimateCall &call) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
auto pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
|
||||
auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
@@ -57,7 +57,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
|
||||
auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
|
||||
ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
|
||||
@@ -114,9 +114,10 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length,
|
||||
pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
|
||||
status);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -133,7 +134,7 @@ void Anova::update() {
|
||||
return;
|
||||
|
||||
if (this->current_request_ < 2) {
|
||||
auto pkt = this->codec_->get_read_device_status_request();
|
||||
auto *pkt = this->codec_->get_read_device_status_request();
|
||||
if (this->current_request_ == 0)
|
||||
this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
|
||||
@@ -36,7 +36,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
|
||||
traits.set_visual_temperature_step(0.1);
|
||||
return traits;
|
||||
}
|
||||
void set_unit_of_measurement(const char *);
|
||||
void set_unit_of_measurement(const char *unit);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<AnovaCodec> codec_;
|
||||
|
||||
@@ -225,9 +225,10 @@ void APDS9960::read_gesture_data_() {
|
||||
|
||||
uint8_t fifo_level;
|
||||
APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed.");
|
||||
if (fifo_level == 0)
|
||||
if (fifo_level == 0) {
|
||||
// no data to process
|
||||
return;
|
||||
}
|
||||
|
||||
APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.")
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ service APIConnection {
|
||||
rpc number_command (NumberCommandRequest) returns (void) {}
|
||||
rpc select_command (SelectCommandRequest) returns (void) {}
|
||||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +96,9 @@ message HelloResponse {
|
||||
// and only exists for debugging/logging purposes.
|
||||
// For example "ESPHome v1.10.0 on ESP8266"
|
||||
string server_info = 3;
|
||||
|
||||
// The name of the server (App.get_name())
|
||||
string name = 4;
|
||||
}
|
||||
|
||||
// Message sent at the beginning of each connection to authenticate the client
|
||||
@@ -525,6 +529,7 @@ message ListEntitiesSwitchResponse {
|
||||
bool assumed_state = 6;
|
||||
bool disabled_by_default = 7;
|
||||
EntityCategory entity_category = 8;
|
||||
string device_class = 9;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
@@ -953,6 +958,63 @@ message SelectCommandRequest {
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
|
||||
// ==================== LOCK ====================
|
||||
enum LockState {
|
||||
LOCK_STATE_NONE = 0;
|
||||
LOCK_STATE_LOCKED = 1;
|
||||
LOCK_STATE_UNLOCKED = 2;
|
||||
LOCK_STATE_JAMMED = 3;
|
||||
LOCK_STATE_LOCKING = 4;
|
||||
LOCK_STATE_UNLOCKING = 5;
|
||||
}
|
||||
enum LockCommand {
|
||||
LOCK_UNLOCK = 0;
|
||||
LOCK_LOCK = 1;
|
||||
LOCK_OPEN = 2;
|
||||
}
|
||||
message ListEntitiesLockResponse {
|
||||
option (id) = 58;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
bool assumed_state = 8;
|
||||
|
||||
bool supports_open = 9;
|
||||
bool requires_code = 10;
|
||||
|
||||
# Not yet implemented:
|
||||
string code_format = 11;
|
||||
}
|
||||
message LockStateResponse {
|
||||
option (id) = 59;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
LockState state = 2;
|
||||
}
|
||||
message LockCommandRequest {
|
||||
option (id) = 60;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
LockCommand command = 2;
|
||||
|
||||
# Not yet implemented:
|
||||
bool has_code = 3;
|
||||
string code = 4;
|
||||
}
|
||||
|
||||
// ==================== BUTTON ====================
|
||||
message ListEntitiesButtonResponse {
|
||||
option (id) = 61;
|
||||
@@ -977,3 +1039,4 @@ message ButtonCommandRequest {
|
||||
|
||||
fixed32 key = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *const TAG = "api.connection";
|
||||
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||
: parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
@@ -254,7 +255,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
// Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
bool APIConnection::send_fan_state(fan::Fan *fan) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
@@ -272,7 +273,7 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
return this->send_fan_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||
bool APIConnection::send_fan_info(fan::Fan *fan) {
|
||||
auto traits = fan->get_traits();
|
||||
ListEntitiesFanResponse msg;
|
||||
msg.key = fan->get_object_id_hash();
|
||||
@@ -289,7 +290,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||
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);
|
||||
fan::Fan *fan = App.get_fan_by_key(msg.key);
|
||||
if (fan == nullptr)
|
||||
return;
|
||||
|
||||
@@ -461,6 +462,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
msg.disabled_by_default = a_switch->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
|
||||
msg.device_class = a_switch->get_device_class();
|
||||
return this->send_list_entities_switch_response(msg);
|
||||
}
|
||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
@@ -468,10 +470,11 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
if (a_switch == nullptr)
|
||||
return;
|
||||
|
||||
if (msg.state)
|
||||
if (msg.state) {
|
||||
a_switch->turn_on();
|
||||
else
|
||||
} else {
|
||||
a_switch->turn_off();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -698,13 +701,58 @@ void APIConnection::button_command(const ButtonCommandRequest &msg) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
LockStateResponse resp{};
|
||||
resp.key = a_lock->get_object_id_hash();
|
||||
resp.state = static_cast<enums::LockState>(state);
|
||||
return this->send_lock_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_lock_info(lock::Lock *a_lock) {
|
||||
ListEntitiesLockResponse msg;
|
||||
msg.key = a_lock->get_object_id_hash();
|
||||
msg.object_id = a_lock->get_object_id();
|
||||
msg.name = a_lock->get_name();
|
||||
msg.unique_id = get_default_unique_id("lock", a_lock);
|
||||
msg.icon = a_lock->get_icon();
|
||||
msg.assumed_state = a_lock->traits.get_assumed_state();
|
||||
msg.disabled_by_default = a_lock->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category());
|
||||
msg.supports_open = a_lock->traits.get_supports_open();
|
||||
msg.requires_code = a_lock->traits.get_requires_code();
|
||||
return this->send_list_entities_lock_response(msg);
|
||||
}
|
||||
void APIConnection::lock_command(const LockCommandRequest &msg) {
|
||||
lock::Lock *a_lock = App.get_lock_by_key(msg.key);
|
||||
if (a_lock == nullptr)
|
||||
return;
|
||||
|
||||
switch (msg.command) {
|
||||
case enums::LOCK_UNLOCK:
|
||||
a_lock->unlock();
|
||||
break;
|
||||
case enums::LOCK_LOCK:
|
||||
a_lock->lock();
|
||||
break;
|
||||
case enums::LOCK_OPEN:
|
||||
a_lock->open();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#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(std::move(image));
|
||||
if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) ||
|
||||
image->was_requested_by(esphome::esp32_camera::IDLE))
|
||||
this->image_reader_.set_image(std::move(image));
|
||||
}
|
||||
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
ListEntitiesCameraResponse msg;
|
||||
@@ -722,9 +770,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||
return;
|
||||
|
||||
if (msg.single)
|
||||
esp32_camera::global_esp32_camera->request_image();
|
||||
if (msg.stream)
|
||||
esp32_camera::global_esp32_camera->request_stream();
|
||||
esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER);
|
||||
if (msg.stream) {
|
||||
esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER);
|
||||
|
||||
App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() {
|
||||
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER);
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -758,6 +811,8 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 6;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
resp.name = App.get_name();
|
||||
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
}
|
||||
@@ -795,15 +850,16 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
resp.project_version = ESPHOME_PROJECT_VERSION;
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER
|
||||
resp.webserver_port = WEBSERVER_PORT;
|
||||
resp.webserver_port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs())
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
|
||||
it.callback(msg.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||
bool found = false;
|
||||
|
||||
@@ -32,8 +32,8 @@ class APIConnection : public APIServerConnection {
|
||||
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);
|
||||
bool send_fan_state(fan::Fan *fan);
|
||||
bool send_fan_info(fan::Fan *fan);
|
||||
void fan_command(const FanCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
@@ -77,6 +77,11 @@ class APIConnection : public APIServerConnection {
|
||||
#ifdef USE_BUTTON
|
||||
bool send_button_info(button::Button *button);
|
||||
void button_command(const ButtonCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
|
||||
bool send_lock_info(lock::Lock *a_lock);
|
||||
void lock_command(const LockCommandRequest &msg) override;
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "api_frame_helper.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "proto.h"
|
||||
#include <cstring>
|
||||
|
||||
@@ -301,9 +303,16 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
if (state_ == State::SERVER_HELLO) {
|
||||
// send server hello
|
||||
uint8_t msg[1];
|
||||
msg[0] = 0x01; // chosen proto
|
||||
aerr = write_frame_(msg, 1);
|
||||
std::vector<uint8_t> msg;
|
||||
// chosen proto
|
||||
msg.push_back(0x01);
|
||||
|
||||
// node name, terminated by null byte
|
||||
const std::string &name = App.get_name();
|
||||
const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
|
||||
msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
|
||||
|
||||
aerr = write_frame_(msg.data(), msg.size());
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
@@ -721,7 +730,12 @@ APIError APINoiseFrameHelper::shutdown(int how) {
|
||||
}
|
||||
extern "C" {
|
||||
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
|
||||
void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast<uint8_t *>(output), len); }
|
||||
void noise_rand_bytes(void *output, size_t len) {
|
||||
if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
|
||||
ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!");
|
||||
arch_restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
|
||||
@@ -278,6 +278,36 @@ template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState value) {
|
||||
switch (value) {
|
||||
case enums::LOCK_STATE_NONE:
|
||||
return "LOCK_STATE_NONE";
|
||||
case enums::LOCK_STATE_LOCKED:
|
||||
return "LOCK_STATE_LOCKED";
|
||||
case enums::LOCK_STATE_UNLOCKED:
|
||||
return "LOCK_STATE_UNLOCKED";
|
||||
case enums::LOCK_STATE_JAMMED:
|
||||
return "LOCK_STATE_JAMMED";
|
||||
case enums::LOCK_STATE_LOCKING:
|
||||
return "LOCK_STATE_LOCKING";
|
||||
case enums::LOCK_STATE_UNLOCKING:
|
||||
return "LOCK_STATE_UNLOCKING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockCommand value) {
|
||||
switch (value) {
|
||||
case enums::LOCK_UNLOCK:
|
||||
return "LOCK_UNLOCK";
|
||||
case enums::LOCK_LOCK:
|
||||
return "LOCK_LOCK";
|
||||
case enums::LOCK_OPEN:
|
||||
return "LOCK_OPEN";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@@ -319,6 +349,10 @@ bool HelloResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value)
|
||||
this->server_info = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -327,6 +361,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->api_version_major);
|
||||
buffer.encode_uint32(2, this->api_version_minor);
|
||||
buffer.encode_string(3, this->server_info);
|
||||
buffer.encode_string(4, this->name);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void HelloResponse::dump_to(std::string &out) const {
|
||||
@@ -345,6 +380,10 @@ void HelloResponse::dump_to(std::string &out) const {
|
||||
out.append(" server_info: ");
|
||||
out.append("'").append(this->server_info).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -2138,6 +2177,10 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2161,6 +2204,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->assumed_state);
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
|
||||
buffer.encode_string(9, this->device_class);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
@@ -2198,6 +2242,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -4177,6 +4225,234 @@ void SelectCommandRequest::dump_to(std::string &out) const {
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->assumed_state = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->supports_open = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->requires_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->object_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->code_format = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesLockResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_bool(8, this->assumed_state);
|
||||
buffer.encode_bool(9, this->supports_open);
|
||||
buffer.encode_bool(10, this->requires_code);
|
||||
buffer.encode_string(11, this->code_format);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLockResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesLockResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unique_id: ");
|
||||
out.append("'").append(this->unique_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" assumed_state: ");
|
||||
out.append(YESNO(this->assumed_state));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_open: ");
|
||||
out.append(YESNO(this->supports_open));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" requires_code: ");
|
||||
out.append(YESNO(this->requires_code));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" code_format: ");
|
||||
out.append("'").append(this->code_format).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->state = value.as_enum<enums::LockState>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void LockStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::LockState>(2, this->state);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void LockStateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("LockStateResponse {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" state: ");
|
||||
out.append(proto_enum_to_string<enums::LockState>(this->state));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->command = value.as_enum<enums::LockCommand>();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->has_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
this->code = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void LockCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::LockCommand>(2, this->command);
|
||||
buffer.encode_bool(3, this->has_code);
|
||||
buffer.encode_string(4, this->code);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void LockCommandRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("LockCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" command: ");
|
||||
out.append(proto_enum_to_string<enums::LockCommand>(this->command));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_code: ");
|
||||
out.append(YESNO(this->has_code));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" code: ");
|
||||
out.append("'").append(this->code).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
@@ -4239,7 +4515,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesButtonResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
@@ -4289,7 +4565,7 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ButtonCommandRequest::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ButtonCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
|
||||
@@ -128,6 +128,19 @@ enum NumberMode : uint32_t {
|
||||
NUMBER_MODE_BOX = 1,
|
||||
NUMBER_MODE_SLIDER = 2,
|
||||
};
|
||||
enum LockState : uint32_t {
|
||||
LOCK_STATE_NONE = 0,
|
||||
LOCK_STATE_LOCKED = 1,
|
||||
LOCK_STATE_UNLOCKED = 2,
|
||||
LOCK_STATE_JAMMED = 3,
|
||||
LOCK_STATE_LOCKING = 4,
|
||||
LOCK_STATE_UNLOCKING = 5,
|
||||
};
|
||||
enum LockCommand : uint32_t {
|
||||
LOCK_UNLOCK = 0,
|
||||
LOCK_LOCK = 1,
|
||||
LOCK_OPEN = 2,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
@@ -147,6 +160,7 @@ class HelloResponse : public ProtoMessage {
|
||||
uint32_t api_version_major{0};
|
||||
uint32_t api_version_minor{0};
|
||||
std::string server_info{};
|
||||
std::string name{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -566,6 +580,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
bool assumed_state{false};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -1048,6 +1063,58 @@ class SelectCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesLockResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
bool assumed_state{false};
|
||||
bool supports_open{false};
|
||||
bool requires_code{false};
|
||||
std::string code_format{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
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 LockStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
enums::LockState state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LockCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
enums::LockCommand command{};
|
||||
bool has_code{false};
|
||||
std::string code{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
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 ListEntitiesButtonResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
|
||||
@@ -282,6 +282,24 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_lock_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesLockResponse>(msg, 58);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool APIServerConnectionBase::send_lock_state_response(const LockStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_lock_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<LockStateResponse>(msg, 59);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -523,6 +541,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_select_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 60: {
|
||||
#ifdef USE_LOCK
|
||||
LockCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_lock_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -771,6 +800,19 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest &
|
||||
this->button_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->lock_command(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
@@ -130,6 +130,15 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#ifdef USE_SELECT
|
||||
virtual void on_select_command_request(const SelectCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state_response(const LockStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual void on_lock_command_request(const LockCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
|
||||
#endif
|
||||
@@ -180,6 +189,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
virtual void button_command(const ButtonCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual void lock_command(const LockCommandRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
@@ -221,6 +233,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_BUTTON
|
||||
void on_button_command_request(const ButtonCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_command_request(const LockCommandRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -24,7 +24,7 @@ static const char *const TAG = "api";
|
||||
void APIServer::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
|
||||
this->setup_controller();
|
||||
socket_ = socket::socket(AF_INET, SOCK_STREAM, 0);
|
||||
socket_ = socket::socket_ip(SOCK_STREAM, 0);
|
||||
if (socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket.");
|
||||
this->mark_failed();
|
||||
@@ -43,13 +43,16 @@ void APIServer::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_in server;
|
||||
memset(&server, 0, sizeof(server));
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = ESPHOME_INADDR_ANY;
|
||||
server.sin_port = htons(this->port_);
|
||||
struct sockaddr_storage server;
|
||||
|
||||
err = socket_->bind((struct sockaddr *) &server, sizeof(server));
|
||||
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_));
|
||||
if (sl == 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
err = socket_->bind((struct sockaddr *) &server, sl);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
||||
this->mark_failed();
|
||||
@@ -80,9 +83,10 @@ void APIServer::setup() {
|
||||
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
|
||||
esp32_camera::global_esp32_camera->add_image_callback(
|
||||
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
|
||||
for (auto &c : this->clients_)
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->remove_)
|
||||
c->send_camera_state(image);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
@@ -188,7 +192,7 @@ void APIServer::on_cover_update(cover::Cover *obj) {
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void APIServer::on_fan_update(fan::FanState *obj) {
|
||||
void APIServer::on_fan_update(fan::Fan *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
@@ -259,6 +263,15 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void APIServer::on_lock_update(lock::Lock *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_lock_state(obj, obj->state);
|
||||
}
|
||||
#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; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -44,7 +44,7 @@ class APIServer : public Component, public Controller {
|
||||
void on_cover_update(cover::Cover *obj) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void on_fan_update(fan::FanState *obj) override;
|
||||
void on_fan_update(fan::Fan *obj) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void on_light_update(light::LightState *obj) override;
|
||||
@@ -66,6 +66,9 @@ class APIServer : public Component, public Controller {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj, const std::string &state) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
#endif
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
|
||||
@@ -12,10 +12,10 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
|
||||
public:
|
||||
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
|
||||
|
||||
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
|
||||
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
|
||||
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
|
||||
|
||||
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
|
||||
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
|
||||
TemplatableStringValue(F f)
|
||||
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_
|
||||
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); }
|
||||
bool ListEntitiesIterator::on_fan(fan::Fan *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); }
|
||||
@@ -35,6 +35,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
|
||||
return this->client_->send_text_sensor_info(text_sensor);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); }
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||
|
||||
@@ -19,7 +19,7 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::FanState *fan) override;
|
||||
bool on_fan(fan::Fan *fan) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
@@ -48,6 +48,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool on_lock(lock::Lock *a_lock) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
|
||||
|
||||
@@ -55,17 +55,19 @@ class ProtoVarInt {
|
||||
}
|
||||
int32_t as_sint32() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1)
|
||||
if (this->value_ & 1) {
|
||||
return static_cast<int32_t>(~(this->value_ >> 1));
|
||||
else
|
||||
} else {
|
||||
return static_cast<int32_t>(this->value_ >> 1);
|
||||
}
|
||||
}
|
||||
int64_t as_sint64() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1)
|
||||
if (this->value_ & 1) {
|
||||
return static_cast<int64_t>(~(this->value_ >> 1));
|
||||
else
|
||||
} else {
|
||||
return static_cast<int64_t>(this->value_ >> 1);
|
||||
}
|
||||
}
|
||||
void encode(std::vector<uint8_t> &out) {
|
||||
uint32_t val = this->value_;
|
||||
@@ -220,10 +222,11 @@ class ProtoWriteBuffer {
|
||||
}
|
||||
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
||||
uint32_t uvalue;
|
||||
if (value < 0)
|
||||
if (value < 0) {
|
||||
uvalue = ~(value << 1);
|
||||
else
|
||||
} 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) {
|
||||
|
||||
@@ -14,7 +14,7 @@ bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_
|
||||
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); }
|
||||
bool InitialStateIterator::on_fan(fan::Fan *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); }
|
||||
@@ -47,6 +47,9 @@ bool InitialStateIterator::on_select(select::Select *select) {
|
||||
return this->client_->send_select_state(select, select->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
|
||||
#endif
|
||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class InitialStateIterator : public ComponentIterator {
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::FanState *fan) override;
|
||||
bool on_fan(fan::Fan *fan) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
@@ -45,6 +45,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool on_lock(lock::Lock *a_lock) override;
|
||||
#endif
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
|
||||
@@ -52,7 +52,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
|
||||
protected:
|
||||
virtual void execute(Ts... x) = 0;
|
||||
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...>) {
|
||||
template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...> type) {
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +212,21 @@ void ComponentIterator::advance() {
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
case IteratorState::LOCK:
|
||||
if (this->at_ >= App.get_locks().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *a_lock = App.get_locks()[this->at_];
|
||||
if (a_lock->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_lock(a_lock);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IteratorState::MAX:
|
||||
if (this->on_end()) {
|
||||
|
||||
@@ -27,7 +27,7 @@ class ComponentIterator {
|
||||
virtual bool on_cover(cover::Cover *cover) = 0;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual bool on_fan(fan::FanState *fan) = 0;
|
||||
virtual bool on_fan(fan::Fan *fan) = 0;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
virtual bool on_light(light::LightState *light) = 0;
|
||||
@@ -56,6 +56,9 @@ class ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual bool on_select(select::Select *select) = 0;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual bool on_lock(lock::Lock *a_lock) = 0;
|
||||
#endif
|
||||
virtual bool on_end();
|
||||
|
||||
@@ -99,6 +102,9 @@ class ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
SELECT,
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
LOCK,
|
||||
#endif
|
||||
MAX,
|
||||
} state_{IteratorState::NONE};
|
||||
|
||||
@@ -58,10 +58,11 @@ void AS3935Component::loop() {
|
||||
|
||||
void AS3935Component::write_indoor(bool indoor) {
|
||||
ESP_LOGV(TAG, "Setting indoor to %d", indoor);
|
||||
if (indoor)
|
||||
if (indoor) {
|
||||
this->write_register(AFE_GAIN, GAIN_MASK, INDOOR, 1);
|
||||
else
|
||||
} 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
|
||||
|
||||
@@ -45,6 +45,8 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
|
||||
this->battery_voltage_->publish_state(*res->battery_voltage);
|
||||
success = true;
|
||||
}
|
||||
if (this->signal_strength_ != nullptr)
|
||||
this->signal_strength_->publish_state(device.get_rssi());
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
|
||||
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; }
|
||||
void set_signal_strength(sensor::Sensor *signal_strength) { signal_strength_ = signal_strength; }
|
||||
|
||||
protected:
|
||||
uint64_t address_;
|
||||
@@ -35,6 +36,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
sensor::Sensor *battery_level_{nullptr};
|
||||
sensor::Sensor *battery_voltage_{nullptr};
|
||||
sensor::Sensor *signal_strength_{nullptr};
|
||||
|
||||
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);
|
||||
|
||||
@@ -6,15 +6,18 @@ from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_HUMIDITY,
|
||||
CONF_SIGNAL_STRENGTH,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_DECIBEL_MILLIWATT,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
@@ -59,6 +62,13 @@ CONFIG_SCHEMA = (
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
@@ -85,3 +95,6 @@ async def to_code(config):
|
||||
if CONF_BATTERY_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
|
||||
cg.add(var.set_battery_voltage(sens))
|
||||
if CONF_SIGNAL_STRENGTH in config:
|
||||
sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH])
|
||||
cg.add(var.set_signal_strength(sens))
|
||||
|
||||
@@ -97,7 +97,7 @@ void BalluClimate::transmit_state() {
|
||||
|
||||
// Send code
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
auto *data = transmit.get_data();
|
||||
|
||||
data->set_carrier_frequency(38000);
|
||||
|
||||
@@ -130,10 +130,10 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
for (int i = 0; i < BALLU_STATE_LENGTH; i++) {
|
||||
// Read bit
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE))
|
||||
if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) {
|
||||
remote_state[i] |= 1 << j;
|
||||
|
||||
else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) {
|
||||
} else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) {
|
||||
ESP_LOGV(TAG, "Byte %d bit %d fail", i, j);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -21,12 +21,13 @@ void BangBangClimate::setup() {
|
||||
restore->to_call(this).perform();
|
||||
} else {
|
||||
// restore from defaults, change_away handles those for us
|
||||
if (supports_cool_ && supports_heat_)
|
||||
if (supports_cool_ && supports_heat_) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
else if (supports_cool_)
|
||||
} else if (supports_cool_) {
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
else if (supports_heat_)
|
||||
} else if (supports_heat_) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
}
|
||||
this->change_away_(false);
|
||||
}
|
||||
}
|
||||
@@ -56,11 +57,12 @@ climate::ClimateTraits BangBangClimate::traits() {
|
||||
if (supports_cool_ && supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||
traits.set_supports_two_point_target_temperature(true);
|
||||
if (supports_away_)
|
||||
if (supports_away_) {
|
||||
traits.set_supported_presets({
|
||||
climate::CLIMATE_PRESET_HOME,
|
||||
climate::CLIMATE_PRESET_AWAY,
|
||||
});
|
||||
}
|
||||
traits.set_supports_action(true);
|
||||
return traits;
|
||||
}
|
||||
@@ -80,21 +82,25 @@ void BangBangClimate::compute_state_() {
|
||||
|
||||
climate::ClimateAction target_action;
|
||||
if (too_cold) {
|
||||
// too cold -> enable heating if possible, else idle
|
||||
if (this->supports_heat_)
|
||||
// too cold -> enable heating if possible and enabled, else idle
|
||||
if (this->supports_heat_ &&
|
||||
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT)) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
else
|
||||
} else {
|
||||
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||
}
|
||||
} else if (too_hot) {
|
||||
// too hot -> enable cooling if possible, else idle
|
||||
if (this->supports_cool_)
|
||||
// too hot -> enable cooling if possible and enabled, else idle
|
||||
if (this->supports_cool_ &&
|
||||
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL)) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
else
|
||||
} 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
|
||||
if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
// if supports both ends and both cooling and heating enabled, go to idle action
|
||||
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||
} else {
|
||||
// else use current mode and don't change (hysteresis)
|
||||
@@ -105,9 +111,10 @@ void BangBangClimate::compute_state_() {
|
||||
this->switch_to_action_(target_action);
|
||||
}
|
||||
void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||
if (action == this->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)) {
|
||||
|
||||
@@ -9,7 +9,7 @@ from esphome.const import (
|
||||
)
|
||||
from .. import binary_ns
|
||||
|
||||
BinaryFan = binary_ns.class_("BinaryFan", cg.Component)
|
||||
BinaryFan = binary_ns.class_("BinaryFan", fan.Fan, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
||||
{
|
||||
@@ -24,9 +24,8 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
await cg.register_component(var, config)
|
||||
await fan.register_fan(var, config)
|
||||
|
||||
fan_ = await fan.create_fan_state(config)
|
||||
cg.add(var.set_fan(fan_))
|
||||
output_ = await cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(output_))
|
||||
|
||||
|
||||
@@ -6,59 +6,35 @@ namespace binary {
|
||||
|
||||
static const char *const 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, 0);
|
||||
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));
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
restore->apply(*this);
|
||||
this->write_state_();
|
||||
}
|
||||
}
|
||||
void BinaryFan::dump_config() { LOG_FAN("", "Binary Fan", this); }
|
||||
fan::FanTraits BinaryFan::get_traits() {
|
||||
return fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0);
|
||||
}
|
||||
void BinaryFan::control(const fan::FanCall &call) {
|
||||
if (call.get_state().has_value())
|
||||
this->state = *call.get_state();
|
||||
if (call.get_oscillating().has_value())
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
|
||||
// We need a higher priority than the FanState component to make sure that the traits are set
|
||||
// when that component sets itself up.
|
||||
float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; }
|
||||
this->write_state_();
|
||||
this->publish_state();
|
||||
}
|
||||
void BinaryFan::write_state_() {
|
||||
this->output_->set_state(this->state);
|
||||
if (this->oscillating_ != nullptr)
|
||||
this->oscillating_->set_state(this->oscillating);
|
||||
if (this->direction_ != nullptr)
|
||||
this->direction_->set_state(this->direction == fan::FanDirection::REVERSE);
|
||||
}
|
||||
|
||||
} // namespace binary
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,28 +2,29 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/fan/fan_state.h"
|
||||
#include "esphome/components/fan/fan.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary {
|
||||
|
||||
class BinaryFan : public Component {
|
||||
class BinaryFan : public Component, public fan::Fan {
|
||||
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_output(output::BinaryOutput *output) { this->output_ = output; }
|
||||
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
|
||||
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
|
||||
|
||||
fan::FanTraits get_traits() override;
|
||||
|
||||
protected:
|
||||
fan::FanState *fan_;
|
||||
void control(const fan::FanCall &call) override;
|
||||
void write_state_();
|
||||
|
||||
output::BinaryOutput *output_;
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
output::BinaryOutput *direction_{nullptr};
|
||||
bool next_update_{true};
|
||||
};
|
||||
|
||||
} // namespace binary
|
||||
|
||||
@@ -48,7 +48,10 @@ void BinarySensor::set_device_class(const std::string &device_class) { this->dev
|
||||
std::string BinarySensor::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
return this->device_class();
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
void BinarySensor::add_filter(Filter *filter) {
|
||||
filter->parent_ = this;
|
||||
|
||||
@@ -74,7 +74,10 @@ class BinarySensor : public EntityBase {
|
||||
|
||||
// ========== 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.
|
||||
/** Override this to set the default device class.
|
||||
*
|
||||
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
|
||||
*/
|
||||
virtual std::string device_class();
|
||||
|
||||
protected:
|
||||
|
||||
1
esphome/components/bl0940/__init__.py
Normal file
1
esphome/components/bl0940/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@tobias-"]
|
||||
137
esphome/components/bl0940/bl0940.cpp
Normal file
137
esphome/components/bl0940/bl0940.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "bl0940.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0940 {
|
||||
|
||||
static const char *const TAG = "bl0940";
|
||||
|
||||
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
|
||||
static const uint8_t BL0940_FULL_PACKET = 0xAA;
|
||||
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
|
||||
|
||||
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
|
||||
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
|
||||
static const uint8_t BL0940_REG_MODE = 0x18;
|
||||
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
|
||||
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
|
||||
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
|
||||
|
||||
const uint8_t BL0940_INIT[5][6] = {
|
||||
// Reset to default
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
||||
// Enable User Operation Write
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
||||
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
||||
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
||||
// 0x181C = Half cycle, Fast RMS threshold 6172
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
||||
|
||||
void BL0940::loop() {
|
||||
DataPacket buffer;
|
||||
if (!this->available()) {
|
||||
return;
|
||||
}
|
||||
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
|
||||
if (validate_checksum(&buffer)) {
|
||||
received_package_(&buffer);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
|
||||
while (read() >= 0)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
bool BL0940::validate_checksum(const DataPacket *data) {
|
||||
uint8_t checksum = BL0940_READ_COMMAND;
|
||||
// Whole package but checksum
|
||||
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
|
||||
checksum += data->raw[i];
|
||||
}
|
||||
checksum ^= 0xFF;
|
||||
if (checksum != data->checksum) {
|
||||
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
||||
}
|
||||
return checksum == data->checksum;
|
||||
}
|
||||
|
||||
void BL0940::update() {
|
||||
this->flush();
|
||||
this->write_byte(BL0940_READ_COMMAND);
|
||||
this->write_byte(BL0940_FULL_PACKET);
|
||||
}
|
||||
|
||||
void BL0940::setup() {
|
||||
for (auto *i : BL0940_INIT) {
|
||||
this->write_array(i, 6);
|
||||
delay(1);
|
||||
}
|
||||
this->flush();
|
||||
}
|
||||
|
||||
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
|
||||
auto tb = (float) (temperature.h << 8 | temperature.l);
|
||||
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
|
||||
if (sensor != nullptr) {
|
||||
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
|
||||
ESP_LOGD("bl0940", "Invalid temperature change. Sensor: '%s', Old temperature: %f, New temperature: %f",
|
||||
sensor->get_name().c_str(), sensor->get_state(), converted_temp);
|
||||
return 0.0f;
|
||||
}
|
||||
sensor->publish_state(converted_temp);
|
||||
}
|
||||
return converted_temp;
|
||||
}
|
||||
|
||||
void BL0940::received_package_(const DataPacket *data) const {
|
||||
// Bad header
|
||||
if (data->frame_header != BL0940_PACKET_HEADER) {
|
||||
ESP_LOGI("bl0940", "Invalid data. Header mismatch: %d", data->frame_header);
|
||||
return;
|
||||
}
|
||||
|
||||
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
|
||||
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
|
||||
float watt = (float) to_int32_t(data->watt) / power_reference_;
|
||||
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
|
||||
float total_energy_consumption = (float) cf_cnt / energy_reference_;
|
||||
|
||||
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
|
||||
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
|
||||
|
||||
if (voltage_sensor_ != nullptr) {
|
||||
voltage_sensor_->publish_state(v_rms);
|
||||
}
|
||||
if (current_sensor_ != nullptr) {
|
||||
current_sensor_->publish_state(i_rms);
|
||||
}
|
||||
if (power_sensor_ != nullptr) {
|
||||
power_sensor_->publish_state(watt);
|
||||
}
|
||||
if (energy_sensor_ != nullptr) {
|
||||
energy_sensor_->publish_state(total_energy_consumption);
|
||||
}
|
||||
|
||||
ESP_LOGV("bl0940", "BL0940: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
|
||||
total_energy_consumption, tps1, tps2);
|
||||
}
|
||||
|
||||
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
|
||||
ESP_LOGCONFIG(TAG, "BL0940:");
|
||||
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR("", "Current", this->current_sensor_);
|
||||
LOG_SENSOR("", "Power", this->power_sensor_);
|
||||
LOG_SENSOR("", "Energy", this->energy_sensor_);
|
||||
LOG_SENSOR("", "Internal temperature", this->internal_temperature_sensor_);
|
||||
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
|
||||
}
|
||||
|
||||
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
||||
|
||||
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
||||
|
||||
} // namespace bl0940
|
||||
} // namespace esphome
|
||||
109
esphome/components/bl0940/bl0940.h
Normal file
109
esphome/components/bl0940/bl0940.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0940 {
|
||||
|
||||
static const float BL0940_PREF = 1430;
|
||||
static const float BL0940_UREF = 33000;
|
||||
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
|
||||
|
||||
// Measured to 297J per click according to power consumption of 5 minutes
|
||||
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
|
||||
static const float BL0940_EREF = 3.6e6 / 297;
|
||||
|
||||
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||
uint8_t l;
|
||||
uint8_t m;
|
||||
uint8_t h;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||
uint8_t l;
|
||||
uint8_t h;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||
uint8_t l;
|
||||
uint8_t m;
|
||||
int8_t h;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Caveat: All these values are big endian (low - middle - high)
|
||||
|
||||
union DataPacket { // NOLINT(altera-struct-pack-align)
|
||||
uint8_t raw[35];
|
||||
struct {
|
||||
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
|
||||
ube24_t i_fast_rms; // 0x00
|
||||
ube24_t i_rms; // 0x04
|
||||
ube24_t RESERVED0; // reserved
|
||||
ube24_t v_rms; // 0x06
|
||||
ube24_t RESERVED1; // reserved
|
||||
sbe24_t watt; // 0x08
|
||||
ube24_t RESERVED2; // reserved
|
||||
ube24_t cf_cnt; // 0x0A
|
||||
ube24_t RESERVED3; // reserved
|
||||
ube16_t tps1; // 0x0c
|
||||
uint8_t RESERVED4; // value of 0x00
|
||||
ube16_t tps2; // 0x0c
|
||||
uint8_t RESERVED5; // value of 0x00
|
||||
uint8_t checksum; // checksum
|
||||
};
|
||||
} __attribute__((packed));
|
||||
|
||||
class BL0940 : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
|
||||
internal_temperature_sensor_ = internal_temperature_sensor;
|
||||
}
|
||||
void set_external_temperature_sensor(sensor::Sensor *external_temperature_sensor) {
|
||||
external_temperature_sensor_ = external_temperature_sensor;
|
||||
}
|
||||
|
||||
void loop() override;
|
||||
|
||||
void update() override;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_;
|
||||
sensor::Sensor *current_sensor_;
|
||||
// NB This may be negative as the circuits is seemingly able to measure
|
||||
// power in both directions
|
||||
sensor::Sensor *power_sensor_;
|
||||
sensor::Sensor *energy_sensor_;
|
||||
sensor::Sensor *internal_temperature_sensor_;
|
||||
sensor::Sensor *external_temperature_sensor_;
|
||||
|
||||
// Max difference between two measurements of the temperature. Used to avoid noise.
|
||||
float max_temperature_diff_{0};
|
||||
// Divide by this to turn into Watt
|
||||
float power_reference_ = BL0940_PREF;
|
||||
// Divide by this to turn into Volt
|
||||
float voltage_reference_ = BL0940_UREF;
|
||||
// Divide by this to turn into Ampere
|
||||
float current_reference_ = BL0940_IREF;
|
||||
// Divide by this to turn into kWh
|
||||
float energy_reference_ = BL0940_EREF;
|
||||
|
||||
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
|
||||
|
||||
static uint32_t to_uint32_t(ube24_t input);
|
||||
|
||||
static int32_t to_int32_t(sbe24_t input);
|
||||
|
||||
static bool validate_checksum(const DataPacket *data);
|
||||
|
||||
void received_package_(const DataPacket *data) const;
|
||||
};
|
||||
} // namespace bl0940
|
||||
} // namespace esphome
|
||||
106
esphome/components/bl0940/sensor.py
Normal file
106
esphome/components/bl0940/sensor.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_CURRENT,
|
||||
CONF_ENERGY,
|
||||
CONF_ID,
|
||||
CONF_POWER,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
|
||||
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
|
||||
|
||||
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
||||
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BL0940),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
UNIT_AMPERE,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
|
||||
UNIT_KILOWATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema(
|
||||
UNIT_CELSIUS,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
if CONF_ENERGY in config:
|
||||
conf = config[CONF_ENERGY]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
if CONF_INTERNAL_TEMPERATURE in config:
|
||||
conf = config[CONF_INTERNAL_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_internal_temperature_sensor(sens))
|
||||
if CONF_EXTERNAL_TEMPERATURE in config:
|
||||
conf = config[CONF_EXTERNAL_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_external_temperature_sensor(sens))
|
||||
@@ -167,7 +167,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
auto descr = this->get_config_descriptor(param->reg_for_notify.handle);
|
||||
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
|
||||
if (descr == nullptr) {
|
||||
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
|
||||
break;
|
||||
@@ -252,16 +252,17 @@ float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
}
|
||||
|
||||
BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) {
|
||||
for (auto svc : this->services_)
|
||||
for (auto *svc : this->services_) {
|
||||
if (svc->uuid == uuid)
|
||||
return svc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
|
||||
|
||||
BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
|
||||
auto svc = this->get_service(service);
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
return svc->get_characteristic(chr);
|
||||
@@ -272,19 +273,24 @@ BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr)
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) {
|
||||
for (auto &svc : this->services_)
|
||||
for (auto &chr : svc->characteristics)
|
||||
if (chr->handle == handle)
|
||||
for (auto &desc : chr->descriptors)
|
||||
for (auto &svc : this->services_) {
|
||||
for (auto &chr : svc->characteristics) {
|
||||
if (chr->handle == handle) {
|
||||
for (auto &desc : chr->descriptors) {
|
||||
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
|
||||
for (auto &chr : this->characteristics)
|
||||
for (auto &chr : this->characteristics) {
|
||||
if (chr->uuid == uuid)
|
||||
return chr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -293,10 +299,10 @@ BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
|
||||
auto svc = this->get_service(service);
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
auto ch = svc->get_characteristic(chr);
|
||||
auto *ch = svc->get_characteristic(chr);
|
||||
if (ch == nullptr)
|
||||
return nullptr;
|
||||
return ch->get_descriptor(descr);
|
||||
@@ -379,24 +385,29 @@ void BLECharacteristic::parse_descriptors() {
|
||||
}
|
||||
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
|
||||
for (auto &desc : this->descriptors)
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->uuid == uuid)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
||||
auto client = this->service->client;
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
|
||||
auto *client = this->service->client;
|
||||
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
||||
write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ class BLECharacteristic {
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
void write_value(uint8_t *new_val, int16_t new_val_size);
|
||||
void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
@@ -126,9 +127,10 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
bool all_nodes_established_() {
|
||||
if (this->state() != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
for (auto &node : nodes_)
|
||||
for (auto &node : nodes_) {
|
||||
if (node->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output, ble_client, esp32_ble_tracker
|
||||
from esphome.components import ble_client, esp32_ble_tracker, output
|
||||
from esphome.const import CONF_ID, CONF_SERVICE_UUID
|
||||
from .. import ble_client_ns
|
||||
|
||||
from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
CONF_REQUIRE_RESPONSE = "require_response"
|
||||
|
||||
BLEBinaryOutput = ble_client_ns.class_(
|
||||
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
|
||||
@@ -19,6 +20,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_REQUIRE_RESPONSE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -61,7 +63,7 @@ def to_code(config):
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
||||
yield output.register_output(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
@@ -32,7 +32,7 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i
|
||||
break;
|
||||
}
|
||||
|
||||
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
|
||||
break;
|
||||
@@ -54,7 +54,7 @@ void BLEBinaryOutput::write_state(bool state) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
|
||||
this->char_uuid_.to_string().c_str());
|
||||
@@ -63,7 +63,11 @@ void BLEBinaryOutput::write_state(bool state) {
|
||||
|
||||
uint8_t state_as_uint = (uint8_t) state;
|
||||
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint));
|
||||
if (this->require_response_) {
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP);
|
||||
} else {
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
|
||||
@@ -25,9 +25,11 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void set_require_response(bool response) { this->require_response_ = response; }
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
bool require_response_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
espbt::ClientState client_state_;
|
||||
|
||||
@@ -43,7 +43,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle = 0;
|
||||
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
@@ -53,7 +53,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
}
|
||||
this->handle = chr->handle;
|
||||
if (this->descr_uuid_.get_uuid().len > 0) {
|
||||
auto descr = chr->get_descriptor(this->descr_uuid_);
|
||||
auto *descr = chr->get_descriptor(this->descr_uuid_);
|
||||
if (descr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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"]
|
||||
|
||||
@@ -14,18 +13,13 @@ BLEScanner = ble_scanner_ns.class_(
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEScanner),
|
||||
}
|
||||
)
|
||||
text_sensor.text_sensor_schema(klass=BLEScanner)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
@@ -214,9 +214,10 @@ void BME280Component::update() {
|
||||
float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
|
||||
int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
if (adc == 0x80000) {
|
||||
// temperature was disabled
|
||||
return NAN;
|
||||
}
|
||||
|
||||
const int32_t t1 = this->calibration_.t1;
|
||||
const int32_t t2 = this->calibration_.t2;
|
||||
@@ -233,9 +234,10 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
|
||||
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
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;
|
||||
|
||||
@@ -95,7 +95,7 @@ void BME680Component::setup() {
|
||||
this->calibration_.t3 = cal1[3];
|
||||
|
||||
this->calibration_.h1 = cal2[2] << 4 | (cal2[1] & 0x0F);
|
||||
this->calibration_.h2 = cal2[0] << 4 | cal2[1];
|
||||
this->calibration_.h2 = cal2[0] << 4 | cal2[1] >> 4;
|
||||
this->calibration_.h3 = cal2[3];
|
||||
this->calibration_.h4 = cal2[4];
|
||||
this->calibration_.h5 = cal2[5];
|
||||
@@ -420,10 +420,11 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) {
|
||||
|
||||
calc_hum = var2 + (var3 + var4 * temp_comp) * var2 * var2;
|
||||
|
||||
if (calc_hum > 100.0f)
|
||||
if (calc_hum > 100.0f) {
|
||||
calc_hum = 100.0f;
|
||||
else if (calc_hum < 0.0f)
|
||||
} else if (calc_hum < 0.0f) {
|
||||
calc_hum = 0.0f;
|
||||
}
|
||||
|
||||
return calc_hum;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ID, CONF_ICON
|
||||
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
|
||||
|
||||
DEPENDENCIES = ["bme680_bsec"]
|
||||
@@ -14,11 +13,8 @@ TYPES = [CONF_IAQ_ACCURACY]
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema(
|
||||
icon=ICON_ACCURACY
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -27,8 +23,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, conf)
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
|
||||
|
||||
|
||||
|
||||
@@ -161,9 +161,10 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
if (adc == 0x80000) {
|
||||
// temperature was disabled
|
||||
return NAN;
|
||||
}
|
||||
|
||||
const int32_t t1 = this->calibration_.t1;
|
||||
const int32_t t2 = this->calibration_.t2;
|
||||
@@ -183,9 +184,10 @@ float BMP280Component::read_pressure_(int32_t t_fine) {
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000)
|
||||
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;
|
||||
|
||||
0
esphome/components/bmp3xx/__init__.py
Normal file
0
esphome/components/bmp3xx/__init__.py
Normal file
388
esphome/components/bmp3xx/bmp3xx.cpp
Normal file
388
esphome/components/bmp3xx/bmp3xx.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
based on BMP388_DEV by Martin Lindupp
|
||||
under MIT License (MIT)
|
||||
Copyright (C) Martin Lindupp 2020
|
||||
http://github.com/MartinL1/BMP388_DEV
|
||||
*/
|
||||
|
||||
#include "bmp3xx.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp3xx {
|
||||
|
||||
static const char *const TAG = "bmp3xx.sensor";
|
||||
|
||||
static const LogString *chip_type_to_str(uint8_t chip_type) {
|
||||
switch (chip_type) {
|
||||
case BMP388_ID:
|
||||
return LOG_STR("BMP 388");
|
||||
case BMP390_ID:
|
||||
return LOG_STR("BMP 390");
|
||||
default:
|
||||
return LOG_STR("Unknown Chip Type");
|
||||
}
|
||||
}
|
||||
|
||||
static const LogString *oversampling_to_str(Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case Oversampling::OVERSAMPLING_NONE:
|
||||
return LOG_STR("None");
|
||||
case Oversampling::OVERSAMPLING_X2:
|
||||
return LOG_STR("2x");
|
||||
case Oversampling::OVERSAMPLING_X4:
|
||||
return LOG_STR("4x");
|
||||
case Oversampling::OVERSAMPLING_X8:
|
||||
return LOG_STR("8x");
|
||||
case Oversampling::OVERSAMPLING_X16:
|
||||
return LOG_STR("16x");
|
||||
case Oversampling::OVERSAMPLING_X32:
|
||||
return LOG_STR("32x");
|
||||
default:
|
||||
return LOG_STR("");
|
||||
}
|
||||
}
|
||||
|
||||
static const LogString *iir_filter_to_str(IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case IIRFilter::IIR_FILTER_OFF:
|
||||
return LOG_STR("OFF");
|
||||
case IIRFilter::IIR_FILTER_2:
|
||||
return LOG_STR("2x");
|
||||
case IIRFilter::IIR_FILTER_4:
|
||||
return LOG_STR("4x");
|
||||
case IIRFilter::IIR_FILTER_8:
|
||||
return LOG_STR("8x");
|
||||
case IIRFilter::IIR_FILTER_16:
|
||||
return LOG_STR("16x");
|
||||
case IIRFilter::IIR_FILTER_32:
|
||||
return LOG_STR("32x");
|
||||
case IIRFilter::IIR_FILTER_64:
|
||||
return LOG_STR("64x");
|
||||
case IIRFilter::IIR_FILTER_128:
|
||||
return LOG_STR("128x");
|
||||
default:
|
||||
return LOG_STR("");
|
||||
}
|
||||
}
|
||||
|
||||
void BMP3XXComponent::setup() {
|
||||
this->error_code_ = NONE;
|
||||
ESP_LOGCONFIG(TAG, "Setting up BMP3XX...");
|
||||
// Call the Device base class "initialise" function
|
||||
if (!reset()) {
|
||||
ESP_LOGE(TAG, "Failed to reset BMP3XX...");
|
||||
this->error_code_ = ERROR_SENSOR_RESET;
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
if (!read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)) {
|
||||
ESP_LOGE(TAG, "Can't read chip id");
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "Chip %s Id 0x%X", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
|
||||
|
||||
if (chip_id_.reg != BMP388_ID && chip_id_.reg != BMP390_ID) {
|
||||
ESP_LOGE(TAG, "Unknown chip id - is this really a BMP388 or BMP390?");
|
||||
this->error_code_ = ERROR_WRONG_CHIP_ID;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// set sensor in sleep mode
|
||||
stop_conversion();
|
||||
// Read the calibration parameters into the params structure
|
||||
if (!read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_))) {
|
||||
ESP_LOGE(TAG, "Can't read calibration data");
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
compensation_float_params_.param_T1 =
|
||||
(float) compensation_params_.param_T1 / powf(2.0f, -8.0f); // Calculate the floating point trim parameters
|
||||
compensation_float_params_.param_T2 = (float) compensation_params_.param_T2 / powf(2.0f, 30.0f);
|
||||
compensation_float_params_.param_T3 = (float) compensation_params_.param_T3 / powf(2.0f, 48.0f);
|
||||
compensation_float_params_.param_P1 = ((float) compensation_params_.param_P1 - powf(2.0f, 14.0f)) / powf(2.0f, 20.0f);
|
||||
compensation_float_params_.param_P2 = ((float) compensation_params_.param_P2 - powf(2.0f, 14.0f)) / powf(2.0f, 29.0f);
|
||||
compensation_float_params_.param_P3 = (float) compensation_params_.param_P3 / powf(2.0f, 32.0f);
|
||||
compensation_float_params_.param_P4 = (float) compensation_params_.param_P4 / powf(2.0f, 37.0f);
|
||||
compensation_float_params_.param_P5 = (float) compensation_params_.param_P5 / powf(2.0f, -3.0f);
|
||||
compensation_float_params_.param_P6 = (float) compensation_params_.param_P6 / powf(2.0f, 6.0f);
|
||||
compensation_float_params_.param_P7 = (float) compensation_params_.param_P7 / powf(2.0f, 8.0f);
|
||||
compensation_float_params_.param_P8 = (float) compensation_params_.param_P8 / powf(2.0f, 15.0f);
|
||||
compensation_float_params_.param_P9 = (float) compensation_params_.param_P9 / powf(2.0f, 48.0f);
|
||||
compensation_float_params_.param_P10 = (float) compensation_params_.param_P10 / powf(2.0f, 48.0f);
|
||||
compensation_float_params_.param_P11 = (float) compensation_params_.param_P11 / powf(2.0f, 65.0f);
|
||||
|
||||
// Initialise the BMP388 IIR filter register
|
||||
if (!set_iir_filter(this->iir_filter_)) {
|
||||
ESP_LOGE(TAG, "Failed to set IIR filter");
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set power control registers
|
||||
pwr_ctrl_.bit.press_en = 1;
|
||||
pwr_ctrl_.bit.temp_en = 1;
|
||||
// Disable pressure if no sensor defined
|
||||
// keep temperature enabled since it's needed for compensation
|
||||
if (this->pressure_sensor_ == nullptr) {
|
||||
pwr_ctrl_.bit.press_en = 0;
|
||||
this->pressure_oversampling_ = OVERSAMPLING_NONE;
|
||||
}
|
||||
// just disable oeversampling for temp if not used
|
||||
if (this->temperature_sensor_ == nullptr) {
|
||||
this->temperature_oversampling_ = OVERSAMPLING_NONE;
|
||||
}
|
||||
// Initialise the BMP388 oversampling register
|
||||
if (!set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_)) {
|
||||
ESP_LOGE(TAG, "Failed to set oversampling register");
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BMP3XXComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BMP3XX:");
|
||||
ESP_LOGCONFIG(TAG, " Type: %s (0x%X)", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
|
||||
LOG_I2C_DEVICE(this);
|
||||
switch (this->error_code_) {
|
||||
case NONE:
|
||||
break;
|
||||
case ERROR_COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with BMP3XX failed!");
|
||||
break;
|
||||
case ERROR_WRONG_CHIP_ID:
|
||||
ESP_LOGE(
|
||||
TAG,
|
||||
"BMP3XX has wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390",
|
||||
this->chip_id_.reg);
|
||||
break;
|
||||
case ERROR_SENSOR_RESET:
|
||||
ESP_LOGE(TAG, "BMP3XX failed to reset");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "BMP3XX error code %d", (int) this->error_code_);
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_filter_)));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->temperature_sensor_) {
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
|
||||
}
|
||||
if (this->pressure_sensor_) {
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
|
||||
}
|
||||
}
|
||||
float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); }
|
||||
|
||||
void BMP3XXComponent::update() {
|
||||
// Enable sensor
|
||||
ESP_LOGV(TAG, "Sending conversion request...");
|
||||
float meas_time = 1.0f;
|
||||
// Ref: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf 3.9.2
|
||||
meas_time += 2.02f * oversampling_to_time(this->temperature_oversampling_) + 0.163f;
|
||||
meas_time += 2.02f * oversampling_to_time(this->pressure_oversampling_) + 0.392f;
|
||||
meas_time += 0.234f;
|
||||
if (!set_mode(FORCED_MODE)) {
|
||||
ESP_LOGE(TAG, "Failed start forced mode");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "measurement time %d", uint32_t(ceilf(meas_time)));
|
||||
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||
float temperature = 0.0f;
|
||||
float pressure = 0.0f;
|
||||
if (this->pressure_sensor_ != nullptr) {
|
||||
if (!get_measurements(temperature, pressure)) {
|
||||
ESP_LOGW(TAG, "Failed to read pressure and temperature - skipping update");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
|
||||
} else {
|
||||
if (!get_temperature(temperature)) {
|
||||
ESP_LOGW(TAG, "Failed to read temperature - skipping update");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C", temperature);
|
||||
}
|
||||
|
||||
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();
|
||||
set_mode(SLEEP_MODE);
|
||||
});
|
||||
}
|
||||
|
||||
// Reset the BMP3XX
|
||||
uint8_t BMP3XXComponent::reset() {
|
||||
write_byte(BMP388_CMD, RESET_CODE); // Write the reset code to the command register
|
||||
// Wait for 10ms
|
||||
delay(10);
|
||||
this->read_byte(BMP388_EVENT, &event_.reg); // Read the BMP388's event register
|
||||
return event_.bit.por_detected; // Return if device reset is complete
|
||||
}
|
||||
|
||||
// Start a one shot measurement in FORCED_MODE
|
||||
bool BMP3XXComponent::start_forced_conversion() {
|
||||
// Only set FORCED_MODE if we're already in SLEEP_MODE
|
||||
if (pwr_ctrl_.bit.mode == SLEEP_MODE) {
|
||||
return set_mode(FORCED_MODE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop the conversion and return to SLEEP_MODE
|
||||
bool BMP3XXComponent::stop_conversion() { return set_mode(SLEEP_MODE); }
|
||||
|
||||
// Set the pressure oversampling rate
|
||||
bool BMP3XXComponent::set_pressure_oversampling(Oversampling oversampling) {
|
||||
osr_.bit.osr_p = oversampling;
|
||||
return this->write_byte(BMP388_OSR, osr_.reg);
|
||||
}
|
||||
|
||||
// Set the temperature oversampling rate
|
||||
bool BMP3XXComponent::set_temperature_oversampling(Oversampling oversampling) {
|
||||
osr_.bit.osr_t = oversampling;
|
||||
return this->write_byte(BMP388_OSR, osr_.reg);
|
||||
}
|
||||
|
||||
// Set the IIR filter setting
|
||||
bool BMP3XXComponent::set_iir_filter(IIRFilter iir_filter) {
|
||||
config_.bit.iir_filter = iir_filter;
|
||||
return this->write_byte(BMP388_CONFIG, config_.reg);
|
||||
}
|
||||
|
||||
// Get temperature
|
||||
bool BMP3XXComponent::get_temperature(float &temperature) {
|
||||
// Check if a measurement is ready
|
||||
if (!data_ready()) {
|
||||
return false;
|
||||
}
|
||||
uint8_t data[3];
|
||||
// Read the temperature
|
||||
if (!this->read_bytes(BMP388_DATA_3, &data[0], 3)) {
|
||||
ESP_LOGE(TAG, "Failed to read temperature");
|
||||
return false;
|
||||
}
|
||||
// Copy the temperature data into the adc variables
|
||||
int32_t adc_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
|
||||
// Temperature compensation (function from BMP388 datasheet)
|
||||
temperature = bmp388_compensate_temperature_((float) adc_temp);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the pressure
|
||||
bool BMP3XXComponent::get_pressure(float &pressure) {
|
||||
float temperature;
|
||||
return get_measurements(temperature, pressure);
|
||||
}
|
||||
|
||||
// Get temperature and pressure
|
||||
bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) {
|
||||
// Check if a measurement is ready
|
||||
if (!data_ready()) {
|
||||
ESP_LOGD(TAG, "BMP3XX Get measurement - data not ready skipping update");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[6];
|
||||
// Read the temperature and pressure data
|
||||
if (!this->read_bytes(BMP388_DATA_0, &data[0], 6)) {
|
||||
ESP_LOGE(TAG, "Failed to read measurements");
|
||||
return false;
|
||||
}
|
||||
// Copy the temperature and pressure data into the adc variables
|
||||
int32_t adc_pres = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
|
||||
int32_t adc_temp = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
|
||||
|
||||
// Temperature compensation (function from BMP388 datasheet)
|
||||
temperature = bmp388_compensate_temperature_((float) adc_temp);
|
||||
// Pressure compensation (function from BMP388 datasheet)
|
||||
pressure = bmp388_compensate_pressure_((float) adc_pres, temperature);
|
||||
// Calculate the pressure in millibar/hPa
|
||||
pressure /= 100.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the BMP388's mode in the power control register
|
||||
bool BMP3XXComponent::set_mode(OperationMode mode) {
|
||||
pwr_ctrl_.bit.mode = mode;
|
||||
return this->write_byte(BMP388_PWR_CTRL, pwr_ctrl_.reg);
|
||||
}
|
||||
|
||||
// Set the BMP388 oversampling register
|
||||
bool BMP3XXComponent::set_oversampling_register(Oversampling pressure_oversampling,
|
||||
Oversampling temperature_oversampling) {
|
||||
osr_.reg = temperature_oversampling << 3 | pressure_oversampling;
|
||||
return this->write_byte(BMP388_OSR, osr_.reg);
|
||||
}
|
||||
|
||||
// Check if measurement data is ready
|
||||
bool BMP3XXComponent::data_ready() {
|
||||
// If we're in SLEEP_MODE return immediately
|
||||
if (pwr_ctrl_.bit.mode == SLEEP_MODE) {
|
||||
ESP_LOGD(TAG, "Not ready - sensor is in sleep mode");
|
||||
return false;
|
||||
}
|
||||
// Read the interrupt status register
|
||||
uint8_t status;
|
||||
if (!this->read_byte(BMP388_INT_STATUS, &status)) {
|
||||
ESP_LOGE(TAG, "Failed to read status register");
|
||||
return false;
|
||||
}
|
||||
int_status_.reg = status;
|
||||
ESP_LOGVV(TAG, "data ready status %d", status);
|
||||
// If we're in FORCED_MODE switch back to SLEEP_MODE
|
||||
if (int_status_.bit.drdy) {
|
||||
if (pwr_ctrl_.bit.mode == FORCED_MODE) {
|
||||
pwr_ctrl_.bit.mode = SLEEP_MODE;
|
||||
}
|
||||
return true; // The measurement is ready
|
||||
}
|
||||
return false; // The measurement is still pending
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Bosch BMP3XXComponent (Private) Member Functions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
float BMP3XXComponent::bmp388_compensate_temperature_(float uncomp_temp) {
|
||||
float partial_data1 = uncomp_temp - compensation_float_params_.param_T1;
|
||||
float partial_data2 = partial_data1 * compensation_float_params_.param_T2;
|
||||
return partial_data2 + partial_data1 * partial_data1 * compensation_float_params_.param_T3;
|
||||
}
|
||||
|
||||
float BMP3XXComponent::bmp388_compensate_pressure_(float uncomp_press, float t_lin) {
|
||||
float partial_data1 = compensation_float_params_.param_P6 * t_lin;
|
||||
float partial_data2 = compensation_float_params_.param_P7 * t_lin * t_lin;
|
||||
float partial_data3 = compensation_float_params_.param_P8 * t_lin * t_lin * t_lin;
|
||||
float partial_out1 = compensation_float_params_.param_P5 + partial_data1 + partial_data2 + partial_data3;
|
||||
partial_data1 = compensation_float_params_.param_P2 * t_lin;
|
||||
partial_data2 = compensation_float_params_.param_P3 * t_lin * t_lin;
|
||||
partial_data3 = compensation_float_params_.param_P4 * t_lin * t_lin * t_lin;
|
||||
float partial_out2 =
|
||||
uncomp_press * (compensation_float_params_.param_P1 + partial_data1 + partial_data2 + partial_data3);
|
||||
partial_data1 = uncomp_press * uncomp_press;
|
||||
partial_data2 = compensation_float_params_.param_P9 + compensation_float_params_.param_P10 * t_lin;
|
||||
partial_data3 = partial_data1 * partial_data2;
|
||||
float partial_data4 =
|
||||
partial_data3 + uncomp_press * uncomp_press * uncomp_press * compensation_float_params_.param_P11;
|
||||
return partial_out1 + partial_out2 + partial_data4;
|
||||
}
|
||||
|
||||
} // namespace bmp3xx
|
||||
} // namespace esphome
|
||||
237
esphome/components/bmp3xx/bmp3xx.h
Normal file
237
esphome/components/bmp3xx/bmp3xx.h
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
based on BMP388_DEV by Martin Lindupp
|
||||
under MIT License (MIT)
|
||||
Copyright (C) Martin Lindupp 2020
|
||||
http://github.com/MartinL1/BMP388_DEV
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp3xx {
|
||||
|
||||
static const uint8_t BMP388_ID = 0x50; // The BMP388 device ID
|
||||
static const uint8_t BMP390_ID = 0x60; // The BMP390 device ID
|
||||
static const uint8_t RESET_CODE = 0xB6; // The BMP388 reset code
|
||||
|
||||
/// BMP388_DEV Registers
|
||||
enum {
|
||||
BMP388_CHIP_ID = 0x00, // Chip ID register sub-address
|
||||
BMP388_ERR_REG = 0x02, // Error register sub-address
|
||||
BMP388_STATUS = 0x03, // Status register sub-address
|
||||
BMP388_DATA_0 = 0x04, // Pressure eXtended Least Significant Byte (XLSB) register sub-address
|
||||
BMP388_DATA_1 = 0x05, // Pressure Least Significant Byte (LSB) register sub-address
|
||||
BMP388_DATA_2 = 0x06, // Pressure Most Significant Byte (MSB) register sub-address
|
||||
BMP388_DATA_3 = 0x07, // Temperature eXtended Least Significant Byte (XLSB) register sub-address
|
||||
BMP388_DATA_4 = 0x08, // Temperature Least Significant Byte (LSB) register sub-address
|
||||
BMP388_DATA_5 = 0x09, // Temperature Most Significant Byte (MSB) register sub-address
|
||||
BMP388_SENSORTIME_0 = 0x0C, // Sensor time register 0 sub-address
|
||||
BMP388_SENSORTIME_1 = 0x0D, // Sensor time register 1 sub-address
|
||||
BMP388_SENSORTIME_2 = 0x0E, // Sensor time register 2 sub-address
|
||||
BMP388_EVENT = 0x10, // Event register sub-address
|
||||
BMP388_INT_STATUS = 0x11, // Interrupt Status register sub-address
|
||||
BMP388_INT_CTRL = 0x19, // Interrupt Control register sub-address
|
||||
BMP388_IF_CONFIG = 0x1A, // Interface Configuration register sub-address
|
||||
BMP388_PWR_CTRL = 0x1B, // Power Control register sub-address
|
||||
BMP388_OSR = 0x1C, // Oversampling register sub-address
|
||||
BMP388_ODR = 0x1D, // Output Data Rate register sub-address
|
||||
BMP388_CONFIG = 0x1F, // Configuration register sub-address
|
||||
BMP388_TRIM_PARAMS = 0x31, // Trim parameter registers' base sub-address
|
||||
BMP388_CMD = 0x7E // Command register sub-address
|
||||
};
|
||||
|
||||
/// Device mode bitfield in the control and measurement register
|
||||
enum OperationMode { SLEEP_MODE = 0x00, FORCED_MODE = 0x01, NORMAL_MODE = 0x03 };
|
||||
|
||||
/// Oversampling bit fields in the control and measurement register
|
||||
enum Oversampling {
|
||||
OVERSAMPLING_NONE = 0x00,
|
||||
OVERSAMPLING_X2 = 0x01,
|
||||
OVERSAMPLING_X4 = 0x02,
|
||||
OVERSAMPLING_X8 = 0x03,
|
||||
OVERSAMPLING_X16 = 0x04,
|
||||
OVERSAMPLING_X32 = 0x05
|
||||
};
|
||||
|
||||
/// Infinite Impulse Response (IIR) filter bit field in the configuration register
|
||||
enum IIRFilter {
|
||||
IIR_FILTER_OFF = 0x00,
|
||||
IIR_FILTER_2 = 0x01,
|
||||
IIR_FILTER_4 = 0x02,
|
||||
IIR_FILTER_8 = 0x03,
|
||||
IIR_FILTER_16 = 0x04,
|
||||
IIR_FILTER_32 = 0x05,
|
||||
IIR_FILTER_64 = 0x06,
|
||||
IIR_FILTER_128 = 0x07
|
||||
};
|
||||
|
||||
/// This class implements support for the BMP3XX Temperature+Pressure i2c sensor.
|
||||
class BMP3XXComponent : 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_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_config(Oversampling temperature_oversampling) {
|
||||
this->temperature_oversampling_ = temperature_oversampling;
|
||||
}
|
||||
/// Set the oversampling value for the pressure sensor. Default is 16x.
|
||||
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
|
||||
this->pressure_oversampling_ = pressure_oversampling;
|
||||
}
|
||||
/// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter.
|
||||
void set_iir_filter_config(IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||
|
||||
/// Soft reset the sensor
|
||||
uint8_t reset();
|
||||
/// Start continuous measurement in NORMAL_MODE
|
||||
bool start_normal_conversion();
|
||||
/// Start a one shot measurement in FORCED_MODE
|
||||
bool start_forced_conversion();
|
||||
/// Stop the conversion and return to SLEEP_MODE
|
||||
bool stop_conversion();
|
||||
/// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16, X32
|
||||
bool set_pressure_oversampling(Oversampling pressure_oversampling);
|
||||
/// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16, X32
|
||||
bool set_temperature_oversampling(Oversampling temperature_oversampling);
|
||||
/// Set the IIR filter setting: OFF, 2, 3, 8, 16, 32
|
||||
bool set_iir_filter(IIRFilter iir_filter);
|
||||
/// Get a temperature measurement
|
||||
bool get_temperature(float &temperature);
|
||||
/// Get a pressure measurement
|
||||
bool get_pressure(float &pressure);
|
||||
/// Get a temperature and pressure measurement
|
||||
bool get_measurements(float &temperature, float &pressure);
|
||||
/// Get a temperature and pressure measurement
|
||||
bool get_measurement();
|
||||
/// Set the barometer mode
|
||||
bool set_mode(OperationMode mode);
|
||||
/// Set the BMP388 oversampling register
|
||||
bool set_oversampling_register(Oversampling pressure_oversampling, Oversampling temperature_oversampling);
|
||||
/// Checks if a measurement is ready
|
||||
bool data_ready();
|
||||
|
||||
protected:
|
||||
Oversampling temperature_oversampling_{OVERSAMPLING_X16};
|
||||
Oversampling pressure_oversampling_{OVERSAMPLING_X16};
|
||||
IIRFilter iir_filter_{IIR_FILTER_OFF};
|
||||
OperationMode operation_mode_{FORCED_MODE};
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
ERROR_COMMUNICATION_FAILED,
|
||||
ERROR_WRONG_CHIP_ID,
|
||||
ERROR_SENSOR_STATUS,
|
||||
ERROR_SENSOR_RESET,
|
||||
} error_code_{NONE};
|
||||
|
||||
struct { // The BMP388 compensation trim parameters (coefficients)
|
||||
uint16_t param_T1;
|
||||
uint16_t param_T2;
|
||||
int8_t param_T3;
|
||||
int16_t param_P1;
|
||||
int16_t param_P2;
|
||||
int8_t param_P3;
|
||||
int8_t param_P4;
|
||||
uint16_t param_P5;
|
||||
uint16_t param_P6;
|
||||
int8_t param_P7;
|
||||
int8_t param_P8;
|
||||
int16_t param_P9;
|
||||
int8_t param_P10;
|
||||
int8_t param_P11;
|
||||
} __attribute__((packed)) compensation_params_;
|
||||
|
||||
struct FloatParams { // The BMP388 float point compensation trim parameters
|
||||
float param_T1;
|
||||
float param_T2;
|
||||
float param_T3;
|
||||
float param_P1;
|
||||
float param_P2;
|
||||
float param_P3;
|
||||
float param_P4;
|
||||
float param_P5;
|
||||
float param_P6;
|
||||
float param_P7;
|
||||
float param_P8;
|
||||
float param_P9;
|
||||
float param_P10;
|
||||
float param_P11;
|
||||
} compensation_float_params_;
|
||||
|
||||
union { // Copy of the BMP388's chip id register
|
||||
struct {
|
||||
uint8_t chip_id_nvm : 4;
|
||||
uint8_t chip_id_fixed : 4;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} chip_id_ = {.reg = 0};
|
||||
|
||||
union { // Copy of the BMP388's event register
|
||||
struct {
|
||||
uint8_t por_detected : 1;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} event_ = {.reg = 0};
|
||||
|
||||
union { // Copy of the BMP388's interrupt status register
|
||||
struct {
|
||||
uint8_t fwm_int : 1;
|
||||
uint8_t ffull_int : 1;
|
||||
uint8_t : 1;
|
||||
uint8_t drdy : 1;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} int_status_ = {.reg = 0};
|
||||
|
||||
union { // Copy of the BMP388's power control register
|
||||
struct {
|
||||
uint8_t press_en : 1;
|
||||
uint8_t temp_en : 1;
|
||||
uint8_t : 2;
|
||||
uint8_t mode : 2;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} pwr_ctrl_ = {.reg = 0};
|
||||
|
||||
union { // Copy of the BMP388's oversampling register
|
||||
struct {
|
||||
uint8_t osr_p : 3;
|
||||
uint8_t osr_t : 3;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} osr_ = {.reg = 0};
|
||||
|
||||
union { // Copy of the BMP388's output data rate register
|
||||
struct {
|
||||
uint8_t odr_sel : 5;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} odr_ = {.reg = 0};
|
||||
|
||||
union { // Copy of the BMP388's configuration register
|
||||
struct {
|
||||
uint8_t : 1;
|
||||
uint8_t iir_filter : 3;
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} config_ = {.reg = 0};
|
||||
|
||||
// Bosch temperature compensation function
|
||||
float bmp388_compensate_temperature_(float uncomp_temp);
|
||||
// Bosch pressure compensation function
|
||||
float bmp388_compensate_pressure_(float uncomp_press, float t_lin);
|
||||
};
|
||||
|
||||
} // namespace bmp3xx
|
||||
} // namespace esphome
|
||||
100
esphome/components/bmp3xx/sensor.py
Normal file
100
esphome/components/bmp3xx/sensor.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@martgras"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bmp3xx_ns = cg.esphome_ns.namespace("bmp3xx")
|
||||
Oversampling = bmp3xx_ns.enum("Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
"NONE": Oversampling.OVERSAMPLING_NONE,
|
||||
"2X": Oversampling.OVERSAMPLING_X2,
|
||||
"4X": Oversampling.OVERSAMPLING_X4,
|
||||
"8X": Oversampling.OVERSAMPLING_X8,
|
||||
"16X": Oversampling.OVERSAMPLING_X16,
|
||||
"32x": Oversampling.OVERSAMPLING_X32,
|
||||
}
|
||||
|
||||
IIRFilter = bmp3xx_ns.enum("IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
"OFF": IIRFilter.IIR_FILTER_OFF,
|
||||
"2X": IIRFilter.IIR_FILTER_2,
|
||||
"4X": IIRFilter.IIR_FILTER_4,
|
||||
"8X": IIRFilter.IIR_FILTER_8,
|
||||
"16X": IIRFilter.IIR_FILTER_16,
|
||||
"32X": IIRFilter.IIR_FILTER_32,
|
||||
"64X": IIRFilter.IIR_FILTER_64,
|
||||
"128X": IIRFilter.IIR_FILTER_128,
|
||||
}
|
||||
|
||||
BMP3XXComponent = bmp3xx_ns.class_(
|
||||
"BMP3XXComponent", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP3XXComponent),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="2X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).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))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER]))
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling_config(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling_config(conf[CONF_OVERSAMPLING]))
|
||||
@@ -14,10 +14,14 @@ CONF_BIT_RATE = "bit_rate"
|
||||
CONF_ON_FRAME = "on_frame"
|
||||
|
||||
|
||||
def validate_id(id_value, id_ext):
|
||||
if not id_ext:
|
||||
if id_value > 0x7FF:
|
||||
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
||||
def validate_id(config):
|
||||
if CONF_CAN_ID in config:
|
||||
id_value = config[CONF_CAN_ID]
|
||||
id_ext = config[CONF_USE_EXTENDED_ID]
|
||||
if not id_ext:
|
||||
if id_value > 0x7FF:
|
||||
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
||||
return config
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
@@ -67,23 +71,18 @@ CANBUS_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
}
|
||||
},
|
||||
validate_id,
|
||||
),
|
||||
}
|
||||
},
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
CANBUS_SCHEMA.add_extra(validate_id)
|
||||
|
||||
|
||||
async def setup_canbus_core_(var, config):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_can_id([config[CONF_CAN_ID]]))
|
||||
cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
|
||||
@@ -92,7 +91,6 @@ async def setup_canbus_core_(var, config):
|
||||
for conf in config.get(CONF_ON_FRAME, []):
|
||||
can_id = conf[CONF_CAN_ID]
|
||||
ext_id = conf[CONF_USE_EXTENDED_ID]
|
||||
validate_id(can_id, ext_id)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
|
||||
await cg.register_component(trigger, conf)
|
||||
await automation.build_automation(
|
||||
@@ -117,11 +115,11 @@ async def register_canbus(var, config):
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
},
|
||||
validate_id,
|
||||
key=CONF_DATA,
|
||||
),
|
||||
)
|
||||
async def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_CANBUS_ID])
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ void Canbus::loop() {
|
||||
}
|
||||
|
||||
// fire all triggers
|
||||
for (auto trigger : this->triggers_) {
|
||||
for (auto *trigger : this->triggers_) {
|
||||
if ((trigger->can_id_ == can_message.can_id) && (trigger->use_extended_id_ == can_message.use_extended_id)) {
|
||||
trigger->trigger(data);
|
||||
}
|
||||
|
||||
@@ -39,14 +39,15 @@ void CCS811Component::setup() {
|
||||
// set MEAS_MODE (page 5)
|
||||
uint8_t meas_mode = 0;
|
||||
uint32_t interval = this->get_update_interval();
|
||||
if (interval >= 60 * 1000)
|
||||
if (interval >= 60 * 1000) {
|
||||
meas_mode = 3 << 4; // sensor takes a reading every 60 seconds
|
||||
else if (interval >= 10 * 1000)
|
||||
} else if (interval >= 10 * 1000) {
|
||||
meas_mode = 2 << 4; // sensor takes a reading every 10 seconds
|
||||
else if (interval >= 1 * 1000)
|
||||
} else if (interval >= 1 * 1000) {
|
||||
meas_mode = 1 << 4; // sensor takes a reading every second
|
||||
else
|
||||
} else {
|
||||
meas_mode = 4 << 4; // sensor takes a reading every 250ms
|
||||
}
|
||||
|
||||
CHECKED_IO(this->write_byte(0x01, meas_mode))
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor, text_sensor
|
||||
from esphome.const import (
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
ICON_RADIATOR,
|
||||
ICON_RESTART,
|
||||
@@ -47,11 +46,8 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
icon=ICON_RESTART
|
||||
),
|
||||
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||
@@ -74,8 +70,7 @@ async def to_code(config):
|
||||
cg.add(var.set_tvoc(sens))
|
||||
|
||||
if CONF_VERSION in config:
|
||||
sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, config[CONF_VERSION])
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_VERSION])
|
||||
cg.add(var.set_version(sens))
|
||||
|
||||
if CONF_BASELINE in config:
|
||||
|
||||
54
esphome/components/cd74hc4067/__init__.py
Normal file
54
esphome/components/cd74hc4067/__init__.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DELAY,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["sensor", "voltage_sampler"]
|
||||
CODEOWNERS = ["@asoehlke"]
|
||||
MULTI_CONF = True
|
||||
|
||||
cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067")
|
||||
|
||||
CD74HC4067Component = cd74hc4067_ns.class_(
|
||||
"CD74HC4067Component", cg.Component, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONF_PIN_S0 = "pin_s0"
|
||||
CONF_PIN_S1 = "pin_s1"
|
||||
CONF_PIN_S2 = "pin_s2"
|
||||
CONF_PIN_S3 = "pin_s3"
|
||||
|
||||
DEFAULT_DELAY = "2ms"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CD74HC4067Component),
|
||||
cv.Required(CONF_PIN_S0): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_PIN_S1): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_PIN_S2): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_PIN_S3): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DELAY, default=DEFAULT_DELAY
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pin_s0 = await cg.gpio_pin_expression(config[CONF_PIN_S0])
|
||||
cg.add(var.set_pin_s0(pin_s0))
|
||||
pin_s1 = await cg.gpio_pin_expression(config[CONF_PIN_S1])
|
||||
cg.add(var.set_pin_s1(pin_s1))
|
||||
pin_s2 = await cg.gpio_pin_expression(config[CONF_PIN_S2])
|
||||
cg.add(var.set_pin_s2(pin_s2))
|
||||
pin_s3 = await cg.gpio_pin_expression(config[CONF_PIN_S3])
|
||||
cg.add(var.set_pin_s3(pin_s3))
|
||||
|
||||
cg.add(var.set_switch_delay(config[CONF_DELAY]))
|
||||
86
esphome/components/cd74hc4067/cd74hc4067.cpp
Normal file
86
esphome/components/cd74hc4067/cd74hc4067.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "cd74hc4067.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cd74hc4067 {
|
||||
|
||||
static const char *const TAG = "cd74hc4067";
|
||||
|
||||
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void CD74HC4067Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up CD74HC4067...");
|
||||
|
||||
this->pin_s0_->setup();
|
||||
this->pin_s1_->setup();
|
||||
this->pin_s2_->setup();
|
||||
this->pin_s3_->setup();
|
||||
|
||||
// set other pin, so that activate_pin will really switch
|
||||
this->active_pin_ = 1;
|
||||
this->activate_pin(0);
|
||||
}
|
||||
|
||||
void CD74HC4067Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:");
|
||||
LOG_PIN(" S0 Pin: ", this->pin_s0_);
|
||||
LOG_PIN(" S1 Pin: ", this->pin_s1_);
|
||||
LOG_PIN(" S2 Pin: ", this->pin_s2_);
|
||||
LOG_PIN(" S3 Pin: ", this->pin_s3_);
|
||||
ESP_LOGCONFIG(TAG, "switch delay: %d", this->switch_delay_);
|
||||
}
|
||||
|
||||
void CD74HC4067Component::activate_pin(uint8_t pin) {
|
||||
if (this->active_pin_ != pin) {
|
||||
ESP_LOGD(TAG, "switch to input %d", pin);
|
||||
|
||||
static int mux_channel[16][4] = {
|
||||
{0, 0, 0, 0}, // channel 0
|
||||
{1, 0, 0, 0}, // channel 1
|
||||
{0, 1, 0, 0}, // channel 2
|
||||
{1, 1, 0, 0}, // channel 3
|
||||
{0, 0, 1, 0}, // channel 4
|
||||
{1, 0, 1, 0}, // channel 5
|
||||
{0, 1, 1, 0}, // channel 6
|
||||
{1, 1, 1, 0}, // channel 7
|
||||
{0, 0, 0, 1}, // channel 8
|
||||
{1, 0, 0, 1}, // channel 9
|
||||
{0, 1, 0, 1}, // channel 10
|
||||
{1, 1, 0, 1}, // channel 11
|
||||
{0, 0, 1, 1}, // channel 12
|
||||
{1, 0, 1, 1}, // channel 13
|
||||
{0, 1, 1, 1}, // channel 14
|
||||
{1, 1, 1, 1} // channel 15
|
||||
};
|
||||
this->pin_s0_->digital_write(mux_channel[pin][0]);
|
||||
this->pin_s1_->digital_write(mux_channel[pin][1]);
|
||||
this->pin_s2_->digital_write(mux_channel[pin][2]);
|
||||
this->pin_s3_->digital_write(mux_channel[pin][3]);
|
||||
// small delay is needed to let the multiplexer switch
|
||||
delay(this->switch_delay_);
|
||||
this->active_pin_ = pin;
|
||||
}
|
||||
}
|
||||
|
||||
CD74HC4067Sensor::CD74HC4067Sensor(CD74HC4067Component *parent) : parent_(parent) {}
|
||||
|
||||
void CD74HC4067Sensor::update() {
|
||||
float value_v = this->sample();
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
|
||||
float CD74HC4067Sensor::get_setup_priority() const { return this->parent_->get_setup_priority() - 1.0f; }
|
||||
|
||||
float CD74HC4067Sensor::sample() {
|
||||
this->parent_->activate_pin(this->pin_);
|
||||
return this->source_->sample();
|
||||
}
|
||||
|
||||
void CD74HC4067Sensor::dump_config() {
|
||||
LOG_SENSOR(TAG, "CD74HC4067 Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
} // namespace cd74hc4067
|
||||
} // namespace esphome
|
||||
65
esphome/components/cd74hc4067/cd74hc4067.h
Normal file
65
esphome/components/cd74hc4067/cd74hc4067.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cd74hc4067 {
|
||||
|
||||
class CD74HC4067Component : public Component {
|
||||
public:
|
||||
/// Set up the internal sensor array.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
/// setting pin active by setting the right combination of the four multiplexer input pins
|
||||
void activate_pin(uint8_t pin);
|
||||
|
||||
/// set the pin connected to multiplexer control pin 0
|
||||
void set_pin_s0(InternalGPIOPin *pin) { this->pin_s0_ = pin; }
|
||||
/// set the pin connected to multiplexer control pin 1
|
||||
void set_pin_s1(InternalGPIOPin *pin) { this->pin_s1_ = pin; }
|
||||
/// set the pin connected to multiplexer control pin 2
|
||||
void set_pin_s2(InternalGPIOPin *pin) { this->pin_s2_ = pin; }
|
||||
/// set the pin connected to multiplexer control pin 3
|
||||
void set_pin_s3(InternalGPIOPin *pin) { this->pin_s3_ = pin; }
|
||||
|
||||
/// set the delay needed after an input switch
|
||||
void set_switch_delay(uint32_t switch_delay) { this->switch_delay_ = switch_delay; }
|
||||
|
||||
private:
|
||||
InternalGPIOPin *pin_s0_;
|
||||
InternalGPIOPin *pin_s1_;
|
||||
InternalGPIOPin *pin_s2_;
|
||||
InternalGPIOPin *pin_s3_;
|
||||
/// the currently active pin
|
||||
uint8_t active_pin_;
|
||||
uint32_t switch_delay_;
|
||||
};
|
||||
|
||||
class CD74HC4067Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
CD74HC4067Sensor(CD74HC4067Component *parent);
|
||||
|
||||
void update() override;
|
||||
|
||||
void dump_config() override;
|
||||
/// `HARDWARE_LATE` setup priority.
|
||||
float get_setup_priority() const override;
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
void set_source(voltage_sampler::VoltageSampler *source) { this->source_ = source; }
|
||||
|
||||
float sample() override;
|
||||
|
||||
protected:
|
||||
CD74HC4067Component *parent_;
|
||||
/// The sampling source to read values from.
|
||||
voltage_sampler::VoltageSampler *source_;
|
||||
|
||||
uint8_t pin_;
|
||||
};
|
||||
} // namespace cd74hc4067
|
||||
} // namespace esphome
|
||||
55
esphome/components/cd74hc4067/sensor.py
Normal file
55
esphome/components/cd74hc4067/sensor.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_SENSOR,
|
||||
CONF_NUMBER,
|
||||
ICON_FLASH,
|
||||
UNIT_VOLT,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
)
|
||||
from . import cd74hc4067_ns, CD74HC4067Component
|
||||
|
||||
DEPENDENCIES = ["cd74hc4067"]
|
||||
|
||||
CD74HC4067Sensor = cd74hc4067_ns.class_(
|
||||
"CD74HC4067Sensor",
|
||||
sensor.Sensor,
|
||||
cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler,
|
||||
)
|
||||
|
||||
CONF_CD74HC4067_ID = "cd74hc4067_id"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_FLASH,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CD74HC4067Sensor),
|
||||
cv.GenerateID(CONF_CD74HC4067_ID): cv.use_id(CD74HC4067Component),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(0, 15),
|
||||
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_CD74HC4067_ID])
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID], parent)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
cg.add(var.set_pin(config[CONF_NUMBER]))
|
||||
|
||||
sens = await cg.get_variable(config[CONF_SENSOR])
|
||||
cg.add(var.set_source(sens))
|
||||
@@ -65,7 +65,7 @@ class ClimateTraits {
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
|
||||
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
|
||||
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
|
||||
const std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
|
||||
std::set<ClimateMode> get_supported_modes() const { return supported_modes_; }
|
||||
|
||||
void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
|
||||
bool get_supports_action() const { return supports_action_; }
|
||||
@@ -93,7 +93,7 @@ class ClimateTraits {
|
||||
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
|
||||
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
|
||||
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
|
||||
const std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; }
|
||||
std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; }
|
||||
|
||||
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
|
||||
supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
|
||||
@@ -141,7 +141,7 @@ class ClimateTraits {
|
||||
}
|
||||
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
|
||||
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
|
||||
const std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
|
||||
std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
|
||||
|
||||
float get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
|
||||
|
||||
@@ -10,21 +10,22 @@ climate::ClimateTraits ClimateIR::traits() {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
||||
if (supports_cool_)
|
||||
if (this->supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (supports_heat_)
|
||||
if (this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
if (supports_dry_)
|
||||
if (this->supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (supports_fan_only_)
|
||||
if (this->supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
|
||||
traits.set_supports_two_point_target_temperature(false);
|
||||
traits.set_visual_min_temperature(this->minimum_temperature_);
|
||||
traits.set_visual_max_temperature(this->maximum_temperature_);
|
||||
traits.set_visual_temperature_step(this->temperature_step_);
|
||||
traits.set_supported_fan_modes(fan_modes_);
|
||||
traits.set_supported_swing_modes(swing_modes_);
|
||||
traits.set_supported_fan_modes(this->fan_modes_);
|
||||
traits.set_supported_swing_modes(this->swing_modes_);
|
||||
traits.set_supported_presets(this->presets_);
|
||||
return traits;
|
||||
}
|
||||
|
||||
@@ -50,6 +51,7 @@ void ClimateIR::setup() {
|
||||
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
this->preset = climate::CLIMATE_PRESET_NONE;
|
||||
}
|
||||
// Never send nan to HA
|
||||
if (std::isnan(this->target_temperature))
|
||||
@@ -65,6 +67,8 @@ void ClimateIR::control(const climate::ClimateCall &call) {
|
||||
this->fan_mode = *call.get_fan_mode();
|
||||
if (call.get_swing_mode().has_value())
|
||||
this->swing_mode = *call.get_swing_mode();
|
||||
if (call.get_preset().has_value())
|
||||
this->preset = *call.get_preset();
|
||||
this->transmit_state();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||
public:
|
||||
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
||||
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
|
||||
std::set<climate::ClimateSwingMode> swing_modes = {}) {
|
||||
std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) {
|
||||
this->minimum_temperature_ = minimum_temperature;
|
||||
this->maximum_temperature_ = maximum_temperature;
|
||||
this->temperature_step_ = temperature_step;
|
||||
@@ -30,6 +30,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||
this->supports_fan_only_ = supports_fan_only;
|
||||
this->fan_modes_ = std::move(fan_modes);
|
||||
this->swing_modes_ = std::move(swing_modes);
|
||||
this->presets_ = std::move(presets);
|
||||
}
|
||||
|
||||
void setup() override;
|
||||
@@ -61,6 +62,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||
bool supports_fan_only_{false};
|
||||
std::set<climate::ClimateFanMode> fan_modes_ = {};
|
||||
std::set<climate::ClimateSwingMode> swing_modes_ = {};
|
||||
std::set<climate::ClimatePreset> presets_ = {};
|
||||
|
||||
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
||||
sensor::Sensor *sensor_{nullptr};
|
||||
|
||||
@@ -137,11 +137,11 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
this->swing_mode =
|
||||
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||
} else {
|
||||
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO)
|
||||
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN)
|
||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) {
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
|
||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else {
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
@@ -156,14 +156,15 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
|
||||
this->mode == climate::CLIMATE_MODE_DRY) {
|
||||
if ((remote_state & FAN_MASK) == FAN_AUTO)
|
||||
if ((remote_state & FAN_MASK) == FAN_AUTO) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
else if ((remote_state & FAN_MASK) == FAN_MIN)
|
||||
} else if ((remote_state & FAN_MASK) == FAN_MIN) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
else if ((remote_state & FAN_MASK) == FAN_MED)
|
||||
} else if ((remote_state & FAN_MASK) == FAN_MED) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
else if ((remote_state & FAN_MASK) == FAN_MAX)
|
||||
} else if ((remote_state & FAN_MASK) == FAN_MAX) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->publish_state();
|
||||
@@ -175,7 +176,7 @@ void LgIrClimate::transmit_(uint32_t value) {
|
||||
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02X", value);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
auto *data = transmit.get_data();
|
||||
|
||||
data->set_carrier_frequency(38000);
|
||||
data->reserve(2 + BITS * 2u);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "coolix.h"
|
||||
#include "esphome/components/remote_base/coolix_protocol.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -6,29 +7,29 @@ namespace coolix {
|
||||
|
||||
static const char *const TAG = "coolix.climate";
|
||||
|
||||
const uint32_t COOLIX_OFF = 0xB27BE0;
|
||||
const uint32_t COOLIX_SWING = 0xB26BE0;
|
||||
const uint32_t COOLIX_LED = 0xB5F5A5;
|
||||
const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
|
||||
static const uint32_t COOLIX_OFF = 0xB27BE0;
|
||||
static const uint32_t COOLIX_SWING = 0xB26BE0;
|
||||
static const uint32_t COOLIX_LED = 0xB5F5A5;
|
||||
static const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
|
||||
|
||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
||||
const uint8_t COOLIX_COOL = 0b0000;
|
||||
const uint8_t COOLIX_DRY_FAN = 0b0100;
|
||||
const uint8_t COOLIX_AUTO = 0b1000;
|
||||
const uint8_t COOLIX_HEAT = 0b1100;
|
||||
const uint32_t COOLIX_MODE_MASK = 0b1100;
|
||||
const uint32_t COOLIX_FAN_MASK = 0xF000;
|
||||
const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
|
||||
const uint32_t COOLIX_FAN_AUTO = 0xB000;
|
||||
const uint32_t COOLIX_FAN_MIN = 0x9000;
|
||||
const uint32_t COOLIX_FAN_MED = 0x5000;
|
||||
const uint32_t COOLIX_FAN_MAX = 0x3000;
|
||||
static const uint8_t COOLIX_COOL = 0b0000;
|
||||
static const uint8_t COOLIX_DRY_FAN = 0b0100;
|
||||
static const uint8_t COOLIX_AUTO = 0b1000;
|
||||
static const uint8_t COOLIX_HEAT = 0b1100;
|
||||
static const uint32_t COOLIX_MODE_MASK = 0b1100;
|
||||
static const uint32_t COOLIX_FAN_MASK = 0xF000;
|
||||
static const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
|
||||
static const uint32_t COOLIX_FAN_AUTO = 0xB000;
|
||||
static const uint32_t COOLIX_FAN_MIN = 0x9000;
|
||||
static const uint32_t COOLIX_FAN_MED = 0x5000;
|
||||
static const uint32_t COOLIX_FAN_MAX = 0x3000;
|
||||
|
||||
// Temperature
|
||||
const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
||||
const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
|
||||
const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
||||
const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||
static const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
||||
static const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
|
||||
static const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
||||
static const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||
0b00000000, // 17C
|
||||
0b00010000, // 18c
|
||||
0b00110000, // 19C
|
||||
@@ -45,17 +46,6 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||
0b10110000 // 30C
|
||||
};
|
||||
|
||||
// Constants
|
||||
static const uint32_t BIT_MARK_US = 660;
|
||||
static const uint32_t HEADER_MARK_US = 560 * 8;
|
||||
static const uint32_t HEADER_SPACE_US = 560 * 8;
|
||||
static const uint32_t BIT_ONE_SPACE_US = 1500;
|
||||
static const uint32_t BIT_ZERO_SPACE_US = 450;
|
||||
static const uint32_t FOOTER_MARK_US = BIT_MARK_US;
|
||||
static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US;
|
||||
|
||||
const uint16_t COOLIX_BITS = 24;
|
||||
|
||||
void CoolixClimate::transmit_state() {
|
||||
uint32_t remote_state = 0xB20F00;
|
||||
|
||||
@@ -111,119 +101,63 @@ void CoolixClimate::transmit_state() {
|
||||
}
|
||||
}
|
||||
}
|
||||
ESP_LOGV(TAG, "Sending coolix code: 0x%02X", remote_state);
|
||||
ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
|
||||
data->set_carrier_frequency(38000);
|
||||
uint16_t repeat = 1;
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
// Header
|
||||
data->mark(HEADER_MARK_US);
|
||||
data->space(HEADER_SPACE_US);
|
||||
// Data
|
||||
// Break data into bytes, starting at the Most Significant
|
||||
// Byte. Each byte then being sent normal, then followed inverted.
|
||||
for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) {
|
||||
// Grab a bytes worth of data.
|
||||
uint8_t byte = (remote_state >> (COOLIX_BITS - i)) & 0xFF;
|
||||
// Normal
|
||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
||||
data->mark(BIT_MARK_US);
|
||||
data->space((byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
||||
}
|
||||
// Inverted
|
||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
||||
data->mark(BIT_MARK_US);
|
||||
data->space(!(byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
||||
}
|
||||
}
|
||||
// Footer
|
||||
data->mark(BIT_MARK_US);
|
||||
data->space(FOOTER_SPACE_US); // Pause before repeating
|
||||
}
|
||||
|
||||
auto *data = transmit.get_data();
|
||||
remote_base::CoolixProtocol().encode(data, remote_state);
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data) {
|
||||
auto decoded = remote_base::CoolixProtocol().decode(data);
|
||||
if (!decoded.has_value())
|
||||
return false;
|
||||
// Decoded remote state y 3 bytes long code.
|
||||
uint32_t remote_state = 0;
|
||||
// The protocol sends the data twice, read here
|
||||
uint32_t loop_read;
|
||||
for (uint16_t loop = 1; loop <= 2; loop++) {
|
||||
if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US))
|
||||
return false;
|
||||
loop_read = 0;
|
||||
for (uint8_t a_byte = 0; a_byte < 3; a_byte++) {
|
||||
uint8_t byte = 0;
|
||||
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
|
||||
if (data.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US))
|
||||
byte |= 1 << a_bit;
|
||||
else if (!data.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US))
|
||||
return false;
|
||||
}
|
||||
// Need to see this segment inverted
|
||||
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
|
||||
bool bit = byte & (1 << a_bit);
|
||||
if (!data.expect_item(BIT_MARK_US, bit ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
|
||||
return false;
|
||||
}
|
||||
// Receiving MSB first: reorder bytes
|
||||
loop_read |= byte << ((2 - a_byte) * 8);
|
||||
}
|
||||
// Footer Mark
|
||||
if (!data.expect_mark(BIT_MARK_US))
|
||||
return false;
|
||||
if (loop == 1) {
|
||||
// Back up state on first loop
|
||||
remote_state = loop_read;
|
||||
if (!data.expect_space(FOOTER_SPACE_US))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Decoded 0x%02X", remote_state);
|
||||
if (remote_state != loop_read || (remote_state & 0xFF0000) != 0xB20000)
|
||||
uint32_t remote_state = *decoded;
|
||||
ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
|
||||
if ((remote_state & 0xFF0000) != 0xB20000)
|
||||
return false;
|
||||
|
||||
if (remote_state == COOLIX_OFF) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
parent->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else if (remote_state == COOLIX_SWING) {
|
||||
this->swing_mode =
|
||||
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||
parent->swing_mode =
|
||||
parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||
} else {
|
||||
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
|
||||
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
else
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) {
|
||||
parent->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) {
|
||||
parent->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
} else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
|
||||
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) {
|
||||
parent->mode = climate::CLIMATE_MODE_DRY;
|
||||
} else {
|
||||
parent->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
}
|
||||
} else
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
parent->mode = climate::CLIMATE_MODE_COOL;
|
||||
|
||||
// Fan Speed
|
||||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
||||
this->mode == climate::CLIMATE_MODE_DRY)
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
||||
parent->mode == climate::CLIMATE_MODE_DRY) {
|
||||
parent->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
} else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) {
|
||||
parent->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
} else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) {
|
||||
parent->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
} else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) {
|
||||
parent->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
}
|
||||
|
||||
// Temperature
|
||||
uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK;
|
||||
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
|
||||
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) {
|
||||
if (COOLIX_TEMP_MAP[i] == temperature_code)
|
||||
this->target_temperature = i + COOLIX_TEMP_MIN;
|
||||
parent->target_temperature = i + COOLIX_TEMP_MIN;
|
||||
}
|
||||
}
|
||||
this->publish_state();
|
||||
parent->publish_state();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -26,11 +26,15 @@ class CoolixClimate : public climate_ir::ClimateIR {
|
||||
climate_ir::ClimateIR::control(call);
|
||||
}
|
||||
|
||||
/// This static method can be used in other climate components that accept the Coolix protocol. See midea_ir for
|
||||
/// example.
|
||||
static bool on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data);
|
||||
|
||||
protected:
|
||||
/// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
/// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override { return CoolixClimate::on_coolix(this, data); }
|
||||
|
||||
bool send_swing_cmd_{false};
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_STATE,
|
||||
CONF_ON_OPEN,
|
||||
CONF_POSITION,
|
||||
CONF_POSITION_COMMAND_TOPIC,
|
||||
CONF_POSITION_STATE_TOPIC,
|
||||
@@ -74,7 +75,6 @@ CoverClosedTrigger = cover_ns.class_(
|
||||
"CoverClosedTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
CONF_ON_OPEN = "on_open"
|
||||
CONF_ON_CLOSED = "on_closed"
|
||||
|
||||
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
|
||||
@@ -210,7 +210,10 @@ Cover::Cover() : Cover("") {}
|
||||
std::string Cover::get_device_class() {
|
||||
if (this->device_class_override_.has_value())
|
||||
return *this->device_class_override_;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
return this->device_class();
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
|
||||
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
|
||||
|
||||
@@ -169,6 +169,11 @@ class Cover : public EntityBase {
|
||||
friend CoverCall;
|
||||
|
||||
virtual void control(const CoverCall &call) = 0;
|
||||
|
||||
/** Override this to set the default device class.
|
||||
*
|
||||
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
|
||||
*/
|
||||
virtual std::string device_class();
|
||||
|
||||
optional<CoverRestoreState> restore_state_();
|
||||
|
||||
@@ -49,8 +49,8 @@ def validate_config(config):
|
||||
if current_gain == 0.0 or voltage_gain == 0.0:
|
||||
raise cv.Invalid("The gains can't be zero")
|
||||
|
||||
max_energy = (0.25 * 0.25 / 3600 / (2 ** -4)) / (voltage_gain * current_gain)
|
||||
min_energy = (0.25 * 0.25 / 3600 / (2 ** 18)) / (voltage_gain * current_gain)
|
||||
max_energy = (0.25 * 0.25 / 3600 / (2**-4)) / (voltage_gain * current_gain)
|
||||
min_energy = (0.25 * 0.25 / 3600 / (2**18)) / (voltage_gain * current_gain)
|
||||
mech_min_energy = (0.25 * 0.25 / 3600 / 7.8) / (voltage_gain * current_gain)
|
||||
if pulse_energy < min_energy or pulse_energy > max_energy:
|
||||
raise cv.Invalid(
|
||||
|
||||
@@ -11,11 +11,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_TEXT_SENSORS): cv.ensure_list(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
)
|
||||
text_sensor.text_sensor_schema()
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ void DaikinClimate::transmit_state() {
|
||||
}
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
auto *data = transmit.get_data();
|
||||
data->set_carrier_frequency(DAIKIN_IR_FREQUENCY);
|
||||
|
||||
data->mark(DAIKIN_HEADER_MARK);
|
||||
@@ -175,14 +175,15 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
}
|
||||
uint8_t fan_mode = frame[8];
|
||||
uint8_t swing_mode = frame[9];
|
||||
if (fan_mode & 0xF && swing_mode & 0xF)
|
||||
if (fan_mode & 0xF && swing_mode & 0xF) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||
else if (fan_mode & 0xF)
|
||||
} else if (fan_mode & 0xF) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||
else if (swing_mode & 0xF)
|
||||
} else if (swing_mode & 0xF) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL;
|
||||
else
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
}
|
||||
switch (fan_mode & 0xF0) {
|
||||
case DAIKIN_FAN_1:
|
||||
case DAIKIN_FAN_2:
|
||||
@@ -212,9 +213,9 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
for (uint8_t pos = 0; pos < DAIKIN_STATE_FRAME_SIZE; pos++) {
|
||||
uint8_t byte = 0;
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE))
|
||||
if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) {
|
||||
byte |= 1 << bit;
|
||||
else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
|
||||
} else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,9 @@ void DallasComponent::setup() {
|
||||
raw_sensors = this->one_wire_->search_vec();
|
||||
|
||||
for (auto &address : raw_sensors) {
|
||||
std::string s = uint64_to_string(address);
|
||||
auto *address8 = reinterpret_cast<uint8_t *>(&address);
|
||||
if (crc8(address8, 7) != address8[7]) {
|
||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", s.c_str());
|
||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
|
||||
continue;
|
||||
}
|
||||
if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
|
||||
@@ -53,7 +52,7 @@ void DallasComponent::setup() {
|
||||
this->found_sensors_.push_back(address);
|
||||
}
|
||||
|
||||
for (auto sensor : this->sensors_) {
|
||||
for (auto *sensor : this->sensors_) {
|
||||
if (sensor->get_index().has_value()) {
|
||||
if (*sensor->get_index() >= this->found_sensors_.size()) {
|
||||
this->status_set_error();
|
||||
@@ -77,8 +76,7 @@ void DallasComponent::dump_config() {
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Found sensors:");
|
||||
for (auto &address : this->found_sensors_) {
|
||||
std::string s = uint64_to_string(address);
|
||||
ESP_LOGD(TAG, " 0x%s", s.c_str());
|
||||
ESP_LOGD(TAG, " 0x%s", format_hex(address).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +145,7 @@ void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
|
||||
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
|
||||
const std::string &DallasTemperatureSensor::get_address_name() {
|
||||
if (this->address_name_.empty()) {
|
||||
this->address_name_ = std::string("0x") + uint64_to_string(this->address_);
|
||||
this->address_name_ = std::string("0x") + format_hex(this->address_);
|
||||
}
|
||||
|
||||
return this->address_name_;
|
||||
@@ -237,7 +235,7 @@ float DallasTemperatureSensor::get_temp_c() {
|
||||
|
||||
return temp / 128.0f;
|
||||
}
|
||||
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + uint64_to_string(this->address_); }
|
||||
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
|
||||
|
||||
} // namespace dallas
|
||||
} // namespace esphome
|
||||
|
||||
@@ -145,9 +145,10 @@ uint64_t ESPOneWire::search() {
|
||||
// read its complement
|
||||
bool cmp_id_bit = this->read_bit();
|
||||
|
||||
if (id_bit && cmp_id_bit)
|
||||
if (id_bit && cmp_id_bit) {
|
||||
// No devices participating in search
|
||||
break;
|
||||
}
|
||||
|
||||
bool branch;
|
||||
|
||||
@@ -170,12 +171,13 @@ uint64_t ESPOneWire::search() {
|
||||
}
|
||||
}
|
||||
|
||||
if (branch)
|
||||
if (branch) {
|
||||
// set bit
|
||||
this->rom_number8_()[rom_byte_number] |= rom_byte_mask;
|
||||
else
|
||||
} else {
|
||||
// clear bit
|
||||
this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask;
|
||||
}
|
||||
|
||||
// choose/announce branch
|
||||
this->write_bit(branch);
|
||||
@@ -190,9 +192,10 @@ uint64_t ESPOneWire::search() {
|
||||
|
||||
if (id_bit_number >= 65) {
|
||||
this->last_discrepancy_ = last_zero;
|
||||
if (this->last_discrepancy_ == 0)
|
||||
if (this->last_discrepancy_ == 0) {
|
||||
// we're at root and have no choices left, so this was the last one.
|
||||
this->last_device_flag_ = true;
|
||||
}
|
||||
search_result = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS
|
||||
from esphome.const import CONF_STATUS
|
||||
from . import DalyBmsComponent, CONF_BMS_DALY_ID
|
||||
|
||||
ICON_CAR_BATTERY = "mdi:car-battery"
|
||||
@@ -14,11 +14,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
|
||||
cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_STATUS): text_sensor.text_sensor_schema(
|
||||
icon=ICON_CAR_BATTERY
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -28,8 +25,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, conf)
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from pathlib import Path
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
|
||||
|
||||
@@ -48,12 +49,24 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
|
||||
if p.exists():
|
||||
raise FileExistsError
|
||||
|
||||
config = {
|
||||
"substitutions": {"name": name},
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": {"name_add_mac_suffix": False},
|
||||
}
|
||||
p.write_text(
|
||||
dump(config) + WIFI_CONFIG,
|
||||
encoding="utf8",
|
||||
)
|
||||
if project_name == "esphome.web":
|
||||
p.write_text(
|
||||
wizard_file(
|
||||
name=name,
|
||||
platform="ESP32" if "esp32" in import_url else "ESP8266",
|
||||
board="esp32dev" if "esp32" in import_url else "esp01_1m",
|
||||
ssid="!secret wifi_ssid",
|
||||
psk="!secret wifi_password",
|
||||
),
|
||||
encoding="utf8",
|
||||
)
|
||||
else:
|
||||
config = {
|
||||
"substitutions": {"name": name},
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": {"name_add_mac_suffix": False},
|
||||
}
|
||||
p.write_text(
|
||||
dump(config) + WIFI_CONFIG,
|
||||
encoding="utf8",
|
||||
)
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_DEVICE,
|
||||
CONF_FREE,
|
||||
CONF_FRAGMENTATION,
|
||||
CONF_BLOCK,
|
||||
CONF_LOOP_TIME,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
DEPENDENCIES = ["logger"]
|
||||
|
||||
CONF_DEBUG_ID = "debug_id"
|
||||
debug_ns = cg.esphome_ns.namespace("debug")
|
||||
DebugComponent = debug_ns.class_("DebugComponent", cg.Component)
|
||||
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DebugComponent),
|
||||
cv.Optional(CONF_DEVICE): cv.invalid(
|
||||
"The 'device' option has been moved to the 'debug' text_sensor component"
|
||||
),
|
||||
cv.Optional(CONF_FREE): cv.invalid(
|
||||
"The 'free' option has been moved to the 'debug' sensor component"
|
||||
),
|
||||
cv.Optional(CONF_BLOCK): cv.invalid(
|
||||
"The 'block' option has been moved to the 'debug' sensor component"
|
||||
),
|
||||
cv.Optional(CONF_FRAGMENTATION): cv.invalid(
|
||||
"The 'fragmentation' option has been moved to the 'debug' sensor component"
|
||||
),
|
||||
cv.Optional(CONF_LOOP_TIME): cv.invalid(
|
||||
"The 'loop_time' option has been moved to the 'debug' sensor component"
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
#include "debug_component.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_system.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if ESP_IDF_VERSION_MAJOR >= 4
|
||||
#include <esp32/rom/rtc.h>
|
||||
#else
|
||||
#include <rom/rtc.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Esp.h>
|
||||
@@ -26,19 +28,40 @@ namespace debug {
|
||||
|
||||
static const char *const TAG = "debug";
|
||||
|
||||
static uint32_t get_free_heap() {
|
||||
#if defined(USE_ESP8266)
|
||||
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP32)
|
||||
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugComponent::dump_config() {
|
||||
std::string device_info;
|
||||
device_info.reserve(256);
|
||||
|
||||
#ifndef ESPHOME_LOG_HAS_DEBUG
|
||||
ESP_LOGE(TAG, "Debug Component requires debug log level!");
|
||||
this->status_set_error();
|
||||
return;
|
||||
#endif
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Debug component:");
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "Device info", this->device_info_);
|
||||
#endif // USE_TEXT_SENSOR
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Free space on heap", this->free_sensor_);
|
||||
LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_);
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_);
|
||||
#endif // defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
#endif // USE_SENSOR
|
||||
|
||||
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
|
||||
#ifdef USE_ARDUINO
|
||||
this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP_IDF)
|
||||
this->free_heap_ = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
device_info += ESPHOME_VERSION;
|
||||
|
||||
this->free_heap_ = get_free_heap();
|
||||
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
@@ -67,9 +90,12 @@ void DebugComponent::dump_config() {
|
||||
default:
|
||||
flash_mode = "UNKNOWN";
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024,
|
||||
ESP.getFlashChipSpeed() / 1000000, flash_mode);
|
||||
ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s",
|
||||
ESP.getFlashChipSize() / 1024, // NOLINT
|
||||
ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT
|
||||
device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT
|
||||
"kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT
|
||||
device_info += flash_mode;
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
#ifdef USE_ESP32
|
||||
@@ -101,13 +127,24 @@ void DebugComponent::dump_config() {
|
||||
info.features &= ~CHIP_FEATURE_BT;
|
||||
}
|
||||
if (info.features)
|
||||
features += "Other:" + uint64_to_string(info.features);
|
||||
features += "Other:" + format_hex(info.features);
|
||||
ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores,
|
||||
info.revision);
|
||||
device_info += "|Chip: ";
|
||||
device_info += model;
|
||||
device_info += " Features:";
|
||||
device_info += features;
|
||||
device_info += " Cores:" + to_string(info.cores);
|
||||
device_info += " Revision:" + to_string(info.revision);
|
||||
|
||||
ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
|
||||
device_info += "|ESP-IDF: ";
|
||||
device_info += esp_get_idf_version();
|
||||
|
||||
ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str());
|
||||
std::string mac = get_mac_address_pretty();
|
||||
ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str());
|
||||
device_info += "|EFuse MAC: ";
|
||||
device_info += mac;
|
||||
|
||||
const char *reset_reason;
|
||||
switch (rtc_get_reset_reason(0)) {
|
||||
@@ -160,6 +197,8 @@ void DebugComponent::dump_config() {
|
||||
reset_reason = "Unknown Reset Reason";
|
||||
}
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason);
|
||||
device_info += "|Reset: ";
|
||||
device_info += reset_reason;
|
||||
|
||||
const char *wakeup_reason;
|
||||
switch (rtc_get_wakeup_cause()) {
|
||||
@@ -203,6 +242,8 @@ void DebugComponent::dump_config() {
|
||||
wakeup_reason = "Unknown";
|
||||
}
|
||||
ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason);
|
||||
device_info += "|Wakeup: ";
|
||||
device_info += wakeup_reason;
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP8266) && !defined(CLANG_TIDY)
|
||||
@@ -214,20 +255,81 @@ void DebugComponent::dump_config() {
|
||||
ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId());
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", ESP.getResetReason().c_str());
|
||||
ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str());
|
||||
|
||||
device_info += "|Chip: 0x" + format_hex(ESP.getChipId());
|
||||
device_info += "|SDK: ";
|
||||
device_info += ESP.getSdkVersion();
|
||||
device_info += "|Core: ";
|
||||
device_info += ESP.getCoreVersion().c_str();
|
||||
device_info += "|Boot: ";
|
||||
device_info += to_string(ESP.getBootVersion());
|
||||
device_info += "|Mode: " + to_string(ESP.getBootMode());
|
||||
device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz());
|
||||
device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId());
|
||||
device_info += "|Reset: ";
|
||||
device_info += ESP.getResetReason().c_str();
|
||||
device_info += "|";
|
||||
device_info += ESP.getResetInfo().c_str();
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->device_info_ != nullptr) {
|
||||
if (device_info.length() > 255)
|
||||
device_info.resize(255);
|
||||
this->device_info_->publish_state(device_info);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
}
|
||||
|
||||
void DebugComponent::loop() {
|
||||
#ifdef USE_ARDUINO
|
||||
uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP_IDF)
|
||||
uint32_t new_free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
// log when free heap space has halved
|
||||
uint32_t new_free_heap = get_free_heap();
|
||||
if (new_free_heap < this->free_heap_ / 2) {
|
||||
this->free_heap_ = new_free_heap;
|
||||
ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_);
|
||||
this->status_momentary_warning("heap", 1000);
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
// calculate loop time - from last call to this one
|
||||
if (this->loop_time_sensor_ != nullptr) {
|
||||
uint32_t now = millis();
|
||||
uint32_t loop_time = now - this->last_loop_timetag_;
|
||||
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
|
||||
this->last_loop_timetag_ = now;
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
}
|
||||
|
||||
void DebugComponent::update() {
|
||||
#ifdef USE_SENSOR
|
||||
if (this->free_sensor_ != nullptr) {
|
||||
this->free_sensor_->publish_state(get_free_heap());
|
||||
}
|
||||
|
||||
if (this->block_sensor_ != nullptr) {
|
||||
#if defined(USE_ESP8266)
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize());
|
||||
#elif defined(USE_ESP32)
|
||||
this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
if (this->fragmentation_sensor_ != nullptr) {
|
||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||
this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this->loop_time_sensor_ != nullptr) {
|
||||
this->loop_time_sensor_->publish_state(this->max_loop_time_);
|
||||
this->max_loop_time_ = 0;
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
}
|
||||
|
||||
float DebugComponent::get_setup_priority() const { return setup_priority::LATE; }
|
||||
|
||||
} // namespace debug
|
||||
|
||||
@@ -1,18 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace debug {
|
||||
|
||||
class DebugComponent : public Component {
|
||||
class DebugComponent : public PollingComponent {
|
||||
public:
|
||||
void loop() override;
|
||||
void update() override;
|
||||
float get_setup_priority() const override;
|
||||
void dump_config() override;
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; }
|
||||
#endif // USE_TEXT_SENSOR
|
||||
#ifdef USE_SENSOR
|
||||
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
|
||||
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
|
||||
#endif
|
||||
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
||||
#endif // USE_SENSOR
|
||||
protected:
|
||||
uint32_t free_heap_{};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
uint32_t last_loop_timetag_{0};
|
||||
uint32_t max_loop_time_{0};
|
||||
|
||||
sensor::Sensor *free_sensor_{nullptr};
|
||||
sensor::Sensor *block_sensor_{nullptr};
|
||||
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
sensor::Sensor *fragmentation_sensor_{nullptr};
|
||||
#endif
|
||||
sensor::Sensor *loop_time_sensor_{nullptr};
|
||||
#endif // USE_SENSOR
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *device_info_{nullptr};
|
||||
#endif // USE_TEXT_SENSOR
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
|
||||
70
esphome/components/debug/sensor.py
Normal file
70
esphome/components/debug/sensor.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_FREE,
|
||||
CONF_FRAGMENTATION,
|
||||
CONF_BLOCK,
|
||||
CONF_LOOP_TIME,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
UNIT_MILLISECOND,
|
||||
UNIT_PERCENT,
|
||||
UNIT_BYTES,
|
||||
ICON_COUNTER,
|
||||
ICON_TIMER,
|
||||
)
|
||||
from . import CONF_DEBUG_ID, DebugComponent
|
||||
|
||||
DEPENDENCIES = ["debug"]
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
|
||||
cv.Optional(CONF_FREE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BYTES,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_BLOCK): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BYTES,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_FRAGMENTATION): cv.All(
|
||||
cv.only_on_esp8266,
|
||||
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_COUNTER,
|
||||
accuracy_decimals=1,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLISECOND,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
|
||||
if CONF_FREE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FREE])
|
||||
cg.add(debug_component.set_free_sensor(sens))
|
||||
|
||||
if CONF_BLOCK in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BLOCK])
|
||||
cg.add(debug_component.set_block_sensor(sens))
|
||||
|
||||
if CONF_FRAGMENTATION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FRAGMENTATION])
|
||||
cg.add(debug_component.set_fragmentation_sensor(sens))
|
||||
|
||||
if CONF_LOOP_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_LOOP_TIME])
|
||||
cg.add(debug_component.set_loop_time_sensor(sens))
|
||||
26
esphome/components/debug/text_sensor.py
Normal file
26
esphome/components/debug/text_sensor.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_DEVICE, ENTITY_CATEGORY_DIAGNOSTIC
|
||||
|
||||
from . import CONF_DEBUG_ID, DebugComponent
|
||||
|
||||
DEPENDENCIES = ["debug"]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
|
||||
cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
|
||||
if CONF_DEVICE in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_DEVICE])
|
||||
cg.add(debug_component.set_device_info_sensor(sens))
|
||||
@@ -37,12 +37,10 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_BLUETOOTH,
|
||||
ICON_BLUR,
|
||||
ICON_EMPTY,
|
||||
ICON_THERMOMETER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
UNIT_PERCENT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
@@ -67,7 +65,7 @@ DemoClimate = demo_ns.class_("DemoClimate", climate.Climate, cg.Component)
|
||||
DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True)
|
||||
DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component)
|
||||
DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True)
|
||||
DemoFan = demo_ns.class_("DemoFan", cg.Component)
|
||||
DemoFan = demo_ns.class_("DemoFan", fan.Fan, cg.Component)
|
||||
DemoFanType = demo_ns.enum("DemoFanType", is_class=True)
|
||||
DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component)
|
||||
DemoLightType = demo_ns.enum("DemoLightType", is_class=True)
|
||||
@@ -339,7 +337,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
},
|
||||
],
|
||||
): [
|
||||
sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 0)
|
||||
sensor.sensor_schema(accuracy_decimals=0)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(
|
||||
{
|
||||
@@ -378,12 +376,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
},
|
||||
],
|
||||
): [
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
text_sensor.text_sensor_schema(klass=DemoTextSensor).extend(
|
||||
cv.polling_component_schema("60s")
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DemoTextSensor),
|
||||
}
|
||||
)
|
||||
],
|
||||
}
|
||||
@@ -411,8 +405,7 @@ async def to_code(config):
|
||||
for conf in config[CONF_FANS]:
|
||||
var = cg.new_Pvariable(conf[CONF_OUTPUT_ID])
|
||||
await cg.register_component(var, conf)
|
||||
fan_ = await fan.create_fan_state(conf)
|
||||
cg.add(var.set_fan(fan_))
|
||||
await fan.register_fan(var, conf)
|
||||
cg.add(var.set_type(conf[CONF_TYPE]))
|
||||
|
||||
for conf in config[CONF_LIGHTS]:
|
||||
@@ -444,6 +437,5 @@ async def to_code(config):
|
||||
await switch.register_switch(var, conf)
|
||||
|
||||
for conf in config[CONF_TEXT_SENSORS]:
|
||||
var = cg.new_Pvariable(conf[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(conf)
|
||||
await cg.register_component(var, conf)
|
||||
await text_sensor.register_text_sensor(var, conf)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/fan/fan_state.h"
|
||||
#include "esphome/components/fan/fan.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace demo {
|
||||
@@ -13,11 +13,10 @@ enum class DemoFanType {
|
||||
TYPE_4,
|
||||
};
|
||||
|
||||
class DemoFan : public Component {
|
||||
class DemoFan : public fan::Fan, public Component {
|
||||
public:
|
||||
void set_type(DemoFanType type) { type_ = type; }
|
||||
void set_fan(fan::FanState *fan) { fan_ = fan; }
|
||||
void setup() override {
|
||||
fan::FanTraits get_traits() override {
|
||||
fan::FanTraits traits{};
|
||||
|
||||
// oscillation
|
||||
@@ -43,10 +42,23 @@ class DemoFan : public Component {
|
||||
break;
|
||||
}
|
||||
|
||||
this->fan_->set_traits(traits);
|
||||
return traits;
|
||||
}
|
||||
|
||||
protected:
|
||||
void control(const fan::FanCall &call) override {
|
||||
if (call.get_state().has_value())
|
||||
this->state = *call.get_state();
|
||||
if (call.get_oscillating().has_value())
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_speed().has_value())
|
||||
this->speed = *call.get_speed();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
fan::FanState *fan_;
|
||||
DemoFanType type_;
|
||||
};
|
||||
|
||||
|
||||
@@ -114,10 +114,11 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
// Wait for rising edge
|
||||
while (!this->pin_->digital_read()) {
|
||||
if (micros() - start_time > 90) {
|
||||
if (i < 0)
|
||||
if (i < 0) {
|
||||
error_code = 1;
|
||||
else
|
||||
} else {
|
||||
error_code = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -130,10 +131,11 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||
// Wait for falling edge
|
||||
while (this->pin_->digital_read()) {
|
||||
if ((end_time = micros()) - start_time > 90) {
|
||||
if (i < 0)
|
||||
if (i < 0) {
|
||||
error_code = 3;
|
||||
else
|
||||
} else {
|
||||
error_code = 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ DHT = dht_ns.class_("DHT", cg.PollingComponent)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DHT),
|
||||
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
@@ -15,7 +16,8 @@ const Color COLOR_OFF(0, 0, 0, 0);
|
||||
const Color COLOR_ON(255, 255, 255, 255);
|
||||
|
||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
this->buffer_ = new (std::nothrow) uint8_t[buffer_length]; // NOLINT
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
this->buffer_ = allocator.allocate(buffer_length);
|
||||
if (this->buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate buffer for display!");
|
||||
return;
|
||||
@@ -174,9 +176,10 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align
|
||||
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
|
||||
if (!font->get_glyphs().empty()) {
|
||||
uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width;
|
||||
for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++)
|
||||
for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) {
|
||||
for (int glyph_y = 0; glyph_y < height; glyph_y++)
|
||||
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
|
||||
}
|
||||
x_at += glyph_width;
|
||||
}
|
||||
|
||||
@@ -231,6 +234,14 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IMAGE_TYPE_TRANSPARENT_BINARY:
|
||||
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
||||
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
||||
if (image->get_pixel(img_x, img_y))
|
||||
this->draw_pixel_at(x + img_x, y + img_y, color_on);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +252,12 @@ void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) {
|
||||
}
|
||||
#endif // USE_GRAPH
|
||||
|
||||
#ifdef USE_QR_CODE
|
||||
void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
|
||||
qr_code->draw(this, x, y, color_on, scale);
|
||||
}
|
||||
#endif // USE_QR_CODE
|
||||
|
||||
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
|
||||
int *width, int *height) {
|
||||
int x_offset, baseline;
|
||||
@@ -415,10 +432,11 @@ int Font::match_next_glyph(const char *str, int *match_length) {
|
||||
int hi = this->glyphs_.size() - 1;
|
||||
while (lo != hi) {
|
||||
int mid = (lo + hi + 1) / 2;
|
||||
if (this->glyphs_[mid].compare_to(str))
|
||||
if (this->glyphs_[mid].compare_to(str)) {
|
||||
lo = mid;
|
||||
else
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
*match_length = this->glyphs_[lo].match_length(str);
|
||||
if (*match_length <= 0)
|
||||
@@ -444,10 +462,11 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
||||
}
|
||||
|
||||
const Glyph &glyph = this->glyphs_[glyph_n];
|
||||
if (!has_char)
|
||||
if (!has_char) {
|
||||
min_x = glyph.glyph_data_->offset_x;
|
||||
else
|
||||
} else {
|
||||
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
|
||||
}
|
||||
x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
|
||||
|
||||
i += match_length;
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
#include "esphome/components/graph/graph.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_QR_CODE
|
||||
#include "esphome/components/qr_code/qr_code.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
@@ -73,7 +77,12 @@ extern const Color COLOR_OFF;
|
||||
/// Turn the pixel ON.
|
||||
extern const Color COLOR_ON;
|
||||
|
||||
enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 };
|
||||
enum ImageType {
|
||||
IMAGE_TYPE_BINARY = 0,
|
||||
IMAGE_TYPE_GRAYSCALE = 1,
|
||||
IMAGE_TYPE_RGB24 = 2,
|
||||
IMAGE_TYPE_TRANSPARENT_BINARY = 3,
|
||||
};
|
||||
|
||||
enum DisplayRotation {
|
||||
DISPLAY_ROTATION_0_DEGREES = 0,
|
||||
@@ -302,6 +311,17 @@ class DisplayBuffer {
|
||||
void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
|
||||
#endif // USE_GRAPH
|
||||
|
||||
#ifdef USE_QR_CODE
|
||||
/** Draw the `qr_code` with the top-left corner at [x,y] to the screen.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param qr_code The qr_code to draw
|
||||
* @param color_on The color to replace in binary images for the on bits.
|
||||
*/
|
||||
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
|
||||
#endif
|
||||
|
||||
/** Get the text bounds of the given string.
|
||||
*
|
||||
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
|
||||
@@ -336,15 +356,15 @@ class DisplayBuffer {
|
||||
// Internal method to set display auto clearing.
|
||||
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
|
||||
|
||||
virtual int get_height_internal() = 0;
|
||||
virtual int get_width_internal() = 0;
|
||||
DisplayRotation get_rotation() const { return this->rotation_; }
|
||||
|
||||
protected:
|
||||
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
|
||||
|
||||
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
|
||||
|
||||
virtual int get_height_internal() = 0;
|
||||
|
||||
virtual int get_width_internal() = 0;
|
||||
|
||||
void init_internal_(uint32_t buffer_length);
|
||||
|
||||
void do_update_();
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
from . import Dsmr, CONF_DSMR_ID
|
||||
|
||||
AUTO_LOAD = ["dsmr"]
|
||||
@@ -11,71 +9,19 @@ AUTO_LOAD = ["dsmr"]
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr),
|
||||
cv.Optional("identification"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("p1_version"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("p1_version_be"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("timestamp"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("electricity_tariff"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("electricity_failure_log"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("message_short"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("message_long"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("gas_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("thermal_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("water_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("sub_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("identification"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("p1_version"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("p1_version_be"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("timestamp"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("electricity_tariff"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("message_short"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("message_long"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -89,8 +35,7 @@ async def to_code(config):
|
||||
continue
|
||||
id = conf.get("id")
|
||||
if id and id.type == text_sensor.TextSensor:
|
||||
var = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(var, conf)
|
||||
var = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}")(var))
|
||||
text_sensors.append(f"F({key})")
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ bool E131Component::process_(int universe, const E131Packet &packet) {
|
||||
|
||||
ESP_LOGV(TAG, "Received E1.31 packet for %d universe, with %d bytes", universe, packet.count);
|
||||
|
||||
for (auto light_effect : light_effects_) {
|
||||
for (auto *light_effect : light_effects_) {
|
||||
handled = light_effect->process_(universe, packet) || handled;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,9 @@ class E131Component : public esphome::Component {
|
||||
void loop() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
public:
|
||||
void add_effect(E131AddressableLightEffect *light_effect);
|
||||
void remove_effect(E131AddressableLightEffect *light_effect);
|
||||
|
||||
public:
|
||||
void set_method(E131ListenMethod listen_method) { this->listen_method_ = listen_method; }
|
||||
|
||||
protected:
|
||||
@@ -47,7 +45,6 @@ class E131Component : public esphome::Component {
|
||||
void join_(int universe);
|
||||
void leave_(int universe);
|
||||
|
||||
protected:
|
||||
E131ListenMethod listen_method_{E131_MULTICAST};
|
||||
std::unique_ptr<UDP> udp_;
|
||||
std::set<E131AddressableLightEffect *> light_effects_;
|
||||
|
||||
@@ -47,7 +47,7 @@ void E131AddressableLightEffect::apply(light::AddressableLight &it, const Color
|
||||
}
|
||||
|
||||
bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet) {
|
||||
auto it = get_addressable_();
|
||||
auto *it = get_addressable_();
|
||||
|
||||
// check if this is our universe and data are valid
|
||||
if (universe < first_universe_ || universe > get_last_universe())
|
||||
@@ -57,7 +57,7 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
|
||||
// limit amount of lights per universe and received
|
||||
int output_end =
|
||||
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
|
||||
auto input_data = packet.values + 1;
|
||||
auto *input_data = packet.values + 1;
|
||||
|
||||
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %d-%d.", get_name().c_str(), universe, output_offset,
|
||||
output_end);
|
||||
|
||||
@@ -17,19 +17,16 @@ class E131AddressableLightEffect : public light::AddressableLightEffect {
|
||||
public:
|
||||
E131AddressableLightEffect(const std::string &name);
|
||||
|
||||
public:
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void apply(light::AddressableLight &it, const Color ¤t_color) override;
|
||||
|
||||
public:
|
||||
int get_data_per_universe() const;
|
||||
int get_lights_per_universe() const;
|
||||
int get_first_universe() const;
|
||||
int get_last_universe() const;
|
||||
int get_universe_count() const;
|
||||
|
||||
public:
|
||||
void set_first_universe(int universe) { this->first_universe_ = universe; }
|
||||
void set_channels(E131LightChannels channels) { this->channels_ = channels; }
|
||||
void set_e131(E131Component *e131) { this->e131_ = e131; }
|
||||
@@ -37,7 +34,6 @@ class E131AddressableLightEffect : public light::AddressableLightEffect {
|
||||
protected:
|
||||
bool process_(int universe, const E131Packet &packet);
|
||||
|
||||
protected:
|
||||
int first_universe_{0};
|
||||
int last_universe_{0};
|
||||
E131LightChannels channels_{E131_RGB};
|
||||
|
||||
@@ -116,7 +116,7 @@ bool E131Component::packet_(const std::vector<uint8_t> &data, int &universe, E13
|
||||
if (data.size() < E131_MIN_PACKET_SIZE)
|
||||
return false;
|
||||
|
||||
auto sbuff = reinterpret_cast<const E131RawPacket *>(&data[0]);
|
||||
auto *sbuff = reinterpret_cast<const E131RawPacket *>(&data[0]);
|
||||
|
||||
if (memcmp(sbuff->acn_id, ACN_ID, sizeof(sbuff->acn_id)) != 0)
|
||||
return false;
|
||||
|
||||
0
esphome/components/ektf2232/__init__.py
Normal file
0
esphome/components/ektf2232/__init__.py
Normal file
166
esphome/components/ektf2232/ektf2232.cpp
Normal file
166
esphome/components/ektf2232/ektf2232.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "ektf2232.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
static const char *const TAG = "ektf2232";
|
||||
|
||||
static const uint8_t SOFT_RESET_CMD[4] = {0x77, 0x77, 0x77, 0x77};
|
||||
static const uint8_t HELLO[4] = {0x55, 0x55, 0x55, 0x55};
|
||||
static const uint8_t GET_X_RES[4] = {0x53, 0x60, 0x00, 0x00};
|
||||
static const uint8_t GET_Y_RES[4] = {0x53, 0x63, 0x00, 0x00};
|
||||
static const uint8_t GET_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01};
|
||||
|
||||
void EKTF2232TouchscreenStore::gpio_intr(EKTF2232TouchscreenStore *store) { store->touch = true; }
|
||||
|
||||
void EKTF2232Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen...");
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
this->store_.pin = this->interrupt_pin_->to_isr();
|
||||
this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_,
|
||||
gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
this->rts_pin_->setup();
|
||||
|
||||
this->hard_reset_();
|
||||
if (!this->soft_reset_()) {
|
||||
ESP_LOGE(TAG, "Failed to soft reset EKT2232!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get touch resolution
|
||||
uint8_t received[4];
|
||||
this->write(GET_X_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to read X resolution!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->x_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
|
||||
this->write(GET_Y_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to read Y resolution!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
this->store_.touch = false;
|
||||
|
||||
this->set_power_state(true);
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::loop() {
|
||||
if (!this->store_.touch)
|
||||
return;
|
||||
this->store_.touch = false;
|
||||
|
||||
uint8_t touch_count = 0;
|
||||
std::vector<TouchPoint> touches;
|
||||
|
||||
uint8_t raw[8];
|
||||
this->read(raw, 8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (raw[7] & (1 << i))
|
||||
touch_count++;
|
||||
}
|
||||
|
||||
if (touch_count == 0) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
}
|
||||
|
||||
touch_count = std::min<uint8_t>(touch_count, 2);
|
||||
|
||||
ESP_LOGV(TAG, "Touch count: %d", touch_count);
|
||||
|
||||
for (int i = 0; i < touch_count; i++) {
|
||||
uint8_t *d = raw + 1 + (i * 3);
|
||||
uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1];
|
||||
uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2];
|
||||
|
||||
raw_x = raw_x * this->display_height_ - 1;
|
||||
raw_y = raw_y * this->display_width_ - 1;
|
||||
|
||||
TouchPoint tp;
|
||||
switch (this->rotation_) {
|
||||
case ROTATE_0_DEGREES:
|
||||
tp.y = raw_x / this->x_resolution_;
|
||||
tp.x = this->display_width_ - 1 - (raw_y / this->y_resolution_);
|
||||
break;
|
||||
case ROTATE_90_DEGREES:
|
||||
tp.x = raw_x / this->x_resolution_;
|
||||
tp.y = raw_y / this->y_resolution_;
|
||||
break;
|
||||
case ROTATE_180_DEGREES:
|
||||
tp.y = this->display_height_ - 1 - (raw_x / this->x_resolution_);
|
||||
tp.x = raw_y / this->y_resolution_;
|
||||
break;
|
||||
case ROTATE_270_DEGREES:
|
||||
tp.x = this->display_height_ - 1 - (raw_x / this->x_resolution_);
|
||||
tp.y = this->display_width_ - 1 - (raw_y / this->y_resolution_);
|
||||
break;
|
||||
}
|
||||
|
||||
this->defer([this, tp]() { this->send_touch_(tp); });
|
||||
}
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::set_power_state(bool enable) {
|
||||
uint8_t data[] = {0x54, 0x50, 0x00, 0x01};
|
||||
data[1] |= (enable << 3);
|
||||
this->write(data, 4);
|
||||
}
|
||||
|
||||
bool EKTF2232Touchscreen::get_power_state() {
|
||||
uint8_t received[4];
|
||||
this->write(GET_POWER_STATE_CMD, 4);
|
||||
this->store_.touch = false;
|
||||
this->read(received, 4);
|
||||
return (received[1] >> 3) & 1;
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::hard_reset_() {
|
||||
this->rts_pin_->digital_write(false);
|
||||
delay(15);
|
||||
this->rts_pin_->digital_write(true);
|
||||
delay(15);
|
||||
}
|
||||
|
||||
bool EKTF2232Touchscreen::soft_reset_() {
|
||||
auto err = this->write(SOFT_RESET_CMD, 4);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return false;
|
||||
|
||||
uint8_t received[4];
|
||||
uint16_t timeout = 1000;
|
||||
while (!this->store_.touch && timeout > 0) {
|
||||
delay(1);
|
||||
timeout--;
|
||||
}
|
||||
if (timeout > 0)
|
||||
this->store_.touch = true;
|
||||
this->read(received, 4);
|
||||
this->store_.touch = false;
|
||||
|
||||
return !memcmp(received, HELLO, 4);
|
||||
}
|
||||
|
||||
void EKTF2232Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "EKT2232 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" RTS Pin: ", this->rts_pin_);
|
||||
}
|
||||
|
||||
} // namespace ektf2232
|
||||
} // namespace esphome
|
||||
45
esphome/components/ektf2232/ektf2232.h
Normal file
45
esphome/components/ektf2232/ektf2232.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
struct EKTF2232TouchscreenStore {
|
||||
volatile bool touch;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(EKTF2232TouchscreenStore *store);
|
||||
};
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }
|
||||
|
||||
void set_power_state(bool enable);
|
||||
bool get_power_state();
|
||||
|
||||
protected:
|
||||
void hard_reset_();
|
||||
bool soft_reset_();
|
||||
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
GPIOPin *rts_pin_;
|
||||
EKTF2232TouchscreenStore store_;
|
||||
uint16_t x_resolution_;
|
||||
uint16_t y_resolution_;
|
||||
};
|
||||
|
||||
} // namespace ektf2232
|
||||
} // namespace esphome
|
||||
48
esphome/components/ektf2232/touchscreen.py
Normal file
48
esphome/components/ektf2232/touchscreen.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 i2c, touchscreen
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ektf2232_ns = cg.esphome_ns.namespace("ektf2232")
|
||||
EKTF2232Touchscreen = ektf2232_ns.class_(
|
||||
"EKTF2232Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CONF_EKTF2232_ID = "ektf2232_id"
|
||||
CONF_INTERRUPT_PIN = "interrupt_pin"
|
||||
CONF_RTS_PIN = "rts_pin"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen),
|
||||
cv.Required(CONF_INTERRUPT_PIN): cv.All(
|
||||
pins.internal_gpio_input_pin_schema
|
||||
),
|
||||
cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x15))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN])
|
||||
cg.add(var.set_rts_pin(rts_pin))
|
||||
@@ -2,8 +2,9 @@ from dataclasses import dataclass
|
||||
from typing import Union
|
||||
from pathlib import Path
|
||||
import logging
|
||||
import os
|
||||
|
||||
from esphome.helpers import write_file_if_changed
|
||||
from esphome.helpers import copy_file_if_changed, write_file_if_changed
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_FRAMEWORK,
|
||||
@@ -17,6 +18,7 @@ from esphome.const import (
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
import esphome.config_validation as cv
|
||||
@@ -105,7 +107,6 @@ def _format_framework_espidf_version(ver: cv.Version) -> str:
|
||||
# The new version needs to be thoroughly validated before changing the
|
||||
# recommended version as otherwise a bunch of devices could be bricked
|
||||
# * For all constants below, update platformio.ini (in this repo)
|
||||
# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository
|
||||
|
||||
# The default/recommended arduino framework version
|
||||
# - https://github.com/espressif/arduino-esp32/releases
|
||||
@@ -114,16 +115,16 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(1, 0, 6)
|
||||
# The platformio/espressif32 version to use for arduino frameworks
|
||||
# - https://github.com/platformio/platform-espressif32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||
ARDUINO_PLATFORM_VERSION = cv.Version(3, 3, 2)
|
||||
ARDUINO_PLATFORM_VERSION = cv.Version(3, 5, 0)
|
||||
|
||||
# The default/recommended esp-idf framework version
|
||||
# - https://github.com/espressif/esp-idf/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 0)
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 2)
|
||||
# The platformio/espressif32 version to use for esp-idf frameworks
|
||||
# - https://github.com/platformio/platform-espressif32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||
ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2)
|
||||
ESP_IDF_PLATFORM_VERSION = cv.Version(3, 5, 0)
|
||||
|
||||
|
||||
def _arduino_check_versions(value):
|
||||
@@ -164,8 +165,8 @@ def _arduino_check_versions(value):
|
||||
def _esp_idf_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(4, 3, 0), None),
|
||||
"dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(4, 3, 2), None),
|
||||
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
@@ -292,9 +293,13 @@ async def to_code(config):
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
|
||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
|
||||
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||
cg.add_platformio_option("framework", "espidf")
|
||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||
@@ -332,6 +337,13 @@ async def to_code(config):
|
||||
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
|
||||
cg.add_define(
|
||||
"USE_ESP_IDF_VERSION_CODE",
|
||||
cg.RawExpression(
|
||||
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
|
||||
),
|
||||
)
|
||||
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
@@ -343,6 +355,13 @@ async def to_code(config):
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
|
||||
cg.add_define(
|
||||
"USE_ARDUINO_VERSION_CODE",
|
||||
cg.RawExpression(
|
||||
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
ARDUINO_PARTITIONS_CSV = """\
|
||||
nvs, data, nvs, 0x009000, 0x005000,
|
||||
@@ -412,3 +431,18 @@ def copy_files():
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
IDF_PARTITIONS_CSV,
|
||||
)
|
||||
# IDF build scripts look for version string to put in the build.
|
||||
# However, if the build path does not have an initialized git repo,
|
||||
# and no version.txt file exists, the CMake script fails for some setups.
|
||||
# Fix by manually pasting a version.txt file, containing the ESPHome version
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("version.txt"),
|
||||
__version__,
|
||||
)
|
||||
|
||||
dir = os.path.dirname(__file__)
|
||||
post_build_file = os.path.join(dir, "post_build.py.script")
|
||||
copy_file_if_changed(
|
||||
post_build_file,
|
||||
CORE.relative_build_path("post_build.py"),
|
||||
)
|
||||
|
||||
@@ -261,6 +261,37 @@ ESP32_BOARD_PINS = {
|
||||
"SS": 33,
|
||||
"TX": 17,
|
||||
},
|
||||
"featheresp32-s2": {
|
||||
"SDA": 3,
|
||||
"SCL": 4,
|
||||
"SS": 42,
|
||||
"MOSI": 35,
|
||||
"SCK": 36,
|
||||
"MISO": 37,
|
||||
"A0": 18,
|
||||
"A1": 17,
|
||||
"A10": 27,
|
||||
"A11": 12,
|
||||
"A12": 13,
|
||||
"A13": 35,
|
||||
"A2": 16,
|
||||
"A3": 15,
|
||||
"A4": 14,
|
||||
"A5": 8,
|
||||
"LED": 13,
|
||||
"TX": 39,
|
||||
"RX": 38,
|
||||
"T5": 5,
|
||||
"T8": 8,
|
||||
"T9": 9,
|
||||
"T10": 10,
|
||||
"T11": 11,
|
||||
"T12": 12,
|
||||
"T13": 13,
|
||||
"T14": 14,
|
||||
"DAC1": 17,
|
||||
"DAC2": 18,
|
||||
},
|
||||
"firebeetle32": {"LED": 2},
|
||||
"fm-devkit": {
|
||||
"D0": 34,
|
||||
|
||||
49
esphome/components/esp32/post_build.py.script
Normal file
49
esphome/components/esp32/post_build.py.script
Normal file
@@ -0,0 +1,49 @@
|
||||
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
|
||||
|
||||
import esptool
|
||||
from SCons.Script import ARGUMENTS
|
||||
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
|
||||
def esp32_create_combined_bin(source, target, env):
|
||||
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
|
||||
if verbose:
|
||||
print("Generating combined binary for serial flashing")
|
||||
app_offset = 0x10000
|
||||
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
||||
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
|
||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||
chip = env.get("BOARD_MCU")
|
||||
flash_size = env.BoardConfig().get("upload.flash_size")
|
||||
cmd = [
|
||||
"--chip",
|
||||
chip,
|
||||
"merge_bin",
|
||||
"-o",
|
||||
new_file_name,
|
||||
"--flash_size",
|
||||
flash_size,
|
||||
]
|
||||
if verbose:
|
||||
print(" Offset | File")
|
||||
for section in sections:
|
||||
sect_adr, sect_file = section.split(" ", 1)
|
||||
if verbose:
|
||||
print(f" - {sect_adr} | {sect_file}")
|
||||
cmd += [sect_adr, sect_file]
|
||||
|
||||
cmd += [hex(app_offset), firmware_name]
|
||||
|
||||
if verbose:
|
||||
print(f" - {hex(app_offset)} | {firmware_name}")
|
||||
print()
|
||||
print(f"Using esptool.py arguments: {' '.join(cmd)}")
|
||||
print()
|
||||
esptool.main(cmd)
|
||||
|
||||
|
||||
# pylint: disable=E0602
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa
|
||||
@@ -42,6 +42,11 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) {
|
||||
this->advertising_uuids_.end());
|
||||
}
|
||||
|
||||
void BLEAdvertising::set_manufacturer_data(uint8_t *data, uint16_t size) {
|
||||
this->advertising_data_.p_manufacturer_data = data;
|
||||
this->advertising_data_.manufacturer_len = size;
|
||||
}
|
||||
|
||||
void BLEAdvertising::start() {
|
||||
int num_services = this->advertising_uuids_.size();
|
||||
if (num_services == 0) {
|
||||
|
||||
@@ -20,6 +20,7 @@ class BLEAdvertising {
|
||||
void remove_service_uuid(ESPBTUUID uuid);
|
||||
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
|
||||
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
|
||||
void set_manufacturer_data(uint8_t *data, uint16_t size);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
@@ -147,40 +147,46 @@ bool BLECharacteristic::is_failed() {
|
||||
}
|
||||
|
||||
void BLECharacteristic::set_broadcast_property(bool value) {
|
||||
if (value)
|
||||
if (value) {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST);
|
||||
else
|
||||
} else {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST);
|
||||
}
|
||||
}
|
||||
void BLECharacteristic::set_indicate_property(bool value) {
|
||||
if (value)
|
||||
if (value) {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE);
|
||||
else
|
||||
} else {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE);
|
||||
}
|
||||
}
|
||||
void BLECharacteristic::set_notify_property(bool value) {
|
||||
if (value)
|
||||
if (value) {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY);
|
||||
else
|
||||
} else {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY);
|
||||
}
|
||||
}
|
||||
void BLECharacteristic::set_read_property(bool value) {
|
||||
if (value)
|
||||
if (value) {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ);
|
||||
else
|
||||
} else {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ);
|
||||
}
|
||||
}
|
||||
void BLECharacteristic::set_write_property(bool value) {
|
||||
if (value)
|
||||
if (value) {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE);
|
||||
else
|
||||
} else {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE);
|
||||
}
|
||||
}
|
||||
void BLECharacteristic::set_write_no_response_property(bool value) {
|
||||
if (value)
|
||||
if (value) {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
|
||||
else
|
||||
} else {
|
||||
this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
|
||||
}
|
||||
}
|
||||
|
||||
void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
|
||||
@@ -18,9 +18,10 @@ BLEService::~BLEService() {
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) {
|
||||
for (auto *chr : this->characteristics_)
|
||||
for (auto *chr : this->characteristics_) {
|
||||
if (chr->get_uuid() == uuid)
|
||||
return chr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,11 +58,12 @@ void ESP32BLETracker::setup() {
|
||||
void ESP32BLETracker::loop() {
|
||||
BLEEvent *ble_event = this->ble_events_.pop();
|
||||
while (ble_event != nullptr) {
|
||||
if (ble_event->type_)
|
||||
if (ble_event->type_) {
|
||||
this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
|
||||
&ble_event->event_.gattc.gattc_param);
|
||||
else
|
||||
} else {
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
}
|
||||
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
@@ -89,11 +90,12 @@ void ESP32BLETracker::loop() {
|
||||
device.parse_scan_rst(this->scan_result_buffer_[i]);
|
||||
|
||||
bool found = false;
|
||||
for (auto *listener : this->listeners_)
|
||||
for (auto *listener : this->listeners_) {
|
||||
if (listener->parse_device(device))
|
||||
found = true;
|
||||
}
|
||||
|
||||
for (auto *client : this->clients_)
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->parse_device(device)) {
|
||||
found = true;
|
||||
if (client->state() == ClientState::DISCOVERED) {
|
||||
@@ -103,6 +105,7 @@ void ESP32BLETracker::loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
this->print_bt_device_info(device);
|
||||
|
||||
@@ -19,8 +19,11 @@ from esphome.cpp_helpers import setup_entity
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
AUTO_LOAD = ["psram"]
|
||||
|
||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
||||
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
|
||||
|
||||
ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize")
|
||||
FRAME_SIZES = {
|
||||
"160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120,
|
||||
@@ -44,30 +47,76 @@ FRAME_SIZES = {
|
||||
"1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200,
|
||||
"UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200,
|
||||
}
|
||||
ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode")
|
||||
ENUM_GAIN_CONTROL_MODE = {
|
||||
"MANUAL": ESP32GainControlMode.ESP32_GC_MODE_MANU,
|
||||
"AUTO": ESP32GainControlMode.ESP32_GC_MODE_AUTO,
|
||||
}
|
||||
ESP32AgcGainCeiling = esp32_camera_ns.enum("ESP32AgcGainCeiling")
|
||||
ENUM_GAIN_CEILING = {
|
||||
"2X": ESP32AgcGainCeiling.ESP32_GAINCEILING_2X,
|
||||
"4X": ESP32AgcGainCeiling.ESP32_GAINCEILING_4X,
|
||||
"8X": ESP32AgcGainCeiling.ESP32_GAINCEILING_8X,
|
||||
"16X": ESP32AgcGainCeiling.ESP32_GAINCEILING_16X,
|
||||
"32X": ESP32AgcGainCeiling.ESP32_GAINCEILING_32X,
|
||||
"64X": ESP32AgcGainCeiling.ESP32_GAINCEILING_64X,
|
||||
"128X": ESP32AgcGainCeiling.ESP32_GAINCEILING_128X,
|
||||
}
|
||||
ESP32WhiteBalanceMode = esp32_camera_ns.enum("ESP32WhiteBalanceMode")
|
||||
ENUM_WB_MODE = {
|
||||
"AUTO": ESP32WhiteBalanceMode.ESP32_WB_MODE_AUTO,
|
||||
"SUNNY": ESP32WhiteBalanceMode.ESP32_WB_MODE_SUNNY,
|
||||
"CLOUDY": ESP32WhiteBalanceMode.ESP32_WB_MODE_CLOUDY,
|
||||
"OFFICE": ESP32WhiteBalanceMode.ESP32_WB_MODE_OFFICE,
|
||||
"HOME": ESP32WhiteBalanceMode.ESP32_WB_MODE_HOME,
|
||||
}
|
||||
ESP32SpecialEffect = esp32_camera_ns.enum("ESP32SpecialEffect")
|
||||
ENUM_SPECIAL_EFFECT = {
|
||||
"NONE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NONE,
|
||||
"NEGATIVE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NEGATIVE,
|
||||
"GRAYSCALE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GRAYSCALE,
|
||||
"RED_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_RED_TINT,
|
||||
"GREEN_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GREEN_TINT,
|
||||
"BLUE_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_BLUE_TINT,
|
||||
"SEPIA": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_SEPIA,
|
||||
}
|
||||
|
||||
# pin assignment
|
||||
CONF_VSYNC_PIN = "vsync_pin"
|
||||
CONF_HREF_PIN = "href_pin"
|
||||
CONF_PIXEL_CLOCK_PIN = "pixel_clock_pin"
|
||||
CONF_EXTERNAL_CLOCK = "external_clock"
|
||||
CONF_I2C_PINS = "i2c_pins"
|
||||
CONF_POWER_DOWN_PIN = "power_down_pin"
|
||||
|
||||
CONF_MAX_FRAMERATE = "max_framerate"
|
||||
CONF_IDLE_FRAMERATE = "idle_framerate"
|
||||
# image
|
||||
CONF_JPEG_QUALITY = "jpeg_quality"
|
||||
CONF_VERTICAL_FLIP = "vertical_flip"
|
||||
CONF_HORIZONTAL_MIRROR = "horizontal_mirror"
|
||||
CONF_SATURATION = "saturation"
|
||||
CONF_SPECIAL_EFFECT = "special_effect"
|
||||
# exposure
|
||||
CONF_AEC_MODE = "aec_mode"
|
||||
CONF_AEC2 = "aec2"
|
||||
CONF_AE_LEVEL = "ae_level"
|
||||
CONF_AEC_VALUE = "aec_value"
|
||||
CONF_SATURATION = "saturation"
|
||||
# gains
|
||||
CONF_AGC_MODE = "agc_mode"
|
||||
CONF_AGC_VALUE = "agc_value"
|
||||
CONF_AGC_GAIN_CEILING = "agc_gain_ceiling"
|
||||
# white balance
|
||||
CONF_WB_MODE = "wb_mode"
|
||||
# test pattern
|
||||
CONF_TEST_PATTERN = "test_pattern"
|
||||
# framerates
|
||||
CONF_MAX_FRAMERATE = "max_framerate"
|
||||
CONF_IDLE_FRAMERATE = "idle_framerate"
|
||||
|
||||
camera_range_param = cv.int_range(min=-2, max=2)
|
||||
|
||||
CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32Camera),
|
||||
# pin assignment
|
||||
cv.Required(CONF_DATA_PINS): cv.All(
|
||||
[pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8)
|
||||
),
|
||||
@@ -90,12 +139,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
),
|
||||
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All(
|
||||
cv.framerate, cv.Range(min=0, min_included=False, max=60)
|
||||
),
|
||||
cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All(
|
||||
cv.framerate, cv.Range(min=0, max=1)
|
||||
),
|
||||
# image
|
||||
cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum(
|
||||
FRAME_SIZES, upper=True
|
||||
),
|
||||
@@ -105,29 +149,66 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
cv.Optional(CONF_SATURATION, default=0): camera_range_param,
|
||||
cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean,
|
||||
cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_SPECIAL_EFFECT, default="NONE"): cv.enum(
|
||||
ENUM_SPECIAL_EFFECT, upper=True
|
||||
),
|
||||
# exposure
|
||||
cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum(
|
||||
ENUM_GAIN_CONTROL_MODE, upper=True
|
||||
),
|
||||
cv.Optional(CONF_AEC2, default=False): cv.boolean,
|
||||
cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param,
|
||||
cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200),
|
||||
# gains
|
||||
cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum(
|
||||
ENUM_GAIN_CONTROL_MODE, upper=True
|
||||
),
|
||||
cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30),
|
||||
cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum(
|
||||
ENUM_GAIN_CEILING, upper=True
|
||||
),
|
||||
# white balance
|
||||
cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum(ENUM_WB_MODE, upper=True),
|
||||
# test pattern
|
||||
cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean,
|
||||
# framerates
|
||||
cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All(
|
||||
cv.framerate, cv.Range(min=0, min_included=False, max=60)
|
||||
),
|
||||
cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All(
|
||||
cv.framerate, cv.Range(min=0, max=1)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
SETTERS = {
|
||||
# pin assignment
|
||||
CONF_DATA_PINS: "set_data_pins",
|
||||
CONF_VSYNC_PIN: "set_vsync_pin",
|
||||
CONF_HREF_PIN: "set_href_pin",
|
||||
CONF_PIXEL_CLOCK_PIN: "set_pixel_clock_pin",
|
||||
CONF_RESET_PIN: "set_reset_pin",
|
||||
CONF_POWER_DOWN_PIN: "set_power_down_pin",
|
||||
# image
|
||||
CONF_JPEG_QUALITY: "set_jpeg_quality",
|
||||
CONF_VERTICAL_FLIP: "set_vertical_flip",
|
||||
CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror",
|
||||
CONF_AEC2: "set_aec2",
|
||||
CONF_AE_LEVEL: "set_ae_level",
|
||||
CONF_AEC_VALUE: "set_aec_value",
|
||||
CONF_CONTRAST: "set_contrast",
|
||||
CONF_BRIGHTNESS: "set_brightness",
|
||||
CONF_SATURATION: "set_saturation",
|
||||
CONF_SPECIAL_EFFECT: "set_special_effect",
|
||||
# exposure
|
||||
CONF_AEC_MODE: "set_aec_mode",
|
||||
CONF_AEC2: "set_aec2",
|
||||
CONF_AE_LEVEL: "set_ae_level",
|
||||
CONF_AEC_VALUE: "set_aec_value",
|
||||
# gains
|
||||
CONF_AGC_MODE: "set_agc_mode",
|
||||
CONF_AGC_VALUE: "set_agc_value",
|
||||
CONF_AGC_GAIN_CEILING: "set_agc_gain_ceiling",
|
||||
# white balance
|
||||
CONF_WB_MODE: "set_wb_mode",
|
||||
# test pattern
|
||||
CONF_TEST_PATTERN: "set_test_pattern",
|
||||
}
|
||||
|
||||
@@ -153,9 +234,7 @@ async def to_code(config):
|
||||
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add_define("USE_ESP32_CAMERA")
|
||||
cg.add_build_flag("-DBOARD_HAS_PSRAM")
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
cg.add_library("espressif/esp32-camera", "1.0.0")
|
||||
add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP32_SPIRAM_SUPPORT", True)
|
||||
|
||||
@@ -11,10 +11,14 @@ namespace esp32_camera {
|
||||
|
||||
static const char *const TAG = "esp32_camera";
|
||||
|
||||
/* ---------------- public API (derivated) ---------------- */
|
||||
void ESP32Camera::setup() {
|
||||
global_esp32_camera = this;
|
||||
|
||||
/* initialize time to now */
|
||||
this->last_update_ = millis();
|
||||
|
||||
/* initialize camera */
|
||||
esp_err_t err = esp_camera_init(&this->config_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_camera_init failed: %s", esp_err_to_name(err));
|
||||
@@ -23,16 +27,10 @@ void ESP32Camera::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
s->set_vflip(s, this->vertical_flip_);
|
||||
s->set_hmirror(s, this->horizontal_mirror_);
|
||||
s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable
|
||||
s->set_ae_level(s, this->ae_level_); // -2 to 2
|
||||
s->set_aec_value(s, this->aec_value_); // 0 to 1200
|
||||
s->set_contrast(s, this->contrast_);
|
||||
s->set_brightness(s, this->brightness_);
|
||||
s->set_saturation(s, this->saturation_);
|
||||
s->set_colorbar(s, this->test_pattern_);
|
||||
/* initialize camera parameters */
|
||||
this->update_camera_parameters();
|
||||
|
||||
/* initialize RTOS */
|
||||
this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
|
||||
this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
|
||||
xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
|
||||
@@ -44,14 +42,12 @@ void ESP32Camera::setup() {
|
||||
1 // core
|
||||
);
|
||||
}
|
||||
|
||||
void ESP32Camera::dump_config() {
|
||||
auto conf = this->config_;
|
||||
ESP_LOGCONFIG(TAG, "ESP32 Camera:");
|
||||
ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str());
|
||||
ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_));
|
||||
#ifdef USE_ARDUINO
|
||||
ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound()));
|
||||
#endif // USE_ARDUINO
|
||||
ESP_LOGCONFIG(TAG, " Data Pins: D0:%d D1:%d D2:%d D3:%d D4:%d D5:%d D6:%d D7:%d", conf.pin_d0, conf.pin_d1,
|
||||
conf.pin_d2, conf.pin_d3, conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7);
|
||||
ESP_LOGCONFIG(TAG, " VSYNC Pin: %d", conf.pin_vsync);
|
||||
@@ -109,17 +105,17 @@ void ESP32Camera::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation);
|
||||
ESP_LOGCONFIG(TAG, " Vertical Flip: %s", ONOFF(st.vflip));
|
||||
ESP_LOGCONFIG(TAG, " Horizontal Mirror: %s", ONOFF(st.hmirror));
|
||||
// ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect);
|
||||
// ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode);
|
||||
ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect);
|
||||
ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode);
|
||||
// ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb);
|
||||
// ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain);
|
||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec);
|
||||
ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec);
|
||||
ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2);
|
||||
ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level);
|
||||
ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value);
|
||||
// ESP_LOGCONFIG(TAG, " AGC: %u", st.agc);
|
||||
// ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain);
|
||||
// ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling);
|
||||
ESP_LOGCONFIG(TAG, " AGC: %u", st.agc);
|
||||
ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain);
|
||||
ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling);
|
||||
// ESP_LOGCONFIG(TAG, " BPC: %u", st.bpc);
|
||||
// ESP_LOGCONFIG(TAG, " WPC: %u", st.wpc);
|
||||
// ESP_LOGCONFIG(TAG, " RAW_GMA: %u", st.raw_gma);
|
||||
@@ -127,6 +123,7 @@ void ESP32Camera::dump_config() {
|
||||
// ESP_LOGCONFIG(TAG, " DCW: %u", st.dcw);
|
||||
ESP_LOGCONFIG(TAG, " Test Pattern: %s", YESNO(st.colorbar));
|
||||
}
|
||||
|
||||
void ESP32Camera::loop() {
|
||||
// check if we can return the image
|
||||
if (this->can_return_image_()) {
|
||||
@@ -136,6 +133,13 @@ void ESP32Camera::loop() {
|
||||
this->current_image_.reset();
|
||||
}
|
||||
|
||||
// request idle image every idle_update_interval
|
||||
const uint32_t now = millis();
|
||||
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
||||
this->last_idle_request_ = now;
|
||||
this->request_image(IDLE);
|
||||
}
|
||||
|
||||
// Check if we should fetch a new image
|
||||
if (!this->has_requested_image_())
|
||||
return;
|
||||
@@ -143,7 +147,6 @@ void ESP32Camera::loop() {
|
||||
// image is still in use
|
||||
return;
|
||||
}
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_update_ <= this->max_update_interval_)
|
||||
return;
|
||||
|
||||
@@ -160,22 +163,17 @@ void ESP32Camera::loop() {
|
||||
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
|
||||
return;
|
||||
}
|
||||
this->current_image_ = std::make_shared<CameraImage>(fb);
|
||||
this->current_image_ = std::make_shared<CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
|
||||
|
||||
ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
|
||||
this->new_image_callback_.call(this->current_image_);
|
||||
this->last_update_ = now;
|
||||
this->single_requester_ = false;
|
||||
}
|
||||
void ESP32Camera::framebuffer_task(void *pv) {
|
||||
while (true) {
|
||||
camera_fb_t *framebuffer = esp_camera_fb_get();
|
||||
xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
|
||||
// return is no-op for config with 1 fb
|
||||
xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
|
||||
esp_camera_fb_return(framebuffer);
|
||||
}
|
||||
this->single_requesters_ = 0;
|
||||
}
|
||||
|
||||
float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
/* ---------------- constructors ---------------- */
|
||||
ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
|
||||
this->config_.pin_pwdn = -1;
|
||||
this->config_.pin_reset = -1;
|
||||
@@ -190,6 +188,9 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
|
||||
global_esp32_camera = this;
|
||||
}
|
||||
ESP32Camera::ESP32Camera() : ESP32Camera("") {}
|
||||
|
||||
/* ---------------- setters ---------------- */
|
||||
/* set pin assignment */
|
||||
void ESP32Camera::set_data_pins(std::array<uint8_t, 8> pins) {
|
||||
this->config_.pin_d0 = pins[0];
|
||||
this->config_.pin_d1 = pins[1];
|
||||
@@ -211,6 +212,10 @@ void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) {
|
||||
this->config_.pin_sscb_sda = sda;
|
||||
this->config_.pin_sscb_scl = scl;
|
||||
}
|
||||
void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; }
|
||||
void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; }
|
||||
|
||||
/* set image parameters */
|
||||
void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) {
|
||||
switch (size) {
|
||||
case ESP32_CAMERA_SIZE_160X120:
|
||||
@@ -246,50 +251,81 @@ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) {
|
||||
}
|
||||
}
|
||||
void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; }
|
||||
void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; }
|
||||
void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; }
|
||||
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f) {
|
||||
this->new_image_callback_.add(std::move(f));
|
||||
}
|
||||
void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; }
|
||||
void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; }
|
||||
void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; }
|
||||
void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; }
|
||||
void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; }
|
||||
void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; }
|
||||
void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; }
|
||||
void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; }
|
||||
float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; }
|
||||
uint32_t ESP32Camera::hash_base() { return 3010542557UL; }
|
||||
void ESP32Camera::request_image() { this->single_requester_ = true; }
|
||||
void ESP32Camera::request_stream() { this->last_stream_request_ = millis(); }
|
||||
bool ESP32Camera::has_requested_image_() const {
|
||||
if (this->single_requester_)
|
||||
// single request
|
||||
return true;
|
||||
|
||||
uint32_t now = millis();
|
||||
if (now - this->last_stream_request_ < 5000)
|
||||
// stream request
|
||||
return true;
|
||||
|
||||
if (this->idle_update_interval_ != 0 && now - this->last_update_ > this->idle_update_interval_)
|
||||
// idle update
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
|
||||
void ESP32Camera::set_special_effect(ESP32SpecialEffect effect) { this->special_effect_ = effect; }
|
||||
/* set exposure parameters */
|
||||
void ESP32Camera::set_aec_mode(ESP32GainControlMode mode) { this->aec_mode_ = mode; }
|
||||
void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; }
|
||||
void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; }
|
||||
void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; }
|
||||
/* set gains parameters */
|
||||
void ESP32Camera::set_agc_mode(ESP32GainControlMode mode) { this->agc_mode_ = mode; }
|
||||
void ESP32Camera::set_agc_value(uint8_t agc_value) { this->agc_value_ = agc_value; }
|
||||
void ESP32Camera::set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling) { this->agc_gain_ceiling_ = gain_ceiling; }
|
||||
/* set white balance */
|
||||
void ESP32Camera::set_wb_mode(ESP32WhiteBalanceMode mode) { this->wb_mode_ = mode; }
|
||||
/* set test mode */
|
||||
void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; }
|
||||
/* set fps */
|
||||
void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) {
|
||||
this->max_update_interval_ = max_update_interval;
|
||||
}
|
||||
void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) {
|
||||
this->idle_update_interval_ = idle_update_interval;
|
||||
}
|
||||
void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; }
|
||||
|
||||
/* ---------------- public API (specific) ---------------- */
|
||||
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f) {
|
||||
this->new_image_callback_.add(std::move(f));
|
||||
}
|
||||
void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= (1U << requester); }
|
||||
void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1U << requester); }
|
||||
void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); }
|
||||
void ESP32Camera::update_camera_parameters() {
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
/* update image */
|
||||
s->set_vflip(s, this->vertical_flip_);
|
||||
s->set_hmirror(s, this->horizontal_mirror_);
|
||||
s->set_contrast(s, this->contrast_);
|
||||
s->set_brightness(s, this->brightness_);
|
||||
s->set_saturation(s, this->saturation_);
|
||||
s->set_special_effect(s, (int) this->special_effect_); // 0 to 6
|
||||
/* update exposure */
|
||||
s->set_exposure_ctrl(s, (bool) this->aec_mode_);
|
||||
s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable
|
||||
s->set_ae_level(s, this->ae_level_); // -2 to 2
|
||||
s->set_aec_value(s, this->aec_value_); // 0 to 1200
|
||||
/* update gains */
|
||||
s->set_gain_ctrl(s, (bool) this->agc_mode_);
|
||||
s->set_agc_gain(s, (int) this->agc_value_); // 0 to 30
|
||||
s->set_gainceiling(s, (gainceiling_t) this->agc_gain_ceiling_);
|
||||
/* update white balance mode */
|
||||
s->set_wb_mode(s, (int) this->wb_mode_); // 0 to 4
|
||||
/* update test patern */
|
||||
s->set_colorbar(s, this->test_pattern_);
|
||||
}
|
||||
|
||||
/* ---------------- Internal methods ---------------- */
|
||||
uint32_t ESP32Camera::hash_base() { return 3010542557UL; }
|
||||
bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; }
|
||||
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
|
||||
void ESP32Camera::framebuffer_task(void *pv) {
|
||||
while (true) {
|
||||
camera_fb_t *framebuffer = esp_camera_fb_get();
|
||||
xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
|
||||
// return is no-op for config with 1 fb
|
||||
xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
|
||||
esp_camera_fb_return(framebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
/* ---------------- CameraImageReader class ---------------- */
|
||||
void CameraImageReader::set_image(std::shared_ptr<CameraImage> image) {
|
||||
this->image_ = std::move(image);
|
||||
this->offset_ = 0;
|
||||
@@ -304,10 +340,15 @@ void CameraImageReader::return_image() { this->image_.reset(); }
|
||||
void CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; }
|
||||
uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; }
|
||||
|
||||
/* ---------------- CameraImage class ---------------- */
|
||||
CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {}
|
||||
|
||||
camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; }
|
||||
uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; }
|
||||
size_t CameraImage::get_data_length() { return this->buffer_->len; }
|
||||
CameraImage::CameraImage(camera_fb_t *buffer) : buffer_(buffer) {}
|
||||
bool CameraImage::was_requested_by(CameraRequester requester) const {
|
||||
return (this->requesters_ & (1 << requester)) != 0;
|
||||
}
|
||||
|
||||
} // namespace esp32_camera
|
||||
} // namespace esphome
|
||||
|
||||
@@ -14,29 +14,8 @@ namespace esp32_camera {
|
||||
|
||||
class ESP32Camera;
|
||||
|
||||
class CameraImage {
|
||||
public:
|
||||
CameraImage(camera_fb_t *buffer);
|
||||
camera_fb_t *get_raw_buffer();
|
||||
uint8_t *get_data_buffer();
|
||||
size_t get_data_length();
|
||||
|
||||
protected:
|
||||
camera_fb_t *buffer_;
|
||||
};
|
||||
|
||||
class CameraImageReader {
|
||||
public:
|
||||
void set_image(std::shared_ptr<CameraImage> image);
|
||||
size_t available() const;
|
||||
uint8_t *peek_data_buffer();
|
||||
void consume_data(size_t consumed);
|
||||
void return_image();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<CameraImage> image_;
|
||||
size_t offset_{0};
|
||||
};
|
||||
/* ---------------- enum classes ---------------- */
|
||||
enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER };
|
||||
|
||||
enum ESP32CameraFrameSize {
|
||||
ESP32_CAMERA_SIZE_160X120, // QQVGA
|
||||
@@ -51,66 +30,165 @@ enum ESP32CameraFrameSize {
|
||||
ESP32_CAMERA_SIZE_1600X1200, // UXGA
|
||||
};
|
||||
|
||||
enum ESP32AgcGainCeiling {
|
||||
ESP32_GAINCEILING_2X = GAINCEILING_2X,
|
||||
ESP32_GAINCEILING_4X = GAINCEILING_4X,
|
||||
ESP32_GAINCEILING_8X = GAINCEILING_8X,
|
||||
ESP32_GAINCEILING_16X = GAINCEILING_16X,
|
||||
ESP32_GAINCEILING_32X = GAINCEILING_32X,
|
||||
ESP32_GAINCEILING_64X = GAINCEILING_64X,
|
||||
ESP32_GAINCEILING_128X = GAINCEILING_128X,
|
||||
};
|
||||
|
||||
enum ESP32GainControlMode {
|
||||
ESP32_GC_MODE_MANU = false,
|
||||
ESP32_GC_MODE_AUTO = true,
|
||||
};
|
||||
|
||||
enum ESP32WhiteBalanceMode {
|
||||
ESP32_WB_MODE_AUTO = 0U,
|
||||
ESP32_WB_MODE_SUNNY = 1U,
|
||||
ESP32_WB_MODE_CLOUDY = 2U,
|
||||
ESP32_WB_MODE_OFFICE = 3U,
|
||||
ESP32_WB_MODE_HOME = 4U,
|
||||
};
|
||||
|
||||
enum ESP32SpecialEffect {
|
||||
ESP32_SPECIAL_EFFECT_NONE = 0U,
|
||||
ESP32_SPECIAL_EFFECT_NEGATIVE = 1U,
|
||||
ESP32_SPECIAL_EFFECT_GRAYSCALE = 2U,
|
||||
ESP32_SPECIAL_EFFECT_RED_TINT = 3U,
|
||||
ESP32_SPECIAL_EFFECT_GREEN_TINT = 4U,
|
||||
ESP32_SPECIAL_EFFECT_BLUE_TINT = 5U,
|
||||
ESP32_SPECIAL_EFFECT_SEPIA = 6U,
|
||||
};
|
||||
|
||||
/* ---------------- CameraImage class ---------------- */
|
||||
class CameraImage {
|
||||
public:
|
||||
CameraImage(camera_fb_t *buffer, uint8_t requester);
|
||||
camera_fb_t *get_raw_buffer();
|
||||
uint8_t *get_data_buffer();
|
||||
size_t get_data_length();
|
||||
bool was_requested_by(CameraRequester requester) const;
|
||||
|
||||
protected:
|
||||
camera_fb_t *buffer_;
|
||||
uint8_t requesters_;
|
||||
};
|
||||
|
||||
/* ---------------- CameraImageReader class ---------------- */
|
||||
class CameraImageReader {
|
||||
public:
|
||||
void set_image(std::shared_ptr<CameraImage> image);
|
||||
size_t available() const;
|
||||
uint8_t *peek_data_buffer();
|
||||
void consume_data(size_t consumed);
|
||||
void return_image();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<CameraImage> image_;
|
||||
size_t offset_{0};
|
||||
};
|
||||
|
||||
/* ---------------- ESP32Camera class ---------------- */
|
||||
class ESP32Camera : public Component, public EntityBase {
|
||||
public:
|
||||
ESP32Camera(const std::string &name);
|
||||
ESP32Camera();
|
||||
|
||||
/* setters */
|
||||
/* -- pin assignment */
|
||||
void set_data_pins(std::array<uint8_t, 8> pins);
|
||||
void set_vsync_pin(uint8_t pin);
|
||||
void set_href_pin(uint8_t pin);
|
||||
void set_pixel_clock_pin(uint8_t pin);
|
||||
void set_external_clock(uint8_t pin, uint32_t frequency);
|
||||
void set_i2c_pins(uint8_t sda, uint8_t scl);
|
||||
void set_frame_size(ESP32CameraFrameSize size);
|
||||
void set_jpeg_quality(uint8_t quality);
|
||||
void set_reset_pin(uint8_t pin);
|
||||
void set_power_down_pin(uint8_t pin);
|
||||
/* -- image */
|
||||
void set_frame_size(ESP32CameraFrameSize size);
|
||||
void set_jpeg_quality(uint8_t quality);
|
||||
void set_vertical_flip(bool vertical_flip);
|
||||
void set_horizontal_mirror(bool horizontal_mirror);
|
||||
void set_aec2(bool aec2);
|
||||
void set_ae_level(int ae_level);
|
||||
void set_aec_value(uint32_t aec_value);
|
||||
void set_contrast(int contrast);
|
||||
void set_brightness(int brightness);
|
||||
void set_saturation(int saturation);
|
||||
void set_special_effect(ESP32SpecialEffect effect);
|
||||
/* -- exposure */
|
||||
void set_aec_mode(ESP32GainControlMode mode);
|
||||
void set_aec2(bool aec2);
|
||||
void set_ae_level(int ae_level);
|
||||
void set_aec_value(uint32_t aec_value);
|
||||
/* -- gains */
|
||||
void set_agc_mode(ESP32GainControlMode mode);
|
||||
void set_agc_value(uint8_t agc_value);
|
||||
void set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling);
|
||||
/* -- white balance */
|
||||
void set_wb_mode(ESP32WhiteBalanceMode mode);
|
||||
/* -- test */
|
||||
void set_test_pattern(bool test_pattern);
|
||||
/* -- framerates */
|
||||
void set_max_update_interval(uint32_t max_update_interval);
|
||||
void set_idle_update_interval(uint32_t idle_update_interval);
|
||||
void set_test_pattern(bool test_pattern);
|
||||
|
||||
/* public API (derivated) */
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f);
|
||||
float get_setup_priority() const override;
|
||||
void request_stream();
|
||||
void request_image();
|
||||
/* public API (specific) */
|
||||
void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&f);
|
||||
void start_stream(CameraRequester requester);
|
||||
void stop_stream(CameraRequester requester);
|
||||
void request_image(CameraRequester requester);
|
||||
void update_camera_parameters();
|
||||
|
||||
protected:
|
||||
/* internal methods */
|
||||
uint32_t hash_base() override;
|
||||
bool has_requested_image_() const;
|
||||
bool can_return_image_() const;
|
||||
|
||||
static void framebuffer_task(void *pv);
|
||||
|
||||
/* attributes */
|
||||
/* camera configuration */
|
||||
camera_config_t config_{};
|
||||
/* -- image */
|
||||
bool vertical_flip_{true};
|
||||
bool horizontal_mirror_{true};
|
||||
bool aec2_{false};
|
||||
int ae_level_{0};
|
||||
uint32_t aec_value_{300};
|
||||
int contrast_{0};
|
||||
int brightness_{0};
|
||||
int saturation_{0};
|
||||
ESP32SpecialEffect special_effect_{ESP32_SPECIAL_EFFECT_NONE};
|
||||
/* -- exposure */
|
||||
ESP32GainControlMode aec_mode_{ESP32_GC_MODE_AUTO};
|
||||
bool aec2_{false};
|
||||
int ae_level_{0};
|
||||
uint32_t aec_value_{300};
|
||||
/* -- gains */
|
||||
ESP32GainControlMode agc_mode_{ESP32_GC_MODE_AUTO};
|
||||
uint8_t agc_value_{0};
|
||||
ESP32AgcGainCeiling agc_gain_ceiling_{ESP32_GAINCEILING_2X};
|
||||
/* -- white balance */
|
||||
ESP32WhiteBalanceMode wb_mode_{ESP32_WB_MODE_AUTO};
|
||||
/* -- Test */
|
||||
bool test_pattern_{false};
|
||||
/* -- framerates */
|
||||
uint32_t max_update_interval_{1000};
|
||||
uint32_t idle_update_interval_{15000};
|
||||
|
||||
esp_err_t init_error_{ESP_OK};
|
||||
std::shared_ptr<CameraImage> current_image_;
|
||||
uint32_t last_stream_request_{0};
|
||||
bool single_requester_{false};
|
||||
uint8_t single_requesters_{0};
|
||||
uint8_t stream_requesters_{0};
|
||||
QueueHandle_t framebuffer_get_queue_;
|
||||
QueueHandle_t framebuffer_return_queue_;
|
||||
CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_;
|
||||
uint32_t max_update_interval_{1000};
|
||||
uint32_t idle_update_interval_{15000};
|
||||
|
||||
uint32_t last_idle_request_{0};
|
||||
uint32_t last_update_{0};
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
namespace esphome {
|
||||
namespace esp32_camera_web_server {
|
||||
|
||||
static const int IMAGE_REQUEST_TIMEOUT = 2000;
|
||||
static const int IMAGE_REQUEST_TIMEOUT = 5000;
|
||||
static const char *const TAG = "esp32_camera_web_server";
|
||||
|
||||
#define PART_BOUNDARY "123456789000000000000987654321"
|
||||
@@ -68,7 +68,7 @@ void CameraWebServer::setup() {
|
||||
httpd_register_uri_handler(this->httpd_, &uri);
|
||||
|
||||
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (this->running_) {
|
||||
if (this->running_ && image->was_requested_by(esp32_camera::WEB_REQUESTER)) {
|
||||
this->image_ = std::move(image);
|
||||
xSemaphoreGive(this->semaphore_);
|
||||
}
|
||||
@@ -87,10 +87,11 @@ void CameraWebServer::on_shutdown() {
|
||||
void CameraWebServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:");
|
||||
ESP_LOGCONFIG(TAG, " Port: %d", this->port_);
|
||||
if (this->mode_ == STREAM)
|
||||
if (this->mode_ == STREAM) {
|
||||
ESP_LOGCONFIG(TAG, " Mode: stream");
|
||||
else
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Mode: snapshot");
|
||||
}
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, " Setup Failed");
|
||||
@@ -169,11 +170,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||
uint32_t last_frame = millis();
|
||||
uint32_t frames = 0;
|
||||
|
||||
while (res == ESP_OK && this->running_) {
|
||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
||||
esp32_camera::global_esp32_camera->request_stream();
|
||||
}
|
||||
esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::WEB_REQUESTER);
|
||||
|
||||
while (res == ESP_OK && this->running_) {
|
||||
auto image = this->wait_for_image_();
|
||||
|
||||
if (!image) {
|
||||
@@ -204,6 +203,8 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
|
||||
}
|
||||
|
||||
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER);
|
||||
|
||||
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
|
||||
|
||||
return res;
|
||||
@@ -212,9 +213,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
|
||||
esp_err_t res = ESP_OK;
|
||||
|
||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
||||
esp32_camera::global_esp32_camera->request_image();
|
||||
}
|
||||
esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::WEB_REQUESTER);
|
||||
|
||||
auto image = this->wait_for_image_();
|
||||
|
||||
@@ -233,9 +232,6 @@ esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
|
||||
|
||||
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
|
||||
|
||||
if (res == ESP_OK) {
|
||||
res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str());
|
||||
}
|
||||
if (res == ESP_OK) {
|
||||
res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ class CameraWebServer : public Component {
|
||||
esp_err_t streaming_handler_(struct httpd_req *req);
|
||||
esp_err_t snapshot_handler_(struct httpd_req *req);
|
||||
|
||||
protected:
|
||||
uint16_t port_{0};
|
||||
void *httpd_{nullptr};
|
||||
SemaphoreHandle_t semaphore_;
|
||||
|
||||
0
esphome/components/esp32_can/__init__.py
Normal file
0
esphome/components/esp32_can/__init__.py
Normal file
39
esphome/components/esp32_can/canbus.py
Normal file
39
esphome/components/esp32_can/canbus.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import canbus
|
||||
from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN
|
||||
from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE
|
||||
|
||||
CODEOWNERS = ["@Sympatron"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_can_ns = cg.esphome_ns.namespace("esp32_can")
|
||||
esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent)
|
||||
|
||||
# Currently the driver only supports a subset of the bit rates defined in canbus
|
||||
CAN_SPEEDS = {
|
||||
"50KBPS": CanSpeed.CAN_50KBPS,
|
||||
"100KBPS": CanSpeed.CAN_100KBPS,
|
||||
"125KBPS": CanSpeed.CAN_125KBPS,
|
||||
"250KBPS": CanSpeed.CAN_250KBPS,
|
||||
"500KBPS": CanSpeed.CAN_500KBPS,
|
||||
"1000KBPS": CanSpeed.CAN_1000KBPS,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(esp32_can),
|
||||
cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True),
|
||||
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await canbus.register_canbus(var, config)
|
||||
|
||||
cg.add(var.set_rx(config[CONF_RX_PIN]))
|
||||
cg.add(var.set_tx(config[CONF_TX_PIN]))
|
||||
123
esphome/components/esp32_can/esp32_can.cpp
Normal file
123
esphome/components/esp32_can/esp32_can.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#ifdef USE_ESP32
|
||||
#include "esp32_can.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <driver/can.h>
|
||||
|
||||
// WORKAROUND, because CAN_IO_UNUSED is just defined as (-1) in this version
|
||||
// of the framework which does not work with -fpermissive
|
||||
#undef CAN_IO_UNUSED
|
||||
#define CAN_IO_UNUSED ((gpio_num_t) -1)
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_can {
|
||||
|
||||
static const char *const TAG = "esp32_can";
|
||||
|
||||
static bool get_bitrate(canbus::CanSpeed bitrate, can_timing_config_t *t_config) {
|
||||
switch (bitrate) {
|
||||
case canbus::CAN_50KBPS:
|
||||
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_50KBITS();
|
||||
return true;
|
||||
case canbus::CAN_100KBPS:
|
||||
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_100KBITS();
|
||||
return true;
|
||||
case canbus::CAN_125KBPS:
|
||||
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_125KBITS();
|
||||
return true;
|
||||
case canbus::CAN_250KBPS:
|
||||
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_250KBITS();
|
||||
return true;
|
||||
case canbus::CAN_500KBPS:
|
||||
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_500KBITS();
|
||||
return true;
|
||||
case canbus::CAN_1000KBPS:
|
||||
*t_config = (can_timing_config_t) CAN_TIMING_CONFIG_1MBITS();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ESP32Can::setup_internal() {
|
||||
can_general_config_t g_config =
|
||||
CAN_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, CAN_MODE_NORMAL);
|
||||
can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();
|
||||
can_timing_config_t t_config;
|
||||
|
||||
if (!get_bitrate(this->bit_rate_, &t_config)) {
|
||||
// invalid bit rate
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install CAN driver
|
||||
if (can_driver_install(&g_config, &t_config, &f_config) != ESP_OK) {
|
||||
// Failed to install driver
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start CAN driver
|
||||
if (can_start() != ESP_OK) {
|
||||
// Failed to start driver
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
|
||||
if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) {
|
||||
return canbus::ERROR_FAILTX;
|
||||
}
|
||||
|
||||
uint32_t flags = CAN_MSG_FLAG_NONE;
|
||||
if (frame->use_extended_id) {
|
||||
flags |= CAN_MSG_FLAG_EXTD;
|
||||
}
|
||||
if (frame->remote_transmission_request) {
|
||||
flags |= CAN_MSG_FLAG_RTR;
|
||||
}
|
||||
|
||||
can_message_t message = {
|
||||
.flags = flags,
|
||||
.identifier = frame->can_id,
|
||||
.data_length_code = frame->can_data_length_code,
|
||||
};
|
||||
if (!frame->remote_transmission_request) {
|
||||
memcpy(message.data, frame->data, frame->can_data_length_code);
|
||||
}
|
||||
|
||||
if (can_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) {
|
||||
return canbus::ERROR_OK;
|
||||
} else {
|
||||
return canbus::ERROR_ALLTXBUSY;
|
||||
}
|
||||
}
|
||||
|
||||
canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) {
|
||||
can_message_t message;
|
||||
|
||||
if (can_receive(&message, 0) != ESP_OK) {
|
||||
return canbus::ERROR_NOMSG;
|
||||
}
|
||||
|
||||
frame->can_id = message.identifier;
|
||||
frame->use_extended_id = message.flags & CAN_MSG_FLAG_EXTD;
|
||||
frame->remote_transmission_request = message.flags & CAN_MSG_FLAG_RTR;
|
||||
frame->can_data_length_code = message.data_length_code;
|
||||
|
||||
if (!frame->remote_transmission_request) {
|
||||
size_t dlc =
|
||||
message.data_length_code < canbus::CAN_MAX_DATA_LENGTH ? message.data_length_code : canbus::CAN_MAX_DATA_LENGTH;
|
||||
memcpy(frame->data, message.data, dlc);
|
||||
}
|
||||
|
||||
return canbus::ERROR_OK;
|
||||
}
|
||||
|
||||
} // namespace esp32_can
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
29
esphome/components/esp32_can/esp32_can.h
Normal file
29
esphome/components/esp32_can/esp32_can.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/canbus/canbus.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_can {
|
||||
|
||||
class ESP32Can : public canbus::Canbus {
|
||||
public:
|
||||
void set_rx(int rx) { rx_ = rx; }
|
||||
void set_tx(int tx) { tx_ = tx; }
|
||||
ESP32Can(){};
|
||||
|
||||
protected:
|
||||
bool setup_internal() override;
|
||||
canbus::Error send_message(struct canbus::CanFrame *frame) override;
|
||||
canbus::Error read_message(struct canbus::CanFrame *frame) override;
|
||||
|
||||
int rx_{-1};
|
||||
int tx_{-1};
|
||||
};
|
||||
|
||||
} // namespace esp32_can
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
@@ -56,7 +56,7 @@ async def to_code(config):
|
||||
cg.add(ble_server.register_service_component(var))
|
||||
|
||||
cg.add_define("USE_IMPROV")
|
||||
cg.add_library("esphome/Improv", "1.0.0")
|
||||
cg.add_library("esphome/Improv", "1.2.1")
|
||||
|
||||
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
|
||||
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
|
||||
|
||||
@@ -128,7 +128,7 @@ void ESP32ImprovComponent::loop() {
|
||||
std::vector<std::string> urls = {ESPHOME_MY_LINK};
|
||||
#ifdef USE_WEBSERVER
|
||||
auto ip = wifi::global_wifi_component->wifi_sta_ip();
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
#endif
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
|
||||
|
||||
@@ -162,7 +162,7 @@ void ESP32TouchComponent::on_shutdown() {
|
||||
}
|
||||
|
||||
ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold)
|
||||
: BinarySensor(), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {}
|
||||
: touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {}
|
||||
|
||||
} // namespace esp32_touch
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
@@ -14,6 +15,7 @@ from esphome.const import (
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.helpers import copy_file_if_changed
|
||||
|
||||
from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, esp8266_ns
|
||||
from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS
|
||||
@@ -59,7 +61,7 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||
# The default/recommended arduino framework version
|
||||
# - https://github.com/esp8266/Arduino/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4)
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 0, 2)
|
||||
# The platformio/espressif8266 version to use for arduino 2 framework versions
|
||||
# - https://github.com/platformio/platform-espressif8266/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266
|
||||
@@ -158,10 +160,13 @@ async def to_code(config):
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", "ESP8266")
|
||||
|
||||
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
|
||||
cg.add_build_flag("-Wno-nonnull-compare")
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
@@ -194,10 +199,15 @@ async def to_code(config):
|
||||
|
||||
cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE])
|
||||
|
||||
ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
cg.add_define(
|
||||
"USE_ARDUINO_VERSION_CODE",
|
||||
cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"),
|
||||
)
|
||||
|
||||
if config[CONF_BOARD] in ESP8266_FLASH_SIZES:
|
||||
flash_size = ESP8266_FLASH_SIZES[config[CONF_BOARD]]
|
||||
ld_scripts = ESP8266_LD_SCRIPTS[flash_size]
|
||||
ver = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
|
||||
if ver <= cv.Version(2, 3, 0):
|
||||
# No ld script support
|
||||
@@ -210,3 +220,14 @@ async def to_code(config):
|
||||
|
||||
if ld_script is not None:
|
||||
cg.add_platformio_option("board_build.ldscript", ld_script)
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files():
|
||||
|
||||
dir = os.path.dirname(__file__)
|
||||
post_build_file = os.path.join(dir, "post_build.py.script")
|
||||
copy_file_if_changed(
|
||||
post_build_file,
|
||||
CORE.relative_build_path("post_build.py"),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FLASH_SIZE_1_MB = 2 ** 20
|
||||
FLASH_SIZE_1_MB = 2**20
|
||||
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
|
||||
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
|
||||
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
|
||||
|
||||
15
esphome/components/esp8266/post_build.py.script
Normal file
15
esphome/components/esp8266/post_build.py.script
Normal file
@@ -0,0 +1,15 @@
|
||||
import shutil
|
||||
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
|
||||
def esp8266_copy_factory_bin(source, target, env):
|
||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
||||
|
||||
shutil.copyfile(firmware_name, new_file_name)
|
||||
|
||||
|
||||
# pylint: disable=E0602
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin) # noqa
|
||||
@@ -98,16 +98,18 @@ static bool load_from_flash(size_t offset, uint32_t *data, size_t len) {
|
||||
}
|
||||
|
||||
static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) {
|
||||
for (uint32_t i = 0; i < len; i++)
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (!esp_rtc_user_mem_write(offset + i, data[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
|
||||
for (uint32_t i = 0; i < len; i++)
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
if (!esp_rtc_user_mem_read(offset + i, &data[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
#include "esp8266_pwm.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0)
|
||||
#error ESP8266 PWM requires at least arduino_version 2.4.0
|
||||
#endif
|
||||
|
||||
#include <core_esp8266_waveform.h>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -23,15 +23,20 @@ ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Componen
|
||||
SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action)
|
||||
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(ESP8266PWM),
|
||||
cv.Required(CONF_PIN): cv.All(
|
||||
pins.internal_gpio_output_pin_schema, valid_pwm_pin
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(ESP8266PWM),
|
||||
cv.Required(CONF_PIN): cv.All(
|
||||
pins.internal_gpio_output_pin_schema, valid_pwm_pin
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 4, 0),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -75,9 +75,10 @@ void EZOSensor::loop() {
|
||||
return;
|
||||
|
||||
// some sensors return multiple comma-separated values, terminate string after first one
|
||||
for (size_t i = 1; i < sizeof(buf) - 1; i++)
|
||||
for (size_t i = 1; i < sizeof(buf) - 1; i++) {
|
||||
if (buf[i] == ',')
|
||||
buf[i] = '\0';
|
||||
}
|
||||
|
||||
float val = parse_number<float>((char *) &buf[1]).value_or(0);
|
||||
this->publish_state(val);
|
||||
|
||||
@@ -19,6 +19,7 @@ from esphome.const import (
|
||||
CONF_ON_TURN_ON,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_DIRECTION,
|
||||
CONF_RESTORE_MODE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
@@ -26,13 +27,24 @@ from esphome.cpp_helpers import setup_entity
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
fan_ns = cg.esphome_ns.namespace("fan")
|
||||
FanState = fan_ns.class_("FanState", cg.EntityBase, cg.Component)
|
||||
MakeFan = cg.Application.struct("MakeFan")
|
||||
Fan = fan_ns.class_("Fan", cg.EntityBase)
|
||||
FanState = fan_ns.class_("Fan", Fan, cg.Component)
|
||||
|
||||
FanDirection = fan_ns.enum("FanDirection")
|
||||
FanDirection = fan_ns.enum("FanDirection", is_class=True)
|
||||
FAN_DIRECTION_ENUM = {
|
||||
"FORWARD": FanDirection.FAN_DIRECTION_FORWARD,
|
||||
"REVERSE": FanDirection.FAN_DIRECTION_REVERSE,
|
||||
"FORWARD": FanDirection.FORWARD,
|
||||
"REVERSE": FanDirection.REVERSE,
|
||||
}
|
||||
|
||||
FanRestoreMode = fan_ns.enum("FanRestoreMode", is_class=True)
|
||||
RESTORE_MODES = {
|
||||
"NO_RESTORE": FanRestoreMode.NO_RESTORE,
|
||||
"ALWAYS_OFF": FanRestoreMode.ALWAYS_OFF,
|
||||
"ALWAYS_ON": FanRestoreMode.ALWAYS_ON,
|
||||
"RESTORE_DEFAULT_OFF": FanRestoreMode.RESTORE_DEFAULT_OFF,
|
||||
"RESTORE_DEFAULT_ON": FanRestoreMode.RESTORE_DEFAULT_ON,
|
||||
"RESTORE_INVERTED_DEFAULT_OFF": FanRestoreMode.RESTORE_INVERTED_DEFAULT_OFF,
|
||||
"RESTORE_INVERTED_DEFAULT_ON": FanRestoreMode.RESTORE_INVERTED_DEFAULT_ON,
|
||||
}
|
||||
|
||||
# Actions
|
||||
@@ -50,7 +62,10 @@ FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.temp
|
||||
|
||||
FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(FanState),
|
||||
cv.GenerateID(): cv.declare_id(Fan),
|
||||
cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum(
|
||||
RESTORE_MODES, upper=True, space="_"
|
||||
),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
|
||||
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
@@ -92,6 +107,8 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte
|
||||
async def setup_fan_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
|
||||
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
@@ -142,19 +159,19 @@ async def register_fan(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_fan(var))
|
||||
await cg.register_component(var, config)
|
||||
await setup_fan_core_(var, config)
|
||||
|
||||
|
||||
async def create_fan_state(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await register_fan(var, config)
|
||||
await cg.register_component(var, config)
|
||||
return var
|
||||
|
||||
|
||||
FAN_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(FanState),
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -176,7 +193,7 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args):
|
||||
TurnOnAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(FanState),
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)),
|
||||
cv.Optional(CONF_DIRECTION): cv.templatable(
|
||||
@@ -211,7 +228,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args):
|
||||
FanIsOnCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(FanState),
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -220,7 +237,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args):
|
||||
FanIsOffCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(FanState),
|
||||
cv.Required(CONF_ID): cv.use_id(Fan),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "automation.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
static const char *const TAG = "fan.automation";
|
||||
|
||||
} // namespace fan
|
||||
} // namespace esphome
|
||||
@@ -9,7 +9,7 @@ namespace fan {
|
||||
|
||||
template<typename... Ts> class TurnOnAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit TurnOnAction(FanState *state) : state_(state) {}
|
||||
explicit TurnOnAction(Fan *state) : state_(state) {}
|
||||
|
||||
TEMPLATABLE_VALUE(bool, oscillating)
|
||||
TEMPLATABLE_VALUE(int, speed)
|
||||
@@ -29,30 +29,30 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> {
|
||||
call.perform();
|
||||
}
|
||||
|
||||
FanState *state_;
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class TurnOffAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit TurnOffAction(FanState *state) : state_(state) {}
|
||||
explicit TurnOffAction(Fan *state) : state_(state) {}
|
||||
|
||||
void play(Ts... x) override { this->state_->turn_off().perform(); }
|
||||
|
||||
FanState *state_;
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ToggleAction(FanState *state) : state_(state) {}
|
||||
explicit ToggleAction(Fan *state) : state_(state) {}
|
||||
|
||||
void play(Ts... x) override { this->state_->toggle().perform(); }
|
||||
|
||||
FanState *state_;
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class CycleSpeedAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit CycleSpeedAction(FanState *state) : state_(state) {}
|
||||
explicit CycleSpeedAction(Fan *state) : state_(state) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
// check to see if fan supports speeds and is on
|
||||
@@ -83,29 +83,29 @@ template<typename... Ts> class CycleSpeedAction : public Action<Ts...> {
|
||||
}
|
||||
}
|
||||
|
||||
FanState *state_;
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class FanIsOnCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit FanIsOnCondition(FanState *state) : state_(state) {}
|
||||
explicit FanIsOnCondition(Fan *state) : state_(state) {}
|
||||
bool check(Ts... x) override { return this->state_->state; }
|
||||
|
||||
protected:
|
||||
FanState *state_;
|
||||
Fan *state_;
|
||||
};
|
||||
template<typename... Ts> class FanIsOffCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit FanIsOffCondition(FanState *state) : state_(state) {}
|
||||
explicit FanIsOffCondition(Fan *state) : state_(state) {}
|
||||
bool check(Ts... x) override { return !this->state_->state; }
|
||||
|
||||
protected:
|
||||
FanState *state_;
|
||||
Fan *state_;
|
||||
};
|
||||
|
||||
class FanTurnOnTrigger : public Trigger<> {
|
||||
public:
|
||||
FanTurnOnTrigger(FanState *state) {
|
||||
FanTurnOnTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto is_on = state->state;
|
||||
auto should_trigger = is_on && !this->last_on_;
|
||||
@@ -123,7 +123,7 @@ class FanTurnOnTrigger : public Trigger<> {
|
||||
|
||||
class FanTurnOffTrigger : public Trigger<> {
|
||||
public:
|
||||
FanTurnOffTrigger(FanState *state) {
|
||||
FanTurnOffTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto is_on = state->state;
|
||||
auto should_trigger = !is_on && this->last_on_;
|
||||
@@ -141,7 +141,7 @@ class FanTurnOffTrigger : public Trigger<> {
|
||||
|
||||
class FanSpeedSetTrigger : public Trigger<> {
|
||||
public:
|
||||
FanSpeedSetTrigger(FanState *state) {
|
||||
FanSpeedSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
auto speed = state->speed;
|
||||
auto should_trigger = speed != !this->last_speed_;
|
||||
|
||||
175
esphome/components/fan/fan.cpp
Normal file
175
esphome/components/fan/fan.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "fan.h"
|
||||
#include "fan_helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
static const char *const TAG = "fan";
|
||||
|
||||
const LogString *fan_direction_to_string(FanDirection direction) {
|
||||
switch (direction) {
|
||||
case FanDirection::FORWARD:
|
||||
return LOG_STR("FORWARD");
|
||||
case FanDirection::REVERSE:
|
||||
return LOG_STR("REVERSE");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
void FanCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str());
|
||||
this->validate_();
|
||||
if (this->binary_state_.has_value())
|
||||
ESP_LOGD(TAG, " State: %s", ONOFF(*this->binary_state_));
|
||||
if (this->oscillating_.has_value())
|
||||
ESP_LOGD(TAG, " Oscillating: %s", YESNO(*this->oscillating_));
|
||||
if (this->speed_.has_value())
|
||||
ESP_LOGD(TAG, " Speed: %d", *this->speed_);
|
||||
if (this->direction_.has_value())
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
|
||||
|
||||
this->parent_.control(*this);
|
||||
}
|
||||
void FanCall::validate_() {
|
||||
auto traits = this->parent_.get_traits();
|
||||
|
||||
if (this->speed_.has_value())
|
||||
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
|
||||
|
||||
if (this->binary_state_.has_value() && *this->binary_state_) {
|
||||
// when turning on, if current speed is zero, set speed to 100%
|
||||
if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0) {
|
||||
this->speed_ = traits.supported_speed_count();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->oscillating_.has_value() && !traits.supports_oscillation()) {
|
||||
ESP_LOGW(TAG, "'%s' - This fan does not support oscillation!", this->parent_.get_name().c_str());
|
||||
this->oscillating_.reset();
|
||||
}
|
||||
|
||||
if (this->speed_.has_value() && !traits.supports_speed()) {
|
||||
ESP_LOGW(TAG, "'%s' - This fan does not support speeds!", this->parent_.get_name().c_str());
|
||||
this->speed_.reset();
|
||||
}
|
||||
|
||||
if (this->direction_.has_value() && !traits.supports_direction()) {
|
||||
ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str());
|
||||
this->direction_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// This whole method is deprecated, don't warn about usage of deprecated methods inside of it.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
FanCall &FanCall::set_speed(const char *legacy_speed) {
|
||||
const auto supported_speed_count = this->parent_.get_traits().supported_speed_count();
|
||||
if (strcasecmp(legacy_speed, "low") == 0) {
|
||||
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count));
|
||||
} else if (strcasecmp(legacy_speed, "medium") == 0) {
|
||||
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count));
|
||||
} else if (strcasecmp(legacy_speed, "high") == 0) {
|
||||
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
FanCall FanRestoreState::to_call(Fan &fan) {
|
||||
auto call = fan.make_call();
|
||||
call.set_state(this->state);
|
||||
call.set_oscillating(this->oscillating);
|
||||
call.set_speed(this->speed);
|
||||
call.set_direction(this->direction);
|
||||
return call;
|
||||
}
|
||||
void FanRestoreState::apply(Fan &fan) {
|
||||
fan.state = this->state;
|
||||
fan.oscillating = this->oscillating;
|
||||
fan.speed = this->speed;
|
||||
fan.direction = this->direction;
|
||||
fan.publish_state();
|
||||
}
|
||||
|
||||
Fan::Fan() : EntityBase("") {}
|
||||
Fan::Fan(const std::string &name) : EntityBase(name) {}
|
||||
|
||||
FanCall Fan::turn_on() { return this->make_call().set_state(true); }
|
||||
FanCall Fan::turn_off() { return this->make_call().set_state(false); }
|
||||
FanCall Fan::toggle() { return this->make_call().set_state(!this->state); }
|
||||
FanCall Fan::make_call() { return FanCall(*this); }
|
||||
|
||||
void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||
void Fan::publish_state() {
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str());
|
||||
ESP_LOGD(TAG, " State: %s", ONOFF(this->state));
|
||||
if (traits.supports_speed())
|
||||
ESP_LOGD(TAG, " Speed: %d", this->speed);
|
||||
if (traits.supports_oscillation())
|
||||
ESP_LOGD(TAG, " Oscillating: %s", YESNO(this->oscillating));
|
||||
if (traits.supports_direction())
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
|
||||
|
||||
this->state_callback_.call();
|
||||
this->save_state_();
|
||||
}
|
||||
|
||||
// Random 32-bit value, change this every time the layout of the FanRestoreState struct changes.
|
||||
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
|
||||
optional<FanRestoreState> Fan::restore_state_() {
|
||||
FanRestoreState recovered{};
|
||||
this->rtc_ = global_preferences->make_preference<FanRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
|
||||
bool restored = this->rtc_.load(&recovered);
|
||||
|
||||
switch (this->restore_mode_) {
|
||||
case FanRestoreMode::NO_RESTORE:
|
||||
return {};
|
||||
case FanRestoreMode::ALWAYS_OFF:
|
||||
recovered.state = false;
|
||||
return recovered;
|
||||
case FanRestoreMode::ALWAYS_ON:
|
||||
recovered.state = true;
|
||||
return recovered;
|
||||
case FanRestoreMode::RESTORE_DEFAULT_OFF:
|
||||
recovered.state = restored ? recovered.state : false;
|
||||
return recovered;
|
||||
case FanRestoreMode::RESTORE_DEFAULT_ON:
|
||||
recovered.state = restored ? recovered.state : true;
|
||||
return recovered;
|
||||
case FanRestoreMode::RESTORE_INVERTED_DEFAULT_OFF:
|
||||
recovered.state = restored ? !recovered.state : false;
|
||||
return recovered;
|
||||
case FanRestoreMode::RESTORE_INVERTED_DEFAULT_ON:
|
||||
recovered.state = restored ? !recovered.state : true;
|
||||
return recovered;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
void Fan::save_state_() {
|
||||
FanRestoreState state{};
|
||||
state.state = this->state;
|
||||
state.oscillating = this->oscillating;
|
||||
state.speed = this->speed;
|
||||
state.direction = this->direction;
|
||||
this->rtc_.save(&state);
|
||||
}
|
||||
|
||||
void Fan::dump_traits_(const char *tag, const char *prefix) {
|
||||
if (this->get_traits().supports_speed()) {
|
||||
ESP_LOGCONFIG(tag, "%s Speed: YES", prefix);
|
||||
ESP_LOGCONFIG(tag, "%s Speed count: %d", prefix, this->get_traits().supported_speed_count());
|
||||
}
|
||||
if (this->get_traits().supports_oscillation())
|
||||
ESP_LOGCONFIG(tag, "%s Oscillation: YES", prefix);
|
||||
if (this->get_traits().supports_direction())
|
||||
ESP_LOGCONFIG(tag, "%s Direction: YES", prefix);
|
||||
}
|
||||
uint32_t Fan::hash_base() { return 418001110UL; }
|
||||
|
||||
} // namespace fan
|
||||
} // namespace esphome
|
||||
154
esphome/components/fan/fan.h
Normal file
154
esphome/components/fan/fan.h
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "fan_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
#define LOG_FAN(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
(obj)->dump_traits_(TAG, prefix); \
|
||||
}
|
||||
|
||||
/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon
|
||||
enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed {
|
||||
FAN_SPEED_LOW = 0, ///< The fan is running on low speed.
|
||||
FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed.
|
||||
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
|
||||
};
|
||||
|
||||
/// Simple enum to represent the direction of a fan.
|
||||
enum class FanDirection { FORWARD = 0, REVERSE = 1 };
|
||||
|
||||
/// Restore mode of a fan.
|
||||
enum class FanRestoreMode {
|
||||
NO_RESTORE,
|
||||
ALWAYS_OFF,
|
||||
ALWAYS_ON,
|
||||
RESTORE_DEFAULT_OFF,
|
||||
RESTORE_DEFAULT_ON,
|
||||
RESTORE_INVERTED_DEFAULT_OFF,
|
||||
RESTORE_INVERTED_DEFAULT_ON,
|
||||
};
|
||||
|
||||
const LogString *fan_direction_to_string(FanDirection direction);
|
||||
|
||||
class Fan;
|
||||
|
||||
class FanCall {
|
||||
public:
|
||||
explicit FanCall(Fan &parent) : parent_(parent) {}
|
||||
|
||||
FanCall &set_state(bool binary_state) {
|
||||
this->binary_state_ = binary_state;
|
||||
return *this;
|
||||
}
|
||||
FanCall &set_state(optional<bool> binary_state) {
|
||||
this->binary_state_ = binary_state;
|
||||
return *this;
|
||||
}
|
||||
optional<bool> get_state() const { return this->binary_state_; }
|
||||
FanCall &set_oscillating(bool oscillating) {
|
||||
this->oscillating_ = oscillating;
|
||||
return *this;
|
||||
}
|
||||
FanCall &set_oscillating(optional<bool> oscillating) {
|
||||
this->oscillating_ = oscillating;
|
||||
return *this;
|
||||
}
|
||||
optional<bool> get_oscillating() const { return this->oscillating_; }
|
||||
FanCall &set_speed(int speed) {
|
||||
this->speed_ = speed;
|
||||
return *this;
|
||||
}
|
||||
ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9")
|
||||
FanCall &set_speed(const char *legacy_speed);
|
||||
optional<int> get_speed() const { return this->speed_; }
|
||||
FanCall &set_direction(FanDirection direction) {
|
||||
this->direction_ = direction;
|
||||
return *this;
|
||||
}
|
||||
FanCall &set_direction(optional<FanDirection> direction) {
|
||||
this->direction_ = direction;
|
||||
return *this;
|
||||
}
|
||||
optional<FanDirection> get_direction() const { return this->direction_; }
|
||||
|
||||
void perform();
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
Fan &parent_;
|
||||
optional<bool> binary_state_;
|
||||
optional<bool> oscillating_;
|
||||
optional<int> speed_;
|
||||
optional<FanDirection> direction_{};
|
||||
};
|
||||
|
||||
struct FanRestoreState {
|
||||
bool state;
|
||||
int speed;
|
||||
bool oscillating;
|
||||
FanDirection direction;
|
||||
|
||||
/// Convert this struct to a fan call that can be performed.
|
||||
FanCall to_call(Fan &fan);
|
||||
/// Apply these settings to the fan.
|
||||
void apply(Fan &fan);
|
||||
} __attribute__((packed));
|
||||
|
||||
class Fan : public EntityBase {
|
||||
public:
|
||||
Fan();
|
||||
/// Construct the fan with name.
|
||||
explicit Fan(const std::string &name);
|
||||
|
||||
/// The current on/off state of the fan.
|
||||
bool state{false};
|
||||
/// The current oscillation state of the fan.
|
||||
bool oscillating{false};
|
||||
/// The current fan speed level
|
||||
int speed{0};
|
||||
/// The current direction of the fan
|
||||
FanDirection direction{FanDirection::FORWARD};
|
||||
|
||||
FanCall turn_on();
|
||||
FanCall turn_off();
|
||||
FanCall toggle();
|
||||
FanCall make_call();
|
||||
|
||||
/// Register a callback that will be called each time the state changes.
|
||||
void add_on_state_callback(std::function<void()> &&callback);
|
||||
|
||||
void publish_state();
|
||||
|
||||
virtual FanTraits get_traits() = 0;
|
||||
|
||||
/// Set the restore mode of this fan.
|
||||
void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||
|
||||
protected:
|
||||
friend FanCall;
|
||||
|
||||
virtual void control(const FanCall &call) = 0;
|
||||
|
||||
optional<FanRestoreState> restore_state_();
|
||||
void save_state_();
|
||||
|
||||
void dump_traits_(const char *tag, const char *prefix);
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
FanRestoreMode restore_mode_;
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
} // namespace esphome
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "fan_state.h"
|
||||
|
||||
#include "fan.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
@@ -1,85 +1,16 @@
|
||||
#include "fan_state.h"
|
||||
#include "fan_helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
static const char *const TAG = "fan";
|
||||
|
||||
const FanTraits &FanState::get_traits() const { return this->traits_; }
|
||||
void FanState::set_traits(const FanTraits &traits) { this->traits_ = traits; }
|
||||
void FanState::add_on_state_callback(std::function<void()> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
FanState::FanState(const std::string &name) : EntityBase(name) {}
|
||||
|
||||
FanStateCall FanState::turn_on() { return this->make_call().set_state(true); }
|
||||
FanStateCall FanState::turn_off() { return this->make_call().set_state(false); }
|
||||
FanStateCall FanState::toggle() { return this->make_call().set_state(!this->state); }
|
||||
FanStateCall FanState::make_call() { return FanStateCall(this); }
|
||||
|
||||
struct FanStateRTCState {
|
||||
bool state;
|
||||
int speed;
|
||||
bool oscillating;
|
||||
FanDirection direction;
|
||||
};
|
||||
|
||||
void FanState::setup() {
|
||||
this->rtc_ = global_preferences->make_preference<FanStateRTCState>(this->get_object_id_hash());
|
||||
FanStateRTCState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return;
|
||||
|
||||
auto call = this->make_call();
|
||||
call.set_state(recovered.state);
|
||||
call.set_speed(recovered.speed);
|
||||
call.set_oscillating(recovered.oscillating);
|
||||
call.set_direction(recovered.direction);
|
||||
call.perform();
|
||||
auto restore = this->restore_state_();
|
||||
if (restore)
|
||||
restore->to_call(*this).perform();
|
||||
}
|
||||
float FanState::get_setup_priority() const { return setup_priority::DATA - 1.0f; }
|
||||
uint32_t FanState::hash_base() { return 418001110UL; }
|
||||
|
||||
void FanStateCall::perform() const {
|
||||
if (this->binary_state_.has_value()) {
|
||||
this->state_->state = *this->binary_state_;
|
||||
}
|
||||
if (this->oscillating_.has_value()) {
|
||||
this->state_->oscillating = *this->oscillating_;
|
||||
}
|
||||
if (this->direction_.has_value()) {
|
||||
this->state_->direction = *this->direction_;
|
||||
}
|
||||
if (this->speed_.has_value()) {
|
||||
const int speed_count = this->state_->get_traits().supported_speed_count();
|
||||
this->state_->speed = clamp(*this->speed_, 1, speed_count);
|
||||
}
|
||||
|
||||
FanStateRTCState saved{};
|
||||
saved.state = this->state_->state;
|
||||
saved.speed = this->state_->speed;
|
||||
saved.oscillating = this->state_->oscillating;
|
||||
saved.direction = this->state_->direction;
|
||||
this->state_->rtc_.save(&saved);
|
||||
|
||||
this->state_->state_callback_.call();
|
||||
}
|
||||
|
||||
// This whole method is deprecated, don't warn about usage of deprecated methods inside of it.
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
FanStateCall &FanStateCall::set_speed(const char *legacy_speed) {
|
||||
const auto supported_speed_count = this->state_->get_traits().supported_speed_count();
|
||||
if (strcasecmp(legacy_speed, "low") == 0) {
|
||||
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count));
|
||||
} else if (strcasecmp(legacy_speed, "medium") == 0) {
|
||||
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count));
|
||||
} else if (strcasecmp(legacy_speed, "high") == 0) {
|
||||
this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace fan
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,111 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "fan_traits.h"
|
||||
#include "fan.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon
|
||||
enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed {
|
||||
FAN_SPEED_LOW = 0, ///< The fan is running on low speed.
|
||||
FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed.
|
||||
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
|
||||
enum ESPDEPRECATED("LegacyFanDirection members are deprecated, use FanDirection instead.",
|
||||
"2022.2") LegacyFanDirection {
|
||||
FAN_DIRECTION_FORWARD = 0,
|
||||
FAN_DIRECTION_REVERSE = 1
|
||||
};
|
||||
|
||||
/// Simple enum to represent the direction of a fan
|
||||
enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 };
|
||||
|
||||
class FanState;
|
||||
|
||||
class FanStateCall {
|
||||
public:
|
||||
explicit FanStateCall(FanState *state) : state_(state) {}
|
||||
|
||||
FanStateCall &set_state(bool binary_state) {
|
||||
this->binary_state_ = binary_state;
|
||||
return *this;
|
||||
}
|
||||
FanStateCall &set_state(optional<bool> binary_state) {
|
||||
this->binary_state_ = binary_state;
|
||||
return *this;
|
||||
}
|
||||
FanStateCall &set_oscillating(bool oscillating) {
|
||||
this->oscillating_ = oscillating;
|
||||
return *this;
|
||||
}
|
||||
FanStateCall &set_oscillating(optional<bool> oscillating) {
|
||||
this->oscillating_ = oscillating;
|
||||
return *this;
|
||||
}
|
||||
FanStateCall &set_speed(int speed) {
|
||||
this->speed_ = speed;
|
||||
return *this;
|
||||
}
|
||||
ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9")
|
||||
FanStateCall &set_speed(const char *legacy_speed);
|
||||
FanStateCall &set_direction(FanDirection direction) {
|
||||
this->direction_ = direction;
|
||||
return *this;
|
||||
}
|
||||
FanStateCall &set_direction(optional<FanDirection> direction) {
|
||||
this->direction_ = direction;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void perform() const;
|
||||
|
||||
protected:
|
||||
FanState *const state_;
|
||||
optional<bool> binary_state_;
|
||||
optional<bool> oscillating_;
|
||||
optional<int> speed_;
|
||||
optional<FanDirection> direction_{};
|
||||
};
|
||||
|
||||
class FanState : public EntityBase, public Component {
|
||||
class ESPDEPRECATED("FanState is deprecated, use Fan instead.", "2022.2") FanState : public Fan, public Component {
|
||||
public:
|
||||
FanState() = default;
|
||||
/// Construct the fan state with name.
|
||||
explicit FanState(const std::string &name);
|
||||
explicit FanState(const std::string &name) : Fan(name) {}
|
||||
|
||||
/// Register a callback that will be called each time the state changes.
|
||||
void add_on_state_callback(std::function<void()> &&callback);
|
||||
|
||||
/// Get the traits of this fan (i.e. what features it supports).
|
||||
const FanTraits &get_traits() const;
|
||||
/// Get the traits of this fan.
|
||||
FanTraits get_traits() override { return this->traits_; }
|
||||
/// Set the traits of this fan (i.e. what features it supports).
|
||||
void set_traits(const FanTraits &traits);
|
||||
|
||||
/// The current ON/OFF state of the fan.
|
||||
bool state{false};
|
||||
/// The current oscillation state of the fan.
|
||||
bool oscillating{false};
|
||||
/// The current fan speed level
|
||||
int speed{};
|
||||
/// The current direction of the fan
|
||||
FanDirection direction{FAN_DIRECTION_FORWARD};
|
||||
|
||||
FanStateCall turn_on();
|
||||
FanStateCall turn_off();
|
||||
FanStateCall toggle();
|
||||
FanStateCall make_call();
|
||||
void set_traits(const FanTraits &traits) { this->traits_ = traits; }
|
||||
|
||||
void setup() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
friend FanStateCall;
|
||||
|
||||
uint32_t hash_base() override;
|
||||
void control(const FanCall &call) override { this->publish_state(); }
|
||||
|
||||
FanTraits traits_{};
|
||||
CallbackManager<void()> state_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
||||
@@ -49,7 +49,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
_validate,
|
||||
cv.only_with_arduino,
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 7, 4),
|
||||
esp32_arduino=cv.Version(99, 0, 0),
|
||||
max_version=True,
|
||||
extra_message="Please see note on documentation for FastLED",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_DATA_RATE): cv.frequency,
|
||||
}
|
||||
),
|
||||
cv.only_with_arduino,
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 7, 4),
|
||||
esp32_arduino=cv.Version(99, 0, 0),
|
||||
max_version=True,
|
||||
extra_message="Please see note on documentation for FastLED",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,10 @@ AURA_LED_COLORS = {
|
||||
"RED": AuraLEDColor.RED,
|
||||
"BLUE": AuraLEDColor.BLUE,
|
||||
"PURPLE": AuraLEDColor.PURPLE,
|
||||
"GREEN": AuraLEDColor.GREEN,
|
||||
"YELLOW": AuraLEDColor.YELLOW,
|
||||
"CYAN": AuraLEDColor.CYAN,
|
||||
"WHITE": AuraLEDColor.WHITE,
|
||||
}
|
||||
validate_aura_led_colors = cv.enum(AURA_LED_COLORS, upper=True)
|
||||
|
||||
|
||||
@@ -278,10 +278,11 @@ void FingerprintGrowComponent::delete_all_fingerprints() {
|
||||
|
||||
void FingerprintGrowComponent::led_control(bool state) {
|
||||
ESP_LOGD(TAG, "Setting LED");
|
||||
if (state)
|
||||
if (state) {
|
||||
this->data_ = {LED_ON};
|
||||
else
|
||||
} else {
|
||||
this->data_ = {LED_OFF};
|
||||
}
|
||||
switch (this->send_command_()) {
|
||||
case OK:
|
||||
ESP_LOGD(TAG, "LED set");
|
||||
|
||||
@@ -76,6 +76,10 @@ enum GrowAuraLEDColor {
|
||||
RED = 0x01,
|
||||
BLUE = 0x02,
|
||||
PURPLE = 0x03,
|
||||
GREEN = 0x04,
|
||||
YELLOW = 0x05,
|
||||
CYAN = 0x06,
|
||||
WHITE = 0x07,
|
||||
};
|
||||
|
||||
class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevice {
|
||||
|
||||
@@ -207,7 +207,7 @@ void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) {
|
||||
ESP_LOGV(TAG, "Transmit message length %d", length);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
auto *data = transmit.get_data();
|
||||
|
||||
data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY);
|
||||
|
||||
|
||||
@@ -33,16 +33,18 @@ void GPIOSwitch::setup() {
|
||||
}
|
||||
|
||||
// write state before setup
|
||||
if (initial_state)
|
||||
if (initial_state) {
|
||||
this->turn_on();
|
||||
else
|
||||
} else {
|
||||
this->turn_off();
|
||||
}
|
||||
this->pin_->setup();
|
||||
// write after setup again for other IOs
|
||||
if (initial_state)
|
||||
if (initial_state) {
|
||||
this->turn_on();
|
||||
else
|
||||
} else {
|
||||
this->turn_off();
|
||||
}
|
||||
}
|
||||
void GPIOSwitch::dump_config() {
|
||||
LOG_SWITCH("", "GPIO Switch", this);
|
||||
|
||||
@@ -115,7 +115,7 @@ class GraphTrace {
|
||||
void set_line_type(enum LineType val) { this->line_type_ = val; }
|
||||
Color get_line_color() { return this->line_color_; }
|
||||
void set_line_color(Color val) { this->line_color_ = val; }
|
||||
const std::string get_name() { return name_; }
|
||||
std::string get_name() { return name_; }
|
||||
const HistoryData *get_tracedata() { return &data_; }
|
||||
|
||||
protected:
|
||||
|
||||
0
esphome/components/growatt_solar/__init__.py
Normal file
0
esphome/components/growatt_solar/__init__.py
Normal file
69
esphome/components/growatt_solar/growatt_solar.cpp
Normal file
69
esphome/components/growatt_solar/growatt_solar.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "growatt_solar.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace growatt_solar {
|
||||
|
||||
static const char *const TAG = "growatt_solar";
|
||||
|
||||
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
|
||||
static const uint8_t MODBUS_REGISTER_COUNT = 33;
|
||||
|
||||
void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
|
||||
|
||||
void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
|
||||
if (sensor == nullptr)
|
||||
return;
|
||||
float value = encode_uint16(data[i * 2], data[i * 2 + 1]) * unit;
|
||||
sensor->publish_state(value);
|
||||
};
|
||||
|
||||
auto publish_2_reg_sensor_state = [&](sensor::Sensor *sensor, size_t reg1, size_t reg2, float unit) -> void {
|
||||
float value = ((encode_uint16(data[reg1 * 2], data[reg1 * 2 + 1]) << 16) +
|
||||
encode_uint16(data[reg2 * 2], data[reg2 * 2 + 1])) *
|
||||
unit;
|
||||
if (sensor != nullptr)
|
||||
sensor->publish_state(value);
|
||||
};
|
||||
|
||||
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
|
||||
|
||||
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
|
||||
|
||||
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
|
||||
|
||||
publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
|
||||
}
|
||||
|
||||
void GrowattSolar::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "GROWATT Solar:");
|
||||
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
||||
}
|
||||
|
||||
} // namespace growatt_solar
|
||||
} // namespace esphome
|
||||
73
esphome/components/growatt_solar/growatt_solar.h
Normal file
73
esphome/components/growatt_solar/growatt_solar.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace growatt_solar {
|
||||
|
||||
static const float TWO_DEC_UNIT = 0.01;
|
||||
static const float ONE_DEC_UNIT = 0.1;
|
||||
|
||||
class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
|
||||
public:
|
||||
void update() override;
|
||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; }
|
||||
|
||||
void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; }
|
||||
void set_grid_active_power_sensor(sensor::Sensor *sensor) { this->grid_active_power_sensor_ = sensor; }
|
||||
void set_pv_active_power_sensor(sensor::Sensor *sensor) { this->pv_active_power_sensor_ = sensor; }
|
||||
|
||||
void set_today_production_sensor(sensor::Sensor *sensor) { this->today_production_ = sensor; }
|
||||
void set_total_energy_production_sensor(sensor::Sensor *sensor) { this->total_energy_production_ = sensor; }
|
||||
void set_inverter_module_temp_sensor(sensor::Sensor *sensor) { this->inverter_module_temp_ = sensor; }
|
||||
|
||||
void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) {
|
||||
this->phases_[phase].voltage_sensor_ = voltage_sensor;
|
||||
}
|
||||
void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) {
|
||||
this->phases_[phase].current_sensor_ = current_sensor;
|
||||
}
|
||||
void set_active_power_sensor(uint8_t phase, sensor::Sensor *active_power_sensor) {
|
||||
this->phases_[phase].active_power_sensor_ = active_power_sensor;
|
||||
}
|
||||
void set_voltage_sensor_pv(uint8_t pv, sensor::Sensor *voltage_sensor) {
|
||||
this->pvs_[pv].voltage_sensor_ = voltage_sensor;
|
||||
}
|
||||
void set_current_sensor_pv(uint8_t pv, sensor::Sensor *current_sensor) {
|
||||
this->pvs_[pv].current_sensor_ = current_sensor;
|
||||
}
|
||||
void set_active_power_sensor_pv(uint8_t pv, sensor::Sensor *active_power_sensor) {
|
||||
this->pvs_[pv].active_power_sensor_ = active_power_sensor;
|
||||
}
|
||||
|
||||
protected:
|
||||
struct GrowattPhase {
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_sensor_{nullptr};
|
||||
} phases_[3];
|
||||
struct GrowattPV {
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_sensor_{nullptr};
|
||||
} pvs_[2];
|
||||
|
||||
sensor::Sensor *inverter_status_{nullptr};
|
||||
|
||||
sensor::Sensor *grid_frequency_sensor_{nullptr};
|
||||
sensor::Sensor *grid_active_power_sensor_{nullptr};
|
||||
|
||||
sensor::Sensor *pv_active_power_sensor_{nullptr};
|
||||
|
||||
sensor::Sensor *today_production_{nullptr};
|
||||
sensor::Sensor *total_energy_production_{nullptr};
|
||||
sensor::Sensor *inverter_module_temp_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace growatt_solar
|
||||
} // namespace esphome
|
||||
201
esphome/components/growatt_solar/sensor.py
Normal file
201
esphome/components/growatt_solar/sensor.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, modbus
|
||||
from esphome.const import (
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_CURRENT,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_CURRENT_AC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
CONF_PHASE_A = "phase_a"
|
||||
CONF_PHASE_B = "phase_b"
|
||||
CONF_PHASE_C = "phase_c"
|
||||
|
||||
CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
|
||||
CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
|
||||
CONF_TOTAL_GENERATION_TIME = "total_generation_time"
|
||||
CONF_TODAY_GENERATION_TIME = "today_generation_time"
|
||||
CONF_PV1 = "pv1"
|
||||
CONF_PV2 = "pv2"
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_HOURS = "h"
|
||||
UNIT_KOHM = "kΩ"
|
||||
UNIT_MILLIAMPERE = "mA"
|
||||
|
||||
CONF_INVERTER_STATUS = "inverter_status"
|
||||
CONF_PV_ACTIVE_POWER = "pv_active_power"
|
||||
CONF_INVERTER_MODULE_TEMP = "inverter_module_temp"
|
||||
|
||||
|
||||
AUTO_LOAD = ["modbus"]
|
||||
CODEOWNERS = ["@leeuwte"]
|
||||
|
||||
growatt_solar_ns = cg.esphome_ns.namespace("growatt_solar")
|
||||
GrowattSolar = growatt_solar_ns.class_(
|
||||
"GrowattSolar", cg.PollingComponent, modbus.ModbusDevice
|
||||
)
|
||||
|
||||
PHASE_SENSORS = {
|
||||
CONF_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_ACTIVE_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
PV_SENSORS = {
|
||||
CONF_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_ACTIVE_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
PHASE_SCHEMA = cv.Schema(
|
||||
{cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()}
|
||||
)
|
||||
PV_SCHEMA = cv.Schema(
|
||||
{cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GrowattSolar),
|
||||
cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PV1): PV_SCHEMA,
|
||||
cv.Optional(CONF_PV2): PV_SCHEMA,
|
||||
cv.Optional(CONF_INVERTER_STATUS): sensor.sensor_schema(),
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PV_ACTIVE_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("10s"))
|
||||
.extend(modbus.modbus_device_schema(0x01))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await modbus.register_modbus_device(var, config)
|
||||
|
||||
if CONF_INVERTER_STATUS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
|
||||
cg.add(var.set_inverter_status_sensor(sens))
|
||||
|
||||
if CONF_FREQUENCY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
cg.add(var.set_grid_frequency_sensor(sens))
|
||||
|
||||
if CONF_ACTIVE_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER])
|
||||
cg.add(var.set_grid_active_power_sensor(sens))
|
||||
|
||||
if CONF_PV_ACTIVE_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PV_ACTIVE_POWER])
|
||||
cg.add(var.set_pv_active_power_sensor(sens))
|
||||
|
||||
if CONF_ENERGY_PRODUCTION_DAY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY])
|
||||
cg.add(var.set_today_production_sensor(sens))
|
||||
|
||||
if CONF_TOTAL_ENERGY_PRODUCTION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION])
|
||||
cg.add(var.set_total_energy_production_sensor(sens))
|
||||
|
||||
if CONF_INVERTER_MODULE_TEMP in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP])
|
||||
cg.add(var.set_inverter_module_temp_sensor(sens))
|
||||
|
||||
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||
if phase not in config:
|
||||
continue
|
||||
|
||||
phase_config = config[phase]
|
||||
for sensor_type in PHASE_SENSORS:
|
||||
if sensor_type in phase_config:
|
||||
sens = await sensor.new_sensor(phase_config[sensor_type])
|
||||
cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens))
|
||||
|
||||
for i, pv in enumerate([CONF_PV1, CONF_PV2]):
|
||||
if pv not in config:
|
||||
continue
|
||||
|
||||
pv_config = config[pv]
|
||||
for sensor_type in pv_config:
|
||||
if sensor_type in pv_config:
|
||||
sens = await sensor.new_sensor(pv_config[sensor_type])
|
||||
cg.add(getattr(var, f"set_{sensor_type}_sensor_pv")(i, sens))
|
||||
@@ -17,7 +17,7 @@ from .. import hbridge_ns
|
||||
CODEOWNERS = ["@WeekendWarrior"]
|
||||
|
||||
|
||||
HBridgeFan = hbridge_ns.class_("HBridgeFan", fan.FanState)
|
||||
HBridgeFan = hbridge_ns.class_("HBridgeFan", cg.Component, fan.Fan)
|
||||
|
||||
DecayMode = hbridge_ns.enum("DecayMode")
|
||||
DECAY_MODE_OPTIONS = {
|
||||
@@ -59,6 +59,7 @@ async def to_code(config):
|
||||
config[CONF_SPEED_COUNT],
|
||||
config[CONF_DECAY_MODE],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await fan.register_fan(var, config)
|
||||
pin_a_ = await cg.get_variable(config[CONF_PIN_A])
|
||||
cg.add(var.set_pin_a(pin_a_))
|
||||
|
||||
@@ -22,47 +22,49 @@ void HBridgeFan::set_hbridge_levels_(float a_level, float b_level, float enable)
|
||||
ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f, enable: %.2f", a_level, b_level, enable);
|
||||
}
|
||||
|
||||
fan::FanStateCall HBridgeFan::brake() {
|
||||
fan::FanCall HBridgeFan::brake() {
|
||||
ESP_LOGD(TAG, "Braking");
|
||||
(this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f) : this->set_hbridge_levels_(1.0f, 1.0f, 1.0f);
|
||||
return this->make_call().set_state(false);
|
||||
}
|
||||
|
||||
void HBridgeFan::setup() {
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
restore->apply(*this);
|
||||
this->write_state_();
|
||||
}
|
||||
}
|
||||
void HBridgeFan::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Fan '%s':", this->get_name().c_str());
|
||||
if (this->get_traits().supports_oscillation()) {
|
||||
ESP_LOGCONFIG(TAG, " Oscillation: YES");
|
||||
}
|
||||
if (this->get_traits().supports_direction()) {
|
||||
ESP_LOGCONFIG(TAG, " Direction: YES");
|
||||
}
|
||||
LOG_FAN("", "H-Bridge Fan", this);
|
||||
if (this->decay_mode_ == DECAY_MODE_SLOW) {
|
||||
ESP_LOGCONFIG(TAG, " Decay Mode: Slow");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Decay Mode: Fast");
|
||||
}
|
||||
}
|
||||
void HBridgeFan::setup() {
|
||||
auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
|
||||
this->set_traits(traits);
|
||||
this->add_on_state_callback([this]() { this->next_update_ = true; });
|
||||
fan::FanTraits HBridgeFan::get_traits() {
|
||||
return fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
|
||||
}
|
||||
void HBridgeFan::loop() {
|
||||
if (!this->next_update_) {
|
||||
return;
|
||||
}
|
||||
this->next_update_ = false;
|
||||
void HBridgeFan::control(const fan::FanCall &call) {
|
||||
if (call.get_state().has_value())
|
||||
this->state = *call.get_state();
|
||||
if (call.get_speed().has_value())
|
||||
this->speed = *call.get_speed();
|
||||
if (call.get_oscillating().has_value())
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
|
||||
float speed = 0.0f;
|
||||
if (this->state) {
|
||||
speed = static_cast<float>(this->speed) / static_cast<float>(this->speed_count_);
|
||||
}
|
||||
this->write_state_();
|
||||
this->publish_state();
|
||||
}
|
||||
void HBridgeFan::write_state_() {
|
||||
float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f;
|
||||
if (speed == 0.0f) { // off means idle
|
||||
(this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, speed)
|
||||
: this->set_hbridge_levels_(speed, speed, speed);
|
||||
return;
|
||||
}
|
||||
if (this->direction == fan::FAN_DIRECTION_FORWARD) {
|
||||
} else if (this->direction == fan::FanDirection::FORWARD) {
|
||||
if (this->decay_mode_ == DECAY_MODE_SLOW) {
|
||||
(this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f - speed, 1.0f)
|
||||
: this->set_hbridge_levels_(1.0f - speed, 1.0f, 1.0f);
|
||||
@@ -79,6 +81,9 @@ void HBridgeFan::loop() {
|
||||
: this->set_hbridge_levels_(1.0f, 0.0f, speed);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->oscillating_ != nullptr)
|
||||
this->oscillating_->set_state(this->oscillating);
|
||||
}
|
||||
|
||||
} // namespace hbridge
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/fan/fan_state.h"
|
||||
#include "esphome/components/fan/fan.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hbridge {
|
||||
@@ -13,7 +13,7 @@ enum DecayMode {
|
||||
DECAY_MODE_FAST = 1,
|
||||
};
|
||||
|
||||
class HBridgeFan : public fan::FanState {
|
||||
class HBridgeFan : public Component, public fan::Fan {
|
||||
public:
|
||||
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
|
||||
|
||||
@@ -22,25 +22,22 @@ class HBridgeFan : public fan::FanState {
|
||||
void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; }
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
fan::FanTraits get_traits() override;
|
||||
|
||||
fan::FanStateCall brake();
|
||||
|
||||
int get_speed_count() { return this->speed_count_; }
|
||||
// update Hbridge without a triggered FanState change, eg. for acceleration/deceleration ramping
|
||||
void internal_update() { this->next_update_ = true; }
|
||||
fan::FanCall brake();
|
||||
|
||||
protected:
|
||||
output::FloatOutput *pin_a_;
|
||||
output::FloatOutput *pin_b_;
|
||||
output::FloatOutput *enable_{nullptr};
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
bool next_update_{true};
|
||||
int speed_count_{};
|
||||
DecayMode decay_mode_{DECAY_MODE_SLOW};
|
||||
|
||||
void control(const fan::FanCall &call) override;
|
||||
void write_state_();
|
||||
|
||||
void set_hbridge_levels_(float a_level, float b_level);
|
||||
void set_hbridge_levels_(float a_level, float b_level, float enable);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,9 @@ void HDC1080Component::setup() {
|
||||
};
|
||||
|
||||
if (!this->write_bytes(HDC1080_CMD_CONFIGURATION, data, 2)) {
|
||||
this->mark_failed();
|
||||
// as instruction is same as powerup defaults (for now), interpret as warning if this fails
|
||||
ESP_LOGW(TAG, "HDC1080 initial config instruction error");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,8 +109,8 @@ def to_code(config):
|
||||
cg.add(var.set_protocol(config[CONF_PROTOCOL]))
|
||||
cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT]))
|
||||
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))
|
||||
cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||
cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||
|
||||
# PIO isn't updating releases, so referencing the release tag directly. See:
|
||||
# https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
|
||||
|
||||
@@ -6,20 +6,20 @@ namespace esphome {
|
||||
namespace heatpumpir {
|
||||
|
||||
void IRSenderESPHome::setFrequency(int frequency) { // NOLINT(readability-identifier-naming)
|
||||
auto data = transmit_.get_data();
|
||||
auto *data = transmit_.get_data();
|
||||
data->set_carrier_frequency(1000 * frequency);
|
||||
}
|
||||
|
||||
// Send an IR 'mark' symbol, i.e. transmitter ON
|
||||
void IRSenderESPHome::mark(int mark_length) {
|
||||
auto data = transmit_.get_data();
|
||||
auto *data = transmit_.get_data();
|
||||
data->mark(mark_length);
|
||||
}
|
||||
|
||||
// Send an IR 'space' symbol, i.e. transmitter OFF
|
||||
void IRSenderESPHome::space(int space_length) {
|
||||
if (space_length) {
|
||||
auto data = transmit_.get_data();
|
||||
auto *data = transmit_.get_data();
|
||||
data->space(space_length);
|
||||
} else {
|
||||
transmit_.perform();
|
||||
|
||||
@@ -19,10 +19,11 @@ void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, con
|
||||
|
||||
void set_bit(uint8_t *const data, const uint8_t position, const bool on) {
|
||||
uint8_t mask = 1 << position;
|
||||
if (on)
|
||||
if (on) {
|
||||
*data |= mask;
|
||||
else
|
||||
} else {
|
||||
*data &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) {
|
||||
@@ -69,10 +70,11 @@ void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) {
|
||||
temp = std::min(celsius, HITACHI_AC344_TEMP_MAX);
|
||||
temp = std::max(temp, HITACHI_AC344_TEMP_MIN);
|
||||
set_bits(&remote_state_[HITACHI_AC344_TEMP_BYTE], HITACHI_AC344_TEMP_OFFSET, HITACHI_AC344_TEMP_SIZE, temp);
|
||||
if (previous_temp_ > temp)
|
||||
if (previous_temp_ > temp) {
|
||||
set_button_(HITACHI_AC344_BUTTON_TEMP_DOWN);
|
||||
else if (previous_temp_ < temp)
|
||||
} else if (previous_temp_ < temp) {
|
||||
set_button_(HITACHI_AC344_BUTTON_TEMP_UP);
|
||||
}
|
||||
if (set_previous)
|
||||
previous_temp_ = temp;
|
||||
}
|
||||
@@ -110,11 +112,12 @@ void HitachiClimate::set_fan_(uint8_t speed) {
|
||||
|
||||
void HitachiClimate::set_swing_v_toggle_(bool on) {
|
||||
uint8_t button = get_button_(); // Get the current button value.
|
||||
if (on)
|
||||
button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV.
|
||||
else if (button == HITACHI_AC344_BUTTON_SWINGV) // Asked to unset it
|
||||
if (on) {
|
||||
button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV.
|
||||
} else if (button == HITACHI_AC344_BUTTON_SWINGV) { // Asked to unset it
|
||||
// It was set previous, so use Power as a default
|
||||
button = HITACHI_AC344_BUTTON_POWER;
|
||||
}
|
||||
set_button_(button);
|
||||
}
|
||||
|
||||
@@ -211,7 +214,7 @@ void HitachiClimate::transmit_state() {
|
||||
invert_byte_pairs(remote_state_ + 3, HITACHI_AC344_STATE_LENGTH - 3);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
auto *data = transmit.get_data();
|
||||
data->set_carrier_frequency(HITACHI_AC344_FREQ);
|
||||
|
||||
uint8_t repeat = 0;
|
||||
@@ -320,9 +323,9 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
for (uint8_t pos = 0; pos < HITACHI_AC344_STATE_LENGTH; pos++) {
|
||||
// Read bit
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE))
|
||||
if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE)) {
|
||||
recv_state[pos] |= 1 << bit;
|
||||
else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) {
|
||||
} else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) {
|
||||
ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, con
|
||||
|
||||
void set_bit(uint8_t *const data, const uint8_t position, const bool on) {
|
||||
uint8_t mask = 1 << position;
|
||||
if (on)
|
||||
if (on) {
|
||||
*data |= mask;
|
||||
else
|
||||
} else {
|
||||
*data &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) {
|
||||
@@ -69,10 +70,11 @@ void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) {
|
||||
temp = std::min(celsius, HITACHI_AC424_TEMP_MAX);
|
||||
temp = std::max(temp, HITACHI_AC424_TEMP_MIN);
|
||||
set_bits(&remote_state_[HITACHI_AC424_TEMP_BYTE], HITACHI_AC424_TEMP_OFFSET, HITACHI_AC424_TEMP_SIZE, temp);
|
||||
if (previous_temp_ > temp)
|
||||
if (previous_temp_ > temp) {
|
||||
set_button_(HITACHI_AC424_BUTTON_TEMP_DOWN);
|
||||
else if (previous_temp_ < temp)
|
||||
} else if (previous_temp_ < temp) {
|
||||
set_button_(HITACHI_AC424_BUTTON_TEMP_UP);
|
||||
}
|
||||
if (set_previous)
|
||||
previous_temp_ = temp;
|
||||
}
|
||||
@@ -110,11 +112,12 @@ void HitachiClimate::set_fan_(uint8_t speed) {
|
||||
|
||||
void HitachiClimate::set_swing_v_toggle_(bool on) {
|
||||
uint8_t button = get_button_(); // Get the current button value.
|
||||
if (on)
|
||||
button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV.
|
||||
else if (button == HITACHI_AC424_BUTTON_SWINGV) // Asked to unset it
|
||||
if (on) {
|
||||
button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV.
|
||||
} else if (button == HITACHI_AC424_BUTTON_SWINGV) { // Asked to unset it
|
||||
// It was set previous, so use Power as a default
|
||||
button = HITACHI_AC424_BUTTON_POWER;
|
||||
}
|
||||
set_button_(button);
|
||||
}
|
||||
|
||||
@@ -212,7 +215,7 @@ void HitachiClimate::transmit_state() {
|
||||
invert_byte_pairs(remote_state_ + 3, HITACHI_AC424_STATE_LENGTH - 3);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto data = transmit.get_data();
|
||||
auto *data = transmit.get_data();
|
||||
data->set_carrier_frequency(HITACHI_AC424_FREQ);
|
||||
|
||||
uint8_t repeat = 0;
|
||||
@@ -321,9 +324,9 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
for (uint8_t pos = 0; pos < HITACHI_AC424_STATE_LENGTH; pos++) {
|
||||
// Read bit
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE))
|
||||
if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) {
|
||||
recv_state[pos] |= 1 << bit;
|
||||
else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) {
|
||||
} else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) {
|
||||
ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
|
||||
AQICalculatorType aqi_calc_type_;
|
||||
AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
|
||||
|
||||
bool validate_checksum_(const uint8_t *);
|
||||
uint16_t get_sensor_value_(const uint8_t *, uint8_t);
|
||||
bool validate_checksum_(const uint8_t *data);
|
||||
uint16_t get_sensor_value_(const uint8_t *data, uint8_t i);
|
||||
};
|
||||
|
||||
} // namespace hm3301
|
||||
|
||||
@@ -25,10 +25,11 @@ void HomeassistantBinarySensor::setup() {
|
||||
} else {
|
||||
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state));
|
||||
}
|
||||
if (this->initial_)
|
||||
if (this->initial_) {
|
||||
this->publish_initial_state(new_state);
|
||||
else
|
||||
} else {
|
||||
this->publish_state(new_state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
this->initial_ = false;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID
|
||||
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
|
||||
|
||||
from .. import homeassistant_ns
|
||||
|
||||
DEPENDENCIES = ["api"]
|
||||
@@ -10,7 +11,7 @@ HomeassistantTextSensor = homeassistant_ns.class_(
|
||||
"HomeassistantTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HomeassistantTextSensor),
|
||||
cv.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
@@ -20,9 +21,8 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
|
||||
if CONF_ATTRIBUTE in config:
|
||||
|
||||
@@ -31,6 +31,8 @@ CONF_BODY = "body"
|
||||
CONF_JSON = "json"
|
||||
CONF_VERIFY_SSL = "verify_ssl"
|
||||
CONF_ON_RESPONSE = "on_response"
|
||||
CONF_FOLLOW_REDIRECTS = "follow_redirects"
|
||||
CONF_REDIRECT_LIMIT = "redirect_limit"
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
@@ -71,6 +73,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
|
||||
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
|
||||
cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
|
||||
cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
|
||||
cv.Optional(
|
||||
CONF_TIMEOUT, default="5s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
@@ -90,6 +94,9 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_useragent(config[CONF_USERAGENT]))
|
||||
cg.add(var.set_follow_redirects(config[CONF_FOLLOW_REDIRECTS]))
|
||||
cg.add(var.set_redirect_limit(config[CONF_REDIRECT_LIMIT]))
|
||||
|
||||
if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
|
||||
cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
|
||||
|
||||
@@ -172,7 +179,7 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
||||
if CONF_JSON in config:
|
||||
json_ = config[CONF_JSON]
|
||||
if isinstance(json_, Lambda):
|
||||
args_ = args + [(cg.JsonObjectRef, "root")]
|
||||
args_ = args + [(cg.JsonObject, "root")]
|
||||
lambda_ = await cg.process_lambda(json_, args_, return_type=cg.void)
|
||||
cg.add(var.set_json(lambda_))
|
||||
else:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "http_request.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
|
||||
@@ -14,6 +14,8 @@ void HttpRequestComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HTTP Request:");
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %ums", this->timeout_);
|
||||
ESP_LOGCONFIG(TAG, " User-Agent: %s", this->useragent_);
|
||||
ESP_LOGCONFIG(TAG, " Follow Redirects: %d", this->follow_redirects_);
|
||||
ESP_LOGCONFIG(TAG, " Redirect limit: %d", this->redirect_limit_);
|
||||
}
|
||||
|
||||
void HttpRequestComponent::set_url(std::string url) {
|
||||
@@ -38,18 +40,21 @@ void HttpRequestComponent::send(const std::vector<HttpRequestResponseTrigger *>
|
||||
|
||||
bool begin_status = false;
|
||||
const String url = this->url_.c_str();
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0))
|
||||
#if defined(USE_ESP32) || USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
|
||||
if (this->follow_redirects_) {
|
||||
this->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
||||
} else {
|
||||
this->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS);
|
||||
}
|
||||
#else
|
||||
this->client_.setFollowRedirects(this->follow_redirects_);
|
||||
#endif
|
||||
this->client_.setRedirectLimit(this->redirect_limit_);
|
||||
#endif
|
||||
#if defined(USE_ESP32)
|
||||
begin_status = this->client_.begin(url);
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
|
||||
this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
|
||||
this->client_.setFollowRedirects(true);
|
||||
#endif
|
||||
#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)
|
||||
this->client_.setRedirectLimit(3);
|
||||
#endif
|
||||
#elif defined(USE_ESP8266)
|
||||
begin_status = this->client_.begin(*this->get_wifi_client_(), url);
|
||||
#endif
|
||||
|
||||
@@ -115,8 +120,11 @@ void HttpRequestComponent::close() {
|
||||
}
|
||||
|
||||
const char *HttpRequestComponent::get_string() {
|
||||
static const String STR = this->client_.getString();
|
||||
return STR.c_str();
|
||||
// The static variable is here because HTTPClient::getString() returns a String on ESP32, and we need something to
|
||||
// to keep a buffer alive.
|
||||
static std::string str;
|
||||
str = this->client_.getString().c_str();
|
||||
return str.c_str();
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
|
||||
@@ -40,6 +40,8 @@ class HttpRequestComponent : public Component {
|
||||
void set_method(const char *method) { this->method_ = method; }
|
||||
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
||||
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
|
||||
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
||||
void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
|
||||
void set_body(const std::string &body) { this->body_ = body; }
|
||||
void set_headers(std::list<Header> headers) { this->headers_ = std::move(headers); }
|
||||
void send(const std::vector<HttpRequestResponseTrigger *> &response_triggers);
|
||||
@@ -53,6 +55,8 @@ class HttpRequestComponent : public Component {
|
||||
const char *method_;
|
||||
const char *useragent_{nullptr};
|
||||
bool secure_;
|
||||
bool follow_redirects_;
|
||||
uint16_t redirect_limit_;
|
||||
uint16_t timeout_{5000};
|
||||
std::string body_;
|
||||
std::list<Header> headers_;
|
||||
@@ -78,7 +82,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
|
||||
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
|
||||
|
||||
void set_json(std::function<void(Ts..., JsonObject &)> json_func) { this->json_func_ = json_func; }
|
||||
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
|
||||
|
||||
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
|
||||
|
||||
@@ -118,17 +122,17 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
}
|
||||
|
||||
protected:
|
||||
void encode_json_(Ts... x, JsonObject &root) {
|
||||
void encode_json_(Ts... x, JsonObject root) {
|
||||
for (const auto &item : this->json_) {
|
||||
auto val = item.second;
|
||||
root[item.first] = val.value(x...);
|
||||
}
|
||||
}
|
||||
void encode_json_func_(Ts... x, JsonObject &root) { this->json_func_(x..., root); }
|
||||
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
|
||||
HttpRequestComponent *parent_;
|
||||
std::map<const char *, TemplatableValue<const char *, Ts...>> headers_{};
|
||||
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
||||
std::function<void(Ts..., JsonObject &)> json_func_{nullptr};
|
||||
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
||||
std::vector<HttpRequestResponseTrigger *> response_triggers_;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,16 +16,17 @@ void ArduinoI2CBus::setup() {
|
||||
|
||||
#ifdef USE_ESP32
|
||||
static uint8_t next_bus_num = 0;
|
||||
if (next_bus_num == 0)
|
||||
if (next_bus_num == 0) {
|
||||
wire_ = &Wire;
|
||||
else
|
||||
} else {
|
||||
wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
next_bus_num++;
|
||||
#else
|
||||
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
|
||||
#endif
|
||||
|
||||
wire_->begin(sda_pin_, scl_pin_);
|
||||
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
|
||||
wire_->setClock(frequency_);
|
||||
initialized_ = true;
|
||||
if (this->scan_) {
|
||||
@@ -55,10 +56,11 @@ void ArduinoI2CBus::dump_config() {
|
||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||
} else {
|
||||
for (const auto &s : scan_results_) {
|
||||
if (s.second)
|
||||
if (s.second) {
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
|
||||
else
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,10 +65,11 @@ void IDFI2CBus::dump_config() {
|
||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||
} else {
|
||||
for (const auto &s : scan_results_) {
|
||||
if (s.second)
|
||||
if (s.second) {
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
|
||||
else
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ IMAGE_TYPE = {
|
||||
"BINARY": ImageType.IMAGE_TYPE_BINARY,
|
||||
"GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE,
|
||||
"RGB24": ImageType.IMAGE_TYPE_RGB24,
|
||||
"TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY,
|
||||
}
|
||||
|
||||
Image_ = display.display_ns.class_("Image")
|
||||
@@ -99,6 +100,17 @@ async def to_code(config):
|
||||
pos = x + y * width8
|
||||
data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
|
||||
elif config[CONF_TYPE] == "TRANSPARENT_BINARY":
|
||||
image = image.convert("RGBA")
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range(height * width8 // 8)]
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if not image.getpixel((x, y))[3]:
|
||||
continue
|
||||
pos = x + y * width8
|
||||
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(
|
||||
|
||||
@@ -30,4 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add_library("esphome/Improv", "1.0.0")
|
||||
cg.add_library("esphome/Improv", "1.2.1")
|
||||
|
||||
@@ -24,6 +24,8 @@ void ImprovSerialComponent::setup() {
|
||||
|
||||
if (wifi::global_wifi_component->has_sta()) {
|
||||
this->state_ = improv::STATE_PROVISIONED;
|
||||
} else {
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +97,7 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
|
||||
std::vector<std::string> urls;
|
||||
#ifdef USE_WEBSERVER
|
||||
auto ip = wifi::global_wifi_component->wifi_sta_ip();
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
#endif
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
|
||||
@@ -111,58 +113,15 @@ std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
|
||||
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
|
||||
size_t at = this->rx_buffer_.size();
|
||||
this->rx_buffer_.push_back(byte);
|
||||
ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte);
|
||||
ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
|
||||
const uint8_t *raw = &this->rx_buffer_[0];
|
||||
if (at == 0)
|
||||
return byte == 'I';
|
||||
if (at == 1)
|
||||
return byte == 'M';
|
||||
if (at == 2)
|
||||
return byte == 'P';
|
||||
if (at == 3)
|
||||
return byte == 'R';
|
||||
if (at == 4)
|
||||
return byte == 'O';
|
||||
if (at == 5)
|
||||
return byte == 'V';
|
||||
|
||||
if (at == 6)
|
||||
return byte == IMPROV_SERIAL_VERSION;
|
||||
|
||||
if (at == 7)
|
||||
return true;
|
||||
uint8_t type = raw[7];
|
||||
|
||||
if (at == 8)
|
||||
return true;
|
||||
uint8_t data_len = raw[8];
|
||||
|
||||
if (at < 8 + data_len)
|
||||
return true;
|
||||
|
||||
if (at == 8 + data_len)
|
||||
return true;
|
||||
|
||||
if (at == 8 + data_len + 1) {
|
||||
uint8_t checksum = 0x00;
|
||||
for (size_t i = 0; i < at; i++)
|
||||
checksum += raw[i];
|
||||
|
||||
if (checksum != byte) {
|
||||
ESP_LOGW(TAG, "Error decoding Improv payload");
|
||||
this->set_error_(improv::ERROR_INVALID_RPC);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == TYPE_RPC) {
|
||||
this->set_error_(improv::ERROR_NONE);
|
||||
auto command = improv::parse_improv_data(&raw[9], data_len, false);
|
||||
return this->parse_improv_payload_(command);
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here then the command coming is is improv, but not an RPC command
|
||||
return false;
|
||||
return improv::parse_improv_serial_byte(
|
||||
at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); },
|
||||
[this](improv::Error error) -> void {
|
||||
ESP_LOGW(TAG, "Error decoding Improv payload");
|
||||
this->set_error_(error);
|
||||
});
|
||||
}
|
||||
|
||||
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
|
||||
@@ -195,6 +154,27 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
this->send_response_(info);
|
||||
return true;
|
||||
}
|
||||
case improv::GET_WIFI_NETWORKS: {
|
||||
std::vector<std::string> networks;
|
||||
auto results = wifi::global_wifi_component->get_scan_result();
|
||||
for (auto &scan : results) {
|
||||
if (scan.get_is_hidden())
|
||||
continue;
|
||||
const std::string &ssid = scan.get_ssid();
|
||||
if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
|
||||
continue;
|
||||
// Send each ssid separately to avoid overflowing the buffer
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(
|
||||
improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
|
||||
this->send_response_(data);
|
||||
networks.push_back(ssid);
|
||||
}
|
||||
// Send empty response to signify the end of the list.
|
||||
std::vector<uint8_t> data =
|
||||
improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
|
||||
this->send_response_(data);
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
ESP_LOGW(TAG, "Unknown Improv payload");
|
||||
this->set_error_(improv::ERROR_UNKNOWN_RPC);
|
||||
|
||||
@@ -32,7 +32,7 @@ class ImprovSerialComponent : public Component {
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
protected:
|
||||
bool parse_improv_serial_byte_(uint8_t byte);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user