mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
Compare commits
400 Commits
jesserockz
...
2023.4.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b89c04b928 | ||
![]() |
12090657bb | ||
![]() |
4e21cf0bdd | ||
![]() |
ba4ef72d56 | ||
![]() |
f3e6a4314f | ||
![]() |
e14ce3d950 | ||
![]() |
0f1e186189 | ||
![]() |
96d208e0d8 | ||
![]() |
38ed38864e | ||
![]() |
a12ba7bd38 | ||
![]() |
19fc1417ae | ||
![]() |
e2fefa51f5 | ||
![]() |
f668d5617f | ||
![]() |
47c4ff15d6 | ||
![]() |
ccf1bdc0b4 | ||
![]() |
e993fcf80c | ||
![]() |
1bdc30a09e | ||
![]() |
f56e89597f | ||
![]() |
9460fb28c4 | ||
![]() |
7207b9734f | ||
![]() |
3be3267d06 | ||
![]() |
98db604dba | ||
![]() |
ebf6f8c6de | ||
![]() |
2ebacad398 | ||
![]() |
53c59cf675 | ||
![]() |
9da261cb39 | ||
![]() |
9fbbcd6d8a | ||
![]() |
1a86167a47 | ||
![]() |
614ed7fd0c | ||
![]() |
4eb69d6af5 | ||
![]() |
0547f2a931 | ||
![]() |
b5fbe0b145 | ||
![]() |
443c3c2a56 | ||
![]() |
d75daa9644 | ||
![]() |
7963abb27a | ||
![]() |
0b9e8fda34 | ||
![]() |
a3cacc0c8b | ||
![]() |
5a4840f641 | ||
![]() |
3d7d689040 | ||
![]() |
b60c08dd28 | ||
![]() |
80bc567c31 | ||
![]() |
888ac2e180 | ||
![]() |
421ebcc8b2 | ||
![]() |
b56fa8c50a | ||
![]() |
42401775e1 | ||
![]() |
9c9bc58c16 | ||
![]() |
fbc129cccc | ||
![]() |
99638190cb | ||
![]() |
d78e9e6aa8 | ||
![]() |
878155a03d | ||
![]() |
9922eb83e2 | ||
![]() |
79f861f012 | ||
![]() |
4faa9d109e | ||
![]() |
28534ecc61 | ||
![]() |
616e0a21d8 | ||
![]() |
a546ffd490 | ||
![]() |
b5d0aede38 | ||
![]() |
a014d853a4 | ||
![]() |
c4ddf7697d | ||
![]() |
a2931b6774 | ||
![]() |
3ac7bf3761 | ||
![]() |
922344811f | ||
![]() |
cb2fcaa9b1 | ||
![]() |
1f50bd0649 | ||
![]() |
e4b2de5c68 | ||
![]() |
f862b479e7 | ||
![]() |
358c59bd8d | ||
![]() |
74fe135c9c | ||
![]() |
8d3896172d | ||
![]() |
9d9725144d | ||
![]() |
06f83bf1c0 | ||
![]() |
c2756d57d8 | ||
![]() |
56504692af | ||
![]() |
e542e75b9e | ||
![]() |
806e43c34c | ||
![]() |
29e7d00894 | ||
![]() |
9ee661c1e4 | ||
![]() |
36c0e2416d | ||
![]() |
e4ba3ff1db | ||
![]() |
be69b49880 | ||
![]() |
cc317d27f5 | ||
![]() |
c16709ed95 | ||
![]() |
e13eaf6706 | ||
![]() |
a1eb3b8475 | ||
![]() |
d52e425ba2 | ||
![]() |
dd8dc1ef1d | ||
![]() |
bc427de16a | ||
![]() |
db5988bbe1 | ||
![]() |
a3875af4b4 | ||
![]() |
d70e7da0ef | ||
![]() |
d42f35de5d | ||
![]() |
cd57469e06 | ||
![]() |
d98d6ff45f | ||
![]() |
14e38f0469 | ||
![]() |
f0f6d3f1cd | ||
![]() |
0b383542da | ||
![]() |
b2cec10601 | ||
![]() |
48658d5a55 | ||
![]() |
5207ca1d52 | ||
![]() |
7196fb8e82 | ||
![]() |
48ada2eebb | ||
![]() |
0de5808ed2 | ||
![]() |
ebc544e4b4 | ||
![]() |
a31fb3c987 | ||
![]() |
dfc7cd7f5d | ||
![]() |
a8bb2a42a1 | ||
![]() |
3d4c0e6667 | ||
![]() |
25fb288016 | ||
![]() |
1b8b8cdd11 | ||
![]() |
e6737479f7 | ||
![]() |
7f75832bf1 | ||
![]() |
33339e3bd8 | ||
![]() |
c298c1166f | ||
![]() |
c3d9eef01f | ||
![]() |
5ffdc66864 | ||
![]() |
2f50e18eb5 | ||
![]() |
9922c1503a | ||
![]() |
fce99d4b17 | ||
![]() |
11567085d8 | ||
![]() |
83f8e84247 | ||
![]() |
215107e8ea | ||
![]() |
d3f2b93c42 | ||
![]() |
11eb5cb0fa | ||
![]() |
9a7af97b2d | ||
![]() |
5e11469f50 | ||
![]() |
0c7a3d1fff | ||
![]() |
8a705bf4b0 | ||
![]() |
ee7102fcd1 | ||
![]() |
a44e38300b | ||
![]() |
b00e20c29f | ||
![]() |
6a89180deb | ||
![]() |
65d2b806cc | ||
![]() |
c149a3033c | ||
![]() |
4b7c233f1a | ||
![]() |
6e8e9c2aa9 | ||
![]() |
b6f628ee40 | ||
![]() |
bf79a700b7 | ||
![]() |
cdeb6e750f | ||
![]() |
d642aeba0f | ||
![]() |
6a6aee510d | ||
![]() |
ea17a92dbc | ||
![]() |
32a0a60480 | ||
![]() |
5a56644702 | ||
![]() |
29113808ee | ||
![]() |
dd226360bb | ||
![]() |
1a9aedf152 | ||
![]() |
d82c6df57e | ||
![]() |
7c91b4474a | ||
![]() |
a4f21db272 | ||
![]() |
58a8e1859e | ||
![]() |
2bed5b18c1 | ||
![]() |
fb5eb57345 | ||
![]() |
6471361715 | ||
![]() |
01687a9d57 | ||
![]() |
801fbf44c5 | ||
![]() |
ba1416cc0e | ||
![]() |
afc1c83af4 | ||
![]() |
da056866ff | ||
![]() |
336c2d34e6 | ||
![]() |
f3a969d35c | ||
![]() |
63db42a1d4 | ||
![]() |
c12dd77c64 | ||
![]() |
f58ffe41f8 | ||
![]() |
4f138c600b | ||
![]() |
445d2e372c | ||
![]() |
1087cb55b4 | ||
![]() |
600f4be2c4 | ||
![]() |
5e6665494d | ||
![]() |
2ef25f3153 | ||
![]() |
bc28ea1fde | ||
![]() |
623e31ddee | ||
![]() |
ceebe14628 | ||
![]() |
b29cc58144 | ||
![]() |
1b328da265 | ||
![]() |
06ca5354b2 | ||
![]() |
356efdb92c | ||
![]() |
bb5ab8b36d | ||
![]() |
6ecf4ecac6 | ||
![]() |
05ab49a615 | ||
![]() |
3773c385c7 | ||
![]() |
3227ef4bca | ||
![]() |
5a07e8d32b | ||
![]() |
29571a1acd | ||
![]() |
b8538c2c12 | ||
![]() |
7466773ac8 | ||
![]() |
b8ca40170e | ||
![]() |
bd86a0ac3b | ||
![]() |
df3f13ded8 | ||
![]() |
a428e2b689 | ||
![]() |
86407b9f6f | ||
![]() |
eceb79ceab | ||
![]() |
43fb68f8a0 | ||
![]() |
14e7b8a1ef | ||
![]() |
62459a8ae1 | ||
![]() |
86c0e6114f | ||
![]() |
1a9141877d | ||
![]() |
6ec18fc630 | ||
![]() |
4d674392e8 | ||
![]() |
6704b2cedf | ||
![]() |
fe4fb5f1ac | ||
![]() |
350d4e5071 | ||
![]() |
23f47d0ad2 | ||
![]() |
c037e95861 | ||
![]() |
2e1b35959f | ||
![]() |
7f46d9e0f9 | ||
![]() |
069b5f81a0 | ||
![]() |
3a36d0b13f | ||
![]() |
f98d93efa8 | ||
![]() |
91e037346b | ||
![]() |
8e1430243e | ||
![]() |
98b3d294aa | ||
![]() |
38a01988a5 | ||
![]() |
d16eff5039 | ||
![]() |
8fb481751f | ||
![]() |
ba6f89a757 | ||
![]() |
48e76e1538 | ||
![]() |
0e1d018ce3 | ||
![]() |
50fbbf2d3b | ||
![]() |
247916fe89 | ||
![]() |
ed801f7a27 | ||
![]() |
f68d577986 | ||
![]() |
5c49730cb9 | ||
![]() |
04c12823b5 | ||
![]() |
add40c7652 | ||
![]() |
f0760e99b7 | ||
![]() |
72391389a3 | ||
![]() |
e68beb8a43 | ||
![]() |
40e2832e67 | ||
![]() |
18fecf8c09 | ||
![]() |
414cf1b333 | ||
![]() |
d10f891f51 | ||
![]() |
36a1f6cfb1 | ||
![]() |
12bef16d54 | ||
![]() |
77db8c8401 | ||
![]() |
66eecd3675 | ||
![]() |
c03b1fae68 | ||
![]() |
b5927322e6 | ||
![]() |
1cf4107e1c | ||
![]() |
c12408326c | ||
![]() |
37d55b55fc | ||
![]() |
4434e59e5a | ||
![]() |
45180d98f6 | ||
![]() |
44494ad18e | ||
![]() |
9aed758d1b | ||
![]() |
dbe5587806 | ||
![]() |
1447536906 | ||
![]() |
27ec517084 | ||
![]() |
ce1f034bac | ||
![]() |
f1f96f16e9 | ||
![]() |
30eec5adee | ||
![]() |
5307dfee21 | ||
![]() |
8b5b9e508b | ||
![]() |
7665e9b076 | ||
![]() |
227d94f38d | ||
![]() |
b724ae9e0e | ||
![]() |
df6cc14201 | ||
![]() |
d981d7859d | ||
![]() |
0f1ec515c1 | ||
![]() |
78e18256f7 | ||
![]() |
a0d04ba091 | ||
![]() |
c02871fdfe | ||
![]() |
0d52f555b2 | ||
![]() |
025cf6320f | ||
![]() |
5997401e9e | ||
![]() |
b4068dac56 | ||
![]() |
458d6e24fc | ||
![]() |
4f4ca61ada | ||
![]() |
dfeeccfcca | ||
![]() |
3a101e8ec5 | ||
![]() |
7a2d7fdd19 | ||
![]() |
4899dfe642 | ||
![]() |
78f5c417a4 | ||
![]() |
b8c0f88440 | ||
![]() |
d6b6e94059 | ||
![]() |
310355a00b | ||
![]() |
8cf26d6f3c | ||
![]() |
b15a10f905 | ||
![]() |
58eeb6b1b8 | ||
![]() |
f8acc45be4 | ||
![]() |
b7ab00b699 | ||
![]() |
045489e6d7 | ||
![]() |
b14e774a27 | ||
![]() |
2a8745d7e0 | ||
![]() |
499cb615f1 | ||
![]() |
5dcf1debd7 | ||
![]() |
9b57e1ac1d | ||
![]() |
68683e3a50 | ||
![]() |
4d192c7387 | ||
![]() |
d83324c4dc | ||
![]() |
ecde4c1d2d | ||
![]() |
bd8e470726 | ||
![]() |
9dd01b30bd | ||
![]() |
d2913fe627 | ||
![]() |
881cd535b9 | ||
![]() |
43acc7dc2c | ||
![]() |
e2a16d758b | ||
![]() |
17ea0efb08 | ||
![]() |
2fbd33267e | ||
![]() |
cf3977f088 | ||
![]() |
d20d4947ac | ||
![]() |
7810ad40d7 | ||
![]() |
7e1e799b3a | ||
![]() |
dfafc41ce6 | ||
![]() |
e460792c43 | ||
![]() |
a9dc491a54 | ||
![]() |
ac6693f177 | ||
![]() |
c6742117d3 | ||
![]() |
b5c47b9669 | ||
![]() |
40df3aa55e | ||
![]() |
393ca64d70 | ||
![]() |
d3627f0972 | ||
![]() |
124ab31f22 | ||
![]() |
1b66fa5004 | ||
![]() |
9494c27ad8 | ||
![]() |
3facfa5c21 | ||
![]() |
93ddce2e79 | ||
![]() |
0bf6e21e1a | ||
![]() |
6b7b076875 | ||
![]() |
8d6ffb9169 | ||
![]() |
4af4649e23 | ||
![]() |
8bcddef39d | ||
![]() |
4ac96ccea2 | ||
![]() |
3c5de77ae9 | ||
![]() |
a2925b1d37 | ||
![]() |
73748e9e20 | ||
![]() |
75c9823899 | ||
![]() |
c8c0bd3351 | ||
![]() |
e1cdeb7c8f | ||
![]() |
7f97f42552 | ||
![]() |
aa7f3569ec | ||
![]() |
2d0a08442e | ||
![]() |
d2380756b2 | ||
![]() |
925e3cb6c9 | ||
![]() |
6757acba56 | ||
![]() |
5cc91cdd95 | ||
![]() |
2b41886819 | ||
![]() |
72c6efd6a0 | ||
![]() |
a1f1804112 | ||
![]() |
a8b1ceb4e9 | ||
![]() |
4fb0f7f8c6 | ||
![]() |
958cadeca8 | ||
![]() |
00f2655f1a | ||
![]() |
074f5029eb | ||
![]() |
1691976587 | ||
![]() |
60e6b4d21e | ||
![]() |
5750591df2 | ||
![]() |
a75da54455 | ||
![]() |
de7f6c3f5f | ||
![]() |
4245480656 | ||
![]() |
1824c8131e | ||
![]() |
4e9606d2e0 | ||
![]() |
78500fa933 | ||
![]() |
9c69b98a49 | ||
![]() |
e6d8ef98d3 | ||
![]() |
3f1af1690b | ||
![]() |
84374b6b1e | ||
![]() |
391316c9b5 | ||
![]() |
705c62ebd7 | ||
![]() |
cb520c00a5 | ||
![]() |
2f24138345 | ||
![]() |
96512b80cc | ||
![]() |
fcb9b51978 | ||
![]() |
f408f1a368 | ||
![]() |
7d8d563c62 | ||
![]() |
0a1f705fda | ||
![]() |
1952c1880b | ||
![]() |
b03967dac1 | ||
![]() |
bcae2596a6 | ||
![]() |
fc0347c86c | ||
![]() |
d9563d4de1 | ||
![]() |
cc7e2bf8db | ||
![]() |
5d98e2923b | ||
![]() |
07197d12f6 | ||
![]() |
7b0a298497 | ||
![]() |
21679cf2ba | ||
![]() |
4be7cd12a1 | ||
![]() |
dee4d0ccb7 | ||
![]() |
7209dd8bae | ||
![]() |
ab736c89bb | ||
![]() |
6911639617 | ||
![]() |
b9720d0715 | ||
![]() |
47b3267ed4 | ||
![]() |
e16ba2adb5 | ||
![]() |
0a19b1e32c | ||
![]() |
bae9a950c0 | ||
![]() |
72b2943332 | ||
![]() |
4ec0ef7548 | ||
![]() |
25bc6761f6 | ||
![]() |
81b6562c25 | ||
![]() |
ae74189fc2 | ||
![]() |
9e516efe10 | ||
![]() |
366e29439e | ||
![]() |
1c9c700d7f | ||
![]() |
b2e6b9d31f | ||
![]() |
7623f63846 | ||
![]() |
2bfaf9dce3 | ||
![]() |
5c2c1560bb | ||
![]() |
f7096ab78e | ||
![]() |
98f8feb625 | ||
![]() |
9944ca414e |
@@ -4,53 +4,60 @@
|
||||
"postCreateCommand": [
|
||||
"script/devcontainer-post-create"
|
||||
],
|
||||
"containerEnv": {
|
||||
"DEVCONTAINER": "1"
|
||||
},
|
||||
"runArgs": [
|
||||
"--privileged",
|
||||
"-e",
|
||||
"ESPHOME_DASHBOARD_USE_PING=1"
|
||||
],
|
||||
"appPort": 6052,
|
||||
"extensions": [
|
||||
// python
|
||||
"ms-python.python",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
// yaml
|
||||
"redhat.vscode-yaml",
|
||||
// cpp
|
||||
"ms-vscode.cpptools",
|
||||
// editorconfig
|
||||
"editorconfig.editorconfig",
|
||||
],
|
||||
"settings": {
|
||||
"python.languageServer": "Pylance",
|
||||
"python.pythonPath": "/usr/bin/python3",
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"yaml.customTags": [
|
||||
"!secret scalar",
|
||||
"!lambda scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
],
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/*.pyc": {
|
||||
"when": "$(basename).py"
|
||||
},
|
||||
"**/__pycache__": true
|
||||
},
|
||||
"files.associations": {
|
||||
"**/.vscode/*.json": "jsonc"
|
||||
},
|
||||
"C_Cpp.clang_format_path": "/usr/bin/clang-format-11",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
// python
|
||||
"ms-python.python",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
// yaml
|
||||
"redhat.vscode-yaml",
|
||||
// cpp
|
||||
"ms-vscode.cpptools",
|
||||
// editorconfig
|
||||
"editorconfig.editorconfig",
|
||||
],
|
||||
"settings": {
|
||||
"python.languageServer": "Pylance",
|
||||
"python.pythonPath": "/usr/bin/python3",
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"yaml.customTags": [
|
||||
"!secret scalar",
|
||||
"!lambda scalar",
|
||||
"!include_dir_named scalar",
|
||||
"!include_dir_list scalar",
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
],
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/*.pyc": {
|
||||
"when": "$(basename).py"
|
||||
},
|
||||
"**/__pycache__": true
|
||||
},
|
||||
"files.associations": {
|
||||
"**/.vscode/*.json": "jsonc"
|
||||
},
|
||||
"C_Cpp.clang_format_path": "/usr/bin/clang-format-13"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
.github/workflows/ci-docker.yml
vendored
5
.github/workflows/ci-docker.yml
vendored
@@ -23,6 +23,11 @@ permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
concurrency:
|
||||
# yamllint disable-line rule:line-length
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check-docker:
|
||||
name: Build docker containers
|
||||
|
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
branches: [dev, beta, release]
|
||||
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -40,6 +41,10 @@ jobs:
|
||||
file: tests/test3.yaml
|
||||
name: Test tests/test3.yaml
|
||||
pio_cache_key: test3
|
||||
- id: test
|
||||
file: tests/test3.1.yaml
|
||||
name: Test tests/test3.1.yaml
|
||||
pio_cache_key: test3.1
|
||||
- id: test
|
||||
file: tests/test4.yaml
|
||||
name: Test tests/test4.yaml
|
||||
@@ -128,7 +133,7 @@ jobs:
|
||||
- name: Install clang tools
|
||||
run: |
|
||||
sudo apt-get install \
|
||||
clang-format-11 \
|
||||
clang-format-13 \
|
||||
clang-tidy-11
|
||||
if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
|
||||
|
||||
@@ -181,9 +186,22 @@ jobs:
|
||||
|
||||
- name: Run yamllint
|
||||
if: matrix.id == 'yamllint'
|
||||
uses: frenck/action-yamllint@v1.3.1
|
||||
uses: frenck/action-yamllint@v1.4.0
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
# yamllint disable-line rule:line-length
|
||||
if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
|
||||
|
||||
ci-status:
|
||||
name: CI Status
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ci]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Successful deploy
|
||||
if: ${{ !(contains(needs.*.result, 'failure')) }}
|
||||
run: exit 0
|
||||
- name: Failing deploy
|
||||
if: ${{ contains(needs.*.result, 'failure') }}
|
||||
run: exit 1
|
||||
|
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
@@ -138,14 +138,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
# yamllint disable rule:line-length
|
||||
run: |
|
||||
curl \
|
||||
-u ":$TOKEN" \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
|
||||
-d '{"ref":"main","inputs":{"version":"${{ github.event.release.tag_name }}","content":${{ toJSON(github.event.release.body) }}}}'
|
||||
# yamllint enable rule:line-length
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
repo: "home-assistant-addon",
|
||||
workflow_id: "bump-version.yml",
|
||||
ref: "main",
|
||||
inputs: {
|
||||
version: "${{ github.event.release.tag_name }}",
|
||||
content: ${{ toJSON(github.event.release.body) }}
|
||||
}
|
||||
})
|
||||
|
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v7
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v7
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
|
60
.github/workflows/sync-device-classes.yml
vendored
Normal file
60
.github/workflows/sync-device-classes.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: Synchronise Device Classes from Home Assistant
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '45 6 * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync Device Classes
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
- name: Install Home Assistant
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e lib/home-assistant
|
||||
|
||||
- name: Sync
|
||||
run: |
|
||||
python ./script/sync-device_class.py
|
||||
|
||||
- name: Get PR template
|
||||
id: pr-template-body
|
||||
run: |
|
||||
body=$(cat .github/PULL_REQUEST_TEMPLATE.md)
|
||||
delimiter="$(openssl rand -hex 8)"
|
||||
echo "body<<$delimiter" >> $GITHUB_OUTPUT
|
||||
echo "$body" >> $GITHUB_OUTPUT
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphomebot <esphome@nabucasa.com>
|
||||
author: esphomebot <esphome@nabucasa.com>
|
||||
branch: sync/device-classes/
|
||||
branch-suffix: timestamp
|
||||
delete-branch: true
|
||||
title: "Synchronise Device Classes from Home Assistant"
|
||||
body: ${{ steps.pr-template-body.outputs.body }}
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -128,3 +128,5 @@ tests/.esphome/
|
||||
|
||||
sdkconfig.*
|
||||
!sdkconfig.defaults
|
||||
|
||||
.tests/
|
@@ -2,8 +2,8 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 22.12.0
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@@ -27,7 +27,7 @@ repos:
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.0
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
|
15
.vscode/tasks.json
vendored
15
.vscode/tasks.json
vendored
@@ -2,15 +2,24 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "run",
|
||||
"label": "Run Dashboard",
|
||||
"type": "shell",
|
||||
"command": "python3 -m esphome dashboard config/",
|
||||
"command": "${command:python.interpreterPath}",
|
||||
"args": [
|
||||
"-m",
|
||||
"esphome",
|
||||
"dashboard",
|
||||
"config/"
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "clang-tidy",
|
||||
"type": "shell",
|
||||
"command": "./script/clang-tidy",
|
||||
"command": "${command:python.interpreterPath}",
|
||||
"args": [
|
||||
"./script/clang-tidy"
|
||||
],
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "clang-tidy",
|
||||
|
18
CODEOWNERS
18
CODEOWNERS
@@ -11,6 +11,7 @@ esphome/*.py @esphome/core
|
||||
esphome/core/* @esphome/core
|
||||
|
||||
# Integrations
|
||||
esphome/components/absolute_humidity/* @DAVe3283
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/adc128s102/* @DeerMaximum
|
||||
@@ -24,6 +25,7 @@ esphome/components/analog_threshold/* @ianchi
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/anova/* @buxtronix
|
||||
esphome/components/api/* @OttoWinter
|
||||
esphome/components/as7341/* @mrgnr
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/b_parasite/* @rbaron
|
||||
@@ -90,11 +92,13 @@ esphome/components/factory_reset/* @anatoly-savchenkov
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/feedback/* @ianchi
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/fs3000/* @kahrendt
|
||||
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/haier/* @Yarikx
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
@@ -107,18 +111,24 @@ esphome/components/hte501/* @Stock-M
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/i2s_audio/media_player/* @jesserockz
|
||||
esphome/components/i2s_audio/microphone/* @jesserockz
|
||||
esphome/components/ili9xxx/* @nielsnl68
|
||||
esphome/components/improv_base/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/ina260/* @MrEditor97
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate6/* @jesserockz
|
||||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/kuntze/* @ssieb
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @sebcaps
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
@@ -146,11 +156,13 @@ esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/media_player/* @jesserockz
|
||||
esphome/components/microphone/* @jesserockz
|
||||
esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mlx90393/* @functionpointer
|
||||
esphome/components/mmc5603/* @benhoff
|
||||
esphome/components/modbus_controller/* @martgras
|
||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||
esphome/components/modbus_controller/number/* @martgras
|
||||
@@ -159,8 +171,9 @@ esphome/components/modbus_controller/select/* @martgras @stegm
|
||||
esphome/components/modbus_controller/sensor/* @martgras
|
||||
esphome/components/modbus_controller/switch/* @martgras
|
||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||
esphome/components/mopeka_ble/* @spbrogan
|
||||
esphome/components/mopeka_ble/* @Fabian-Schmidt @spbrogan
|
||||
esphome/components/mopeka_pro_check/* @spbrogan
|
||||
esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
||||
esphome/components/mpl3115a2/* @kbickar
|
||||
esphome/components/mpu6886/* @fabaff
|
||||
esphome/components/network/* @esphome/core
|
||||
@@ -207,6 +220,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
esphome/components/selec_meter/* @sourabhjaiswal
|
||||
esphome/components/select/* @esphome/core
|
||||
esphome/components/sen21231/* @shreyaskarnik
|
||||
esphome/components/sen5x/* @martgras
|
||||
esphome/components/sensirion_common/* @martgras
|
||||
esphome/components/sensor/* @esphome/core
|
||||
@@ -274,7 +288,9 @@ esphome/components/uart/* @esphome/core
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/voice_assistant/* @jesserockz
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
|
@@ -1,8 +1,6 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
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 *.cpp *.h *.tcc *.c
|
||||
recursive-include esphome *.py.script
|
||||
recursive-include esphome LICENSE.txt
|
||||
|
@@ -6,9 +6,9 @@
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
# https://github.com/hassio-addons/addon-debian-base/releases
|
||||
FROM ghcr.io/hassio-addons/debian-base:6.2.0 AS base-hassio
|
||||
FROM ghcr.io/hassio-addons/debian-base:6.2.3 AS base-hassio
|
||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
|
||||
FROM debian:bullseye-20221024-slim AS base-docker
|
||||
FROM debian:bullseye-20230208-slim AS base-docker
|
||||
|
||||
FROM base-${BASEIMGTYPE} AS base
|
||||
|
||||
@@ -26,7 +26,7 @@ RUN \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
curl=7.74.0-1.3+deb11u3 \
|
||||
curl=7.74.0-1.3+deb11u7 \
|
||||
openssh-client=1:8.4p1-5+deb11u1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
@@ -51,7 +51,7 @@ RUN \
|
||||
# Ubuntu python3-pip is missing wheel
|
||||
pip3 install --no-cache-dir \
|
||||
wheel==0.37.1 \
|
||||
platformio==6.1.5 \
|
||||
platformio==6.1.6 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_platformio_interval 1000000 \
|
||||
@@ -135,7 +135,7 @@ RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
clang-format-11=1:11.0.1-2 \
|
||||
clang-format-13=1:13.0.1-6~deb11u1 \
|
||||
clang-tidy-11=1:11.0.1-2 \
|
||||
patch=2.7.6-7 \
|
||||
software-properties-common=0.96.20.2-2.1 \
|
||||
|
@@ -152,6 +152,8 @@ def run_miniterm(config, port):
|
||||
_LOGGER.error("Could not connect to serial port %s", port)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def wrap_to_code(name, comp):
|
||||
coro = coroutine(comp.to_code)
|
||||
|
@@ -254,7 +254,11 @@ async def repeat_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
|
||||
cg.add(var.set_count(count_template))
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
actions = await build_action_list(
|
||||
config[CONF_THEN],
|
||||
cg.TemplateArguments(cg.uint32, *template_arg.args),
|
||||
[(cg.uint32, "iteration"), *args],
|
||||
)
|
||||
cg.add(var.add_then(actions))
|
||||
return var
|
||||
|
||||
|
@@ -47,6 +47,7 @@ from esphome.cpp_helpers import ( # noqa
|
||||
build_registry_list,
|
||||
extract_registry_entry_config,
|
||||
register_parented,
|
||||
past_safe_mode,
|
||||
)
|
||||
from esphome.cpp_types import ( # noqa
|
||||
global_ns,
|
||||
@@ -63,6 +64,7 @@ from esphome.cpp_types import ( # noqa
|
||||
uint16,
|
||||
uint32,
|
||||
uint64,
|
||||
int16,
|
||||
int32,
|
||||
int64,
|
||||
size_t,
|
||||
|
1
esphome/components/absolute_humidity/__init__.py
Normal file
1
esphome/components/absolute_humidity/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@DAVe3283"]
|
182
esphome/components/absolute_humidity/absolute_humidity.cpp
Normal file
182
esphome/components/absolute_humidity/absolute_humidity.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "absolute_humidity.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace absolute_humidity {
|
||||
|
||||
static const char *const TAG = "absolute_humidity.sensor";
|
||||
|
||||
void AbsoluteHumidityComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str());
|
||||
|
||||
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
|
||||
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
|
||||
if (this->temperature_sensor_->has_state()) {
|
||||
this->temperature_callback_(this->temperature_sensor_->get_state());
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, " Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str());
|
||||
this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); });
|
||||
if (this->humidity_sensor_->has_state()) {
|
||||
this->humidity_callback_(this->humidity_sensor_->get_state());
|
||||
}
|
||||
}
|
||||
|
||||
void AbsoluteHumidityComponent::dump_config() {
|
||||
LOG_SENSOR("", "Absolute Humidity", this);
|
||||
|
||||
switch (this->equation_) {
|
||||
case BUCK:
|
||||
ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Buck");
|
||||
break;
|
||||
case TETENS:
|
||||
ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Tetens");
|
||||
break;
|
||||
case WOBUS:
|
||||
ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Wobus");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Sources");
|
||||
ESP_LOGCONFIG(TAG, " Temperature: '%s'", this->temperature_sensor_->get_name().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Relative Humidity: '%s'", this->humidity_sensor_->get_name().c_str());
|
||||
}
|
||||
|
||||
float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void AbsoluteHumidityComponent::loop() {
|
||||
if (!this->next_update_) {
|
||||
return;
|
||||
}
|
||||
this->next_update_ = false;
|
||||
|
||||
// Ensure we have source data
|
||||
const bool no_temperature = std::isnan(this->temperature_);
|
||||
const bool no_humidity = std::isnan(this->humidity_);
|
||||
if (no_temperature || no_humidity) {
|
||||
if (no_temperature) {
|
||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||
}
|
||||
if (no_humidity) {
|
||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||
}
|
||||
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
|
||||
this->publish_state(NAN);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to desired units
|
||||
const float temperature_c = this->temperature_;
|
||||
const float temperature_k = temperature_c + 273.15;
|
||||
const float hr = this->humidity_ / 100;
|
||||
|
||||
// Calculate saturation vapor pressure
|
||||
float es;
|
||||
switch (this->equation_) {
|
||||
case BUCK:
|
||||
es = es_buck(temperature_c);
|
||||
break;
|
||||
case TETENS:
|
||||
es = es_tetens(temperature_c);
|
||||
break;
|
||||
case WOBUS:
|
||||
es = es_wobus(temperature_c);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
|
||||
this->publish_state(NAN);
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
||||
|
||||
// Calculate absolute humidity
|
||||
const float absolute_humidity = vapor_density(es, hr, temperature_k);
|
||||
|
||||
// Publish absolute humidity
|
||||
ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity);
|
||||
this->status_clear_warning();
|
||||
this->publish_state(absolute_humidity);
|
||||
}
|
||||
|
||||
// Buck equation (https://en.wikipedia.org/wiki/Arden_Buck_equation)
|
||||
// More accurate than Tetens in normal meteorologic conditions
|
||||
float AbsoluteHumidityComponent::es_buck(float temperature_c) {
|
||||
float a, b, c, d;
|
||||
if (temperature_c >= 0) {
|
||||
a = 0.61121;
|
||||
b = 18.678;
|
||||
c = 234.5;
|
||||
d = 257.14;
|
||||
} else {
|
||||
a = 0.61115;
|
||||
b = 18.678;
|
||||
c = 233.7;
|
||||
d = 279.82;
|
||||
}
|
||||
return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c)));
|
||||
}
|
||||
|
||||
// Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation)
|
||||
float AbsoluteHumidityComponent::es_tetens(float temperature_c) {
|
||||
float a, b;
|
||||
if (temperature_c >= 0) {
|
||||
a = 17.27;
|
||||
b = 237.3;
|
||||
} else {
|
||||
a = 21.875;
|
||||
b = 265.5;
|
||||
}
|
||||
return 0.61078 * expf((a * temperature_c) / (temperature_c + b));
|
||||
}
|
||||
|
||||
// Wobus equation
|
||||
// https://wahiduddin.net/calc/density_altitude.htm
|
||||
// https://wahiduddin.net/calc/density_algorithms.htm
|
||||
// Calculate the saturation vapor pressure (kPa)
|
||||
float AbsoluteHumidityComponent::es_wobus(float t) {
|
||||
// THIS FUNCTION RETURNS THE SATURATION VAPOR PRESSURE ESW (MILLIBARS)
|
||||
// OVER LIQUID WATER GIVEN THE TEMPERATURE T (CELSIUS). THE POLYNOMIAL
|
||||
// APPROXIMATION BELOW IS DUE TO HERMAN WOBUS, A MATHEMATICIAN WHO
|
||||
// WORKED AT THE NAVY WEATHER RESEARCH FACILITY, NORFOLK, VIRGINIA,
|
||||
// BUT WHO IS NOW RETIRED. THE COEFFICIENTS OF THE POLYNOMIAL WERE
|
||||
// CHOSEN TO FIT THE VALUES IN TABLE 94 ON PP. 351-353 OF THE SMITH-
|
||||
// SONIAN METEOROLOGICAL TABLES BY ROLAND LIST (6TH EDITION). THE
|
||||
// APPROXIMATION IS VALID FOR -50 < T < 100C.
|
||||
//
|
||||
// Baker, Schlatter 17-MAY-1982 Original version.
|
||||
|
||||
const float c0 = +0.99999683e00;
|
||||
const float c1 = -0.90826951e-02;
|
||||
const float c2 = +0.78736169e-04;
|
||||
const float c3 = -0.61117958e-06;
|
||||
const float c4 = +0.43884187e-08;
|
||||
const float c5 = -0.29883885e-10;
|
||||
const float c6 = +0.21874425e-12;
|
||||
const float c7 = -0.17892321e-14;
|
||||
const float c8 = +0.11112018e-16;
|
||||
const float c9 = -0.30994571e-19;
|
||||
const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))));
|
||||
return 0.61078 / pow(p, 8);
|
||||
}
|
||||
|
||||
// From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/
|
||||
// H/T to https://esphome.io/cookbook/bme280_environment.html
|
||||
// H/T to https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
|
||||
float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) {
|
||||
// es = saturated vapor pressure (kPa)
|
||||
// hr = relative humidity [0-1]
|
||||
// ta = absolute temperature (K)
|
||||
|
||||
const float ea = hr * es * 1000; // vapor pressure of the air (Pa)
|
||||
const float mw = 18.01528; // molar mass of water (g⋅mol⁻¹)
|
||||
const float r = 8.31446261815324; // molar gas constant (J⋅K⁻¹)
|
||||
return (ea * mw) / (r * ta);
|
||||
}
|
||||
|
||||
} // namespace absolute_humidity
|
||||
} // namespace esphome
|
76
esphome/components/absolute_humidity/absolute_humidity.h
Normal file
76
esphome/components/absolute_humidity/absolute_humidity.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace absolute_humidity {
|
||||
|
||||
/// Enum listing all implemented saturation vapor pressure equations.
|
||||
enum SaturationVaporPressureEquation {
|
||||
BUCK,
|
||||
TETENS,
|
||||
WOBUS,
|
||||
};
|
||||
|
||||
/// This class implements calculation of absolute humidity from temperature and relative humidity.
|
||||
class AbsoluteHumidityComponent : public sensor::Sensor, public Component {
|
||||
public:
|
||||
AbsoluteHumidityComponent() = default;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
void temperature_callback_(float state) {
|
||||
this->next_update_ = true;
|
||||
this->temperature_ = state;
|
||||
}
|
||||
void humidity_callback_(float state) {
|
||||
this->next_update_ = true;
|
||||
this->humidity_ = state;
|
||||
}
|
||||
|
||||
/** Buck equation for saturation vapor pressure in kPa.
|
||||
*
|
||||
* @param temperature_c Air temperature in °C.
|
||||
*/
|
||||
static float es_buck(float temperature_c);
|
||||
/** Tetens equation for saturation vapor pressure in kPa.
|
||||
*
|
||||
* @param temperature_c Air temperature in °C.
|
||||
*/
|
||||
static float es_tetens(float temperature_c);
|
||||
/** Wobus equation for saturation vapor pressure in kPa.
|
||||
*
|
||||
* @param temperature_c Air temperature in °C.
|
||||
*/
|
||||
static float es_wobus(float temperature_c);
|
||||
|
||||
/** Calculate vapor density (absolute humidity) in g/m³.
|
||||
*
|
||||
* @param es Saturation vapor pressure in kPa.
|
||||
* @param hr Relative humidity 0 to 1.
|
||||
* @param ta Absolute temperature in K.
|
||||
* @param heater_duration The duration in ms that the heater should turn on for when measuring.
|
||||
*/
|
||||
static float vapor_density(float es, float hr, float ta);
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
|
||||
bool next_update_{false};
|
||||
|
||||
float temperature_{NAN};
|
||||
float humidity_{NAN};
|
||||
SaturationVaporPressureEquation equation_;
|
||||
};
|
||||
|
||||
} // namespace absolute_humidity
|
||||
} // namespace esphome
|
56
esphome/components/absolute_humidity/sensor.py
Normal file
56
esphome/components/absolute_humidity/sensor.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
CONF_EQUATION,
|
||||
ICON_WATER,
|
||||
UNIT_GRAMS_PER_CUBIC_METER,
|
||||
)
|
||||
|
||||
absolute_humidity_ns = cg.esphome_ns.namespace("absolute_humidity")
|
||||
AbsoluteHumidityComponent = absolute_humidity_ns.class_(
|
||||
"AbsoluteHumidityComponent", sensor.Sensor, cg.Component
|
||||
)
|
||||
|
||||
SaturationVaporPressureEquation = absolute_humidity_ns.enum(
|
||||
"SaturationVaporPressureEquation"
|
||||
)
|
||||
EQUATION = {
|
||||
"BUCK": SaturationVaporPressureEquation.BUCK,
|
||||
"TETENS": SaturationVaporPressureEquation.TETENS,
|
||||
"WOBUS": SaturationVaporPressureEquation.WOBUS,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
|
||||
icon=ICON_WATER,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AbsoluteHumidityComponent),
|
||||
cv.Required(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_EQUATION, default="WOBUS"): cv.enum(EQUATION, upper=True),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
temperature_sensor = await cg.get_variable(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(temperature_sensor))
|
||||
|
||||
humidity_sensor = await cg.get_variable(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity_sensor(humidity_sensor))
|
||||
|
||||
cg.add(var.set_equation(config[CONF_EQUATION]))
|
@@ -16,13 +16,16 @@ ADC128S102Sensor = adc128s102_ns.class_(
|
||||
)
|
||||
CONF_ADC128S102_ID = "adc128s102_id"
|
||||
|
||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADC128S102Sensor),
|
||||
cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(ADC128S102Sensor)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
@@ -65,7 +65,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_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
|
@@ -15,18 +15,24 @@ AnalogThresholdBinarySensor = analog_threshold_ns.class_(
|
||||
CONF_UPPER = "upper"
|
||||
CONF_LOWER = "lower"
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AnalogThresholdBinarySensor),
|
||||
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_THRESHOLD): cv.Any(
|
||||
cv.float_,
|
||||
cv.Schema(
|
||||
{cv.Required(CONF_UPPER): cv.float_, cv.Required(CONF_LOWER): cv.float_}
|
||||
CONFIG_SCHEMA = (
|
||||
binary_sensor.binary_sensor_schema(AnalogThresholdBinarySensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_THRESHOLD): cv.Any(
|
||||
cv.float_,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_UPPER): cv.float_,
|
||||
cv.Required(CONF_LOWER): cv.float_,
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
@@ -76,8 +76,6 @@ 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])
|
||||
|
@@ -53,6 +53,9 @@ service APIConnection {
|
||||
rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
|
||||
rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
|
||||
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||
|
||||
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -208,6 +211,8 @@ message DeviceInfoResponse {
|
||||
string manufacturer = 12;
|
||||
|
||||
string friendly_name = 13;
|
||||
|
||||
uint32 voice_assistant_version = 14;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@@ -787,6 +792,7 @@ enum ClimateFanMode {
|
||||
CLIMATE_FAN_MIDDLE = 6;
|
||||
CLIMATE_FAN_FOCUS = 7;
|
||||
CLIMATE_FAN_DIFFUSE = 8;
|
||||
CLIMATE_FAN_QUIET = 9;
|
||||
}
|
||||
enum ClimateSwingMode {
|
||||
CLIMATE_SWING_OFF = 0;
|
||||
@@ -828,7 +834,7 @@ message ListEntitiesClimateResponse {
|
||||
repeated ClimateMode supported_modes = 7;
|
||||
float visual_min_temperature = 8;
|
||||
float visual_max_temperature = 9;
|
||||
float visual_temperature_step = 10;
|
||||
float visual_target_temperature_step = 10;
|
||||
// for older peer versions - in new system this
|
||||
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
||||
bool legacy_supports_away = 11;
|
||||
@@ -841,6 +847,7 @@ message ListEntitiesClimateResponse {
|
||||
bool disabled_by_default = 18;
|
||||
string icon = 19;
|
||||
EntityCategory entity_category = 20;
|
||||
float visual_current_temperature_step = 21;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
@@ -1123,6 +1130,7 @@ message MediaPlayerCommandRequest {
|
||||
message SubscribeBluetoothLEAdvertisementsRequest {
|
||||
option (id) = 66;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
}
|
||||
|
||||
message BluetoothServiceData {
|
||||
@@ -1154,6 +1162,7 @@ enum BluetoothDeviceRequestType {
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6;
|
||||
}
|
||||
|
||||
message BluetoothDeviceRequest {
|
||||
@@ -1337,3 +1346,91 @@ message BluetoothGATTNotifyResponse {
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
||||
message BluetoothDevicePairingResponse {
|
||||
option (id) = 85;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
bool paired = 2;
|
||||
int32 error = 3;
|
||||
}
|
||||
|
||||
message BluetoothDeviceUnpairingResponse {
|
||||
option (id) = 86;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
bool success = 2;
|
||||
int32 error = 3;
|
||||
}
|
||||
|
||||
message UnsubscribeBluetoothLEAdvertisementsRequest {
|
||||
option (id) = 87;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
}
|
||||
|
||||
message BluetoothDeviceClearCacheResponse {
|
||||
option (id) = 88;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
bool success = 2;
|
||||
int32 error = 3;
|
||||
}
|
||||
|
||||
// ==================== PUSH TO TALK ====================
|
||||
message SubscribeVoiceAssistantRequest {
|
||||
option (id) = 89;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
bool subscribe = 1;
|
||||
}
|
||||
|
||||
message VoiceAssistantRequest {
|
||||
option (id) = 90;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
bool start = 1;
|
||||
}
|
||||
|
||||
message VoiceAssistantResponse {
|
||||
option (id) = 91;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
uint32 port = 1;
|
||||
bool error = 2;
|
||||
}
|
||||
|
||||
enum VoiceAssistantEvent {
|
||||
VOICE_ASSISTANT_ERROR = 0;
|
||||
VOICE_ASSISTANT_RUN_START = 1;
|
||||
VOICE_ASSISTANT_RUN_END = 2;
|
||||
VOICE_ASSISTANT_STT_START = 3;
|
||||
VOICE_ASSISTANT_STT_END = 4;
|
||||
VOICE_ASSISTANT_INTENT_START = 5;
|
||||
VOICE_ASSISTANT_INTENT_END = 6;
|
||||
VOICE_ASSISTANT_TTS_START = 7;
|
||||
VOICE_ASSISTANT_TTS_END = 8;
|
||||
}
|
||||
|
||||
message VoiceAssistantEventData {
|
||||
string name = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message VoiceAssistantEventResponse {
|
||||
option (id) = 92;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
VoiceAssistantEvent event_type = 1;
|
||||
repeated VoiceAssistantEventData data = 2;
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#include "api_connection.h"
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
@@ -15,6 +16,9 @@
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
#include "esphome/components/voice_assistant/voice_assistant.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -180,7 +184,8 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
|
||||
ListEntitiesBinarySensorResponse msg;
|
||||
msg.object_id = binary_sensor->get_object_id();
|
||||
msg.key = binary_sensor->get_object_id_hash();
|
||||
msg.name = binary_sensor->get_name();
|
||||
if (binary_sensor->has_own_name())
|
||||
msg.name = binary_sensor->get_name();
|
||||
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
|
||||
msg.device_class = binary_sensor->get_device_class();
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
@@ -212,7 +217,8 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
ListEntitiesCoverResponse msg;
|
||||
msg.key = cover->get_object_id_hash();
|
||||
msg.object_id = cover->get_object_id();
|
||||
msg.name = cover->get_name();
|
||||
if (cover->has_own_name())
|
||||
msg.name = cover->get_name();
|
||||
msg.unique_id = get_default_unique_id("cover", cover);
|
||||
msg.assumed_state = traits.get_is_assumed_state();
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
@@ -275,7 +281,8 @@ bool APIConnection::send_fan_info(fan::Fan *fan) {
|
||||
ListEntitiesFanResponse msg;
|
||||
msg.key = fan->get_object_id_hash();
|
||||
msg.object_id = fan->get_object_id();
|
||||
msg.name = fan->get_name();
|
||||
if (fan->has_own_name())
|
||||
msg.name = fan->get_name();
|
||||
msg.unique_id = get_default_unique_id("fan", fan);
|
||||
msg.supports_oscillation = traits.supports_oscillation();
|
||||
msg.supports_speed = traits.supports_speed();
|
||||
@@ -337,7 +344,8 @@ bool APIConnection::send_light_info(light::LightState *light) {
|
||||
ListEntitiesLightResponse msg;
|
||||
msg.key = light->get_object_id_hash();
|
||||
msg.object_id = light->get_object_id();
|
||||
msg.name = light->get_name();
|
||||
if (light->has_own_name())
|
||||
msg.name = light->get_name();
|
||||
msg.unique_id = get_default_unique_id("light", light);
|
||||
|
||||
msg.disabled_by_default = light->is_disabled_by_default();
|
||||
@@ -418,7 +426,8 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
ListEntitiesSensorResponse msg;
|
||||
msg.key = sensor->get_object_id_hash();
|
||||
msg.object_id = sensor->get_object_id();
|
||||
msg.name = sensor->get_name();
|
||||
if (sensor->has_own_name())
|
||||
msg.name = sensor->get_name();
|
||||
msg.unique_id = sensor->unique_id();
|
||||
if (msg.unique_id.empty())
|
||||
msg.unique_id = get_default_unique_id("sensor", sensor);
|
||||
@@ -448,7 +457,8 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
ListEntitiesSwitchResponse msg;
|
||||
msg.key = a_switch->get_object_id_hash();
|
||||
msg.object_id = a_switch->get_object_id();
|
||||
msg.name = a_switch->get_name();
|
||||
if (a_switch->has_own_name())
|
||||
msg.name = a_switch->get_name();
|
||||
msg.unique_id = get_default_unique_id("switch", a_switch);
|
||||
msg.icon = a_switch->get_icon();
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
@@ -533,7 +543,8 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
ListEntitiesClimateResponse msg;
|
||||
msg.key = climate->get_object_id_hash();
|
||||
msg.object_id = climate->get_object_id();
|
||||
msg.name = climate->get_name();
|
||||
if (climate->has_own_name())
|
||||
msg.name = climate->get_name();
|
||||
msg.unique_id = get_default_unique_id("climate", climate);
|
||||
|
||||
msg.disabled_by_default = climate->is_disabled_by_default();
|
||||
@@ -548,7 +559,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
|
||||
msg.visual_min_temperature = traits.get_visual_min_temperature();
|
||||
msg.visual_max_temperature = traits.get_visual_max_temperature();
|
||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
||||
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
|
||||
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
||||
|
||||
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
|
||||
msg.supports_action = traits.get_supports_action();
|
||||
|
||||
@@ -609,7 +622,8 @@ bool APIConnection::send_number_info(number::Number *number) {
|
||||
ListEntitiesNumberResponse msg;
|
||||
msg.key = number->get_object_id_hash();
|
||||
msg.object_id = number->get_object_id();
|
||||
msg.name = number->get_name();
|
||||
if (number->has_own_name())
|
||||
msg.name = number->get_name();
|
||||
msg.unique_id = get_default_unique_id("number", number);
|
||||
msg.icon = number->get_icon();
|
||||
msg.disabled_by_default = number->is_disabled_by_default();
|
||||
@@ -650,7 +664,8 @@ bool APIConnection::send_select_info(select::Select *select) {
|
||||
ListEntitiesSelectResponse msg;
|
||||
msg.key = select->get_object_id_hash();
|
||||
msg.object_id = select->get_object_id();
|
||||
msg.name = select->get_name();
|
||||
if (select->has_own_name())
|
||||
msg.name = select->get_name();
|
||||
msg.unique_id = get_default_unique_id("select", select);
|
||||
msg.icon = select->get_icon();
|
||||
msg.disabled_by_default = select->is_disabled_by_default();
|
||||
@@ -677,7 +692,8 @@ bool APIConnection::send_button_info(button::Button *button) {
|
||||
ListEntitiesButtonResponse msg;
|
||||
msg.key = button->get_object_id_hash();
|
||||
msg.object_id = button->get_object_id();
|
||||
msg.name = button->get_name();
|
||||
if (button->has_own_name())
|
||||
msg.name = button->get_name();
|
||||
msg.unique_id = get_default_unique_id("button", button);
|
||||
msg.icon = button->get_icon();
|
||||
msg.disabled_by_default = button->is_disabled_by_default();
|
||||
@@ -708,7 +724,8 @@ 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();
|
||||
if (a_lock->has_own_name())
|
||||
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();
|
||||
@@ -753,7 +770,8 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
|
||||
ListEntitiesMediaPlayerResponse msg;
|
||||
msg.key = media_player->get_object_id_hash();
|
||||
msg.object_id = media_player->get_object_id();
|
||||
msg.name = media_player->get_name();
|
||||
if (media_player->has_own_name())
|
||||
msg.name = media_player->get_name();
|
||||
msg.unique_id = get_default_unique_id("media_player", media_player);
|
||||
msg.icon = media_player->get_icon();
|
||||
msg.disabled_by_default = media_player->is_disabled_by_default();
|
||||
@@ -797,7 +815,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
ListEntitiesCameraResponse msg;
|
||||
msg.key = camera->get_object_id_hash();
|
||||
msg.object_id = camera->get_object_id();
|
||||
msg.name = camera->get_name();
|
||||
if (camera->has_own_name())
|
||||
msg.name = camera->get_name();
|
||||
msg.unique_id = get_default_unique_id("camera", camera);
|
||||
msg.disabled_by_default = camera->is_disabled_by_default();
|
||||
msg.icon = camera->get_icon();
|
||||
@@ -877,6 +896,30 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool APIConnection::request_voice_assistant(bool start) {
|
||||
if (!this->voice_assistant_subscription_)
|
||||
return false;
|
||||
VoiceAssistantRequest msg;
|
||||
msg.start = start;
|
||||
return this->send_voice_assistant_request(msg);
|
||||
}
|
||||
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
|
||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
this->helper_->getpeername((struct sockaddr *) &storage, &len);
|
||||
voice_assistant::global_voice_assistant->start(&storage, msg.port);
|
||||
}
|
||||
};
|
||||
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
|
||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
||||
voice_assistant::global_voice_assistant->on_event(msg);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
@@ -896,7 +939,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
this->helper_->set_log_info(client_info_);
|
||||
this->client_api_version_major_ = msg.api_version_major;
|
||||
this->client_api_version_minor_ = msg.api_version_minor;
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(),
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(),
|
||||
this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
HelloResponse resp;
|
||||
@@ -951,7 +994,12 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
resp.webserver_port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 3 : 1;
|
||||
resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active()
|
||||
? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION
|
||||
: bluetooth_proxy::PASSIVE_ONLY_VERSION;
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
resp.voice_assistant_version = 1;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@
|
||||
#include "api_server.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -97,6 +98,12 @@ class APIConnection : public APIServerConnection {
|
||||
this->send_homeassistant_service_response(call);
|
||||
}
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override {
|
||||
this->bluetooth_le_advertisement_subscription_ = true;
|
||||
}
|
||||
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override {
|
||||
this->bluetooth_le_advertisement_subscription_ = false;
|
||||
}
|
||||
bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
|
||||
|
||||
void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
@@ -117,6 +124,15 @@ class APIConnection : public APIServerConnection {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
|
||||
this->voice_assistant_subscription_ = msg.subscribe;
|
||||
}
|
||||
bool request_voice_assistant(bool start);
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
|
||||
#endif
|
||||
|
||||
void on_disconnect_response(const DisconnectResponse &value) override;
|
||||
void on_ping_response(const PingResponse &value) override {
|
||||
// we initiated ping
|
||||
@@ -150,9 +166,7 @@ class APIConnection : public APIServerConnection {
|
||||
return {};
|
||||
}
|
||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override {
|
||||
this->bluetooth_le_advertisement_subscription_ = true;
|
||||
}
|
||||
|
||||
bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
|
||||
bool is_connection_setup() override {
|
||||
return this->connection_state_ == ConnectionState ::CONNECTED || this->is_authenticated();
|
||||
@@ -197,7 +211,12 @@ class APIConnection : public APIServerConnection {
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool bluetooth_le_advertisement_subscription_{false};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool voice_assistant_subscription_{false};
|
||||
#endif
|
||||
bool next_close_ = false;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
|
@@ -295,7 +295,7 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
// ignore contents, may be used in future for flags
|
||||
prologue_.push_back((uint8_t)(frame.msg.size() >> 8));
|
||||
prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
|
||||
prologue_.push_back((uint8_t) frame.msg.size());
|
||||
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
|
||||
|
||||
@@ -492,9 +492,9 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
|
||||
// tmpbuf[1], tmpbuf[2] to be set later
|
||||
const uint8_t msg_offset = 3;
|
||||
const uint8_t payload_offset = msg_offset + 4;
|
||||
tmpbuf[msg_offset + 0] = (uint8_t)(type >> 8); // type
|
||||
tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8); // type
|
||||
tmpbuf[msg_offset + 1] = (uint8_t) type;
|
||||
tmpbuf[msg_offset + 2] = (uint8_t)(payload_len >> 8); // data_len
|
||||
tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len
|
||||
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
|
||||
// copy data
|
||||
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
|
||||
@@ -512,7 +512,7 @@ APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload
|
||||
}
|
||||
|
||||
size_t total_len = 3 + mbuf.size;
|
||||
tmpbuf[1] = (uint8_t)(mbuf.size >> 8);
|
||||
tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
|
||||
tmpbuf[2] = (uint8_t) mbuf.size;
|
||||
|
||||
struct iovec iov;
|
||||
@@ -610,7 +610,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
||||
uint8_t header[3];
|
||||
header[0] = 0x01; // indicator
|
||||
header[1] = (uint8_t)(len >> 8);
|
||||
header[1] = (uint8_t) (len >> 8);
|
||||
header[2] = (uint8_t) len;
|
||||
|
||||
struct iovec iov[2];
|
||||
|
@@ -10,8 +10,8 @@
|
||||
#include "noise/protocol.h"
|
||||
#endif
|
||||
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "api_noise_context.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -67,6 +67,7 @@ class APIFrameHelper {
|
||||
virtual bool can_write_without_blocking() = 0;
|
||||
virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
|
||||
virtual std::string getpeername() = 0;
|
||||
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
||||
virtual APIError close() = 0;
|
||||
virtual APIError shutdown(int how) = 0;
|
||||
// Give this helper a name for logging
|
||||
@@ -84,7 +85,10 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
bool can_write_without_blocking() override;
|
||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
||||
std::string getpeername() override { return socket_->getpeername(); }
|
||||
std::string getpeername() override { return this->socket_->getpeername(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
return this->socket_->getpeername(addr, addrlen);
|
||||
}
|
||||
APIError close() override;
|
||||
APIError shutdown(int how) override;
|
||||
// Give this helper a name for logging
|
||||
@@ -144,7 +148,10 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
bool can_write_without_blocking() override;
|
||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
||||
std::string getpeername() override { return socket_->getpeername(); }
|
||||
std::string getpeername() override { return this->socket_->getpeername(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
return this->socket_->getpeername(addr, addrlen);
|
||||
}
|
||||
APIError close() override;
|
||||
APIError shutdown(int how) override;
|
||||
// Give this helper a name for logging
|
||||
|
@@ -235,6 +235,8 @@ template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::Climat
|
||||
return "CLIMATE_FAN_FOCUS";
|
||||
case enums::CLIMATE_FAN_DIFFUSE:
|
||||
return "CLIMATE_FAN_DIFFUSE";
|
||||
case enums::CLIMATE_FAN_QUIET:
|
||||
return "CLIMATE_FAN_QUIET";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
@@ -398,6 +400,34 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::VoiceAssistantEvent value) {
|
||||
switch (value) {
|
||||
case enums::VOICE_ASSISTANT_ERROR:
|
||||
return "VOICE_ASSISTANT_ERROR";
|
||||
case enums::VOICE_ASSISTANT_RUN_START:
|
||||
return "VOICE_ASSISTANT_RUN_START";
|
||||
case enums::VOICE_ASSISTANT_RUN_END:
|
||||
return "VOICE_ASSISTANT_RUN_END";
|
||||
case enums::VOICE_ASSISTANT_STT_START:
|
||||
return "VOICE_ASSISTANT_STT_START";
|
||||
case enums::VOICE_ASSISTANT_STT_END:
|
||||
return "VOICE_ASSISTANT_STT_END";
|
||||
case enums::VOICE_ASSISTANT_INTENT_START:
|
||||
return "VOICE_ASSISTANT_INTENT_START";
|
||||
case enums::VOICE_ASSISTANT_INTENT_END:
|
||||
return "VOICE_ASSISTANT_INTENT_END";
|
||||
case enums::VOICE_ASSISTANT_TTS_START:
|
||||
return "VOICE_ASSISTANT_TTS_START";
|
||||
case enums::VOICE_ASSISTANT_TTS_END:
|
||||
return "VOICE_ASSISTANT_TTS_END";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
@@ -590,6 +620,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->bluetooth_proxy_version = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->voice_assistant_version = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -650,6 +684,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(11, this->bluetooth_proxy_version);
|
||||
buffer.encode_string(12, this->manufacturer);
|
||||
buffer.encode_string(13, this->friendly_name);
|
||||
buffer.encode_uint32(14, this->voice_assistant_version);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@@ -708,6 +743,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(" friendly_name: ");
|
||||
out.append("'").append(this->friendly_name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" voice_assistant_version: ");
|
||||
sprintf(buffer, "%u", this->voice_assistant_version);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -3449,7 +3489,11 @@ bool ListEntitiesClimateResponse::decode_32bit(uint32_t field_id, Proto32Bit val
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->visual_temperature_step = value.as_float();
|
||||
this->visual_target_temperature_step = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 21: {
|
||||
this->visual_current_temperature_step = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
@@ -3468,7 +3512,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
buffer.encode_float(8, this->visual_min_temperature);
|
||||
buffer.encode_float(9, this->visual_max_temperature);
|
||||
buffer.encode_float(10, this->visual_temperature_step);
|
||||
buffer.encode_float(10, this->visual_target_temperature_step);
|
||||
buffer.encode_bool(11, this->legacy_supports_away);
|
||||
buffer.encode_bool(12, this->supports_action);
|
||||
for (auto &it : this->supported_fan_modes) {
|
||||
@@ -3489,6 +3533,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(18, this->disabled_by_default);
|
||||
buffer.encode_string(19, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
|
||||
buffer.encode_float(21, this->visual_current_temperature_step);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
@@ -3535,8 +3580,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" visual_temperature_step: ");
|
||||
sprintf(buffer, "%g", this->visual_temperature_step);
|
||||
out.append(" visual_target_temperature_step: ");
|
||||
sprintf(buffer, "%g", this->visual_target_temperature_step);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
@@ -3589,6 +3634,11 @@ void ListEntitiesClimateResponse::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(" visual_current_temperature_step: ");
|
||||
sprintf(buffer, "%g", this->visual_current_temperature_step);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -5962,6 +6012,290 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothDevicePairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->paired = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->error = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->paired);
|
||||
buffer.encode_int32(3, this->error);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothDevicePairingResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothDevicePairingResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" paired: ");
|
||||
out.append(YESNO(this->paired));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" error: ");
|
||||
sprintf(buffer, "%d", this->error);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothDeviceUnpairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->success = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->error = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->success);
|
||||
buffer.encode_int32(3, this->error);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothDeviceUnpairingResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" success: ");
|
||||
out.append(YESNO(this->success));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" error: ");
|
||||
sprintf(buffer, "%d", this->error);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
void UnsubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
|
||||
out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothDeviceClearCacheResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->success = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->error = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->success);
|
||||
buffer.encode_int32(3, this->error);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothDeviceClearCacheResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothDeviceClearCacheResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" success: ");
|
||||
out.append(YESNO(this->success));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" error: ");
|
||||
sprintf(buffer, "%d", this->error);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->subscribe = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->subscribe); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("SubscribeVoiceAssistantRequest {\n");
|
||||
out.append(" subscribe: ");
|
||||
out.append(YESNO(this->subscribe));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->start = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->start); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("VoiceAssistantRequest {\n");
|
||||
out.append(" start: ");
|
||||
out.append(YESNO(this->start));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool VoiceAssistantResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->port = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->error = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void VoiceAssistantResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->port);
|
||||
buffer.encode_bool(2, this->error);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("VoiceAssistantResponse {\n");
|
||||
out.append(" port: ");
|
||||
sprintf(buffer, "%u", this->port);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" error: ");
|
||||
out.append(YESNO(this->error));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool VoiceAssistantEventData::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->value = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void VoiceAssistantEventData::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->name);
|
||||
buffer.encode_string(2, this->value);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantEventData::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("VoiceAssistantEventData {\n");
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" value: ");
|
||||
out.append("'").append(this->value).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool VoiceAssistantEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->event_type = value.as_enum<enums::VoiceAssistantEvent>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool VoiceAssistantEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->data.push_back(value.as_message<VoiceAssistantEventData>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void VoiceAssistantEventResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::VoiceAssistantEvent>(1, this->event_type);
|
||||
for (auto &it : this->data) {
|
||||
buffer.encode_message<VoiceAssistantEventData>(2, it, true);
|
||||
}
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantEventResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("VoiceAssistantEventResponse {\n");
|
||||
out.append(" event_type: ");
|
||||
out.append(proto_enum_to_string<enums::VoiceAssistantEvent>(this->event_type));
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->data) {
|
||||
out.append(" data: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -99,6 +99,7 @@ enum ClimateFanMode : uint32_t {
|
||||
CLIMATE_FAN_MIDDLE = 6,
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
CLIMATE_FAN_QUIET = 9,
|
||||
};
|
||||
enum ClimateSwingMode : uint32_t {
|
||||
CLIMATE_SWING_OFF = 0,
|
||||
@@ -162,6 +163,18 @@ enum BluetoothDeviceRequestType : uint32_t {
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6,
|
||||
};
|
||||
enum VoiceAssistantEvent : uint32_t {
|
||||
VOICE_ASSISTANT_ERROR = 0,
|
||||
VOICE_ASSISTANT_RUN_START = 1,
|
||||
VOICE_ASSISTANT_RUN_END = 2,
|
||||
VOICE_ASSISTANT_STT_START = 3,
|
||||
VOICE_ASSISTANT_STT_END = 4,
|
||||
VOICE_ASSISTANT_INTENT_START = 5,
|
||||
VOICE_ASSISTANT_INTENT_END = 6,
|
||||
VOICE_ASSISTANT_TTS_START = 7,
|
||||
VOICE_ASSISTANT_TTS_END = 8,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
@@ -277,6 +290,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
uint32_t bluetooth_proxy_version{0};
|
||||
std::string manufacturer{};
|
||||
std::string friendly_name{};
|
||||
uint32_t voice_assistant_version{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -914,7 +928,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
std::vector<enums::ClimateMode> supported_modes{};
|
||||
float visual_min_temperature{0.0f};
|
||||
float visual_max_temperature{0.0f};
|
||||
float visual_temperature_step{0.0f};
|
||||
float visual_target_temperature_step{0.0f};
|
||||
bool legacy_supports_away{false};
|
||||
bool supports_action{false};
|
||||
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
||||
@@ -925,6 +939,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
float visual_current_temperature_step{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -1526,6 +1541,113 @@ class BluetoothGATTNotifyResponse : public ProtoMessage {
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothDevicePairingResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
bool paired{false};
|
||||
int32_t error{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothDeviceUnpairingResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
bool success{false};
|
||||
int32_t error{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class BluetoothDeviceClearCacheResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
bool success{false};
|
||||
int32_t error{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeVoiceAssistantRequest : public ProtoMessage {
|
||||
public:
|
||||
bool subscribe{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class VoiceAssistantRequest : public ProtoMessage {
|
||||
public:
|
||||
bool start{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class VoiceAssistantResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t port{0};
|
||||
bool error{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class VoiceAssistantEventData : public ProtoMessage {
|
||||
public:
|
||||
std::string name{};
|
||||
std::string value{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class VoiceAssistantEventResponse : public ProtoMessage {
|
||||
public:
|
||||
enums::VoiceAssistantEvent event_type{};
|
||||
std::vector<VoiceAssistantEventData> data{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -329,6 +329,8 @@ bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayer
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_le_advertisement_response: %s", msg.dump().c_str());
|
||||
@@ -425,6 +427,46 @@ bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const Bluetoot
|
||||
return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_device_pairing_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothDevicePairingResponse>(msg, 85);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_device_unpairing_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothDeviceUnpairingResponse>(msg, 86);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_device_clear_cache_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantRequest &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_voice_assistant_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<VoiceAssistantRequest>(msg, 90);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
@@ -693,12 +735,14 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
break;
|
||||
}
|
||||
case 66: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
SubscribeBluetoothLEAdvertisementsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_subscribe_bluetooth_le_advertisements_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 68: {
|
||||
@@ -786,6 +830,50 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_subscribe_bluetooth_connections_free_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 87: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
UnsubscribeBluetoothLEAdvertisementsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_unsubscribe_bluetooth_le_advertisements_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 89: {
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
SubscribeVoiceAssistantRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_subscribe_voice_assistant_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 91: {
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
VoiceAssistantResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_voice_assistant_response(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 92: {
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
VoiceAssistantEventResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_voice_assistant_event_response(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -1049,6 +1137,7 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
|
||||
this->media_player_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
@@ -1061,6 +1150,7 @@ void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||
}
|
||||
this->subscribe_bluetooth_le_advertisements(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
@@ -1169,6 +1259,33 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
|
||||
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->unsubscribe_bluetooth_le_advertisements(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_voice_assistant(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -154,8 +154,10 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
|
||||
#endif
|
||||
@@ -209,6 +211,31 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_unsubscribe_bluetooth_le_advertisements_request(
|
||||
const UnsubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool send_voice_assistant_request(const VoiceAssistantRequest &msg);
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
@@ -261,7 +288,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0;
|
||||
#endif
|
||||
@@ -286,6 +315,12 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
@@ -333,7 +368,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
#endif
|
||||
@@ -358,6 +395,13 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_unsubscribe_bluetooth_le_advertisements_request(
|
||||
const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
@@ -45,7 +45,7 @@ void APIServer::setup() {
|
||||
|
||||
struct sockaddr_storage server;
|
||||
|
||||
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_));
|
||||
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
|
||||
if (sl == 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
|
||||
this->mark_failed();
|
||||
@@ -309,6 +309,39 @@ void APIServer::send_bluetooth_device_connection(uint64_t address, bool connecte
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error) {
|
||||
BluetoothDevicePairingResponse call;
|
||||
call.address = address;
|
||||
call.paired = paired;
|
||||
call.error = error;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_device_pairing_response(call);
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error) {
|
||||
BluetoothDeviceUnpairingResponse call;
|
||||
call.address = address;
|
||||
call.success = success;
|
||||
call.error = error;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_device_unpairing_response(call);
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) {
|
||||
BluetoothDeviceClearCacheResponse call;
|
||||
call.address = address;
|
||||
call.success = success;
|
||||
call.error = error;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_device_clear_cache_response(call);
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
|
||||
BluetoothConnectionsFreeResponse call;
|
||||
call.free = free;
|
||||
@@ -394,5 +427,21 @@ void APIServer::on_shutdown() {
|
||||
delay(10);
|
||||
}
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool APIServer::start_voice_assistant() {
|
||||
for (auto &c : this->clients_) {
|
||||
if (c->request_voice_assistant(true))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void APIServer::stop_voice_assistant() {
|
||||
for (auto &c : this->clients_) {
|
||||
if (c->request_voice_assistant(false))
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -78,6 +78,9 @@ class APIServer : public Component, public Controller {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
|
||||
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
|
||||
void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
|
||||
void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
|
||||
void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
|
||||
void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
|
||||
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
|
||||
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
|
||||
@@ -92,6 +95,11 @@ class APIServer : public Component, public Controller {
|
||||
void request_time();
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool start_voice_assistant();
|
||||
void stop_voice_assistant();
|
||||
#endif
|
||||
|
||||
bool is_connected() const;
|
||||
|
||||
struct HomeAssistantStateSubscription {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#include "proto.h"
|
||||
#include <cinttypes>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -13,7 +14,7 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
uint32_t consumed;
|
||||
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid field start at %u", i);
|
||||
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -25,12 +26,12 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
case 0: { // VarInt
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid VarInt at %u", i);
|
||||
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_varint(field_id, *res)) {
|
||||
ESP_LOGV(TAG, "Cannot decode VarInt field %u with value %u!", field_id, res->as_uint32());
|
||||
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
|
||||
}
|
||||
i += consumed;
|
||||
break;
|
||||
@@ -38,38 +39,38 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
case 2: { // Length-delimited
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at %u", i);
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
i += consumed;
|
||||
if (field_length > length - i) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %u", i);
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
|
||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %u!", field_id);
|
||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
|
||||
}
|
||||
i += field_length;
|
||||
break;
|
||||
}
|
||||
case 5: { // 32-bit
|
||||
if (length - i < 4) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %u", i);
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
|
||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
||||
}
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGV(TAG, "Invalid field type at %u", i);
|
||||
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
@@ -12,7 +12,6 @@ from esphome.const import (
|
||||
CONF_CAPACITANCE,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_AS3935_ID = "as3935_id"
|
||||
|
@@ -26,9 +26,13 @@ void AS3935Component::setup() {
|
||||
void AS3935Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AS3935:");
|
||||
LOG_PIN(" Interrupt Pin: ", this->irq_pin_);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Thunder alert", this->thunder_alert_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
|
||||
LOG_SENSOR(" ", "Lightning energy", this->energy_sensor_);
|
||||
#endif
|
||||
}
|
||||
|
||||
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
@@ -44,16 +48,22 @@ void AS3935Component::loop() {
|
||||
ESP_LOGI(TAG, "Disturber was detected - try increasing the spike rejection value!");
|
||||
} else if (int_value == LIGHTNING_INT) {
|
||||
ESP_LOGI(TAG, "Lightning has been detected!");
|
||||
if (this->thunder_alert_binary_sensor_ != nullptr)
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->thunder_alert_binary_sensor_ != nullptr) {
|
||||
this->thunder_alert_binary_sensor_->publish_state(true);
|
||||
this->set_timeout(10, [this]() { this->thunder_alert_binary_sensor_->publish_state(false); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
uint8_t distance = this->get_distance_to_storm_();
|
||||
if (this->distance_sensor_ != nullptr)
|
||||
this->distance_sensor_->publish_state(distance);
|
||||
|
||||
uint32_t energy = this->get_lightning_energy_();
|
||||
if (this->energy_sensor_ != nullptr)
|
||||
this->energy_sensor_->publish_state(energy);
|
||||
#endif
|
||||
}
|
||||
this->thunder_alert_binary_sensor_->publish_state(false);
|
||||
}
|
||||
|
||||
void AS3935Component::write_indoor(bool indoor) {
|
||||
|
@@ -1,9 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935 {
|
||||
@@ -52,6 +57,15 @@ enum AS3935Values {
|
||||
};
|
||||
|
||||
class AS3935Component : public Component {
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(distance)
|
||||
SUB_SENSOR(energy)
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
SUB_BINARY_SENSOR(thunder_alert)
|
||||
#endif
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
@@ -59,11 +73,7 @@ class AS3935Component : public Component {
|
||||
void loop() override;
|
||||
|
||||
void set_irq_pin(GPIOPin *irq_pin) { irq_pin_ = irq_pin; }
|
||||
void set_distance_sensor(sensor::Sensor *distance_sensor) { distance_sensor_ = distance_sensor; }
|
||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||
void set_thunder_alert_binary_sensor(binary_sensor::BinarySensor *thunder_alert_binary_sensor) {
|
||||
thunder_alert_binary_sensor_ = thunder_alert_binary_sensor;
|
||||
}
|
||||
|
||||
void set_indoor(bool indoor) { indoor_ = indoor; }
|
||||
void write_indoor(bool indoor);
|
||||
void set_noise_level(uint8_t noise_level) { noise_level_ = noise_level; }
|
||||
@@ -92,9 +102,6 @@ class AS3935Component : public Component {
|
||||
|
||||
virtual void write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) = 0;
|
||||
|
||||
sensor::Sensor *distance_sensor_{nullptr};
|
||||
sensor::Sensor *energy_sensor_{nullptr};
|
||||
binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr};
|
||||
GPIOPin *irq_pin_;
|
||||
|
||||
bool indoor_;
|
||||
|
0
esphome/components/as7341/__init__.py
Normal file
0
esphome/components/as7341/__init__.py
Normal file
271
esphome/components/as7341/as7341.cpp
Normal file
271
esphome/components/as7341/as7341.cpp
Normal file
@@ -0,0 +1,271 @@
|
||||
#include "as7341.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as7341 {
|
||||
|
||||
static const char *const TAG = "as7341";
|
||||
|
||||
void AS7341Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up AS7341...");
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
// Verify device ID
|
||||
uint8_t id;
|
||||
this->read_byte(AS7341_ID, &id);
|
||||
ESP_LOGCONFIG(TAG, " Read ID: 0x%X", id);
|
||||
if ((id & 0xFC) != (AS7341_CHIP_ID << 2)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Power on (enter IDLE state)
|
||||
if (!this->enable_power(true)) {
|
||||
ESP_LOGE(TAG, " Power on failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set configuration
|
||||
this->write_byte(AS7341_CONFIG, 0x00);
|
||||
this->setup_atime(this->atime_);
|
||||
this->setup_astep(this->astep_);
|
||||
this->setup_gain(this->gain_);
|
||||
}
|
||||
|
||||
void AS7341Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AS7341:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with AS7341 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", get_gain());
|
||||
ESP_LOGCONFIG(TAG, " ATIME: %u", get_atime());
|
||||
ESP_LOGCONFIG(TAG, " ASTEP: %u", get_astep());
|
||||
|
||||
LOG_SENSOR(" ", "F1", this->f1_);
|
||||
LOG_SENSOR(" ", "F2", this->f2_);
|
||||
LOG_SENSOR(" ", "F3", this->f3_);
|
||||
LOG_SENSOR(" ", "F4", this->f4_);
|
||||
LOG_SENSOR(" ", "F5", this->f5_);
|
||||
LOG_SENSOR(" ", "F6", this->f6_);
|
||||
LOG_SENSOR(" ", "F7", this->f7_);
|
||||
LOG_SENSOR(" ", "F8", this->f8_);
|
||||
LOG_SENSOR(" ", "Clear", this->clear_);
|
||||
LOG_SENSOR(" ", "NIR", this->nir_);
|
||||
}
|
||||
|
||||
float AS7341Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void AS7341Component::update() {
|
||||
this->read_channels(this->channel_readings_);
|
||||
|
||||
if (this->f1_ != nullptr) {
|
||||
this->f1_->publish_state(this->channel_readings_[0]);
|
||||
}
|
||||
if (this->f2_ != nullptr) {
|
||||
this->f2_->publish_state(this->channel_readings_[1]);
|
||||
}
|
||||
if (this->f3_ != nullptr) {
|
||||
this->f3_->publish_state(this->channel_readings_[2]);
|
||||
}
|
||||
if (this->f4_ != nullptr) {
|
||||
this->f4_->publish_state(this->channel_readings_[3]);
|
||||
}
|
||||
if (this->f5_ != nullptr) {
|
||||
this->f5_->publish_state(this->channel_readings_[6]);
|
||||
}
|
||||
if (this->f6_ != nullptr) {
|
||||
this->f6_->publish_state(this->channel_readings_[7]);
|
||||
}
|
||||
if (this->f7_ != nullptr) {
|
||||
this->f7_->publish_state(this->channel_readings_[8]);
|
||||
}
|
||||
if (this->f8_ != nullptr) {
|
||||
this->f8_->publish_state(this->channel_readings_[9]);
|
||||
}
|
||||
if (this->clear_ != nullptr) {
|
||||
this->clear_->publish_state(this->channel_readings_[10]);
|
||||
}
|
||||
if (this->nir_ != nullptr) {
|
||||
this->nir_->publish_state(this->channel_readings_[11]);
|
||||
}
|
||||
}
|
||||
|
||||
AS7341Gain AS7341Component::get_gain() {
|
||||
uint8_t data;
|
||||
this->read_byte(AS7341_CFG1, &data);
|
||||
return (AS7341Gain) data;
|
||||
}
|
||||
|
||||
uint8_t AS7341Component::get_atime() {
|
||||
uint8_t data;
|
||||
this->read_byte(AS7341_ATIME, &data);
|
||||
return data;
|
||||
}
|
||||
|
||||
uint16_t AS7341Component::get_astep() {
|
||||
uint16_t data;
|
||||
this->read_byte_16(AS7341_ASTEP, &data);
|
||||
return this->swap_bytes(data);
|
||||
}
|
||||
|
||||
bool AS7341Component::setup_gain(AS7341Gain gain) { return this->write_byte(AS7341_CFG1, gain); }
|
||||
|
||||
bool AS7341Component::setup_atime(uint8_t atime) { return this->write_byte(AS7341_ATIME, atime); }
|
||||
|
||||
bool AS7341Component::setup_astep(uint16_t astep) { return this->write_byte_16(AS7341_ASTEP, swap_bytes(astep)); }
|
||||
|
||||
bool AS7341Component::read_channels(uint16_t *data) {
|
||||
this->set_smux_low_channels(true);
|
||||
this->enable_spectral_measurement(true);
|
||||
this->wait_for_data();
|
||||
bool low_success = this->read_bytes_16(AS7341_CH0_DATA_L, data, 6);
|
||||
|
||||
this->set_smux_low_channels(false);
|
||||
this->enable_spectral_measurement(true);
|
||||
this->wait_for_data();
|
||||
bool high_sucess = this->read_bytes_16(AS7341_CH0_DATA_L, &data[6], 6);
|
||||
|
||||
return low_success && high_sucess;
|
||||
}
|
||||
|
||||
void AS7341Component::set_smux_low_channels(bool enable) {
|
||||
this->enable_spectral_measurement(false);
|
||||
this->set_smux_command(AS7341_SMUX_CMD_WRITE);
|
||||
|
||||
if (enable) {
|
||||
this->configure_smux_low_channels();
|
||||
|
||||
} else {
|
||||
this->configure_smux_high_channels();
|
||||
}
|
||||
this->enable_smux();
|
||||
}
|
||||
|
||||
bool AS7341Component::set_smux_command(AS7341SmuxCommand command) {
|
||||
uint8_t data = command << 3; // Write to bits 4:3 of the register
|
||||
return this->write_byte(AS7341_CFG6, data);
|
||||
}
|
||||
|
||||
void AS7341Component::configure_smux_low_channels() {
|
||||
// SMUX Config for F1,F2,F3,F4,NIR,Clear
|
||||
this->write_byte(0x00, 0x30); // F3 left set to ADC2
|
||||
this->write_byte(0x01, 0x01); // F1 left set to ADC0
|
||||
this->write_byte(0x02, 0x00); // Reserved or disabled
|
||||
this->write_byte(0x03, 0x00); // F8 left disabled
|
||||
this->write_byte(0x04, 0x00); // F6 left disabled
|
||||
this->write_byte(0x05, 0x42); // F4 left connected to ADC3/f2 left connected to ADC1
|
||||
this->write_byte(0x06, 0x00); // F5 left disbled
|
||||
this->write_byte(0x07, 0x00); // F7 left disbled
|
||||
this->write_byte(0x08, 0x50); // CLEAR connected to ADC4
|
||||
this->write_byte(0x09, 0x00); // F5 right disabled
|
||||
this->write_byte(0x0A, 0x00); // F7 right disabled
|
||||
this->write_byte(0x0B, 0x00); // Reserved or disabled
|
||||
this->write_byte(0x0C, 0x20); // F2 right connected to ADC1
|
||||
this->write_byte(0x0D, 0x04); // F4 right connected to ADC3
|
||||
this->write_byte(0x0E, 0x00); // F6/F8 right disabled
|
||||
this->write_byte(0x0F, 0x30); // F3 right connected to AD2
|
||||
this->write_byte(0x10, 0x01); // F1 right connected to AD0
|
||||
this->write_byte(0x11, 0x50); // CLEAR right connected to AD4
|
||||
this->write_byte(0x12, 0x00); // Reserved or disabled
|
||||
this->write_byte(0x13, 0x06); // NIR connected to ADC5
|
||||
}
|
||||
|
||||
void AS7341Component::configure_smux_high_channels() {
|
||||
// SMUX Config for F5,F6,F7,F8,NIR,Clear
|
||||
this->write_byte(0x00, 0x00); // F3 left disable
|
||||
this->write_byte(0x01, 0x00); // F1 left disable
|
||||
this->write_byte(0x02, 0x00); // reserved/disable
|
||||
this->write_byte(0x03, 0x40); // F8 left connected to ADC3
|
||||
this->write_byte(0x04, 0x02); // F6 left connected to ADC1
|
||||
this->write_byte(0x05, 0x00); // F4/ F2 disabled
|
||||
this->write_byte(0x06, 0x10); // F5 left connected to ADC0
|
||||
this->write_byte(0x07, 0x03); // F7 left connected to ADC2
|
||||
this->write_byte(0x08, 0x50); // CLEAR Connected to ADC4
|
||||
this->write_byte(0x09, 0x10); // F5 right connected to ADC0
|
||||
this->write_byte(0x0A, 0x03); // F7 right connected to ADC2
|
||||
this->write_byte(0x0B, 0x00); // Reserved or disabled
|
||||
this->write_byte(0x0C, 0x00); // F2 right disabled
|
||||
this->write_byte(0x0D, 0x00); // F4 right disabled
|
||||
this->write_byte(0x0E, 0x24); // F8 right connected to ADC2/ F6 right connected to ADC1
|
||||
this->write_byte(0x0F, 0x00); // F3 right disabled
|
||||
this->write_byte(0x10, 0x00); // F1 right disabled
|
||||
this->write_byte(0x11, 0x50); // CLEAR right connected to AD4
|
||||
this->write_byte(0x12, 0x00); // Reserved or disabled
|
||||
this->write_byte(0x13, 0x06); // NIR connected to ADC5
|
||||
}
|
||||
|
||||
bool AS7341Component::enable_smux() {
|
||||
this->set_register_bit(AS7341_ENABLE, 4);
|
||||
|
||||
uint16_t timeout = 1000;
|
||||
for (uint16_t time = 0; time < timeout; time++) {
|
||||
// The SMUXEN bit is cleared once the SMUX operation is finished
|
||||
bool smuxen = this->read_register_bit(AS7341_ENABLE, 4);
|
||||
if (!smuxen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AS7341Component::wait_for_data() {
|
||||
uint16_t timeout = 1000;
|
||||
for (uint16_t time = 0; time < timeout; time++) {
|
||||
if (this->is_data_ready()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AS7341Component::is_data_ready() { return this->read_register_bit(AS7341_STATUS2, 6); }
|
||||
|
||||
bool AS7341Component::enable_power(bool enable) { return this->write_register_bit(AS7341_ENABLE, enable, 0); }
|
||||
|
||||
bool AS7341Component::enable_spectral_measurement(bool enable) {
|
||||
return this->write_register_bit(AS7341_ENABLE, enable, 1);
|
||||
}
|
||||
|
||||
bool AS7341Component::read_register_bit(uint8_t address, uint8_t bit_position) {
|
||||
uint8_t data;
|
||||
this->read_byte(address, &data);
|
||||
bool bit = (data & (1 << bit_position)) > 0;
|
||||
return bit;
|
||||
}
|
||||
|
||||
bool AS7341Component::write_register_bit(uint8_t address, bool value, uint8_t bit_position) {
|
||||
if (value) {
|
||||
return this->set_register_bit(address, bit_position);
|
||||
}
|
||||
|
||||
return this->clear_register_bit(address, bit_position);
|
||||
}
|
||||
|
||||
bool AS7341Component::set_register_bit(uint8_t address, uint8_t bit_position) {
|
||||
uint8_t data;
|
||||
this->read_byte(address, &data);
|
||||
data |= (1 << bit_position);
|
||||
return this->write_byte(address, data);
|
||||
}
|
||||
|
||||
bool AS7341Component::clear_register_bit(uint8_t address, uint8_t bit_position) {
|
||||
uint8_t data;
|
||||
this->read_byte(address, &data);
|
||||
data &= ~(1 << bit_position);
|
||||
return this->write_byte(address, data);
|
||||
}
|
||||
|
||||
uint16_t AS7341Component::swap_bytes(uint16_t data) { return (data >> 8) | (data << 8); }
|
||||
|
||||
} // namespace as7341
|
||||
} // namespace esphome
|
144
esphome/components/as7341/as7341.h
Normal file
144
esphome/components/as7341/as7341.h
Normal file
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as7341 {
|
||||
|
||||
static const uint8_t AS7341_CHIP_ID = 0X09;
|
||||
|
||||
static const uint8_t AS7341_CONFIG = 0x70;
|
||||
static const uint8_t AS7341_LED = 0x74;
|
||||
|
||||
static const uint8_t AS7341_ENABLE = 0x80;
|
||||
static const uint8_t AS7341_ATIME = 0x81;
|
||||
|
||||
static const uint8_t AS7341_WTIME = 0x83;
|
||||
|
||||
static const uint8_t AS7341_AUXID = 0x90;
|
||||
static const uint8_t AS7341_REVID = 0x91;
|
||||
static const uint8_t AS7341_ID = 0x92;
|
||||
static const uint8_t AS7341_STATUS = 0x93;
|
||||
|
||||
static const uint8_t AS7341_CH0_DATA_L = 0x95;
|
||||
static const uint8_t AS7341_CH0_DATA_H = 0x96;
|
||||
static const uint8_t AS7341_CH1_DATA_L = 0x97;
|
||||
static const uint8_t AS7341_CH1_DATA_H = 0x98;
|
||||
static const uint8_t AS7341_CH2_DATA_L = 0x99;
|
||||
static const uint8_t AS7341_CH2_DATA_H = 0x9A;
|
||||
static const uint8_t AS7341_CH3_DATA_L = 0x9B;
|
||||
static const uint8_t AS7341_CH3_DATA_H = 0x9C;
|
||||
static const uint8_t AS7341_CH4_DATA_L = 0x9D;
|
||||
static const uint8_t AS7341_CH4_DATA_H = 0x9E;
|
||||
static const uint8_t AS7341_CH5_DATA_L = 0x9F;
|
||||
static const uint8_t AS7341_CH5_DATA_H = 0xA0;
|
||||
|
||||
static const uint8_t AS7341_STATUS2 = 0xA3;
|
||||
|
||||
static const uint8_t AS7341_CFG1 = 0xAA; ///< Controls ADC Gain
|
||||
|
||||
static const uint8_t AS7341_CFG6 = 0xAF; // Stores SMUX command
|
||||
static const uint8_t AS7341_CFG9 = 0xB2; // Config for system interrupts (SMUX, Flicker detection)
|
||||
|
||||
static const uint8_t AS7341_ASTEP = 0xCA; // LSB
|
||||
static const uint8_t AS7341_ASTEP_MSB = 0xCB; // MSB
|
||||
|
||||
enum AS7341AdcChannel {
|
||||
AS7341_ADC_CHANNEL_0,
|
||||
AS7341_ADC_CHANNEL_1,
|
||||
AS7341_ADC_CHANNEL_2,
|
||||
AS7341_ADC_CHANNEL_3,
|
||||
AS7341_ADC_CHANNEL_4,
|
||||
AS7341_ADC_CHANNEL_5,
|
||||
};
|
||||
|
||||
enum AS7341SmuxCommand {
|
||||
AS7341_SMUX_CMD_ROM_RESET, ///< ROM code initialization of SMUX
|
||||
AS7341_SMUX_CMD_READ, ///< Read SMUX configuration to RAM from SMUX chain
|
||||
AS7341_SMUX_CMD_WRITE, ///< Write SMUX configuration from RAM to SMUX chain
|
||||
};
|
||||
|
||||
enum AS7341Gain {
|
||||
AS7341_GAIN_0_5X,
|
||||
AS7341_GAIN_1X,
|
||||
AS7341_GAIN_2X,
|
||||
AS7341_GAIN_4X,
|
||||
AS7341_GAIN_8X,
|
||||
AS7341_GAIN_16X,
|
||||
AS7341_GAIN_32X,
|
||||
AS7341_GAIN_64X,
|
||||
AS7341_GAIN_128X,
|
||||
AS7341_GAIN_256X,
|
||||
AS7341_GAIN_512X,
|
||||
};
|
||||
|
||||
class AS7341Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
void set_f1_sensor(sensor::Sensor *f1_sensor) { this->f1_ = f1_sensor; }
|
||||
void set_f2_sensor(sensor::Sensor *f2_sensor) { f2_ = f2_sensor; }
|
||||
void set_f3_sensor(sensor::Sensor *f3_sensor) { f3_ = f3_sensor; }
|
||||
void set_f4_sensor(sensor::Sensor *f4_sensor) { f4_ = f4_sensor; }
|
||||
void set_f5_sensor(sensor::Sensor *f5_sensor) { f5_ = f5_sensor; }
|
||||
void set_f6_sensor(sensor::Sensor *f6_sensor) { f6_ = f6_sensor; }
|
||||
void set_f7_sensor(sensor::Sensor *f7_sensor) { f7_ = f7_sensor; }
|
||||
void set_f8_sensor(sensor::Sensor *f8_sensor) { f8_ = f8_sensor; }
|
||||
void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_ = clear_sensor; }
|
||||
void set_nir_sensor(sensor::Sensor *nir_sensor) { nir_ = nir_sensor; }
|
||||
|
||||
void set_gain(AS7341Gain gain) { gain_ = gain; }
|
||||
void set_atime(uint8_t atime) { atime_ = atime; }
|
||||
void set_astep(uint16_t astep) { astep_ = astep; }
|
||||
|
||||
AS7341Gain get_gain();
|
||||
uint8_t get_atime();
|
||||
uint16_t get_astep();
|
||||
bool setup_gain(AS7341Gain gain);
|
||||
bool setup_atime(uint8_t atime);
|
||||
bool setup_astep(uint16_t astep);
|
||||
|
||||
uint16_t read_channel(AS7341AdcChannel channel);
|
||||
bool read_channels(uint16_t *data);
|
||||
void set_smux_low_channels(bool enable);
|
||||
bool set_smux_command(AS7341SmuxCommand command);
|
||||
void configure_smux_low_channels();
|
||||
void configure_smux_high_channels();
|
||||
bool enable_smux();
|
||||
|
||||
bool wait_for_data();
|
||||
bool is_data_ready();
|
||||
bool enable_power(bool enable);
|
||||
bool enable_spectral_measurement(bool enable);
|
||||
|
||||
bool read_register_bit(uint8_t address, uint8_t bit_position);
|
||||
bool write_register_bit(uint8_t address, bool value, uint8_t bit_position);
|
||||
bool set_register_bit(uint8_t address, uint8_t bit_position);
|
||||
bool clear_register_bit(uint8_t address, uint8_t bit_position);
|
||||
uint16_t swap_bytes(uint16_t data);
|
||||
|
||||
protected:
|
||||
sensor::Sensor *f1_{nullptr};
|
||||
sensor::Sensor *f2_{nullptr};
|
||||
sensor::Sensor *f3_{nullptr};
|
||||
sensor::Sensor *f4_{nullptr};
|
||||
sensor::Sensor *f5_{nullptr};
|
||||
sensor::Sensor *f6_{nullptr};
|
||||
sensor::Sensor *f7_{nullptr};
|
||||
sensor::Sensor *f8_{nullptr};
|
||||
sensor::Sensor *clear_{nullptr};
|
||||
sensor::Sensor *nir_{nullptr};
|
||||
|
||||
uint16_t astep_;
|
||||
AS7341Gain gain_;
|
||||
uint8_t atime_;
|
||||
uint16_t channel_readings_[12];
|
||||
};
|
||||
|
||||
} // namespace as7341
|
||||
} // namespace esphome
|
112
esphome/components/as7341/sensor.py
Normal file
112
esphome/components/as7341/sensor.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_GAIN,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
ICON_BRIGHTNESS_5,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
|
||||
CODEOWNERS = ["@mrgnr"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
as7341_ns = cg.esphome_ns.namespace("as7341")
|
||||
|
||||
AS7341Component = as7341_ns.class_(
|
||||
"AS7341Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONF_ATIME = "atime"
|
||||
CONF_ASTEP = "astep"
|
||||
|
||||
CONF_F1 = "f1"
|
||||
CONF_F2 = "f2"
|
||||
CONF_F3 = "f3"
|
||||
CONF_F4 = "f4"
|
||||
CONF_F5 = "f5"
|
||||
CONF_F6 = "f6"
|
||||
CONF_F7 = "f7"
|
||||
CONF_F8 = "f8"
|
||||
CONF_CLEAR = "clear"
|
||||
CONF_NIR = "nir"
|
||||
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
AS7341_GAIN = as7341_ns.enum("AS7341Gain")
|
||||
GAIN_OPTIONS = {
|
||||
"X0.5": AS7341_GAIN.AS7341_GAIN_0_5X,
|
||||
"X1": AS7341_GAIN.AS7341_GAIN_1X,
|
||||
"X2": AS7341_GAIN.AS7341_GAIN_2X,
|
||||
"X4": AS7341_GAIN.AS7341_GAIN_4X,
|
||||
"X8": AS7341_GAIN.AS7341_GAIN_8X,
|
||||
"X16": AS7341_GAIN.AS7341_GAIN_16X,
|
||||
"X32": AS7341_GAIN.AS7341_GAIN_32X,
|
||||
"X64": AS7341_GAIN.AS7341_GAIN_64X,
|
||||
"X128": AS7341_GAIN.AS7341_GAIN_128X,
|
||||
"X256": AS7341_GAIN.AS7341_GAIN_256X,
|
||||
"X512": AS7341_GAIN.AS7341_GAIN_512X,
|
||||
}
|
||||
|
||||
|
||||
SENSOR_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_5,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AS7341Component),
|
||||
cv.Optional(CONF_F1): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_F2): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_F3): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_F4): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_F5): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_F6): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_F7): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_F8): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_CLEAR): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_NIR): SENSOR_SCHEMA,
|
||||
cv.Optional(CONF_GAIN, default="X8"): cv.enum(GAIN_OPTIONS),
|
||||
cv.Optional(CONF_ATIME, default=29): cv.int_range(min=0, max=255),
|
||||
cv.Optional(CONF_ASTEP, default=599): cv.int_range(min=0, max=65534),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x39))
|
||||
)
|
||||
|
||||
SENSORS = {
|
||||
CONF_F1: "set_f1_sensor",
|
||||
CONF_F2: "set_f2_sensor",
|
||||
CONF_F3: "set_f3_sensor",
|
||||
CONF_F4: "set_f4_sensor",
|
||||
CONF_F5: "set_f5_sensor",
|
||||
CONF_F6: "set_f6_sensor",
|
||||
CONF_F7: "set_f7_sensor",
|
||||
CONF_F8: "set_f8_sensor",
|
||||
CONF_CLEAR: "set_clear_sensor",
|
||||
CONF_NIR: "set_nir_sensor",
|
||||
}
|
||||
|
||||
|
||||
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_gain(config[CONF_GAIN]))
|
||||
cg.add(var.set_atime(config[CONF_ATIME]))
|
||||
cg.add(var.set_astep(config[CONF_ASTEP]))
|
||||
|
||||
for conf_id, set_sensor_func in SENSORS.items():
|
||||
if conf_id in config:
|
||||
sens = await sensor.new_sensor(config[conf_id])
|
||||
cg.add(getattr(var, set_sensor_func)(sens))
|
@@ -80,7 +80,7 @@ async def to_code(config):
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
for (config_key, setter) in [
|
||||
for config_key, setter in [
|
||||
(CONF_TEMPERATURE, var.set_temperature),
|
||||
(CONF_HUMIDITY, var.set_humidity),
|
||||
(CONF_BATTERY_VOLTAGE, var.set_battery_voltage),
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "bedjet_hub.h"
|
||||
#include "bedjet_child.h"
|
||||
#include "bedjet_const.h"
|
||||
@@ -541,3 +543,5 @@ void BedJetHub::register_child(BedJetClient *obj) {
|
||||
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
@@ -14,8 +15,6 @@
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
@@ -117,7 +116,7 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
void setup() override { this->codec_ = make_unique<BedjetCodec>(); }
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
float get_setup_priority() const override { return setup_priority::BLUETOOTH; }
|
||||
|
||||
/** @return The BedJet's configured name, or the MAC address if not discovered yet. */
|
||||
std::string get_name() {
|
||||
|
@@ -27,13 +27,13 @@ from esphome.const import (
|
||||
CONF_TIMING,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MQTT_ID,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_BATTERY_CHARGING,
|
||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||
DEVICE_CLASS_COLD,
|
||||
DEVICE_CLASS_CONNECTIVITY,
|
||||
DEVICE_CLASS_DOOR,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_GARAGE_DOOR,
|
||||
DEVICE_CLASS_GAS,
|
||||
DEVICE_CLASS_HEAT,
|
||||
@@ -62,13 +62,13 @@ from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_BATTERY_CHARGING,
|
||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||
DEVICE_CLASS_COLD,
|
||||
DEVICE_CLASS_CONNECTIVITY,
|
||||
DEVICE_CLASS_DOOR,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_GARAGE_DOOR,
|
||||
DEVICE_CLASS_GAS,
|
||||
DEVICE_CLASS_HEAT,
|
||||
@@ -393,28 +393,21 @@ def binary_sensor_schema(
|
||||
entity_category: str = _UNDEF,
|
||||
device_class: str = _UNDEF,
|
||||
) -> cv.Schema:
|
||||
schema = BINARY_SENSOR_SCHEMA
|
||||
schema = {}
|
||||
|
||||
if class_ is not _UNDEF:
|
||||
schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)})
|
||||
if icon is not _UNDEF:
|
||||
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
|
||||
if entity_category is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=entity_category
|
||||
): cv.entity_category
|
||||
}
|
||||
)
|
||||
if device_class is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, default=device_class
|
||||
): validate_device_class
|
||||
}
|
||||
)
|
||||
return schema
|
||||
# Not cv.optional
|
||||
schema[cv.GenerateID()] = cv.declare_id(class_)
|
||||
|
||||
for key, default, validator in [
|
||||
(CONF_ICON, icon, cv.icon),
|
||||
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
|
||||
(CONF_DEVICE_CLASS, device_class, validate_device_class),
|
||||
]:
|
||||
if default is not _UNDEF:
|
||||
schema[cv.Optional(key, default=default)] = validator
|
||||
|
||||
return BINARY_SENSOR_SCHEMA.extend(schema)
|
||||
|
||||
|
||||
async def setup_binary_sensor_core_(var, config):
|
||||
|
@@ -41,16 +41,13 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
this->state_callback_.call(state);
|
||||
}
|
||||
}
|
||||
std::string BinarySensor::device_class() { return ""; }
|
||||
|
||||
BinarySensor::BinarySensor() : state(false) {}
|
||||
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
std::string BinarySensor::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
return this->device_class();
|
||||
#pragma GCC diagnostic pop
|
||||
return "";
|
||||
}
|
||||
void BinarySensor::add_filter(Filter *filter) {
|
||||
filter->parent_ = this;
|
||||
|
@@ -19,6 +19,15 @@ namespace binary_sensor {
|
||||
} \
|
||||
}
|
||||
|
||||
#define SUB_BINARY_SENSOR(name) \
|
||||
protected: \
|
||||
binary_sensor::BinarySensor *name##_binary_sensor_{nullptr}; \
|
||||
\
|
||||
public: \
|
||||
void set_##name##_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { \
|
||||
this->name##_binary_sensor_ = binary_sensor; \
|
||||
}
|
||||
|
||||
/** Base class for all binary_sensor-type classes.
|
||||
*
|
||||
* This class includes a callback that components such as MQTT can subscribe to for state changes.
|
||||
@@ -71,14 +80,6 @@ class BinarySensor : public EntityBase {
|
||||
|
||||
virtual bool is_status_binary_sensor() const;
|
||||
|
||||
// ========== OVERRIDE METHODS ==========
|
||||
// (You'll only need this when creating your own custom binary sensor)
|
||||
/** 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:
|
||||
CallbackManager<void(bool)> state_callback_{};
|
||||
optional<std::string> device_class_{}; ///< Stores the override of the device class
|
||||
|
@@ -30,7 +30,7 @@ void BinarySensorMap::process_group_() {
|
||||
if (bs.binary_sensor->state) {
|
||||
num_active_sensors++;
|
||||
total_current_value += bs.sensor_value;
|
||||
mask |= 1 << i;
|
||||
mask |= 1ULL << i;
|
||||
}
|
||||
}
|
||||
// check if the sensor map was touched
|
||||
@@ -38,12 +38,11 @@ void BinarySensorMap::process_group_() {
|
||||
// did the bit_mask change or is it a new sensor touch
|
||||
if (this->last_mask_ != mask) {
|
||||
float publish_value = total_current_value / num_active_sensors;
|
||||
ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value);
|
||||
this->publish_state(publish_value);
|
||||
}
|
||||
} else if (this->last_mask_ != 0ULL) {
|
||||
// is this a new sensor release
|
||||
ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
|
||||
ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
|
||||
this->publish_state(NAN);
|
||||
}
|
||||
this->last_mask_ = mask;
|
||||
@@ -52,28 +51,22 @@ void BinarySensorMap::process_group_() {
|
||||
void BinarySensorMap::process_sum_() {
|
||||
float total_current_value = 0.0;
|
||||
uint64_t mask = 0x00;
|
||||
// check all binary_sensors for its state. when active add its value to total_current_value.
|
||||
// create a bitmask for the binary_sensor status on all channels
|
||||
// - check all binary_sensor states
|
||||
// - if active, add its value to total_current_value
|
||||
// - creates a bitmask for the binary_sensor status on all channels
|
||||
for (size_t i = 0; i < this->channels_.size(); i++) {
|
||||
auto bs = this->channels_[i];
|
||||
if (bs.binary_sensor->state) {
|
||||
total_current_value += bs.sensor_value;
|
||||
mask |= 1 << i;
|
||||
mask |= 1ULL << i;
|
||||
}
|
||||
}
|
||||
// check if the sensor map was touched
|
||||
if (mask != 0ULL) {
|
||||
// did the bit_mask change or is it a new sensor touch
|
||||
if (this->last_mask_ != mask) {
|
||||
float publish_value = total_current_value;
|
||||
ESP_LOGD(TAG, "'%s' - Publishing %.2f", this->name_.c_str(), publish_value);
|
||||
this->publish_state(publish_value);
|
||||
}
|
||||
} else if (this->last_mask_ != 0ULL) {
|
||||
// is this a new sensor release
|
||||
ESP_LOGD(TAG, "'%s' - No binary sensor active, publishing 0", this->name_.c_str());
|
||||
this->publish_state(0.0);
|
||||
|
||||
// update state only if the binary sensor states have changed or if no state has ever been sent on boot
|
||||
if ((this->last_mask_ != mask) || (!this->has_state())) {
|
||||
this->publish_state(total_current_value);
|
||||
}
|
||||
|
||||
this->last_mask_ = mask;
|
||||
}
|
||||
|
||||
|
@@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_CHANNELS): cv.All(
|
||||
cv.ensure_list(entry), cv.Length(min=1)
|
||||
cv.ensure_list(entry), cv.Length(min=1, max=64)
|
||||
),
|
||||
}
|
||||
),
|
||||
@@ -50,7 +50,7 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_CHANNELS): cv.All(
|
||||
cv.ensure_list(entry), cv.Length(min=1)
|
||||
cv.ensure_list(entry), cv.Length(min=1, max=64)
|
||||
),
|
||||
}
|
||||
),
|
||||
|
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_VOLT,
|
||||
@@ -66,16 +67,19 @@ CONFIG_SCHEMA = (
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_ENERGY_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_ENERGY_TOTAL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -1,3 +1,5 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "automation.h"
|
||||
|
||||
#include <esp_bt_defs.h>
|
||||
@@ -73,3 +75,5 @@ void BLEWriterClientNode::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
|
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
|
@@ -158,6 +158,25 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
return true;
|
||||
}
|
||||
|
||||
void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
BLEClientBase::gap_event_handler(event, param);
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
|
||||
break;
|
||||
if (param->ble_security.auth_cmpl.success) {
|
||||
api::global_api_server->send_bluetooth_device_pairing(this->address_, true);
|
||||
} else {
|
||||
api::global_api_server->send_bluetooth_device_pairing(this->address_, false,
|
||||
param->ble_security.auth_cmpl.fail_reason);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
||||
if (!this->connected()) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
|
||||
|
@@ -13,6 +13,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||
public:
|
||||
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
|
||||
esp_err_t read_characteristic(uint16_t handle);
|
||||
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
|
||||
|
@@ -257,12 +257,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str());
|
||||
}
|
||||
if (msg.has_address_type) {
|
||||
connection->remote_bda_[0] = (msg.address >> 40) & 0xFF;
|
||||
connection->remote_bda_[1] = (msg.address >> 32) & 0xFF;
|
||||
connection->remote_bda_[2] = (msg.address >> 24) & 0xFF;
|
||||
connection->remote_bda_[3] = (msg.address >> 16) & 0xFF;
|
||||
connection->remote_bda_[4] = (msg.address >> 8) & 0xFF;
|
||||
connection->remote_bda_[5] = (msg.address >> 0) & 0xFF;
|
||||
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
||||
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
||||
connection->set_state(espbt::ClientState::DISCOVERED);
|
||||
} else {
|
||||
@@ -290,9 +285,34 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||
}
|
||||
break;
|
||||
}
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: {
|
||||
auto *connection = this->get_connection_(msg.address, false);
|
||||
if (connection != nullptr) {
|
||||
if (!connection->is_paired()) {
|
||||
auto err = connection->pair();
|
||||
if (err != ESP_OK) {
|
||||
api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err);
|
||||
}
|
||||
} else {
|
||||
api::global_api_server->send_bluetooth_device_pairing(msg.address, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: {
|
||||
esp_bd_addr_t address;
|
||||
uint64_to_bd_addr(msg.address, address);
|
||||
esp_err_t ret = esp_ble_remove_bond_device(address);
|
||||
api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret);
|
||||
break;
|
||||
}
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: {
|
||||
esp_bd_addr_t address;
|
||||
uint64_to_bd_addr(msg.address, address);
|
||||
esp_err_t ret = esp_ble_gattc_cache_clean(address);
|
||||
api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -44,6 +44,15 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
int get_bluetooth_connections_free();
|
||||
int get_bluetooth_connections_limit() { return this->connections_.size(); }
|
||||
|
||||
static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
|
||||
bd_addr[0] = (address >> 40) & 0xff;
|
||||
bd_addr[1] = (address >> 32) & 0xff;
|
||||
bd_addr[2] = (address >> 24) & 0xff;
|
||||
bd_addr[3] = (address >> 16) & 0xff;
|
||||
bd_addr[4] = (address >> 8) & 0xff;
|
||||
bd_addr[5] = (address >> 0) & 0xff;
|
||||
}
|
||||
|
||||
void set_active(bool active) { this->active_ = active; }
|
||||
bool has_active() { return this->active_; }
|
||||
|
||||
@@ -59,6 +68,14 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
|
||||
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
// Version 1: Initial version without active connections
|
||||
// Version 2: Support for active connections
|
||||
// Version 3: New connection API
|
||||
// Version 4: Pairing support
|
||||
// Version 5: Cache clear support
|
||||
static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5;
|
||||
static const uint32_t PASSIVE_ONLY_VERSION = 1;
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
} // namespace esphome
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#include "bme680.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme680 {
|
||||
@@ -117,18 +117,24 @@ void BME680Component::setup() {
|
||||
this->calibration_.gh2 = cal2[12] << 8 | cal2[13];
|
||||
this->calibration_.gh3 = cal2[15];
|
||||
|
||||
if (!this->read_byte(0x02, &this->calibration_.res_heat_range)) {
|
||||
uint8_t temp_var = 0;
|
||||
if (!this->read_byte(0x02, &temp_var)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->read_byte(0x00, &this->calibration_.res_heat_val)) {
|
||||
this->calibration_.res_heat_range = ((temp_var & 0x30) / 16);
|
||||
|
||||
if (!this->read_byte(0x00, &temp_var)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->read_byte(0x04, &this->calibration_.range_sw_err)) {
|
||||
this->calibration_.res_heat_val = (int8_t) temp_var;
|
||||
|
||||
if (!this->read_byte(0x04, &temp_var)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->calibration_.range_sw_err = ((int8_t) temp_var & (int8_t) 0xf0) / 16;
|
||||
|
||||
this->calibration_.ambient_temperature = 25; // prime ambient temperature
|
||||
|
||||
@@ -181,7 +187,7 @@ void BME680Component::setup() {
|
||||
return;
|
||||
}
|
||||
gas0_control &= ~0b00001000;
|
||||
gas0_control |= heat_off ? 0b100 : 0b000;
|
||||
gas0_control |= heat_off << 3;
|
||||
if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
@@ -249,12 +255,12 @@ uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) {
|
||||
if (temperature > 400)
|
||||
temperature = 400;
|
||||
|
||||
const uint8_t ambient_temperature = this->calibration_.ambient_temperature;
|
||||
const int8_t ambient_temperature = this->calibration_.ambient_temperature;
|
||||
const int8_t gh1 = this->calibration_.gh1;
|
||||
const int16_t gh2 = this->calibration_.gh2;
|
||||
const int8_t gh3 = this->calibration_.gh3;
|
||||
const uint8_t res_heat_range = this->calibration_.res_heat_range;
|
||||
const uint8_t res_heat_val = this->calibration_.res_heat_val;
|
||||
const int8_t res_heat_val = this->calibration_.res_heat_val;
|
||||
|
||||
uint8_t heatr_res;
|
||||
int32_t var1;
|
||||
@@ -269,8 +275,8 @@ uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) {
|
||||
var3 = var1 + (var2 / 2);
|
||||
var4 = (var3 / (res_heat_range + 4));
|
||||
var5 = (131 * res_heat_val) + 65536;
|
||||
heatr_res_x100 = (int32_t)(((var4 / var5) - 250) * 34);
|
||||
heatr_res = (uint8_t)((heatr_res_x100 + 50) / 100);
|
||||
heatr_res_x100 = (int32_t) (((var4 / var5) - 250) * 34);
|
||||
heatr_res = (uint8_t) ((heatr_res_x100 + 50) / 100);
|
||||
|
||||
return heatr_res;
|
||||
}
|
||||
@@ -293,35 +299,57 @@ uint8_t BME680Component::calc_heater_duration_(uint16_t duration) {
|
||||
void BME680Component::read_data_() {
|
||||
uint8_t data[15];
|
||||
if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) {
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(NAN);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(NAN);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(NAN);
|
||||
if (this->gas_resistance_sensor_ != nullptr)
|
||||
this->gas_resistance_sensor_->publish_state(NAN);
|
||||
ESP_LOGW(TAG, "Communication with BME680 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
||||
uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4);
|
||||
uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4);
|
||||
uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]);
|
||||
uint16_t raw_gas = (uint16_t(data[13]) << 2) | (uint16_t(14) >> 6);
|
||||
uint16_t raw_gas = (uint16_t) ((uint32_t) data[13] * 4 | (((uint32_t) data[14]) / 64));
|
||||
uint8_t gas_range = data[14] & 0x0F;
|
||||
|
||||
float temperature = this->calc_temperature_(raw_temperature);
|
||||
float pressure = this->calc_pressure_(raw_pressure);
|
||||
float humidity = this->calc_humidity_(raw_humidity);
|
||||
float gas_resistance = NAN;
|
||||
if (data[14] & 0x20) {
|
||||
gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range);
|
||||
}
|
||||
float gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range);
|
||||
|
||||
bool gas_valid = (data[14] >> 5) & 1;
|
||||
bool heat_stable = (data[14] >> 4) & 1;
|
||||
if (this->heater_temperature_ == 0 || this->heater_duration_ == 0)
|
||||
heat_stable = true; // Allow reporting gas resistance when heater is disabled
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure,
|
||||
humidity, gas_resistance);
|
||||
if (!gas_valid)
|
||||
ESP_LOGW(TAG, "Gas measurement unsuccessful, reading invalid!");
|
||||
if (!heat_stable)
|
||||
ESP_LOGW(TAG, "Heater unstable, reading invalid! (Normal for a few readings after a power cycle)");
|
||||
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
if (this->gas_resistance_sensor_ != nullptr)
|
||||
this->gas_resistance_sensor_->publish_state(gas_resistance);
|
||||
this->status_clear_warning();
|
||||
if (this->gas_resistance_sensor_ != nullptr) {
|
||||
if (gas_valid && heat_stable) {
|
||||
this->gas_resistance_sensor_->publish_state(gas_resistance);
|
||||
} else {
|
||||
this->status_set_warning();
|
||||
this->gas_resistance_sensor_->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float BME680Component::calc_temperature_(uint32_t raw_temperature) {
|
||||
@@ -428,20 +456,22 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) {
|
||||
|
||||
return calc_hum;
|
||||
}
|
||||
uint32_t BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) {
|
||||
float BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) {
|
||||
float calc_gas_res;
|
||||
float var1 = 0;
|
||||
float var2 = 0;
|
||||
float var3 = 0;
|
||||
float raw_gas_f = raw_gas;
|
||||
float range_f = 1U << range;
|
||||
const float range_sw_err = this->calibration_.range_sw_err;
|
||||
|
||||
var1 = 1340.0f + (5.0f * range_sw_err);
|
||||
var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f);
|
||||
var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f);
|
||||
|
||||
calc_gas_res = 1.0f / (var3 * 0.000000125f * float(1 << range) * (((float(raw_gas) - 512.0f) / var2) + 1.0f));
|
||||
calc_gas_res = 1.0f / (var3 * 0.000000125f * range_f * (((raw_gas_f - 512.0f) / var2) + 1.0f));
|
||||
|
||||
return static_cast<uint32_t>(calc_gas_res);
|
||||
return calc_gas_res;
|
||||
}
|
||||
uint32_t BME680Component::calc_meas_duration_() {
|
||||
uint32_t tph_dur; // Calculate in us
|
||||
|
@@ -59,11 +59,11 @@ struct BME680CalibrationData {
|
||||
int8_t gh3;
|
||||
|
||||
uint8_t res_heat_range;
|
||||
uint8_t res_heat_val;
|
||||
uint8_t range_sw_err;
|
||||
int8_t res_heat_val;
|
||||
int8_t range_sw_err;
|
||||
|
||||
float tfine;
|
||||
uint8_t ambient_temperature;
|
||||
int8_t ambient_temperature;
|
||||
};
|
||||
|
||||
class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
||||
@@ -117,7 +117,7 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
||||
/// Calculate the relative humidity in % using the provided raw ADC value.
|
||||
float calc_humidity_(uint16_t raw_humidity);
|
||||
/// Calculate the gas resistance in Ω using the provided raw ADC value.
|
||||
uint32_t calc_gas_resistance_(uint16_t raw_gas, uint8_t range);
|
||||
float calc_gas_resistance_(uint16_t raw_gas, uint8_t range);
|
||||
/// Calculate how long the sensor will take until we can retrieve data.
|
||||
uint32_t calc_meas_duration_();
|
||||
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.components import i2c, esp32
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@trvrnrth"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
|
||||
CONF_TEMPERATURE_OFFSET = "temperature_offset"
|
||||
@@ -31,22 +32,31 @@ BME680BSECComponent = bme680_bsec_ns.class_(
|
||||
"BME680BSECComponent", cg.Component, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
|
||||
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
|
||||
IAQ_MODE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
|
||||
SAMPLE_RATE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
||||
): cv.positive_time_period_minutes,
|
||||
},
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
|
||||
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
|
||||
IAQ_MODE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
|
||||
SAMPLE_RATE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
||||
): cv.positive_time_period_minutes,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x76)),
|
||||
cv.only_with_arduino,
|
||||
).extend(i2c.i2c_device_schema(0x76))
|
||||
cv.Any(
|
||||
cv.only_on_esp8266,
|
||||
cv.All(
|
||||
cv.only_on_esp32,
|
||||
esp32.only_on_variant(supported=[esp32.const.VARIANT_ESP32]),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
@@ -54,6 +64,7 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_device_id(str(config[CONF_ID])))
|
||||
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
|
||||
cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE]))
|
||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
|
@@ -10,19 +10,24 @@ static const char *const TAG = "bme680_bsec.sensor";
|
||||
|
||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||
|
||||
BME680BSECComponent *BME680BSECComponent::instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
std::vector<BME680BSECComponent *>
|
||||
BME680BSECComponent::instances; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0};
|
||||
|
||||
void BME680BSECComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC...");
|
||||
BME680BSECComponent::instance = this;
|
||||
ESP_LOGCONFIG(TAG, "Setting up BME680(%s) via BSEC...", this->device_id_.c_str());
|
||||
|
||||
this->bsec_status_ = bsec_init();
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint8_t new_idx = BME680BSECComponent::instances.size();
|
||||
BME680BSECComponent::instances.push_back(this);
|
||||
|
||||
this->bme680_.dev_id = this->address_;
|
||||
this->bsec_state_data_valid_ = false;
|
||||
|
||||
// Initialize the bme680_ structure (passed-in to the bme680_* functions) and the BME680 device
|
||||
this->bme680_.dev_id =
|
||||
new_idx; // This is a "Place holder to store the id of the device structure" (see bme680_defs.h).
|
||||
// This will be passed-in as first parameter to the next "read" and "write" function pointers.
|
||||
// We currently use the index of the object in the BME680BSECComponent::instances vector to identify
|
||||
// the different devices in the system.
|
||||
this->bme680_.intf = BME680_I2C_INTF;
|
||||
this->bme680_.read = BME680BSECComponent::read_bytes_wrapper;
|
||||
this->bme680_.write = BME680BSECComponent::write_bytes_wrapper;
|
||||
@@ -35,29 +40,30 @@ void BME680BSECComponent::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
|
||||
const uint8_t bsec_config[] = {
|
||||
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->set_config_(bsec_config);
|
||||
} else {
|
||||
const uint8_t bsec_config[] = {
|
||||
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->set_config_(bsec_config);
|
||||
}
|
||||
this->update_subscription_();
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
// Initialize the BSEC library
|
||||
if (this->reinit_bsec_lib_() != 0) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the BSEC library state from storage
|
||||
this->load_state_();
|
||||
}
|
||||
|
||||
void BME680BSECComponent::set_config_(const uint8_t *config) {
|
||||
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
|
||||
this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer));
|
||||
void BME680BSECComponent::set_config_() {
|
||||
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
|
||||
const uint8_t config[] = {
|
||||
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->bsec_status_ =
|
||||
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||
} else {
|
||||
const uint8_t config[] = {
|
||||
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->bsec_status_ =
|
||||
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||
}
|
||||
}
|
||||
|
||||
float BME680BSECComponent::calc_sensor_sample_rate_(SampleRate sample_rate) {
|
||||
@@ -118,10 +124,12 @@ void BME680BSECComponent::update_subscription_() {
|
||||
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
|
||||
this->bsec_status_ =
|
||||
bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings);
|
||||
ESP_LOGV(TAG, "%s: updating subscription for %d virtual sensors (out=%d sensors)", this->device_id_.c_str(),
|
||||
num_virtual_sensors, num_sensor_settings);
|
||||
}
|
||||
|
||||
void BME680BSECComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BME680 via BSEC:");
|
||||
ESP_LOGCONFIG(TAG, "%s via BSEC:", this->device_id_.c_str());
|
||||
|
||||
bsec_version_t version;
|
||||
bsec_get_version(&version);
|
||||
@@ -185,23 +193,31 @@ void BME680BSECComponent::run_() {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Performing sensor run");
|
||||
ESP_LOGV(TAG, "%s: Performing sensor run", this->device_id_.c_str());
|
||||
|
||||
bsec_bme_settings_t bme680_settings;
|
||||
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings);
|
||||
// Restore BSEC library state
|
||||
// The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32.
|
||||
// It can be skipped entirely when there is only one device (since the BSEC library won't be shared)
|
||||
if (BME680BSECComponent::instances.size() > 1) {
|
||||
int res = this->reinit_bsec_lib_();
|
||||
if (res != 0)
|
||||
return;
|
||||
}
|
||||
|
||||
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_);
|
||||
if (this->bsec_status_ < BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
this->next_call_ns_ = bme680_settings.next_call;
|
||||
this->next_call_ns_ = this->bme680_settings_.next_call;
|
||||
|
||||
if (bme680_settings.trigger_measurement) {
|
||||
this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling;
|
||||
this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling;
|
||||
this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling;
|
||||
this->bme680_.gas_sett.run_gas = bme680_settings.run_gas;
|
||||
this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature;
|
||||
this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration;
|
||||
if (this->bme680_settings_.trigger_measurement) {
|
||||
this->bme680_.tph_sett.os_temp = this->bme680_settings_.temperature_oversampling;
|
||||
this->bme680_.tph_sett.os_pres = this->bme680_settings_.pressure_oversampling;
|
||||
this->bme680_.tph_sett.os_hum = this->bme680_settings_.humidity_oversampling;
|
||||
this->bme680_.gas_sett.run_gas = this->bme680_settings_.run_gas;
|
||||
this->bme680_.gas_sett.heatr_temp = this->bme680_settings_.heater_temperature;
|
||||
this->bme680_.gas_sett.heatr_dur = this->bme680_settings_.heating_duration;
|
||||
this->bme680_.power_mode = BME680_FORCED_MODE;
|
||||
uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL;
|
||||
this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_);
|
||||
@@ -218,19 +234,26 @@ void BME680BSECComponent::run_() {
|
||||
|
||||
uint16_t meas_dur = 0;
|
||||
bme680_get_profile_dur(&meas_dur, &this->bme680_);
|
||||
|
||||
// Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later
|
||||
// TODO: it would be interesting to see if this is really needed here, or if it's needed only after each
|
||||
// bsec_do_steps() call
|
||||
if (BME680BSECComponent::instances.size() > 1)
|
||||
this->snapshot_state_();
|
||||
|
||||
ESP_LOGV(TAG, "Queueing read in %ums", meas_dur);
|
||||
this->set_timeout("read", meas_dur,
|
||||
[this, curr_time_ns, bme680_settings]() { this->read_(curr_time_ns, bme680_settings); });
|
||||
this->set_timeout("read", meas_dur, [this]() { this->read_(); });
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Measurement not required");
|
||||
this->read_(curr_time_ns, bme680_settings);
|
||||
this->read_();
|
||||
}
|
||||
}
|
||||
|
||||
void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings) {
|
||||
ESP_LOGV(TAG, "Reading data");
|
||||
void BME680BSECComponent::read_() {
|
||||
ESP_LOGV(TAG, "%s: Reading data", this->device_id_.c_str());
|
||||
int64_t curr_time_ns = this->get_time_ns_();
|
||||
|
||||
if (bme680_settings.trigger_measurement) {
|
||||
if (this->bme680_settings_.trigger_measurement) {
|
||||
while (this->bme680_.power_mode != BME680_SLEEP_MODE) {
|
||||
this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_);
|
||||
if (this->bme680_status_ != BME680_OK) {
|
||||
@@ -239,7 +262,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||
}
|
||||
}
|
||||
|
||||
if (!bme680_settings.process_data) {
|
||||
if (!this->bme680_settings_.process_data) {
|
||||
ESP_LOGV(TAG, "Data processing not required");
|
||||
return;
|
||||
}
|
||||
@@ -259,35 +282,35 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
|
||||
uint8_t num_inputs = 0;
|
||||
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) {
|
||||
if (this->bme680_settings_.process_data & BSEC_PROCESS_TEMPERATURE) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
|
||||
inputs[num_inputs].signal = data.temperature / 100.0f;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
|
||||
// Temperature offset from the real temperature due to external heat sources
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
|
||||
inputs[num_inputs].signal = this->temperature_offset_;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) {
|
||||
if (this->bme680_settings_.process_data & BSEC_PROCESS_HUMIDITY) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
|
||||
inputs[num_inputs].signal = data.humidity / 1000.0f;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) {
|
||||
if (this->bme680_settings_.process_data & BSEC_PROCESS_PRESSURE) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
|
||||
inputs[num_inputs].signal = data.pressure;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
}
|
||||
if (bme680_settings.process_data & BSEC_PROCESS_GAS) {
|
||||
if (this->bme680_settings_.process_data & BSEC_PROCESS_GAS) {
|
||||
if (data.status & BME680_GASM_VALID_MSK) {
|
||||
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
|
||||
inputs[num_inputs].signal = data.gas_resistance;
|
||||
inputs[num_inputs].time_stamp = trigger_time_ns;
|
||||
inputs[num_inputs].time_stamp = curr_time_ns;
|
||||
num_inputs++;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "BME680 did not report gas data");
|
||||
@@ -298,6 +321,22 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore BSEC library state
|
||||
// The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32.
|
||||
// It can be skipped entirely when there is only one device (since the BSEC library won't be shared)
|
||||
if (BME680BSECComponent::instances.size() > 1) {
|
||||
int res = this->reinit_bsec_lib_();
|
||||
if (res != 0)
|
||||
return;
|
||||
// Now that the BSEC library has been re-initialized, bsec_sensor_control *NEEDS* to be called in order to support
|
||||
// multiple devices with a different set of enabled sensors (even if the bme680_settings_ data is not used)
|
||||
this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_);
|
||||
if (this->bsec_status_ < BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
|
||||
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
|
||||
this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs);
|
||||
@@ -305,6 +344,13 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||
ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "%s: after bsec_do_steps: num_inputs=%d num_outputs=%d", this->device_id_.c_str(), num_inputs,
|
||||
num_outputs);
|
||||
|
||||
// Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later
|
||||
if (BME680BSECComponent::instances.size() > 1)
|
||||
this->snapshot_state_();
|
||||
|
||||
if (num_outputs < 1) {
|
||||
ESP_LOGD(TAG, "No signal outputs provided by BSEC");
|
||||
return;
|
||||
@@ -314,7 +360,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
|
||||
}
|
||||
|
||||
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
|
||||
ESP_LOGV(TAG, "Queuing sensor state publish actions");
|
||||
ESP_LOGV(TAG, "%s: Queuing sensor state publish actions", this->device_id_.c_str());
|
||||
for (uint8_t i = 0; i < num_outputs; i++) {
|
||||
float signal = outputs[i].signal;
|
||||
switch (outputs[i].sensor_id) {
|
||||
@@ -376,12 +422,20 @@ void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const
|
||||
sensor->publish_state(value);
|
||||
}
|
||||
|
||||
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||
return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1;
|
||||
// Communication function - read
|
||||
// First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is
|
||||
int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||
BME680BSECComponent *inst = instances[devid];
|
||||
// Use the I2CDevice::read_bytes method to perform the actual I2C register read
|
||||
return inst->read_bytes(a_register, data, len) ? 0 : -1;
|
||||
}
|
||||
|
||||
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||
return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1;
|
||||
// Communication function - write
|
||||
// First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is
|
||||
int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) {
|
||||
BME680BSECComponent *inst = instances[devid];
|
||||
// Use the I2CDevice::write_bytes method to perform the actual I2C register write
|
||||
return inst->write_bytes(a_register, data, len) ? 0 : -1;
|
||||
}
|
||||
|
||||
void BME680BSECComponent::delay_ms(uint32_t period) {
|
||||
@@ -389,41 +443,97 @@ void BME680BSECComponent::delay_ms(uint32_t period) {
|
||||
delay(period);
|
||||
}
|
||||
|
||||
// Fetch the BSEC library state and save it in the bsec_state_data_ member (volatile memory)
|
||||
// Used to share the library when using more than one sensor
|
||||
void BME680BSECComponent::snapshot_state_() {
|
||||
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
|
||||
this->bsec_status_ = bsec_get_state(0, this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_,
|
||||
sizeof(this->work_buffer_), &num_serialized_state);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "%s: Failed to fetch BSEC library state for snapshot (BSEC Error Code %d)", this->device_id_.c_str(),
|
||||
this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
this->bsec_state_data_valid_ = true;
|
||||
}
|
||||
|
||||
// Restores the BSEC library state from a snapshot in memory
|
||||
// Used to share the library when using more than one sensor
|
||||
void BME680BSECComponent::restore_state_() {
|
||||
if (!this->bsec_state_data_valid_) {
|
||||
ESP_LOGV(TAG, "%s: BSEC state data NOT valid, aborting restore_state_()", this->device_id_.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
this->bsec_status_ =
|
||||
bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to restore BSEC library state (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int BME680BSECComponent::reinit_bsec_lib_() {
|
||||
this->bsec_status_ = bsec_init();
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
return -1;
|
||||
}
|
||||
|
||||
this->set_config_();
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
return -2;
|
||||
}
|
||||
|
||||
this->restore_state_();
|
||||
|
||||
this->update_subscription_();
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
this->mark_failed();
|
||||
return -3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BME680BSECComponent::load_state_() {
|
||||
uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_));
|
||||
uint32_t hash = fnv1_hash("bme680_bsec_state_" + this->device_id_);
|
||||
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
|
||||
|
||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
if (this->bsec_state_.load(&state)) {
|
||||
ESP_LOGV(TAG, "Loading state");
|
||||
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
|
||||
this->bsec_status_ = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_);
|
||||
}
|
||||
ESP_LOGI(TAG, "Loaded state");
|
||||
if (!this->bsec_state_.load(&this->bsec_state_data_)) {
|
||||
// No saved BSEC library state available
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "%s: Loading BSEC library state", this->device_id_.c_str());
|
||||
this->bsec_status_ =
|
||||
bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "%s: Failed to load BSEC library state (BSEC Error Code %d)", this->device_id_.c_str(),
|
||||
this->bsec_status_);
|
||||
return;
|
||||
}
|
||||
// All OK: set the BSEC state data as valid
|
||||
this->bsec_state_data_valid_ = true;
|
||||
ESP_LOGI(TAG, "%s: Loaded BSEC library state", this->device_id_.c_str());
|
||||
}
|
||||
|
||||
void BME680BSECComponent::save_state_(uint8_t accuracy) {
|
||||
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Saving state");
|
||||
|
||||
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
|
||||
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
|
||||
|
||||
this->bsec_status_ =
|
||||
bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
|
||||
if (this->bsec_status_ != BSEC_OK) {
|
||||
ESP_LOGW(TAG, "Failed fetch state for save (BSEC Error Code %d)", this->bsec_status_);
|
||||
return;
|
||||
if (BME680BSECComponent::instances.size() <= 1) {
|
||||
// When a single device is in use, no snapshot is taken regularly so one is taken now
|
||||
// On multiple devices, a snapshot is taken at every loop, so there is no need to take one here
|
||||
this->snapshot_state_();
|
||||
}
|
||||
if (!this->bsec_state_data_valid_)
|
||||
return;
|
||||
|
||||
if (!this->bsec_state_.save(&state)) {
|
||||
ESP_LOGV(TAG, "%s: Saving state", this->device_id_.c_str());
|
||||
|
||||
if (!this->bsec_state_.save(&this->bsec_state_data_)) {
|
||||
ESP_LOGW(TAG, "Failed to save state");
|
||||
return;
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ enum SampleRate {
|
||||
|
||||
class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_device_id(const std::string &devid) { this->device_id_.assign(devid); }
|
||||
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
|
||||
void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; }
|
||||
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
|
||||
@@ -50,9 +51,9 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
|
||||
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
|
||||
|
||||
static BME680BSECComponent *instance;
|
||||
static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||
static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||
static std::vector<BME680BSECComponent *> instances;
|
||||
static int8_t read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||
static int8_t write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len);
|
||||
static void delay_ms(uint32_t period);
|
||||
|
||||
void setup() override;
|
||||
@@ -61,23 +62,33 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
void set_config_(const uint8_t *config);
|
||||
void set_config_();
|
||||
float calc_sensor_sample_rate_(SampleRate sample_rate);
|
||||
void update_subscription_();
|
||||
|
||||
void run_();
|
||||
void read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings);
|
||||
void read_();
|
||||
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
|
||||
int64_t get_time_ns_();
|
||||
|
||||
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
|
||||
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
|
||||
|
||||
void load_state_();
|
||||
void save_state_(uint8_t accuracy);
|
||||
void snapshot_state_(); // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
|
||||
// memory)
|
||||
void restore_state_(); // Push the state contained in the bsec_state_data_ member (volatile memory) to the BSEC
|
||||
// library
|
||||
int reinit_bsec_lib_(); // Prepare the BSEC library to be used again after this object returns active
|
||||
// (as the library may have been used by other objects)
|
||||
void load_state_(); // Initialize the ESP preferences object; retrieve the BSEC library state from the ESP
|
||||
// preferences (storage); then save it in the bsec_state_data_ member (volatile memory) and
|
||||
// push it to the BSEC library
|
||||
void save_state_(
|
||||
uint8_t accuracy); // Save the bsec_state_data_ member (volatile memory) to the ESP preferences (storage)
|
||||
|
||||
void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
|
||||
|
||||
static uint8_t work_buffer_[BSEC_MAX_WORKBUFFER_SIZE];
|
||||
struct bme680_dev bme680_;
|
||||
bsec_library_return_t bsec_status_{BSEC_OK};
|
||||
int8_t bme680_status_{BME680_OK};
|
||||
@@ -88,10 +99,14 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
|
||||
std::queue<std::function<void()>> queue_;
|
||||
|
||||
bool bsec_state_data_valid_;
|
||||
uint8_t bsec_state_data_[BSEC_MAX_STATE_BLOB_SIZE]; // This is the current snapshot of the BSEC library state
|
||||
ESPPreferenceObject bsec_state_;
|
||||
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
|
||||
uint32_t last_state_save_ms_ = 0;
|
||||
bsec_bme_settings_t bme680_settings_;
|
||||
|
||||
std::string device_id_;
|
||||
float temperature_offset_{0};
|
||||
IAQMode iaq_mode_{IAQ_MODE_STATIC};
|
||||
|
||||
|
@@ -11,16 +11,19 @@ from esphome.const import (
|
||||
CONF_ON_PRESS,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MQTT_ID,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_RESTART,
|
||||
DEVICE_CLASS_UPDATE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_RESTART,
|
||||
DEVICE_CLASS_UPDATE,
|
||||
]
|
||||
@@ -54,30 +57,23 @@ _UNDEF = object()
|
||||
|
||||
|
||||
def button_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
icon: str = _UNDEF,
|
||||
entity_category: str = _UNDEF,
|
||||
device_class: str = _UNDEF,
|
||||
) -> cv.Schema:
|
||||
schema = BUTTON_SCHEMA
|
||||
if icon is not _UNDEF:
|
||||
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
|
||||
if entity_category is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=entity_category
|
||||
): cv.entity_category
|
||||
}
|
||||
)
|
||||
if device_class is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, default=device_class
|
||||
): validate_device_class
|
||||
}
|
||||
)
|
||||
return schema
|
||||
schema = {cv.GenerateID(): cv.declare_id(class_)}
|
||||
|
||||
for key, default, validator in [
|
||||
(CONF_ICON, icon, cv.icon),
|
||||
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
|
||||
(CONF_DEVICE_CLASS, device_class, validate_device_class),
|
||||
]:
|
||||
if default is not _UNDEF:
|
||||
schema[cv.Optional(key, default=default)] = validator
|
||||
|
||||
return BUTTON_SCHEMA.extend(schema)
|
||||
|
||||
|
||||
async def setup_button_core_(var, config):
|
||||
|
@@ -6,9 +6,6 @@ namespace button {
|
||||
|
||||
static const char *const TAG = "button";
|
||||
|
||||
Button::Button(const std::string &name) : EntityBase(name) {}
|
||||
Button::Button() : Button("") {}
|
||||
|
||||
void Button::press() {
|
||||
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
|
||||
this->press_action();
|
||||
|
@@ -15,15 +15,19 @@ namespace button {
|
||||
} \
|
||||
}
|
||||
|
||||
#define SUB_BUTTON(name) \
|
||||
protected: \
|
||||
button::Button *name##_button_{nullptr}; \
|
||||
\
|
||||
public: \
|
||||
void set_##name##_button(button::Button *button) { this->name##_button_ = button; }
|
||||
|
||||
/** Base class for all buttons.
|
||||
*
|
||||
* A button is just a momentary switch that does not have a state, only a trigger.
|
||||
*/
|
||||
class Button : public EntityBase {
|
||||
public:
|
||||
explicit Button();
|
||||
explicit Button(const std::string &name);
|
||||
|
||||
/** Press this button. This is called by the front-end.
|
||||
*
|
||||
* For implementing buttons, please override press_action.
|
||||
|
@@ -145,8 +145,8 @@ void CCS811Component::send_env_data_() {
|
||||
// https://github.com/adafruit/Adafruit_CCS811/blob/0990f5c620354d8bc087c4706bec091d8e6e5dfd/Adafruit_CCS811.cpp#L135-L142
|
||||
uint16_t hum_conv = static_cast<uint16_t>(lroundf(humidity * 512.0f + 0.5f));
|
||||
uint16_t temp_conv = static_cast<uint16_t>(lroundf(temperature * 512.0f + 0.5f));
|
||||
this->write_bytes(0x05, {(uint8_t)((hum_conv >> 8) & 0xff), (uint8_t)((hum_conv & 0xff)),
|
||||
(uint8_t)((temp_conv >> 8) & 0xff), (uint8_t)((temp_conv & 0xff))});
|
||||
this->write_bytes(0x05, {(uint8_t) ((hum_conv >> 8) & 0xff), (uint8_t) ((hum_conv & 0xff)),
|
||||
(uint8_t) ((temp_conv >> 8) & 0xff), (uint8_t) ((temp_conv & 0xff))});
|
||||
}
|
||||
void CCS811Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CCS811");
|
||||
|
@@ -20,8 +20,11 @@ from esphome.const import (
|
||||
CONF_MODE,
|
||||
CONF_MODE_COMMAND_TOPIC,
|
||||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_ON_CONTROL,
|
||||
CONF_ON_STATE,
|
||||
CONF_PRESET,
|
||||
CONF_PRESET_COMMAND_TOPIC,
|
||||
CONF_PRESET_STATE_TOPIC,
|
||||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
CONF_SWING_MODE_STATE_TOPIC,
|
||||
@@ -73,6 +76,7 @@ CLIMATE_FAN_MODES = {
|
||||
"MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE,
|
||||
"FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS,
|
||||
"DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE,
|
||||
"QUIET": ClimateFanMode.CLIMATE_FAN_QUIET,
|
||||
}
|
||||
|
||||
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
|
||||
@@ -101,9 +105,40 @@ CLIMATE_SWING_MODES = {
|
||||
|
||||
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||
|
||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||
|
||||
visual_temperature = cv.float_with_unit(
|
||||
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
|
||||
)
|
||||
|
||||
|
||||
def single_visual_temperature(value):
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
|
||||
value = visual_temperature(value)
|
||||
return VISUAL_TEMPERATURE_STEP_SCHEMA(
|
||||
{
|
||||
CONF_TARGET_TEMPERATURE: value,
|
||||
CONF_CURRENT_TEMPERATURE: value,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# Actions
|
||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
|
||||
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
|
||||
ControlTrigger = climate_ns.class_("ControlTrigger", automation.Trigger.template())
|
||||
|
||||
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
|
||||
single_visual_temperature,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature,
|
||||
cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
@@ -113,9 +148,7 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||
{
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): cv.float_with_unit(
|
||||
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||
@@ -142,6 +175,12 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
@@ -166,6 +205,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
@@ -184,7 +228,12 @@ async def setup_climate_core_(var, config):
|
||||
if CONF_MAX_TEMPERATURE in visual:
|
||||
cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE]))
|
||||
if CONF_TEMPERATURE_STEP in visual:
|
||||
cg.add(var.set_visual_temperature_step_override(visual[CONF_TEMPERATURE_STEP]))
|
||||
cg.add(
|
||||
var.set_visual_temperature_step_override(
|
||||
visual[CONF_TEMPERATURE_STEP][CONF_TARGET_TEMPERATURE],
|
||||
visual[CONF_TEMPERATURE_STEP][CONF_CURRENT_TEMPERATURE],
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
@@ -216,7 +265,12 @@ async def setup_climate_core_(var, config):
|
||||
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
|
||||
if CONF_MODE_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
|
||||
|
||||
if CONF_PRESET_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_preset_command_topic(config[CONF_PRESET_COMMAND_TOPIC])
|
||||
)
|
||||
if CONF_PRESET_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_preset_state_topic(config[CONF_PRESET_STATE_TOPIC]))
|
||||
if CONF_SWING_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_swing_mode_command_topic(
|
||||
@@ -270,6 +324,10 @@ async def setup_climate_core_(var, config):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_CONTROL, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
async def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
@@ -42,6 +42,13 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
Climate *climate_;
|
||||
};
|
||||
|
||||
class ControlTrigger : public Trigger<> {
|
||||
public:
|
||||
ControlTrigger(Climate *climate) {
|
||||
climate->add_on_control_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class StateTrigger : public Trigger<> {
|
||||
public:
|
||||
StateTrigger(Climate *climate) {
|
||||
|
@@ -44,6 +44,7 @@ void ClimateCall::perform() {
|
||||
if (this->target_temperature_high_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
|
||||
}
|
||||
this->parent_->control_callback_.call();
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
void ClimateCall::validate_() {
|
||||
@@ -174,6 +175,8 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||
this->set_fan_mode(CLIMATE_FAN_FOCUS);
|
||||
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
|
||||
} else if (str_equals_case_insensitive(fan_mode, "QUIET")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_QUIET);
|
||||
} else {
|
||||
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
|
||||
this->custom_fan_mode_ = fan_mode;
|
||||
@@ -315,6 +318,10 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void Climate::add_on_control_callback(std::function<void()> &&callback) {
|
||||
this->control_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// Random 32bit value; If this changes existing restore preferences are invalidated
|
||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||
|
||||
@@ -428,9 +435,11 @@ ClimateTraits Climate::get_traits() {
|
||||
if (this->visual_max_temperature_override_.has_value()) {
|
||||
traits.set_visual_max_temperature(*this->visual_max_temperature_override_);
|
||||
}
|
||||
if (this->visual_temperature_step_override_.has_value()) {
|
||||
traits.set_visual_temperature_step(*this->visual_temperature_step_override_);
|
||||
if (this->visual_target_temperature_step_override_.has_value()) {
|
||||
traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_);
|
||||
traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_);
|
||||
}
|
||||
|
||||
return traits;
|
||||
}
|
||||
|
||||
@@ -440,15 +449,11 @@ void Climate::set_visual_min_temperature_override(float visual_min_temperature_o
|
||||
void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) {
|
||||
this->visual_max_temperature_override_ = visual_max_temperature_override;
|
||||
}
|
||||
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
|
||||
this->visual_temperature_step_override_ = visual_temperature_step_override;
|
||||
void Climate::set_visual_temperature_step_override(float target, float current) {
|
||||
this->visual_target_temperature_step_override_ = target;
|
||||
this->visual_current_temperature_step_override_ = current;
|
||||
}
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
Climate::Climate(const std::string &name) : EntityBase(name) {}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
Climate::Climate() : Climate("") {}
|
||||
ClimateCall Climate::make_call() { return ClimateCall(this); }
|
||||
|
||||
ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
||||
@@ -539,7 +544,9 @@ void Climate::dump_traits_(const char *tag) {
|
||||
ESP_LOGCONFIG(tag, " [x] Visual settings:");
|
||||
ESP_LOGCONFIG(tag, " - Min: %.1f", traits.get_visual_min_temperature());
|
||||
ESP_LOGCONFIG(tag, " - Max: %.1f", traits.get_visual_max_temperature());
|
||||
ESP_LOGCONFIG(tag, " - Step: %.1f", traits.get_visual_temperature_step());
|
||||
ESP_LOGCONFIG(tag, " - Step:");
|
||||
ESP_LOGCONFIG(tag, " Target: %.1f", traits.get_visual_target_temperature_step());
|
||||
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
||||
}
|
||||
|
@@ -166,11 +166,6 @@ struct ClimateDeviceRestoreState {
|
||||
*/
|
||||
class Climate : public EntityBase {
|
||||
public:
|
||||
/// Construct a climate device with empty name (will be set later).
|
||||
Climate();
|
||||
/// Construct a climate device with a name.
|
||||
Climate(const std::string &name);
|
||||
|
||||
/// The active mode of the climate device.
|
||||
ClimateMode mode{CLIMATE_MODE_OFF};
|
||||
/// The active state of the climate device.
|
||||
@@ -219,6 +214,14 @@ class Climate : public EntityBase {
|
||||
*/
|
||||
void add_on_state_callback(std::function<void()> &&callback);
|
||||
|
||||
/**
|
||||
* Add a callback for the climate device configuration; each time the configuration parameters of a climate device
|
||||
* is updated (using perform() of a ClimateCall), this callback will be called, before any on_state callback.
|
||||
*
|
||||
* @param callback The callback to call.
|
||||
*/
|
||||
void add_on_control_callback(std::function<void()> &&callback);
|
||||
|
||||
/** Make a climate device control call, this is used to control the climate device, see the ClimateCall description
|
||||
* for more info.
|
||||
* @return A new ClimateCall instance targeting this climate device.
|
||||
@@ -241,7 +244,7 @@ class Climate : public EntityBase {
|
||||
|
||||
void set_visual_min_temperature_override(float visual_min_temperature_override);
|
||||
void set_visual_max_temperature_override(float visual_max_temperature_override);
|
||||
void set_visual_temperature_step_override(float visual_temperature_step_override);
|
||||
void set_visual_temperature_step_override(float target, float current);
|
||||
|
||||
protected:
|
||||
friend ClimateCall;
|
||||
@@ -285,10 +288,12 @@ class Climate : public EntityBase {
|
||||
void dump_traits_(const char *tag);
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
CallbackManager<void()> control_callback_{};
|
||||
ESPPreferenceObject rtc_;
|
||||
optional<float> visual_min_temperature_override_{};
|
||||
optional<float> visual_max_temperature_override_{};
|
||||
optional<float> visual_temperature_step_override_{};
|
||||
optional<float> visual_target_temperature_step_override_{};
|
||||
optional<float> visual_current_temperature_step_override_{};
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
|
@@ -62,6 +62,8 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
|
||||
return LOG_STR("FOCUS");
|
||||
case climate::CLIMATE_FAN_DIFFUSE:
|
||||
return LOG_STR("DIFFUSE");
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
return LOG_STR("QUIET");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
@@ -62,6 +62,8 @@ enum ClimateFanMode : uint8_t {
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
/// The fan mode is set to Diffuse
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
/// The fan mode is set to Quiet
|
||||
CLIMATE_FAN_QUIET = 9,
|
||||
};
|
||||
|
||||
/// Enum for all modes a climate swing can be in
|
||||
|
@@ -3,8 +3,12 @@
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
|
||||
return step_to_accuracy_decimals(this->visual_temperature_step_);
|
||||
int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const {
|
||||
return step_to_accuracy_decimals(this->visual_target_temperature_step_);
|
||||
}
|
||||
|
||||
int8_t ClimateTraits::get_current_temperature_accuracy_decimals() const {
|
||||
return step_to_accuracy_decimals(this->visual_current_temperature_step_);
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
|
@@ -28,7 +28,7 @@ namespace climate {
|
||||
* - supports action - if the climate device supports reporting the active
|
||||
* current action of the device with the action property.
|
||||
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
|
||||
* - on, off, auto, high, medium, low, middle, focus, diffuse
|
||||
* - on, off, auto, high, medium, low, middle, focus, diffuse, quiet
|
||||
* - supports swing modes - optionally, if it has a swing which can be configured in different ways:
|
||||
* - off, both, vertical, horizontal
|
||||
*
|
||||
@@ -147,9 +147,20 @@ class ClimateTraits {
|
||||
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
|
||||
float get_visual_max_temperature() const { return visual_max_temperature_; }
|
||||
void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; }
|
||||
float get_visual_temperature_step() const { return visual_temperature_step_; }
|
||||
int8_t get_temperature_accuracy_decimals() const;
|
||||
void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; }
|
||||
float get_visual_target_temperature_step() const { return visual_target_temperature_step_; }
|
||||
float get_visual_current_temperature_step() const { return visual_current_temperature_step_; }
|
||||
void set_visual_target_temperature_step(float temperature_step) {
|
||||
visual_target_temperature_step_ = temperature_step;
|
||||
}
|
||||
void set_visual_current_temperature_step(float temperature_step) {
|
||||
visual_current_temperature_step_ = temperature_step;
|
||||
}
|
||||
void set_visual_temperature_step(float temperature_step) {
|
||||
visual_target_temperature_step_ = temperature_step;
|
||||
visual_current_temperature_step_ = temperature_step;
|
||||
}
|
||||
int8_t get_target_temperature_accuracy_decimals() const;
|
||||
int8_t get_current_temperature_accuracy_decimals() const;
|
||||
|
||||
protected:
|
||||
void set_mode_support_(climate::ClimateMode mode, bool supported) {
|
||||
@@ -186,7 +197,8 @@ class ClimateTraits {
|
||||
|
||||
float visual_min_temperature_{10};
|
||||
float visual_max_temperature_{30};
|
||||
float visual_temperature_step_{0.1};
|
||||
float visual_target_temperature_step_{0.1};
|
||||
float visual_current_temperature_step_{0.1};
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
|
@@ -10,23 +10,42 @@ CONF_RED_INT = "red_int"
|
||||
CONF_GREEN_INT = "green_int"
|
||||
CONF_BLUE_INT = "blue_int"
|
||||
CONF_WHITE_INT = "white_int"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
|
||||
cv.Exclusive(CONF_RED, "red"): cv.percentage,
|
||||
cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t,
|
||||
cv.Exclusive(CONF_GREEN, "green"): cv.percentage,
|
||||
cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t,
|
||||
cv.Exclusive(CONF_BLUE, "blue"): cv.percentage,
|
||||
cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t,
|
||||
cv.Exclusive(CONF_WHITE, "white"): cv.percentage,
|
||||
cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONF_HEX = "hex"
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
def hex_color(value):
|
||||
if len(value) != 6:
|
||||
raise cv.Invalid("Color must have six digits")
|
||||
try:
|
||||
return (int(value[0:2], 16), int(value[2:4], 16), int(value[4:6], 16))
|
||||
except ValueError as exc:
|
||||
raise cv.Invalid("Color must be hexadecimal") from exc
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Any(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
|
||||
cv.Exclusive(CONF_RED, "red"): cv.percentage,
|
||||
cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t,
|
||||
cv.Exclusive(CONF_GREEN, "green"): cv.percentage,
|
||||
cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t,
|
||||
cv.Exclusive(CONF_BLUE, "blue"): cv.percentage,
|
||||
cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t,
|
||||
cv.Exclusive(CONF_WHITE, "white"): cv.percentage,
|
||||
cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
|
||||
cv.Required(CONF_HEX): hex_color,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
def from_rgbw(config):
|
||||
r = 0
|
||||
if CONF_RED in config:
|
||||
r = int(config[CONF_RED] * 255)
|
||||
@@ -51,6 +70,16 @@ async def to_code(config):
|
||||
elif CONF_WHITE_INT in config:
|
||||
w = config[CONF_WHITE_INT]
|
||||
|
||||
return (r, g, b, w)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
if CONF_HEX in config:
|
||||
r, g, b = config[CONF_HEX]
|
||||
w = 0
|
||||
else:
|
||||
r, g, b, w = from_rgbw(config)
|
||||
|
||||
cg.new_variable(
|
||||
config[CONF_ID],
|
||||
cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)),
|
||||
|
@@ -16,10 +16,9 @@ CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
button.button_schema()
|
||||
button.button_schema(CopyButton)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CopyButton),
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button),
|
||||
}
|
||||
)
|
||||
|
@@ -15,12 +15,15 @@ from .. import copy_ns
|
||||
CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CopyNumber),
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
number.number_schema(CopyNumber)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
|
||||
|
@@ -14,12 +14,15 @@ from .. import copy_ns
|
||||
CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = select.SELECT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CopySelect),
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
select.select_schema(CopySelect)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
|
||||
|
@@ -17,6 +17,17 @@ from esphome.const import (
|
||||
CONF_STOP,
|
||||
CONF_MQTT_ID,
|
||||
CONF_TRIGGER_ID,
|
||||
DEVICE_CLASS_AWNING,
|
||||
DEVICE_CLASS_BLIND,
|
||||
DEVICE_CLASS_CURTAIN,
|
||||
DEVICE_CLASS_DAMPER,
|
||||
DEVICE_CLASS_DOOR,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_GARAGE,
|
||||
DEVICE_CLASS_GATE,
|
||||
DEVICE_CLASS_SHADE,
|
||||
DEVICE_CLASS_SHUTTER,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
@@ -25,17 +36,17 @@ IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEVICE_CLASSES = [
|
||||
"",
|
||||
"awning",
|
||||
"blind",
|
||||
"curtain",
|
||||
"damper",
|
||||
"door",
|
||||
"garage",
|
||||
"gate",
|
||||
"shade",
|
||||
"shutter",
|
||||
"window",
|
||||
DEVICE_CLASS_AWNING,
|
||||
DEVICE_CLASS_BLIND,
|
||||
DEVICE_CLASS_CURTAIN,
|
||||
DEVICE_CLASS_DAMPER,
|
||||
DEVICE_CLASS_DOOR,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_GARAGE,
|
||||
DEVICE_CLASS_GATE,
|
||||
DEVICE_CLASS_SHADE,
|
||||
DEVICE_CLASS_SHUTTER,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
]
|
||||
|
||||
cover_ns = cg.esphome_ns.namespace("cover")
|
||||
|
@@ -31,7 +31,7 @@ const char *cover_operation_to_str(CoverOperation op) {
|
||||
}
|
||||
}
|
||||
|
||||
Cover::Cover(const std::string &name) : EntityBase(name), position{COVER_OPEN} {}
|
||||
Cover::Cover() : position{COVER_OPEN} {}
|
||||
|
||||
CoverCall::CoverCall(Cover *parent) : parent_(parent) {}
|
||||
CoverCall &CoverCall::set_command(const char *command) {
|
||||
@@ -204,18 +204,13 @@ optional<CoverRestoreState> Cover::restore_state_() {
|
||||
return {};
|
||||
return recovered;
|
||||
}
|
||||
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
|
||||
return "";
|
||||
}
|
||||
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
|
||||
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
|
||||
std::string Cover::device_class() { return ""; }
|
||||
|
||||
CoverCall CoverRestoreState::to_call(Cover *cover) {
|
||||
auto call = cover->make_call();
|
||||
|
@@ -111,7 +111,6 @@ const char *cover_operation_to_str(CoverOperation op);
|
||||
class Cover : public EntityBase {
|
||||
public:
|
||||
explicit Cover();
|
||||
explicit Cover(const std::string &name);
|
||||
|
||||
/// The current operation of the cover (idle, opening, closing).
|
||||
CoverOperation current_operation{COVER_OPERATION_IDLE};
|
||||
@@ -170,12 +169,6 @@ class Cover : public EntityBase {
|
||||
|
||||
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_();
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
|
@@ -305,7 +305,7 @@ bool CS5460AComponent::check_status_() {
|
||||
voltage_sensor_->publish_state(raw_voltage * voltage_multiplier_);
|
||||
|
||||
if (power_sensor_ != nullptr && raw_energy != prev_raw_energy_) {
|
||||
int32_t raw = (int32_t)(raw_energy << 8) >> 8; /* Sign-extend */
|
||||
int32_t raw = (int32_t) (raw_energy << 8) >> 8; /* Sign-extend */
|
||||
power_sensor_->publish_state(raw * power_multiplier_);
|
||||
prev_raw_energy_ = raw_energy;
|
||||
}
|
||||
|
@@ -33,7 +33,10 @@ void CTClampSensor::update() {
|
||||
|
||||
const float rms_ac_dc_squared = this->sample_squared_sum_ / this->num_samples_;
|
||||
const float rms_dc = this->sample_sum_ / this->num_samples_;
|
||||
const float rms_ac = std::sqrt(rms_ac_dc_squared - rms_dc * rms_dc);
|
||||
const float rms_ac_squared = rms_ac_dc_squared - rms_dc * rms_dc;
|
||||
float rms_ac = 0;
|
||||
if (rms_ac_squared > 0)
|
||||
rms_ac = std::sqrt(rms_ac_squared);
|
||||
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac,
|
||||
this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
|
||||
this->publish_state(rms_ac);
|
||||
|
@@ -10,7 +10,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CustomSensorConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA),
|
||||
cv.Required(CONF_SENSORS): cv.ensure_list(sensor.sensor_schema()),
|
||||
}
|
||||
)
|
||||
|
||||
|
@@ -61,8 +61,8 @@ void DalyBmsComponent::request_data_(uint8_t data_id) {
|
||||
request_message[9] = 0x00; // |
|
||||
request_message[10] = 0x00; // |
|
||||
request_message[11] = 0x00; // Empty Data
|
||||
request_message[12] = (uint8_t)(request_message[0] + request_message[1] + request_message[2] +
|
||||
request_message[3]); // Checksum (Lower byte of the other bytes sum)
|
||||
request_message[12] = (uint8_t) (request_message[0] + request_message[1] + request_message[2] +
|
||||
request_message[3]); // Checksum (Lower byte of the other bytes sum)
|
||||
|
||||
this->write_array(request_message, sizeof(request_message));
|
||||
this->flush();
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import base64
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -73,6 +75,7 @@ def import_config(
|
||||
project_name: str,
|
||||
import_url: str,
|
||||
network: str = CONF_WIFI,
|
||||
encryption: bool = False,
|
||||
) -> None:
|
||||
p = Path(path)
|
||||
|
||||
@@ -80,15 +83,40 @@ def import_config(
|
||||
raise FileExistsError
|
||||
|
||||
if project_name == "esphome.web":
|
||||
if "esp32c3" in import_url:
|
||||
board = "esp32-c3-devkitm-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32s2" in import_url:
|
||||
board = "esp32-s2-saola-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32s3" in import_url:
|
||||
board = "esp32-s3-devkitc-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32" in import_url:
|
||||
board = "esp32dev"
|
||||
platform = "ESP32"
|
||||
elif "esp8266" in import_url:
|
||||
board = "esp01_1m"
|
||||
platform = "ESP8266"
|
||||
elif "pico-w" in import_url:
|
||||
board = "pico-w"
|
||||
platform = "RP2040"
|
||||
|
||||
kwargs = {
|
||||
"name": name,
|
||||
"friendly_name": friendly_name,
|
||||
"platform": platform,
|
||||
"board": board,
|
||||
"ssid": "!secret wifi_ssid",
|
||||
"psk": "!secret wifi_password",
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
kwargs["api_encryption_key"] = key
|
||||
|
||||
p.write_text(
|
||||
wizard_file(
|
||||
name=name,
|
||||
friendly_name=friendly_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",
|
||||
),
|
||||
wizard_file(**kwargs),
|
||||
encoding="utf8",
|
||||
)
|
||||
else:
|
||||
@@ -115,6 +143,11 @@ def import_config(
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": esphome_core,
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
config["api"] = {"encryption": {"key": key}}
|
||||
|
||||
output = dump(config)
|
||||
|
||||
if network == CONF_WIFI:
|
||||
|
@@ -1,15 +1,11 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome.components import logger
|
||||
from esphome.const import (
|
||||
CONF_BLOCK,
|
||||
CONF_DEVICE,
|
||||
CONF_FRAGMENTATION,
|
||||
CONF_FREE,
|
||||
CONF_ID,
|
||||
CONF_LEVEL,
|
||||
CONF_LOGGER,
|
||||
CONF_LOOP_TIME,
|
||||
)
|
||||
|
||||
@@ -21,38 +17,29 @@ debug_ns = cg.esphome_ns.namespace("debug")
|
||||
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.polling_component_schema("60s"))
|
||||
|
||||
|
||||
def _final_validate(_):
|
||||
logger_conf = fv.full_config.get()[CONF_LOGGER]
|
||||
severity = logger.LOG_LEVEL_SEVERITY.index(logger_conf[CONF_LEVEL])
|
||||
if severity < logger.LOG_LEVEL_SEVERITY.index("DEBUG"):
|
||||
raise cv.Invalid(
|
||||
"The debug component requires the logger to be at least at DEBUG level"
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
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.polling_component_schema("60s")),
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
@@ -37,6 +37,10 @@ static uint32_t get_free_heap() {
|
||||
}
|
||||
|
||||
void DebugComponent::dump_config() {
|
||||
#ifndef ESPHOME_LOG_HAS_DEBUG
|
||||
return; // Can't log below if debug logging is disabled
|
||||
#endif
|
||||
|
||||
std::string device_info;
|
||||
std::string reset_reason;
|
||||
device_info.reserve(256);
|
||||
|
@@ -21,6 +21,7 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
|
||||
WAKEUP_PINS = {
|
||||
@@ -69,6 +70,30 @@ WAKEUP_PINS = {
|
||||
20,
|
||||
21,
|
||||
],
|
||||
VARIANT_ESP32S3: [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
@@ -284,9 +284,10 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
},
|
||||
],
|
||||
): [
|
||||
number.NUMBER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
|
||||
number.number_schema(DemoNumber)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DemoNumber),
|
||||
cv.Required(CONF_TYPE): cv.enum(NUMBER_TYPES, int=True),
|
||||
cv.Required(CONF_MIN_VALUE): cv.float_,
|
||||
cv.Required(CONF_MAX_VALUE): cv.float_,
|
||||
|
@@ -111,6 +111,7 @@ class DemoClimate : public climate::Climate, public Component {
|
||||
climate::CLIMATE_FAN_MIDDLE,
|
||||
climate::CLIMATE_FAN_FOCUS,
|
||||
climate::CLIMATE_FAN_DIFFUSE,
|
||||
climate::CLIMATE_FAN_QUIET,
|
||||
});
|
||||
traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"});
|
||||
traits.set_supported_swing_modes({
|
||||
|
@@ -19,7 +19,7 @@ void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
|
||||
}
|
||||
|
||||
void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) {
|
||||
uint8_t buffer[10]{0x7e, 0xff, 0x06, cmd, 0x01, (uint8_t)(argument >> 8), (uint8_t) argument, 0x00, 0x00, 0xef};
|
||||
uint8_t buffer[10]{0x7e, 0xff, 0x06, cmd, 0x01, (uint8_t) (argument >> 8), (uint8_t) argument, 0x00, 0x00, 0xef};
|
||||
uint16_t checksum = 0;
|
||||
for (uint8_t i = 1; i < 7; i++)
|
||||
checksum += buffer[i];
|
||||
|
@@ -15,6 +15,93 @@ static const char *const TAG = "display";
|
||||
const Color COLOR_OFF(0, 0, 0, 0);
|
||||
const Color COLOR_ON(255, 255, 255, 255);
|
||||
|
||||
void Rect::expand(int16_t horizontal, int16_t vertical) {
|
||||
if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
|
||||
this->x = this->x - horizontal;
|
||||
this->y = this->y - vertical;
|
||||
this->w = this->w + (2 * horizontal);
|
||||
this->h = this->h + (2 * vertical);
|
||||
}
|
||||
}
|
||||
|
||||
void Rect::extend(Rect rect) {
|
||||
if (!this->is_set()) {
|
||||
this->x = rect.x;
|
||||
this->y = rect.y;
|
||||
this->w = rect.w;
|
||||
this->h = rect.h;
|
||||
} else {
|
||||
if (this->x > rect.x) {
|
||||
this->w = this->w + (this->x - rect.x);
|
||||
this->x = rect.x;
|
||||
}
|
||||
if (this->y > rect.y) {
|
||||
this->h = this->h + (this->y - rect.y);
|
||||
this->y = rect.y;
|
||||
}
|
||||
if (this->x2() < rect.x2()) {
|
||||
this->w = rect.x2() - this->x;
|
||||
}
|
||||
if (this->y2() < rect.y2()) {
|
||||
this->h = rect.y2() - this->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
void Rect::shrink(Rect rect) {
|
||||
if (!this->inside(rect)) {
|
||||
(*this) = Rect();
|
||||
} else {
|
||||
if (this->x2() > rect.x2()) {
|
||||
this->w = rect.x2() - this->x;
|
||||
}
|
||||
if (this->x < rect.x) {
|
||||
this->w = this->w + (this->x - rect.x);
|
||||
this->x = rect.x;
|
||||
}
|
||||
if (this->y2() > rect.y2()) {
|
||||
this->h = rect.y2() - this->y;
|
||||
}
|
||||
if (this->y < rect.y) {
|
||||
this->h = this->h + (this->y - rect.y);
|
||||
this->y = rect.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Rect::equal(Rect rect) {
|
||||
return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
|
||||
}
|
||||
|
||||
bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) { // NOLINT
|
||||
if (!this->is_set()) {
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2()));
|
||||
} else {
|
||||
return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h));
|
||||
}
|
||||
}
|
||||
|
||||
bool Rect::inside(Rect rect, bool absolute) {
|
||||
if (!this->is_set() || !rect.is_set()) {
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
|
||||
} else {
|
||||
return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
|
||||
}
|
||||
}
|
||||
|
||||
void Rect::info(const std::string &prefix) {
|
||||
if (this->is_set()) {
|
||||
ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(),
|
||||
this->y2());
|
||||
} else
|
||||
ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
|
||||
}
|
||||
|
||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
this->buffer_ = allocator.allocate(buffer_length);
|
||||
@@ -24,6 +111,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
||||
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
|
||||
int DisplayBuffer::get_width() {
|
||||
@@ -50,6 +138,9 @@ int DisplayBuffer::get_height() {
|
||||
}
|
||||
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
|
||||
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
return; // NOLINT
|
||||
|
||||
switch (this->rotation_) {
|
||||
case DISPLAY_ROTATION_0_DEGREES:
|
||||
break;
|
||||
@@ -368,6 +459,10 @@ void DisplayBuffer::do_update_() {
|
||||
} else if (this->writer_.has_value()) {
|
||||
(*this->writer_)(*this);
|
||||
}
|
||||
// remove all not ended clipping regions
|
||||
while (is_clipping()) {
|
||||
end_clipping();
|
||||
}
|
||||
}
|
||||
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
|
||||
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
|
||||
@@ -392,6 +487,41 @@ void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time:
|
||||
}
|
||||
#endif
|
||||
|
||||
void DisplayBuffer::start_clipping(Rect rect) {
|
||||
if (!this->clipping_rectangle_.empty()) {
|
||||
Rect r = this->clipping_rectangle_.back();
|
||||
rect.shrink(r);
|
||||
}
|
||||
this->clipping_rectangle_.push_back(rect);
|
||||
}
|
||||
void DisplayBuffer::end_clipping() {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "clear: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.pop_back();
|
||||
}
|
||||
}
|
||||
void DisplayBuffer::extend_clipping(Rect add_rect) {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "add: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.back().extend(add_rect);
|
||||
}
|
||||
}
|
||||
void DisplayBuffer::shrink_clipping(Rect add_rect) {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "add: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.back().shrink(add_rect);
|
||||
}
|
||||
}
|
||||
Rect DisplayBuffer::get_clipping() {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
return Rect();
|
||||
} else {
|
||||
return this->clipping_rectangle_.back();
|
||||
}
|
||||
}
|
||||
bool Glyph::get_pixel(int x, int y) const {
|
||||
const int x_data = x - this->glyph_data_->offset_x;
|
||||
const int y_data = y - this->glyph_data_->offset_y;
|
||||
@@ -482,10 +612,10 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
||||
*x_offset = min_x;
|
||||
*width = x - min_x;
|
||||
}
|
||||
const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; }
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
|
||||
glyphs_.reserve(data_nr);
|
||||
for (int i = 0; i < data_nr; ++i)
|
||||
glyphs_.emplace_back(data + i);
|
||||
glyphs_.emplace_back(&data[i]);
|
||||
}
|
||||
|
||||
bool Image::get_pixel(int x, int y) const {
|
||||
@@ -534,7 +664,7 @@ bool Animation::get_pixel(int x, int y) const {
|
||||
return false;
|
||||
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
||||
const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
|
||||
if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
|
||||
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
|
||||
return false;
|
||||
const uint32_t pos = x + y * width_8 + frame_index;
|
||||
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||
@@ -543,7 +673,7 @@ Color Animation::get_color_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return Color::BLACK;
|
||||
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
|
||||
if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
|
||||
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
|
||||
return Color::BLACK;
|
||||
const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
|
||||
const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
|
||||
@@ -555,7 +685,7 @@ Color Animation::get_rgb565_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return Color::BLACK;
|
||||
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
|
||||
if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
|
||||
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
|
||||
return Color::BLACK;
|
||||
const uint32_t pos = (x + y * this->width_ + frame_index) * 2;
|
||||
uint16_t rgb565 =
|
||||
@@ -569,7 +699,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
return Color::BLACK;
|
||||
const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
|
||||
if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
|
||||
if (frame_index >= (uint32_t) (this->width_ * this->height_ * this->animation_frame_count_))
|
||||
return Color::BLACK;
|
||||
const uint32_t pos = (x + y * this->width_ + frame_index);
|
||||
const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
|
||||
|
@@ -4,7 +4,6 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "display_color_utils.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <vector>
|
||||
|
||||
@@ -100,6 +99,33 @@ enum DisplayRotation {
|
||||
DISPLAY_ROTATION_270_DEGREES = 270,
|
||||
};
|
||||
|
||||
static const int16_t VALUE_NO_SET = 32766;
|
||||
|
||||
class Rect {
|
||||
public:
|
||||
int16_t x; ///< X coordinate of corner
|
||||
int16_t y; ///< Y coordinate of corner
|
||||
int16_t w; ///< Width of region
|
||||
int16_t h; ///< Height of region
|
||||
|
||||
Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {} // NOLINT
|
||||
inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {}
|
||||
inline int16_t x2() { return this->x + this->w; }; ///< X coordinate of corner
|
||||
inline int16_t y2() { return this->y + this->h; }; ///< Y coordinate of corner
|
||||
|
||||
inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
|
||||
|
||||
void expand(int16_t horizontal, int16_t vertical);
|
||||
|
||||
void extend(Rect rect);
|
||||
void shrink(Rect rect);
|
||||
|
||||
bool inside(Rect rect, bool absolute = true);
|
||||
bool inside(int16_t test_x, int16_t test_y, bool absolute = true);
|
||||
bool equal(Rect rect);
|
||||
void info(const std::string &prefix = "rect info:");
|
||||
};
|
||||
|
||||
class Font;
|
||||
class Image;
|
||||
class DisplayBuffer;
|
||||
@@ -126,6 +152,7 @@ class DisplayBuffer {
|
||||
int get_width();
|
||||
/// Get the height of the image in pixels with rotation applied.
|
||||
int get_height();
|
||||
|
||||
/// Set a single pixel at the specified coordinates to the given color.
|
||||
void draw_pixel_at(int x, int y, Color color = COLOR_ON);
|
||||
|
||||
@@ -374,6 +401,49 @@ class DisplayBuffer {
|
||||
*/
|
||||
virtual DisplayType get_display_type() = 0;
|
||||
|
||||
/** Set the clipping rectangle for further drawing
|
||||
*
|
||||
* @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen)
|
||||
*
|
||||
* return true if success, false if error
|
||||
*/
|
||||
void start_clipping(Rect rect);
|
||||
void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
start_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** Add a rectangular region to the invalidation region
|
||||
* - This is usually called when an element has been modified
|
||||
*
|
||||
* @param[in] rect: Rectangle to add to the invalidation region
|
||||
*/
|
||||
void extend_clipping(Rect rect);
|
||||
void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
this->extend_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** substract a rectangular region to the invalidation region
|
||||
* - This is usually called when an element has been modified
|
||||
*
|
||||
* @param[in] rect: Rectangle to add to the invalidation region
|
||||
*/
|
||||
void shrink_clipping(Rect rect);
|
||||
void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
|
||||
this->shrink_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** Reset the invalidation region
|
||||
*/
|
||||
void end_clipping();
|
||||
|
||||
/** Get the current the clipping rectangle
|
||||
*
|
||||
* return rect for active clipping region
|
||||
*/
|
||||
Rect get_clipping();
|
||||
|
||||
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
|
||||
|
||||
protected:
|
||||
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
|
||||
|
||||
@@ -390,6 +460,7 @@ class DisplayBuffer {
|
||||
DisplayPage *previous_page_{nullptr};
|
||||
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
||||
bool auto_clear_enabled_{true};
|
||||
std::vector<Rect> clipping_rectangle_;
|
||||
};
|
||||
|
||||
class DisplayPage {
|
||||
@@ -456,10 +527,10 @@ class Font {
|
||||
inline int get_baseline() { return this->baseline_; }
|
||||
inline int get_height() { return this->height_; }
|
||||
|
||||
const std::vector<Glyph> &get_glyphs() const;
|
||||
const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
||||
|
||||
protected:
|
||||
std::vector<Glyph> glyphs_;
|
||||
std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
|
||||
int baseline_;
|
||||
int height_;
|
||||
};
|
||||
|
@@ -8,6 +8,7 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
CONF_NUMBER,
|
||||
CONF_FORMAT,
|
||||
CONF_MODE,
|
||||
@@ -32,7 +33,6 @@ CONF_BACK = "back"
|
||||
CONF_TEXT = "text"
|
||||
CONF_SELECT = "select"
|
||||
CONF_SWITCH = "switch"
|
||||
CONF_CUSTOM = "custom"
|
||||
CONF_ITEMS = "items"
|
||||
CONF_ON_TEXT = "on_text"
|
||||
CONF_OFF_TEXT = "off_text"
|
||||
|
@@ -168,7 +168,7 @@ void ENS210Component::update() {
|
||||
return;
|
||||
}
|
||||
// Pack bytes for humidity
|
||||
h_val_data = (uint32_t)((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]);
|
||||
h_val_data = (uint32_t) ((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]);
|
||||
// Extract humidity data and update the status
|
||||
extract_measurement_(h_val_data, &humidity_data, &humidity_status);
|
||||
|
||||
@@ -183,7 +183,7 @@ void ENS210Component::update() {
|
||||
return;
|
||||
}
|
||||
// Pack bytes for temperature
|
||||
t_val_data = (uint32_t)((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]);
|
||||
t_val_data = (uint32_t) ((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]);
|
||||
// Extract temperature data and update the status
|
||||
extract_measurement_(t_val_data, &temperature_data, &temperature_status);
|
||||
|
||||
|
@@ -4,29 +4,43 @@ from pathlib import Path
|
||||
import logging
|
||||
import os
|
||||
|
||||
from esphome.helpers import copy_file_if_changed, write_file_if_changed
|
||||
from esphome.helpers import copy_file_if_changed, write_file_if_changed, mkdir_p
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_COMPONENTS,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_NAME,
|
||||
CONF_SOURCE,
|
||||
CONF_TYPE,
|
||||
CONF_VARIANT,
|
||||
CONF_VERSION,
|
||||
CONF_ADVANCED,
|
||||
CONF_REFRESH,
|
||||
CONF_PATH,
|
||||
CONF_URL,
|
||||
CONF_REF,
|
||||
CONF_IGNORE_EFUSE_MAC_CRC,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
TYPE_GIT,
|
||||
TYPE_LOCAL,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.core import CORE, HexInt, TimePeriod
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome import git
|
||||
|
||||
from .const import ( # noqa
|
||||
KEY_BOARD,
|
||||
KEY_COMPONENTS,
|
||||
KEY_ESP32,
|
||||
KEY_PATH,
|
||||
KEY_REF,
|
||||
KEY_REFRESH,
|
||||
KEY_REPO,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32C3,
|
||||
@@ -51,6 +65,7 @@ def set_core_data(config):
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf"
|
||||
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||
@@ -104,6 +119,21 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
|
||||
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value
|
||||
|
||||
|
||||
def add_idf_component(
|
||||
name: str, repo: str, ref: str = None, path: str = None, refresh: TimePeriod = None
|
||||
):
|
||||
"""Add an esp-idf component to the project."""
|
||||
if not CORE.using_esp_idf:
|
||||
raise ValueError("Not an esp-idf project")
|
||||
if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: path,
|
||||
KEY_REFRESH: refresh,
|
||||
}
|
||||
|
||||
|
||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||
# format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to
|
||||
# a PIO platformio/framework-arduinoespressif32 value
|
||||
@@ -133,23 +163,23 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5)
|
||||
# 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(5, 2, 0)
|
||||
ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 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, 4, 2)
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 4)
|
||||
# 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(5, 2, 0)
|
||||
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 3, 0)
|
||||
|
||||
|
||||
def _arduino_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(2, 0, 5), "https://github.com/espressif/arduino-esp32.git"),
|
||||
"latest": (cv.Version(2, 0, 5), None),
|
||||
"dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"),
|
||||
"latest": (cv.Version(2, 0, 7), None),
|
||||
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
@@ -183,8 +213,8 @@ def _arduino_check_versions(value):
|
||||
def _esp_idf_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(4, 4, 2), None),
|
||||
"dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(5, 0, 1), None),
|
||||
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
@@ -270,6 +300,18 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH, default="1d"): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
_esp_idf_check_versions,
|
||||
@@ -372,6 +414,19 @@ async def to_code(config):
|
||||
),
|
||||
)
|
||||
|
||||
for component in conf[CONF_COMPONENTS]:
|
||||
source = component[CONF_SOURCE]
|
||||
if source[CONF_TYPE] == TYPE_GIT:
|
||||
add_idf_component(
|
||||
name=component[CONF_NAME],
|
||||
repo=source[CONF_URL],
|
||||
ref=source.get(CONF_REF),
|
||||
path=component.get(CONF_PATH),
|
||||
refresh=component[CONF_REFRESH],
|
||||
)
|
||||
elif source[CONF_TYPE] == TYPE_LOCAL:
|
||||
_LOGGER.warning("Local components are not implemented yet.")
|
||||
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
@@ -468,6 +523,32 @@ def copy_files():
|
||||
__version__,
|
||||
)
|
||||
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True)
|
||||
|
||||
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
|
||||
for name, component in components.items():
|
||||
repo_dir, _ = git.clone_or_update(
|
||||
url=component[KEY_REPO],
|
||||
ref=component[KEY_REF],
|
||||
refresh=component[KEY_REFRESH],
|
||||
domain="idf_components",
|
||||
)
|
||||
mkdir_p(CORE.relative_build_path("components"))
|
||||
component_dir = repo_dir
|
||||
if component[KEY_PATH] is not None:
|
||||
component_dir = component_dir / component[KEY_PATH]
|
||||
|
||||
shutil.copytree(
|
||||
component_dir,
|
||||
CORE.relative_build_path(f"components/{name}"),
|
||||
dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns(".git", ".github"),
|
||||
)
|
||||
|
||||
dir = os.path.dirname(__file__)
|
||||
post_build_file = os.path.join(dir, "post_build.py.script")
|
||||
copy_file_if_changed(
|
||||
|
@@ -4,6 +4,11 @@ KEY_ESP32 = "esp32"
|
||||
KEY_BOARD = "board"
|
||||
KEY_VARIANT = "variant"
|
||||
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||
KEY_COMPONENTS = "components"
|
||||
KEY_REPO = "repo"
|
||||
KEY_REF = "ref"
|
||||
KEY_REFRESH = "refresh"
|
||||
KEY_PATH = "path"
|
||||
|
||||
VARIANT_ESP32 = "ESP32"
|
||||
VARIANT_ESP32S2 = "ESP32S2"
|
||||
|
@@ -23,7 +23,7 @@ void loop();
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { vPortYield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return (uint32_t) (esp_timer_get_time() / 1000ULL); }
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
@@ -74,7 +75,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
|
||||
|
||||
std::string ESP32InternalGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast<uint32_t>(pin_));
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%" PRIu32, static_cast<uint32_t>(pin_));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user