Compare commits
473 Commits
dev
...
kamstrup_m
Author | SHA1 | Date | |
---|---|---|---|
|
a75af2bb1d | ||
|
b1fb9eb9b1 | ||
|
0277c0731f | ||
|
a059b7e777 | ||
|
393ed9c00e | ||
|
f006170e17 | ||
|
d90fd96fb2 | ||
|
cc45945fcf | ||
|
0c24d951ff | ||
|
92e44b8238 | ||
|
34387adbcd | ||
|
c6885c1bf4 | ||
|
8b8efb57af | ||
|
499e120aa4 | ||
|
6f198a4736 | ||
|
f500f448b1 | ||
|
6ad9baa870 | ||
|
f843925301 | ||
|
4a3b628946 | ||
|
4ffdc38cf5 | ||
|
f83f1bff19 | ||
|
9370ff3dfa | ||
|
2053b02c61 | ||
|
f34e797a0d | ||
|
30a2fc1273 | ||
|
48da5ef1c4 | ||
|
dbbbba3cf8 | ||
|
16e523ca68 | ||
|
3b2bbd306f | ||
|
54caed36f7 | ||
|
dfcccda69e | ||
|
de352c1609 | ||
|
c30068fc97 | ||
|
f28f712827 | ||
|
b63ade298f | ||
|
f117d4f50a | ||
|
6e4267b797 | ||
|
2dd032475b | ||
|
db0ed055dd | ||
|
106c1bfac2 | ||
|
eb2a0f45db | ||
|
2b4fdd6c39 | ||
|
3669320398 | ||
|
d706f40ce1 | ||
|
027284c29c | ||
|
c55e01ff3f | ||
|
a59ce7bfa2 | ||
|
a6196267c9 | ||
|
eb664b99ba | ||
|
8414bb9a7a | ||
|
ccef7c322f | ||
|
120327866f | ||
|
bc5c2d4eb4 | ||
|
d5ff8f6117 | ||
|
ad0d6f6337 | ||
|
873de13b3d | ||
|
56de8e5cc4 | ||
|
73c82862cf | ||
|
75573a3ed1 | ||
|
1166d93805 | ||
|
ac112a32c9 | ||
|
cee45c1221 | ||
|
fb56b5388e | ||
|
ed42cefeee | ||
|
9052947a71 | ||
|
53e0af18fb | ||
|
c5f59fad62 | ||
|
b089a4ea80 | ||
|
555bba7698 | ||
|
294901fbe9 | ||
|
ec576bf9f9 | ||
|
9273e3775b | ||
|
ce5cedb466 | ||
|
b184b01600 | ||
|
81b4078871 | ||
|
d067c8f80b | ||
|
8975b4b3f6 | ||
|
20da03f8c6 | ||
|
ef26677b67 | ||
|
91925b1826 | ||
|
1f33ad037d | ||
|
fef60e335e | ||
|
195c78846f | ||
|
0f9c956c04 | ||
|
7258a82875 | ||
|
c1f696c32a | ||
|
f2b63d9c67 | ||
|
7896a7783b | ||
|
621771e1ee | ||
|
2b032e8606 | ||
|
5e1b724697 | ||
|
e6db61c2f0 | ||
|
c2e198311c | ||
|
d874626662 | ||
|
eead72333e | ||
|
719c212009 | ||
|
65030e1c37 | ||
|
3f88b63920 | ||
|
70f1c71a9f | ||
|
816df5ad47 | ||
|
f7b1602adf | ||
|
7e88eea532 | ||
|
d1cdfd3b72 | ||
|
d6a03d48f5 | ||
|
d453b42b1a | ||
|
7c19b961e2 | ||
|
147b113b62 | ||
|
d924702825 | ||
|
e8784ba383 | ||
|
e3a454d1a6 | ||
|
392dc8b0db | ||
|
2f62426f09 | ||
|
58fda40389 | ||
|
6a73699a38 | ||
|
3bd6456fbe | ||
|
608be4e050 | ||
|
10f590324b | ||
|
cb2d9e4bec | ||
|
9e3ee28744 | ||
|
472dcebf2c | ||
|
39f0f748bf | ||
|
9efe59a984 | ||
|
fcb02af782 | ||
|
9f30f53c6b | ||
|
2f18ae00c5 | ||
|
27a339fa12 | ||
|
3aeef1afd4 | ||
|
a45ee8f4ac | ||
|
31b62d7dca | ||
|
22f81475db | ||
|
cc7cf73d59 | ||
|
9682e60a25 | ||
|
2c2e68123a | ||
|
fcec7d45cb | ||
|
7c8f502e7e | ||
|
dc17c47634 | ||
|
3d927c2f44 | ||
|
0ae61410d2 | ||
|
2455589f61 | ||
|
c6afae0da5 | ||
|
3155f02be6 | ||
|
4fa0e860ad | ||
|
8c122aa372 | ||
|
5a0bf9fee9 | ||
|
dc794918ed | ||
|
02b15dbc4a | ||
|
ed316b1ce3 | ||
|
d7858f16c1 | ||
|
291deb12ad | ||
|
3e110681c9 | ||
|
65fbfa2097 | ||
|
16ebf9da4c | ||
|
2c76381fcd | ||
|
90683223dd | ||
|
de79171815 | ||
|
1554c5700e | ||
|
5cf257b251 | ||
|
04883e14f6 | ||
|
0a649c184f | ||
|
0e66c899ce | ||
|
e7d236f939 | ||
|
fae4d03473 | ||
|
fd8b9fb028 | ||
|
97bd3e7320 | ||
|
dfca2f88d3 | ||
|
8cad93de37 | ||
|
bdf1813b3a | ||
|
45b6c93f5f | ||
|
e5b8dd7f2d | ||
|
a1c8b8092b | ||
|
109ca2406d | ||
|
3a689112fd | ||
|
40e0cd0f03 | ||
|
bf4d3df906 | ||
|
0e30c49e3f | ||
|
e61a01f7bb | ||
|
f8640cf2cd | ||
|
4bcfeb6e33 | ||
|
a5d4ca0f6d | ||
|
85faecb2fd | ||
|
991fc54994 | ||
|
2de891dc32 | ||
|
9865cb7f55 | ||
|
f97252b93a | ||
|
6124531479 | ||
|
b8549d323c | ||
|
01adece673 | ||
|
0220934e4c | ||
|
ca09693efa | ||
|
e96d7483b3 | ||
|
f2c4f018de | ||
|
237c7dd169 | ||
|
b781b8d77d | ||
|
60b7d1c8a1 | ||
|
8161222b33 | ||
|
1000c4466f | ||
|
60717b074e | ||
|
c3fba97b4c | ||
|
d93f35701f | ||
|
702b60ce66 | ||
|
f8ce597918 | ||
|
22e0a944c8 | ||
|
3a134ef009 | ||
|
96e8cb66b6 | ||
|
615288c151 | ||
|
6153bcc6ad | ||
|
e87edcc77a | ||
|
a21c3e8e2d | ||
|
d7576f67e8 | ||
|
138de643a2 | ||
|
f30e54d177 | ||
|
41b5cb06d3 | ||
|
4ac72d7d08 | ||
|
06ac4980ba | ||
|
ccbfa20bb9 | ||
|
58cd754e07 | ||
|
d1263e583b | ||
|
67c911c37f | ||
|
288e3c3e3e | ||
|
a84378c6c2 | ||
|
b6073408f4 | ||
|
b2d91ac5de | ||
|
8bf34e09f4 | ||
|
be914f2c15 | ||
|
8bb670521d | ||
|
225b3c1494 | ||
|
4bf94e0757 | ||
|
3b21d1d81e | ||
|
5ec1588110 | ||
|
71387be72e | ||
|
98171c9f49 | ||
|
a6c999dea0 | ||
|
bf15b1d302 | ||
|
f422fabab4 | ||
|
01b130ec59 | ||
|
48a1797e72 | ||
|
b34d24735a | ||
|
fe38b36c26 | ||
|
03fca8d91e | ||
|
9f9980e338 | ||
|
19900b004b | ||
|
a8ff0a8913 | ||
|
45861456f1 | ||
|
44b335e7e3 | ||
|
de23bbace2 | ||
|
edff9ae322 | ||
|
3c2766448d | ||
|
786c8b6cfe | ||
|
e8de6a3a67 | ||
|
3c320c4c83 | ||
|
3b83f967e4 | ||
|
5e96b8ef7c | ||
|
5df0e82c37 | ||
|
fd57b21aff | ||
|
6087183a0c | ||
|
01b7c4200e | ||
|
7171286c3c | ||
|
2d58239b74 | ||
|
6b52f62531 | ||
|
1001d9c04e | ||
|
d220d41182 | ||
|
c3a8972550 | ||
|
263b603188 | ||
|
584b722e7e | ||
|
05edfd0e82 | ||
|
16249c02a5 | ||
|
e8ff36d1f3 | ||
|
ed443c6153 | ||
|
f4a84765cd | ||
|
106de3530d | ||
|
119c3f6f46 | ||
|
d2c1c7507c | ||
|
efdb3d1f40 | ||
|
8095db6715 | ||
|
ce2e161b08 | ||
|
9dbc32b85f | ||
|
66226abb48 | ||
|
34bef2f2ca | ||
|
6ef93452f5 | ||
|
fdd4ca6837 | ||
|
9655362f23 | ||
|
130c9fad22 | ||
|
3de0b601bf | ||
|
91560ae4e9 | ||
|
fd6135aebb | ||
|
68ea59f3ae | ||
|
e5fe5d1249 | ||
|
9a69769a7e | ||
|
63b42f3608 | ||
|
d56107e97f | ||
|
33f296e05b | ||
|
3572c62315 | ||
|
97e067a277 | ||
|
1444cddda9 | ||
|
5f56cf3128 | ||
|
5c4e83ebdc | ||
|
91f1c25fcc | ||
|
47a7a239ae | ||
|
fb9984e21f | ||
|
b2db524366 | ||
|
ab8674a5c7 | ||
|
d1c85fc3fa | ||
|
55ad45e3ee | ||
|
f6e5a8cb2a | ||
|
7a91ca9809 | ||
|
71dd04b09e | ||
|
7deabbb512 | ||
|
625a575e49 | ||
|
df4d0da221 | ||
|
6b23b7cad7 | ||
|
cea7deab91 | ||
|
c61abf6aca | ||
|
917bbc669c | ||
|
0ac4c055de | ||
|
78b55d86e9 | ||
|
aaf50fc2e6 | ||
|
6a8f4e92df | ||
|
6d267fda01 | ||
|
88943103a2 | ||
|
e557dc7208 | ||
|
3558806b0e | ||
|
f1e8cc2cf0 | ||
|
6236db1a27 | ||
|
f4b0917239 | ||
|
a5e3cd1a42 | ||
|
b3cca5dcb6 | ||
|
49465223a4 | ||
|
15f0e54cbf | ||
|
ed8f343aad | ||
|
cbd8d70431 | ||
|
9ff187c3f8 | ||
|
be473b97c4 | ||
|
9a5f865eea | ||
|
790280ace9 | ||
|
8ba207fc7f | ||
|
d66b2a1778 | ||
|
e3f2562047 | ||
|
f77118a90c | ||
|
041eb8f6cc | ||
|
733a84df75 | ||
|
acd0b50b40 | ||
|
635851807a | ||
|
89fd367297 | ||
|
60e46d485e | ||
|
5bf0c92318 | ||
|
39d493c278 | ||
|
d2ce62aa13 | ||
|
c8eb30ef27 | ||
|
c317422ed7 | ||
|
614eb81ad7 | ||
|
219c5953f1 | ||
|
5d712c73ea | ||
|
7097b7677e | ||
|
7a4cf13e0c | ||
|
4788a6182e | ||
|
e3dad7c632 | ||
|
1b4156646e | ||
|
31ad75d01b | ||
|
aa2eb29274 | ||
|
22eb4f9cb9 | ||
|
3acc8e7479 | ||
|
2650441013 | ||
|
71697df2b6 | ||
|
acd55b9601 | ||
|
0907de8662 | ||
|
15eb9605a8 | ||
|
6d5cb866db | ||
|
768490089e | ||
|
4d66fab360 | ||
|
bd6bc283b6 | ||
|
3120a0ba83 | ||
|
b2199d5464 | ||
|
84bac8356a | ||
|
2819166539 | ||
|
8fa18ca7c7 | ||
|
63290a265c | ||
|
b854e17995 | ||
|
5dec9d88f6 | ||
|
3d0a85ee78 | ||
|
ac3cdf487f | ||
|
a47e92f2bc | ||
|
fc15ddfa91 | ||
|
d546ef941f | ||
|
b4bbe3d7b5 | ||
|
5561d4eaeb | ||
|
0f6dab394a | ||
|
adc8c1aa38 | ||
|
3a82f500d4 | ||
|
7d1d4831a8 | ||
|
43539f2dbf | ||
|
df6830110d | ||
|
1a98e882dc | ||
|
c943d84036 | ||
|
d07a6704d5 | ||
|
8cfcd5904c | ||
|
fb8846bb45 | ||
|
0d0733dd94 | ||
|
1a524a5a50 | ||
|
1a2288cccf | ||
|
8df27d4c3f | ||
|
0688deca6b | ||
|
01a4443b6c | ||
|
e008b054cb | ||
|
a67d58948d | ||
|
84c051d097 | ||
|
b918abfd54 | ||
|
7f41b7cd93 | ||
|
4759b4fe2e | ||
|
e2c8e69d12 | ||
|
1cf213dad8 | ||
|
aeb81e547b | ||
|
479f7703a2 | ||
|
c7dc396b6d | ||
|
917e8e155c | ||
|
80e3030811 | ||
|
a97e3d827d | ||
|
029014d9d6 | ||
|
e4c2922536 | ||
|
7133ae6aaa | ||
|
2f7f0ff3a1 | ||
|
df853bf61e | ||
|
f0ac753f9b | ||
|
4d56a975e6 | ||
|
d56c53c848 | ||
|
f83b16320d | ||
|
ac10e27f08 | ||
|
34df7a6072 | ||
|
e2cddf1005 | ||
|
ced423748e | ||
|
77fb02729e | ||
|
fef39b9fbe | ||
|
02810105fb | ||
|
baad92515b | ||
|
ab86ddcf02 | ||
|
bf8eddb13b | ||
|
e5eaf7a3fe | ||
|
50f32a3aa5 | ||
|
eb878710c1 | ||
|
311980e0e4 | ||
|
df73170e5a | ||
|
a12c6b5f35 | ||
|
522646c64d | ||
|
4791093e48 | ||
|
599a455150 | ||
|
2deef16ebe | ||
|
989b7be99b | ||
|
cd473e1395 | ||
|
e246ebfb2e | ||
|
8546ae56da | ||
|
9e227b0192 | ||
|
ba7737e9f8 | ||
|
98aa3d51ed | ||
|
e7cfb5492e | ||
|
9ed136dc3a | ||
|
9217216723 | ||
|
936c408a58 | ||
|
54427eac9a | ||
|
e809488cc0 | ||
|
ed26c57d99 | ||
|
2a49811f6e | ||
|
c95acd2568 | ||
|
6a4e0cf667 | ||
|
04f4dd8a22 | ||
|
f33d829ce9 | ||
|
578671ea94 | ||
|
d10300c330 | ||
|
ccc13cc9e1 | ||
|
020b2c05c8 | ||
|
4c37c17df1 | ||
|
8f67acadd8 | ||
|
ca13c4c1a6 | ||
|
d0c646c721 | ||
|
8a055675af |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ESPHome Dev",
|
||||
"image": "esphome/esphome-lint:dev",
|
||||
"image": "ghcr.io/esphome/esphome-lint:dev",
|
||||
"postCreateCommand": [
|
||||
"script/devcontainer-post-create"
|
||||
],
|
||||
|
@ -25,10 +25,9 @@ indent_size = 2
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
quote_type = single
|
||||
quote_type = double
|
||||
|
||||
# JSON
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1,3 +1,4 @@
|
||||
---
|
||||
# These are supported funding model platforms
|
||||
|
||||
custom: https://www.nabucasa.com
|
||||
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,3 +1,4 @@
|
||||
---
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Issue Tracker
|
||||
@ -5,7 +6,10 @@ contact_links:
|
||||
about: Please create bug reports in the dedicated issue tracker.
|
||||
- name: Feature Request Tracker
|
||||
url: https://github.com/esphome/feature-requests
|
||||
about: Please create feature requests in the dedicated feature request tracker.
|
||||
about: |
|
||||
Please create feature requests in the dedicated feature request tracker.
|
||||
- name: Frequently Asked Question
|
||||
url: https://esphome.io/guides/faq.html
|
||||
about: Please view the FAQ for common questions and what to include in a bug report.
|
||||
about: |
|
||||
Please view the FAQ for common questions and what
|
||||
to include in a bug report.
|
||||
|
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,6 +1,6 @@
|
||||
# What does this implement/fix?
|
||||
|
||||
Quick description and explanation of changes
|
||||
<!-- Quick description and explanation of changes -->
|
||||
|
||||
## Types of changes
|
||||
|
||||
@ -18,6 +18,7 @@ Quick description and explanation of changes
|
||||
- [ ] ESP32
|
||||
- [ ] ESP32 IDF
|
||||
- [ ] ESP8266
|
||||
- [ ] RP2040
|
||||
|
||||
## Example entry for `config.yaml`:
|
||||
<!--
|
||||
|
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
@ -1,13 +1,14 @@
|
||||
---
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: daily
|
||||
ignore:
|
||||
# Hypotehsis is only used for testing and is updated quite often
|
||||
- dependency-name: hypothesis
|
||||
- package-ecosystem: "github-actions"
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
57
.github/workflows/ci-docker.yml
vendored
57
.github/workflows/ci-docker.yml
vendored
@ -1,21 +1,23 @@
|
||||
---
|
||||
name: CI for docker images
|
||||
|
||||
# Only run when docker paths change
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, release]
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
- 'requirements*.txt'
|
||||
- 'platformio.ini'
|
||||
- "docker/**"
|
||||
- ".github/workflows/**"
|
||||
- "requirements*.txt"
|
||||
- "platformio.ini"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'docker/**'
|
||||
- '.github/workflows/**'
|
||||
- 'requirements*.txt'
|
||||
- 'platformio.ini'
|
||||
- "docker/**"
|
||||
- ".github/workflows/**"
|
||||
- "requirements*.txt"
|
||||
- "platformio.ini"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@ -26,28 +28,29 @@ jobs:
|
||||
name: Build docker containers
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=check" >> $GITHUB_ENV
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=check" >> $GITHUB_ENV
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
|
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
@ -1,5 +1,7 @@
|
||||
---
|
||||
name: CI
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, release]
|
||||
@ -10,6 +12,7 @@ permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
# yamllint disable-line rule:line-length
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
@ -45,6 +48,10 @@ jobs:
|
||||
file: tests/test5.yaml
|
||||
name: Test tests/test5.yaml
|
||||
pio_cache_key: test5
|
||||
- id: test
|
||||
file: tests/test6.yaml
|
||||
name: Test tests/test6.yaml
|
||||
pio_cache_key: test6
|
||||
- id: pytest
|
||||
name: Run pytest
|
||||
- id: clang-format
|
||||
@ -73,6 +80,8 @@ jobs:
|
||||
name: Run script/clang-tidy for ESP32 IDF
|
||||
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
||||
pio_cache_key: tidyesp32-idf
|
||||
- id: yamllint
|
||||
name: Run yamllint
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -80,17 +89,19 @@ jobs:
|
||||
uses: actions/setup-python@v4
|
||||
id: python
|
||||
with:
|
||||
python-version: '3.8'
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Cache virtualenv
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .venv
|
||||
# yamllint disable-line rule:line-length
|
||||
key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
|
||||
restore-keys: |
|
||||
venv-${{ steps.python.outputs.python-version }}-
|
||||
|
||||
- name: Set up virtualenv
|
||||
# yamllint disable rule:line-length
|
||||
run: |
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
@ -99,12 +110,14 @@ jobs:
|
||||
pip install -e .
|
||||
echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
|
||||
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
|
||||
# yamllint enable rule:line-length
|
||||
|
||||
# Use per check platformio cache because checks use different parts
|
||||
- name: Cache platformio
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.platformio
|
||||
# yamllint disable-line rule:line-length
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
if: matrix.id == 'test' || matrix.id == 'clang-tidy'
|
||||
|
||||
@ -131,7 +144,7 @@ jobs:
|
||||
if: matrix.id == 'ci-custom'
|
||||
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
run: script/lint-python -a
|
||||
if: matrix.id == 'lint-python'
|
||||
|
||||
- run: esphome compile ${{ matrix.file }}
|
||||
@ -145,8 +158,9 @@ jobs:
|
||||
pytest -vv --tb=native tests
|
||||
if: matrix.id == 'pytest'
|
||||
|
||||
# Also run git-diff-index so that the step is marked as failed on formatting errors,
|
||||
# since clang-format doesn't do anything but change files if -i is passed.
|
||||
# Also run git-diff-index so that the step is marked as failed on
|
||||
# formatting errors, since clang-format doesn't do anything but
|
||||
# change files if -i is passed.
|
||||
- name: Run clang-format
|
||||
run: |
|
||||
script/clang-format -i
|
||||
@ -161,6 +175,11 @@ jobs:
|
||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
||||
|
||||
- name: Run yamllint
|
||||
if: matrix.id == 'yamllint'
|
||||
uses: frenck/action-yamllint@v1.3.1
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format')
|
||||
# yamllint disable-line rule:line-length
|
||||
if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python')
|
||||
|
6
.github/workflows/lock.yml
vendored
6
.github/workflows/lock.yml
vendored
@ -1,8 +1,10 @@
|
||||
---
|
||||
name: Lock
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 0 * * *'
|
||||
- cron: "30 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -16,7 +18,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3
|
||||
- uses: dessant/lock-threads@v4
|
||||
with:
|
||||
pr-inactive-days: "1"
|
||||
pr-lock-reason: ""
|
||||
|
106
.github/workflows/release.yml
vendored
106
.github/workflows/release.yml
vendored
@ -1,5 +1,7 @@
|
||||
---
|
||||
name: Publish Release
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
@ -20,6 +22,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
run: |
|
||||
if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
@ -28,7 +31,8 @@ jobs:
|
||||
today="$(date --utc '+%Y%m%d')"
|
||||
TAG="${TAG}${today}"
|
||||
fi
|
||||
echo "::set-output name=tag::${TAG}"
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
# yamllint enable rule:line-length
|
||||
|
||||
deploy-pypi:
|
||||
name: Build and publish to PyPi
|
||||
@ -39,7 +43,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
python-version: "3.x"
|
||||
- name: Set up python environment
|
||||
run: |
|
||||
script/setup
|
||||
@ -65,37 +69,37 @@ jobs:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build \
|
||||
--push
|
||||
- name: Build and push
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build \
|
||||
--push
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
@ -108,34 +112,34 @@ jobs:
|
||||
matrix:
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run manifest
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
manifest
|
||||
- name: Run manifest
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
manifest
|
||||
|
||||
deploy-ha-addon-repo:
|
||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||
@ -144,6 +148,7 @@ jobs:
|
||||
steps:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
# yamllint disable rule:line-length
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
curl \
|
||||
@ -152,3 +157,4 @@ jobs:
|
||||
-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\":\"$TAG\"}}"
|
||||
# yamllint enable rule:line-length
|
||||
|
11
.github/workflows/stale.yml
vendored
11
.github/workflows/stale.yml
vendored
@ -1,8 +1,10 @@
|
||||
---
|
||||
name: Stale
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 0 * * *'
|
||||
- cron: "30 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -16,7 +18,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
- uses: actions/stale@v6
|
||||
with:
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
@ -31,11 +33,12 @@ jobs:
|
||||
and will be closed if no further activity occurs within 7 days.
|
||||
Thank you for your contributions.
|
||||
|
||||
# Use stale to automatically close issues with a reference to the issue tracker
|
||||
# Use stale to automatically close issues with a
|
||||
# reference to the issue tracker
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
- uses: actions/stale@v6
|
||||
with:
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
|
@ -1,6 +0,0 @@
|
||||
ports:
|
||||
- port: 6052
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
|
||||
command: python -m esphome dashboard config
|
@ -1,16 +1,17 @@
|
||||
---
|
||||
# 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.6.0
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 4.0.1
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
@ -26,7 +27,7 @@ repos:
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.34.0
|
||||
rev: v3.3.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
args: [--py39-plus]
|
||||
|
30
CODEOWNERS
30
CODEOWNERS
@ -13,6 +13,7 @@ esphome/core/* @esphome/core
|
||||
# Integrations
|
||||
esphome/components/ac_dimmer/* @glmnet
|
||||
esphome/components/adc/* @esphome/core
|
||||
esphome/components/adc128s102/* @DeerMaximum
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/airthings_ble/* @jeromelaban
|
||||
esphome/components/airthings_wave_mini/* @ncareau
|
||||
@ -29,11 +30,15 @@ esphome/components/b_parasite/* @rbaron
|
||||
esphome/components/ballu/* @bazuchan
|
||||
esphome/components/bang_bang/* @OttoWinter
|
||||
esphome/components/bedjet/* @jhansche
|
||||
esphome/components/bedjet/climate/* @jhansche
|
||||
esphome/components/bedjet/fan/* @jhansche
|
||||
esphome/components/bh1750/* @OttoWinter
|
||||
esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/bl0939/* @ziceva
|
||||
esphome/components/bl0940/* @tobias-
|
||||
esphome/components/bl0942/* @dbuezas
|
||||
esphome/components/ble_client/* @buxtronix
|
||||
esphome/components/bluetooth_proxy/* @jesserockz
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/bmp3xx/* @martgras
|
||||
esphome/components/button/* @esphome/core
|
||||
@ -53,26 +58,34 @@ esphome/components/cse7761/* @berfenger
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/current_based/* @djwmarcx
|
||||
esphome/components/dac7678/* @NickB1
|
||||
esphome/components/daikin_brc/* @hagak
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/delonghi/* @grob6000
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/display_menu_base/* @numo68
|
||||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_client/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_can/* @Sympatron
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/ethernet_info/* @gtjadsonsantos
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/ezo_pmp/* @carlos-sarmiento
|
||||
esphome/components/factory_reset/* @anatoly-savchenkov
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/feedback/* @ianchi
|
||||
esphome/components/fingerprint_grow/* @OnFreund @loongyh
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
@ -98,6 +111,8 @@ esphome/components/integration/* @OttoWinter
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
@ -119,6 +134,7 @@ esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp3204/* @rsumner
|
||||
esphome/components/mcp4728/* @berfenger
|
||||
esphome/components/mcp47a1/* @jesserockz
|
||||
esphome/components/mcp9600/* @MrEditor97
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
@ -137,6 +153,7 @@ esphome/components/modbus_controller/switch/* @martgras
|
||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||
esphome/components/mopeka_ble/* @spbrogan
|
||||
esphome/components/mopeka_pro_check/* @spbrogan
|
||||
esphome/components/mpl3115a2/* @kbickar
|
||||
esphome/components/mpu6886/* @fabaff
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @senexcrenshaw
|
||||
@ -170,6 +187,8 @@ esphome/components/rc522_spi/* @glmnet
|
||||
esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rgbct/* @jesserockz
|
||||
esphome/components/rp2040/* @jesserockz
|
||||
esphome/components/rp2040_pwm/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
esphome/components/scd4x/* @martgras @sjtrny
|
||||
@ -189,9 +208,12 @@ esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/sml/* @alengwenus
|
||||
esphome/components/smt100/* @piechade
|
||||
esphome/components/sn74hc165/* @jesserockz
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/sprinkler/* @kbx81
|
||||
esphome/components/sps30/* @martgras
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
@ -217,7 +239,9 @@ esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/tlc5947/* @rnauber
|
||||
esphome/components/tm1621/* @Philippe12
|
||||
esphome/components/tm1637/* @glmnet
|
||||
esphome/components/tm1638/* @skykingjwc
|
||||
esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tmp117/* @Azimath
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
@ -232,13 +256,17 @@ esphome/components/tuya/sensor/* @jesserockz
|
||||
esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/tuya/text_sensor/* @dentra
|
||||
esphome/components/uart/* @esphome/core
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/whynter/* @aeonsablaze
|
||||
esphome/components/wl_134/* @hobbypunk90
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc303/* @drug123
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||
esphome/components/xpt2046/* @numo68
|
||||
esphome/components/xpt2046/* @nielsnl68 @numo68
|
||||
|
@ -6,17 +6,11 @@
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
# https://github.com/hassio-addons/addon-debian-base/releases
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7
|
||||
FROM ghcr.io/hassio-addons/debian-base:6.1.3 AS base-hassio
|
||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-armv7
|
||||
FROM debian:bullseye-20221024-slim AS base-docker
|
||||
|
||||
# Use TARGETARCH/TARGETVARIANT defined by docker
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base
|
||||
FROM base-${BASEIMGTYPE} AS base
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
@ -29,8 +23,8 @@ RUN \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
curl=7.74.0-1.3+deb11u1 \
|
||||
openssh-client=1:8.4p1-5 \
|
||||
curl=7.74.0-1.3+deb11u3 \
|
||||
openssh-client=1:8.4p1-5+deb11u1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
@ -46,7 +40,7 @@ RUN \
|
||||
# Ubuntu python3-pip is missing wheel
|
||||
pip3 install --no-cache-dir \
|
||||
wheel==0.37.1 \
|
||||
platformio==6.0.2 \
|
||||
platformio==6.1.5 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_platformio_interval 1000000 \
|
||||
@ -94,7 +88,7 @@ RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
nginx-light=1.18.0-6.1 \
|
||||
nginx-light=1.18.0-6.1+deb11u3 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
@ -134,7 +128,7 @@ RUN \
|
||||
clang-tidy-11=1:11.0.1-2 \
|
||||
patch=2.7.6-7 \
|
||||
software-properties-common=0.96.20.2-2.1 \
|
||||
nano=5.4-2 \
|
||||
nano=5.4-2+deb11u1 \
|
||||
build-essential=12.9 \
|
||||
python3-dev=3.9.2-3 \
|
||||
&& rm -rf \
|
||||
|
@ -88,10 +88,12 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
# detect channel from tag
|
||||
match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag)
|
||||
match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag)
|
||||
major_minor_version = None
|
||||
if match is None:
|
||||
channel = CHANNEL_DEV
|
||||
elif match.group(1) is None:
|
||||
elif match.group(2) is None:
|
||||
major_minor_version = match.group(1)
|
||||
channel = CHANNEL_RELEASE
|
||||
else:
|
||||
channel = CHANNEL_BETA
|
||||
@ -106,6 +108,11 @@ def main():
|
||||
tags_to_push.append("beta")
|
||||
tags_to_push.append("latest")
|
||||
|
||||
# Compatibility with HA tags
|
||||
if major_minor_version:
|
||||
tags_to_push.append("stable")
|
||||
tags_to_push.append(major_minor_version)
|
||||
|
||||
if args.command == "build":
|
||||
# 1. pull cache image
|
||||
params = DockerParams.for_type_arch(args.build_type, args.arch)
|
||||
|
0
docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh
Normal file → Executable file
0
docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh
Normal file → Executable file
@ -3,7 +3,13 @@
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when ESPHome fails
|
||||
# ==============================================================================
|
||||
if -n { s6-test $# -ne 0 }
|
||||
if -n { s6-test ${1} -eq 256 }
|
||||
|
||||
s6-svscanctl -t /var/run/s6/services
|
||||
declare APP_EXIT_CODE=${1}
|
||||
|
||||
if [[ "${APP_EXIT_CODE}" -ne 0 ]] && [[ "${APP_EXIT_CODE}" -ne 256 ]]; then
|
||||
bashio::log.warning "Halt add-on with exit code ${APP_EXIT_CODE}"
|
||||
echo "${APP_EXIT_CODE}" > /run/s6-linux-init-container-results/exitcode
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
||||
|
||||
bashio::log.info "Service restart after closing"
|
||||
|
@ -22,6 +22,14 @@ if bashio::config.has_value 'relative_url'; then
|
||||
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'default_compile_process_limit'; then
|
||||
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
|
||||
else
|
||||
if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
|
||||
export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1;
|
||||
fi
|
||||
fi
|
||||
|
||||
pio_cache_base=/data/cache/platformio
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
|
@ -3,7 +3,13 @@
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when NGINX fails
|
||||
# ==============================================================================
|
||||
if -n { s6-test $# -ne 0 }
|
||||
if -n { s6-test ${1} -eq 256 }
|
||||
|
||||
s6-svscanctl -t /var/run/s6/services
|
||||
declare APP_EXIT_CODE=${1}
|
||||
|
||||
if [[ "${APP_EXIT_CODE}" -ne 0 ]] && [[ "${APP_EXIT_CODE}" -ne 256 ]]; then
|
||||
bashio::log.warning "Halt add-on with exit code ${APP_EXIT_CODE}"
|
||||
echo "${APP_EXIT_CODE}" > /run/s6-linux-init-container-results/exitcode
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
||||
|
||||
bashio::log.info "Service restart after closing"
|
||||
|
@ -4,6 +4,7 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from esphome import const, writer, yaml_util
|
||||
@ -22,6 +23,9 @@ from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
CONF_SUBSTITUTIONS,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
SECRETS_FILES,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
@ -101,11 +105,11 @@ def run_miniterm(config, port):
|
||||
|
||||
if CONF_LOGGER not in config:
|
||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||
return
|
||||
return 1
|
||||
baud_rate = config["logger"][CONF_BAUD_RATE]
|
||||
if baud_rate == 0:
|
||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
||||
return
|
||||
return 1
|
||||
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||
|
||||
backtrace_state = False
|
||||
@ -119,25 +123,34 @@ def run_miniterm(config, port):
|
||||
ser.dtr = False
|
||||
ser.rts = False
|
||||
|
||||
with ser:
|
||||
while True:
|
||||
try:
|
||||
raw = ser.readline()
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return
|
||||
line = (
|
||||
raw.replace(b"\r", b"")
|
||||
.replace(b"\n", b"")
|
||||
.decode("utf8", "backslashreplace")
|
||||
)
|
||||
time = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
message = time + line
|
||||
safe_print(message)
|
||||
tries = 0
|
||||
while tries < 5:
|
||||
try:
|
||||
with ser:
|
||||
while True:
|
||||
try:
|
||||
raw = ser.readline()
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Serial port closed!")
|
||||
return 0
|
||||
line = (
|
||||
raw.replace(b"\r", b"")
|
||||
.replace(b"\n", b"")
|
||||
.decode("utf8", "backslashreplace")
|
||||
)
|
||||
time_str = datetime.now().time().strftime("[%H:%M:%S]")
|
||||
message = time_str + line
|
||||
safe_print(message)
|
||||
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
backtrace_state = platformio_api.process_stacktrace(
|
||||
config, line, backtrace_state=backtrace_state
|
||||
)
|
||||
except serial.SerialException:
|
||||
tries += 1
|
||||
time.sleep(1)
|
||||
if tries >= 5:
|
||||
_LOGGER.error("Could not connect to serial port %s", port)
|
||||
return 1
|
||||
|
||||
|
||||
def wrap_to_code(name, comp):
|
||||
@ -241,8 +254,7 @@ def upload_using_esptool(config, port):
|
||||
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
||||
import esptool
|
||||
|
||||
# pylint: disable=protected-access
|
||||
return run_external_command(esptool._main, *cmd)
|
||||
return run_external_command(esptool.main, *cmd) # pylint: disable=no-member
|
||||
|
||||
return run_external_process(*cmd)
|
||||
|
||||
@ -258,9 +270,21 @@ def upload_using_esptool(config, port):
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
# if upload is to a serial port use platformio, otherwise assume ota
|
||||
if get_port_type(host) == "SERIAL":
|
||||
return upload_using_esptool(config, host)
|
||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||
return upload_using_esptool(config, host)
|
||||
|
||||
if CORE.target_platform in (PLATFORM_RP2040):
|
||||
from esphome import platformio_api
|
||||
|
||||
upload_args = ["-t", "upload"]
|
||||
if args.device is not None:
|
||||
upload_args += ["--upload-port", args.device]
|
||||
return platformio_api.run_platformio_cli_run(
|
||||
config, CORE.verbose, *upload_args
|
||||
)
|
||||
|
||||
return 1 # Unknown target platform
|
||||
|
||||
from esphome import espota2
|
||||
|
||||
@ -273,6 +297,8 @@ def upload_program(config, args, host):
|
||||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
password = ota_conf.get(CONF_PASSWORD, "")
|
||||
if getattr(args, "file", None) is not None:
|
||||
return espota2.run_ota(host, remote_port, password, args.file)
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
|
||||
|
||||
@ -280,8 +306,7 @@ def show_logs(config, args, port):
|
||||
if "logger" not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
if get_port_type(port) == "SERIAL":
|
||||
run_miniterm(config, port)
|
||||
return 0
|
||||
return run_miniterm(config, port)
|
||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||
from esphome.components.api.client import run_logs
|
||||
|
||||
@ -663,6 +688,10 @@ def parse_args(argv):
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--file",
|
||||
help="Manually specify the binary file to upload.",
|
||||
)
|
||||
|
||||
parser_logs = subparsers.add_parser(
|
||||
"logs",
|
||||
|
@ -22,6 +22,7 @@ from esphome.cpp_generator import ( # noqa
|
||||
static_const_array,
|
||||
statement,
|
||||
variable,
|
||||
with_local_variable,
|
||||
new_variable,
|
||||
Pvariable,
|
||||
new_Pvariable,
|
||||
|
@ -121,11 +121,8 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
|
||||
// also take into account min_power
|
||||
auto min_us = this->cycle_time_us * this->min_power / 1000;
|
||||
// calculate required value to provide a true RMS voltage output
|
||||
this->enable_time_us =
|
||||
std::max((uint32_t) 1, (uint32_t)((65535 - (acos(1 - (2 * this->value / 65535.0)) / 3.14159 * 65535)) *
|
||||
(this->cycle_time_us - min_us)) /
|
||||
65535);
|
||||
this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
|
||||
|
||||
if (this->method == DIM_METHOD_LEADING_PULSE) {
|
||||
// Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
|
||||
// this is for brightness near 99%
|
||||
@ -206,6 +203,7 @@ void AcDimmer::setup() {
|
||||
#endif
|
||||
}
|
||||
void AcDimmer::write_state(float state) {
|
||||
state = std::acos(1 - (2 * state)) / 3.14159; // RMS power compensation
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
if (new_value != 0 && this->store_.value == 0)
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
|
@ -11,6 +11,10 @@ ADC_MODE(ADC_VCC)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#include <hardware/adc.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
@ -32,9 +36,13 @@ static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 b
|
||||
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit)
|
||||
#endif
|
||||
|
||||
void ADCSensor::setup() {
|
||||
#ifdef USE_RP2040
|
||||
extern "C"
|
||||
#endif
|
||||
void
|
||||
ADCSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||
#ifndef USE_ADC_SENSOR_VCC
|
||||
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
|
||||
pin_->setup();
|
||||
#endif
|
||||
|
||||
@ -62,11 +70,17 @@ void ADCSensor::setup() {
|
||||
}
|
||||
}
|
||||
|
||||
// adc_gpio_init doesn't exist on ESP32-S2, ESP32-C3 or ESP32-H2
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2) && !defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
adc_init();
|
||||
initialized = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
|
||||
}
|
||||
|
||||
void ADCSensor::dump_config() {
|
||||
@ -102,6 +116,13 @@ void ADCSensor::dump_config() {
|
||||
}
|
||||
}
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_RP2040
|
||||
if (this->is_temperature_) {
|
||||
ESP_LOGCONFIG(TAG, " Pin: Temperature");
|
||||
} else {
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
}
|
||||
#endif
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
@ -179,6 +200,29 @@ float ADCSensor::sample() {
|
||||
}
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
float ADCSensor::sample() {
|
||||
if (this->is_temperature_) {
|
||||
adc_set_temp_sensor_enabled(true);
|
||||
delay(1);
|
||||
adc_select_input(4);
|
||||
} else {
|
||||
uint8_t pin = this->pin_->get_pin();
|
||||
adc_gpio_init(pin);
|
||||
adc_select_input(pin - 26);
|
||||
}
|
||||
|
||||
int raw = adc_read();
|
||||
if (this->is_temperature_) {
|
||||
adc_set_temp_sensor_enabled(false);
|
||||
}
|
||||
if (output_raw_) {
|
||||
return raw;
|
||||
}
|
||||
return raw * 3.3f / 4096.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||
#endif
|
||||
|
@ -38,10 +38,18 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
std::string unique_id() override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
void set_is_temperature() { is_temperature_ = true; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *pin_;
|
||||
bool output_raw_{false};
|
||||
|
||||
#ifdef USE_RP2040
|
||||
bool is_temperature_{false};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
|
||||
adc1_channel_t channel_{};
|
||||
|
@ -94,6 +94,9 @@ def validate_adc_pin(value):
|
||||
if str(value).upper() == "VCC":
|
||||
return cv.only_on_esp8266("VCC")
|
||||
|
||||
if str(value).upper() == "TEMPERATURE":
|
||||
return cv.only_on_rp2040("TEMPERATURE")
|
||||
|
||||
if CORE.is_esp32:
|
||||
value = pins.internal_gpio_input_pin_number(value)
|
||||
variant = get_esp32_variant()
|
||||
@ -117,6 +120,12 @@ def validate_adc_pin(value):
|
||||
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
|
||||
)(value)
|
||||
|
||||
if CORE.is_rp2040:
|
||||
value = pins.internal_gpio_input_pin_number(value)
|
||||
if value not in (26, 27, 28, 29):
|
||||
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@ -160,6 +169,8 @@ async def to_code(config):
|
||||
|
||||
if config[CONF_PIN] == "VCC":
|
||||
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||
elif config[CONF_PIN] == "TEMPERATURE":
|
||||
cg.add(var.set_is_temperature())
|
||||
else:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
|
23
esphome/components/adc128s102/__init__.py
Normal file
23
esphome/components/adc128s102/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import spi
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
MULTI_CONF = True
|
||||
CODEOWNERS = ["@DeerMaximum"]
|
||||
|
||||
adc128s102_ns = cg.esphome_ns.namespace("adc128s102")
|
||||
ADC128S102 = adc128s102_ns.class_("ADC128S102", cg.Component, spi.SPIDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADC128S102),
|
||||
}
|
||||
).extend(spi.spi_device_schema(cs_pin_required=True))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
35
esphome/components/adc128s102/adc128s102.cpp
Normal file
35
esphome/components/adc128s102/adc128s102.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include "adc128s102.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc128s102 {
|
||||
|
||||
static const char *const TAG = "adc128s102";
|
||||
|
||||
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
void ADC128S102::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up adc128s102");
|
||||
this->spi_setup();
|
||||
}
|
||||
|
||||
void ADC128S102::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADC128S102:");
|
||||
LOG_PIN(" CS Pin:", this->cs_);
|
||||
}
|
||||
|
||||
uint16_t ADC128S102::read_data(uint8_t channel) {
|
||||
uint8_t control = channel << 3;
|
||||
|
||||
this->enable();
|
||||
uint8_t adc_primary_byte = this->transfer_byte(control);
|
||||
uint8_t adc_secondary_byte = this->transfer_byte(0x00);
|
||||
this->disable();
|
||||
|
||||
uint16_t digital_value = adc_primary_byte << 8 | adc_secondary_byte;
|
||||
|
||||
return digital_value;
|
||||
}
|
||||
|
||||
} // namespace adc128s102
|
||||
} // namespace esphome
|
23
esphome/components/adc128s102/adc128s102.h
Normal file
23
esphome/components/adc128s102/adc128s102.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc128s102 {
|
||||
|
||||
class ADC128S102 : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_10MHZ> {
|
||||
public:
|
||||
ADC128S102() = default;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
uint16_t read_data(uint8_t channel);
|
||||
};
|
||||
|
||||
} // namespace adc128s102
|
||||
} // namespace esphome
|
35
esphome/components/adc128s102/sensor/__init__.py
Normal file
35
esphome/components/adc128s102/sensor/__init__.py
Normal file
@ -0,0 +1,35 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import CONF_ID, CONF_CHANNEL
|
||||
|
||||
from .. import adc128s102_ns, ADC128S102
|
||||
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
DEPENDENCIES = ["adc128s102"]
|
||||
|
||||
ADC128S102Sensor = adc128s102_ns.class_(
|
||||
"ADC128S102Sensor",
|
||||
sensor.Sensor,
|
||||
cg.PollingComponent,
|
||||
voltage_sampler.VoltageSampler,
|
||||
)
|
||||
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"))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_CHANNEL],
|
||||
)
|
||||
await cg.register_parented(var, config[CONF_ADC128S102_ID])
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
24
esphome/components/adc128s102/sensor/adc128s102_sensor.cpp
Normal file
24
esphome/components/adc128s102/sensor/adc128s102_sensor.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
#include "adc128s102_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc128s102 {
|
||||
|
||||
static const char *const TAG = "adc128s102.sensor";
|
||||
|
||||
ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {}
|
||||
|
||||
float ADC128S102Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void ADC128S102Sensor::dump_config() {
|
||||
LOG_SENSOR("", "ADC128S102 Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->channel_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
float ADC128S102Sensor::sample() { return this->parent_->read_data(this->channel_); }
|
||||
void ADC128S102Sensor::update() { this->publish_state(this->sample()); }
|
||||
|
||||
} // namespace adc128s102
|
||||
} // namespace esphome
|
29
esphome/components/adc128s102/sensor/adc128s102_sensor.h
Normal file
29
esphome/components/adc128s102/sensor/adc128s102_sensor.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include "../adc128s102.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace adc128s102 {
|
||||
|
||||
class ADC128S102Sensor : public PollingComponent,
|
||||
public Parented<ADC128S102>,
|
||||
public sensor::Sensor,
|
||||
public voltage_sampler::VoltageSampler {
|
||||
public:
|
||||
ADC128S102Sensor(uint8_t channel);
|
||||
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
float sample() override;
|
||||
|
||||
protected:
|
||||
uint8_t channel_;
|
||||
};
|
||||
} // namespace adc128s102
|
||||
} // namespace esphome
|
@ -5,6 +5,8 @@
|
||||
#include "esphome/components/display/display_buffer.h"
|
||||
#include "esphome/components/light/addressable_light.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace addressable_light {
|
||||
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7953 {
|
||||
|
||||
@ -82,7 +84,7 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
return i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
InternalGPIOPin *irq_pin_ = nullptr;
|
||||
InternalGPIOPin *irq_pin_{nullptr};
|
||||
bool is_setup_{false};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_a_sensor_{nullptr};
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace ads1115 {
|
||||
|
||||
|
@ -122,8 +122,9 @@ void AHT10Component::update() {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
if (std::isnan(humidity))
|
||||
if (std::isnan(humidity)) {
|
||||
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
|
||||
}
|
||||
this->humidity_sensor_->publish_state(humidity);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
@ -18,8 +18,8 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace aht10
|
||||
|
@ -38,7 +38,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@ -88,8 +88,8 @@ void AirthingsWaveMini::update() {
|
||||
}
|
||||
|
||||
void AirthingsWaveMini::request_read_values_() {
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@ -109,8 +109,8 @@ void AirthingsWavePlus::update() {
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::request_read_values_() {
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
|
@ -4,33 +4,15 @@
|
||||
// - Arduino - AM2320: https://github.com/EngDial/AM2320/blob/master/src/AM2320.cpp
|
||||
|
||||
#include "am2320.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am2320 {
|
||||
|
||||
static const char *const TAG = "am2320";
|
||||
|
||||
// ---=== Calc CRC16 ===---
|
||||
uint16_t crc_16(uint8_t *ptr, uint8_t length) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
uint8_t i;
|
||||
//------------------------------
|
||||
while (length--) {
|
||||
crc ^= *ptr++;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if ((crc & 0x01) != 0) {
|
||||
crc >>= 1;
|
||||
crc ^= 0xA001;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void AM2320Component::update() {
|
||||
uint8_t data[8];
|
||||
data[0] = 0;
|
||||
@ -98,7 +80,7 @@ bool AM2320Component::read_data_(uint8_t *data) {
|
||||
checksum = data[7] << 8;
|
||||
checksum += data[6];
|
||||
|
||||
if (crc_16(data, 6) != checksum) {
|
||||
if (crc16(data, 6) != checksum) {
|
||||
ESP_LOGW(TAG, "AM2320 Checksum invalid!");
|
||||
return false;
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ class AM2320Component : public PollingComponent, public i2c::I2CDevice {
|
||||
bool read_data_(uint8_t *data);
|
||||
bool read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0);
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace am2320
|
||||
|
@ -76,9 +76,9 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
||||
if (this->current_sensor_ > 0) {
|
||||
if (this->illuminance_ != nullptr) {
|
||||
auto *packet = this->encoder_->get_light_level_request();
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
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);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
|
||||
status);
|
||||
@ -102,10 +102,11 @@ void Am43::update() {
|
||||
if (this->battery_ != nullptr) {
|
||||
auto *packet = this->encoder_->get_battery_level_request();
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (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);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
}
|
||||
this->current_sensor_++;
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ void Am43Component::loop() {
|
||||
if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
|
||||
auto *packet = this->encoder_->get_send_pin_request(this->pin_);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_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);
|
||||
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
|
||||
@ -54,10 +54,11 @@ void Am43Component::control(const CoverCall &call) {
|
||||
if (call.get_stop()) {
|
||||
auto *packet = this->encoder_->get_stop_request();
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (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);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
@ -66,10 +67,11 @@ void Am43Component::control(const CoverCall &call) {
|
||||
pos = 1 - pos;
|
||||
auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (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);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +94,8 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
@ -122,21 +125,24 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
if (this->decoder_->pin_ok_) {
|
||||
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
|
||||
auto *packet = this->encoder_->get_position_request();
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
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);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
|
||||
if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_) {
|
||||
ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
|
||||
}
|
||||
|
||||
if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
|
||||
if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_) {
|
||||
ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ["display"]
|
||||
MULTI_CONF = True
|
||||
|
||||
Animation_ = display.display_ns.class_("Animation")
|
||||
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
|
||||
|
||||
ANIMATION_SCHEMA = cv.Schema(
|
||||
{
|
||||
|
@ -34,17 +34,21 @@ void Anova::control(const ClimateCall &call) {
|
||||
ESP_LOGW(TAG, "Unsupported mode: %d", mode);
|
||||
return;
|
||||
}
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
}
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +69,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
@ -112,8 +117,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
}
|
||||
if (pkt != nullptr) {
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length,
|
||||
pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
|
||||
status);
|
||||
@ -137,10 +142,12 @@ void Anova::update() {
|
||||
auto *pkt = this->codec_->get_read_device_status_request();
|
||||
if (this->current_request_ == 0)
|
||||
this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
this->current_request_++;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,27 @@ AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_APDS9960_ID = "apds9960_id"
|
||||
CONF_LED_DRIVE = "led_drive"
|
||||
CONF_PROXIMITY_GAIN = "proximity_gain"
|
||||
CONF_AMBIENT_LIGHT_GAIN = "ambient_light_gain"
|
||||
CONF_GESTURE_LED_DRIVE = "gesture_led_drive"
|
||||
CONF_GESTURE_GAIN = "gesture_gain"
|
||||
CONF_GESTURE_WAIT_TIME = "gesture_wait_time"
|
||||
|
||||
DRIVE_LEVELS = {"100ma": 0, "50ma": 1, "25ma": 2, "12.5ma": 3}
|
||||
PROXIMITY_LEVELS = {"1x": 0, "2x": 1, "4x": 2, "8x": 3}
|
||||
AMBIENT_LEVELS = {"1x": 0, "4x": 1, "16x": 2, "64x": 3}
|
||||
GESTURE_LEVELS = {"1x": 0, "2x": 1, "4x": 2, "8x": 3}
|
||||
GESTURE_WAIT_TIMES = {
|
||||
"0ms": 0,
|
||||
"2.8ms": 1,
|
||||
"5.6ms": 2,
|
||||
"8.4ms": 3,
|
||||
"14ms": 4,
|
||||
"22.4ms": 5,
|
||||
"30.8ms": 6,
|
||||
"39.2ms": 7,
|
||||
}
|
||||
|
||||
apds9960_nds = cg.esphome_ns.namespace("apds9960")
|
||||
APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice)
|
||||
@ -16,6 +37,20 @@ CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(APDS9960),
|
||||
cv.Optional(CONF_LED_DRIVE, "100mA"): cv.enum(DRIVE_LEVELS, lower=True),
|
||||
cv.Optional(CONF_PROXIMITY_GAIN, "4x"): cv.enum(
|
||||
PROXIMITY_LEVELS, lower=True
|
||||
),
|
||||
cv.Optional(CONF_AMBIENT_LIGHT_GAIN, "4x"): cv.enum(
|
||||
AMBIENT_LEVELS, lower=True
|
||||
),
|
||||
cv.Optional(CONF_GESTURE_LED_DRIVE, "100mA"): cv.enum(
|
||||
DRIVE_LEVELS, lower=True
|
||||
),
|
||||
cv.Optional(CONF_GESTURE_GAIN, "4x"): cv.enum(GESTURE_LEVELS, lower=True),
|
||||
cv.Optional(CONF_GESTURE_WAIT_TIME, "2.8ms"): cv.enum(
|
||||
GESTURE_WAIT_TIMES, lower=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
@ -27,3 +62,9 @@ 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_led_drive(config[CONF_LED_DRIVE]))
|
||||
cg.add(var.set_proximity_gain(config[CONF_PROXIMITY_GAIN]))
|
||||
cg.add(var.set_ambient_gain(config[CONF_AMBIENT_LIGHT_GAIN]))
|
||||
cg.add(var.set_gesture_led_drive(config[CONF_GESTURE_LED_DRIVE]))
|
||||
cg.add(var.set_gesture_gain(config[CONF_GESTURE_GAIN]))
|
||||
cg.add(var.set_gesture_wait_time(config[CONF_GESTURE_WAIT_TIME]))
|
||||
|
@ -46,16 +46,16 @@ void APDS9960::setup() {
|
||||
uint8_t val = 0;
|
||||
APDS9960_ERROR_CHECK(this->read_byte(0x8F, &val));
|
||||
val &= 0b00111111;
|
||||
uint8_t led_drive = 0; // led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
||||
val |= (led_drive & 0b11) << 6;
|
||||
// led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
||||
val |= (this->led_drive_ & 0b11) << 6;
|
||||
|
||||
val &= 0b11110011;
|
||||
uint8_t proximity_gain = 2; // proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 4 -> 8X
|
||||
val |= (proximity_gain & 0b11) << 2;
|
||||
// proximity gain, 0 -> 1x, 1 -> 2X, 2 -> 4X, 3 -> 8X
|
||||
val |= (this->proximity_gain_ & 0b11) << 2;
|
||||
|
||||
val &= 0b11111100;
|
||||
uint8_t ambient_gain = 1; // ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x
|
||||
val |= (ambient_gain & 0b11) << 0;
|
||||
// ambient light gain, 0 -> 1x, 1 -> 4x, 2 -> 16x, 3 -> 64x
|
||||
val |= (this->ambient_gain_ & 0b11) << 0;
|
||||
APDS9960_WRITE_BYTE(0x8F, val);
|
||||
|
||||
// Pers (0x8C) -> 0x11 (2 consecutive proximity or ALS for interrupt)
|
||||
@ -75,19 +75,18 @@ void APDS9960::setup() {
|
||||
// GConf 2 (0xA3, gesture config 2) ->
|
||||
APDS9960_ERROR_CHECK(this->read_byte(0xA3, &val));
|
||||
val &= 0b10011111;
|
||||
uint8_t gesture_gain = 2; // gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x
|
||||
val |= (gesture_gain & 0b11) << 5;
|
||||
// gesture gain, 0 -> 1x, 1 -> 2x, 2 -> 4x, 3 -> 8x
|
||||
val |= (this->gesture_gain_ & 0b11) << 5;
|
||||
|
||||
val &= 0b11100111;
|
||||
uint8_t gesture_led_drive = 0; // gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
||||
val |= (gesture_led_drive & 0b11) << 3;
|
||||
// gesture led drive, 0 -> 100mA, 1 -> 50mA, 2 -> 25mA, 3 -> 12.5mA
|
||||
val |= (this->gesture_led_drive_ & 0b11) << 3;
|
||||
|
||||
val &= 0b11111000;
|
||||
// gesture wait time
|
||||
// 0 -> 0ms, 1 -> 2.8ms, 2 -> 5.6ms, 3 -> 8.4ms
|
||||
// 4 -> 14.0ms, 5 -> 22.4 ms, 6 -> 30.8ms, 7 -> 39.2 ms
|
||||
uint8_t gesture_wait_time = 1; // gesture wait time
|
||||
val |= (gesture_wait_time & 0b111) << 0;
|
||||
val |= (this->gesture_wait_time_ & 0b111) << 0;
|
||||
APDS9960_WRITE_BYTE(0xA3, val);
|
||||
|
||||
// GOffsetU (0xA4) -> 0x00 (no offset)
|
||||
|
@ -16,6 +16,13 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
|
||||
void update() override;
|
||||
void loop() override;
|
||||
|
||||
void set_led_drive(uint8_t level) { this->led_drive_ = level; }
|
||||
void set_proximity_gain(uint8_t gain) { this->proximity_gain_ = gain; }
|
||||
void set_ambient_gain(uint8_t gain) { this->ambient_gain_ = gain; }
|
||||
void set_gesture_led_drive(uint8_t level) { this->gesture_led_drive_ = level; }
|
||||
void set_gesture_gain(uint8_t gain) { this->gesture_gain_ = gain; }
|
||||
void set_gesture_wait_time(uint8_t wait_time) { this->gesture_wait_time_ = wait_time; }
|
||||
|
||||
void set_red_channel(sensor::Sensor *red_channel) { red_channel_ = red_channel; }
|
||||
void set_green_channel(sensor::Sensor *green_channel) { green_channel_ = green_channel; }
|
||||
void set_blue_channel(sensor::Sensor *blue_channel) { blue_channel_ = blue_channel; }
|
||||
@ -36,6 +43,13 @@ class APDS9960 : public PollingComponent, public i2c::I2CDevice {
|
||||
void report_gesture_(int gesture);
|
||||
void process_dataset_(int up, int down, int left, int right);
|
||||
|
||||
uint8_t led_drive_;
|
||||
uint8_t proximity_gain_;
|
||||
uint8_t ambient_gain_;
|
||||
uint8_t gesture_led_drive_;
|
||||
uint8_t gesture_gain_;
|
||||
uint8_t gesture_wait_time_;
|
||||
|
||||
sensor::Sensor *red_channel_{nullptr};
|
||||
sensor::Sensor *green_channel_{nullptr};
|
||||
sensor::Sensor *blue_channel_{nullptr};
|
||||
|
@ -43,6 +43,16 @@ service APIConnection {
|
||||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
|
||||
|
||||
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
|
||||
rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
|
||||
}
|
||||
|
||||
|
||||
@ -77,6 +87,8 @@ message HelloRequest {
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1;
|
||||
uint32 api_version_major = 2;
|
||||
uint32 api_version_minor = 3;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
@ -190,6 +202,10 @@ message DeviceInfoResponse {
|
||||
string project_version = 9;
|
||||
|
||||
uint32 webserver_port = 10;
|
||||
|
||||
uint32 bluetooth_proxy_version = 11;
|
||||
|
||||
string manufacturer = 12;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@ -473,6 +489,7 @@ enum SensorStateClass {
|
||||
STATE_CLASS_NONE = 0;
|
||||
STATE_CLASS_MEASUREMENT = 1;
|
||||
STATE_CLASS_TOTAL_INCREASING = 2;
|
||||
STATE_CLASS_TOTAL = 3;
|
||||
}
|
||||
|
||||
enum SensorLastResetType {
|
||||
@ -898,6 +915,7 @@ message ListEntitiesNumberResponse {
|
||||
EntityCategory entity_category = 10;
|
||||
string unit_of_measurement = 11;
|
||||
NumberMode mode = 12;
|
||||
string device_class = 13;
|
||||
}
|
||||
message NumberStateResponse {
|
||||
option (id) = 50;
|
||||
@ -1098,3 +1116,222 @@ message MediaPlayerCommandRequest {
|
||||
bool has_media_url = 6;
|
||||
string media_url = 7;
|
||||
}
|
||||
|
||||
// ==================== BLUETOOTH ====================
|
||||
message SubscribeBluetoothLEAdvertisementsRequest {
|
||||
option (id) = 66;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
}
|
||||
|
||||
message BluetoothServiceData {
|
||||
string uuid = 1;
|
||||
repeated uint32 legacy_data = 2 [deprecated = true];
|
||||
bytes data = 3; // Changed in proto version 1.7
|
||||
}
|
||||
message BluetoothLEAdvertisementResponse {
|
||||
option (id) = 67;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
option (no_delay) = true;
|
||||
|
||||
uint64 address = 1;
|
||||
string name = 2;
|
||||
sint32 rssi = 3;
|
||||
|
||||
repeated string service_uuids = 4;
|
||||
repeated BluetoothServiceData service_data = 5;
|
||||
repeated BluetoothServiceData manufacturer_data = 6;
|
||||
|
||||
uint32 address_type = 7;
|
||||
}
|
||||
|
||||
enum BluetoothDeviceRequestType {
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5;
|
||||
}
|
||||
|
||||
message BluetoothDeviceRequest {
|
||||
option (id) = 68;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
BluetoothDeviceRequestType request_type = 2;
|
||||
bool has_address_type = 3;
|
||||
uint32 address_type = 4;
|
||||
}
|
||||
|
||||
message BluetoothDeviceConnectionResponse {
|
||||
option (id) = 69;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
bool connected = 2;
|
||||
uint32 mtu = 3;
|
||||
int32 error = 4;
|
||||
}
|
||||
|
||||
message BluetoothGATTGetServicesRequest {
|
||||
option (id) = 70;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
}
|
||||
|
||||
message BluetoothGATTDescriptor {
|
||||
repeated uint64 uuid = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTCharacteristic {
|
||||
repeated uint64 uuid = 1;
|
||||
uint32 handle = 2;
|
||||
uint32 properties = 3;
|
||||
repeated BluetoothGATTDescriptor descriptors = 4;
|
||||
}
|
||||
|
||||
message BluetoothGATTService {
|
||||
repeated uint64 uuid = 1;
|
||||
uint32 handle = 2;
|
||||
repeated BluetoothGATTCharacteristic characteristics = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTGetServicesResponse {
|
||||
option (id) = 71;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
repeated BluetoothGATTService services = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTGetServicesDoneResponse {
|
||||
option (id) = 72;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
}
|
||||
|
||||
message BluetoothGATTReadRequest {
|
||||
option (id) = 73;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTReadResponse {
|
||||
option (id) = 74;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3;
|
||||
|
||||
}
|
||||
|
||||
message BluetoothGATTWriteRequest {
|
||||
option (id) = 75;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
bool response = 3;
|
||||
|
||||
bytes data = 4;
|
||||
}
|
||||
|
||||
message BluetoothGATTReadDescriptorRequest {
|
||||
option (id) = 76;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTWriteDescriptorRequest {
|
||||
option (id) = 77;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTNotifyRequest {
|
||||
option (id) = 78;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
bool enable = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTNotifyDataResponse {
|
||||
option (id) = 79;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message SubscribeBluetoothConnectionsFreeRequest {
|
||||
option (id) = 80;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
}
|
||||
|
||||
message BluetoothConnectionsFreeResponse {
|
||||
option (id) = 81;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint32 free = 1;
|
||||
uint32 limit = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTErrorResponse {
|
||||
option (id) = 82;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
int32 error = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTWriteResponse {
|
||||
option (id) = 83;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTNotifyResponse {
|
||||
option (id) = 84;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cerrno>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
@ -12,6 +12,9 @@
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@ -613,6 +616,7 @@ bool APIConnection::send_number_info(number::Number *number) {
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
|
||||
msg.unit_of_measurement = number->traits.get_unit_of_measurement();
|
||||
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
|
||||
msg.device_class = number->traits.get_device_class();
|
||||
|
||||
msg.min_value = number->traits.get_min_value();
|
||||
msg.max_value = number->traits.get_max_value();
|
||||
@ -823,6 +827,56 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
|
||||
if (!this->bluetooth_le_advertisement_subscription_)
|
||||
return false;
|
||||
if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
|
||||
BluetoothLEAdvertisementResponse resp = msg;
|
||||
for (auto &service : resp.service_data) {
|
||||
service.legacy_data.assign(service.data.begin(), service.data.end());
|
||||
service.data.clear();
|
||||
}
|
||||
for (auto &manufacturer_data : resp.manufacturer_data) {
|
||||
manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end());
|
||||
manufacturer_data.data.clear();
|
||||
}
|
||||
return this->send_bluetooth_le_advertisement_response(resp);
|
||||
}
|
||||
return this->send_bluetooth_le_advertisement_response(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg);
|
||||
}
|
||||
|
||||
void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg);
|
||||
}
|
||||
|
||||
BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
||||
BluetoothConnectionsFreeResponse resp;
|
||||
resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
|
||||
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
|
||||
return resp;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
@ -840,11 +894,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")";
|
||||
this->helper_->set_log_info(client_info_);
|
||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||
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(),
|
||||
this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 6;
|
||||
resp.api_version_minor = 7;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
resp.name = App.get_name();
|
||||
|
||||
@ -876,6 +933,11 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
resp.manufacturer = "Espressif";
|
||||
#elif defined(USE_RP2040)
|
||||
resp.manufacturer = "Raspberry Pi";
|
||||
#endif
|
||||
resp.model = ESPHOME_BOARD;
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||
@ -886,6 +948,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER
|
||||
resp.webserver_port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 3 : 1;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_frame_helper.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_server.h"
|
||||
#include "api_frame_helper.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@ -94,6 +96,20 @@ class APIConnection : public APIServerConnection {
|
||||
return;
|
||||
this->send_homeassistant_service_response(call);
|
||||
}
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
|
||||
|
||||
void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override;
|
||||
void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override;
|
||||
void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override;
|
||||
void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override;
|
||||
void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override;
|
||||
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
|
||||
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
|
||||
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void send_time_request() {
|
||||
GetTimeRequest req;
|
||||
@ -134,6 +150,9 @@ 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();
|
||||
@ -167,6 +186,8 @@ class APIConnection : public APIServerConnection {
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
|
||||
std::string client_info_;
|
||||
uint32_t client_api_version_major_{0};
|
||||
uint32_t client_api_version_minor_{0};
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
esp32_camera::CameraImageReader image_reader_;
|
||||
#endif
|
||||
@ -176,6 +197,7 @@ class APIConnection : public APIServerConnection {
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
bool bluetooth_le_advertisement_subscription_{false};
|
||||
bool next_close_ = false;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
|
@ -116,9 +116,9 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
std::vector<uint8_t> prologue_;
|
||||
|
||||
std::shared_ptr<APINoiseContext> ctx_;
|
||||
NoiseHandshakeState *handshake_ = nullptr;
|
||||
NoiseCipherState *send_cipher_ = nullptr;
|
||||
NoiseCipherState *recv_cipher_ = nullptr;
|
||||
NoiseHandshakeState *handshake_{nullptr};
|
||||
NoiseCipherState *send_cipher_{nullptr};
|
||||
NoiseCipherState *recv_cipher_{nullptr};
|
||||
NoiseProtocolId nid_;
|
||||
|
||||
enum class State {
|
||||
|
@ -6,6 +6,7 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
|
||||
switch (value) {
|
||||
case enums::ENTITY_CATEGORY_NONE:
|
||||
@ -18,6 +19,8 @@ template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::Entity
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
|
||||
switch (value) {
|
||||
case enums::LEGACY_COVER_STATE_OPEN:
|
||||
@ -28,6 +31,8 @@ template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::Lega
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::CoverOperation>(enums::CoverOperation value) {
|
||||
switch (value) {
|
||||
case enums::COVER_OPERATION_IDLE:
|
||||
@ -40,6 +45,8 @@ template<> const char *proto_enum_to_string<enums::CoverOperation>(enums::CoverO
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::LegacyCoverCommand>(enums::LegacyCoverCommand value) {
|
||||
switch (value) {
|
||||
case enums::LEGACY_COVER_COMMAND_OPEN:
|
||||
@ -52,6 +59,8 @@ template<> const char *proto_enum_to_string<enums::LegacyCoverCommand>(enums::Le
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed value) {
|
||||
switch (value) {
|
||||
case enums::FAN_SPEED_LOW:
|
||||
@ -64,6 +73,8 @@ template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed val
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirection value) {
|
||||
switch (value) {
|
||||
case enums::FAN_DIRECTION_FORWARD:
|
||||
@ -74,6 +85,8 @@ template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirec
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::ColorMode>(enums::ColorMode value) {
|
||||
switch (value) {
|
||||
case enums::COLOR_MODE_UNKNOWN:
|
||||
@ -100,6 +113,8 @@ template<> const char *proto_enum_to_string<enums::ColorMode>(enums::ColorMode v
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::SensorStateClass value) {
|
||||
switch (value) {
|
||||
case enums::STATE_CLASS_NONE:
|
||||
@ -108,10 +123,14 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
|
||||
return "STATE_CLASS_MEASUREMENT";
|
||||
case enums::STATE_CLASS_TOTAL_INCREASING:
|
||||
return "STATE_CLASS_TOTAL_INCREASING";
|
||||
case enums::STATE_CLASS_TOTAL:
|
||||
return "STATE_CLASS_TOTAL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::SensorLastResetType>(enums::SensorLastResetType value) {
|
||||
switch (value) {
|
||||
case enums::LAST_RESET_NONE:
|
||||
@ -124,6 +143,8 @@ template<> const char *proto_enum_to_string<enums::SensorLastResetType>(enums::S
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
||||
switch (value) {
|
||||
case enums::LOG_LEVEL_NONE:
|
||||
@ -146,6 +167,8 @@ template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel val
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::ServiceArgType value) {
|
||||
switch (value) {
|
||||
case enums::SERVICE_ARG_TYPE_BOOL:
|
||||
@ -168,6 +191,8 @@ template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::Servic
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMode value) {
|
||||
switch (value) {
|
||||
case enums::CLIMATE_MODE_OFF:
|
||||
@ -188,6 +213,8 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::ClimateFanMode value) {
|
||||
switch (value) {
|
||||
case enums::CLIMATE_FAN_ON:
|
||||
@ -212,6 +239,8 @@ template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::Climat
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::ClimateSwingMode value) {
|
||||
switch (value) {
|
||||
case enums::CLIMATE_SWING_OFF:
|
||||
@ -226,6 +255,8 @@ template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::Clim
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::ClimateAction value) {
|
||||
switch (value) {
|
||||
case enums::CLIMATE_ACTION_OFF:
|
||||
@ -244,6 +275,8 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::ClimatePreset value) {
|
||||
switch (value) {
|
||||
case enums::CLIMATE_PRESET_NONE:
|
||||
@ -266,6 +299,8 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
|
||||
switch (value) {
|
||||
case enums::NUMBER_MODE_AUTO:
|
||||
@ -278,6 +313,8 @@ template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState value) {
|
||||
switch (value) {
|
||||
case enums::LOCK_STATE_NONE:
|
||||
@ -296,6 +333,8 @@ template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState v
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockCommand value) {
|
||||
switch (value) {
|
||||
case enums::LOCK_UNLOCK:
|
||||
@ -308,6 +347,8 @@ template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockComma
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::MediaPlayerState>(enums::MediaPlayerState value) {
|
||||
switch (value) {
|
||||
case enums::MEDIA_PLAYER_STATE_NONE:
|
||||
@ -322,6 +363,8 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerState>(enums::Medi
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::MediaPlayerCommand value) {
|
||||
switch (value) {
|
||||
case enums::MEDIA_PLAYER_COMMAND_PLAY:
|
||||
@ -338,6 +381,42 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<>
|
||||
const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
|
||||
switch (value) {
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
|
||||
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";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->api_version_major = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->api_version_minor = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@ -348,7 +427,11 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
|
||||
void HelloRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->client_info);
|
||||
buffer.encode_uint32(2, this->api_version_major);
|
||||
buffer.encode_uint32(3, this->api_version_minor);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void HelloRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
@ -356,6 +439,16 @@ void HelloRequest::dump_to(std::string &out) const {
|
||||
out.append(" client_info: ");
|
||||
out.append("'").append(this->client_info).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" api_version_major: ");
|
||||
sprintf(buffer, "%u", this->api_version_major);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" api_version_minor: ");
|
||||
sprintf(buffer, "%u", this->api_version_minor);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -493,6 +586,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->webserver_port = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->bluetooth_proxy_version = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -527,6 +624,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
||||
this->project_version = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->manufacturer = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -542,6 +643,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(8, this->project_name);
|
||||
buffer.encode_string(9, this->project_version);
|
||||
buffer.encode_uint32(10, this->webserver_port);
|
||||
buffer.encode_uint32(11, this->bluetooth_proxy_version);
|
||||
buffer.encode_string(12, this->manufacturer);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@ -587,6 +690,15 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
sprintf(buffer, "%u", this->webserver_port);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" bluetooth_proxy_version: ");
|
||||
sprintf(buffer, "%u", this->bluetooth_proxy_version);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" manufacturer: ");
|
||||
out.append("'").append(this->manufacturer).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -3878,6 +3990,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
this->unit_of_measurement = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -3917,6 +4033,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
|
||||
buffer.encode_string(11, this->unit_of_measurement);
|
||||
buffer.encode_enum<enums::NumberMode>(12, this->mode);
|
||||
buffer.encode_string(13, this->device_class);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
@ -3973,6 +4090,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
out.append(" mode: ");
|
||||
out.append(proto_enum_to_string<enums::NumberMode>(this->mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -4852,6 +4973,986 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
|
||||
out.append("SubscribeBluetoothLEAdvertisementsRequest {}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->legacy_data.push_back(value.as_uint32());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->uuid = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->uuid);
|
||||
for (auto &it : this->legacy_data) {
|
||||
buffer.encode_uint32(2, it, true);
|
||||
}
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothServiceData::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothServiceData {\n");
|
||||
out.append(" uuid: ");
|
||||
out.append("'").append(this->uuid).append("'");
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->legacy_data) {
|
||||
out.append(" legacy_data: ");
|
||||
sprintf(buffer, "%u", it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothLEAdvertisementResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->rssi = value.as_sint32();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->address_type = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothLEAdvertisementResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->service_uuids.push_back(value.as_string());
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->service_data.push_back(value.as_message<BluetoothServiceData>());
|
||||
return true;
|
||||
}
|
||||
case 6: {
|
||||
this->manufacturer_data.push_back(value.as_message<BluetoothServiceData>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_string(2, this->name);
|
||||
buffer.encode_sint32(3, this->rssi);
|
||||
for (auto &it : this->service_uuids) {
|
||||
buffer.encode_string(4, it, true);
|
||||
}
|
||||
for (auto &it : this->service_data) {
|
||||
buffer.encode_message<BluetoothServiceData>(5, it, true);
|
||||
}
|
||||
for (auto &it : this->manufacturer_data) {
|
||||
buffer.encode_message<BluetoothServiceData>(6, it, true);
|
||||
}
|
||||
buffer.encode_uint32(7, this->address_type);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothLEAdvertisementResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" rssi: ");
|
||||
sprintf(buffer, "%d", this->rssi);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->service_uuids) {
|
||||
out.append(" service_uuids: ");
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->service_data) {
|
||||
out.append(" service_data: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->manufacturer_data) {
|
||||
out.append(" manufacturer_data: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" address_type: ");
|
||||
sprintf(buffer, "%u", this->address_type);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->request_type = value.as_enum<enums::BluetoothDeviceRequestType>();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->has_address_type = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->address_type = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_enum<enums::BluetoothDeviceRequestType>(2, this->request_type);
|
||||
buffer.encode_bool(3, this->has_address_type);
|
||||
buffer.encode_uint32(4, this->address_type);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothDeviceRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothDeviceRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" request_type: ");
|
||||
out.append(proto_enum_to_string<enums::BluetoothDeviceRequestType>(this->request_type));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_address_type: ");
|
||||
out.append(YESNO(this->has_address_type));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" address_type: ");
|
||||
sprintf(buffer, "%u", this->address_type);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->connected = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->mtu = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->error = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->connected);
|
||||
buffer.encode_uint32(3, this->mtu);
|
||||
buffer.encode_int32(4, this->error);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothDeviceConnectionResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" connected: ");
|
||||
out.append(YESNO(this->connected));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" mtu: ");
|
||||
sprintf(buffer, "%u", this->mtu);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" error: ");
|
||||
sprintf(buffer, "%d", this->error);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTGetServicesRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->uuid.push_back(value.as_uint64());
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->uuid) {
|
||||
buffer.encode_uint64(1, it, true);
|
||||
}
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTDescriptor::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTDescriptor {\n");
|
||||
for (const auto &it : this->uuid) {
|
||||
out.append(" uuid: ");
|
||||
sprintf(buffer, "%llu", it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->uuid.push_back(value.as_uint64());
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->properties = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTCharacteristic::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
this->descriptors.push_back(value.as_message<BluetoothGATTDescriptor>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->uuid) {
|
||||
buffer.encode_uint64(1, it, true);
|
||||
}
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_uint32(3, this->properties);
|
||||
for (auto &it : this->descriptors) {
|
||||
buffer.encode_message<BluetoothGATTDescriptor>(4, it, true);
|
||||
}
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTCharacteristic {\n");
|
||||
for (const auto &it : this->uuid) {
|
||||
out.append(" uuid: ");
|
||||
sprintf(buffer, "%llu", it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" properties: ");
|
||||
sprintf(buffer, "%u", this->properties);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->descriptors) {
|
||||
out.append(" descriptors: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->uuid.push_back(value.as_uint64());
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTService::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->characteristics.push_back(value.as_message<BluetoothGATTCharacteristic>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->uuid) {
|
||||
buffer.encode_uint64(1, it, true);
|
||||
}
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
for (auto &it : this->characteristics) {
|
||||
buffer.encode_message<BluetoothGATTCharacteristic>(3, it, true);
|
||||
}
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTService::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTService {\n");
|
||||
for (const auto &it : this->uuid) {
|
||||
out.append(" uuid: ");
|
||||
sprintf(buffer, "%llu", it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->characteristics) {
|
||||
out.append(" characteristics: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTGetServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->services.push_back(value.as_message<BluetoothGATTService>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
for (auto &it : this->services) {
|
||||
buffer.encode_message<BluetoothGATTService>(2, it, true);
|
||||
}
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTGetServicesResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->services) {
|
||||
out.append(" services: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTGetServicesDoneResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTReadRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTReadRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTReadRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTReadResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTReadResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTReadResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->response = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bool(3, this->response);
|
||||
buffer.encode_string(4, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTWriteRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" response: ");
|
||||
out.append(YESNO(this->response));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTReadDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTReadDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTReadDescriptorRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTWriteDescriptorRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->enable = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTNotifyRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bool(3, this->enable);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTNotifyRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" enable: ");
|
||||
out.append(YESNO(this->enable));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTNotifyDataResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTNotifyDataResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const {
|
||||
out.append("SubscribeBluetoothConnectionsFreeRequest {}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->free = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->limit = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->free);
|
||||
buffer.encode_uint32(2, this->limit);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothConnectionsFreeResponse {\n");
|
||||
out.append(" free: ");
|
||||
sprintf(buffer, "%u", this->free);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" limit: ");
|
||||
sprintf(buffer, "%u", this->limit);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTErrorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->error = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_int32(3, this->error);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTErrorResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTErrorResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" error: ");
|
||||
sprintf(buffer, "%d", this->error);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTWriteResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTWriteResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTWriteResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTNotifyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTNotifyResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@ -53,6 +53,7 @@ enum SensorStateClass : uint32_t {
|
||||
STATE_CLASS_NONE = 0,
|
||||
STATE_CLASS_MEASUREMENT = 1,
|
||||
STATE_CLASS_TOTAL_INCREASING = 2,
|
||||
STATE_CLASS_TOTAL = 3,
|
||||
};
|
||||
enum SensorLastResetType : uint32_t {
|
||||
LAST_RESET_NONE = 0,
|
||||
@ -154,12 +155,22 @@ enum MediaPlayerCommand : uint32_t {
|
||||
MEDIA_PLAYER_COMMAND_MUTE = 3,
|
||||
MEDIA_PLAYER_COMMAND_UNMUTE = 4,
|
||||
};
|
||||
enum BluetoothDeviceRequestType : uint32_t {
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string client_info{};
|
||||
uint32_t api_version_major{0};
|
||||
uint32_t api_version_minor{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -167,6 +178,7 @@ class HelloRequest : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class HelloResponse : public ProtoMessage {
|
||||
public:
|
||||
@ -262,6 +274,8 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
std::string project_name{};
|
||||
std::string project_version{};
|
||||
uint32_t webserver_port{0};
|
||||
uint32_t bluetooth_proxy_version{0};
|
||||
std::string manufacturer{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -992,6 +1006,7 @@ class ListEntitiesNumberResponse : public ProtoMessage {
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string unit_of_measurement{};
|
||||
enums::NumberMode mode{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -1213,6 +1228,303 @@ class MediaPlayerCommandRequest : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeBluetoothLEAdvertisementsRequest : 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 BluetoothServiceData : public ProtoMessage {
|
||||
public:
|
||||
std::string uuid{};
|
||||
std::vector<uint32_t> legacy_data{};
|
||||
std::string 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;
|
||||
};
|
||||
class BluetoothLEAdvertisementResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
std::string name{};
|
||||
int32_t rssi{0};
|
||||
std::vector<std::string> service_uuids{};
|
||||
std::vector<BluetoothServiceData> service_data{};
|
||||
std::vector<BluetoothServiceData> manufacturer_data{};
|
||||
uint32_t address_type{0};
|
||||
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;
|
||||
};
|
||||
class BluetoothDeviceRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
enums::BluetoothDeviceRequestType request_type{};
|
||||
bool has_address_type{false};
|
||||
uint32_t address_type{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 BluetoothDeviceConnectionResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
bool connected{false};
|
||||
uint32_t mtu{0};
|
||||
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 BluetoothGATTGetServicesRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{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 BluetoothGATTDescriptor : public ProtoMessage {
|
||||
public:
|
||||
std::vector<uint64_t> uuid{};
|
||||
uint32_t handle{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 BluetoothGATTCharacteristic : public ProtoMessage {
|
||||
public:
|
||||
std::vector<uint64_t> uuid{};
|
||||
uint32_t handle{0};
|
||||
uint32_t properties{0};
|
||||
std::vector<BluetoothGATTDescriptor> descriptors{};
|
||||
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;
|
||||
};
|
||||
class BluetoothGATTService : public ProtoMessage {
|
||||
public:
|
||||
std::vector<uint64_t> uuid{};
|
||||
uint32_t handle{0};
|
||||
std::vector<BluetoothGATTCharacteristic> characteristics{};
|
||||
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;
|
||||
};
|
||||
class BluetoothGATTGetServicesResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
std::vector<BluetoothGATTService> services{};
|
||||
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;
|
||||
};
|
||||
class BluetoothGATTGetServicesDoneResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{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 BluetoothGATTReadRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{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 BluetoothGATTReadResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
std::string 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;
|
||||
};
|
||||
class BluetoothGATTWriteRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
bool response{false};
|
||||
std::string 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;
|
||||
};
|
||||
class BluetoothGATTReadDescriptorRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{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 BluetoothGATTWriteDescriptorRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
std::string 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;
|
||||
};
|
||||
class BluetoothGATTNotifyRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
bool enable{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 BluetoothGATTNotifyDataResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
std::string 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;
|
||||
};
|
||||
class SubscribeBluetoothConnectionsFreeRequest : 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 BluetoothConnectionsFreeResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t free{0};
|
||||
uint32_t limit{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 BluetoothGATTErrorResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
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 BluetoothGATTWriteResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{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 BluetoothGATTNotifyResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{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;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@ -328,6 +328,103 @@ bool APIServerConnectionBase::send_media_player_state_response(const MediaPlayer
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
#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());
|
||||
#endif
|
||||
return this->send_message_<BluetoothLEAdvertisementResponse>(msg, 67);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_device_connection_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothDeviceConnectionResponse>(msg, 69);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTGetServicesResponse>(msg, 71);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_get_services_done_response(
|
||||
const BluetoothGATTGetServicesDoneResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_done_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTGetServicesDoneResponse>(msg, 72);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_read_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTReadResponse>(msg, 74);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_data_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTNotifyDataResponse>(msg, 79);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_connections_free_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothConnectionsFreeResponse>(msg, 81);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_error_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTErrorResponse>(msg, 82);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_write_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTWriteResponse>(msg, 83);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
|
||||
}
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
@ -592,6 +689,103 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_media_player_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 66: {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case 68: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothDeviceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_device_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 70: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTGetServicesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_get_services_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 73: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTReadRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_read_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 75: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTWriteRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_write_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 76: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTReadDescriptorRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_read_descriptor_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 77: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTWriteDescriptorRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_write_descriptor_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 78: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTNotifyRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_notify_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 80: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
SubscribeBluetoothConnectionsFreeRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
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;
|
||||
}
|
||||
@ -855,6 +1049,126 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
|
||||
this->media_player_command(msg);
|
||||
}
|
||||
#endif
|
||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_bluetooth_le_advertisements(msg);
|
||||
}
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_device_request(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_get_services(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_read(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_write(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_read_descriptor(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_write_descriptor(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_notify(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg);
|
||||
if (!this->send_bluetooth_connections_free_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@ -153,6 +153,62 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
|
||||
#endif
|
||||
virtual void on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
@ -204,6 +260,32 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
|
||||
#endif
|
||||
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
@ -250,6 +332,31 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
|
||||
#endif
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -291,6 +291,79 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon
|
||||
client->send_homeassistant_service_call(call);
|
||||
}
|
||||
}
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_le_advertisement(call);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
|
||||
BluetoothDeviceConnectionResponse call;
|
||||
call.address = address;
|
||||
call.connected = connected;
|
||||
call.mtu = mtu;
|
||||
call.error = error;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_device_connection_response(call);
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
|
||||
BluetoothConnectionsFreeResponse call;
|
||||
call.free = free;
|
||||
call.limit = limit;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_connections_free_response(call);
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_gatt_read_response(call);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_gatt_write_response(call);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_gatt_notify_data_response(call);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_gatt_notify_response(call);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_gatt_get_services_response(call);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
|
||||
BluetoothGATTGetServicesDoneResponse call;
|
||||
call.address = address;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_gatt_get_services_done_response(call);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
|
||||
BluetoothGATTErrorResponse call;
|
||||
call.address = address;
|
||||
call.handle = handle;
|
||||
call.error = error;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_gatt_error_response(call);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f) {
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include "user_services.h"
|
||||
#include "api_noise_context.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
@ -73,6 +75,18 @@ class APIServer : public Component, public Controller {
|
||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||
#endif
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
#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_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);
|
||||
void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call);
|
||||
void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call);
|
||||
void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call);
|
||||
void send_bluetooth_gatt_services_done(uint64_t address);
|
||||
void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
|
||||
#endif
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void request_time();
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "api_pb2.h"
|
||||
#include "api_server.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
#define HAS_PROTO_MESSAGE_DUMP
|
||||
#endif
|
||||
@ -70,7 +72,7 @@ class ProtoVarInt {
|
||||
}
|
||||
}
|
||||
void encode(std::vector<uint8_t> &out) {
|
||||
uint32_t val = this->value_;
|
||||
uint64_t val = this->value_;
|
||||
if (val <= 0x7F) {
|
||||
out.push_back(val);
|
||||
return;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
@ -92,9 +92,9 @@ 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_;
|
||||
sensor::Sensor *energy_sensor_;
|
||||
binary_sensor::BinarySensor *thunder_alert_binary_sensor_;
|
||||
sensor::Sensor *distance_sensor_{nullptr};
|
||||
sensor::Sensor *energy_sensor_{nullptr};
|
||||
binary_sensor::BinarySensor *thunder_alert_binary_sensor_{nullptr};
|
||||
GPIOPin *irq_pin_;
|
||||
|
||||
bool indoor_;
|
||||
|
@ -8,6 +8,7 @@ CODEOWNERS = ["@OttoWinter"]
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
)
|
||||
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
|
@ -1 +1,52 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import ble_client, time
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_RECEIVE_TIMEOUT,
|
||||
CONF_TIME_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jhansche"]
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
MULTI_CONF = True
|
||||
CONF_BEDJET_ID = "bedjet_id"
|
||||
|
||||
bedjet_ns = cg.esphome_ns.namespace("bedjet")
|
||||
BedJetHub = bedjet_ns.class_("BedJetHub", ble_client.BLEClientNode, cg.PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BedJetHub),
|
||||
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
cv.Optional(
|
||||
CONF_RECEIVE_TIMEOUT, default="0s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("15s"))
|
||||
)
|
||||
|
||||
BEDJET_CLIENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BEDJET_ID): cv.use_id(BedJetHub),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def register_bedjet_child(var, config):
|
||||
parent = await cg.get_variable(config[CONF_BEDJET_ID])
|
||||
cg.add(parent.register_child(var))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
if CONF_TIME_ID in config:
|
||||
time_ = await cg.get_variable(config[CONF_TIME_ID])
|
||||
cg.add(var.set_time_id(time_))
|
||||
if CONF_RECEIVE_TIMEOUT in config:
|
||||
cg.add(var.set_status_timeout(config[CONF_RECEIVE_TIMEOUT]))
|
||||
|
@ -1,675 +0,0 @@
|
||||
#include "bedjet.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace bedjet {
|
||||
|
||||
using namespace esphome::climate;
|
||||
|
||||
/// Converts a BedJet temp step into degrees Celsius.
|
||||
float bedjet_temp_to_c(const uint8_t temp) {
|
||||
// BedJet temp is "C*2"; to get C, divide by 2.
|
||||
return temp / 2.0f;
|
||||
}
|
||||
|
||||
/// Converts a BedJet fan step to a speed percentage, in the range of 5% to 100%.
|
||||
uint8_t bedjet_fan_step_to_speed(const uint8_t fan) {
|
||||
// 0 = 5%
|
||||
// 19 = 100%
|
||||
return 5 * fan + 5;
|
||||
}
|
||||
|
||||
static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) {
|
||||
if (fan_step >= 0 && fan_step <= 19)
|
||||
return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) {
|
||||
for (int i = 0; i < sizeof(BEDJET_FAN_STEP_NAME_STRINGS); i++) {
|
||||
if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static BedjetButton heat_button(BedjetHeatMode mode) {
|
||||
BedjetButton btn = BTN_HEAT;
|
||||
if (mode == HEAT_MODE_EXTENDED) {
|
||||
btn = BTN_EXTHT;
|
||||
}
|
||||
return btn;
|
||||
}
|
||||
|
||||
void Bedjet::upgrade_firmware() {
|
||||
auto *pkt = this->codec_->get_button_request(MAGIC_UPDATE);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
void Bedjet::dump_config() {
|
||||
LOG_CLIMATE("", "BedJet Climate", this);
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported modes:");
|
||||
for (auto mode : traits.get_supported_modes()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_mode_to_string(mode)));
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported fan modes:");
|
||||
for (const auto &mode : traits.get_supported_fan_modes()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(mode)));
|
||||
}
|
||||
for (const auto &mode : traits.get_supported_custom_fan_modes()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s (c)", mode.c_str());
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported presets:");
|
||||
for (auto preset : traits.get_supported_presets()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_preset_to_string(preset)));
|
||||
}
|
||||
for (const auto &preset : traits.get_supported_custom_presets()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s (c)", preset.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Bedjet::setup() {
|
||||
this->codec_ = make_unique<BedjetCodec>();
|
||||
|
||||
// restore set points
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
ESP_LOGI(TAG, "Restored previous saved state.");
|
||||
restore->apply(this);
|
||||
} else {
|
||||
// Initial status is unknown until we connect
|
||||
this->reset_state_();
|
||||
}
|
||||
|
||||
#ifdef USE_TIME
|
||||
this->setup_time_();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Resets states to defaults. */
|
||||
void Bedjet::reset_state_() {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
this->action = climate::CLIMATE_ACTION_IDLE;
|
||||
this->target_temperature = NAN;
|
||||
this->current_temperature = NAN;
|
||||
this->preset.reset();
|
||||
this->custom_preset.reset();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void Bedjet::loop() {}
|
||||
|
||||
void Bedjet::control(const ClimateCall &call) {
|
||||
ESP_LOGD(TAG, "Received Bedjet::control");
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "Not connected, cannot handle control call yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (call.get_mode().has_value()) {
|
||||
ClimateMode mode = *call.get_mode();
|
||||
BedjetPacket *pkt;
|
||||
switch (mode) {
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
pkt = this->codec_->get_button_request(BTN_OFF);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
pkt = this->codec_->get_button_request(heat_button(this->heating_mode_));
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
pkt = this->codec_->get_button_request(BTN_COOL);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
pkt = this->codec_->get_button_request(BTN_DRY);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unsupported mode: %d", mode);
|
||||
return;
|
||||
}
|
||||
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
} else {
|
||||
this->force_refresh_ = true;
|
||||
this->mode = mode;
|
||||
// We're using (custom) preset for Turbo, EXT HT, & M1-3 presets, so changing climate mode will clear those
|
||||
this->custom_preset.reset();
|
||||
this->preset.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
auto target_temp = *call.get_target_temperature();
|
||||
auto *pkt = this->codec_->get_set_target_temp_request(target_temp);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
} else {
|
||||
this->target_temperature = target_temp;
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_preset().has_value()) {
|
||||
ClimatePreset preset = *call.get_preset();
|
||||
BedjetPacket *pkt;
|
||||
|
||||
if (preset == climate::CLIMATE_PRESET_BOOST) {
|
||||
pkt = this->codec_->get_button_request(BTN_TURBO);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported preset: %d", preset);
|
||||
return;
|
||||
}
|
||||
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
} else {
|
||||
// We use BOOST preset for TURBO mode, which is a short-lived/high-heat mode.
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
this->preset = preset;
|
||||
this->custom_preset.reset();
|
||||
this->force_refresh_ = true;
|
||||
}
|
||||
} else if (call.get_custom_preset().has_value()) {
|
||||
std::string preset = *call.get_custom_preset();
|
||||
BedjetPacket *pkt;
|
||||
|
||||
if (preset == "M1") {
|
||||
pkt = this->codec_->get_button_request(BTN_M1);
|
||||
} else if (preset == "M2") {
|
||||
pkt = this->codec_->get_button_request(BTN_M2);
|
||||
} else if (preset == "M3") {
|
||||
pkt = this->codec_->get_button_request(BTN_M3);
|
||||
} else if (preset == "LTD HT") {
|
||||
pkt = this->codec_->get_button_request(BTN_HEAT);
|
||||
} else if (preset == "EXT HT") {
|
||||
pkt = this->codec_->get_button_request(BTN_EXTHT);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
} else {
|
||||
this->force_refresh_ = true;
|
||||
this->custom_preset = preset;
|
||||
this->preset.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
// Climate fan mode only supports low/med/high, but the BedJet supports 5-100% increments.
|
||||
// We can still support a ClimateCall that requests low/med/high, and just translate it to a step increment here.
|
||||
auto fan_mode = *call.get_fan_mode();
|
||||
BedjetPacket *pkt;
|
||||
if (fan_mode == climate::CLIMATE_FAN_LOW) {
|
||||
pkt = this->codec_->get_set_fan_speed_request(3 /* = 20% */);
|
||||
} else if (fan_mode == climate::CLIMATE_FAN_MEDIUM) {
|
||||
pkt = this->codec_->get_set_fan_speed_request(9 /* = 50% */);
|
||||
} else if (fan_mode == climate::CLIMATE_FAN_HIGH) {
|
||||
pkt = this->codec_->get_set_fan_speed_request(14 /* = 75% */);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Unsupported fan mode: %s", this->get_name().c_str(),
|
||||
LOG_STR_ARG(climate_fan_mode_to_string(fan_mode)));
|
||||
return;
|
||||
}
|
||||
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
} else {
|
||||
this->force_refresh_ = true;
|
||||
}
|
||||
} else if (call.get_custom_fan_mode().has_value()) {
|
||||
auto fan_mode = *call.get_custom_fan_mode();
|
||||
auto fan_step = bedjet_fan_speed_to_step(fan_mode);
|
||||
if (fan_step >= 0 && fan_step <= 19) {
|
||||
ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode.c_str(),
|
||||
fan_step);
|
||||
// The index should represent the fan_step index.
|
||||
BedjetPacket *pkt = this->codec_->get_set_fan_speed_request(fan_step);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
} else {
|
||||
this->force_refresh_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Bedjet::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGV(TAG, "Disconnected: reason=%d", param->disconnect.reason);
|
||||
this->status_set_warning();
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto *chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_COMMAND_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No control service found at device, not a BedJet..?", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
this->char_handle_cmd_ = chr->handle;
|
||||
|
||||
chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_STATUS_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No status service found at device, not a BedJet..?", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
this->char_handle_status_ = chr->handle;
|
||||
// We also need to obtain the config descriptor for this handle.
|
||||
// Otherwise once we set node_state=Established, the parent will flush all handles/descriptors, and we won't be
|
||||
// able to look it up.
|
||||
auto *descr = this->parent_->get_config_descriptor(this->char_handle_status_);
|
||||
if (descr == nullptr) {
|
||||
ESP_LOGW(TAG, "No config descriptor for status handle 0x%x. Will not be able to receive status notifications",
|
||||
this->char_handle_status_);
|
||||
} else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
|
||||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_,
|
||||
descr->uuid.to_string().c_str());
|
||||
} else {
|
||||
this->config_descr_status_ = descr->handle;
|
||||
}
|
||||
|
||||
chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_NAME_UUID);
|
||||
if (chr != nullptr) {
|
||||
this->char_handle_name_ = chr->handle;
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Services complete: obtained char handles.");
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
|
||||
this->set_notify_(true);
|
||||
|
||||
#ifdef USE_TIME
|
||||
if (this->time_id_.has_value()) {
|
||||
this->send_local_time();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
// ESP_GATT_INVALID_ATTR_LEN
|
||||
ESP_LOGW(TAG, "Error writing descr at handle 0x%04d, status=%d", param->write.handle, param->write.status);
|
||||
break;
|
||||
}
|
||||
// [16:44:44][V][bedjet:279]: [JOENJET] Register for notify event success: h=0x002a s=0
|
||||
// This might be the enable-notify descriptor? (or disable-notify)
|
||||
ESP_LOGV(TAG, "[%s] Write to handle 0x%04x status=%d", this->get_name().c_str(), param->write.handle,
|
||||
param->write.status);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error writing char at handle 0x%04d, status=%d", param->write.handle, param->write.status);
|
||||
break;
|
||||
}
|
||||
if (param->write.handle == this->char_handle_cmd_) {
|
||||
if (this->force_refresh_) {
|
||||
// Command write was successful. Publish the pending state, hoping that notify will kick in.
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent_->conn_id)
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->char_handle_status_) {
|
||||
// This is the additional packet that doesn't fit in the notify packet.
|
||||
this->codec_->decode_extra(param->read.value, param->read.value_len);
|
||||
} else if (param->read.handle == this->char_handle_name_) {
|
||||
// The data should represent the name.
|
||||
if (param->read.status == ESP_GATT_OK && param->read.value_len > 0) {
|
||||
std::string bedjet_name(reinterpret_cast<char const *>(param->read.value), param->read.value_len);
|
||||
// this->set_name(bedjet_name);
|
||||
ESP_LOGV(TAG, "[%s] Got BedJet name: '%s'", this->get_name().c_str(), bedjet_name.c_str());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
// This event means that ESP received the request to enable notifications on the client side. But we also have to
|
||||
// tell the server that we want it to send notifications. Normally BLEClient parent would handle this
|
||||
// automatically, but as soon as we set our status to Established, the parent is going to purge all the
|
||||
// service/char/descriptor handles, and then get_config_descriptor() won't work anymore. There's no way to disable
|
||||
// the BLEClient parent behavior, so our only option is to write the handle anyway, and hope a double-write
|
||||
// doesn't break anything.
|
||||
|
||||
if (param->reg_for_notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Register for notify on unexpected handle 0x%04x, expecting 0x%04x",
|
||||
this->get_name().c_str(), param->reg_for_notify.handle, this->char_handle_status_);
|
||||
break;
|
||||
}
|
||||
|
||||
this->write_notify_config_descriptor_(true);
|
||||
this->last_notify_ = 0;
|
||||
this->force_refresh_ = true;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
// This event is not handled by the parent BLEClient, so we need to do this either way.
|
||||
if (param->unreg_for_notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Unregister for notify on unexpected handle 0x%04x, expecting 0x%04x",
|
||||
this->get_name().c_str(), param->unreg_for_notify.handle, this->char_handle_status_);
|
||||
break;
|
||||
}
|
||||
|
||||
this->write_notify_config_descriptor_(false);
|
||||
this->last_notify_ = 0;
|
||||
// Now we wait until the next update() poll to re-register notify...
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Unexpected notify handle, wanted %04X, got %04X", this->get_name().c_str(),
|
||||
this->char_handle_status_, param->notify.handle);
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME: notify events come in every ~200-300 ms, which is too fast to be helpful. So we
|
||||
// throttle the updates to once every MIN_NOTIFY_THROTTLE (5 seconds).
|
||||
// Another idea would be to keep notify off by default, and use update() as an opportunity to turn on
|
||||
// notify to get enough data to update status, then turn off notify again.
|
||||
|
||||
uint32_t now = millis();
|
||||
auto delta = now - this->last_notify_;
|
||||
|
||||
if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) {
|
||||
bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len);
|
||||
this->last_notify_ = now;
|
||||
|
||||
if (needs_extra) {
|
||||
// this means the packet was partial, so read the status characteristic to get the second part.
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id,
|
||||
this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (this->force_refresh_) {
|
||||
// If we requested an immediate update, do that now.
|
||||
this->update();
|
||||
this->force_refresh_ = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGVV(TAG, "[%s] gattc unhandled event: enum=%d", this->get_name().c_str(), event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reimplementation of BLEClient.gattc_event_handler() for ESP_GATTC_REG_FOR_NOTIFY_EVT.
|
||||
*
|
||||
* This is a copy of ble_client's automatic handling of `ESP_GATTC_REG_FOR_NOTIFY_EVT`, in order
|
||||
* to undo the same on unregister. It also allows us to maintain the config descriptor separately,
|
||||
* since the parent BLEClient is going to purge all descriptors once we set our connection status
|
||||
* to `Established`.
|
||||
*/
|
||||
uint8_t Bedjet::write_notify_config_descriptor_(bool enable) {
|
||||
auto handle = this->config_descr_status_;
|
||||
if (handle == 0) {
|
||||
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", this->char_handle_status_);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits.
|
||||
uint8_t notify_en[] = {0, 0};
|
||||
notify_en[0] = enable;
|
||||
auto status =
|
||||
esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en),
|
||||
¬ify_en[0], ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
|
||||
return status;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x", this->get_name().c_str(), enable ? "true" : "false",
|
||||
handle);
|
||||
return ESP_GATT_OK;
|
||||
}
|
||||
|
||||
#ifdef USE_TIME
|
||||
/** Attempts to sync the local time (via `time_id`) to the BedJet device. */
|
||||
void Bedjet::send_local_time() {
|
||||
if (this->time_id_.has_value()) {
|
||||
auto *time_id = *this->time_id_;
|
||||
time::ESPTime now = time_id->now();
|
||||
if (now.is_valid()) {
|
||||
this->set_clock(now.hour, now.minute);
|
||||
ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes time sync callbacks to support syncing current time to the BedJet. */
|
||||
void Bedjet::setup_time_() {
|
||||
if (this->time_id_.has_value()) {
|
||||
this->send_local_time();
|
||||
auto *time_id = *this->time_id_;
|
||||
time_id->add_on_time_sync_callback([this] { this->send_local_time(); });
|
||||
} else {
|
||||
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Attempt to set the BedJet device's clock to the specified time. */
|
||||
void Bedjet::set_clock(uint8_t hour, uint8_t minute) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGV(TAG, "[%s] Not connected, cannot send time.", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
BedjetPacket *pkt = this->codec_->get_set_time_request(hour, minute);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Failed setting BedJet clock: %d", status);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] BedJet clock set to: %d:%02d", this->get_name().c_str(), hour, minute);
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes one BedjetPacket to the BLE client on the BEDJET_COMMAND_UUID. */
|
||||
uint8_t Bedjet::write_bedjet_packet_(BedjetPacket *pkt) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
if (!this->parent_->enabled) {
|
||||
ESP_LOGI(TAG, "[%s] Cannot write packet: Not connected, enabled=false", this->get_name().c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Cannot write packet: Not connected", this->get_name().c_str());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_,
|
||||
pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
return status;
|
||||
}
|
||||
|
||||
/** Configures the local ESP BLE client to register (`true`) or unregister (`false`) for status notifications. */
|
||||
uint8_t Bedjet::set_notify_(const bool enable) {
|
||||
uint8_t status;
|
||||
if (enable) {
|
||||
status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda,
|
||||
this->char_handle_status_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
} else {
|
||||
status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda,
|
||||
this->char_handle_status_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
ESP_LOGV(TAG, "[%s] set_notify: enable=%d; result=%d", this->get_name().c_str(), enable, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/** Attempts to update the climate device from the last received BedjetStatusPacket.
|
||||
*
|
||||
* @return `true` if the status has been applied; `false` if there is nothing to apply.
|
||||
*/
|
||||
bool Bedjet::update_status_() {
|
||||
if (!this->codec_->has_status())
|
||||
return false;
|
||||
|
||||
BedjetStatusPacket status = *this->codec_->get_status_packet();
|
||||
|
||||
auto converted_temp = bedjet_temp_to_c(status.target_temp_step);
|
||||
if (converted_temp > 0)
|
||||
this->target_temperature = converted_temp;
|
||||
converted_temp = bedjet_temp_to_c(status.ambient_temp_step);
|
||||
if (converted_temp > 0)
|
||||
this->current_temperature = converted_temp;
|
||||
|
||||
const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(status.fan_step);
|
||||
if (fan_mode_name != nullptr) {
|
||||
this->custom_fan_mode = *fan_mode_name;
|
||||
}
|
||||
|
||||
// TODO: Get biorhythm data to determine which preset (M1-3) is running, if any.
|
||||
switch (status.mode) {
|
||||
case MODE_WAIT: // Biorhythm "wait" step: device is idle
|
||||
case MODE_STANDBY:
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
this->action = climate::CLIMATE_ACTION_IDLE;
|
||||
this->fan_mode = climate::CLIMATE_FAN_OFF;
|
||||
this->custom_preset.reset();
|
||||
this->preset.reset();
|
||||
break;
|
||||
|
||||
case MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
this->action = climate::CLIMATE_ACTION_HEATING;
|
||||
this->preset.reset();
|
||||
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
||||
this->set_custom_preset_("LTD HT");
|
||||
} else {
|
||||
this->custom_preset.reset();
|
||||
}
|
||||
break;
|
||||
|
||||
case MODE_EXTHT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
this->action = climate::CLIMATE_ACTION_HEATING;
|
||||
this->preset.reset();
|
||||
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
||||
this->custom_preset.reset();
|
||||
} else {
|
||||
this->set_custom_preset_("EXT HT");
|
||||
}
|
||||
break;
|
||||
|
||||
case MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
this->action = climate::CLIMATE_ACTION_COOLING;
|
||||
this->custom_preset.reset();
|
||||
this->preset.reset();
|
||||
break;
|
||||
|
||||
case MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
this->action = climate::CLIMATE_ACTION_DRYING;
|
||||
this->custom_preset.reset();
|
||||
this->preset.reset();
|
||||
break;
|
||||
|
||||
case MODE_TURBO:
|
||||
this->preset = climate::CLIMATE_PRESET_BOOST;
|
||||
this->custom_preset.reset();
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
this->action = climate::CLIMATE_ACTION_HEATING;
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "[%s] Unexpected mode: 0x%02X", this->get_name().c_str(), status.mode);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->is_valid_()) {
|
||||
this->publish_state();
|
||||
this->codec_->clear_status();
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bedjet::update() {
|
||||
ESP_LOGV(TAG, "[%s] update()", this->get_name().c_str());
|
||||
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
if (!this->parent()->enabled) {
|
||||
ESP_LOGD(TAG, "[%s] Not connected, because enabled=false", this->get_name().c_str());
|
||||
} else {
|
||||
// Possibly still trying to connect.
|
||||
ESP_LOGD(TAG, "[%s] Not connected, enabled=true", this->get_name().c_str());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = this->update_status_();
|
||||
if (!result) {
|
||||
uint32_t now = millis();
|
||||
uint32_t diff = now - this->last_notify_;
|
||||
|
||||
if (this->last_notify_ == 0) {
|
||||
// This means we're connected and haven't received a notification, so it likely means that the BedJet is off.
|
||||
// However, it could also mean that it's running, but failing to send notifications.
|
||||
// We can try to unregister for notifications now, and then re-register, hoping to clear it up...
|
||||
// But how do we know for sure which state we're in, and how do we actually clear out the buggy state?
|
||||
|
||||
ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str());
|
||||
this->set_notify_(false);
|
||||
} else if (diff > NOTIFY_WARN_THRESHOLD) {
|
||||
ESP_LOGW(TAG, "[%s] Last GATT notify was %d seconds ago.", this->get_name().c_str(), diff / 1000);
|
||||
}
|
||||
|
||||
if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
|
||||
ESP_LOGW(TAG, "[%s] Timed out after %d sec. Retrying...", this->get_name().c_str(), this->timeout_);
|
||||
this->parent()->set_enabled(false);
|
||||
this->parent()->set_enabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
23
esphome/components/bedjet/bedjet_child.h
Normal file
23
esphome/components/bedjet/bedjet_child.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "bedjet_codec.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bedjet {
|
||||
|
||||
// Forward declare BedJetHub
|
||||
class BedJetHub;
|
||||
|
||||
class BedJetClient : public Parented<BedJetHub> {
|
||||
public:
|
||||
virtual void on_status(const BedjetStatusPacket *data) = 0;
|
||||
virtual void on_bedjet_state(bool is_ready) = 0;
|
||||
|
||||
protected:
|
||||
friend BedJetHub;
|
||||
virtual std::string describe() = 0;
|
||||
};
|
||||
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
@ -1,4 +1,4 @@
|
||||
#include "bedjet_base.h"
|
||||
#include "bedjet_codec.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
@ -48,7 +48,16 @@ BedjetPacket *BedjetCodec::get_set_fan_speed_request(const uint8_t fan_step) {
|
||||
|
||||
/** Returns a BedjetPacket that will set the device's current time. */
|
||||
BedjetPacket *BedjetCodec::get_set_time_request(const uint8_t hour, const uint8_t minute) {
|
||||
this->packet_.command = CMD_SET_TIME;
|
||||
this->packet_.command = CMD_SET_CLOCK;
|
||||
this->packet_.data_length = 2;
|
||||
this->packet_.data[0] = hour;
|
||||
this->packet_.data[1] = minute;
|
||||
return this->clean_packet_();
|
||||
}
|
||||
|
||||
/** Returns a BedjetPacket that will set the device's remaining runtime. */
|
||||
BedjetPacket *BedjetCodec::get_set_runtime_remaining_request(const uint8_t hour, const uint8_t minute) {
|
||||
this->packet_.command = CMD_SET_RUNTIME;
|
||||
this->packet_.data_length = 2;
|
||||
this->packet_.data[0] = hour;
|
||||
this->packet_.data[1] = minute;
|
||||
@ -57,17 +66,17 @@ BedjetPacket *BedjetCodec::get_set_time_request(const uint8_t hour, const uint8_
|
||||
|
||||
/** Decodes the extra bytes that were received after being notified with a partial packet. */
|
||||
void BedjetCodec::decode_extra(const uint8_t *data, uint16_t length) {
|
||||
ESP_LOGV(TAG, "Received extra: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
|
||||
ESP_LOGVV(TAG, "Received extra: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
|
||||
uint8_t offset = this->last_buffer_size_;
|
||||
if (offset > 0 && length + offset <= sizeof(BedjetStatusPacket)) {
|
||||
memcpy(((uint8_t *) (&this->buf_)) + offset, data, length);
|
||||
ESP_LOGV(TAG,
|
||||
"Extra bytes: skip1=0x%08x, skip2=0x%04x, skip3=0x%02x; update phase=0x%02x, "
|
||||
"flags=BedjetFlags <conn=%c, leds=%c, units=%c, mute=%c, others=%02x>",
|
||||
this->buf_._skip_1_, this->buf_._skip_2_, this->buf_._skip_3_, this->buf_.update_phase,
|
||||
this->buf_.flags & 0x20 ? '1' : '0', this->buf_.flags & 0x10 ? '1' : '0',
|
||||
this->buf_.flags & 0x04 ? '1' : '0', this->buf_.flags & 0x01 ? '1' : '0',
|
||||
this->buf_.flags & ~(0x20 | 0x10 | 0x04 | 0x01));
|
||||
ESP_LOGVV(TAG,
|
||||
"Extra bytes: skip1=0x%08x, skip2=0x%04x, skip3=0x%02x; update phase=0x%02x, "
|
||||
"flags=BedjetFlags <conn=%c, leds=%c, units=%c, mute=%c; packed=%02x>",
|
||||
this->buf_.unused_1, this->buf_.unused_2, this->buf_.unused_3, this->buf_.update_phase,
|
||||
this->buf_.flags.conn_test_passed ? '1' : '0', this->buf_.flags.leds_enabled ? '1' : '0',
|
||||
this->buf_.flags.units_setup ? '1' : '0', this->buf_.flags.beeps_muted ? '1' : '0',
|
||||
this->buf_.flags_packed);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Could not determine where to append to, last offset=%d, max size=%u, new size would be %d", offset,
|
||||
sizeof(BedjetStatusPacket), length + offset);
|
||||
@ -82,8 +91,6 @@ bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) {
|
||||
ESP_LOGV(TAG, "Received: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
|
||||
|
||||
if (data[1] == PACKET_FORMAT_V3_HOME && data[3] == PACKET_TYPE_STATUS) {
|
||||
this->status_packet_.reset();
|
||||
|
||||
// Clear old buffer
|
||||
memset(&this->buf_, 0, sizeof(BedjetStatusPacket));
|
||||
// Copy new data into buffer
|
||||
@ -91,23 +98,24 @@ bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) {
|
||||
this->last_buffer_size_ = length;
|
||||
|
||||
// TODO: validate the packet checksum?
|
||||
if (this->buf_.mode >= 0 && this->buf_.mode < 7 && this->buf_.target_temp_step >= 38 &&
|
||||
this->buf_.target_temp_step <= 86 && this->buf_.actual_temp_step > 1 && this->buf_.actual_temp_step <= 100 &&
|
||||
this->buf_.ambient_temp_step > 1 && this->buf_.ambient_temp_step <= 100) {
|
||||
if (this->buf_.mode < 7 && this->buf_.target_temp_step >= 38 && this->buf_.target_temp_step <= 86 &&
|
||||
this->buf_.actual_temp_step > 1 && this->buf_.actual_temp_step <= 100 && this->buf_.ambient_temp_step > 1 &&
|
||||
this->buf_.ambient_temp_step <= 100) {
|
||||
// and save it for the update() loop
|
||||
this->status_packet_ = this->buf_;
|
||||
return this->buf_.is_partial == 1;
|
||||
this->status_packet_ = &this->buf_;
|
||||
return this->buf_.is_partial;
|
||||
} else {
|
||||
this->status_packet_ = nullptr;
|
||||
// TODO: log a warning if we detect that we connected to a non-V3 device.
|
||||
ESP_LOGW(TAG, "Received potentially invalid packet (len %d):", length);
|
||||
}
|
||||
} else if (data[1] == PACKET_FORMAT_DEBUG || data[3] == PACKET_TYPE_DEBUG) {
|
||||
// We don't actually know the packet format for this. Dump packets to log, in case a pattern presents itself.
|
||||
ESP_LOGV(TAG,
|
||||
"received DEBUG packet: set1=%01fF, set2=%01fF, air=%01fF; [7]=%d, [8]=%d, [9]=%d, [10]=%d, [11]=%d, "
|
||||
"[12]=%d, [-1]=%d",
|
||||
bedjet_temp_to_f(data[4]), bedjet_temp_to_f(data[5]), bedjet_temp_to_f(data[6]), data[7], data[8], data[9],
|
||||
data[10], data[11], data[12], data[length - 1]);
|
||||
ESP_LOGVV(TAG,
|
||||
"received DEBUG packet: set1=%01fF, set2=%01fF, air=%01fF; [7]=%d, [8]=%d, [9]=%d, [10]=%d, [11]=%d, "
|
||||
"[12]=%d, [-1]=%d",
|
||||
bedjet_temp_to_f(data[4]), bedjet_temp_to_f(data[5]), bedjet_temp_to_f(data[6]), data[7], data[8],
|
||||
data[9], data[10], data[11], data[12], data[length - 1]);
|
||||
|
||||
if (this->has_status()) {
|
||||
this->status_packet_->ambient_temp_step = data[6];
|
||||
@ -119,5 +127,35 @@ bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return `true` if the new packet is meaningfully different from the last seen packet. */
|
||||
bool BedjetCodec::compare(const uint8_t *data, uint16_t length) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length < 17) {
|
||||
// New packet looks small, skip it.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->buf_.packet_format != PACKET_FORMAT_V3_HOME ||
|
||||
this->buf_.packet_type != PACKET_TYPE_STATUS) { // No last seen packet, so take the new one.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data[1] != PACKET_FORMAT_V3_HOME || data[3] != PACKET_TYPE_STATUS) { // New packet is not a v3 status, skip it.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now coerce it to a status packet and compare some key fields
|
||||
const BedjetStatusPacket *test = reinterpret_cast<const BedjetStatusPacket *>(data);
|
||||
// These are fields that will only change due to explicit action.
|
||||
// That is why we do not check ambient or actual temp here, because those are environmental.
|
||||
bool explicit_fields_changed = this->buf_.mode != test->mode || this->buf_.fan_step != test->fan_step ||
|
||||
this->buf_.target_temp_step != test->target_temp_step;
|
||||
|
||||
return explicit_fields_changed;
|
||||
}
|
||||
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
@ -14,18 +14,6 @@ struct BedjetPacket {
|
||||
uint8_t data[2];
|
||||
};
|
||||
|
||||
struct BedjetFlags {
|
||||
/* uint8_t */
|
||||
int a_ : 1; // 0x80
|
||||
int b_ : 1; // 0x40
|
||||
int conn_test_passed : 1; ///< (0x20) Bit is set `1` if the last connection test passed.
|
||||
int leds_enabled : 1; ///< (0x10) Bit is set `1` if the LEDs on the device are enabled.
|
||||
int c_ : 1; // 0x08
|
||||
int units_setup : 1; ///< (0x04) Bit is set `1` if the device's units have been configured.
|
||||
int d_ : 1; // 0x02
|
||||
int beeps_muted : 1; ///< (0x01) Bit is set `1` if the device's sound output is muted.
|
||||
} __attribute__((packed));
|
||||
|
||||
enum BedjetPacketFormat : uint8_t {
|
||||
PACKET_FORMAT_DEBUG = 0x05, // 5
|
||||
PACKET_FORMAT_V3_HOME = 0x56, // 86
|
||||
@ -36,15 +24,25 @@ enum BedjetPacketType : uint8_t {
|
||||
PACKET_TYPE_DEBUG = 0x2,
|
||||
};
|
||||
|
||||
enum BedjetNotification : uint8_t {
|
||||
NOTIFY_NONE = 0, ///< No notification pending
|
||||
NOTIFY_FILTER = 1, ///< Clean Filter / Please check BedJet air filter and clean if necessary.
|
||||
NOTIFY_UPDATE = 2, ///< Firmware Update / A newer version of firmware is available.
|
||||
NOTIFY_UPDATE_FAIL = 3, ///< Firmware Update / Unable to connect to the firmware update server.
|
||||
NOTIFY_BIO_FAIL_CLOCK_NOT_SET = 4, ///< The specified sequence cannot be run because the clock is not set
|
||||
NOTIFY_BIO_FAIL_TOO_LONG = 5, ///< The specified sequence cannot be run because it contains steps that would be too
|
||||
///< long running from the current time.
|
||||
// Note: after handling a notification, send MAGIC_NOTIFY_ACK
|
||||
};
|
||||
|
||||
/** The format of a BedJet V3 status packet. */
|
||||
struct BedjetStatusPacket {
|
||||
// [0]
|
||||
uint8_t is_partial : 8; ///< `1` indicates that this is a partial packet, and more data can be read directly from the
|
||||
///< characteristic.
|
||||
bool is_partial : 8; ///< `1` indicates that this is a partial packet, and more data can be read directly from the
|
||||
///< characteristic.
|
||||
BedjetPacketFormat packet_format : 8; ///< BedjetPacketFormat::PACKET_FORMAT_V3_HOME for BedJet V3 status packet
|
||||
///< format. BedjetPacketFormat::PACKET_FORMAT_DEBUG for debugging packets.
|
||||
uint8_t
|
||||
expecting_length : 8; ///< The expected total length of the status packet after merging the additional packet.
|
||||
uint8_t expecting_length : 8; ///< The expected total length of the status packet after merging the extra packet.
|
||||
BedjetPacketType packet_type : 8; ///< Typically BedjetPacketType::PACKET_TYPE_STATUS for BedJet V3 status packet.
|
||||
|
||||
// [4]
|
||||
@ -77,11 +75,26 @@ struct BedjetStatusPacket {
|
||||
uint8_t shutdown_reason : 8; ///< The reason for the last device shutdown.
|
||||
|
||||
// [19-25]; the initial partial packet cuts off here after [19]
|
||||
// Skip 7 bytes?
|
||||
uint32_t _skip_1_ : 32; // Unknown 19-22 = 0x01810112
|
||||
|
||||
uint16_t _skip_2_ : 16; // Unknown 23-24 = 0x1310
|
||||
uint8_t _skip_3_ : 8; // Unknown 25 = 0x00
|
||||
uint8_t unused_1 : 8; // Unknown [19] = 0x01
|
||||
uint8_t unused_2 : 8; // Unknown [20] = 0x81
|
||||
uint8_t unused_3 : 8; // Unknown [21] = 0x01
|
||||
|
||||
// [22]: 0x2=is_dual_zone, ...?
|
||||
struct {
|
||||
int unused_1 : 1; // 0x80
|
||||
int unused_2 : 1; // 0x40
|
||||
int unused_3 : 1; // 0x20
|
||||
int unused_4 : 1; // 0x10
|
||||
int unused_5 : 1; // 0x8
|
||||
int unused_6 : 1; // 0x4
|
||||
bool is_dual_zone : 1; /// Is part of a Dual Zone configuration
|
||||
int unused_7 : 1; // 0x1
|
||||
} dual_zone_flags;
|
||||
|
||||
uint8_t unused_4 : 8; // Unknown 23-24 = 0x1310
|
||||
uint8_t unused_5 : 8; // Unknown 23-24 = 0x1310
|
||||
uint8_t unused_6 : 8; // Unknown 25 = 0x00
|
||||
|
||||
// [26]
|
||||
// 0x18(24) = "Connection test has completed OK"
|
||||
@ -89,10 +102,27 @@ struct BedjetStatusPacket {
|
||||
uint8_t update_phase : 8; ///< The current status/phase of a firmware update.
|
||||
|
||||
// [27]
|
||||
// FIXME: cannot nest packed struct of matching length here?
|
||||
/* BedjetFlags */ uint8_t flags : 8; /// See BedjetFlags for the packed byte flags.
|
||||
// [28-31]; 20+11 bytes
|
||||
uint32_t _skip_4_ : 32; // Unknown
|
||||
union {
|
||||
uint8_t flags_packed;
|
||||
struct {
|
||||
/* uint8_t */
|
||||
int unused_1 : 1; // 0x80
|
||||
int unused_2 : 1; // 0x40
|
||||
bool conn_test_passed : 1; ///< (0x20) Bit is set `1` if the last connection test passed.
|
||||
bool leds_enabled : 1; ///< (0x10) Bit is set `1` if the LEDs on the device are enabled.
|
||||
int unused_3 : 1; // 0x08
|
||||
bool units_setup : 1; ///< (0x04) Bit is set `1` if the device's units have been configured.
|
||||
int unused_4 : 1; // 0x02
|
||||
bool beeps_muted : 1; ///< (0x01) Bit is set `1` if the device's sound output is muted.
|
||||
} __attribute__((packed)) flags;
|
||||
};
|
||||
|
||||
// [28] = (biorhythm?) sequence step
|
||||
uint8_t bio_sequence_step : 8; /// Biorhythm sequence step number
|
||||
// [29] = notify_code:
|
||||
BedjetNotification notify_code : 8; /// See BedjetNotification
|
||||
|
||||
uint16_t unused_7 : 16; // Unknown
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
@ -127,7 +157,7 @@ struct BedjetStatusPacket {
|
||||
* - Set current time
|
||||
* The BedJet needs to have its clock set properly in order to run the biorhythm programs, which might
|
||||
* contain time-of-day based step rules.
|
||||
* - BedjetPacket#command = BedjetCommand::CMD_SET_TIME
|
||||
* - BedjetPacket#command = BedjetCommand::CMD_SET_CLOCK
|
||||
* - BedjetPacket#data [0] is hours, [1] is minutes
|
||||
*/
|
||||
class BedjetCodec {
|
||||
@ -136,13 +166,15 @@ class BedjetCodec {
|
||||
BedjetPacket *get_set_target_temp_request(float temperature);
|
||||
BedjetPacket *get_set_fan_speed_request(uint8_t fan_step);
|
||||
BedjetPacket *get_set_time_request(uint8_t hour, uint8_t minute);
|
||||
BedjetPacket *get_set_runtime_remaining_request(uint8_t hour, uint8_t minute);
|
||||
|
||||
bool decode_notify(const uint8_t *data, uint16_t length);
|
||||
void decode_extra(const uint8_t *data, uint16_t length);
|
||||
bool compare(const uint8_t *data, uint16_t length);
|
||||
|
||||
inline bool has_status() { return this->status_packet_.has_value(); }
|
||||
const optional<BedjetStatusPacket> &get_status_packet() const { return this->status_packet_; }
|
||||
void clear_status() { this->status_packet_.reset(); }
|
||||
inline bool has_status() { return this->status_packet_ != nullptr; }
|
||||
const BedjetStatusPacket *get_status_packet() const { return this->status_packet_; }
|
||||
void clear_status() { this->status_packet_ = nullptr; }
|
||||
|
||||
protected:
|
||||
BedjetPacket *clean_packet_();
|
||||
@ -151,7 +183,7 @@ class BedjetCodec {
|
||||
|
||||
BedjetPacket packet_;
|
||||
|
||||
optional<BedjetStatusPacket> status_packet_;
|
||||
BedjetStatusPacket *status_packet_;
|
||||
BedjetStatusPacket buf_;
|
||||
};
|
||||
|
@ -7,6 +7,14 @@ namespace bedjet {
|
||||
|
||||
static const char *const TAG = "bedjet";
|
||||
|
||||
/// Converts a BedJet fan step to a speed percentage, in the range of 5% to 100%.
|
||||
inline static uint8_t bedjet_fan_step_to_speed(const uint8_t fan) {
|
||||
// 0 = 5%
|
||||
// 19 = 100%
|
||||
return 5 * fan + 5;
|
||||
}
|
||||
inline static uint8_t bedjet_fan_speed_to_index(const uint8_t speed) { return speed / 5 - 1; }
|
||||
|
||||
enum BedjetMode : uint8_t {
|
||||
/// BedJet is Off
|
||||
MODE_STANDBY = 0,
|
||||
@ -62,14 +70,17 @@ enum BedjetButton : uint8_t {
|
||||
MAGIC_CONNTEST = 0x42,
|
||||
/// Request a firmware update. This will also restart the Bedjet.
|
||||
MAGIC_UPDATE = 0x43,
|
||||
/// Acknowledge notification handled. See BedjetNotify
|
||||
MAGIC_NOTIFY_ACK = 0x52,
|
||||
};
|
||||
|
||||
enum BedjetCommand : uint8_t {
|
||||
CMD_BUTTON = 0x1,
|
||||
CMD_SET_RUNTIME = 0x2,
|
||||
CMD_SET_TEMP = 0x3,
|
||||
CMD_STATUS = 0x6,
|
||||
CMD_SET_FAN = 0x7,
|
||||
CMD_SET_TIME = 0x8,
|
||||
CMD_SET_CLOCK = 0x8,
|
||||
};
|
||||
|
||||
#define BEDJET_FAN_STEP_NAMES_ \
|
||||
@ -78,8 +89,10 @@ enum BedjetCommand : uint8_t {
|
||||
"85%", "90%", "95%", "100%" \
|
||||
}
|
||||
|
||||
static const char *const BEDJET_FAN_STEP_NAMES[20] = BEDJET_FAN_STEP_NAMES_;
|
||||
static const std::string BEDJET_FAN_STEP_NAME_STRINGS[20] = BEDJET_FAN_STEP_NAMES_;
|
||||
static const uint8_t BEDJET_FAN_SPEED_COUNT = 20;
|
||||
|
||||
static const char *const BEDJET_FAN_STEP_NAMES[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
|
||||
static const std::string BEDJET_FAN_STEP_NAME_STRINGS[BEDJET_FAN_SPEED_COUNT] = BEDJET_FAN_STEP_NAMES_;
|
||||
static const std::set<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_;
|
||||
|
||||
} // namespace bedjet
|
||||
|
543
esphome/components/bedjet/bedjet_hub.cpp
Normal file
543
esphome/components/bedjet/bedjet_hub.cpp
Normal file
@ -0,0 +1,543 @@
|
||||
#include "bedjet_hub.h"
|
||||
#include "bedjet_child.h"
|
||||
#include "bedjet_const.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bedjet {
|
||||
|
||||
static const LogString *bedjet_button_to_string(BedjetButton button) {
|
||||
switch (button) {
|
||||
case BTN_OFF:
|
||||
return LOG_STR("OFF");
|
||||
case BTN_COOL:
|
||||
return LOG_STR("COOL");
|
||||
case BTN_HEAT:
|
||||
return LOG_STR("HEAT");
|
||||
case BTN_EXTHT:
|
||||
return LOG_STR("EXT HT");
|
||||
case BTN_TURBO:
|
||||
return LOG_STR("TURBO");
|
||||
case BTN_DRY:
|
||||
return LOG_STR("DRY");
|
||||
case BTN_M1:
|
||||
return LOG_STR("M1");
|
||||
case BTN_M2:
|
||||
return LOG_STR("M2");
|
||||
case BTN_M3:
|
||||
return LOG_STR("M3");
|
||||
default:
|
||||
return LOG_STR("unknown");
|
||||
}
|
||||
}
|
||||
|
||||
/* Public */
|
||||
|
||||
void BedJetHub::upgrade_firmware() {
|
||||
auto *pkt = this->codec_->get_button_request(MAGIC_UPDATE);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] MAGIC_UPDATE button failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
bool BedJetHub::button_heat() { return this->send_button(BTN_HEAT); }
|
||||
bool BedJetHub::button_ext_heat() { return this->send_button(BTN_EXTHT); }
|
||||
bool BedJetHub::button_turbo() { return this->send_button(BTN_TURBO); }
|
||||
bool BedJetHub::button_cool() { return this->send_button(BTN_COOL); }
|
||||
bool BedJetHub::button_dry() { return this->send_button(BTN_DRY); }
|
||||
bool BedJetHub::button_off() { return this->send_button(BTN_OFF); }
|
||||
bool BedJetHub::button_memory1() { return this->send_button(BTN_M1); }
|
||||
bool BedJetHub::button_memory2() { return this->send_button(BTN_M2); }
|
||||
bool BedJetHub::button_memory3() { return this->send_button(BTN_M3); }
|
||||
|
||||
bool BedJetHub::set_fan_index(uint8_t fan_speed_index) {
|
||||
if (fan_speed_index > 19) {
|
||||
ESP_LOGW(TAG, "Invalid fan speed index %d, expecting 0-19.", fan_speed_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *pkt = this->codec_->get_set_fan_speed_request(fan_speed_index);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] writing fan speed failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
uint8_t BedJetHub::get_fan_index() {
|
||||
auto *status = this->codec_->get_status_packet();
|
||||
if (status != nullptr) {
|
||||
return status->fan_step;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool BedJetHub::set_target_temp(float temp_c) {
|
||||
auto *pkt = this->codec_->get_set_target_temp_request(temp_c);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] writing target temp failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
bool BedJetHub::set_time_remaining(uint8_t hours, uint8_t mins) {
|
||||
// FIXME: this may fail depending on current mode or other restrictions enforced by the unit.
|
||||
auto *pkt = this->codec_->get_set_runtime_remaining_request(hours, mins);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] writing remaining runtime failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
bool BedJetHub::send_button(BedjetButton button) {
|
||||
auto *pkt = this->codec_->get_button_request(button);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] writing button %s failed, status=%d", this->get_name().c_str(),
|
||||
LOG_STR_ARG(bedjet_button_to_string(button)), status);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] writing button %s success", this->get_name().c_str(),
|
||||
LOG_STR_ARG(bedjet_button_to_string(button)));
|
||||
}
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
uint16_t BedJetHub::get_time_remaining() {
|
||||
auto *status = this->codec_->get_status_packet();
|
||||
if (status != nullptr) {
|
||||
return status->time_remaining_secs + status->time_remaining_mins * 60 + status->time_remaining_hrs * 3600;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Bluetooth/GATT */
|
||||
|
||||
uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) {
|
||||
if (!this->is_connected()) {
|
||||
if (!this->parent_->enabled) {
|
||||
ESP_LOGI(TAG, "[%s] Cannot write packet: Not connected, enabled=false", this->get_name().c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Cannot write packet: Not connected", this->get_name().c_str());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_cmd_, pkt->data_length + 1, (uint8_t *) &pkt->command,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
return status;
|
||||
}
|
||||
|
||||
/** Configures the local ESP BLE client to register (`true`) or unregister (`false`) for status notifications. */
|
||||
uint8_t BedJetHub::set_notify_(const bool enable) {
|
||||
uint8_t status;
|
||||
if (enable) {
|
||||
status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
this->char_handle_status_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
} else {
|
||||
status = esp_ble_gattc_unregister_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
this->char_handle_status_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
ESP_LOGV(TAG, "[%s] set_notify: enable=%d; result=%d", this->get_name().c_str(), enable, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool BedJetHub::discover_characteristics_() {
|
||||
bool result = true;
|
||||
esphome::ble_client::BLECharacteristic *chr;
|
||||
|
||||
if (!this->char_handle_cmd_) {
|
||||
chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_COMMAND_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No control service found at device, not a BedJet..?", this->get_name().c_str());
|
||||
result = false;
|
||||
} else {
|
||||
this->char_handle_cmd_ = chr->handle;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->char_handle_status_) {
|
||||
chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_STATUS_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No status service found at device, not a BedJet..?", this->get_name().c_str());
|
||||
result = false;
|
||||
} else {
|
||||
this->char_handle_status_ = chr->handle;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->config_descr_status_) {
|
||||
// We also need to obtain the config descriptor for this handle.
|
||||
// Otherwise once we set node_state=Established, the parent will flush all handles/descriptors, and we won't be
|
||||
// able to look it up.
|
||||
auto *descr = this->parent_->get_config_descriptor(this->char_handle_status_);
|
||||
if (descr == nullptr) {
|
||||
ESP_LOGW(TAG, "No config descriptor for status handle 0x%x. Will not be able to receive status notifications",
|
||||
this->char_handle_status_);
|
||||
result = false;
|
||||
} else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
|
||||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_,
|
||||
descr->uuid.to_string().c_str());
|
||||
result = false;
|
||||
} else {
|
||||
this->config_descr_status_ = descr->handle;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->char_handle_name_) {
|
||||
chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_NAME_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No name service found at device, not a BedJet..?", this->get_name().c_str());
|
||||
result = false;
|
||||
} else {
|
||||
this->char_handle_name_ = chr->handle;
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "[%s] Discovered service characteristics: ", this->get_name().c_str());
|
||||
ESP_LOGI(TAG, " - Command char: 0x%x", this->char_handle_cmd_);
|
||||
ESP_LOGI(TAG, " - Status char: 0x%x", this->char_handle_status_);
|
||||
ESP_LOGI(TAG, " - config descriptor: 0x%x", this->config_descr_status_);
|
||||
ESP_LOGI(TAG, " - Name char: 0x%x", this->char_handle_name_);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGV(TAG, "Disconnected: reason=%d", param->disconnect.reason);
|
||||
this->status_set_warning();
|
||||
this->dispatch_state_(false);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto result = this->discover_characteristics_();
|
||||
|
||||
if (result) {
|
||||
ESP_LOGD(TAG, "[%s] Services complete: obtained char handles.", this->get_name().c_str());
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->set_notify_(true);
|
||||
|
||||
#ifdef USE_TIME
|
||||
if (this->time_id_.has_value()) {
|
||||
this->send_local_time();
|
||||
}
|
||||
#endif
|
||||
|
||||
this->dispatch_state_(true);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Failed discovering service characteristics.", this->get_name().c_str());
|
||||
this->parent()->set_enabled(false);
|
||||
this->status_set_warning();
|
||||
this->dispatch_state_(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
if (param->write.status == ESP_GATT_INVALID_ATTR_LEN) {
|
||||
// This probably means that our hack for notify_en (8 bit vs 16 bit) didn't work right.
|
||||
// Should we try to fall back to BLEClient's way?
|
||||
ESP_LOGW(TAG, "[%s] Invalid attr length writing descr at handle 0x%04d, status=%d", this->get_name().c_str(),
|
||||
param->write.handle, param->write.status);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Error writing descr at handle 0x%04d, status=%d", this->get_name().c_str(),
|
||||
param->write.handle, param->write.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%s] Write to handle 0x%04x status=%d", this->get_name().c_str(), param->write.handle,
|
||||
param->write.status);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error writing char at handle 0x%04d, status=%d", param->write.handle, param->write.status);
|
||||
break;
|
||||
}
|
||||
if (param->write.handle == this->char_handle_cmd_) {
|
||||
if (this->force_refresh_) {
|
||||
// Command write was successful. Publish the pending state, hoping that notify will kick in.
|
||||
// FIXME: better to wait until we know the status has changed
|
||||
this->dispatch_status_();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent_->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
|
||||
if (param->read.handle == this->char_handle_status_) {
|
||||
// This is the additional packet that doesn't fit in the notify packet.
|
||||
this->codec_->decode_extra(param->read.value, param->read.value_len);
|
||||
this->status_packet_ready_();
|
||||
} else if (param->read.handle == this->char_handle_name_) {
|
||||
// The data should represent the name.
|
||||
if (param->read.status == ESP_GATT_OK && param->read.value_len > 0) {
|
||||
std::string bedjet_name(reinterpret_cast<char const *>(param->read.value), param->read.value_len);
|
||||
ESP_LOGV(TAG, "[%s] Got BedJet name: '%s'", this->get_name().c_str(), bedjet_name.c_str());
|
||||
this->set_name_(bedjet_name);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
// This event means that ESP received the request to enable notifications on the client side. But we also have to
|
||||
// tell the server that we want it to send notifications. Normally BLEClient parent would handle this
|
||||
// automatically, but as soon as we set our status to Established, the parent is going to purge all the
|
||||
// service/char/descriptor handles, and then get_config_descriptor() won't work anymore. There's no way to disable
|
||||
// the BLEClient parent behavior, so our only option is to write the handle anyway, and hope a double-write
|
||||
// doesn't break anything.
|
||||
|
||||
if (param->reg_for_notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Register for notify on unexpected handle 0x%04x, expecting 0x%04x",
|
||||
this->get_name().c_str(), param->reg_for_notify.handle, this->char_handle_status_);
|
||||
break;
|
||||
}
|
||||
|
||||
this->write_notify_config_descriptor_(true);
|
||||
this->last_notify_ = 0;
|
||||
this->force_refresh_ = true;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
// This event is not handled by the parent BLEClient, so we need to do this either way.
|
||||
if (param->unreg_for_notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Unregister for notify on unexpected handle 0x%04x, expecting 0x%04x",
|
||||
this->get_name().c_str(), param->unreg_for_notify.handle, this->char_handle_status_);
|
||||
break;
|
||||
}
|
||||
|
||||
this->write_notify_config_descriptor_(false);
|
||||
this->last_notify_ = 0;
|
||||
// Now we wait until the next update() poll to re-register notify...
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (this->processing_)
|
||||
break;
|
||||
|
||||
if (param->notify.conn_id != this->parent_->get_conn_id()) {
|
||||
ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x",
|
||||
this->get_name().c_str(), this->parent_->get_conn_id(), param->notify.conn_id);
|
||||
// FIXME: bug in BLEClient holding wrong conn_id.
|
||||
}
|
||||
|
||||
if (param->notify.handle != this->char_handle_status_) {
|
||||
ESP_LOGW(TAG, "[%s] Unexpected notify handle, wanted %04X, got %04X", this->get_name().c_str(),
|
||||
this->char_handle_status_, param->notify.handle);
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME: notify events come in every ~200-300 ms, which is too fast to be helpful. So we
|
||||
// throttle the updates to once every MIN_NOTIFY_THROTTLE (5 seconds).
|
||||
// Another idea would be to keep notify off by default, and use update() as an opportunity to turn on
|
||||
// notify to get enough data to update status, then turn off notify again.
|
||||
|
||||
uint32_t now = millis();
|
||||
auto delta = now - this->last_notify_;
|
||||
|
||||
if (!this->force_refresh_ && this->codec_->compare(param->notify.value, param->notify.value_len)) {
|
||||
// If the packet is meaningfully different, trigger children as well
|
||||
this->force_refresh_ = true;
|
||||
ESP_LOGV(TAG, "[%s] Incoming packet indicates a significant change.", this->get_name().c_str());
|
||||
}
|
||||
|
||||
if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) {
|
||||
// Set reentrant flag to prevent processing multiple packets.
|
||||
this->processing_ = true;
|
||||
ESP_LOGVV(TAG, "[%s] Decoding packet: last=%d, delta=%d, force=%s", this->get_name().c_str(),
|
||||
this->last_notify_, delta, this->force_refresh_ ? "y" : "n");
|
||||
bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len);
|
||||
|
||||
if (needs_extra) {
|
||||
// This means the packet was partial, so read the status characteristic to get the second part.
|
||||
// Ideally this will complete quickly. We won't process additional notification events until it does.
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str());
|
||||
}
|
||||
} else {
|
||||
this->status_packet_ready_();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGVV(TAG, "[%s] gattc unhandled event: enum=%d", this->get_name().c_str(), event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void BedJetHub::status_packet_ready_() {
|
||||
this->last_notify_ = millis();
|
||||
this->processing_ = false;
|
||||
|
||||
if (this->force_refresh_) {
|
||||
// If we requested an immediate update, do that now.
|
||||
this->update();
|
||||
this->force_refresh_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reimplementation of BLEClient.gattc_event_handler() for ESP_GATTC_REG_FOR_NOTIFY_EVT.
|
||||
*
|
||||
* This is a copy of ble_client's automatic handling of `ESP_GATTC_REG_FOR_NOTIFY_EVT`, in order
|
||||
* to undo the same on unregister. It also allows us to maintain the config descriptor separately,
|
||||
* since the parent BLEClient is going to purge all descriptors once we set our connection status
|
||||
* to `Established`.
|
||||
*/
|
||||
uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
|
||||
auto handle = this->config_descr_status_;
|
||||
if (handle == 0) {
|
||||
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", this->char_handle_status_);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits.
|
||||
uint16_t notify_en = enable ? 1 : 0;
|
||||
auto status = esp_ble_gattc_write_char_descr(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), handle,
|
||||
sizeof(notify_en), (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
|
||||
return status;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(),
|
||||
enable ? "true" : "false", handle, this->parent_->get_conn_id());
|
||||
return ESP_GATT_OK;
|
||||
}
|
||||
|
||||
/* Time Component */
|
||||
|
||||
#ifdef USE_TIME
|
||||
void BedJetHub::send_local_time() {
|
||||
if (this->time_id_.has_value()) {
|
||||
auto *time_id = *this->time_id_;
|
||||
time::ESPTime now = time_id->now();
|
||||
if (now.is_valid()) {
|
||||
this->set_clock(now.hour, now.minute);
|
||||
ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
|
||||
}
|
||||
}
|
||||
|
||||
void BedJetHub::setup_time_() {
|
||||
if (this->time_id_.has_value()) {
|
||||
this->send_local_time();
|
||||
auto *time_id = *this->time_id_;
|
||||
time_id->add_on_time_sync_callback([this] { this->send_local_time(); });
|
||||
} else {
|
||||
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void BedJetHub::set_clock(uint8_t hour, uint8_t minute) {
|
||||
if (!this->is_connected()) {
|
||||
ESP_LOGV(TAG, "[%s] Not connected, cannot send time.", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
BedjetPacket *pkt = this->codec_->get_set_time_request(hour, minute);
|
||||
auto status = this->write_bedjet_packet_(pkt);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Failed setting BedJet clock: %d", status);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] BedJet clock set to: %d:%02d", this->get_name().c_str(), hour, minute);
|
||||
}
|
||||
}
|
||||
|
||||
/* Internal */
|
||||
|
||||
void BedJetHub::loop() {}
|
||||
void BedJetHub::update() { this->dispatch_status_(); }
|
||||
|
||||
void BedJetHub::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str());
|
||||
ESP_LOGCONFIG(TAG, " ble_client.app_id: %d", this->parent()->app_id);
|
||||
ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->get_conn_id());
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
|
||||
for (auto *child : this->children_) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void BedJetHub::dispatch_state_(bool is_ready) {
|
||||
for (auto *child : this->children_) {
|
||||
child->on_bedjet_state(is_ready);
|
||||
}
|
||||
}
|
||||
|
||||
void BedJetHub::dispatch_status_() {
|
||||
auto *status = this->codec_->get_status_packet();
|
||||
|
||||
if (!this->is_connected()) {
|
||||
ESP_LOGD(TAG, "[%s] Not connected, will not send status.", this->get_name().c_str());
|
||||
} else if (status != nullptr) {
|
||||
ESP_LOGD(TAG, "[%s] Notifying %d children of latest status @%p.", this->get_name().c_str(), this->children_.size(),
|
||||
status);
|
||||
for (auto *child : this->children_) {
|
||||
child->on_status(status);
|
||||
}
|
||||
} else {
|
||||
uint32_t now = millis();
|
||||
uint32_t diff = now - this->last_notify_;
|
||||
|
||||
if (this->last_notify_ == 0) {
|
||||
// This means we're connected and haven't received a notification, so it likely means that the BedJet is off.
|
||||
// However, it could also mean that it's running, but failing to send notifications.
|
||||
// We can try to unregister for notifications now, and then re-register, hoping to clear it up...
|
||||
// But how do we know for sure which state we're in, and how do we actually clear out the buggy state?
|
||||
|
||||
ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str());
|
||||
} else if (diff > NOTIFY_WARN_THRESHOLD) {
|
||||
ESP_LOGW(TAG, "[%s] Last GATT notify was %d seconds ago.", this->get_name().c_str(), diff / 1000);
|
||||
}
|
||||
|
||||
if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
|
||||
ESP_LOGW(TAG, "[%s] Timed out after %d sec. Retrying...", this->get_name().c_str(), this->timeout_);
|
||||
// set_enabled(false) will only close the connection if state != IDLE.
|
||||
this->parent()->set_state(espbt::ClientState::CONNECTING);
|
||||
this->parent()->set_enabled(false);
|
||||
this->parent()->set_enabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BedJetHub::register_child(BedJetClient *obj) {
|
||||
this->children_.push_back(obj);
|
||||
obj->set_parent(this);
|
||||
}
|
||||
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
178
esphome/components/bedjet/bedjet_hub.h
Normal file
178
esphome/components/bedjet/bedjet_hub.h
Normal file
@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "bedjet_child.h"
|
||||
#include "bedjet_codec.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace bedjet {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
// Forward declare BedJetClient
|
||||
class BedJetClient;
|
||||
|
||||
static const espbt::ESPBTUUID BEDJET_SERVICE_UUID = espbt::ESPBTUUID::from_raw("00001000-bed0-0080-aa55-4265644a6574");
|
||||
static const espbt::ESPBTUUID BEDJET_STATUS_UUID = espbt::ESPBTUUID::from_raw("00002000-bed0-0080-aa55-4265644a6574");
|
||||
static const espbt::ESPBTUUID BEDJET_COMMAND_UUID = espbt::ESPBTUUID::from_raw("00002004-bed0-0080-aa55-4265644a6574");
|
||||
static const espbt::ESPBTUUID BEDJET_NAME_UUID = espbt::ESPBTUUID::from_raw("00002001-bed0-0080-aa55-4265644a6574");
|
||||
|
||||
/**
|
||||
* Hub component connecting to the BedJet device over Bluetooth.
|
||||
*/
|
||||
class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
public:
|
||||
/* BedJet functionality exposed to `BedJetClient` children and/or accessible from action lambdas. */
|
||||
|
||||
/** Attempts to check for and apply firmware updates. */
|
||||
void upgrade_firmware();
|
||||
|
||||
/** Press the OFF button. */
|
||||
bool button_off();
|
||||
/** Press the HEAT button. */
|
||||
bool button_heat();
|
||||
/** Press the EXT HT button. */
|
||||
bool button_ext_heat();
|
||||
/** Press the TURBO button. */
|
||||
bool button_turbo();
|
||||
/** Press the COOL button. */
|
||||
bool button_cool();
|
||||
/** Press the DRY button. */
|
||||
bool button_dry();
|
||||
/** Press the M1 (memory recall) button. */
|
||||
bool button_memory1();
|
||||
/** Press the M2 (memory recall) button. */
|
||||
bool button_memory2();
|
||||
/** Press the M3 (memory recall) button. */
|
||||
bool button_memory3();
|
||||
|
||||
/** Send the `button`. */
|
||||
bool send_button(BedjetButton button);
|
||||
|
||||
/** Set the target temperature to `temp_c` in °C. */
|
||||
bool set_target_temp(float temp_c);
|
||||
|
||||
/** Set the fan speed to a stepped index in the range 0-19. */
|
||||
bool set_fan_index(uint8_t fan_speed_index);
|
||||
|
||||
/** Set the fan speed to a percent in the range 5% - 100%, at 5% increments. */
|
||||
bool set_fan_speed(uint8_t fan_speed_pct) { return this->set_fan_index(bedjet_fan_speed_to_index(fan_speed_pct)); }
|
||||
|
||||
/** Return the fan speed index, in the range 0-19. */
|
||||
uint8_t get_fan_index();
|
||||
|
||||
/** Return the fan speed as a percent in the range 5%-100%. */
|
||||
uint8_t get_fan_speed() { return bedjet_fan_step_to_speed(this->get_fan_index()); }
|
||||
|
||||
/** Set the operational runtime remaining.
|
||||
*
|
||||
* The unit establishes and enforces runtime limits for some modes, so this call is not guaranteed to succeed.
|
||||
*/
|
||||
bool set_time_remaining(uint8_t hours, uint8_t mins);
|
||||
|
||||
/** Return the remaining runtime, in seconds. */
|
||||
uint16_t get_time_remaining();
|
||||
|
||||
/** @return `true` if the `BLEClient::node_state` is `ClientState::ESTABLISHED`. */
|
||||
bool is_connected() { return this->node_state == espbt::ClientState::ESTABLISHED; }
|
||||
|
||||
bool has_status() { return this->codec_->has_status(); }
|
||||
const BedjetStatusPacket *get_status_packet() const { return this->codec_->get_status_packet(); }
|
||||
|
||||
/** Register a `BedJetClient` child component. */
|
||||
void register_child(BedJetClient *obj);
|
||||
|
||||
/** Set the status timeout.
|
||||
*
|
||||
* This is the max time to wait for a status update before the connection is presumed unusable.
|
||||
*/
|
||||
void set_status_timeout(uint32_t timeout) { this->timeout_ = timeout; }
|
||||
|
||||
#ifdef USE_TIME
|
||||
/** Set the `time::RealTimeClock` implementation. */
|
||||
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
|
||||
/** Attempts to sync the local time (via `time_id`) to the BedJet device. */
|
||||
void send_local_time();
|
||||
#endif
|
||||
/** Attempt to set the BedJet device's clock to the specified time. */
|
||||
void set_clock(uint8_t hour, uint8_t minute);
|
||||
|
||||
/* Component overrides */
|
||||
|
||||
void loop() override;
|
||||
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; }
|
||||
|
||||
/** @return The BedJet's configured name, or the MAC address if not discovered yet. */
|
||||
std::string get_name() {
|
||||
if (this->name_.empty()) {
|
||||
return this->parent_->address_str();
|
||||
} else {
|
||||
return this->name_;
|
||||
}
|
||||
}
|
||||
|
||||
/* BLEClient overrides */
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
protected:
|
||||
std::vector<BedJetClient *> children_;
|
||||
void dispatch_status_();
|
||||
void dispatch_state_(bool is_ready);
|
||||
|
||||
#ifdef USE_TIME
|
||||
/** Initializes time sync callbacks to support syncing current time to the BedJet. */
|
||||
void setup_time_();
|
||||
optional<time::RealTimeClock *> time_id_{};
|
||||
#endif
|
||||
|
||||
uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};
|
||||
static const uint32_t MIN_NOTIFY_THROTTLE = 15000;
|
||||
static const uint32_t NOTIFY_WARN_THRESHOLD = 300000;
|
||||
static const uint32_t DEFAULT_STATUS_TIMEOUT = 900000;
|
||||
|
||||
uint8_t set_notify_(bool enable);
|
||||
/** Send the `BedjetPacket` to the device. */
|
||||
uint8_t write_bedjet_packet_(BedjetPacket *pkt);
|
||||
void set_name_(const std::string &name) { this->name_ = name; }
|
||||
|
||||
std::string name_;
|
||||
|
||||
uint32_t last_notify_ = 0;
|
||||
inline void status_packet_ready_();
|
||||
bool force_refresh_ = false;
|
||||
bool processing_ = false;
|
||||
|
||||
std::unique_ptr<BedjetCodec> codec_;
|
||||
|
||||
bool discover_characteristics_();
|
||||
uint16_t char_handle_cmd_;
|
||||
uint16_t char_handle_name_;
|
||||
uint16_t char_handle_status_;
|
||||
uint16_t config_descr_status_;
|
||||
|
||||
uint8_t write_notify_config_descriptor_(bool enable);
|
||||
};
|
||||
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@ -1,52 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate, ble_client, time
|
||||
from esphome.const import (
|
||||
CONF_HEAT_MODE,
|
||||
CONF_ID,
|
||||
CONF_RECEIVE_TIMEOUT,
|
||||
CONF_TIME_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jhansche"]
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
bedjet_ns = cg.esphome_ns.namespace("bedjet")
|
||||
Bedjet = bedjet_ns.class_(
|
||||
"Bedjet", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent
|
||||
)
|
||||
BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode")
|
||||
BEDJET_HEAT_MODES = {
|
||||
"heat": BedjetHeatMode.HEAT_MODE_HEAT,
|
||||
"extended": BedjetHeatMode.HEAT_MODE_EXTENDED,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
climate.CLIMATE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Bedjet),
|
||||
cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum(
|
||||
BEDJET_HEAT_MODES, lower=True
|
||||
),
|
||||
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
cv.Optional(
|
||||
CONF_RECEIVE_TIMEOUT, default="0s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("30s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await climate.register_climate(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
cg.add(var.set_heating_mode(config[CONF_HEAT_MODE]))
|
||||
if CONF_TIME_ID in config:
|
||||
time_ = await cg.get_variable(config[CONF_TIME_ID])
|
||||
cg.add(var.set_time_id(time_))
|
||||
if CONF_RECEIVE_TIMEOUT in config:
|
||||
cg.add(var.set_status_timeout(config[CONF_RECEIVE_TIMEOUT]))
|
65
esphome/components/bedjet/climate/__init__.py
Normal file
65
esphome/components/bedjet/climate/__init__.py
Normal file
@ -0,0 +1,65 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate, ble_client
|
||||
from esphome.const import (
|
||||
CONF_HEAT_MODE,
|
||||
CONF_ID,
|
||||
CONF_RECEIVE_TIMEOUT,
|
||||
CONF_TIME_ID,
|
||||
)
|
||||
from .. import (
|
||||
BEDJET_CLIENT_SCHEMA,
|
||||
bedjet_ns,
|
||||
register_bedjet_child,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CODEOWNERS = ["@jhansche"]
|
||||
DEPENDENCIES = ["bedjet"]
|
||||
|
||||
BedJetClimate = bedjet_ns.class_("BedJetClimate", climate.Climate, cg.PollingComponent)
|
||||
BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode")
|
||||
BEDJET_HEAT_MODES = {
|
||||
"heat": BedjetHeatMode.HEAT_MODE_HEAT,
|
||||
"extended": BedjetHeatMode.HEAT_MODE_EXTENDED,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
climate.CLIMATE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BedJetClimate),
|
||||
cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum(
|
||||
BEDJET_HEAT_MODES, lower=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(
|
||||
# TODO: remove compat layer.
|
||||
{
|
||||
cv.Optional(ble_client.CONF_BLE_CLIENT_ID): cv.invalid(
|
||||
"The 'ble_client_id' option has been removed. Please migrate "
|
||||
"to the new `bedjet_id` option in the `bedjet` component.\n"
|
||||
"See https://esphome.io/components/climate/bedjet.html"
|
||||
),
|
||||
cv.Optional(CONF_TIME_ID): cv.invalid(
|
||||
"The 'time_id' option has been moved to the `bedjet` component."
|
||||
),
|
||||
cv.Optional(CONF_RECEIVE_TIMEOUT): cv.invalid(
|
||||
"The 'receive_timeout' option has been moved to the `bedjet` component."
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(BEDJET_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await climate.register_climate(var, config)
|
||||
await register_bedjet_child(var, config)
|
||||
|
||||
cg.add(var.set_heating_mode(config[CONF_HEAT_MODE]))
|
354
esphome/components/bedjet/climate/bedjet_climate.cpp
Normal file
354
esphome/components/bedjet/climate/bedjet_climate.cpp
Normal file
@ -0,0 +1,354 @@
|
||||
#include "bedjet_climate.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace bedjet {
|
||||
|
||||
using namespace esphome::climate;
|
||||
|
||||
/// Converts a BedJet temp step into degrees Celsius.
|
||||
float bedjet_temp_to_c(const uint8_t temp) {
|
||||
// BedJet temp is "C*2"; to get C, divide by 2.
|
||||
return temp / 2.0f;
|
||||
}
|
||||
|
||||
static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) {
|
||||
if (fan_step < BEDJET_FAN_SPEED_COUNT)
|
||||
return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) {
|
||||
for (int i = 0; i < BEDJET_FAN_SPEED_COUNT; i++) {
|
||||
if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline BedjetButton heat_button(BedjetHeatMode mode) {
|
||||
return mode == HEAT_MODE_EXTENDED ? BTN_EXTHT : BTN_HEAT;
|
||||
}
|
||||
|
||||
std::string BedJetClimate::describe() { return "BedJet Climate"; }
|
||||
|
||||
void BedJetClimate::dump_config() {
|
||||
LOG_CLIMATE("", "BedJet Climate", this);
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported modes:");
|
||||
for (auto mode : traits.get_supported_modes()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_mode_to_string(mode)));
|
||||
}
|
||||
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
||||
ESP_LOGCONFIG(TAG, " - BedJet heating mode: EXT HT");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " - BedJet heating mode: HEAT");
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported fan modes:");
|
||||
for (const auto &mode : traits.get_supported_fan_modes()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(mode)));
|
||||
}
|
||||
for (const auto &mode : traits.get_supported_custom_fan_modes()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s (c)", mode.c_str());
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported presets:");
|
||||
for (auto preset : traits.get_supported_presets()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s", LOG_STR_ARG(climate_preset_to_string(preset)));
|
||||
}
|
||||
for (const auto &preset : traits.get_supported_custom_presets()) {
|
||||
ESP_LOGCONFIG(TAG, " - %s (c)", preset.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void BedJetClimate::setup() {
|
||||
// restore set points
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
ESP_LOGI(TAG, "Restored previous saved state.");
|
||||
restore->apply(this);
|
||||
} else {
|
||||
// Initial status is unknown until we connect
|
||||
this->reset_state_();
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets states to defaults. */
|
||||
void BedJetClimate::reset_state_() {
|
||||
this->mode = CLIMATE_MODE_OFF;
|
||||
this->action = CLIMATE_ACTION_IDLE;
|
||||
this->target_temperature = NAN;
|
||||
this->current_temperature = NAN;
|
||||
this->preset.reset();
|
||||
this->custom_preset.reset();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void BedJetClimate::loop() {}
|
||||
|
||||
void BedJetClimate::control(const ClimateCall &call) {
|
||||
ESP_LOGD(TAG, "Received BedJetClimate::control");
|
||||
if (!this->parent_->is_connected()) {
|
||||
ESP_LOGW(TAG, "Not connected, cannot handle control call yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (call.get_mode().has_value()) {
|
||||
ClimateMode mode = *call.get_mode();
|
||||
bool button_result;
|
||||
switch (mode) {
|
||||
case CLIMATE_MODE_OFF:
|
||||
button_result = this->parent_->button_off();
|
||||
break;
|
||||
case CLIMATE_MODE_HEAT:
|
||||
button_result = this->parent_->send_button(heat_button(this->heating_mode_));
|
||||
break;
|
||||
case CLIMATE_MODE_FAN_ONLY:
|
||||
button_result = this->parent_->button_cool();
|
||||
break;
|
||||
case CLIMATE_MODE_DRY:
|
||||
button_result = this->parent_->button_dry();
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unsupported mode: %d", mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (button_result) {
|
||||
this->mode = mode;
|
||||
// We're using (custom) preset for Turbo, EXT HT, & M1-3 presets, so changing climate mode will clear those
|
||||
this->custom_preset.reset();
|
||||
this->preset.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
auto target_temp = *call.get_target_temperature();
|
||||
auto result = this->parent_->set_target_temp(target_temp);
|
||||
|
||||
if (result) {
|
||||
this->target_temperature = target_temp;
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_preset().has_value()) {
|
||||
ClimatePreset preset = *call.get_preset();
|
||||
bool result;
|
||||
|
||||
if (preset == CLIMATE_PRESET_BOOST) {
|
||||
// We use BOOST preset for TURBO mode, which is a short-lived/high-heat mode.
|
||||
result = this->parent_->button_turbo();
|
||||
|
||||
if (result) {
|
||||
this->mode = CLIMATE_MODE_HEAT;
|
||||
this->preset = CLIMATE_PRESET_BOOST;
|
||||
this->custom_preset.reset();
|
||||
}
|
||||
} else if (preset == CLIMATE_PRESET_NONE && this->preset.has_value()) {
|
||||
if (this->mode == CLIMATE_MODE_HEAT && this->preset == CLIMATE_PRESET_BOOST) {
|
||||
// We were in heat mode with Boost preset, and now preset is set to None, so revert to normal heat.
|
||||
result = this->parent_->send_button(heat_button(this->heating_mode_));
|
||||
if (result) {
|
||||
this->preset.reset();
|
||||
this->custom_preset.reset();
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Ignoring preset '%s' call; with current mode '%s' and preset '%s'",
|
||||
LOG_STR_ARG(climate_preset_to_string(preset)), LOG_STR_ARG(climate_mode_to_string(this->mode)),
|
||||
LOG_STR_ARG(climate_preset_to_string(this->preset.value_or(CLIMATE_PRESET_NONE))));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported preset: %d", preset);
|
||||
return;
|
||||
}
|
||||
} else if (call.get_custom_preset().has_value()) {
|
||||
std::string preset = *call.get_custom_preset();
|
||||
bool result;
|
||||
|
||||
if (preset == "M1") {
|
||||
result = this->parent_->button_memory1();
|
||||
} else if (preset == "M2") {
|
||||
result = this->parent_->button_memory2();
|
||||
} else if (preset == "M3") {
|
||||
result = this->parent_->button_memory3();
|
||||
} else if (preset == "LTD HT") {
|
||||
result = this->parent_->button_heat();
|
||||
} else if (preset == "EXT HT") {
|
||||
result = this->parent_->button_ext_heat();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
this->custom_preset = preset;
|
||||
this->preset.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
// Climate fan mode only supports low/med/high, but the BedJet supports 5-100% increments.
|
||||
// We can still support a ClimateCall that requests low/med/high, and just translate it to a step increment here.
|
||||
auto fan_mode = *call.get_fan_mode();
|
||||
bool result;
|
||||
if (fan_mode == CLIMATE_FAN_LOW) {
|
||||
result = this->parent_->set_fan_speed(20);
|
||||
} else if (fan_mode == CLIMATE_FAN_MEDIUM) {
|
||||
result = this->parent_->set_fan_speed(50);
|
||||
} else if (fan_mode == CLIMATE_FAN_HIGH) {
|
||||
result = this->parent_->set_fan_speed(75);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%s] Unsupported fan mode: %s", this->get_name().c_str(),
|
||||
LOG_STR_ARG(climate_fan_mode_to_string(fan_mode)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
this->fan_mode = fan_mode;
|
||||
this->custom_fan_mode.reset();
|
||||
}
|
||||
} else if (call.get_custom_fan_mode().has_value()) {
|
||||
auto fan_mode = *call.get_custom_fan_mode();
|
||||
auto fan_index = bedjet_fan_speed_to_step(fan_mode);
|
||||
if (fan_index <= 19) {
|
||||
ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode.c_str(),
|
||||
fan_index);
|
||||
bool result = this->parent_->set_fan_index(fan_index);
|
||||
if (result) {
|
||||
this->custom_fan_mode = fan_mode;
|
||||
this->fan_mode.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BedJetClimate::on_bedjet_state(bool is_ready) {}
|
||||
|
||||
void BedJetClimate::on_status(const BedjetStatusPacket *data) {
|
||||
ESP_LOGV(TAG, "[%s] Handling on_status with data=%p", this->get_name().c_str(), (void *) data);
|
||||
|
||||
auto converted_temp = bedjet_temp_to_c(data->target_temp_step);
|
||||
if (converted_temp > 0)
|
||||
this->target_temperature = converted_temp;
|
||||
|
||||
converted_temp = bedjet_temp_to_c(data->ambient_temp_step);
|
||||
if (converted_temp > 0)
|
||||
this->current_temperature = converted_temp;
|
||||
|
||||
const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(data->fan_step);
|
||||
if (fan_mode_name != nullptr) {
|
||||
this->custom_fan_mode = *fan_mode_name;
|
||||
}
|
||||
|
||||
// TODO: Get biorhythm data to determine which preset (M1-3) is running, if any.
|
||||
switch (data->mode) {
|
||||
case MODE_WAIT: // Biorhythm "wait" step: device is idle
|
||||
case MODE_STANDBY:
|
||||
this->mode = CLIMATE_MODE_OFF;
|
||||
this->action = CLIMATE_ACTION_IDLE;
|
||||
this->fan_mode = CLIMATE_FAN_OFF;
|
||||
this->custom_preset.reset();
|
||||
this->preset.reset();
|
||||
break;
|
||||
|
||||
case MODE_HEAT:
|
||||
this->mode = CLIMATE_MODE_HEAT;
|
||||
this->action = CLIMATE_ACTION_HEATING;
|
||||
this->preset.reset();
|
||||
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
||||
this->set_custom_preset_("LTD HT");
|
||||
} else {
|
||||
this->custom_preset.reset();
|
||||
}
|
||||
break;
|
||||
|
||||
case MODE_EXTHT:
|
||||
this->mode = CLIMATE_MODE_HEAT;
|
||||
this->action = CLIMATE_ACTION_HEATING;
|
||||
this->preset.reset();
|
||||
if (this->heating_mode_ == HEAT_MODE_EXTENDED) {
|
||||
this->custom_preset.reset();
|
||||
} else {
|
||||
this->set_custom_preset_("EXT HT");
|
||||
}
|
||||
break;
|
||||
|
||||
case MODE_COOL:
|
||||
this->mode = CLIMATE_MODE_FAN_ONLY;
|
||||
this->action = CLIMATE_ACTION_COOLING;
|
||||
this->custom_preset.reset();
|
||||
this->preset.reset();
|
||||
break;
|
||||
|
||||
case MODE_DRY:
|
||||
this->mode = CLIMATE_MODE_DRY;
|
||||
this->action = CLIMATE_ACTION_DRYING;
|
||||
this->custom_preset.reset();
|
||||
this->preset.reset();
|
||||
break;
|
||||
|
||||
case MODE_TURBO:
|
||||
this->preset = CLIMATE_PRESET_BOOST;
|
||||
this->custom_preset.reset();
|
||||
this->mode = CLIMATE_MODE_HEAT;
|
||||
this->action = CLIMATE_ACTION_HEATING;
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "[%s] Unexpected mode: 0x%02X", this->get_name().c_str(), data->mode);
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "[%s] After on_status, new mode=%s", this->get_name().c_str(),
|
||||
LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||
// FIXME: compare new state to previous state.
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
/** Attempts to update the climate device from the last received BedjetStatusPacket.
|
||||
*
|
||||
* This will be called from #on_status() when the parent dispatches new status packets,
|
||||
* and from #update() when the polling interval is triggered.
|
||||
*
|
||||
* @return `true` if the status has been applied; `false` if there is nothing to apply.
|
||||
*/
|
||||
bool BedJetClimate::update_status_() {
|
||||
if (!this->parent_->is_connected())
|
||||
return false;
|
||||
if (!this->parent_->has_status())
|
||||
return false;
|
||||
|
||||
auto *status = this->parent_->get_status_packet();
|
||||
|
||||
if (status == nullptr)
|
||||
return false;
|
||||
|
||||
this->on_status(status);
|
||||
|
||||
if (this->is_valid_()) {
|
||||
// TODO: only if state changed?
|
||||
this->publish_state();
|
||||
this->status_clear_warning();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BedJetClimate::update() {
|
||||
ESP_LOGD(TAG, "[%s] update()", this->get_name().c_str());
|
||||
// TODO: if the hub component is already polling, do we also need to include polling?
|
||||
// We're already going to get on_status() at the hub's polling interval.
|
||||
auto result = this->update_status_();
|
||||
ESP_LOGD(TAG, "[%s] update_status result=%s", this->get_name().c_str(), result ? "true" : "false");
|
||||
}
|
||||
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@ -1,53 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "bedjet_base.h"
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
#include "esphome/components/bedjet/bedjet_child.h"
|
||||
#include "esphome/components/bedjet/bedjet_codec.h"
|
||||
#include "esphome/components/bedjet/bedjet_hub.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace bedjet {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
static const espbt::ESPBTUUID BEDJET_SERVICE_UUID = espbt::ESPBTUUID::from_raw("00001000-bed0-0080-aa55-4265644a6574");
|
||||
static const espbt::ESPBTUUID BEDJET_STATUS_UUID = espbt::ESPBTUUID::from_raw("00002000-bed0-0080-aa55-4265644a6574");
|
||||
static const espbt::ESPBTUUID BEDJET_COMMAND_UUID = espbt::ESPBTUUID::from_raw("00002004-bed0-0080-aa55-4265644a6574");
|
||||
static const espbt::ESPBTUUID BEDJET_NAME_UUID = espbt::ESPBTUUID::from_raw("00002001-bed0-0080-aa55-4265644a6574");
|
||||
|
||||
class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent {
|
||||
class BedJetClimate : public climate::Climate, public BedJetClient, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
#ifdef USE_TIME
|
||||
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
|
||||
void send_local_time();
|
||||
#endif
|
||||
void set_clock(uint8_t hour, uint8_t minute);
|
||||
void set_status_timeout(uint32_t timeout) { this->timeout_ = timeout; }
|
||||
/* BedJetClient status update */
|
||||
void on_status(const BedjetStatusPacket *data) override;
|
||||
void on_bedjet_state(bool is_ready) override;
|
||||
std::string describe() override;
|
||||
|
||||
/** Sets the default strategy to use for climate::CLIMATE_MODE_HEAT. */
|
||||
void set_heating_mode(BedjetHeatMode mode) { this->heating_mode_ = mode; }
|
||||
|
||||
/** Attempts to check for and apply firmware updates. */
|
||||
void upgrade_firmware();
|
||||
|
||||
climate::ClimateTraits traits() override {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_action(true);
|
||||
@ -92,20 +73,8 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod
|
||||
protected:
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
|
||||
#ifdef USE_TIME
|
||||
void setup_time_();
|
||||
optional<time::RealTimeClock *> time_id_{};
|
||||
#endif
|
||||
|
||||
uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};
|
||||
BedjetHeatMode heating_mode_ = HEAT_MODE_HEAT;
|
||||
|
||||
static const uint32_t MIN_NOTIFY_THROTTLE = 5000;
|
||||
static const uint32_t NOTIFY_WARN_THRESHOLD = 300000;
|
||||
static const uint32_t DEFAULT_STATUS_TIMEOUT = 900000;
|
||||
|
||||
uint8_t set_notify_(bool enable);
|
||||
uint8_t write_bedjet_packet_(BedjetPacket *pkt);
|
||||
void reset_state_();
|
||||
bool update_status_();
|
||||
|
||||
@ -114,17 +83,6 @@ class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNod
|
||||
return !std::isnan(this->current_temperature) && !std::isnan(this->target_temperature) &&
|
||||
this->current_temperature > 1 && this->target_temperature > 1;
|
||||
}
|
||||
|
||||
uint32_t last_notify_ = 0;
|
||||
bool force_refresh_ = false;
|
||||
|
||||
std::unique_ptr<BedjetCodec> codec_;
|
||||
uint16_t char_handle_cmd_;
|
||||
uint16_t char_handle_name_;
|
||||
uint16_t char_handle_status_;
|
||||
uint16_t config_descr_status_;
|
||||
|
||||
uint8_t write_notify_config_descriptor_(bool enable);
|
||||
};
|
||||
|
||||
} // namespace bedjet
|
36
esphome/components/bedjet/fan/__init__.py
Normal file
36
esphome/components/bedjet/fan/__init__.py
Normal file
@ -0,0 +1,36 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import fan
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
)
|
||||
from .. import (
|
||||
BEDJET_CLIENT_SCHEMA,
|
||||
bedjet_ns,
|
||||
register_bedjet_child,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CODEOWNERS = ["@jhansche"]
|
||||
DEPENDENCIES = ["bedjet"]
|
||||
|
||||
BedJetFan = bedjet_ns.class_("BedJetFan", fan.Fan, cg.PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
fan.FAN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BedJetFan),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(BEDJET_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await fan.register_fan(var, config)
|
||||
await register_bedjet_child(var, config)
|
108
esphome/components/bedjet/fan/bedjet_fan.cpp
Normal file
108
esphome/components/bedjet/fan/bedjet_fan.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
#include "bedjet_fan.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace bedjet {
|
||||
|
||||
using namespace esphome::fan;
|
||||
|
||||
void BedJetFan::dump_config() { LOG_FAN("", "BedJet Fan", this); }
|
||||
std::string BedJetFan::describe() { return "BedJet Fan"; }
|
||||
|
||||
void BedJetFan::control(const fan::FanCall &call) {
|
||||
ESP_LOGD(TAG, "Received BedJetFan::control");
|
||||
if (!this->parent_->is_connected()) {
|
||||
ESP_LOGW(TAG, "Not connected, cannot handle control call yet.");
|
||||
return;
|
||||
}
|
||||
bool did_change = false;
|
||||
|
||||
if (call.get_state().has_value() && this->state != *call.get_state()) {
|
||||
// Turning off is easy:
|
||||
if (this->state && this->parent_->button_off()) {
|
||||
this->state = false;
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
// Turning on, we have to choose a specific mode; for now, use "COOL" mode
|
||||
// In the future we could configure the mode to use for fan.turn_on.
|
||||
if (this->parent_->button_cool()) {
|
||||
this->state = true;
|
||||
did_change = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore speed changes if not on or turning on
|
||||
if (this->state && call.get_speed().has_value()) {
|
||||
this->speed = *call.get_speed();
|
||||
this->parent_->set_fan_index(this->speed);
|
||||
did_change = true;
|
||||
}
|
||||
|
||||
if (did_change) {
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
void BedJetFan::on_status(const BedjetStatusPacket *data) {
|
||||
ESP_LOGVV(TAG, "[%s] Handling on_status with data=%p", this->get_name().c_str(), (void *) data);
|
||||
bool did_change = false;
|
||||
bool new_state = data->mode != MODE_STANDBY && data->mode != MODE_WAIT;
|
||||
|
||||
if (new_state != this->state) {
|
||||
this->state = new_state;
|
||||
did_change = true;
|
||||
}
|
||||
|
||||
if (data->fan_step != this->speed) {
|
||||
this->speed = data->fan_step;
|
||||
did_change = true;
|
||||
}
|
||||
|
||||
if (did_change) {
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
/** Attempts to update the fan device from the last received BedjetStatusPacket.
|
||||
*
|
||||
* This will be called from #on_status() when the parent dispatches new status packets,
|
||||
* and from #update() when the polling interval is triggered.
|
||||
*
|
||||
* @return `true` if the status has been applied; `false` if there is nothing to apply.
|
||||
*/
|
||||
bool BedJetFan::update_status_() {
|
||||
if (!this->parent_->is_connected())
|
||||
return false;
|
||||
if (!this->parent_->has_status())
|
||||
return false;
|
||||
|
||||
auto *status = this->parent_->get_status_packet();
|
||||
|
||||
if (status == nullptr)
|
||||
return false;
|
||||
|
||||
this->on_status(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
void BedJetFan::update() {
|
||||
ESP_LOGD(TAG, "[%s] update()", this->get_name().c_str());
|
||||
// TODO: if the hub component is already polling, do we also need to include polling?
|
||||
// We're already going to get on_status() at the hub's polling interval.
|
||||
auto result = this->update_status_();
|
||||
ESP_LOGD(TAG, "[%s] update_status result=%s", this->get_name().c_str(), result ? "true" : "false");
|
||||
}
|
||||
|
||||
/** Resets states to defaults. */
|
||||
void BedJetFan::reset_state_() {
|
||||
this->state = false;
|
||||
this->publish_state();
|
||||
}
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
40
esphome/components/bedjet/fan/bedjet_fan.h
Normal file
40
esphome/components/bedjet/fan/bedjet_fan.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/bedjet/bedjet_child.h"
|
||||
#include "esphome/components/bedjet/bedjet_codec.h"
|
||||
#include "esphome/components/bedjet/bedjet_hub.h"
|
||||
#include "esphome/components/fan/fan.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace bedjet {
|
||||
|
||||
class BedJetFan : public fan::Fan, public BedJetClient, public PollingComponent {
|
||||
public:
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
/* BedJetClient status update */
|
||||
void on_status(const BedjetStatusPacket *data) override;
|
||||
void on_bedjet_state(bool is_ready) override{};
|
||||
std::string describe() override;
|
||||
|
||||
fan::FanTraits get_traits() override { return fan::FanTraits(false, true, false, BEDJET_FAN_SPEED_COUNT); }
|
||||
|
||||
protected:
|
||||
void control(const fan::FanCall &call) override;
|
||||
|
||||
private:
|
||||
void reset_state_();
|
||||
bool update_status_();
|
||||
};
|
||||
|
||||
} // namespace bedjet
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@ -22,6 +22,7 @@ from esphome.const import (
|
||||
CONF_ON_PRESS,
|
||||
CONF_ON_RELEASE,
|
||||
CONF_ON_STATE,
|
||||
CONF_PUBLISH_INITIAL_STATE,
|
||||
CONF_STATE,
|
||||
CONF_TIMING,
|
||||
CONF_TRIGGER_ID,
|
||||
@ -29,6 +30,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_BATTERY_CHARGING,
|
||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||
DEVICE_CLASS_COLD,
|
||||
DEVICE_CLASS_CONNECTIVITY,
|
||||
DEVICE_CLASS_DOOR,
|
||||
@ -63,6 +65,7 @@ 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,
|
||||
@ -326,6 +329,7 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTBinarySensorComponent
|
||||
),
|
||||
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
@ -418,6 +422,8 @@ async def setup_binary_sensor_core_(var, config):
|
||||
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
if CONF_PUBLISH_INITIAL_STATE in config:
|
||||
cg.add(var.set_publish_initial_state(config[CONF_PUBLISH_INITIAL_STATE]))
|
||||
if CONF_INVERTED in config:
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
if CONF_FILTERS in config:
|
||||
@ -477,8 +483,8 @@ async def register_binary_sensor(var, config):
|
||||
await setup_binary_sensor_core_(var, config)
|
||||
|
||||
|
||||
async def new_binary_sensor(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
async def new_binary_sensor(config, *args):
|
||||
var = cg.new_Pvariable(config[CONF_ID], *args)
|
||||
await register_binary_sensor(var, config)
|
||||
return var
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
@ -37,7 +37,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
}
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
if (!is_initial) {
|
||||
if (!is_initial || this->publish_initial_state_) {
|
||||
this->state_callback_.call(state);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/filter.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
@ -58,6 +60,8 @@ class BinarySensor : public EntityBase {
|
||||
void add_filter(Filter *filter);
|
||||
void add_filters(const std::vector<Filter *> &filters);
|
||||
|
||||
void set_publish_initial_state(bool publish_initial_state) { this->publish_initial_state_ = publish_initial_state; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool state, bool is_initial);
|
||||
@ -80,6 +84,7 @@ class BinarySensor : public EntityBase {
|
||||
optional<std::string> device_class_{}; ///< Stores the override of the device class
|
||||
Filter *filter_list_{nullptr};
|
||||
bool has_state_{false};
|
||||
bool publish_initial_state_{false};
|
||||
Deduplicator<bool> publish_dedup_;
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
@ -13,6 +13,9 @@ void BinarySensorMap::loop() {
|
||||
case BINARY_SENSOR_MAP_TYPE_GROUP:
|
||||
this->process_group_();
|
||||
break;
|
||||
case BINARY_SENSOR_MAP_TYPE_SUM:
|
||||
this->process_sum_();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,6 +49,34 @@ void BinarySensorMap::process_group_() {
|
||||
this->last_mask_ = mask;
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
this->last_mask_ = mask;
|
||||
}
|
||||
|
||||
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
|
||||
BinarySensorMapChannel sensor_channel{
|
||||
.binary_sensor = sensor,
|
||||
|
@ -4,11 +4,14 @@
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace binary_sensor_map {
|
||||
|
||||
enum BinarySensorMapType {
|
||||
BINARY_SENSOR_MAP_TYPE_GROUP,
|
||||
BINARY_SENSOR_MAP_TYPE_SUM,
|
||||
};
|
||||
|
||||
struct BinarySensorMapChannel {
|
||||
@ -50,8 +53,10 @@ class BinarySensorMap : public sensor::Sensor, public Component {
|
||||
/**
|
||||
* methods to process the types of binary_sensor_maps
|
||||
* GROUP: process_group_() just map to a value
|
||||
* ADD: process_add_() adds all the values
|
||||
* */
|
||||
void process_group_();
|
||||
void process_sum_();
|
||||
};
|
||||
|
||||
} // namespace binary_sensor_map
|
||||
|
@ -9,6 +9,7 @@ from esphome.const import (
|
||||
ICON_CHECK_CIRCLE_OUTLINE,
|
||||
CONF_BINARY_SENSOR,
|
||||
CONF_GROUP,
|
||||
CONF_SUM,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["binary_sensor"]
|
||||
@ -21,6 +22,7 @@ SensorMapType = binary_sensor_map_ns.enum("SensorMapType")
|
||||
|
||||
SENSOR_MAP_TYPES = {
|
||||
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
|
||||
CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM,
|
||||
}
|
||||
|
||||
entry = {
|
||||
@ -41,6 +43,17 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_SUM: sensor.sensor_schema(
|
||||
BinarySensorMap,
|
||||
icon=ICON_CHECK_CIRCLE_OUTLINE,
|
||||
accuracy_decimals=0,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_CHANNELS): cv.All(
|
||||
cv.ensure_list(entry), cv.Length(min=1)
|
||||
),
|
||||
}
|
||||
),
|
||||
},
|
||||
lower=True,
|
||||
)
|
||||
|
@ -75,16 +75,16 @@ class BL0939 : public PollingComponent, public uart::UARTDevice {
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_;
|
||||
sensor::Sensor *current_sensor_1_;
|
||||
sensor::Sensor *current_sensor_2_;
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_1_{nullptr};
|
||||
sensor::Sensor *current_sensor_2_{nullptr};
|
||||
// NB This may be negative as the circuits is seemingly able to measure
|
||||
// power in both directions
|
||||
sensor::Sensor *power_sensor_1_;
|
||||
sensor::Sensor *power_sensor_2_;
|
||||
sensor::Sensor *energy_sensor_1_;
|
||||
sensor::Sensor *energy_sensor_2_;
|
||||
sensor::Sensor *energy_sensor_sum_;
|
||||
sensor::Sensor *power_sensor_1_{nullptr};
|
||||
sensor::Sensor *power_sensor_2_{nullptr};
|
||||
sensor::Sensor *energy_sensor_1_{nullptr};
|
||||
sensor::Sensor *energy_sensor_2_{nullptr};
|
||||
sensor::Sensor *energy_sensor_sum_{nullptr};
|
||||
|
||||
// Divide by this to turn into Watt
|
||||
float power_reference_ = BL0939_PREF;
|
||||
|
@ -75,14 +75,14 @@ class BL0940 : public PollingComponent, public uart::UARTDevice {
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_;
|
||||
sensor::Sensor *current_sensor_;
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
// NB This may be negative as the circuits is seemingly able to measure
|
||||
// power in both directions
|
||||
sensor::Sensor *power_sensor_;
|
||||
sensor::Sensor *energy_sensor_;
|
||||
sensor::Sensor *internal_temperature_sensor_;
|
||||
sensor::Sensor *external_temperature_sensor_;
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *energy_sensor_{nullptr};
|
||||
sensor::Sensor *internal_temperature_sensor_{nullptr};
|
||||
sensor::Sensor *external_temperature_sensor_{nullptr};
|
||||
|
||||
// Max difference between two measurements of the temperature. Used to avoid noise.
|
||||
float max_temperature_diff_{0};
|
||||
|
1
esphome/components/bl0942/__init__.py
Normal file
1
esphome/components/bl0942/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@dbuezas"]
|
121
esphome/components/bl0942/bl0942.cpp
Normal file
121
esphome/components/bl0942/bl0942.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include "bl0942.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0942 {
|
||||
|
||||
static const char *const TAG = "bl0942";
|
||||
|
||||
static const uint8_t BL0942_READ_COMMAND = 0x58;
|
||||
static const uint8_t BL0942_FULL_PACKET = 0xAA;
|
||||
static const uint8_t BL0942_PACKET_HEADER = 0x55;
|
||||
|
||||
static const uint8_t BL0942_WRITE_COMMAND = 0xA8;
|
||||
static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10;
|
||||
static const uint8_t BL0942_REG_MODE = 0x18;
|
||||
static const uint8_t BL0942_REG_SOFT_RESET = 0x19;
|
||||
static const uint8_t BL0942_REG_USR_WRPROT = 0x1A;
|
||||
static const uint8_t BL0942_REG_TPS_CTRL = 0x1B;
|
||||
|
||||
// TODO: Confirm insialisation works as intended
|
||||
const uint8_t BL0942_INIT[5][6] = {
|
||||
// Reset to default
|
||||
{BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
||||
// Enable User Operation Write
|
||||
{BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
||||
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
|
||||
{BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
||||
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
|
||||
{BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
||||
// 0x181C = Half cycle, Fast RMS threshold 6172
|
||||
{BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
||||
|
||||
void BL0942::loop() {
|
||||
DataPacket buffer;
|
||||
if (!this->available()) {
|
||||
return;
|
||||
}
|
||||
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
|
||||
if (validate_checksum(&buffer)) {
|
||||
received_package_(&buffer);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
|
||||
while (read() >= 0)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
bool BL0942::validate_checksum(DataPacket *data) {
|
||||
uint8_t checksum = BL0942_READ_COMMAND;
|
||||
// Whole package but checksum
|
||||
uint8_t *raw = (uint8_t *) data;
|
||||
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
|
||||
checksum += raw[i];
|
||||
}
|
||||
checksum ^= 0xFF;
|
||||
if (checksum != data->checksum) {
|
||||
ESP_LOGW(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
||||
}
|
||||
return checksum == data->checksum;
|
||||
}
|
||||
|
||||
void BL0942::update() {
|
||||
this->flush();
|
||||
this->write_byte(BL0942_READ_COMMAND);
|
||||
this->write_byte(BL0942_FULL_PACKET);
|
||||
}
|
||||
|
||||
void BL0942::setup() {
|
||||
for (auto *i : BL0942_INIT) {
|
||||
this->write_array(i, 6);
|
||||
delay(1);
|
||||
}
|
||||
this->flush();
|
||||
}
|
||||
|
||||
void BL0942::received_package_(DataPacket *data) {
|
||||
// Bad header
|
||||
if (data->frame_header != BL0942_PACKET_HEADER) {
|
||||
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
|
||||
return;
|
||||
}
|
||||
|
||||
float v_rms = (uint24_t) data->v_rms / voltage_reference_;
|
||||
float i_rms = (uint24_t) data->i_rms / current_reference_;
|
||||
float watt = (int24_t) data->watt / power_reference_;
|
||||
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
|
||||
float total_energy_consumption = cf_cnt / energy_reference_;
|
||||
float frequency = 1000000.0f / data->frequency;
|
||||
|
||||
if (voltage_sensor_ != nullptr) {
|
||||
voltage_sensor_->publish_state(v_rms);
|
||||
}
|
||||
if (current_sensor_ != nullptr) {
|
||||
current_sensor_->publish_state(i_rms);
|
||||
}
|
||||
if (power_sensor_ != nullptr) {
|
||||
power_sensor_->publish_state(watt);
|
||||
}
|
||||
if (energy_sensor_ != nullptr) {
|
||||
energy_sensor_->publish_state(total_energy_consumption);
|
||||
}
|
||||
if (frequency_sensor_ != nullptr) {
|
||||
frequency_sensor_->publish_state(frequency);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, frequency %f°Hz, status 0x%08X", v_rms, i_rms, watt,
|
||||
cf_cnt, total_energy_consumption, frequency, data->status);
|
||||
}
|
||||
|
||||
void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity)
|
||||
ESP_LOGCONFIG(TAG, "BL0942:");
|
||||
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR("", "Current", this->current_sensor_);
|
||||
LOG_SENSOR("", "Power", this->power_sensor_);
|
||||
LOG_SENSOR("", "Energy", this->energy_sensor_);
|
||||
LOG_SENSOR("", "frequency", this->frequency_sensor_);
|
||||
}
|
||||
|
||||
} // namespace bl0942
|
||||
} // namespace esphome
|
68
esphome/components/bl0942/bl0942.h
Normal file
68
esphome/components/bl0942/bl0942.h
Normal file
@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/datatypes.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0942 {
|
||||
|
||||
static const float BL0942_PREF = 596; // taken from tasmota
|
||||
static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218
|
||||
static const float BL0942_IREF = 251213.46469622; // 305978/1.218
|
||||
static const float BL0942_EREF = 3304.61127328; // Measured
|
||||
|
||||
struct DataPacket {
|
||||
uint8_t frame_header;
|
||||
uint24_le_t i_rms;
|
||||
uint24_le_t v_rms;
|
||||
uint24_le_t i_fast_rms;
|
||||
int24_le_t watt;
|
||||
uint24_le_t cf_cnt;
|
||||
uint16_le_t frequency;
|
||||
uint8_t reserved1;
|
||||
uint8_t status;
|
||||
uint8_t reserved2;
|
||||
uint8_t reserved3;
|
||||
uint8_t checksum;
|
||||
} __attribute__((packed));
|
||||
|
||||
class BL0942 : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
|
||||
|
||||
void loop() override;
|
||||
|
||||
void update() override;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
// NB This may be negative as the circuits is seemingly able to measure
|
||||
// power in both directions
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *energy_sensor_{nullptr};
|
||||
sensor::Sensor *frequency_sensor_{nullptr};
|
||||
|
||||
// Divide by this to turn into Watt
|
||||
float power_reference_ = BL0942_PREF;
|
||||
// Divide by this to turn into Volt
|
||||
float voltage_reference_ = BL0942_UREF;
|
||||
// Divide by this to turn into Ampere
|
||||
float current_reference_ = BL0942_IREF;
|
||||
// Divide by this to turn into kWh
|
||||
float energy_reference_ = BL0942_EREF;
|
||||
|
||||
static bool validate_checksum(DataPacket *data);
|
||||
|
||||
void received_package_(DataPacket *data);
|
||||
};
|
||||
} // namespace bl0942
|
||||
} // namespace esphome
|
93
esphome/components/bl0942/sensor.py
Normal file
93
esphome/components/bl0942/sensor.py
Normal file
@ -0,0 +1,93 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_CURRENT,
|
||||
CONF_ENERGY,
|
||||
CONF_ID,
|
||||
CONF_POWER,
|
||||
CONF_VOLTAGE,
|
||||
CONF_FREQUENCY,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_FREQUENCY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_KILOWATT_HOURS,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
UNIT_HERTZ,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
bl0942_ns = cg.esphome_ns.namespace("bl0942")
|
||||
BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BL0942),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
if CONF_ENERGY in config:
|
||||
conf = config[CONF_ENERGY]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
if CONF_FREQUENCY in config:
|
||||
conf = config[CONF_FREQUENCY]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_frequency_sensor(sens))
|
@ -1,23 +1,25 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.components import esp32_ble_tracker, esp32_ble_client
|
||||
from esphome.const import (
|
||||
CONF_CHARACTERISTIC_UUID,
|
||||
CONF_ID,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_NAME,
|
||||
CONF_ON_CONNECT,
|
||||
CONF_ON_DISCONNECT,
|
||||
CONF_SERVICE_UUID,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VALUE,
|
||||
)
|
||||
from esphome import automation
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_client"]
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
ble_client_ns = cg.esphome_ns.namespace("ble_client")
|
||||
BLEClient = ble_client_ns.class_(
|
||||
"BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient
|
||||
)
|
||||
BLEClient = ble_client_ns.class_("BLEClient", esp32_ble_client.BLEClientBase)
|
||||
BLEClientNode = ble_client_ns.class_("BLEClientNode")
|
||||
BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const")
|
||||
# Triggers
|
||||
@ -27,6 +29,8 @@ BLEClientConnectTrigger = ble_client_ns.class_(
|
||||
BLEClientDisconnectTrigger = ble_client_ns.class_(
|
||||
"BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
# Actions
|
||||
BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action)
|
||||
|
||||
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
|
||||
# enforce this in yaml checks.
|
||||
@ -62,7 +66,7 @@ CONF_BLE_CLIENT_ID = "ble_client_id"
|
||||
|
||||
BLE_CLIENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BLE_CLIENT_ID): cv.use_id(BLEClient),
|
||||
cv.GenerateID(CONF_BLE_CLIENT_ID): cv.use_id(BLEClient),
|
||||
}
|
||||
)
|
||||
|
||||
@ -72,6 +76,67 @@ async def register_ble_node(var, config):
|
||||
cg.add(parent.register_ble_node(var))
|
||||
|
||||
|
||||
BLE_WRITE_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.ensure_list(cv.hex_uint8_t)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA
|
||||
)
|
||||
async def ble_write_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
value = config[CONF_VALUE]
|
||||
if cg.is_template(value):
|
||||
templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_value_template(templ))
|
||||
else:
|
||||
cg.add(var.set_value_simple(value))
|
||||
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(
|
||||
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_char_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_char_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
75
esphome/components/ble_client/automation.cpp
Normal file
75
esphome/components/ble_client/automation.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "automation.h"
|
||||
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
static const char *const TAG = "ble_client.automation";
|
||||
|
||||
void BLEWriterClientNode::write(const std::vector<uint8_t> &value) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "Cannot write to BLE characteristic - not connected");
|
||||
return;
|
||||
} else if (this->ble_char_handle_ == 0) {
|
||||
ESP_LOGW(TAG, "Cannot write to BLE characteristic - characteristic not found");
|
||||
return;
|
||||
}
|
||||
esp_gatt_write_type_t write_type;
|
||||
if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) {
|
||||
write_type = ESP_GATT_WRITE_TYPE_RSP;
|
||||
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP");
|
||||
} else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) {
|
||||
write_type = ESP_GATT_WRITE_TYPE_NO_RSP;
|
||||
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str());
|
||||
return;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_,
|
||||
value.size(), const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
void BLEWriterClientNode::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGD(TAG, "Connection established with %s", ble_client_->address_str().c_str());
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW("ble_write_action", "Characteristic %s was not found in service %s",
|
||||
this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->ble_char_handle_ = chr->handle;
|
||||
this->char_props_ = chr->properties;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
|
||||
ble_client_->address_str().c_str());
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
this->node_state = espbt::ClientState::IDLE;
|
||||
this->ble_char_handle_ = 0;
|
||||
ESP_LOGD(TAG, "Disconnected from %s", ble_client_->address_str().c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
|
||||
@ -13,10 +16,10 @@ class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
void loop() override {}
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
if (event == ESP_GATTC_OPEN_EVT && param->open.status == ESP_GATT_OK)
|
||||
this->trigger();
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT) {
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->trigger();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -26,13 +29,71 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
void loop() override {}
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0)
|
||||
if (event == ESP_GATTC_DISCONNECT_EVT &&
|
||||
memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0)
|
||||
this->trigger();
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
};
|
||||
|
||||
class BLEWriterClientNode : public BLEClientNode {
|
||||
public:
|
||||
BLEWriterClientNode(BLEClient *ble_client) {
|
||||
ble_client->register_ble_node(this);
|
||||
ble_client_ = ble_client;
|
||||
}
|
||||
|
||||
// Attempts to write the contents of value to char_uuid_.
|
||||
void write(const std::vector<uint8_t> &value);
|
||||
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
|
||||
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
private:
|
||||
BLEClient *ble_client_;
|
||||
int ble_char_handle_ = 0;
|
||||
esp_gatt_char_prop_t char_props_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, public BLEWriterClientNode {
|
||||
public:
|
||||
BLEClientWriteAction(BLEClient *ble_client) : BLEWriterClientNode(ble_client) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (has_simple_value_) {
|
||||
return write(this->value_simple_);
|
||||
} else {
|
||||
return write(this->value_template_(x...));
|
||||
}
|
||||
}
|
||||
|
||||
void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const std::vector<uint8_t> &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool has_simple_value_ = true;
|
||||
std::vector<uint8_t> value_simple_;
|
||||
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "ble_client.h"
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "ble_client.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@ -11,22 +12,13 @@ namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_client";
|
||||
|
||||
float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
void BLEClient::setup() {
|
||||
auto ret = esp_ble_gattc_app_register(this->app_id);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
||||
this->mark_failed();
|
||||
}
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
BLEClientBase::setup();
|
||||
this->enabled = true;
|
||||
}
|
||||
|
||||
void BLEClient::loop() {
|
||||
if (this->state() == espbt::ClientState::DISCOVERED) {
|
||||
this->connect();
|
||||
}
|
||||
BLEClientBase::loop();
|
||||
for (auto *node : this->nodes_)
|
||||
node->loop();
|
||||
}
|
||||
@ -39,33 +31,7 @@ void BLEClient::dump_config() {
|
||||
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
|
||||
if (!this->enabled)
|
||||
return false;
|
||||
if (device.address_uint64() != this->address)
|
||||
return false;
|
||||
if (this->state() != espbt::ClientState::IDLE)
|
||||
return false;
|
||||
|
||||
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
|
||||
this->set_states_(espbt::ClientState::DISCOVERED);
|
||||
|
||||
auto addr = device.address_uint64();
|
||||
this->remote_bda[0] = (addr >> 40) & 0xFF;
|
||||
this->remote_bda[1] = (addr >> 32) & 0xFF;
|
||||
this->remote_bda[2] = (addr >> 24) & 0xFF;
|
||||
this->remote_bda[3] = (addr >> 16) & 0xFF;
|
||||
this->remote_bda[4] = (addr >> 8) & 0xFF;
|
||||
this->remote_bda[5] = (addr >> 0) & 0xFF;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string BLEClient::address_str() const {
|
||||
char buf[20];
|
||||
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (uint8_t)(this->address >> 40) & 0xff,
|
||||
(uint8_t)(this->address >> 32) & 0xff, (uint8_t)(this->address >> 24) & 0xff,
|
||||
(uint8_t)(this->address >> 16) & 0xff, (uint8_t)(this->address >> 8) & 0xff,
|
||||
(uint8_t)(this->address >> 0) & 0xff);
|
||||
std::string ret;
|
||||
ret = buf;
|
||||
return ret;
|
||||
return BLEClientBase::parse_device(device);
|
||||
}
|
||||
|
||||
void BLEClient::set_enabled(bool enabled) {
|
||||
@ -73,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) {
|
||||
return;
|
||||
if (!enabled && this->state() != espbt::ClientState::IDLE) {
|
||||
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
|
||||
auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id);
|
||||
auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret);
|
||||
}
|
||||
@ -81,125 +47,13 @@ void BLEClient::set_enabled(bool enabled) {
|
||||
this->enabled = enabled;
|
||||
}
|
||||
|
||||
void BLEClient::connect() {
|
||||
ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
|
||||
auto ret = esp_ble_gattc_open(this->gattc_if, this->remote_bda, BLE_ADDR_TYPE_PUBLIC, true);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_states_(espbt::ClientState::CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||
bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
||||
return;
|
||||
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
|
||||
return;
|
||||
|
||||
bool all_established = this->all_nodes_established_();
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT: {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
|
||||
this->gattc_if = esp_gattc_if;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
|
||||
this->conn_id = param->open.conn_id;
|
||||
if (param->open.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
|
||||
if (this->conn_id != param->connect.conn_id) {
|
||||
ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d",
|
||||
this->address_str().c_str(), param->connect.conn_id, this->conn_id);
|
||||
}
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CFG_MTU_EVT: {
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
|
||||
param->cfg_mtu.status);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
|
||||
ble_service->start_handle = param->search_res.start_handle;
|
||||
ble_service->end_handle = param->search_res.end_handle;
|
||||
ble_service->client = this;
|
||||
this->services_.push_back(ble_service);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
|
||||
for (auto &svc : this->services_) {
|
||||
ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
|
||||
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
|
||||
svc->parse_characteristics();
|
||||
}
|
||||
this->set_states_(espbt::ClientState::CONNECTED);
|
||||
this->set_state(espbt::ClientState::ESTABLISHED);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
|
||||
if (descr == nullptr) {
|
||||
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
|
||||
break;
|
||||
}
|
||||
if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
|
||||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
|
||||
descr->uuid.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
uint16_t notify_en = 1;
|
||||
auto status =
|
||||
esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en),
|
||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param))
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for (auto *node : this->nodes_)
|
||||
node->gattc_event_handler(event, esp_gattc_if, param);
|
||||
|
||||
@ -209,239 +63,30 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
// This event is sent by the server when it requests security
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
// This event is sent once authentication has completed
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
|
||||
param->ble_security.auth_cmpl.auth_mode);
|
||||
}
|
||||
break;
|
||||
// There are other events we'll want to implement at some point to support things like pass key
|
||||
// https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
|
||||
default:
|
||||
break;
|
||||
BLEClientBase::gap_event_handler(event, param);
|
||||
|
||||
for (auto *node : this->nodes_)
|
||||
node->gap_event_handler(event, param);
|
||||
}
|
||||
|
||||
void BLEClient::set_state(espbt::ClientState state) {
|
||||
BLEClientBase::set_state(state);
|
||||
for (auto &node : nodes_)
|
||||
node->node_state = state;
|
||||
}
|
||||
|
||||
bool BLEClient::all_nodes_established_() {
|
||||
if (this->state() != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
for (auto &node : nodes_) {
|
||||
if (node->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse GATT values into a float for a sensor.
|
||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
|
||||
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
// A length of one means a single octet value.
|
||||
if (length == 0)
|
||||
return 0;
|
||||
if (length == 1)
|
||||
return (float) ((uint8_t) value[0]);
|
||||
|
||||
switch (value[0]) {
|
||||
case 0x1: // boolean.
|
||||
case 0x2: // 2bit.
|
||||
case 0x3: // nibble.
|
||||
case 0x4: // uint8.
|
||||
return (float) ((uint8_t) value[1]);
|
||||
case 0x5: // uint12.
|
||||
case 0x6: // uint16.
|
||||
if (length > 2) {
|
||||
return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]);
|
||||
}
|
||||
case 0x7: // uint24.
|
||||
if (length > 3) {
|
||||
return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3]));
|
||||
}
|
||||
case 0x8: // uint32.
|
||||
if (length > 4) {
|
||||
return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) +
|
||||
(uint32_t)(value[4]));
|
||||
}
|
||||
case 0xC: // int8.
|
||||
return (float) ((int8_t) value[1]);
|
||||
case 0xD: // int12.
|
||||
case 0xE: // int16.
|
||||
if (length > 2) {
|
||||
return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
|
||||
}
|
||||
case 0xF: // int24.
|
||||
if (length > 3) {
|
||||
return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
|
||||
}
|
||||
case 0x10: // int32.
|
||||
if (length > 4) {
|
||||
return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
|
||||
(int32_t)(value[4]));
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
|
||||
return NAN;
|
||||
}
|
||||
|
||||
BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (svc->uuid == uuid)
|
||||
return svc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
|
||||
|
||||
BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
return svc->get_characteristic(chr);
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) {
|
||||
for (auto &svc : this->services_) {
|
||||
for (auto &chr : svc->characteristics) {
|
||||
if (chr->handle == handle) {
|
||||
for (auto &desc : chr->descriptors) {
|
||||
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
|
||||
for (auto &chr : this->characteristics) {
|
||||
if (chr->uuid == uuid)
|
||||
return chr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
auto *ch = svc->get_characteristic(chr);
|
||||
if (ch == nullptr)
|
||||
return nullptr;
|
||||
return ch->get_descriptor(descr);
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
|
||||
espbt::ESPBTUUID::from_uint16(descr));
|
||||
}
|
||||
|
||||
BLEService::~BLEService() {
|
||||
for (auto &chr : this->characteristics)
|
||||
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLEService::parse_characteristics() {
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_char_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_all_char(
|
||||
this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
characteristic->properties = result.properties;
|
||||
characteristic->handle = result.char_handle;
|
||||
characteristic->service = this;
|
||||
this->characteristics.push_back(characteristic);
|
||||
ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
|
||||
characteristic->handle, characteristic->properties);
|
||||
characteristic->parse_descriptors();
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
BLECharacteristic::~BLECharacteristic() {
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLECharacteristic::parse_descriptors() {
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_descr_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_all_descr(
|
||||
this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
desc->handle = result.handle;
|
||||
desc->characteristic = this;
|
||||
this->descriptors.push_back(desc);
|
||||
ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->uuid == uuid)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
|
||||
auto *client = this->service->client;
|
||||
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
|
||||
write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
||||
write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
|
@ -1,32 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatt_common_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
using namespace esp32_ble_client;
|
||||
|
||||
class BLEClient;
|
||||
class BLEService;
|
||||
class BLECharacteristic;
|
||||
|
||||
class BLEClientNode {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
virtual void loop(){};
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
|
||||
virtual void loop() {}
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
espbt::ESPBTClient *client;
|
||||
// This should be transitioned to Established once the node no longer needs
|
||||
@ -42,57 +45,17 @@ class BLEClientNode {
|
||||
uint64_t address_;
|
||||
};
|
||||
|
||||
class BLEDescriptor {
|
||||
public:
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
|
||||
BLECharacteristic *characteristic;
|
||||
};
|
||||
|
||||
class BLECharacteristic {
|
||||
public:
|
||||
~BLECharacteristic();
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
esp_gatt_char_prop_t properties;
|
||||
std::vector<BLEDescriptor *> descriptors;
|
||||
void parse_descriptors();
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
void write_value(uint8_t *new_val, int16_t new_val_size);
|
||||
void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
class BLEService {
|
||||
public:
|
||||
~BLEService();
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
std::vector<BLECharacteristic *> characteristics;
|
||||
BLEClient *client;
|
||||
void parse_characteristics();
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
|
||||
BLECharacteristic *get_characteristic(uint16_t uuid);
|
||||
};
|
||||
|
||||
class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
class BLEClient : public BLEClientBase {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
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;
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
void on_scan_end() override {}
|
||||
void connect() override;
|
||||
|
||||
void set_address(uint64_t address) { this->address = address; }
|
||||
|
||||
void set_enabled(bool enabled);
|
||||
|
||||
@ -102,42 +65,14 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
this->nodes_.push_back(node);
|
||||
}
|
||||
|
||||
BLEService *get_service(espbt::ESPBTUUID uuid);
|
||||
BLEService *get_service(uint16_t uuid);
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
|
||||
// Get the configuration descriptor for the given characteristic handle.
|
||||
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
||||
|
||||
float parse_char_value(uint8_t *value, uint16_t length);
|
||||
|
||||
int gattc_if;
|
||||
esp_bd_addr_t remote_bda;
|
||||
uint16_t conn_id;
|
||||
uint64_t address;
|
||||
bool enabled;
|
||||
std::string address_str() const;
|
||||
|
||||
void set_state(espbt::ClientState state) override;
|
||||
|
||||
protected:
|
||||
void set_states_(espbt::ClientState st) {
|
||||
this->set_state(st);
|
||||
for (auto &node : nodes_)
|
||||
node->node_state = st;
|
||||
}
|
||||
bool all_nodes_established_() {
|
||||
if (this->state() != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
for (auto &node : nodes_) {
|
||||
if (node->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool all_nodes_established_();
|
||||
|
||||
std::vector<BLEClientNode *> nodes_;
|
||||
std::vector<BLEService *> services_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
|
@ -1,13 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import ble_client, esp32_ble_tracker, output
|
||||
from esphome.const import CONF_ID, CONF_SERVICE_UUID
|
||||
from esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID
|
||||
|
||||
from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
CONF_REQUIRE_RESPONSE = "require_response"
|
||||
|
||||
BLEBinaryOutput = ble_client_ns.class_(
|
||||
|
@ -2,20 +2,26 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client, esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
CONF_CHARACTERISTIC_UUID,
|
||||
CONF_LAMBDA,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
CONF_SERVICE_UUID,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_DECIBEL_MILLIWATT,
|
||||
)
|
||||
from esphome import automation
|
||||
from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
CONF_DESCRIPTOR_UUID = "descriptor_uuid"
|
||||
|
||||
CONF_NOTIFY = "notify"
|
||||
CONF_ON_NOTIFY = "on_notify"
|
||||
TYPE_CHARACTERISTIC = "characteristic"
|
||||
TYPE_RSSI = "rssi"
|
||||
|
||||
adv_data_t = cg.std_vector.template(cg.uint8)
|
||||
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
|
||||
@ -27,33 +33,67 @@ BLESensorNotifyTrigger = ble_client_ns.class_(
|
||||
"BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
BLESensor,
|
||||
accuracy_decimals=0,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLESensorNotifyTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
BLEClientRssiSensor = ble_client_ns.class_(
|
||||
"BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
def checkType(value):
|
||||
if CONF_TYPE not in value and CONF_SERVICE_UUID in value:
|
||||
raise cv.Invalid(
|
||||
"Looks like you're trying to create a ble characteristic sensor. Please add `type: characteristic` to your sensor config."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
checkType,
|
||||
cv.typed_schema(
|
||||
{
|
||||
TYPE_CHARACTERISTIC: sensor.sensor_schema(
|
||||
BLESensor,
|
||||
accuracy_decimals=0,
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLESensorNotifyTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
TYPE_RSSI: sensor.sensor_schema(
|
||||
BLEClientRssiSensor,
|
||||
accuracy_decimals=0,
|
||||
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||
},
|
||||
lower=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def rssi_sensor_to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
|
||||
async def characteristic_sensor_to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
@ -125,3 +165,10 @@ async def to_code(config):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await ble_client.register_ble_node(trigger, config)
|
||||
await automation.build_automation(trigger, [(float, "x")], conf)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
if config[CONF_TYPE] == TYPE_RSSI:
|
||||
await rssi_sensor_to_code(config)
|
||||
elif config[CONF_TYPE] == TYPE_CHARACTERISTIC:
|
||||
await characteristic_sensor_to_code(config)
|
||||
|
@ -19,7 +19,8 @@ class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
|
||||
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
|
||||
param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
|
||||
}
|
||||
|
78
esphome/components/ble_client/sensor/ble_rssi_sensor.cpp
Normal file
78
esphome/components/ble_client/sensor/ble_rssi_sensor.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "ble_rssi_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_rssi_sensor";
|
||||
|
||||
void BLEClientRSSISensor::loop() {}
|
||||
|
||||
void BLEClientRSSISensor::dump_config() {
|
||||
LOG_SENSOR("", "BLE Client RSSI Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status == ESP_GATT_OK) {
|
||||
ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
// server response on RSSI request:
|
||||
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||
if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) {
|
||||
int8_t rssi = param->read_rssi_cmpl.rssi;
|
||||
ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi);
|
||||
this->publish_state(rssi);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientRSSISensor::update() {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str());
|
||||
auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda());
|
||||
if (status != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status);
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
31
esphome/components/ble_client/sensor/ble_rssi_sensor.h
Normal file
31
esphome/components/ble_client/sensor/ble_rssi_sensor.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
|
||||
public:
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@ -63,8 +63,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
this->handle = descr->handle;
|
||||
}
|
||||
if (this->notify_) {
|
||||
auto status =
|
||||
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
|
||||
this->parent()->get_remote_bda(), chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
@ -74,7 +74,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@ -87,7 +87,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
|
||||
if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
@ -122,8 +122,8 @@ void BLESensor::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import switch, ble_client
|
||||
from esphome.const import CONF_ICON, CONF_ID, CONF_INVERTED, ICON_BLUETOOTH
|
||||
from esphome.const import ICON_BLUETOOTH
|
||||
from .. import ble_client_ns
|
||||
|
||||
BLEClientSwitch = ble_client_ns.class_(
|
||||
@ -9,22 +9,13 @@ BLEClientSwitch = ble_client_ns.class_(
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEClientSwitch),
|
||||
cv.Optional(CONF_INVERTED): cv.invalid(
|
||||
"BLE client switches do not support inverted mode!"
|
||||
),
|
||||
cv.Optional(CONF_ICON, default=ICON_BLUETOOTH): switch.icon,
|
||||
}
|
||||
)
|
||||
switch.switch_schema(BLEClientSwitch, icon=ICON_BLUETOOTH, block_inverted=True)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await switch.new_switch(config)
|
||||
await cg.register_component(var, config)
|
||||
await switch.register_switch(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor, ble_client, esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
CONF_CHARACTERISTIC_UUID,
|
||||
CONF_ID,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_SERVICE_UUID,
|
||||
@ -11,7 +12,6 @@ from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
CONF_DESCRIPTOR_UUID = "descriptor_uuid"
|
||||
|
||||
CONF_NOTIFY = "notify"
|
||||
|
@ -19,7 +19,8 @@ class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSe
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
|
||||
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
|
||||
param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
|
||||
}
|
||||
|
@ -66,8 +66,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->handle = descr->handle;
|
||||
}
|
||||
if (this->notify_) {
|
||||
auto status =
|
||||
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
|
||||
this->parent()->get_remote_bda(), chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
@ -77,7 +77,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@ -90,7 +90,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
|
||||
if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
@ -121,8 +121,8 @@ void BLETextSensor::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
|
@ -58,7 +58,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||
case MATCH_BY_SERVICE_UUID:
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
if (this->uuid_ == uuid) {
|
||||
this->publish_state(device.get_rssi());
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
@ -83,7 +83,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||
return false;
|
||||
}
|
||||
|
||||
this->publish_state(device.get_rssi());
|
||||
this->publish_state(true);
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
|
@ -12,41 +12,78 @@ namespace ble_rssi {
|
||||
class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
void set_address(uint64_t address) {
|
||||
this->by_address_ = true;
|
||||
this->match_by_ = MATCH_BY_MAC_ADDRESS;
|
||||
this->address_ = address;
|
||||
}
|
||||
void set_service_uuid16(uint16_t uuid) {
|
||||
this->by_address_ = false;
|
||||
this->match_by_ = MATCH_BY_SERVICE_UUID;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid);
|
||||
}
|
||||
void set_service_uuid32(uint32_t uuid) {
|
||||
this->by_address_ = false;
|
||||
this->match_by_ = MATCH_BY_SERVICE_UUID;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid);
|
||||
}
|
||||
void set_service_uuid128(uint8_t *uuid) {
|
||||
this->by_address_ = false;
|
||||
this->match_by_ = MATCH_BY_SERVICE_UUID;
|
||||
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
|
||||
}
|
||||
void set_ibeacon_uuid(uint8_t *uuid) {
|
||||
this->match_by_ = MATCH_BY_IBEACON_UUID;
|
||||
this->ibeacon_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
|
||||
}
|
||||
void set_ibeacon_major(uint16_t major) {
|
||||
this->check_ibeacon_major_ = true;
|
||||
this->ibeacon_major_ = major;
|
||||
}
|
||||
void set_ibeacon_minor(uint16_t minor) {
|
||||
this->check_ibeacon_minor_ = true;
|
||||
this->ibeacon_minor_ = minor;
|
||||
}
|
||||
void on_scan_end() override {
|
||||
if (!this->found_)
|
||||
this->publish_state(NAN);
|
||||
this->found_ = false;
|
||||
}
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||
if (this->by_address_) {
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
if (this->uuid_ == uuid) {
|
||||
switch (this->match_by_) {
|
||||
case MATCH_BY_MAC_ADDRESS:
|
||||
if (device.address_uint64() == this->address_) {
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MATCH_BY_SERVICE_UUID:
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
if (this->uuid_ == uuid) {
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MATCH_BY_IBEACON_UUID:
|
||||
if (!device.get_ibeacon().has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ibeacon = device.get_ibeacon().value();
|
||||
|
||||
if (this->ibeacon_uuid_ != ibeacon.get_uuid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->check_ibeacon_major_ && this->ibeacon_major_ != ibeacon.get_major()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->check_ibeacon_minor_ && this->ibeacon_minor_ != ibeacon.get_minor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->publish_state(device.get_rssi());
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -54,10 +91,20 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
enum MatchType { MATCH_BY_MAC_ADDRESS, MATCH_BY_SERVICE_UUID, MATCH_BY_IBEACON_UUID };
|
||||
MatchType match_by_;
|
||||
|
||||
bool found_{false};
|
||||
bool by_address_{false};
|
||||
|
||||
uint64_t address_;
|
||||
|
||||
esp32_ble_tracker::ESPBTUUID uuid_;
|
||||
|
||||
esp32_ble_tracker::ESPBTUUID ibeacon_uuid_;
|
||||
uint16_t ibeacon_major_;
|
||||
bool check_ibeacon_major_;
|
||||
uint16_t ibeacon_minor_;
|
||||
bool check_ibeacon_minor_;
|
||||
};
|
||||
|
||||
} // namespace ble_rssi
|
||||
|
@ -2,11 +2,14 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
CONF_IBEACON_MAJOR,
|
||||
CONF_IBEACON_MINOR,
|
||||
CONF_IBEACON_UUID,
|
||||
CONF_SERVICE_UUID,
|
||||
CONF_MAC_ADDRESS,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_DECIBEL,
|
||||
UNIT_DECIBEL_MILLIWATT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
@ -16,10 +19,19 @@ BLERSSISensor = ble_rssi_ns.class_(
|
||||
"BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener
|
||||
)
|
||||
|
||||
|
||||
def _validate(config):
|
||||
if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config:
|
||||
raise cv.Invalid("iBeacon major identifier requires iBeacon UUID")
|
||||
if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config:
|
||||
raise cv.Invalid("iBeacon minor identifier requires iBeacon UUID")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
BLERSSISensor,
|
||||
unit_of_measurement=UNIT_DECIBEL,
|
||||
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@ -28,11 +40,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
|
||||
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
|
||||
cv.Optional(CONF_IBEACON_UUID): cv.uuid,
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID),
|
||||
cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_IBEACON_UUID),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
@ -60,3 +76,13 @@ async def to_code(config):
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if CONF_IBEACON_UUID in config:
|
||||
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID]))
|
||||
cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
|
||||
|
||||
if CONF_IBEACON_MAJOR in config:
|
||||
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR]))
|
||||
|
||||
if CONF_IBEACON_MINOR in config:
|
||||
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR]))
|
||||
|
75
esphome/components/bluetooth_proxy/__init__.py
Normal file
75
esphome/components/bluetooth_proxy/__init__.py
Normal file
@ -0,0 +1,75 @@
|
||||
from esphome.components import esp32_ble_tracker, esp32_ble_client
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ACTIVE, CONF_ID
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
||||
DEPENDENCIES = ["api", "esp32"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
CONF_CONNECTIONS = "connections"
|
||||
MAX_CONNECTIONS = 3
|
||||
|
||||
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
|
||||
|
||||
BluetoothProxy = bluetooth_proxy_ns.class_(
|
||||
"BluetoothProxy", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||
)
|
||||
BluetoothConnection = bluetooth_proxy_ns.class_(
|
||||
"BluetoothConnection", esp32_ble_client.BLEClientBase
|
||||
)
|
||||
|
||||
CONNECTION_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BluetoothConnection),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def validate_connections(config):
|
||||
if CONF_CONNECTIONS in config:
|
||||
if not config[CONF_ACTIVE]:
|
||||
raise cv.Invalid(
|
||||
"Connections can only be used if the proxy is set to active"
|
||||
)
|
||||
else:
|
||||
if config[CONF_ACTIVE]:
|
||||
conf = config.copy()
|
||||
conf[CONF_CONNECTIONS] = [
|
||||
CONNECTION_SCHEMA({}) for _ in range(MAX_CONNECTIONS)
|
||||
]
|
||||
return conf
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
||||
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_CONNECTIONS): cv.All(
|
||||
cv.ensure_list(CONNECTION_SCHEMA),
|
||||
cv.Length(min=1, max=MAX_CONNECTIONS),
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
validate_connections,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_active(config[CONF_ACTIVE]))
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
||||
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||
await cg.register_component(connection_var, connection_conf)
|
||||
cg.add(var.register_connection(connection_var))
|
||||
await esp32_ble_tracker.register_client(connection_var, connection_conf)
|
||||
|
||||
cg.add_define("USE_BLUETOOTH_PROXY")
|
270
esphome/components/bluetooth_proxy/bluetooth_connection.cpp
Normal file
270
esphome/components/bluetooth_proxy/bluetooth_connection.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
#include "bluetooth_connection.h"
|
||||
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "bluetooth_proxy.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
static const char *const TAG = "bluetooth_proxy.connection";
|
||||
|
||||
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
||||
return false;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->disconnect.reason);
|
||||
this->set_address(0);
|
||||
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
|
||||
this->proxy_->get_bluetooth_connections_limit());
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.conn_id != this->conn_id_)
|
||||
break;
|
||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||
api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->open.status);
|
||||
this->set_address(0);
|
||||
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
|
||||
this->proxy_->get_bluetooth_connections_limit());
|
||||
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
|
||||
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
|
||||
this->proxy_->get_bluetooth_connections_limit());
|
||||
}
|
||||
this->seen_mtu_or_services_ = false;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CFG_MTU_EVT: {
|
||||
if (param->cfg_mtu.conn_id != this->conn_id_)
|
||||
break;
|
||||
if (!this->seen_mtu_or_services_) {
|
||||
// We don't know if we will get the MTU or the services first, so
|
||||
// only send the device connection true if we have already received
|
||||
// the services.
|
||||
this->seen_mtu_or_services_ = true;
|
||||
break;
|
||||
}
|
||||
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
|
||||
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
|
||||
this->proxy_->get_bluetooth_connections_limit());
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
if (param->search_cmpl.conn_id != this->conn_id_)
|
||||
break;
|
||||
if (!this->seen_mtu_or_services_) {
|
||||
// We don't know if we will get the MTU or the services first, so
|
||||
// only send the device connection true if we have already received
|
||||
// the mtu.
|
||||
this->seen_mtu_or_services_ = true;
|
||||
break;
|
||||
}
|
||||
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
|
||||
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
|
||||
this->proxy_->get_bluetooth_connections_limit());
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->conn_id_)
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->read.handle, param->read.status);
|
||||
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
api::BluetoothGATTReadResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->read.handle;
|
||||
resp.data.reserve(param->read.value_len);
|
||||
for (uint16_t i = 0; i < param->read.value_len; i++) {
|
||||
resp.data.push_back(param->read.value[i]);
|
||||
}
|
||||
api::global_api_server->send_bluetooth_gatt_read_response(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (param->write.conn_id != this->conn_id_)
|
||||
break;
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->write.handle, param->write.status);
|
||||
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status);
|
||||
break;
|
||||
}
|
||||
api::BluetoothGATTWriteResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->write.handle;
|
||||
api::global_api_server->send_bluetooth_gatt_write_response(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
if (param->unreg_for_notify.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
|
||||
this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
|
||||
param->unreg_for_notify.status);
|
||||
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle,
|
||||
param->unreg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
api::BluetoothGATTNotifyResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->unreg_for_notify.handle;
|
||||
api::global_api_server->send_bluetooth_gatt_notify_response(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
|
||||
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle,
|
||||
param->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
api::BluetoothGATTNotifyResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->reg_for_notify.handle;
|
||||
api::global_api_server->send_bluetooth_gatt_notify_response(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->conn_id_)
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(),
|
||||
param->notify.handle);
|
||||
api::BluetoothGATTNotifyDataResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->notify.handle;
|
||||
resp.data.reserve(param->notify.value_len);
|
||||
for (uint16_t i = 0; i < param->notify.value_len; i++) {
|
||||
resp.data.push_back(param->notify.value[i]);
|
||||
}
|
||||
api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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_,
|
||||
this->address_str_.c_str());
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||
handle);
|
||||
|
||||
esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), err);
|
||||
return err;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
|
||||
if (!this->connected()) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||
handle);
|
||||
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), err);
|
||||
return err;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
|
||||
if (!this->connected()) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||
handle);
|
||||
|
||||
esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), err);
|
||||
return err;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
|
||||
if (!this->connected()) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||
handle);
|
||||
|
||||
esp_err_t err = esp_ble_gattc_write_char_descr(
|
||||
this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), err);
|
||||
return err;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
|
||||
if (!this->connected()) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
|
||||
this->address_str_.c_str(), handle);
|
||||
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), err);
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
|
||||
this->address_str_.c_str(), handle);
|
||||
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
35
esphome/components/bluetooth_proxy/bluetooth_connection.h
Normal file
35
esphome/components/bluetooth_proxy/bluetooth_connection.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
class BluetoothProxy;
|
||||
|
||||
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;
|
||||
|
||||
esp_err_t read_characteristic(uint16_t handle);
|
||||
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
|
||||
esp_err_t read_descriptor(uint16_t handle);
|
||||
esp_err_t write_descriptor(uint16_t handle, const std::string &data, bool response);
|
||||
|
||||
esp_err_t notify_characteristic(uint16_t handle, bool enable);
|
||||
|
||||
protected:
|
||||
friend class BluetoothProxy;
|
||||
bool seen_mtu_or_services_{false};
|
||||
|
||||
int16_t send_service_{-2};
|
||||
BluetoothProxy *proxy_;
|
||||
};
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
391
esphome/components/bluetooth_proxy/bluetooth_proxy.cpp
Normal file
391
esphome/components/bluetooth_proxy/bluetooth_proxy.cpp
Normal file
@ -0,0 +1,391 @@
|
||||
#include "bluetooth_proxy.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/api/api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
static const char *const TAG = "bluetooth_proxy";
|
||||
static const int DONE_SENDING_SERVICES = -2;
|
||||
|
||||
std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
|
||||
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
|
||||
return std::vector<uint64_t>{((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]),
|
||||
((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0])};
|
||||
}
|
||||
|
||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
||||
|
||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (!api::global_api_server->is_connected())
|
||||
return false;
|
||||
ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
|
||||
device.get_rssi());
|
||||
this->send_api_packet_(device);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
api::BluetoothLEAdvertisementResponse resp;
|
||||
resp.address = device.address_uint64();
|
||||
resp.address_type = device.get_address_type();
|
||||
if (!device.get_name().empty())
|
||||
resp.name = device.get_name();
|
||||
resp.rssi = device.get_rssi();
|
||||
for (auto uuid : device.get_service_uuids()) {
|
||||
resp.service_uuids.push_back(uuid.to_string());
|
||||
}
|
||||
for (auto &data : device.get_service_datas()) {
|
||||
api::BluetoothServiceData service_data;
|
||||
service_data.uuid = data.uuid.to_string();
|
||||
service_data.data.assign(data.data.begin(), data.data.end());
|
||||
resp.service_data.push_back(std::move(service_data));
|
||||
}
|
||||
for (auto &data : device.get_manufacturer_datas()) {
|
||||
api::BluetoothServiceData manufacturer_data;
|
||||
manufacturer_data.uuid = data.uuid.to_string();
|
||||
manufacturer_data.data.assign(data.data.begin(), data.data.end());
|
||||
resp.manufacturer_data.push_back(std::move(manufacturer_data));
|
||||
}
|
||||
api::global_api_server->send_bluetooth_le_advertisement(resp);
|
||||
}
|
||||
|
||||
void BluetoothProxy::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
||||
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
|
||||
}
|
||||
|
||||
int BluetoothProxy::get_bluetooth_connections_free() {
|
||||
int free = 0;
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->address_ == 0) {
|
||||
free++;
|
||||
ESP_LOGV(TAG, "[%d] Free connection", connection->get_connection_index());
|
||||
} else {
|
||||
ESP_LOGV(TAG, "[%d] Used connection by [%s]", connection->get_connection_index(),
|
||||
connection->address_str().c_str());
|
||||
}
|
||||
}
|
||||
return free;
|
||||
}
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
if (!api::global_api_server->is_connected()) {
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->get_address() != 0) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->send_service_ == connection->service_count_) {
|
||||
connection->send_service_ = DONE_SENDING_SERVICES;
|
||||
api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address());
|
||||
if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
connection->release_services();
|
||||
}
|
||||
} else if (connection->send_service_ >= 0) {
|
||||
esp_gattc_service_elem_t service_result;
|
||||
uint16_t service_count = 1;
|
||||
esp_gatt_status_t service_status =
|
||||
esp_ble_gattc_get_service(connection->get_gattc_if(), connection->get_conn_id(), nullptr, &service_result,
|
||||
&service_count, connection->send_service_);
|
||||
connection->send_service_++;
|
||||
if (service_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d",
|
||||
connection->get_connection_index(), connection->address_str().c_str(), connection->send_service_ - 1,
|
||||
service_status);
|
||||
continue;
|
||||
}
|
||||
if (service_count == 0) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d",
|
||||
connection->get_connection_index(), connection->address_str().c_str(), service_count);
|
||||
continue;
|
||||
}
|
||||
api::BluetoothGATTGetServicesResponse resp;
|
||||
resp.address = connection->get_address();
|
||||
api::BluetoothGATTService service_resp;
|
||||
service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
|
||||
service_resp.handle = service_result.start_handle;
|
||||
uint16_t char_offset = 0;
|
||||
esp_gattc_char_elem_t char_result;
|
||||
while (true) { // characteristics
|
||||
uint16_t char_count = 1;
|
||||
esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
|
||||
connection->get_gattc_if(), connection->get_conn_id(), service_result.start_handle,
|
||||
service_result.end_handle, &char_result, &char_count, char_offset);
|
||||
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (char_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", connection->get_connection_index(),
|
||||
connection->address_str().c_str(), char_status);
|
||||
break;
|
||||
}
|
||||
if (char_count == 0) {
|
||||
break;
|
||||
}
|
||||
api::BluetoothGATTCharacteristic characteristic_resp;
|
||||
characteristic_resp.uuid = get_128bit_uuid_vec(char_result.uuid);
|
||||
characteristic_resp.handle = char_result.char_handle;
|
||||
characteristic_resp.properties = char_result.properties;
|
||||
char_offset++;
|
||||
uint16_t desc_offset = 0;
|
||||
esp_gattc_descr_elem_t desc_result;
|
||||
while (true) { // descriptors
|
||||
uint16_t desc_count = 1;
|
||||
esp_gatt_status_t desc_status =
|
||||
esp_ble_gattc_get_all_descr(connection->get_gattc_if(), connection->get_conn_id(),
|
||||
char_result.char_handle, &desc_result, &desc_count, desc_offset);
|
||||
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (desc_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", connection->get_connection_index(),
|
||||
connection->address_str().c_str(), desc_status);
|
||||
break;
|
||||
}
|
||||
if (desc_count == 0) {
|
||||
break;
|
||||
}
|
||||
api::BluetoothGATTDescriptor descriptor_resp;
|
||||
descriptor_resp.uuid = get_128bit_uuid_vec(desc_result.uuid);
|
||||
descriptor_resp.handle = desc_result.handle;
|
||||
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
|
||||
desc_offset++;
|
||||
}
|
||||
service_resp.characteristics.push_back(std::move(characteristic_resp));
|
||||
}
|
||||
resp.services.push_back(std::move(service_resp));
|
||||
api::global_api_server->send_bluetooth_gatt_services(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->get_address() == address)
|
||||
return connection;
|
||||
}
|
||||
|
||||
if (!reserve)
|
||||
return nullptr;
|
||||
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->get_address() == 0) {
|
||||
connection->send_service_ = DONE_SENDING_SERVICES;
|
||||
connection->set_address(address);
|
||||
// All connections must start at INIT
|
||||
// We only set the state if we allocate the connection
|
||||
// to avoid a race where multiple connection attempts
|
||||
// are made.
|
||||
connection->set_state(espbt::ClientState::INIT);
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
|
||||
switch (msg.request_type) {
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
||||
auto *connection = this->get_connection_(msg.address, true);
|
||||
if (connection == nullptr) {
|
||||
ESP_LOGW(TAG, "No free connections available");
|
||||
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
|
||||
return;
|
||||
}
|
||||
if (connection->state() == espbt::ClientState::CONNECTED ||
|
||||
connection->state() == espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
|
||||
connection->address_str().c_str());
|
||||
api::global_api_server->send_bluetooth_device_connection(msg.address, true);
|
||||
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
||||
this->get_bluetooth_connections_limit());
|
||||
return;
|
||||
} else if (connection->state() == espbt::ClientState::SEARCHING) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device",
|
||||
connection->get_connection_index(), connection->address_str().c_str());
|
||||
return;
|
||||
} else if (connection->state() == espbt::ClientState::DISCOVERED) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device already discovered",
|
||||
connection->get_connection_index(), connection->address_str().c_str());
|
||||
return;
|
||||
} else if (connection->state() == espbt::ClientState::READY_TO_CONNECT) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, waiting in line to connect",
|
||||
connection->get_connection_index(), connection->address_str().c_str());
|
||||
return;
|
||||
} else if (connection->state() == espbt::ClientState::CONNECTING) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already connecting", connection->get_connection_index(),
|
||||
connection->address_str().c_str());
|
||||
return;
|
||||
} else if (connection->state() == espbt::ClientState::DISCONNECTING) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device is disconnecting",
|
||||
connection->get_connection_index(), connection->address_str().c_str());
|
||||
return;
|
||||
} else if (connection->state() != espbt::ClientState::INIT) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection already in progress", connection->get_connection_index(),
|
||||
connection->address_str().c_str());
|
||||
return;
|
||||
}
|
||||
if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE) {
|
||||
connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connecting v3 with cache", connection->get_connection_index(),
|
||||
connection->address_str().c_str());
|
||||
} else if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE) {
|
||||
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connecting v3 without cache", connection->get_connection_index(),
|
||||
connection->address_str().c_str());
|
||||
} else {
|
||||
connection->set_connection_type(espbt::ConnectionType::V1);
|
||||
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;
|
||||
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
||||
connection->set_state(espbt::ClientState::DISCOVERED);
|
||||
} else {
|
||||
connection->set_state(espbt::ClientState::SEARCHING);
|
||||
}
|
||||
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
||||
this->get_bluetooth_connections_limit());
|
||||
break;
|
||||
}
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
|
||||
auto *connection = this->get_connection_(msg.address, false);
|
||||
if (connection == nullptr) {
|
||||
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
|
||||
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
||||
this->get_bluetooth_connections_limit());
|
||||
return;
|
||||
}
|
||||
if (connection->state() != espbt::ClientState::IDLE) {
|
||||
connection->disconnect();
|
||||
} else {
|
||||
connection->set_address(0);
|
||||
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
|
||||
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
||||
this->get_bluetooth_connections_limit());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
||||
auto *connection = this->get_connection_(msg.address, false);
|
||||
if (connection == nullptr) {
|
||||
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
auto err = connection->read_characteristic(msg.handle);
|
||||
if (err != ESP_OK) {
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
|
||||
auto *connection = this->get_connection_(msg.address, false);
|
||||
if (connection == nullptr) {
|
||||
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
|
||||
if (err != ESP_OK) {
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
|
||||
auto *connection = this->get_connection_(msg.address, false);
|
||||
if (connection == nullptr) {
|
||||
ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
auto err = connection->read_descriptor(msg.handle);
|
||||
if (err != ESP_OK) {
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
|
||||
auto *connection = this->get_connection_(msg.address, false);
|
||||
if (connection == nullptr) {
|
||||
ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
auto err = connection->write_descriptor(msg.handle, msg.data, true);
|
||||
if (err != ESP_OK) {
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
|
||||
auto *connection = this->get_connection_(msg.address, false);
|
||||
if (connection == nullptr || !connection->connected()) {
|
||||
ESP_LOGW(TAG, "Cannot get GATT services, not connected");
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
if (!connection->service_count_) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
|
||||
api::global_api_server->send_bluetooth_gatt_services_done(msg.address);
|
||||
return;
|
||||
}
|
||||
if (connection->send_service_ ==
|
||||
DONE_SENDING_SERVICES) // Only start sending services if we're not already sending them
|
||||
connection->send_service_ = 0;
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
|
||||
auto *connection = this->get_connection_(msg.address, false);
|
||||
if (connection == nullptr) {
|
||||
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
auto err = connection->notify_characteristic(msg.handle, msg.enable);
|
||||
if (err != ESP_OK) {
|
||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
65
esphome/components/bluetooth_proxy/bluetooth_proxy.h
Normal file
65
esphome/components/bluetooth_proxy/bluetooth_proxy.h
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include "bluetooth_connection.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||
|
||||
using namespace esp32_ble_client;
|
||||
|
||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
public:
|
||||
BluetoothProxy();
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
void register_connection(BluetoothConnection *connection) {
|
||||
this->connections_.push_back(connection);
|
||||
connection->proxy_ = this;
|
||||
}
|
||||
|
||||
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
||||
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
|
||||
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg);
|
||||
void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg);
|
||||
void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg);
|
||||
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
|
||||
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
|
||||
|
||||
int get_bluetooth_connections_free();
|
||||
int get_bluetooth_connections_limit() { return this->connections_.size(); }
|
||||
|
||||
void set_active(bool active) { this->active_ = active; }
|
||||
bool has_active() { return this->active_; }
|
||||
|
||||
protected:
|
||||
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
|
||||
|
||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||
|
||||
bool active_;
|
||||
|
||||
std::vector<BluetoothConnection *> connections_{};
|
||||
};
|
||||
|
||||
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@ -1,4 +1,5 @@
|
||||
#include "bme280.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@ -28,6 +29,7 @@ static const uint8_t BME280_REGISTER_DIG_H5 = 0xE5;
|
||||
static const uint8_t BME280_REGISTER_DIG_H6 = 0xE7;
|
||||
|
||||
static const uint8_t BME280_REGISTER_CHIPID = 0xD0;
|
||||
static const uint8_t BME280_REGISTER_RESET = 0xE0;
|
||||
|
||||
static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
|
||||
static const uint8_t BME280_REGISTER_STATUS = 0xF3;
|
||||
@ -39,6 +41,8 @@ static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
|
||||
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;
|
||||
|
||||
static const uint8_t BME280_MODE_FORCED = 0b01;
|
||||
static const uint8_t BME280_SOFT_RESET = 0xB6;
|
||||
static const uint8_t BME280_STATUS_IM_UPDATE = 0b01;
|
||||
|
||||
inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); }
|
||||
|
||||
@ -97,6 +101,28 @@ void BME280Component::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a soft reset.
|
||||
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// Wait until the NVM data has finished loading.
|
||||
uint8_t status;
|
||||
uint8_t retry = 5;
|
||||
do {
|
||||
delay(2);
|
||||
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
|
||||
ESP_LOGW(TAG, "Error reading status register.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
|
||||
if (status & BME280_STATUS_IM_UPDATE) {
|
||||
ESP_LOGW(TAG, "Timeout loading NVM.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read calibration
|
||||
this->calibration_.t1 = read_u16_le_(BME280_REGISTER_DIG_T1);
|
||||
this->calibration_.t2 = read_s16_le_(BME280_REGISTER_DIG_T2);
|
||||
@ -137,7 +163,7 @@ void BME280Component::setup() {
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||
config_register |= 0b101 << 5; // 1000 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed();
|
||||
|
@ -96,9 +96,9 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice {
|
||||
BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X};
|
||||
BME280Oversampling humidity_oversampling_{BME280_OVERSAMPLING_16X};
|
||||
BME280IIRFilter iir_filter_{BME280_IIR_FILTER_OFF};
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
|
@ -129,10 +129,10 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice {
|
||||
uint16_t heater_temperature_{320};
|
||||
uint16_t heater_duration_{150};
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *gas_resistance_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
sensor::Sensor *gas_resistance_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace bme680
|
||||
|
@ -100,15 +100,15 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *gas_resistance_sensor_;
|
||||
sensor::Sensor *iaq_sensor_;
|
||||
text_sensor::TextSensor *iaq_accuracy_text_sensor_;
|
||||
sensor::Sensor *iaq_accuracy_sensor_;
|
||||
sensor::Sensor *co2_equivalent_sensor_;
|
||||
sensor::Sensor *breath_voc_equivalent_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
sensor::Sensor *gas_resistance_sensor_{nullptr};
|
||||
sensor::Sensor *iaq_sensor_{nullptr};
|
||||
text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr};
|
||||
sensor::Sensor *iaq_accuracy_sensor_{nullptr};
|
||||
sensor::Sensor *co2_equivalent_sensor_{nullptr};
|
||||
sensor::Sensor *breath_voc_equivalent_sensor_{nullptr};
|
||||
};
|
||||
#endif
|
||||
} // namespace bme680_bsec
|
||||
|
@ -81,8 +81,8 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice {
|
||||
BMP280Oversampling temperature_oversampling_{BMP280_OVERSAMPLING_16X};
|
||||
BMP280Oversampling pressure_oversampling_{BMP280_OVERSAMPLING_16X};
|
||||
BMP280IIRFilter iir_filter_{BMP280_IIR_FILTER_OFF};
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
|
@ -125,8 +125,8 @@ class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
Oversampling pressure_oversampling_{OVERSAMPLING_X16};
|
||||
IIRFilter iir_filter_{IIR_FILTER_OFF};
|
||||
OperationMode operation_mode_{FORCED_MODE};
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
ERROR_COMMUNICATION_FAILED,
|
||||
|
@ -102,8 +102,8 @@ async def register_button(var, config):
|
||||
await setup_button_core_(var, config)
|
||||
|
||||
|
||||
async def new_button(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
async def new_button(config, *args):
|
||||
var = cg.new_Pvariable(config[CONF_ID], *args)
|
||||
await register_button(var, config)
|
||||
return var
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace canbus {
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace cap1188 {
|
||||
|
||||
|
@ -22,6 +22,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
)
|
||||
|
||||
|
||||
|
@ -113,7 +113,9 @@ 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.temperature,
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): cv.float_with_unit(
|
||||
"visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||
|
@ -540,12 +540,15 @@ void Climate::dump_traits_(const char *tag) {
|
||||
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());
|
||||
if (traits.get_supports_current_temperature())
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
|
||||
if (traits.get_supports_two_point_target_temperature())
|
||||
}
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature");
|
||||
if (traits.get_supports_action())
|
||||
}
|
||||
if (traits.get_supports_action()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supports action");
|
||||
}
|
||||
if (!traits.get_supported_modes().empty()) {
|
||||
ESP_LOGCONFIG(tag, " [x] Supported modes:");
|
||||
for (ClimateMode m : traits.get_supported_modes())
|
||||
|
@ -1,19 +1,10 @@
|
||||
#include "climate_traits.h"
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace climate {
|
||||
|
||||
int8_t ClimateTraits::get_temperature_accuracy_decimals() const {
|
||||
// use printf %g to find number of digits based on temperature step
|
||||
char buf[32];
|
||||
sprintf(buf, "%.5g", this->visual_temperature_step_);
|
||||
std::string str{buf};
|
||||
size_t dot_pos = str.find('.');
|
||||
if (dot_pos == std::string::npos)
|
||||
return 0;
|
||||
|
||||
return str.length() - dot_pos - 1;
|
||||
return step_to_accuracy_decimals(this->visual_temperature_step_);
|
||||
}
|
||||
|
||||
} // namespace climate
|
||||
|
@ -5,7 +5,6 @@ from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_SOURCE_ID,
|
||||
)
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
@ -15,12 +14,15 @@ from .. import copy_ns
|
||||
CopySwitch = copy_ns.class_("CopySwitch", switch.Switch, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CopySwitch),
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
switch.switch_schema(CopySwitch)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
|
||||
@ -30,8 +32,7 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await switch.register_switch(var, config)
|
||||
var = await switch.new_switch(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
|
@ -13,8 +13,9 @@ void CSE7766Component::loop() {
|
||||
this->raw_data_index_ = 0;
|
||||
}
|
||||
|
||||
if (this->available() == 0)
|
||||
if (this->available() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->last_transmission_ = now;
|
||||
while (this->available() != 0) {
|
||||
@ -22,6 +23,7 @@ void CSE7766Component::loop() {
|
||||
if (!this->check_byte_()) {
|
||||
this->raw_data_index_ = 0;
|
||||
this->status_set_warning();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this->raw_data_index_ == 23) {
|
||||
@ -51,8 +53,9 @@ bool CSE7766Component::check_byte_() {
|
||||
|
||||
if (index == 23) {
|
||||
uint8_t checksum = 0;
|
||||
for (uint8_t i = 2; i < 23; i++)
|
||||
for (uint8_t i = 2; i < 23; i++) {
|
||||
checksum += this->raw_data_[i];
|
||||
}
|
||||
|
||||
if (checksum != this->raw_data_[23]) {
|
||||
ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, this->raw_data_[23]);
|
||||
@ -66,20 +69,34 @@ bool CSE7766Component::check_byte_() {
|
||||
void CSE7766Component::parse_data_() {
|
||||
ESP_LOGVV(TAG, "CSE7766 Data: ");
|
||||
for (uint8_t i = 0; i < 23; i++) {
|
||||
ESP_LOGVV(TAG, " i=%u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i, BYTE_TO_BINARY(this->raw_data_[i]),
|
||||
ESP_LOGVV(TAG, " %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->raw_data_[i]),
|
||||
this->raw_data_[i]);
|
||||
}
|
||||
|
||||
uint8_t header1 = this->raw_data_[0];
|
||||
if (header1 == 0xAA) {
|
||||
ESP_LOGW(TAG, "CSE7766 not calibrated!");
|
||||
ESP_LOGE(TAG, "CSE7766 not calibrated!");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((header1 & 0xF0) == 0xF0 && ((header1 >> 0) & 1) == 1) {
|
||||
ESP_LOGW(TAG, "CSE7766 reports abnormal hardware: (0x%02X)", header1);
|
||||
ESP_LOGW(TAG, " Coefficient storage area is abnormal.");
|
||||
return;
|
||||
bool power_cycle_exceeds_range = false;
|
||||
|
||||
if ((header1 & 0xF0) == 0xF0) {
|
||||
if (header1 & 0xD) {
|
||||
ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1);
|
||||
if (header1 & (1 << 3)) {
|
||||
ESP_LOGE(TAG, " Voltage cycle exceeds range.");
|
||||
}
|
||||
if (header1 & (1 << 2)) {
|
||||
ESP_LOGE(TAG, " Current cycle exceeds range.");
|
||||
}
|
||||
if (header1 & (1 << 0)) {
|
||||
ESP_LOGE(TAG, " Coefficient storage area is abnormal.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
power_cycle_exceeds_range = header1 & (1 << 1);
|
||||
}
|
||||
|
||||
uint32_t voltage_calib = this->get_24_bit_uint_(2);
|
||||
@ -92,46 +109,29 @@ void CSE7766Component::parse_data_() {
|
||||
uint8_t adj = this->raw_data_[20];
|
||||
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
|
||||
|
||||
bool power_ok = true;
|
||||
bool voltage_ok = true;
|
||||
bool current_ok = true;
|
||||
|
||||
if (header1 > 0xF0) {
|
||||
// ESP_LOGV(TAG, "CSE7766 reports abnormal hardware: (0x%02X)", byte);
|
||||
if ((header1 >> 3) & 1) {
|
||||
ESP_LOGV(TAG, " Voltage cycle exceeds range.");
|
||||
voltage_ok = false;
|
||||
}
|
||||
if ((header1 >> 2) & 1) {
|
||||
ESP_LOGV(TAG, " Current cycle exceeds range.");
|
||||
current_ok = false;
|
||||
}
|
||||
if ((header1 >> 1) & 1) {
|
||||
ESP_LOGV(TAG, " Power cycle exceeds range.");
|
||||
power_ok = false;
|
||||
}
|
||||
if ((header1 >> 0) & 1) {
|
||||
ESP_LOGV(TAG, " Coefficient storage area is abnormal.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((adj & 0x40) == 0x40 && voltage_ok && current_ok) {
|
||||
bool have_voltage = adj & 0x40;
|
||||
if (have_voltage) {
|
||||
// voltage cycle of serial port outputted is a complete cycle;
|
||||
this->voltage_acc_ += voltage_calib / float(voltage_cycle);
|
||||
this->voltage_counts_ += 1;
|
||||
}
|
||||
|
||||
float power = 0;
|
||||
if ((adj & 0x10) == 0x10 && voltage_ok && current_ok && power_ok) {
|
||||
bool have_power = adj & 0x10;
|
||||
float power = 0.0f;
|
||||
|
||||
if (have_power) {
|
||||
// power cycle of serial port outputted is a complete cycle;
|
||||
power = power_calib / float(power_cycle);
|
||||
// According to the user manual, power cycle exceeding range means the measured power is 0
|
||||
if (!power_cycle_exceeds_range) {
|
||||
power = power_calib / float(power_cycle);
|
||||
}
|
||||
this->power_acc_ += power;
|
||||
this->power_counts_ += 1;
|
||||
|
||||
uint32_t difference;
|
||||
if (this->cf_pulses_last_ == 0)
|
||||
if (this->cf_pulses_last_ == 0) {
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
}
|
||||
|
||||
if (cf_pulses < this->cf_pulses_last_) {
|
||||
difference = cf_pulses + (0x10000 - this->cf_pulses_last_);
|
||||
@ -139,41 +139,52 @@ void CSE7766Component::parse_data_() {
|
||||
difference = cf_pulses - this->cf_pulses_last_;
|
||||
}
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0;
|
||||
this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f;
|
||||
this->energy_total_counts_ += 1;
|
||||
}
|
||||
|
||||
if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) {
|
||||
if (adj & 0x20) {
|
||||
// indicates current cycle of serial port outputted is a complete cycle;
|
||||
this->current_acc_ += current_calib / float(current_cycle);
|
||||
float current = 0.0f;
|
||||
if (have_voltage && !have_power) {
|
||||
// Testing has shown that when we have voltage and current but not power, that means the power is 0.
|
||||
// We report a power of 0, which in turn means we should report a current of 0.
|
||||
this->power_counts_ += 1;
|
||||
} else if (power != 0.0f) {
|
||||
current = current_calib / float(current_cycle);
|
||||
}
|
||||
this->current_acc_ += current;
|
||||
this->current_counts_ += 1;
|
||||
}
|
||||
}
|
||||
void CSE7766Component::update() {
|
||||
float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f;
|
||||
float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f;
|
||||
float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f;
|
||||
const auto publish_state = [](const char *name, sensor::Sensor *sensor, float &acc, uint32_t &counts) {
|
||||
if (counts != 0) {
|
||||
const auto avg = acc / counts;
|
||||
|
||||
ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_,
|
||||
this->power_acc_);
|
||||
ESP_LOGV(TAG, "Got voltage_counts=%d current_counts=%d power_counts=%d", this->voltage_counts_, this->current_counts_,
|
||||
this->power_counts_);
|
||||
ESP_LOGD(TAG, "Got voltage=%.1fV current=%.1fA power=%.1fW", voltage, current, power);
|
||||
ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%d %s=%.1f", name, acc, name, counts, name, avg);
|
||||
|
||||
if (this->voltage_sensor_ != nullptr)
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
if (this->current_sensor_ != nullptr)
|
||||
this->current_sensor_->publish_state(current);
|
||||
if (this->power_sensor_ != nullptr)
|
||||
this->power_sensor_->publish_state(power);
|
||||
if (this->energy_sensor_ != nullptr)
|
||||
this->energy_sensor_->publish_state(this->energy_total_);
|
||||
if (sensor != nullptr) {
|
||||
sensor->publish_state(avg);
|
||||
}
|
||||
|
||||
this->voltage_acc_ = 0.0f;
|
||||
this->current_acc_ = 0.0f;
|
||||
this->power_acc_ = 0.0f;
|
||||
this->voltage_counts_ = 0;
|
||||
this->power_counts_ = 0;
|
||||
this->current_counts_ = 0;
|
||||
acc = 0.0f;
|
||||
counts = 0;
|
||||
}
|
||||
};
|
||||
|
||||
publish_state("voltage", this->voltage_sensor_, this->voltage_acc_, this->voltage_counts_);
|
||||
publish_state("current", this->current_sensor_, this->current_acc_, this->current_counts_);
|
||||
publish_state("power", this->power_sensor_, this->power_acc_, this->power_counts_);
|
||||
|
||||
if (this->energy_total_counts_ != 0) {
|
||||
ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%d", this->energy_total_, this->energy_total_counts_);
|
||||
|
||||
if (this->energy_sensor_ != nullptr) {
|
||||
this->energy_sensor_->publish_state(this->energy_total_);
|
||||
}
|
||||
this->energy_total_counts_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) {
|
||||
|
@ -39,6 +39,8 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
|
||||
uint32_t voltage_counts_{0};
|
||||
uint32_t current_counts_{0};
|
||||
uint32_t power_counts_{0};
|
||||
// Setting this to 1 means it will always publish 0 once at startup
|
||||
uint32_t energy_total_counts_{1};
|
||||
};
|
||||
|
||||
} // namespace cse7766
|
||||
|
@ -13,6 +13,7 @@ using namespace esphome::cover;
|
||||
CoverTraits CurrentBasedCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_toggle(true);
|
||||
traits.set_is_assumed_state(false);
|
||||
return traits;
|
||||
}
|
||||
@ -20,6 +21,20 @@ void CurrentBasedCover::control(const CoverCall &call) {
|
||||
if (call.get_stop()) {
|
||||
this->direction_idle_();
|
||||
}
|
||||
if (call.get_toggle().has_value()) {
|
||||
if (this->current_operation != COVER_OPERATION_IDLE) {
|
||||
this->start_direction_(COVER_OPERATION_IDLE);
|
||||
this->publish_state();
|
||||
} else {
|
||||
if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) {
|
||||
this->target_position_ = COVER_OPEN;
|
||||
this->start_direction_(COVER_OPERATION_OPENING);
|
||||
} else {
|
||||
this->target_position_ = COVER_CLOSED;
|
||||
this->start_direction_(COVER_OPERATION_CLOSING);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
if (pos == this->position) {
|
||||
@ -164,7 +179,7 @@ bool CurrentBasedCover::is_closing_blocked_() const {
|
||||
if (this->close_obstacle_current_threshold_ == FLT_MAX) {
|
||||
return false;
|
||||
}
|
||||
return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
|
||||
return this->close_sensor_->get_state() > this->close_obstacle_current_threshold_;
|
||||
}
|
||||
bool CurrentBasedCover::is_initial_delay_finished_() const {
|
||||
return millis() - this->start_dir_time_ > this->start_sensing_delay_;
|
||||
@ -202,9 +217,11 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
|
||||
trig = this->stop_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_OPENING:
|
||||
this->last_operation_ = dir;
|
||||
trig = this->open_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
this->last_operation_ = dir;
|
||||
trig = this->close_trigger_;
|
||||
break;
|
||||
default:
|
||||
|
@ -89,6 +89,8 @@ class CurrentBasedCover : public cover::Cover, public Component {
|
||||
uint32_t start_dir_time_{0};
|
||||
uint32_t last_publish_time_{0};
|
||||
float target_position_{0};
|
||||
|
||||
cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
|
||||
};
|
||||
|
||||
} // namespace current_based
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "esphome/components/cover/cover.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
|
@ -10,13 +10,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CustomSwitchConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_SWITCHES): cv.ensure_list(
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(switch.Switch),
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.Required(CONF_SWITCHES): cv.ensure_list(switch.switch_schema(switch.Switch)),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom {
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace custom_component {
|
||||
|
||||
|
@ -29,8 +29,9 @@ void DAC7678Output::setup() {
|
||||
ESP_LOGE(TAG, "Reset failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
} else
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Reset succeeded");
|
||||
}
|
||||
|
||||
delayMicroseconds(1000);
|
||||
|
||||
@ -40,16 +41,18 @@ void DAC7678Output::setup() {
|
||||
ESP_LOGE(TAG, "Set internal reference failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
} else
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Internal reference enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DAC7678Output::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Setting up DAC7678 failed!");
|
||||
} else
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "DAC7678 initialised");
|
||||
}
|
||||
}
|
||||
|
||||
void DAC7678Output::register_channel(DAC7678Channel *channel) {
|
||||
|
1
esphome/components/daikin_brc/__init__.py
Normal file
1
esphome/components/daikin_brc/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@hagak"]
|
24
esphome/components/daikin_brc/climate.py
Normal file
24
esphome/components/daikin_brc/climate.py
Normal file
@ -0,0 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import climate_ir
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ["climate_ir"]
|
||||
|
||||
daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
|
||||
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONF_USE_FAHRENHEIT = "use_fahrenheit"
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DaikinBrcClimate),
|
||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await climate_ir.register_climate_ir(var, config)
|
||||
cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))
|
273
esphome/components/daikin_brc/daikin_brc.cpp
Normal file
273
esphome/components/daikin_brc/daikin_brc.cpp
Normal file
@ -0,0 +1,273 @@
|
||||
#include "daikin_brc.h"
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daikin_brc {
|
||||
|
||||
static const char *const TAG = "daikin_brc.climate";
|
||||
|
||||
void DaikinBrcClimate::control(const climate::ClimateCall &call) {
|
||||
this->mode_button_ = 0x00;
|
||||
if (call.get_mode().has_value()) {
|
||||
// Need to determine if this is call due to Mode button pressed so that we can set the Mode button byte
|
||||
this->mode_button_ = DAIKIN_BRC_IR_MODE_BUTTON;
|
||||
}
|
||||
ClimateIR::control(call);
|
||||
}
|
||||
|
||||
void DaikinBrcClimate::transmit_state() {
|
||||
uint8_t remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE] = {0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, 0x11,
|
||||
0xDA, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x20, 0x00};
|
||||
|
||||
remote_state[12] = this->alt_mode_();
|
||||
remote_state[13] = this->mode_button_;
|
||||
remote_state[14] = this->operation_mode_();
|
||||
remote_state[17] = this->temperature_();
|
||||
remote_state[18] = this->fan_speed_swing_();
|
||||
|
||||
// Calculate checksum
|
||||
for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1; i++) {
|
||||
remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1] += remote_state[i];
|
||||
}
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto *data = transmit.get_data();
|
||||
data->set_carrier_frequency(DAIKIN_BRC_IR_FREQUENCY);
|
||||
|
||||
data->mark(DAIKIN_BRC_HEADER_MARK);
|
||||
data->space(DAIKIN_BRC_HEADER_SPACE);
|
||||
for (int i = 0; i < DAIKIN_BRC_PREAMBLE_SIZE; i++) {
|
||||
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
|
||||
data->mark(DAIKIN_BRC_BIT_MARK);
|
||||
bool bit = remote_state[i] & mask;
|
||||
data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
|
||||
}
|
||||
}
|
||||
data->mark(DAIKIN_BRC_BIT_MARK);
|
||||
data->space(DAIKIN_BRC_MESSAGE_SPACE);
|
||||
data->mark(DAIKIN_BRC_HEADER_MARK);
|
||||
data->space(DAIKIN_BRC_HEADER_SPACE);
|
||||
|
||||
for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE; i++) {
|
||||
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
|
||||
data->mark(DAIKIN_BRC_BIT_MARK);
|
||||
bool bit = remote_state[i] & mask;
|
||||
data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
|
||||
}
|
||||
}
|
||||
|
||||
data->mark(DAIKIN_BRC_BIT_MARK);
|
||||
data->space(0);
|
||||
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
uint8_t DaikinBrcClimate::alt_mode_() {
|
||||
uint8_t alt_mode = 0x00;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
alt_mode = 0x23;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
alt_mode = 0x63;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
default:
|
||||
alt_mode = 0x73;
|
||||
break;
|
||||
}
|
||||
return alt_mode;
|
||||
}
|
||||
|
||||
uint8_t DaikinBrcClimate::operation_mode_() {
|
||||
uint8_t operating_mode = DAIKIN_BRC_MODE_ON;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode |= DAIKIN_BRC_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
operating_mode |= DAIKIN_BRC_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode |= DAIKIN_BRC_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode |= DAIKIN_BRC_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
operating_mode |= DAIKIN_BRC_MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = DAIKIN_BRC_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint8_t DaikinBrcClimate::fan_speed_swing_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value()) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_BRC_FAN_1;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = DAIKIN_BRC_FAN_2;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = DAIKIN_BRC_FAN_3;
|
||||
break;
|
||||
default:
|
||||
fan_speed = DAIKIN_BRC_FAN_1;
|
||||
}
|
||||
|
||||
// If swing is enabled switch first 4 bits to 1111
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
fan_speed |= DAIKIN_BRC_IR_SWING_ON;
|
||||
break;
|
||||
default:
|
||||
fan_speed |= DAIKIN_BRC_IR_SWING_OFF;
|
||||
break;
|
||||
}
|
||||
return fan_speed;
|
||||
}
|
||||
|
||||
uint8_t DaikinBrcClimate::temperature_() {
|
||||
// Force special temperatures depending on the mode
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
if (this->fahrenheit_) {
|
||||
return DAIKIN_BRC_IR_DRY_FAN_TEMP_F;
|
||||
}
|
||||
return DAIKIN_BRC_IR_DRY_FAN_TEMP_C;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
default:
|
||||
uint8_t temperature;
|
||||
// Temperature in remote is in F
|
||||
if (this->fahrenheit_) {
|
||||
temperature = (uint8_t) roundf(
|
||||
clamp<float>(((this->target_temperature * 1.8) + 32), DAIKIN_BRC_TEMP_MIN_F, DAIKIN_BRC_TEMP_MAX_F));
|
||||
} else {
|
||||
temperature = ((uint8_t) roundf(this->target_temperature) - 9) << 1;
|
||||
}
|
||||
return temperature;
|
||||
}
|
||||
}
|
||||
|
||||
bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
uint8_t checksum = 0;
|
||||
for (int i = 0; i < (DAIKIN_BRC_STATE_FRAME_SIZE - 1); i++) {
|
||||
checksum += frame[i];
|
||||
}
|
||||
if (frame[DAIKIN_BRC_STATE_FRAME_SIZE - 1] != checksum) {
|
||||
ESP_LOGCONFIG(TAG, "Bad CheckSum %x", checksum);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t mode = frame[7];
|
||||
if (mode & DAIKIN_BRC_MODE_ON) {
|
||||
switch (mode & 0xF0) {
|
||||
case DAIKIN_BRC_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case DAIKIN_BRC_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case DAIKIN_BRC_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case DAIKIN_BRC_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case DAIKIN_BRC_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
}
|
||||
|
||||
uint8_t temperature = frame[10];
|
||||
float temperature_c;
|
||||
if (this->fahrenheit_) {
|
||||
temperature_c = clamp<float>(((temperature - 32) / 1.8), DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C);
|
||||
} else {
|
||||
temperature_c = (temperature >> 1) + 9;
|
||||
}
|
||||
|
||||
this->target_temperature = temperature_c;
|
||||
|
||||
uint8_t fan_mode = frame[11];
|
||||
uint8_t swing_mode = frame[11];
|
||||
switch (swing_mode & 0xF) {
|
||||
case DAIKIN_BRC_IR_SWING_ON:
|
||||
this->swing_mode = climate::CLIMATE_SWING_BOTH;
|
||||
break;
|
||||
case DAIKIN_BRC_IR_SWING_OFF:
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (fan_mode & 0xF0) {
|
||||
case DAIKIN_BRC_FAN_1:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DAIKIN_BRC_FAN_2:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case DAIKIN_BRC_FAN_3:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
}
|
||||
this->publish_state();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DaikinBrcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
uint8_t state_frame[DAIKIN_BRC_STATE_FRAME_SIZE] = {};
|
||||
if (!data.expect_item(DAIKIN_BRC_HEADER_MARK, DAIKIN_BRC_HEADER_SPACE)) {
|
||||
return false;
|
||||
}
|
||||
for (uint8_t pos = 0; pos < DAIKIN_BRC_STATE_FRAME_SIZE; pos++) {
|
||||
uint8_t byte = 0;
|
||||
for (int8_t bit = 0; bit < 8; bit++) {
|
||||
if (data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ONE_SPACE)) {
|
||||
byte |= 1 << bit;
|
||||
} else if (!data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ZERO_SPACE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
state_frame[pos] = byte;
|
||||
if (pos == 0) {
|
||||
// frame header
|
||||
if (byte != 0x11)
|
||||
return false;
|
||||
} else if (pos == 1) {
|
||||
// frame header
|
||||
if (byte != 0xDA)
|
||||
return false;
|
||||
} else if (pos == 2) {
|
||||
// frame header
|
||||
if (byte != 0x17)
|
||||
return false;
|
||||
} else if (pos == 3) {
|
||||
// frame header
|
||||
if (byte != 0x18)
|
||||
return false;
|
||||
} else if (pos == 4) {
|
||||
// frame type
|
||||
if (byte != 0x00)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this->parse_state_frame_(state_frame);
|
||||
}
|
||||
|
||||
} // namespace daikin_brc
|
||||
} // namespace esphome
|
82
esphome/components/daikin_brc/daikin_brc.h
Normal file
82
esphome/components/daikin_brc/daikin_brc.h
Normal file
@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/climate_ir/climate_ir.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daikin_brc {
|
||||
|
||||
// Values for Daikin BRC4CXXX IR Controllers
|
||||
// Temperature
|
||||
const uint8_t DAIKIN_BRC_TEMP_MIN_F = 60; // fahrenheit
|
||||
const uint8_t DAIKIN_BRC_TEMP_MAX_F = 90; // fahrenheit
|
||||
const float DAIKIN_BRC_TEMP_MIN_C = (DAIKIN_BRC_TEMP_MIN_F - 32) / 1.8; // fahrenheit
|
||||
const float DAIKIN_BRC_TEMP_MAX_C = (DAIKIN_BRC_TEMP_MAX_F - 32) / 1.8; // fahrenheit
|
||||
|
||||
// Modes
|
||||
const uint8_t DAIKIN_BRC_MODE_AUTO = 0x30;
|
||||
const uint8_t DAIKIN_BRC_MODE_COOL = 0x20;
|
||||
const uint8_t DAIKIN_BRC_MODE_HEAT = 0x10;
|
||||
const uint8_t DAIKIN_BRC_MODE_DRY = 0x70;
|
||||
const uint8_t DAIKIN_BRC_MODE_FAN = 0x00;
|
||||
const uint8_t DAIKIN_BRC_MODE_OFF = 0x00;
|
||||
const uint8_t DAIKIN_BRC_MODE_ON = 0x01;
|
||||
|
||||
// Fan Speed
|
||||
const uint8_t DAIKIN_BRC_FAN_1 = 0x10;
|
||||
const uint8_t DAIKIN_BRC_FAN_2 = 0x30;
|
||||
const uint8_t DAIKIN_BRC_FAN_3 = 0x50;
|
||||
const uint8_t DAIKIN_BRC_FAN_AUTO = 0xA0;
|
||||
|
||||
// IR Transmission
|
||||
const uint32_t DAIKIN_BRC_IR_FREQUENCY = 38000;
|
||||
const uint32_t DAIKIN_BRC_HEADER_MARK = 5070;
|
||||
const uint32_t DAIKIN_BRC_HEADER_SPACE = 2140;
|
||||
const uint32_t DAIKIN_BRC_BIT_MARK = 370;
|
||||
const uint32_t DAIKIN_BRC_ONE_SPACE = 1780;
|
||||
const uint32_t DAIKIN_BRC_ZERO_SPACE = 710;
|
||||
const uint32_t DAIKIN_BRC_MESSAGE_SPACE = 29410;
|
||||
|
||||
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_F = 72; // Dry/Fan mode is always 17 Celsius.
|
||||
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_C = (17 - 9) * 2; // Dry/Fan mode is always 17 Celsius.
|
||||
const uint8_t DAIKIN_BRC_IR_SWING_ON = 0x5;
|
||||
const uint8_t DAIKIN_BRC_IR_SWING_OFF = 0x6;
|
||||
const uint8_t DAIKIN_BRC_IR_MODE_BUTTON = 0x4; // This is set after a mode action
|
||||
|
||||
// State Frame size
|
||||
const uint8_t DAIKIN_BRC_STATE_FRAME_SIZE = 15;
|
||||
// Preamble size
|
||||
const uint8_t DAIKIN_BRC_PREAMBLE_SIZE = 7;
|
||||
// Transmit Frame size - includes a preamble
|
||||
const uint8_t DAIKIN_BRC_TRANSMIT_FRAME_SIZE = DAIKIN_BRC_PREAMBLE_SIZE + DAIKIN_BRC_STATE_FRAME_SIZE;
|
||||
|
||||
class DaikinBrcClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
DaikinBrcClimate()
|
||||
: climate_ir::ClimateIR(DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C, 0.5f, true, true,
|
||||
{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH}) {}
|
||||
|
||||
/// Set use of Fahrenheit units
|
||||
void set_fahrenheit(bool value) {
|
||||
this->fahrenheit_ = value;
|
||||
this->temperature_step_ = value ? 0.5f : 1.0f;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t mode_button_ = 0x00;
|
||||
// Capture if the MODE was changed
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
uint8_t alt_mode_();
|
||||
uint8_t operation_mode_();
|
||||
uint8_t fan_speed_swing_();
|
||||
uint8_t temperature_();
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
bool parse_state_frame_(const uint8_t frame[]);
|
||||
bool fahrenheit_{false};
|
||||
};
|
||||
|
||||
} // namespace daikin_brc
|
||||
} // namespace esphome
|
@ -4,6 +4,8 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esp_one_wire.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas {
|
||||
|
||||
|
@ -85,9 +85,7 @@ bool HOT IRAM_ATTR ESPOneWire::read_bit() {
|
||||
// whereas on esp-idf it already happens during the pin_mode(OUTPUT)
|
||||
// manually correct for this with these constants.
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
uint32_t timing_constant = 14;
|
||||
#elif defined(USE_ESP32_FRAMEWORK_ESP_IDF)
|
||||
#ifdef USE_ESP32
|
||||
uint32_t timing_constant = 12;
|
||||
#else
|
||||
uint32_t timing_constant = 14;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_ID, CONF_ADDRESS
|
||||
|
||||
CODEOWNERS = ["@s1lvi0"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
@ -15,7 +15,12 @@ DalyBmsComponent = daly_bms.class_(
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.GenerateID(): cv.declare_id(DalyBmsComponent)})
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DalyBmsComponent),
|
||||
cv.Optional(CONF_ADDRESS, default=0x80): cv.positive_int,
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.polling_component_schema("30s"))
|
||||
)
|
||||
@ -25,3 +30,4 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
cg.add(var.set_address(config[CONF_ADDRESS]))
|
||||
|
@ -50,7 +50,7 @@ void DalyBmsComponent::request_data_(uint8_t data_id) {
|
||||
uint8_t request_message[DALY_FRAME_SIZE];
|
||||
|
||||
request_message[0] = 0xA5; // Start Flag
|
||||
request_message[1] = 0x80; // Communication Module Address
|
||||
request_message[1] = addr_; // Communication Module Address
|
||||
request_message[2] = data_id; // Data ID
|
||||
request_message[3] = 0x08; // Data Length (Fixed)
|
||||
request_message[4] = 0x00; // Empty Data
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace daly_bms {
|
||||
|
||||
@ -69,11 +71,14 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice {
|
||||
void update() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
void set_address(uint8_t address) { this->addr_ = address; }
|
||||
|
||||
protected:
|
||||
void request_data_(uint8_t data_id);
|
||||
void decode_data_(std::vector<uint8_t> data);
|
||||
|
||||
uint8_t addr_;
|
||||
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *battery_level_sensor_{nullptr};
|
||||
|
@ -1,10 +1,13 @@
|
||||
from pathlib import Path
|
||||
import requests
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.const import CONF_WIFI
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
from esphome import git
|
||||
|
||||
|
||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
||||
@ -24,9 +27,12 @@ def validate_import_url(value):
|
||||
|
||||
|
||||
CONF_PACKAGE_IMPORT_URL = "package_import_url"
|
||||
CONF_IMPORT_FULL_CONFIG = "import_full_config"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url,
|
||||
cv.Optional(CONF_IMPORT_FULL_CONFIG, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
@ -40,10 +46,15 @@ wifi:
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_DASHBOARD_IMPORT")
|
||||
url = config[CONF_PACKAGE_IMPORT_URL]
|
||||
if config[CONF_IMPORT_FULL_CONFIG]:
|
||||
url += "?full_config"
|
||||
cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL]))
|
||||
|
||||
|
||||
def import_config(path: str, name: str, project_name: str, import_url: str) -> None:
|
||||
def import_config(
|
||||
path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI
|
||||
) -> None:
|
||||
p = Path(path)
|
||||
|
||||
if p.exists():
|
||||
@ -61,15 +72,30 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
|
||||
encoding="utf8",
|
||||
)
|
||||
else:
|
||||
config = {
|
||||
"substitutions": {"name": name},
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": {
|
||||
"name": "${name}",
|
||||
"name_add_mac_suffix": False,
|
||||
},
|
||||
}
|
||||
p.write_text(
|
||||
dump(config) + WIFI_CONFIG,
|
||||
encoding="utf8",
|
||||
)
|
||||
git_file = git.GitFile.from_shorthand(import_url)
|
||||
|
||||
if git_file.query and "full_config" in git_file.query:
|
||||
url = git_file.raw_url
|
||||
try:
|
||||
req = requests.get(url, timeout=30)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise ValueError(f"Error while fetching {url}: {e}") from e
|
||||
|
||||
p.write_text(req.text, encoding="utf8")
|
||||
|
||||
else:
|
||||
config = {
|
||||
"substitutions": {"name": name},
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": {
|
||||
"name": "${name}",
|
||||
"name_add_mac_suffix": False,
|
||||
},
|
||||
}
|
||||
output = dump(config)
|
||||
|
||||
if network == CONF_WIFI:
|
||||
output += WIFI_CONFIG
|
||||
|
||||
p.write_text(output, encoding="utf8")
|
||||
|
@ -1,11 +1,15 @@
|
||||
import esphome.config_validation as cv
|
||||
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_ID,
|
||||
CONF_DEVICE,
|
||||
CONF_FREE,
|
||||
CONF_FRAGMENTATION,
|
||||
CONF_BLOCK,
|
||||
CONF_DEVICE,
|
||||
CONF_FRAGMENTATION,
|
||||
CONF_FREE,
|
||||
CONF_ID,
|
||||
CONF_LEVEL,
|
||||
CONF_LOGGER,
|
||||
CONF_LOOP_TIME,
|
||||
)
|
||||
|
||||
@ -39,6 +43,18 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).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
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
@ -38,14 +38,9 @@ static uint32_t get_free_heap() {
|
||||
|
||||
void DebugComponent::dump_config() {
|
||||
std::string device_info;
|
||||
std::string reset_reason;
|
||||
device_info.reserve(256);
|
||||
|
||||
#ifndef ESPHOME_LOG_HAS_DEBUG
|
||||
ESP_LOGE(TAG, "Debug Component requires debug log level!");
|
||||
this->status_set_error();
|
||||
return;
|
||||
#endif
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Debug component:");
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "Device info", this->device_info_);
|
||||
@ -146,7 +141,6 @@ void DebugComponent::dump_config() {
|
||||
device_info += "|EFuse MAC: ";
|
||||
device_info += mac;
|
||||
|
||||
const char *reset_reason;
|
||||
switch (rtc_get_reset_reason(0)) {
|
||||
case POWERON_RESET:
|
||||
reset_reason = "Power On Reset";
|
||||
@ -196,7 +190,7 @@ void DebugComponent::dump_config() {
|
||||
default:
|
||||
reset_reason = "Unknown Reset Reason";
|
||||
}
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason);
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
|
||||
device_info += "|Reset: ";
|
||||
device_info += reset_reason;
|
||||
|
||||
@ -270,6 +264,8 @@ void DebugComponent::dump_config() {
|
||||
device_info += ESP.getResetReason().c_str();
|
||||
device_info += "|";
|
||||
device_info += ESP.getResetInfo().c_str();
|
||||
|
||||
reset_reason = ESP.getResetReason().c_str();
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
@ -278,6 +274,9 @@ void DebugComponent::dump_config() {
|
||||
device_info.resize(255);
|
||||
this->device_info_->publish_state(device_info);
|
||||
}
|
||||
if (this->reset_reason_ != nullptr) {
|
||||
this->reset_reason_->publish_state(reset_reason);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ class DebugComponent : public PollingComponent {
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; }
|
||||
void set_reset_reason_sensor(text_sensor::TextSensor *reset_reason) { reset_reason_ = reset_reason; }
|
||||
#endif // USE_TEXT_SENSOR
|
||||
#ifdef USE_SENSOR
|
||||
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
|
||||
@ -50,6 +51,7 @@ class DebugComponent : public PollingComponent {
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *device_info_{nullptr};
|
||||
text_sensor::TextSensor *reset_reason_{nullptr};
|
||||
#endif // USE_TEXT_SENSOR
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,29 @@
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_DEVICE, ENTITY_CATEGORY_DIAGNOSTIC
|
||||
from esphome.const import (
|
||||
CONF_DEVICE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_CHIP,
|
||||
ICON_RESTART,
|
||||
)
|
||||
|
||||
from . import CONF_DEBUG_ID, DebugComponent
|
||||
|
||||
DEPENDENCIES = ["debug"]
|
||||
|
||||
|
||||
CONF_RESET_REASON = "reset_reason"
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
|
||||
cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
icon=ICON_CHIP,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_RESET_REASON): text_sensor.text_sensor_schema(
|
||||
icon=ICON_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
@ -24,3 +35,6 @@ async def to_code(config):
|
||||
if CONF_DEVICE in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_DEVICE])
|
||||
cg.add(debug_component.set_device_info_sensor(sens))
|
||||
if CONF_RESET_REASON in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_RESET_REASON])
|
||||
cg.add(debug_component.set_reset_reason_sensor(sens))
|
||||
|
@ -114,9 +114,9 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Beginning Deep Sleep");
|
||||
if (this->sleep_duration_.has_value())
|
||||
if (this->sleep_duration_.has_value()) {
|
||||
ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_);
|
||||
|
||||
}
|
||||
App.run_safe_shutdown_hooks();
|
||||
|
||||
#if defined(USE_ESP32)
|
||||
@ -147,7 +147,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||
level = !level;
|
||||
}
|
||||
esp_deep_sleep_enable_gpio_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()),
|
||||
esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(),
|
||||
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
|
||||
}
|
||||
#endif
|
||||
|
@ -349,13 +349,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
CONF_ICON: ICON_BLUETOOTH,
|
||||
},
|
||||
],
|
||||
): [
|
||||
switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DemoSwitch),
|
||||
}
|
||||
)
|
||||
],
|
||||
): [switch.switch_schema(DemoSwitch).extend(cv.COMPONENT_SCHEMA)],
|
||||
cv.Optional(
|
||||
CONF_TEXT_SENSORS,
|
||||
default=[
|
||||
@ -422,9 +416,8 @@ async def to_code(config):
|
||||
await cg.register_component(var, conf)
|
||||
|
||||
for conf in config[CONF_SWITCHES]:
|
||||
var = cg.new_Pvariable(conf[CONF_ID])
|
||||
var = await switch.new_switch(conf)
|
||||
await cg.register_component(var, conf)
|
||||
await switch.register_switch(var, conf)
|
||||
|
||||
for conf in config[CONF_TEXT_SENSORS]:
|
||||
var = await text_sensor.new_text_sensor(conf)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE, CONF_VOLUME
|
||||
from esphome.components import uart
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
@ -19,7 +19,6 @@ DFPlayerIsPlayingCondition = dfplayer_ns.class_(
|
||||
MULTI_CONF = True
|
||||
CONF_FOLDER = "folder"
|
||||
CONF_LOOP = "loop"
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_EQ_PRESET = "eq_preset"
|
||||
CONF_ON_FINISHED_PLAYBACK = "on_finished_playback"
|
||||
|
||||
|
@ -77,14 +77,16 @@ void DFPlayer::loop() {
|
||||
case 0x3A:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB loaded");
|
||||
} else if (argument == 2)
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card loaded");
|
||||
}
|
||||
break;
|
||||
case 0x3B:
|
||||
if (argument == 1) {
|
||||
ESP_LOGI(TAG, "USB unloaded");
|
||||
} else if (argument == 2)
|
||||
} else if (argument == 2) {
|
||||
ESP_LOGI(TAG, "TF Card unloaded");
|
||||
}
|
||||
break;
|
||||
case 0x3F:
|
||||
if (argument == 1) {
|
||||
|
@ -20,8 +20,8 @@ class DHT12Component : public PollingComponent, public i2c::I2CDevice {
|
||||
protected:
|
||||
bool read_data_(uint8_t *data);
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace dht12
|
||||
|
@ -591,6 +591,18 @@ void Animation::prev_frame() {
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::set_frame(int frame) {
|
||||
unsigned abs_frame = abs(frame);
|
||||
|
||||
if (abs_frame < this->animation_frame_count_) {
|
||||
if (frame >= 0) {
|
||||
this->current_frame_ = frame;
|
||||
} else {
|
||||
this->current_frame_ = this->animation_frame_count_ - abs_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
|
||||
void DisplayPage::show() { this->parent_->show_page(this); }
|
||||
void DisplayPage::show_next() { this->next_->show(); }
|
||||
|
@ -4,7 +4,9 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "display_color_utils.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
@ -491,6 +493,12 @@ class Animation : public Image {
|
||||
void next_frame();
|
||||
void prev_frame();
|
||||
|
||||
/** Selects a specific frame within the animation.
|
||||
*
|
||||
* @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
|
||||
*/
|
||||
void set_frame(int frame);
|
||||
|
||||
protected:
|
||||
int current_frame_;
|
||||
int animation_frame_count_;
|
||||
|
430
esphome/components/display_menu_base/__init__.py
Normal file
430
esphome/components/display_menu_base/__init__.py
Normal file
@ -0,0 +1,430 @@
|
||||
import re
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation, core
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TYPE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_COMMAND,
|
||||
CONF_NUMBER,
|
||||
CONF_FORMAT,
|
||||
CONF_MODE,
|
||||
CONF_ACTIVE,
|
||||
)
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.components.select import Select
|
||||
from esphome.components.number import Number
|
||||
from esphome.components.switch import Switch
|
||||
|
||||
CODEOWNERS = ["@numo68"]
|
||||
|
||||
display_menu_base_ns = cg.esphome_ns.namespace("display_menu_base")
|
||||
|
||||
CONF_DISPLAY_ID = "display_id"
|
||||
|
||||
CONF_ROTARY = "rotary"
|
||||
CONF_JOYSTICK = "joystick"
|
||||
CONF_LABEL = "label"
|
||||
CONF_MENU = "menu"
|
||||
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"
|
||||
CONF_VALUE_LAMBDA = "value_lambda"
|
||||
CONF_IMMEDIATE_EDIT = "immediate_edit"
|
||||
CONF_ROOT_ITEM_ID = "root_item_id"
|
||||
CONF_ON_ENTER = "on_enter"
|
||||
CONF_ON_LEAVE = "on_leave"
|
||||
CONF_ON_NEXT = "on_next"
|
||||
CONF_ON_PREV = "on_prev"
|
||||
|
||||
DisplayMenuComponent = display_menu_base_ns.class_("DisplayMenuComponent", cg.Component)
|
||||
|
||||
MenuItem = display_menu_base_ns.class_("MenuItem")
|
||||
MenuItemConstPtr = MenuItem.operator("ptr").operator("const")
|
||||
MenuItemMenu = display_menu_base_ns.class_("MenuItemMenu")
|
||||
MenuItemSelect = display_menu_base_ns.class_("MenuItemSelect")
|
||||
MenuItemNumber = display_menu_base_ns.class_("MenuItemNumber")
|
||||
MenuItemSwitch = display_menu_base_ns.class_("MenuItemSwitch")
|
||||
MenuItemCommand = display_menu_base_ns.class_("MenuItemCommand")
|
||||
MenuItemCustom = display_menu_base_ns.class_("MenuItemCustom")
|
||||
|
||||
UpAction = display_menu_base_ns.class_("UpAction", automation.Action)
|
||||
DownAction = display_menu_base_ns.class_("DownAction", automation.Action)
|
||||
LeftAction = display_menu_base_ns.class_("LeftAction", automation.Action)
|
||||
RightAction = display_menu_base_ns.class_("RightAction", automation.Action)
|
||||
EnterAction = display_menu_base_ns.class_("EnterAction", automation.Action)
|
||||
ShowAction = display_menu_base_ns.class_("ShowAction", automation.Action)
|
||||
HideAction = display_menu_base_ns.class_("HideAction", automation.Action)
|
||||
ShowMainAction = display_menu_base_ns.class_("ShowMainAction", automation.Action)
|
||||
|
||||
IsActiveCondition = display_menu_base_ns.class_(
|
||||
"IsActiveCondition", automation.Condition
|
||||
)
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
MenuItemType = display_menu_base_ns.enum("MenuItemType")
|
||||
|
||||
MENU_ITEM_TYPES = {
|
||||
CONF_LABEL: MenuItemType.MENU_ITEM_LABEL,
|
||||
CONF_MENU: MenuItemType.MENU_ITEM_MENU,
|
||||
CONF_BACK: MenuItemType.MENU_ITEM_BACK,
|
||||
CONF_SELECT: MenuItemType.MENU_ITEM_SELECT,
|
||||
CONF_NUMBER: MenuItemType.MENU_ITEM_NUMBER,
|
||||
CONF_SWITCH: MenuItemType.MENU_ITEM_SWITCH,
|
||||
CONF_COMMAND: MenuItemType.MENU_ITEM_COMMAND,
|
||||
CONF_CUSTOM: MenuItemType.MENU_ITEM_CUSTOM,
|
||||
}
|
||||
|
||||
MENU_ITEMS_WITH_SPECIALIZED_CLASSES = (
|
||||
CONF_MENU,
|
||||
CONF_SELECT,
|
||||
CONF_NUMBER,
|
||||
CONF_SWITCH,
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
)
|
||||
|
||||
MenuMode = display_menu_base_ns.enum("MenuMode")
|
||||
|
||||
MENU_MODES = {
|
||||
CONF_ROTARY: MenuMode.MENU_MODE_ROTARY,
|
||||
CONF_JOYSTICK: MenuMode.MENU_MODE_JOYSTICK,
|
||||
}
|
||||
|
||||
DisplayMenuOnEnterTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnEnterTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
DisplayMenuOnLeaveTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnLeaveTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
DisplayMenuOnValueTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnValueTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
DisplayMenuOnNextTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnNextTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
DisplayMenuOnPrevTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnPrevTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
|
||||
def validate_format(format):
|
||||
if re.search(r"^%([+-])*(\d+)*(\.\d+)*[fg]$", format) is None:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_FORMAT}: has to specify a printf-like format string specifying exactly one f or g type conversion, '{format}' provided"
|
||||
)
|
||||
|
||||
return format
|
||||
|
||||
|
||||
# Use a simple indirection to circumvent the recursion limitation
|
||||
def menu_item_schema(value):
|
||||
return MENU_ITEM_SCHEMA(value)
|
||||
|
||||
|
||||
MENU_ITEM_COMMON_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TEXT): cv.templatable(cv.string),
|
||||
}
|
||||
)
|
||||
|
||||
MENU_ITEM_ENTER_LEAVE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_ENTER): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnEnterTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_LEAVE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnLeaveTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
MENU_ITEM_VALUE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnValueTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA = MENU_ITEM_ENTER_LEAVE_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnValueTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
MENU_ITEM_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
CONF_LABEL: MENU_ITEM_COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItem),
|
||||
}
|
||||
),
|
||||
CONF_BACK: MENU_ITEM_COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItem),
|
||||
}
|
||||
),
|
||||
CONF_MENU: MENU_ITEM_ENTER_LEAVE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemMenu),
|
||||
cv.Required(CONF_ITEMS): cv.All(
|
||||
cv.ensure_list(menu_item_schema), cv.Length(min=1)
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_SELECT: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemSelect),
|
||||
cv.Required(CONF_SELECT): cv.use_id(Select),
|
||||
cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
),
|
||||
CONF_NUMBER: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemNumber),
|
||||
cv.Required(CONF_NUMBER): cv.use_id(Number),
|
||||
cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_FORMAT, default="%.1f"): cv.All(
|
||||
cv.string_strict,
|
||||
validate_format,
|
||||
),
|
||||
cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
),
|
||||
CONF_SWITCH: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemSwitch),
|
||||
cv.Required(CONF_SWITCH): cv.use_id(Switch),
|
||||
cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_TEXT, default="On"): cv.string_strict,
|
||||
cv.Optional(CONF_OFF_TEXT, default="Off"): cv.string_strict,
|
||||
cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
),
|
||||
CONF_COMMAND: MENU_ITEM_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCommand),
|
||||
}
|
||||
),
|
||||
CONF_CUSTOM: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCustom),
|
||||
cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_ON_NEXT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnNextTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PREV): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnPrevTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
},
|
||||
default_type="label",
|
||||
lower=True,
|
||||
)
|
||||
|
||||
DISPLAY_MENU_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||
cv.GenerateID(CONF_ROOT_ITEM_ID): cv.declare_id(MenuItemMenu),
|
||||
cv.Optional(CONF_MODE, default=CONF_ROTARY): cv.enum(MENU_MODES),
|
||||
cv.Optional(CONF_ON_ENTER): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnEnterTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_LEAVE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnLeaveTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Required(CONF_ITEMS): cv.All(
|
||||
cv.ensure_list(MENU_ITEM_SCHEMA), cv.Length(min=1)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
MENU_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.up", UpAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_up_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.down", DownAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_down_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.left", LeftAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_left_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.right", RightAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_right_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.enter", EnterAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_enter_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.show", ShowAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_show_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.hide", HideAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_hide_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"display_menu.show_main", ShowMainAction, MENU_ACTION_SCHEMA
|
||||
)
|
||||
async def menu_show_main_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"display_menu.is_active",
|
||||
IsActiveCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def display_menu_is_active_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
|
||||
async def menu_item_to_code(menu, config, parent):
|
||||
if config[CONF_TYPE] in MENU_ITEMS_WITH_SPECIALIZED_CLASSES:
|
||||
item = cg.new_Pvariable(config[CONF_ID])
|
||||
else:
|
||||
item = cg.new_Pvariable(config[CONF_ID], MENU_ITEM_TYPES[config[CONF_TYPE]])
|
||||
cg.add(parent.add_item(item))
|
||||
if CONF_TEXT in config:
|
||||
if isinstance(config[CONF_TEXT], core.Lambda):
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_TEXT], [(MenuItemConstPtr, "it")], cg.std_string
|
||||
)
|
||||
cg.add(item.set_text(template_))
|
||||
else:
|
||||
cg.add(item.set_text(config[CONF_TEXT]))
|
||||
if CONF_VALUE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_VALUE_LAMBDA],
|
||||
[(MenuItemConstPtr, "it")],
|
||||
return_type=cg.std_string,
|
||||
)
|
||||
cg.add(item.set_value_lambda(template_))
|
||||
if CONF_ITEMS in config:
|
||||
for c in config[CONF_ITEMS]:
|
||||
await menu_item_to_code(menu, c, item)
|
||||
if CONF_IMMEDIATE_EDIT in config:
|
||||
cg.add(item.set_immediate_edit(config[CONF_IMMEDIATE_EDIT]))
|
||||
if config[CONF_TYPE] == CONF_SELECT:
|
||||
var = await cg.get_variable(config[CONF_SELECT])
|
||||
cg.add(item.set_select_variable(var))
|
||||
if config[CONF_TYPE] == CONF_NUMBER:
|
||||
var = await cg.get_variable(config[CONF_NUMBER])
|
||||
cg.add(item.set_number_variable(var))
|
||||
cg.add(item.set_format(config[CONF_FORMAT]))
|
||||
if config[CONF_TYPE] == CONF_SWITCH:
|
||||
var = await cg.get_variable(config[CONF_SWITCH])
|
||||
cg.add(item.set_switch_variable(var))
|
||||
cg.add(item.set_on_text(config[CONF_ON_TEXT]))
|
||||
cg.add(item.set_off_text(config[CONF_OFF_TEXT]))
|
||||
for conf in config.get(CONF_ON_ENTER, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_LEAVE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_NEXT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_PREV, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
|
||||
|
||||
async def display_menu_to_code(menu, config):
|
||||
cg.add(menu.set_active(config[CONF_ACTIVE]))
|
||||
root_item = cg.new_Pvariable(config[CONF_ROOT_ITEM_ID])
|
||||
cg.add(menu.set_root_item(root_item))
|
||||
cg.add(menu.set_mode(config[CONF_MODE]))
|
||||
for c in config[CONF_ITEMS]:
|
||||
await menu_item_to_code(menu, c, root_item)
|
||||
for conf in config.get(CONF_ON_ENTER, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_LEAVE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
133
esphome/components/display_menu_base/automation.h
Normal file
133
esphome/components/display_menu_base/automation.h
Normal file
@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "display_menu_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
template<typename... Ts> class UpAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit UpAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->up(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DownAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit DownAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->down(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LeftAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit LeftAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->left(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class RightAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit RightAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->right(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EnterAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit EnterAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->enter(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ShowAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ShowAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->show(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class HideAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit HideAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->hide(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ShowMainAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ShowMainAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->show_main(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
template<typename... Ts> class IsActiveCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit IsActiveCondition(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
bool check(Ts... x) override { return this->menu_->is_active(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
class DisplayMenuOnEnterTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnEnterTrigger(MenuItem *parent) {
|
||||
parent->add_on_enter_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
class DisplayMenuOnLeaveTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) {
|
||||
parent->add_on_leave_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
class DisplayMenuOnValueTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnValueTrigger(MenuItem *parent) {
|
||||
parent->add_on_value_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
class DisplayMenuOnNextTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) {
|
||||
parent->add_on_next_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
class DisplayMenuOnPrevTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) {
|
||||
parent->add_on_prev_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
315
esphome/components/display_menu_base/display_menu_base.cpp
Normal file
315
esphome/components/display_menu_base/display_menu_base.cpp
Normal file
@ -0,0 +1,315 @@
|
||||
#include "display_menu_base.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
void DisplayMenuComponent::up() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
|
||||
if (this->editing_) {
|
||||
switch (this->mode_) {
|
||||
case MENU_MODE_ROTARY:
|
||||
changed = this->get_selected_item_()->select_prev();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
changed = this->cursor_up_();
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::down() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
|
||||
if (this->editing_) {
|
||||
switch (this->mode_) {
|
||||
case MENU_MODE_ROTARY:
|
||||
changed = this->get_selected_item_()->select_next();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
changed = this->cursor_down_();
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::left() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
|
||||
switch (this->get_selected_item_()->get_type()) {
|
||||
case MENU_ITEM_SELECT:
|
||||
case MENU_ITEM_SWITCH:
|
||||
case MENU_ITEM_NUMBER:
|
||||
case MENU_ITEM_CUSTOM:
|
||||
switch (this->mode_) {
|
||||
case MENU_MODE_ROTARY:
|
||||
if (this->editing_) {
|
||||
this->finish_editing_();
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case MENU_MODE_JOYSTICK:
|
||||
if (this->editing_ || this->get_selected_item_()->get_immediate_edit())
|
||||
changed = this->get_selected_item_()->select_prev();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MENU_ITEM_BACK:
|
||||
changed = this->leave_menu_();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::right() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
|
||||
switch (this->get_selected_item_()->get_type()) {
|
||||
case MENU_ITEM_SELECT:
|
||||
case MENU_ITEM_SWITCH:
|
||||
case MENU_ITEM_NUMBER:
|
||||
case MENU_ITEM_CUSTOM:
|
||||
switch (this->mode_) {
|
||||
case MENU_MODE_JOYSTICK:
|
||||
if (this->editing_ || this->get_selected_item_()->get_immediate_edit())
|
||||
changed = this->get_selected_item_()->select_next();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MENU_ITEM_MENU:
|
||||
changed = this->enter_menu_();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::enter() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
MenuItem *item = this->get_selected_item_();
|
||||
|
||||
if (this->editing_) {
|
||||
this->finish_editing_();
|
||||
changed = true;
|
||||
} else {
|
||||
switch (item->get_type()) {
|
||||
case MENU_ITEM_MENU:
|
||||
changed = this->enter_menu_();
|
||||
break;
|
||||
case MENU_ITEM_BACK:
|
||||
changed = this->leave_menu_();
|
||||
break;
|
||||
case MENU_ITEM_SELECT:
|
||||
case MENU_ITEM_SWITCH:
|
||||
case MENU_ITEM_CUSTOM:
|
||||
if (item->get_immediate_edit()) {
|
||||
changed = item->select_next();
|
||||
} else {
|
||||
this->editing_ = true;
|
||||
item->on_enter();
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case MENU_ITEM_NUMBER:
|
||||
// A number cannot be immediate in the rotary mode
|
||||
if (!item->get_immediate_edit() || this->mode_ == MENU_MODE_ROTARY) {
|
||||
this->editing_ = true;
|
||||
item->on_enter();
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case MENU_ITEM_COMMAND:
|
||||
changed = item->select_next();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::draw() {
|
||||
if (this->check_healthy_and_active_())
|
||||
this->draw_menu();
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::show_main() {
|
||||
bool disp_changed = false;
|
||||
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
this->process_initial_();
|
||||
|
||||
if (this->active_ && this->editing_)
|
||||
this->finish_editing_();
|
||||
|
||||
if (this->displayed_item_ != this->root_item_) {
|
||||
this->displayed_item_->on_leave();
|
||||
disp_changed = true;
|
||||
}
|
||||
|
||||
this->reset_();
|
||||
this->active_ = true;
|
||||
|
||||
if (disp_changed) {
|
||||
this->displayed_item_->on_enter();
|
||||
}
|
||||
|
||||
this->draw_and_update();
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::show() {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
this->process_initial_();
|
||||
|
||||
if (!this->active_) {
|
||||
this->active_ = true;
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::hide() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
if (this->editing_)
|
||||
this->finish_editing_();
|
||||
this->active_ = false;
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::reset_() {
|
||||
this->displayed_item_ = this->root_item_;
|
||||
this->cursor_index_ = this->top_index_ = 0;
|
||||
this->selection_stack_.clear();
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::process_initial_() {
|
||||
if (!this->root_on_enter_called_) {
|
||||
this->root_item_->on_enter();
|
||||
this->root_on_enter_called_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::check_healthy_and_active_() {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
this->process_initial_();
|
||||
|
||||
return this->active_;
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::cursor_up_() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->cursor_index_ > 0) {
|
||||
changed = true;
|
||||
|
||||
--this->cursor_index_;
|
||||
|
||||
if (this->cursor_index_ < this->top_index_)
|
||||
this->top_index_ = this->cursor_index_;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::cursor_down_() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->cursor_index_ + 1 < this->displayed_item_->items_size()) {
|
||||
changed = true;
|
||||
|
||||
++this->cursor_index_;
|
||||
|
||||
if (this->cursor_index_ >= this->top_index_ + this->rows_)
|
||||
this->top_index_ = this->cursor_index_ - this->rows_ + 1;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::enter_menu_() {
|
||||
this->displayed_item_->on_leave();
|
||||
this->displayed_item_ = static_cast<MenuItemMenu *>(this->get_selected_item_());
|
||||
this->selection_stack_.push_front({this->top_index_, this->cursor_index_});
|
||||
this->cursor_index_ = this->top_index_ = 0;
|
||||
this->displayed_item_->on_enter();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::leave_menu_() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->displayed_item_->get_parent() != nullptr) {
|
||||
this->displayed_item_->on_leave();
|
||||
this->displayed_item_ = this->displayed_item_->get_parent();
|
||||
this->top_index_ = this->selection_stack_.front().first;
|
||||
this->cursor_index_ = this->selection_stack_.front().second;
|
||||
this->selection_stack_.pop_front();
|
||||
this->displayed_item_->on_enter();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::finish_editing_() {
|
||||
switch (this->get_selected_item_()->get_type()) {
|
||||
case MENU_ITEM_SELECT:
|
||||
case MENU_ITEM_NUMBER:
|
||||
case MENU_ITEM_SWITCH:
|
||||
case MENU_ITEM_CUSTOM:
|
||||
this->get_selected_item_()->on_leave();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this->editing_ = false;
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::draw_menu() {
|
||||
for (size_t i = 0; i < this->rows_ && this->top_index_ + i < this->displayed_item_->items_size(); ++i) {
|
||||
this->draw_item(this->displayed_item_->get_item(this->top_index_ + i), i,
|
||||
this->top_index_ + i == this->cursor_index_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
77
esphome/components/display_menu_base/display_menu_base.h
Normal file
77
esphome/components/display_menu_base/display_menu_base.h
Normal file
@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "menu_item.h"
|
||||
|
||||
#include <forward_list>
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
enum MenuMode {
|
||||
MENU_MODE_ROTARY,
|
||||
MENU_MODE_JOYSTICK,
|
||||
};
|
||||
|
||||
class MenuItem;
|
||||
|
||||
/** Class to display a hierarchical menu.
|
||||
*
|
||||
*/
|
||||
class DisplayMenuComponent : public Component {
|
||||
public:
|
||||
void set_root_item(MenuItemMenu *item) { this->displayed_item_ = this->root_item_ = item; }
|
||||
void set_active(bool active) { this->active_ = active; }
|
||||
void set_mode(MenuMode mode) { this->mode_ = mode; }
|
||||
void set_rows(uint8_t rows) { this->rows_ = rows; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
|
||||
void up();
|
||||
void down();
|
||||
void left();
|
||||
void right();
|
||||
void enter();
|
||||
|
||||
void show_main();
|
||||
void show();
|
||||
void hide();
|
||||
|
||||
void draw();
|
||||
|
||||
bool is_active() const { return this->active_; }
|
||||
|
||||
protected:
|
||||
void reset_();
|
||||
void process_initial_();
|
||||
bool check_healthy_and_active_();
|
||||
MenuItem *get_selected_item_() { return this->displayed_item_->get_item(this->cursor_index_); }
|
||||
bool cursor_up_();
|
||||
bool cursor_down_();
|
||||
bool enter_menu_();
|
||||
bool leave_menu_();
|
||||
void finish_editing_();
|
||||
virtual void draw_menu();
|
||||
virtual void draw_item(const MenuItem *item, uint8_t row, bool selected) = 0;
|
||||
virtual void update() {}
|
||||
virtual void draw_and_update() {
|
||||
draw_menu();
|
||||
update();
|
||||
}
|
||||
|
||||
uint8_t rows_;
|
||||
bool active_;
|
||||
MenuMode mode_;
|
||||
MenuItemMenu *root_item_{nullptr};
|
||||
|
||||
MenuItemMenu *displayed_item_{nullptr};
|
||||
uint8_t top_index_{0};
|
||||
uint8_t cursor_index_{0};
|
||||
std::forward_list<std::pair<uint8_t, uint8_t>> selection_stack_{};
|
||||
bool editing_{false};
|
||||
bool root_on_enter_called_{false};
|
||||
};
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
179
esphome/components/display_menu_base/menu_item.cpp
Normal file
179
esphome/components/display_menu_base/menu_item.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
#include "menu_item.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
void MenuItem::on_enter() { this->on_enter_callbacks_.call(); }
|
||||
|
||||
void MenuItem::on_leave() { this->on_leave_callbacks_.call(); }
|
||||
|
||||
void MenuItem::on_value_() { this->on_value_callbacks_.call(); }
|
||||
|
||||
#ifdef USE_SELECT
|
||||
std::string MenuItemSelect::get_value_text() const {
|
||||
std::string result;
|
||||
|
||||
if (this->value_getter_.has_value()) {
|
||||
result = this->value_getter_.value()(this);
|
||||
} else {
|
||||
if (this->select_var_ != nullptr) {
|
||||
result = this->select_var_->state;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MenuItemSelect::select_next() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_next(true).perform();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool MenuItemSelect::select_prev() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_previous(true).perform();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
#endif // USE_SELECT
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
std::string MenuItemNumber::get_value_text() const {
|
||||
std::string result;
|
||||
|
||||
if (this->value_getter_.has_value()) {
|
||||
result = this->value_getter_.value()(this);
|
||||
} else {
|
||||
char data[32];
|
||||
snprintf(data, sizeof(data), this->format_.c_str(), get_number_value_());
|
||||
result = data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MenuItemNumber::select_next() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->number_var_ != nullptr) {
|
||||
float last = this->number_var_->state;
|
||||
this->number_var_->make_call().number_increment(false).perform();
|
||||
|
||||
if (this->number_var_->state != last) {
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool MenuItemNumber::select_prev() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->number_var_ != nullptr) {
|
||||
float last = this->number_var_->state;
|
||||
this->number_var_->make_call().number_decrement(false).perform();
|
||||
|
||||
if (this->number_var_->state != last) {
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
float MenuItemNumber::get_number_value_() const {
|
||||
float result = 0.0;
|
||||
|
||||
if (this->number_var_ != nullptr) {
|
||||
if (!this->number_var_->has_state() || this->number_var_->state < this->number_var_->traits.get_min_value()) {
|
||||
result = this->number_var_->traits.get_min_value();
|
||||
} else if (this->number_var_->state > this->number_var_->traits.get_max_value()) {
|
||||
result = this->number_var_->traits.get_max_value();
|
||||
} else {
|
||||
result = this->number_var_->state;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif // USE_NUMBER
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
std::string MenuItemSwitch::get_value_text() const {
|
||||
std::string result;
|
||||
|
||||
if (this->value_getter_.has_value()) {
|
||||
result = this->value_getter_.value()(this);
|
||||
} else {
|
||||
result = this->get_switch_state_() ? this->switch_on_text_ : this->switch_off_text_;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MenuItemSwitch::select_next() { return this->toggle_switch_(); }
|
||||
|
||||
bool MenuItemSwitch::select_prev() { return this->toggle_switch_(); }
|
||||
|
||||
bool MenuItemSwitch::get_switch_state_() const { return (this->switch_var_ != nullptr && this->switch_var_->state); }
|
||||
|
||||
bool MenuItemSwitch::toggle_switch_() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->switch_var_ != nullptr) {
|
||||
this->switch_var_->toggle();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
#endif // USE_SWITCH
|
||||
|
||||
std::string MenuItemCustom::get_value_text() const {
|
||||
return (this->value_getter_.has_value()) ? this->value_getter_.value()(this) : "";
|
||||
}
|
||||
|
||||
bool MenuItemCommand::select_next() {
|
||||
this->on_value_();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MenuItemCommand::select_prev() {
|
||||
this->on_value_();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MenuItemCustom::select_next() {
|
||||
this->on_next_();
|
||||
this->on_value_();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MenuItemCustom::select_prev() {
|
||||
this->on_prev_();
|
||||
this->on_value_();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MenuItemCustom::on_next_() { this->on_next_callbacks_.call(); }
|
||||
|
||||
void MenuItemCustom::on_prev_() { this->on_prev_callbacks_.call(); }
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
187
esphome/components/display_menu_base/menu_item.h
Normal file
187
esphome/components/display_menu_base/menu_item.h
Normal file
@ -0,0 +1,187 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
enum MenuItemType {
|
||||
MENU_ITEM_LABEL,
|
||||
MENU_ITEM_MENU,
|
||||
MENU_ITEM_BACK,
|
||||
MENU_ITEM_SELECT,
|
||||
MENU_ITEM_NUMBER,
|
||||
MENU_ITEM_SWITCH,
|
||||
MENU_ITEM_COMMAND,
|
||||
MENU_ITEM_CUSTOM,
|
||||
};
|
||||
|
||||
class MenuItem;
|
||||
class MenuItemMenu;
|
||||
using value_getter_t = std::function<std::string(const MenuItem *)>;
|
||||
|
||||
class MenuItem {
|
||||
public:
|
||||
explicit MenuItem(MenuItemType t) : item_type_(t) {}
|
||||
void set_parent(MenuItemMenu *parent) { this->parent_ = parent; }
|
||||
MenuItemMenu *get_parent() { return this->parent_; }
|
||||
MenuItemType get_type() const { return this->item_type_; }
|
||||
template<typename V> void set_text(V val) { this->text_ = val; }
|
||||
void add_on_enter_callback(std::function<void()> &&cb) { this->on_enter_callbacks_.add(std::move(cb)); }
|
||||
void add_on_leave_callback(std::function<void()> &&cb) { this->on_leave_callbacks_.add(std::move(cb)); }
|
||||
void add_on_value_callback(std::function<void()> &&cb) { this->on_value_callbacks_.add(std::move(cb)); }
|
||||
|
||||
std::string get_text() const { return const_cast<MenuItem *>(this)->text_.value(this); }
|
||||
virtual bool get_immediate_edit() const { return false; }
|
||||
virtual bool has_value() const { return false; }
|
||||
virtual std::string get_value_text() const { return ""; }
|
||||
|
||||
virtual bool select_next() { return false; }
|
||||
virtual bool select_prev() { return false; }
|
||||
|
||||
void on_enter();
|
||||
void on_leave();
|
||||
|
||||
protected:
|
||||
void on_value_();
|
||||
|
||||
MenuItemType item_type_;
|
||||
MenuItemMenu *parent_{nullptr};
|
||||
TemplatableValue<std::string, const MenuItem *> text_;
|
||||
|
||||
CallbackManager<void()> on_enter_callbacks_{};
|
||||
CallbackManager<void()> on_leave_callbacks_{};
|
||||
CallbackManager<void()> on_value_callbacks_{};
|
||||
};
|
||||
|
||||
class MenuItemMenu : public MenuItem {
|
||||
public:
|
||||
explicit MenuItemMenu() : MenuItem(MENU_ITEM_MENU) {}
|
||||
void add_item(MenuItem *item) {
|
||||
item->set_parent(this);
|
||||
this->items_.push_back(item);
|
||||
}
|
||||
size_t items_size() const { return this->items_.size(); }
|
||||
MenuItem *get_item(size_t i) { return this->items_[i]; }
|
||||
|
||||
protected:
|
||||
std::vector<MenuItem *> items_;
|
||||
};
|
||||
|
||||
class MenuItemEditable : public MenuItem {
|
||||
public:
|
||||
explicit MenuItemEditable(MenuItemType t) : MenuItem(t) {}
|
||||
void set_immediate_edit(bool val) { this->immediate_edit_ = val; }
|
||||
bool get_immediate_edit() const override { return this->immediate_edit_; }
|
||||
void set_value_lambda(value_getter_t &&getter) { this->value_getter_ = getter; }
|
||||
|
||||
protected:
|
||||
bool immediate_edit_{false};
|
||||
optional<value_getter_t> value_getter_{};
|
||||
};
|
||||
|
||||
#ifdef USE_SELECT
|
||||
class MenuItemSelect : public MenuItemEditable {
|
||||
public:
|
||||
explicit MenuItemSelect() : MenuItemEditable(MENU_ITEM_SELECT) {}
|
||||
void set_select_variable(select::Select *var) { this->select_var_ = var; }
|
||||
|
||||
bool has_value() const override { return true; }
|
||||
std::string get_value_text() const override;
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
|
||||
protected:
|
||||
select::Select *select_var_{nullptr};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
class MenuItemNumber : public MenuItemEditable {
|
||||
public:
|
||||
explicit MenuItemNumber() : MenuItemEditable(MENU_ITEM_NUMBER) {}
|
||||
void set_number_variable(number::Number *var) { this->number_var_ = var; }
|
||||
void set_format(const std::string &fmt) { this->format_ = fmt; }
|
||||
|
||||
bool has_value() const override { return true; }
|
||||
std::string get_value_text() const override;
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
|
||||
protected:
|
||||
float get_number_value_() const;
|
||||
|
||||
number::Number *number_var_{nullptr};
|
||||
std::string format_;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
class MenuItemSwitch : public MenuItemEditable {
|
||||
public:
|
||||
explicit MenuItemSwitch() : MenuItemEditable(MENU_ITEM_SWITCH) {}
|
||||
void set_switch_variable(switch_::Switch *var) { this->switch_var_ = var; }
|
||||
void set_on_text(const std::string &t) { this->switch_on_text_ = t; }
|
||||
void set_off_text(const std::string &t) { this->switch_off_text_ = t; }
|
||||
|
||||
bool has_value() const override { return true; }
|
||||
std::string get_value_text() const override;
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
|
||||
protected:
|
||||
bool get_switch_state_() const;
|
||||
bool toggle_switch_();
|
||||
|
||||
switch_::Switch *switch_var_{nullptr};
|
||||
std::string switch_on_text_;
|
||||
std::string switch_off_text_;
|
||||
};
|
||||
#endif
|
||||
|
||||
class MenuItemCommand : public MenuItem {
|
||||
public:
|
||||
explicit MenuItemCommand() : MenuItem(MENU_ITEM_COMMAND) {}
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
};
|
||||
|
||||
class MenuItemCustom : public MenuItemEditable {
|
||||
public:
|
||||
explicit MenuItemCustom() : MenuItemEditable(MENU_ITEM_CUSTOM) {}
|
||||
void add_on_next_callback(std::function<void()> &&cb) { this->on_next_callbacks_.add(std::move(cb)); }
|
||||
void add_on_prev_callback(std::function<void()> &&cb) { this->on_prev_callbacks_.add(std::move(cb)); }
|
||||
|
||||
bool has_value() const override { return this->value_getter_.has_value(); }
|
||||
std::string get_value_text() const override;
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
|
||||
protected:
|
||||
void on_next_();
|
||||
void on_prev_();
|
||||
|
||||
CallbackManager<void()> on_next_callbacks_{};
|
||||
CallbackManager<void()> on_prev_callbacks_{};
|
||||
};
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
0
esphome/components/dps310/__init__.py
Normal file
0
esphome/components/dps310/__init__.py
Normal file
189
esphome/components/dps310/dps310.cpp
Normal file
189
esphome/components/dps310/dps310.cpp
Normal file
@ -0,0 +1,189 @@
|
||||
#include "dps310.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dps310 {
|
||||
|
||||
static const char *const TAG = "dps310";
|
||||
|
||||
void DPS310Component::setup() {
|
||||
uint8_t coef_data_raw[DPS310_NUM_COEF_REGS];
|
||||
auto timer = DPS310_INIT_TIMEOUT;
|
||||
uint8_t reg = 0;
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Setting up DPS310...");
|
||||
// first, reset the sensor
|
||||
if (!this->write_byte(DPS310_REG_RESET, DPS310_CMD_RESET)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(10);
|
||||
// wait for the sensor and its coefficients to be ready
|
||||
while (timer-- && (!(reg & DPS310_BIT_SENSOR_RDY) || !(reg & DPS310_BIT_COEF_RDY))) {
|
||||
reg = this->read_byte(DPS310_REG_MEAS_CFG).value_or(0);
|
||||
delay(5);
|
||||
}
|
||||
|
||||
if (!(reg & DPS310_BIT_SENSOR_RDY) || !(reg & DPS310_BIT_COEF_RDY)) { // the flags were not set in time
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// read device ID
|
||||
if (!this->read_byte(DPS310_REG_PROD_REV_ID, &this->prod_rev_id_)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// read in coefficients used to calculate the compensated pressure and temperature values
|
||||
if (!this->read_bytes(DPS310_REG_COEF, coef_data_raw, DPS310_NUM_COEF_REGS)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// read in coefficients source register, too -- we need this a few lines down
|
||||
if (!this->read_byte(DPS310_REG_TMP_COEF_SRC, ®)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// set up operational stuff
|
||||
if (!this->write_byte(DPS310_REG_PRS_CFG, DPS310_VAL_PRS_CFG)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->write_byte(DPS310_REG_TMP_CFG, DPS310_VAL_TMP_CFG | (reg & DPS310_BIT_TMP_COEF_SRC))) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->write_byte(DPS310_REG_CFG, DPS310_VAL_REG_CFG)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->write_byte(DPS310_REG_MEAS_CFG, 0x07)) { // enable background mode
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->c0_ = // we only ever use c0/2, so just divide by 2 here to save time later
|
||||
DPS310Component::twos_complement(
|
||||
int16_t(((uint16_t) coef_data_raw[0] << 4) | (((uint16_t) coef_data_raw[1] >> 4) & 0x0F)), 12) /
|
||||
2;
|
||||
|
||||
this->c1_ =
|
||||
DPS310Component::twos_complement(int16_t((((uint16_t) coef_data_raw[1] & 0x0F) << 8) | coef_data_raw[2]), 12);
|
||||
|
||||
this->c00_ = ((uint32_t) coef_data_raw[3] << 12) | ((uint32_t) coef_data_raw[4] << 4) |
|
||||
(((uint32_t) coef_data_raw[5] >> 4) & 0x0F);
|
||||
this->c00_ = DPS310Component::twos_complement(c00_, 20);
|
||||
|
||||
this->c10_ =
|
||||
(((uint32_t) coef_data_raw[5] & 0x0F) << 16) | ((uint32_t) coef_data_raw[6] << 8) | (uint32_t) coef_data_raw[7];
|
||||
this->c10_ = DPS310Component::twos_complement(c10_, 20);
|
||||
|
||||
this->c01_ = int16_t(((uint16_t) coef_data_raw[8] << 8) | (uint16_t) coef_data_raw[9]);
|
||||
this->c11_ = int16_t(((uint16_t) coef_data_raw[10] << 8) | (uint16_t) coef_data_raw[11]);
|
||||
this->c20_ = int16_t(((uint16_t) coef_data_raw[12] << 8) | (uint16_t) coef_data_raw[13]);
|
||||
this->c21_ = int16_t(((uint16_t) coef_data_raw[14] << 8) | (uint16_t) coef_data_raw[15]);
|
||||
this->c30_ = int16_t(((uint16_t) coef_data_raw[16] << 8) | (uint16_t) coef_data_raw[17]);
|
||||
}
|
||||
|
||||
void DPS310Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "DPS310:");
|
||||
ESP_LOGCONFIG(TAG, " Product ID: %u", this->prod_rev_id_ & 0x0F);
|
||||
ESP_LOGCONFIG(TAG, " Revision ID: %u", (this->prod_rev_id_ >> 4) & 0x0F);
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with DPS310 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
}
|
||||
|
||||
float DPS310Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void DPS310Component::update() {
|
||||
if (!this->update_in_progress_) {
|
||||
this->update_in_progress_ = true;
|
||||
this->read_();
|
||||
}
|
||||
}
|
||||
|
||||
void DPS310Component::read_() {
|
||||
uint8_t reg = 0;
|
||||
if (!this->read_byte(DPS310_REG_MEAS_CFG, ®)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!this->got_pres_) && (reg & DPS310_BIT_PRS_RDY)) {
|
||||
this->read_pressure_();
|
||||
}
|
||||
|
||||
if ((!this->got_temp_) && (reg & DPS310_BIT_TMP_RDY)) {
|
||||
this->read_temperature_();
|
||||
}
|
||||
|
||||
if (this->got_pres_ && this->got_temp_) {
|
||||
this->calculate_values_(this->raw_temperature_, this->raw_pressure_);
|
||||
this->got_pres_ = false;
|
||||
this->got_temp_ = false;
|
||||
this->update_in_progress_ = false;
|
||||
this->status_clear_warning();
|
||||
} else {
|
||||
auto f = std::bind(&DPS310Component::read_, this);
|
||||
this->set_timeout("dps310", 10, f);
|
||||
}
|
||||
}
|
||||
|
||||
void DPS310Component::read_pressure_() {
|
||||
uint8_t bytes[3];
|
||||
if (!this->read_bytes(DPS310_REG_PRS_B2, bytes, 3)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->got_pres_ = true;
|
||||
this->raw_pressure_ = DPS310Component::twos_complement(
|
||||
int32_t((uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]))), 24);
|
||||
}
|
||||
|
||||
void DPS310Component::read_temperature_() {
|
||||
uint8_t bytes[3];
|
||||
if (!this->read_bytes(DPS310_REG_TMP_B2, bytes, 3)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->got_temp_ = true;
|
||||
this->raw_temperature_ = DPS310Component::twos_complement(
|
||||
int32_t((uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]))), 24);
|
||||
}
|
||||
|
||||
// Calculations are taken from the datasheet which can be found here:
|
||||
// https://www.infineon.com/dgdl/Infineon-DPS310-DataSheet-v01_02-EN.pdf?fileId=5546d462576f34750157750826c42242
|
||||
// Sections "How to Calculate Compensated Pressure Values" and "How to Calculate Compensated Temperature Values"
|
||||
// Variable names below match variable names from the datasheet but lowercased
|
||||
void DPS310Component::calculate_values_(int32_t raw_temperature, int32_t raw_pressure) {
|
||||
const float t_raw_sc = (float) raw_temperature / DPS310_SCALE_FACTOR;
|
||||
const float p_raw_sc = (float) raw_pressure / DPS310_SCALE_FACTOR;
|
||||
|
||||
const float temperature = t_raw_sc * this->c1_ + this->c0_; // c0/2 done earlier!
|
||||
|
||||
const float pressure = (this->c00_ + p_raw_sc * (this->c10_ + p_raw_sc * (this->c20_ + p_raw_sc * this->c30_)) +
|
||||
t_raw_sc * this->c01_ + t_raw_sc * p_raw_sc * (this->c11_ + p_raw_sc * this->c21_)) /
|
||||
100; // divide by 100 for hPa
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
if (this->pressure_sensor_ != nullptr) {
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t DPS310Component::twos_complement(int32_t val, uint8_t bits) {
|
||||
if (val & ((uint32_t) 1 << (bits - 1))) {
|
||||
val -= (uint32_t) 1 << bits;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
} // namespace dps310
|
||||
} // namespace esphome
|
65
esphome/components/dps310/dps310.h
Normal file
65
esphome/components/dps310/dps310.h
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dps310 {
|
||||
|
||||
static const uint8_t DPS310_REG_PRS_B2 = 0x00; // Highest byte of pressure data
|
||||
static const uint8_t DPS310_REG_TMP_B2 = 0x03; // Highest byte of temperature data
|
||||
static const uint8_t DPS310_REG_PRS_CFG = 0x06; // Pressure configuration
|
||||
static const uint8_t DPS310_REG_TMP_CFG = 0x07; // Temperature configuration
|
||||
static const uint8_t DPS310_REG_MEAS_CFG = 0x08; // Sensor configuration
|
||||
static const uint8_t DPS310_REG_CFG = 0x09; // Interrupt/FIFO configuration
|
||||
static const uint8_t DPS310_REG_RESET = 0x0C; // Soft reset
|
||||
static const uint8_t DPS310_REG_PROD_REV_ID = 0x0D; // Register that contains the part ID
|
||||
static const uint8_t DPS310_REG_COEF = 0x10; // Top of calibration coefficient data space
|
||||
static const uint8_t DPS310_REG_TMP_COEF_SRC = 0x28; // Temperature calibration src
|
||||
|
||||
static const uint8_t DPS310_BIT_PRS_RDY = 0x10; // Pressure measurement is ready
|
||||
static const uint8_t DPS310_BIT_TMP_RDY = 0x20; // Temperature measurement is ready
|
||||
static const uint8_t DPS310_BIT_SENSOR_RDY = 0x40; // Sensor initialization complete when bit is set
|
||||
static const uint8_t DPS310_BIT_COEF_RDY = 0x80; // Coefficients are available when bit is set
|
||||
static const uint8_t DPS310_BIT_TMP_COEF_SRC = 0x80; // Temperature measurement source (0 = ASIC, 1 = MEMS element)
|
||||
static const uint8_t DPS310_BIT_REQ_PRES = 0x01; // Set this bit to request pressure reading
|
||||
static const uint8_t DPS310_BIT_REQ_TEMP = 0x02; // Set this bit to request temperature reading
|
||||
|
||||
static const uint8_t DPS310_CMD_RESET = 0x89; // What to write to reset the device
|
||||
|
||||
static const uint8_t DPS310_VAL_PRS_CFG = 0x01; // Value written to DPS310_REG_PRS_CFG at startup
|
||||
static const uint8_t DPS310_VAL_TMP_CFG = 0x01; // Value written to DPS310_REG_TMP_CFG at startup
|
||||
static const uint8_t DPS310_VAL_REG_CFG = 0x00; // Value written to DPS310_REG_CFG at startup
|
||||
|
||||
static const uint8_t DPS310_INIT_TIMEOUT = 20; // How long to wait for DPS310 start-up to complete
|
||||
static const uint8_t DPS310_NUM_COEF_REGS = 18; // Number of coefficients we need to read from the device
|
||||
static const int32_t DPS310_SCALE_FACTOR = 1572864; // Measurement compensation scale factor
|
||||
|
||||
class DPS310Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
|
||||
protected:
|
||||
void read_();
|
||||
void read_pressure_();
|
||||
void read_temperature_();
|
||||
void calculate_values_(int32_t raw_temperature, int32_t raw_pressure);
|
||||
static int32_t twos_complement(int32_t val, uint8_t bits);
|
||||
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
int32_t raw_pressure_, raw_temperature_, c00_, c10_;
|
||||
int16_t c0_, c1_, c01_, c11_, c20_, c21_, c30_;
|
||||
uint8_t prod_rev_id_;
|
||||
bool got_pres_, got_temp_, update_in_progress_;
|
||||
};
|
||||
|
||||
} // namespace dps310
|
||||
} // namespace esphome
|
62
esphome/components/dps310/sensor.py
Normal file
62
esphome/components/dps310/sensor.py
Normal file
@ -0,0 +1,62 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
ICON_GAUGE,
|
||||
ICON_THERMOMETER,
|
||||
UNIT_HECTOPASCAL,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
dps310_ns = cg.esphome_ns.namespace("dps310")
|
||||
DPS310Component = dps310_ns.class_(
|
||||
"DPS310Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DPS310Component),
|
||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
icon=ICON_GAUGE,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x77))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
||||
cg.add(var.set_pressure_sensor(sens))
|
@ -85,4 +85,4 @@ async def to_code(config):
|
||||
cg.add_library("glmnet/Dsmr", "0.5")
|
||||
|
||||
# Crypto
|
||||
cg.add_library("rweather/Crypto", "0.2.0")
|
||||
cg.add_library("rweather/Crypto", "0.4.0")
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include <dsmr/parser.h>
|
||||
#include <dsmr/fields.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace dsmr {
|
||||
|
||||
|
@ -4,9 +4,10 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
class UDP;
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace ektf2232 {
|
||||
|
||||
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@ -17,10 +17,8 @@ EKTF2232Touchscreen = ektf2232_ns.class_(
|
||||
)
|
||||
|
||||
CONF_EKTF2232_ID = "ektf2232_id"
|
||||
CONF_INTERRUPT_PIN = "interrupt_pin"
|
||||
CONF_RTS_PIN = "rts_pin"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
|
@ -31,8 +31,8 @@ class ENS210Component : public PollingComponent, public i2c::I2CDevice {
|
||||
bool set_low_power_(bool enable);
|
||||
void extract_measurement_(uint32_t val, int *data, int *status);
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ens210
|
||||
|
@ -33,7 +33,7 @@ from .const import ( # noqa
|
||||
VARIANT_FRIENDLY,
|
||||
VARIANTS,
|
||||
)
|
||||
from .boards import BOARD_TO_VARIANT
|
||||
from .boards import BOARDS
|
||||
|
||||
# force import gpio to register pin schema
|
||||
from .gpio import esp32_pin_to_code # noqa
|
||||
@ -129,27 +129,27 @@ def _format_framework_espidf_version(ver: cv.Version) -> str:
|
||||
# The default/recommended arduino framework version
|
||||
# - https://github.com/espressif/arduino-esp32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(1, 0, 6)
|
||||
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(3, 5, 0)
|
||||
ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0)
|
||||
|
||||
# The default/recommended esp-idf framework version
|
||||
# - https://github.com/espressif/esp-idf/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 2)
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 2)
|
||||
# The platformio/espressif32 version to use for esp-idf frameworks
|
||||
# - https://github.com/platformio/platform-espressif32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||
ESP_IDF_PLATFORM_VERSION = cv.Version(3, 5, 0)
|
||||
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 2, 0)
|
||||
|
||||
|
||||
def _arduino_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(2, 0, 0), "https://github.com/espressif/arduino-esp32.git"),
|
||||
"latest": (cv.Version(1, 0, 6), None),
|
||||
"dev": (cv.Version(2, 0, 5), "https://github.com/espressif/arduino-esp32.git"),
|
||||
"latest": (cv.Version(2, 0, 5), None),
|
||||
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
@ -184,7 +184,7 @@ 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, 3, 2), None),
|
||||
"latest": (cv.Version(4, 4, 2), None),
|
||||
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
@ -230,14 +230,14 @@ def _parse_platform_version(value):
|
||||
def _detect_variant(value):
|
||||
if CONF_VARIANT not in value:
|
||||
board = value[CONF_BOARD]
|
||||
if board not in BOARD_TO_VARIANT:
|
||||
if board not in BOARDS:
|
||||
raise cv.Invalid(
|
||||
"This board is unknown, please set the variant manually",
|
||||
path=[CONF_BOARD],
|
||||
)
|
||||
|
||||
value = value.copy()
|
||||
value[CONF_VARIANT] = BOARD_TO_VARIANT[board]
|
||||
value[CONF_VARIANT] = BOARDS[board][KEY_VARIANT]
|
||||
|
||||
return value
|
||||
|
||||
@ -327,6 +327,11 @@ async def to_code(config):
|
||||
"platform_packages",
|
||||
[f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"],
|
||||
)
|
||||
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
|
||||
# This is espressif's own published version which is more up to date.
|
||||
cg.add_platformio_option(
|
||||
"platform_packages", ["espressif/toolchain-esp32ulp @ 2.35.0-20220830"]
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
|
||||
add_idf_sdkconfig_option(
|
||||
|
@ -1,4 +1,4 @@
|
||||
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3
|
||||
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3, VARIANT_ESP32S3
|
||||
|
||||
ESP32_BASE_PINS = {
|
||||
"TX": 1,
|
||||
@ -42,6 +42,64 @@ ESP32_BASE_PINS = {
|
||||
}
|
||||
|
||||
ESP32_BOARD_PINS = {
|
||||
"adafruit_qtpy_esp32c3": {
|
||||
"A0": 4,
|
||||
"A1": 3,
|
||||
"A2": 1,
|
||||
"A3": 0,
|
||||
"SDA": 5,
|
||||
"SCL": 6,
|
||||
"MOSI": 7,
|
||||
"MISO": 8,
|
||||
"SCK": 10,
|
||||
"RX": 20,
|
||||
"TX": 21,
|
||||
"NEOPIXEL": 2,
|
||||
"PIN_NEOPIXEL": 2,
|
||||
"BUTTON": 9,
|
||||
"SWITCH": 9,
|
||||
},
|
||||
"adafruit_qtpy_esp32s2": {
|
||||
"A0": 18,
|
||||
"A1": 17,
|
||||
"A2": 9,
|
||||
"A3": 8,
|
||||
"SDA": 7,
|
||||
"SCL": 6,
|
||||
"MOSI": 35,
|
||||
"MISO": 37,
|
||||
"SCK": 36,
|
||||
"RX": 16,
|
||||
"TX": 5,
|
||||
"SDA1": 41,
|
||||
"SCL1": 40,
|
||||
"NEOPIXEL": 39,
|
||||
"PIN_NEOPIXEL": 39,
|
||||
"NEOPIXEL_POWER": 38,
|
||||
"D0": 0,
|
||||
"BUTTON": 0,
|
||||
"SWITCH": 0,
|
||||
},
|
||||
"adafruit_qtpy_esp32": {
|
||||
"A0": 26,
|
||||
"A1": 25,
|
||||
"A2": 27,
|
||||
"A3": 15,
|
||||
"SDA": 4,
|
||||
"SCL": 33,
|
||||
"MOSI": 13,
|
||||
"MISO": 12,
|
||||
"SCK": 14,
|
||||
"RX": 7,
|
||||
"TX": 32,
|
||||
"SDA1": 22,
|
||||
"SCL1": 19,
|
||||
"NEOPIXEL": 5,
|
||||
"PIN_NEOPIXEL": 5,
|
||||
"NEOPIXEL_POWER": 8,
|
||||
"BUTTON": 0,
|
||||
"SWITCH": 0,
|
||||
},
|
||||
"alksesp32": {
|
||||
"A0": 32,
|
||||
"A1": 33,
|
||||
@ -550,6 +608,25 @@ ESP32_BOARD_PINS = {
|
||||
},
|
||||
"lolin_d32": {"LED": 5, "_VBAT": 35},
|
||||
"lolin_d32_pro": {"LED": 5, "_VBAT": 35},
|
||||
"lolin_s2_mini": {
|
||||
"TX": 43,
|
||||
"RX": 44,
|
||||
"SPICS1": 29,
|
||||
"SPIHD": 31,
|
||||
"SPIWP": 32,
|
||||
"SPICS0": 33,
|
||||
"SPICLK": 34,
|
||||
"SPIQ": 35,
|
||||
"SPID": 36,
|
||||
"MISO": 9,
|
||||
"MOSI": 11,
|
||||
"SCK": 7,
|
||||
"SCL": 35,
|
||||
"SDA": 33,
|
||||
"DAC1": 17,
|
||||
"DAC2": 18,
|
||||
"LED": 15,
|
||||
},
|
||||
"lopy": {
|
||||
"A1": 37,
|
||||
"A2": 38,
|
||||
@ -984,127 +1061,743 @@ ESP32_BOARD_PINS = {
|
||||
"D13": 2,
|
||||
},
|
||||
"xinabox_cw02": {"LED": 27},
|
||||
"upesy_wroom": {"LED": 2},
|
||||
"upesy_wrover": {"LED": 2},
|
||||
}
|
||||
|
||||
"""
|
||||
BOARD_TO_VARIANT generated with:
|
||||
BOARDS generated with:
|
||||
|
||||
git clone https://github.com/platformio/platform-espressif32
|
||||
for x in platform-espressif32/boards/*.json; do
|
||||
mcu=$(jq -r .build.mcu <"$x");
|
||||
name=$(jq -r .name <"$x");
|
||||
fname=$(basename "$x")
|
||||
board="${fname%.*}"
|
||||
variant=$(echo "$mcu" | tr '[:lower:]' '[:upper:]')
|
||||
echo " \"$board\": VARIANT_${variant},"
|
||||
echo " \"$board\": {\"name\": \"$name\", \"variant\": VARIANT_${variant},},"
|
||||
done | sort
|
||||
"""
|
||||
|
||||
BOARD_TO_VARIANT = {
|
||||
"alksesp32": VARIANT_ESP32,
|
||||
"az-delivery-devkit-v4": VARIANT_ESP32,
|
||||
"bpi-bit": VARIANT_ESP32,
|
||||
"briki_abc_esp32": VARIANT_ESP32,
|
||||
"briki_mbc-wb_esp32": VARIANT_ESP32,
|
||||
"d-duino-32": VARIANT_ESP32,
|
||||
"esp320": VARIANT_ESP32,
|
||||
"esp32-c3-devkitm-1": VARIANT_ESP32C3,
|
||||
"esp32cam": VARIANT_ESP32,
|
||||
"esp32-devkitlipo": VARIANT_ESP32,
|
||||
"esp32dev": VARIANT_ESP32,
|
||||
"esp32doit-devkit-v1": VARIANT_ESP32,
|
||||
"esp32doit-espduino": VARIANT_ESP32,
|
||||
"esp32-evb": VARIANT_ESP32,
|
||||
"esp32-gateway": VARIANT_ESP32,
|
||||
"esp32-poe-iso": VARIANT_ESP32,
|
||||
"esp32-poe": VARIANT_ESP32,
|
||||
"esp32-pro": VARIANT_ESP32,
|
||||
"esp32-s2-kaluga-1": VARIANT_ESP32S2,
|
||||
"esp32-s2-saola-1": VARIANT_ESP32S2,
|
||||
"esp32thing_plus": VARIANT_ESP32,
|
||||
"esp32thing": VARIANT_ESP32,
|
||||
"esp32vn-iot-uno": VARIANT_ESP32,
|
||||
"espea32": VARIANT_ESP32,
|
||||
"espectro32": VARIANT_ESP32,
|
||||
"espino32": VARIANT_ESP32,
|
||||
"esp-wrover-kit": VARIANT_ESP32,
|
||||
"etboard": VARIANT_ESP32,
|
||||
"featheresp32-s2": VARIANT_ESP32S2,
|
||||
"featheresp32": VARIANT_ESP32,
|
||||
"firebeetle32": VARIANT_ESP32,
|
||||
"fm-devkit": VARIANT_ESP32,
|
||||
"frogboard": VARIANT_ESP32,
|
||||
"healthypi4": VARIANT_ESP32,
|
||||
"heltec_wifi_kit_32_v2": VARIANT_ESP32,
|
||||
"heltec_wifi_kit_32": VARIANT_ESP32,
|
||||
"heltec_wifi_lora_32_V2": VARIANT_ESP32,
|
||||
"heltec_wifi_lora_32": VARIANT_ESP32,
|
||||
"heltec_wireless_stick_lite": VARIANT_ESP32,
|
||||
"heltec_wireless_stick": VARIANT_ESP32,
|
||||
"honeylemon": VARIANT_ESP32,
|
||||
"hornbill32dev": VARIANT_ESP32,
|
||||
"hornbill32minima": VARIANT_ESP32,
|
||||
"imbrios-logsens-v1p1": VARIANT_ESP32,
|
||||
"inex_openkb": VARIANT_ESP32,
|
||||
"intorobot": VARIANT_ESP32,
|
||||
"iotaap_magnolia": VARIANT_ESP32,
|
||||
"iotbusio": VARIANT_ESP32,
|
||||
"iotbusproteus": VARIANT_ESP32,
|
||||
"kits-edu": VARIANT_ESP32,
|
||||
"labplus_mpython": VARIANT_ESP32,
|
||||
"lolin32_lite": VARIANT_ESP32,
|
||||
"lolin32": VARIANT_ESP32,
|
||||
"lolin_c3_mini": VARIANT_ESP32C3,
|
||||
"lolin_d32_pro": VARIANT_ESP32,
|
||||
"lolin_d32": VARIANT_ESP32,
|
||||
"lopy4": VARIANT_ESP32,
|
||||
"lopy": VARIANT_ESP32,
|
||||
"m5stack-atom": VARIANT_ESP32,
|
||||
"m5stack-core2": VARIANT_ESP32,
|
||||
"m5stack-core-esp32": VARIANT_ESP32,
|
||||
"m5stack-coreink": VARIANT_ESP32,
|
||||
"m5stack-fire": VARIANT_ESP32,
|
||||
"m5stack-grey": VARIANT_ESP32,
|
||||
"m5stack-timer-cam": VARIANT_ESP32,
|
||||
"m5stick-c": VARIANT_ESP32,
|
||||
"magicbit": VARIANT_ESP32,
|
||||
"mgbot-iotik32a": VARIANT_ESP32,
|
||||
"mgbot-iotik32b": VARIANT_ESP32,
|
||||
"mhetesp32devkit": VARIANT_ESP32,
|
||||
"mhetesp32minikit": VARIANT_ESP32,
|
||||
"microduino-core-esp32": VARIANT_ESP32,
|
||||
"nano32": VARIANT_ESP32,
|
||||
"nina_w10": VARIANT_ESP32,
|
||||
"node32s": VARIANT_ESP32,
|
||||
"nodemcu-32s": VARIANT_ESP32,
|
||||
"nscreen-32": VARIANT_ESP32,
|
||||
"odroid_esp32": VARIANT_ESP32,
|
||||
"onehorse32dev": VARIANT_ESP32,
|
||||
"oroca_edubot": VARIANT_ESP32,
|
||||
"pico32": VARIANT_ESP32,
|
||||
"piranha_esp32": VARIANT_ESP32,
|
||||
"pocket_32": VARIANT_ESP32,
|
||||
"pycom_gpy": VARIANT_ESP32,
|
||||
"qchip": VARIANT_ESP32,
|
||||
"quantum": VARIANT_ESP32,
|
||||
"sensesiot_weizen": VARIANT_ESP32,
|
||||
"sg-o_airMon": VARIANT_ESP32,
|
||||
"s_odi_ultra": VARIANT_ESP32,
|
||||
"sparkfun_lora_gateway_1-channel": VARIANT_ESP32,
|
||||
"tinypico": VARIANT_ESP32,
|
||||
"ttgo-lora32-v1": VARIANT_ESP32,
|
||||
"ttgo-lora32-v21": VARIANT_ESP32,
|
||||
"ttgo-lora32-v2": VARIANT_ESP32,
|
||||
"ttgo-t1": VARIANT_ESP32,
|
||||
"ttgo-t7-v13-mini32": VARIANT_ESP32,
|
||||
"ttgo-t7-v14-mini32": VARIANT_ESP32,
|
||||
"ttgo-t-beam": VARIANT_ESP32,
|
||||
"ttgo-t-watch": VARIANT_ESP32,
|
||||
"turta_iot_node": VARIANT_ESP32,
|
||||
"vintlabs-devkit-v1": VARIANT_ESP32,
|
||||
"wemosbat": VARIANT_ESP32,
|
||||
"wemos_d1_mini32": VARIANT_ESP32,
|
||||
"wesp32": VARIANT_ESP32,
|
||||
"widora-air": VARIANT_ESP32,
|
||||
"wifiduino32": VARIANT_ESP32,
|
||||
"xinabox_cw02": VARIANT_ESP32,
|
||||
BOARDS = {
|
||||
"adafruit_feather_esp32s2_tft": {
|
||||
"name": "Adafruit Feather ESP32-S2 TFT",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"adafruit_feather_esp32s3": {
|
||||
"name": "Adafruit Feather ESP32-S3 2MB PSRAM",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"adafruit_feather_esp32s3_nopsram": {
|
||||
"name": "Adafruit Feather ESP32-S3 No PSRAM",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"adafruit_feather_esp32s3_tft": {
|
||||
"name": "Adafruit Feather ESP32-S3 TFT",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"adafruit_feather_esp32_v2": {
|
||||
"name": "Adafruit Feather ESP32 V2",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"adafruit_funhouse_esp32s2": {
|
||||
"name": "Adafruit FunHouse",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"adafruit_itsybitsy_esp32": {
|
||||
"name": "Adafruit ItsyBitsy ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"adafruit_magtag29_esp32s2": {
|
||||
"name": "Adafruit MagTag 2.9",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"adafruit_metro_esp32s2": {
|
||||
"name": "Adafruit Metro ESP32-S2",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"adafruit_qtpy_esp32c3": {
|
||||
"name": "Adafruit QT Py ESP32-C3",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"adafruit_qtpy_esp32": {
|
||||
"name": "Adafruit QT Py ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"adafruit_qtpy_esp32s2": {
|
||||
"name": "Adafruit QT Py ESP32-S2",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"adafruit_qtpy_esp32s3_nopsram": {
|
||||
"name": "Adafruit QT Py ESP32-S3 No PSRAM",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"airm2m_core_esp32c3": {
|
||||
"name": "AirM2M CORE ESP32C3",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"alksesp32": {
|
||||
"name": "ALKS ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"atmegazero_esp32s2": {
|
||||
"name": "EspinalLab ATMegaZero ESP32-S2",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"az-delivery-devkit-v4": {
|
||||
"name": "AZ-Delivery ESP-32 Dev Kit C V4",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"bee_motion_mini": {
|
||||
"name": "Smart Bee Motion Mini",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"bee_motion": {
|
||||
"name": "Smart Bee Motion",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"bee_motion_s3": {
|
||||
"name": "Smart Bee Motion S3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"bee_s3": {
|
||||
"name": "Smart Bee S3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"bpi-bit": {
|
||||
"name": "BPI-Bit",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"briki_abc_esp32": {
|
||||
"name": "Briki ABC (MBC-WB) - ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"briki_mbc-wb_esp32": {
|
||||
"name": "Briki MBC-WB - ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"cnrs_aw2eth": {
|
||||
"name": "CNRS AW2ETH",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"connaxio_espoir": {
|
||||
"name": "Connaxio's Espoir",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"d-duino-32": {
|
||||
"name": "D-duino-32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"deneyapkart1A": {
|
||||
"name": "Deneyap Kart 1A",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"deneyapkartg": {
|
||||
"name": "Deneyap Kart G",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"deneyapkart": {
|
||||
"name": "Deneyap Kart",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"deneyapmini": {
|
||||
"name": "Deneyap Mini",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"denky32": {
|
||||
"name": "Denky32 (WROOM32)",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"denky_d4": {
|
||||
"name": "Denky D4 (PICO-V3-02)",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"dfrobot_beetle_esp32c3": {
|
||||
"name": "DFRobot Beetle ESP32-C3",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"dfrobot_firebeetle2_esp32s3": {
|
||||
"name": "DFRobot Firebeetle 2 ESP32-S3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"dpu_esp32": {
|
||||
"name": "TAMC DPU ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp320": {
|
||||
"name": "Electronic SweetPeas ESP320",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32-c3-devkitm-1": {
|
||||
"name": "Espressif ESP32-C3-DevKitM-1",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"esp32cam": {
|
||||
"name": "AI Thinker ESP32-CAM",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32-devkitlipo": {
|
||||
"name": "OLIMEX ESP32-DevKit-LiPo",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32dev": {
|
||||
"name": "Espressif ESP32 Dev Module",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32doit-devkit-v1": {
|
||||
"name": "DOIT ESP32 DEVKIT V1",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32doit-espduino": {
|
||||
"name": "DOIT ESPduino32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32-evb": {
|
||||
"name": "OLIMEX ESP32-EVB",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32-gateway": {
|
||||
"name": "OLIMEX ESP32-GATEWAY",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32-poe-iso": {
|
||||
"name": "OLIMEX ESP32-PoE-ISO",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32-poe": {
|
||||
"name": "OLIMEX ESP32-PoE",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32-pro": {
|
||||
"name": "OLIMEX ESP32-PRO",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32-s2-franzininho": {
|
||||
"name": "Franzininho WiFi Board",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"esp32-s2-kaluga-1": {
|
||||
"name": "Espressif ESP32-S2-Kaluga-1 Kit",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"esp32-s2-saola-1": {
|
||||
"name": "Espressif ESP32-S2-Saola-1",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"esp32s3box": {
|
||||
"name": "Espressif ESP32-S3-Box",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"esp32s3camlcd": {
|
||||
"name": "ESP32S3 CAM LCD",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"esp32-s3-devkitc-1": {
|
||||
"name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"esp32thing": {
|
||||
"name": "SparkFun ESP32 Thing",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32thing_plus": {
|
||||
"name": "SparkFun ESP32 Thing Plus",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp32vn-iot-uno": {
|
||||
"name": "ESP32vn IoT Uno",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"espea32": {
|
||||
"name": "April Brother ESPea32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"espectro32": {
|
||||
"name": "ESPectro32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"espino32": {
|
||||
"name": "ESPino32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"esp-wrover-kit": {
|
||||
"name": "Espressif ESP-WROVER-KIT",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"etboard": {
|
||||
"name": "ETBoard",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"featheresp32": {
|
||||
"name": "Adafruit ESP32 Feather",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"featheresp32-s2": {
|
||||
"name": "Adafruit ESP32-S2 Feather Development Board",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"firebeetle32": {
|
||||
"name": "FireBeetle-ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"fm-devkit": {
|
||||
"name": "ESP32 FM DevKit",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"franzininho_wifi_esp32s2": {
|
||||
"name": "Franzininho WiFi",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"franzininho_wifi_msc_esp32s2": {
|
||||
"name": "Franzininho WiFi MSC",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"frogboard": {
|
||||
"name": "Frog Board ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"healthypi4": {
|
||||
"name": "ProtoCentral HealthyPi 4",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"heltec_wifi_kit_32": {
|
||||
"name": "Heltec WiFi Kit 32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"heltec_wifi_kit_32_v2": {
|
||||
"name": "Heltec WiFi Kit 32 (V2)",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"heltec_wifi_lora_32": {
|
||||
"name": "Heltec WiFi LoRa 32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"heltec_wifi_lora_32_V2": {
|
||||
"name": "Heltec WiFi LoRa 32 (V2)",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"heltec_wireless_stick_lite": {
|
||||
"name": "Heltec Wireless Stick Lite",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"heltec_wireless_stick": {
|
||||
"name": "Heltec Wireless Stick",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"honeylemon": {
|
||||
"name": "HONEYLemon",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"hornbill32dev": {
|
||||
"name": "Hornbill ESP32 Dev",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"hornbill32minima": {
|
||||
"name": "Hornbill ESP32 Minima",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"imbrios-logsens-v1p1": {
|
||||
"name": "Imbrios LogSens V1P1",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"inex_openkb": {
|
||||
"name": "INEX OpenKB",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"intorobot": {
|
||||
"name": "IntoRobot Fig",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"iotaap_magnolia": {
|
||||
"name": "IoTaaP Magnolia",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"iotbusio": {
|
||||
"name": "oddWires IoT-Bus Io",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"iotbusproteus": {
|
||||
"name": "oddWires IoT-Bus Proteus",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"kb32-ft": {
|
||||
"name": "MakerAsia KB32-FT",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"kits-edu": {
|
||||
"name": "KITS ESP32 EDU",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"labplus_mpython": {
|
||||
"name": "Labplus mPython",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"lionbit": {
|
||||
"name": "Lion:Bit Dev Board",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"lolin32_lite": {
|
||||
"name": "WEMOS LOLIN32 Lite",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"lolin32": {
|
||||
"name": "WEMOS LOLIN32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"lolin_c3_mini": {
|
||||
"name": "WEMOS LOLIN C3 Mini",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"lolin_d32": {
|
||||
"name": "WEMOS LOLIN D32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"lolin_d32_pro": {
|
||||
"name": "WEMOS LOLIN D32 PRO",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"lolin_s2_mini": {
|
||||
"name": "WEMOS LOLIN S2 Mini",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"lolin_s2_pico": {
|
||||
"name": "WEMOS LOLIN S2 PICO",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"lolin_s3": {
|
||||
"name": "WEMOS LOLIN S3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"lopy4": {
|
||||
"name": "Pycom LoPy4",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"lopy": {
|
||||
"name": "Pycom LoPy",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stack-atom": {
|
||||
"name": "M5Stack-ATOM",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stack-core2": {
|
||||
"name": "M5Stack Core2",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stack-core-esp32": {
|
||||
"name": "M5Stack Core ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stack-coreink": {
|
||||
"name": "M5Stack-Core Ink",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stack-fire": {
|
||||
"name": "M5Stack FIRE",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stack-grey": {
|
||||
"name": "M5Stack GREY ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stack-station": {
|
||||
"name": "M5Stack Station",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stack-timer-cam": {
|
||||
"name": "M5Stack Timer CAM",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"m5stick-c": {
|
||||
"name": "M5Stick-C",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"magicbit": {
|
||||
"name": "MagicBit",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"mgbot-iotik32a": {
|
||||
"name": "MGBOT IOTIK 32A",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"mgbot-iotik32b": {
|
||||
"name": "MGBOT IOTIK 32B",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"mhetesp32devkit": {
|
||||
"name": "MH ET LIVE ESP32DevKIT",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"mhetesp32minikit": {
|
||||
"name": "MH ET LIVE ESP32MiniKit",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"microduino-core-esp32": {
|
||||
"name": "Microduino Core ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"micros2": {
|
||||
"name": "microS2",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"minimain_esp32s2": {
|
||||
"name": "Deparment of Alchemy MiniMain ESP32-S2",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"nano32": {
|
||||
"name": "MakerAsia Nano32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"nina_w10": {
|
||||
"name": "u-blox NINA-W10 series",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"node32s": {
|
||||
"name": "Node32s",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"nodemcu-32s2": {
|
||||
"name": "Ai-Thinker NodeMCU-32S2 (ESP-12K)",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"nodemcu-32s": {
|
||||
"name": "NodeMCU-32S",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"nscreen-32": {
|
||||
"name": "YeaCreate NSCREEN-32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"odroid_esp32": {
|
||||
"name": "ODROID-GO",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"onehorse32dev": {
|
||||
"name": "Onehorse ESP32 Dev Module",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"oroca_edubot": {
|
||||
"name": "OROCA EduBot",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"pico32": {
|
||||
"name": "ESP32 Pico Kit",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"piranha_esp32": {
|
||||
"name": "Fishino Piranha ESP-32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"pocket_32": {
|
||||
"name": "Dongsen Tech Pocket 32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"pycom_gpy": {
|
||||
"name": "Pycom GPy",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"qchip": {
|
||||
"name": "Qchip",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"quantum": {
|
||||
"name": "Noduino Quantum",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"seeed_xiao_esp32c3": {
|
||||
"name": "Seeed Studio XIAO ESP32C3",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"sensesiot_weizen": {
|
||||
"name": "LOGISENSES Senses Weizen",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"sg-o_airMon": {
|
||||
"name": "SG-O AirMon",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"s_odi_ultra": {
|
||||
"name": "S.ODI Ultra v1",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"sparkfun_esp32_iot_redboard": {
|
||||
"name": "SparkFun ESP32 IoT RedBoard",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"sparkfun_esp32micromod": {
|
||||
"name": "SparkFun ESP32 MicroMod",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"sparkfun_esp32s2_thing_plus_c": {
|
||||
"name": "SparkFun ESP32 Thing Plus C",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"sparkfun_esp32s2_thing_plus": {
|
||||
"name": "SparkFun ESP32-S2 Thing Plus",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"sparkfun_lora_gateway_1-channel": {
|
||||
"name": "SparkFun LoRa Gateway 1-Channel",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"tamc_termod_s3": {
|
||||
"name": "TAMC Termod S3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"tinypico": {
|
||||
"name": "Unexpected Maker TinyPICO",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"trueverit-iot-driver-mk2": {
|
||||
"name": "Trueverit ESP32 Universal IoT Driver MK II",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"trueverit-iot-driver-mk3": {
|
||||
"name": "Trueverit ESP32 Universal IoT Driver MK III",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"trueverit-iot-driver": {
|
||||
"name": "Trueverit ESP32 Universal IoT Driver",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"ttgo-lora32-v1": {
|
||||
"name": "TTGO LoRa32-OLED V1",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"ttgo-lora32-v21": {
|
||||
"name": "TTGO LoRa32-OLED v2.1.6",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"ttgo-lora32-v2": {
|
||||
"name": "TTGO LoRa32-OLED V2",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"ttgo-t1": {
|
||||
"name": "TTGO T1",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"ttgo-t7-v13-mini32": {
|
||||
"name": "TTGO T7 V1.3 Mini32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"ttgo-t7-v14-mini32": {
|
||||
"name": "TTGO T7 V1.4 Mini32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"ttgo-t-beam": {
|
||||
"name": "TTGO T-Beam",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"ttgo-t-oi-plus": {
|
||||
"name": "TTGO T-OI PLUS RISC-V ESP32-C3",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"ttgo-t-watch": {
|
||||
"name": "TTGO T-Watch",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"turta_iot_node": {
|
||||
"name": "Turta IoT Node",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"um_feathers2": {
|
||||
"name": "Unexpected Maker FeatherS2",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"um_feathers2_neo": {
|
||||
"name": "Unexpected Maker FeatherS2 Neo",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"um_feathers3": {
|
||||
"name": "Unexpected Maker FeatherS3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"um_pros3": {
|
||||
"name": "Unexpected Maker PROS3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"um_rmp": {
|
||||
"name": "Unexpected Maker RMP",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"um_tinys2": {
|
||||
"name": "Unexpected Maker TinyS2",
|
||||
"variant": VARIANT_ESP32S2,
|
||||
},
|
||||
"um_tinys3": {
|
||||
"name": "Unexpected Maker TinyS3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"unphone7": {
|
||||
"name": "unPhone 7",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"unphone8": {
|
||||
"name": "unPhone 8",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"unphone9": {
|
||||
"name": "unPhone 9",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"upesy_wroom": {
|
||||
"name": "uPesy ESP32 Wroom DevKit",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"upesy_wrover": {
|
||||
"name": "uPesy ESP32 Wrover DevKit",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"vintlabs-devkit-v1": {
|
||||
"name": "VintLabs ESP32 Devkit",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"watchy": {
|
||||
"name": "SQFMI Watchy v2.0",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"wemosbat": {
|
||||
"name": "WeMos WiFi and Bluetooth Battery",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"wemos_d1_mini32": {
|
||||
"name": "WEMOS D1 MINI ESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"wemos_d1_uno32": {
|
||||
"name": "WEMOS D1 R32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"wesp32": {
|
||||
"name": "Silicognition wESP32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"widora-air": {
|
||||
"name": "Widora AIR",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"wifiduino32c3": {
|
||||
"name": "Blinker WiFiduinoV2 (ESP32-C3)",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"wifiduino32": {
|
||||
"name": "Blinker WiFiduino32",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"wifiduino32s3": {
|
||||
"name": "Blinker WiFiduino32S3",
|
||||
"variant": VARIANT_ESP32S3,
|
||||
},
|
||||
"wipy3": {
|
||||
"name": "Pycom WiPy3",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"wt32-eth01": {
|
||||
"name": "Wireless-Tag WT32-ETH01 Ethernet Module",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
"xinabox_cw02": {
|
||||
"name": "XinaBox CW02",
|
||||
"variant": VARIANT_ESP32,
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "gpio_idf.h"
|
||||
#include "gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@ -8,13 +8,11 @@ namespace esp32 {
|
||||
|
||||
static const char *const TAG = "esp32";
|
||||
|
||||
bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
bool ESP32InternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
|
||||
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
|
||||
if (flags == gpio::FLAG_NONE) {
|
||||
return GPIO_MODE_DISABLE;
|
||||
} else if (flags == gpio::FLAG_INPUT) {
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
return GPIO_MODE_INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
return GPIO_MODE_OUTPUT;
|
||||
@ -25,7 +23,7 @@ static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) {
|
||||
return GPIO_MODE_INPUT_OUTPUT;
|
||||
} else {
|
||||
// unsupported
|
||||
// unsupported or gpio::FLAG_NONE
|
||||
return GPIO_MODE_DISABLE;
|
||||
}
|
||||
}
|
||||
@ -35,14 +33,14 @@ struct ISRPinArg {
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
|
||||
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
|
||||
switch (type) {
|
||||
case gpio::INTERRUPT_RISING_EDGE:
|
||||
@ -74,13 +72,13 @@ void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio:
|
||||
gpio_isr_handler_add(pin_, func, arg);
|
||||
}
|
||||
|
||||
std::string IDFInternalGPIOPin::dump_summary() const {
|
||||
std::string ESP32InternalGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast<uint32_t>(pin_));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::setup() {
|
||||
void ESP32InternalGPIOPin::setup() {
|
||||
gpio_config_t conf{};
|
||||
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
|
||||
conf.mode = flags_to_mode(flags_);
|
||||
@ -88,10 +86,12 @@ void IDFInternalGPIOPin::setup() {
|
||||
conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
conf.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&conf);
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
if (flags_ & gpio::FLAG_OUTPUT) {
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
}
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
// can't call gpio_config here because that logs in esp-idf which may cause issues
|
||||
gpio_set_direction(pin_, flags_to_mode(flags));
|
||||
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
|
||||
@ -105,9 +105,9 @@ void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
gpio_set_pull_mode(pin_, pull_mode);
|
||||
}
|
||||
|
||||
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||
void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
|
||||
bool ESP32InternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||
void ESP32InternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
|
||||
|
||||
} // namespace esp32
|
||||
|
||||
@ -140,4 +140,4 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ESP_IDF
|
||||
#endif // USE_ESP32
|
@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
#include "esphome/core/hal.h"
|
||||
#include <driver/gpio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
|
||||
class IDFInternalGPIOPin : public InternalGPIOPin {
|
||||
class ESP32InternalGPIOPin : public InternalGPIOPin {
|
||||
public:
|
||||
void set_pin(gpio_num_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
@ -37,4 +37,4 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
|
||||
} // namespace esp32
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ESP_IDF
|
||||
#endif // USE_ESP32
|
@ -38,8 +38,7 @@ from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_support
|
||||
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
|
||||
|
||||
|
||||
IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin)
|
||||
ArduinoInternalGPIOPin = esp32_ns.class_("ArduinoInternalGPIOPin", cg.InternalGPIOPin)
|
||||
ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin)
|
||||
|
||||
|
||||
def _lookup_pin(value):
|
||||
@ -105,6 +104,16 @@ _esp32_validations = {
|
||||
|
||||
def validate_gpio_pin(value):
|
||||
value = _translate_pin(value)
|
||||
board = CORE.data[KEY_ESP32][KEY_BOARD]
|
||||
board_pins = boards.ESP32_BOARD_PINS.get(board, {})
|
||||
|
||||
# Resolved aliased board pins (shorthand when two boards have the same pin configuration)
|
||||
while isinstance(board_pins, str):
|
||||
board_pins = boards.ESP32_BOARD_PINS[board_pins]
|
||||
|
||||
if value in board_pins.values():
|
||||
return value
|
||||
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
@ -163,18 +172,10 @@ DRIVE_STRENGTHS = {
|
||||
gpio_num_t = cg.global_ns.enum("gpio_num_t")
|
||||
|
||||
|
||||
def _choose_pin_declaration(value):
|
||||
if CORE.using_esp_idf:
|
||||
return cv.declare_id(IDFInternalGPIOPin)(value)
|
||||
if CORE.using_arduino:
|
||||
return cv.declare_id(ArduinoInternalGPIOPin)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
CONF_DRIVE_STRENGTH = "drive_strength"
|
||||
ESP32_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): _choose_pin_declaration,
|
||||
cv.GenerateID(): cv.declare_id(ESP32InternalGPIOPin),
|
||||
cv.Required(CONF_NUMBER): validate_gpio_pin,
|
||||
cv.Optional(CONF_MODE, default={}): cv.Schema(
|
||||
{
|
||||
@ -186,8 +187,7 @@ ESP32_PIN_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_DRIVE_STRENGTH, esp32_idf="20mA"): cv.All(
|
||||
cv.only_with_esp_idf,
|
||||
cv.Optional(CONF_DRIVE_STRENGTH, default="20mA"): cv.All(
|
||||
cv.float_with_unit("current", "mA", optional_unit=True),
|
||||
cv.enum(DRIVE_STRENGTHS),
|
||||
),
|
||||
@ -200,10 +200,7 @@ ESP32_PIN_SCHEMA = cv.All(
|
||||
async def esp32_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
if CORE.using_esp_idf:
|
||||
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
|
||||
else:
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
if CONF_DRIVE_STRENGTH in config:
|
||||
cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
|
||||
|
@ -42,7 +42,7 @@ def esp32_validate_gpio_pin(value):
|
||||
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
|
||||
value,
|
||||
)
|
||||
if value in (20, 24, 28, 29, 30, 31):
|
||||
if value in (24, 28, 29, 30, 31):
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
|
||||
|
@ -110,9 +110,11 @@ class ESP32Preferences : public ESPPreferences {
|
||||
if (s_pending_save.empty())
|
||||
return true;
|
||||
|
||||
ESP_LOGD(TAG, "Saving preferences to flash...");
|
||||
ESP_LOGD(TAG, "Saving %d preferences to flash...", s_pending_save.size());
|
||||
// goal try write all pending saves even if one fails
|
||||
bool any_failed = false;
|
||||
int cached = 0, written = 0, failed = 0;
|
||||
esp_err_t last_err = ESP_OK;
|
||||
std::string last_key{};
|
||||
|
||||
// go through vector from back to front (makes erase easier/more efficient)
|
||||
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
|
||||
@ -124,14 +126,24 @@ class ESP32Preferences : public ESPPreferences {
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", save.key.c_str(), save.data.size(),
|
||||
esp_err_to_name(err));
|
||||
any_failed = true;
|
||||
failed++;
|
||||
last_err = err;
|
||||
last_key = save.key;
|
||||
continue;
|
||||
}
|
||||
written++;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "NVS data not changed skipping %s len=%u", save.key.c_str(), save.data.size());
|
||||
ESP_LOGV(TAG, "NVS data not changed skipping %s len=%u", save.key.c_str(), save.data.size());
|
||||
cached++;
|
||||
}
|
||||
s_pending_save.erase(s_pending_save.begin() + i);
|
||||
}
|
||||
ESP_LOGD(TAG, "Saving %d preferences to flash: %d cached, %d written, %d failed", cached + written + failed, cached,
|
||||
written, failed);
|
||||
if (failed > 0) {
|
||||
ESP_LOGE(TAG, "Error saving %d preferences to flash. Last error=%s for key=%s", failed, esp_err_to_name(last_err),
|
||||
last_key.c_str());
|
||||
}
|
||||
|
||||
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
|
||||
esp_err_t err = nvs_commit(nvs_handle);
|
||||
@ -140,7 +152,7 @@ class ESP32Preferences : public ESPPreferences {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !any_failed;
|
||||
return failed == 0;
|
||||
}
|
||||
bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
|
||||
NVSData stored_data{};
|
||||
@ -150,7 +162,7 @@ class ESP32Preferences : public ESPPreferences {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err));
|
||||
return true;
|
||||
}
|
||||
stored_data.data.reserve(actual_len);
|
||||
stored_data.data.resize(actual_len);
|
||||
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.data.data(), &actual_len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
|
||||
@ -158,6 +170,17 @@ class ESP32Preferences : public ESPPreferences {
|
||||
}
|
||||
return to_save.data != stored_data.data;
|
||||
}
|
||||
|
||||
bool reset() override {
|
||||
ESP_LOGD(TAG, "Cleaning up preferences in flash...");
|
||||
s_pending_save.clear();
|
||||
|
||||
nvs_flash_deinit();
|
||||
nvs_flash_erase();
|
||||
// Make the handle invalid to prevent any saves until restart
|
||||
nvs_handle = 0;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void setup_preferences() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID
|
||||
from esphome.core import CORE
|
||||
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER
|
||||
from esphome.core import CORE, TimePeriod
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
@ -12,16 +12,47 @@ ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component)
|
||||
|
||||
CONF_MAJOR = "major"
|
||||
CONF_MINOR = "minor"
|
||||
CONF_MIN_INTERVAL = "min_interval"
|
||||
CONF_MAX_INTERVAL = "max_interval"
|
||||
CONF_MEASURED_POWER = "measured_power"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
|
||||
cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
|
||||
cv.Required(CONF_UUID): cv.uuid,
|
||||
cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
|
||||
cv.Optional(CONF_MINOR, default=61958): cv.uint16_t,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
def validate_config(config):
|
||||
if config[CONF_MIN_INTERVAL] > config.get(CONF_MAX_INTERVAL):
|
||||
raise cv.Invalid("min_interval must be <= max_interval")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
|
||||
cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
|
||||
cv.Required(CONF_UUID): cv.uuid,
|
||||
cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
|
||||
cv.Optional(CONF_MINOR, default=61958): cv.uint16_t,
|
||||
cv.Optional(CONF_MIN_INTERVAL, default="100ms"): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(
|
||||
min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_MAX_INTERVAL, default="100ms"): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(
|
||||
min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_MEASURED_POWER, default=-59): cv.int_range(
|
||||
min=-128, max=0
|
||||
),
|
||||
cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All(
|
||||
cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_config,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
@ -31,6 +62,10 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_major(config[CONF_MAJOR]))
|
||||
cg.add(var.set_minor(config[CONF_MINOR]))
|
||||
cg.add(var.set_min_interval(config[CONF_MIN_INTERVAL]))
|
||||
cg.add(var.set_max_interval(config[CONF_MAX_INTERVAL]))
|
||||
cg.add(var.set_measured_power(config[CONF_MEASURED_POWER]))
|
||||
cg.add(var.set_tx_power(config[CONF_TX_POWER]))
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
@ -36,11 +36,24 @@ static esp_ble_adv_params_t ble_adv_params = {
|
||||
#define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8))
|
||||
|
||||
static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = {
|
||||
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502};
|
||||
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}};
|
||||
|
||||
void ESP32BLEBeacon::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 BLE Beacon:");
|
||||
ESP_LOGCONFIG(TAG, " Major: %u, Minor: %u", this->major_, this->minor_);
|
||||
char uuid[37];
|
||||
char *bpos = uuid;
|
||||
for (int8_t ii = 0; ii < 16; ++ii) {
|
||||
bpos += sprintf(bpos, "%02X", this->uuid_[ii]);
|
||||
if (ii == 3 || ii == 5 || ii == 7 || ii == 9) {
|
||||
bpos += sprintf(bpos, "-");
|
||||
}
|
||||
}
|
||||
uuid[36] = '\0';
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d"
|
||||
", TX Power: %ddBm",
|
||||
uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_,
|
||||
this->tx_power_);
|
||||
}
|
||||
|
||||
void ESP32BLEBeacon::setup() {
|
||||
@ -67,6 +80,9 @@ void ESP32BLEBeacon::ble_core_task(void *params) {
|
||||
}
|
||||
|
||||
void ESP32BLEBeacon::ble_setup() {
|
||||
ble_adv_params.adv_int_min = static_cast<uint16_t>(global_esp32_ble_beacon->min_interval_ / 0.625f);
|
||||
ble_adv_params.adv_int_max = static_cast<uint16_t>(global_esp32_ble_beacon->max_interval_ / 0.625f);
|
||||
|
||||
// Initialize non-volatile storage for the bluetooth controller
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err != ESP_OK) {
|
||||
@ -118,6 +134,12 @@ void ESP32BLEBeacon::ble_setup() {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
|
||||
return;
|
||||
}
|
||||
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV,
|
||||
static_cast<esp_power_level_t>((global_esp32_ble_beacon->tx_power_ + 12) / 3));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
|
||||
@ -130,7 +152,7 @@ void ESP32BLEBeacon::ble_setup() {
|
||||
sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid));
|
||||
ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_);
|
||||
ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_);
|
||||
ibeacon_adv_data.ibeacon_vendor.measured_power = 0xC5;
|
||||
ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(global_esp32_ble_beacon->measured_power_);
|
||||
|
||||
esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data));
|
||||
}
|
||||
@ -153,7 +175,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: {
|
||||
err = param->adv_start_cmpl.status;
|
||||
err = param->adv_stop_cmpl.status;
|
||||
if (err != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "BLE adv stop failed: %s", esp_err_to_name(err));
|
||||
} else {
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_bt.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_beacon {
|
||||
@ -14,8 +15,8 @@ typedef struct {
|
||||
uint8_t flags[3];
|
||||
uint8_t length;
|
||||
uint8_t type;
|
||||
uint16_t company_id;
|
||||
uint16_t beacon_type;
|
||||
uint8_t company_id[2];
|
||||
uint8_t beacon_type[2];
|
||||
} __attribute__((packed)) esp_ble_ibeacon_head_t;
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
@ -42,6 +43,10 @@ class ESP32BLEBeacon : public Component {
|
||||
|
||||
void set_major(uint16_t major) { this->major_ = major; }
|
||||
void set_minor(uint16_t minor) { this->minor_ = minor; }
|
||||
void set_min_interval(uint16_t val) { this->min_interval_ = val; }
|
||||
void set_max_interval(uint16_t val) { this->max_interval_ = val; }
|
||||
void set_measured_power(int8_t val) { this->measured_power_ = val; }
|
||||
void set_tx_power(int8_t val) { this->tx_power_ = val; }
|
||||
|
||||
protected:
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
@ -51,6 +56,10 @@ class ESP32BLEBeacon : public Component {
|
||||
std::array<uint8_t, 16> uuid_;
|
||||
uint16_t major_{};
|
||||
uint16_t minor_{};
|
||||
uint16_t min_interval_{};
|
||||
uint16_t max_interval_{};
|
||||
int8_t measured_power_{};
|
||||
int8_t tx_power_{};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
12
esphome/components/esp32_ble_client/__init__.py
Normal file
12
esphome/components/esp32_ble_client/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.components import esp32_ble_tracker
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_tracker"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client")
|
||||
BLEClientBase = esp32_ble_client_ns.class_(
|
||||
"BLEClientBase", esp32_ble_tracker.ESPBTClient, cg.Component
|
||||
)
|
99
esphome/components/esp32_ble_client/ble_characteristic.cpp
Normal file
99
esphome/components/esp32_ble_client/ble_characteristic.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
#include "ble_characteristic.h"
|
||||
#include "ble_client_base.h"
|
||||
#include "ble_service.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
|
||||
BLECharacteristic::~BLECharacteristic() {
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLECharacteristic::release_descriptors() {
|
||||
this->parsed = false;
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->descriptors.clear();
|
||||
}
|
||||
|
||||
void BLECharacteristic::parse_descriptors() {
|
||||
this->parsed = true;
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_descr_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status =
|
||||
esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(),
|
||||
this->handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d",
|
||||
this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
desc->handle = result.handle;
|
||||
desc->characteristic = this;
|
||||
this->descriptors.push_back(desc);
|
||||
ESP_LOGV(TAG, "[%d] [%s] descriptor %s, handle 0x%x", this->service->client->get_connection_index(),
|
||||
this->service->client->address_str().c_str(), desc->uuid.to_string().c_str(), desc->handle);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
|
||||
if (!this->parsed)
|
||||
this->parse_descriptors();
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->uuid == uuid)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
|
||||
if (!this->parsed)
|
||||
this->parse_descriptors();
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->handle == handle)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
|
||||
auto *client = this->service->client;
|
||||
auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size,
|
||||
new_val, write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error sending write value to BLE gattc server, status=%d",
|
||||
this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
||||
return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
39
esphome/components/esp32_ble_client/ble_characteristic.h
Normal file
39
esphome/components/esp32_ble_client/ble_characteristic.h
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#include "ble_descriptor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEService;
|
||||
|
||||
class BLECharacteristic {
|
||||
public:
|
||||
~BLECharacteristic();
|
||||
bool parsed = false;
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
esp_gatt_char_prop_t properties;
|
||||
std::vector<BLEDescriptor *> descriptors;
|
||||
void parse_descriptors();
|
||||
void release_descriptors();
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
|
||||
esp_err_t write_value(uint8_t *new_val, int16_t new_val_size);
|
||||
esp_err_t write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
401
esphome/components/esp32_ble_client/ble_client_base.cpp
Normal file
401
esphome/components/esp32_ble_client/ble_client_base.cpp
Normal file
@ -0,0 +1,401 @@
|
||||
#include "ble_client_base.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid =
|
||||
{
|
||||
.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,
|
||||
},
|
||||
};
|
||||
|
||||
void BLEClientBase::setup() {
|
||||
static uint8_t connection_index = 0;
|
||||
this->connection_index_ = connection_index++;
|
||||
|
||||
auto ret = esp_ble_gattc_app_register(this->app_id);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
||||
this->mark_failed();
|
||||
}
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
}
|
||||
|
||||
void BLEClientBase::loop() {
|
||||
// READY_TO_CONNECT means we have discovered the device
|
||||
// and the scanner has been stopped by the tracker.
|
||||
if (this->state_ == espbt::ClientState::READY_TO_CONNECT) {
|
||||
this->connect();
|
||||
}
|
||||
}
|
||||
|
||||
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != this->address_)
|
||||
return false;
|
||||
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
|
||||
return false;
|
||||
|
||||
ESP_LOGD(TAG, "[%d] [%s] Found device", this->connection_index_, this->address_str_.c_str());
|
||||
this->set_state(espbt::ClientState::DISCOVERED);
|
||||
|
||||
auto addr = device.address_uint64();
|
||||
this->remote_bda_[0] = (addr >> 40) & 0xFF;
|
||||
this->remote_bda_[1] = (addr >> 32) & 0xFF;
|
||||
this->remote_bda_[2] = (addr >> 24) & 0xFF;
|
||||
this->remote_bda_[3] = (addr >> 16) & 0xFF;
|
||||
this->remote_bda_[4] = (addr >> 8) & 0xFF;
|
||||
this->remote_bda_[5] = (addr >> 0) & 0xFF;
|
||||
this->remote_addr_type_ = device.get_address_type();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLEClientBase::connect() {
|
||||
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
|
||||
this->remote_addr_type_);
|
||||
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||
ret);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_state(espbt::ClientState::CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientBase::disconnect() {
|
||||
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING)
|
||||
return;
|
||||
ESP_LOGI(TAG, "[%d] [%s] Disconnecting.", this->connection_index_, this->address_str_.c_str());
|
||||
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(),
|
||||
err);
|
||||
}
|
||||
|
||||
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
|
||||
this->state_ == espbt::ClientState::DISCOVERED) {
|
||||
this->set_address(0);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_state(espbt::ClientState::DISCONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientBase::release_services() {
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
esp_ble_gattc_cache_clean(this->remote_bda_);
|
||||
}
|
||||
|
||||
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
||||
return false;
|
||||
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
|
||||
return false;
|
||||
|
||||
ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), event, esp_gattc_if);
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT: {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
ESP_LOGV(TAG, "[%d] [%s] gattc registered app id %d", this->connection_index_, this->address_str_.c_str(),
|
||||
this->app_id);
|
||||
this->gattc_if_ = esp_gattc_if;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->reg.app_id, param->reg.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str());
|
||||
this->conn_id_ = param->open.conn_id;
|
||||
this->service_count_ = 0;
|
||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||
param->open.status);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
|
||||
this->address_str_.c_str(), ret);
|
||||
}
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CFG_MTU_EVT: {
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(),
|
||||
param->cfg_mtu.status, param->cfg_mtu.mtu);
|
||||
this->mtu_ = param->cfg_mtu.mtu;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0)
|
||||
return false;
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->disconnect.reason);
|
||||
this->release_services();
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
this->service_count_++;
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
// V3 clients don't need services initialized since
|
||||
// they only request by handle after receiving the services.
|
||||
break;
|
||||
}
|
||||
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
|
||||
ble_service->start_handle = param->search_res.start_handle;
|
||||
ble_service->end_handle = param->search_res.end_handle;
|
||||
ble_service->client = this;
|
||||
this->services_.push_back(ble_service);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str());
|
||||
for (auto &svc : this->services_) {
|
||||
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
|
||||
svc->uuid.to_string().c_str());
|
||||
ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_,
|
||||
this->address_str_.c_str(), svc->start_handle, svc->end_handle);
|
||||
}
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
// Client is responsible for flipping the descriptor value
|
||||
// when using the cache
|
||||
break;
|
||||
}
|
||||
esp_gattc_descr_elem_t desc_result;
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t descr_status =
|
||||
esp_ble_gattc_get_descr_by_char_handle(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle,
|
||||
NOTIFY_DESC_UUID, &desc_result, &count);
|
||||
if (descr_status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_descr_by_char_handle error, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), descr_status);
|
||||
break;
|
||||
}
|
||||
esp_gattc_char_elem_t char_result;
|
||||
esp_gatt_status_t char_status =
|
||||
esp_ble_gattc_get_all_char(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle,
|
||||
param->reg_for_notify.handle, &char_result, &count, 0);
|
||||
if (char_status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), char_status);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
1 = notify
|
||||
2 = indicate
|
||||
*/
|
||||
uint16_t notify_en = char_result.properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY ? 1 : 2;
|
||||
esp_err_t status =
|
||||
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
|
||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
// This event is sent by the server when it requests security
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
// This event is sent once authentication has completed
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
|
||||
format_hex(bd_addr, 6).c_str());
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
|
||||
param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
|
||||
param->ble_security.auth_cmpl.auth_mode);
|
||||
}
|
||||
break;
|
||||
// There are other events we'll want to implement at some point to support things like pass key
|
||||
// https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse GATT values into a float for a sensor.
|
||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
|
||||
float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
// A length of one means a single octet value.
|
||||
if (length == 0)
|
||||
return 0;
|
||||
if (length == 1)
|
||||
return (float) ((uint8_t) value[0]);
|
||||
|
||||
switch (value[0]) {
|
||||
case 0x1: // boolean.
|
||||
case 0x2: // 2bit.
|
||||
case 0x3: // nibble.
|
||||
case 0x4: // uint8.
|
||||
return (float) ((uint8_t) value[1]);
|
||||
case 0x5: // uint12.
|
||||
case 0x6: // uint16.
|
||||
if (length > 2) {
|
||||
return (float) encode_uint16(value[1], value[2]);
|
||||
}
|
||||
case 0x7: // uint24.
|
||||
if (length > 3) {
|
||||
return (float) encode_uint24(value[1], value[2], value[3]);
|
||||
}
|
||||
case 0x8: // uint32.
|
||||
if (length > 4) {
|
||||
return (float) encode_uint32(value[1], value[2], value[3], value[4]);
|
||||
}
|
||||
case 0xC: // int8.
|
||||
return (float) ((int8_t) value[1]);
|
||||
case 0xD: // int12.
|
||||
case 0xE: // int16.
|
||||
if (length > 2) {
|
||||
return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
|
||||
}
|
||||
case 0xF: // int24.
|
||||
if (length > 3) {
|
||||
return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
|
||||
}
|
||||
case 0x10: // int32.
|
||||
if (length > 4) {
|
||||
return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
|
||||
(int32_t)(value[4]));
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_,
|
||||
this->address_str_.c_str(), value[0], length);
|
||||
return NAN;
|
||||
}
|
||||
|
||||
BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (svc->uuid == uuid)
|
||||
return svc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
return svc->get_characteristic(chr);
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (!svc->parsed)
|
||||
svc->parse_characteristics();
|
||||
for (auto *chr : svc->characteristics) {
|
||||
if (chr->handle == handle)
|
||||
return chr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) {
|
||||
auto *chr = this->get_characteristic(handle);
|
||||
if (chr != nullptr) {
|
||||
if (!chr->parsed)
|
||||
chr->parse_descriptors();
|
||||
for (auto &desc : chr->descriptors) {
|
||||
if (desc->uuid.get_uuid().uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
auto *ch = svc->get_characteristic(chr);
|
||||
if (ch == nullptr)
|
||||
return nullptr;
|
||||
return ch->get_descriptor(descr);
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
|
||||
espbt::ESPBTUUID::from_uint16(descr));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (!svc->parsed)
|
||||
svc->parse_characteristics();
|
||||
for (auto *chr : svc->characteristics) {
|
||||
if (!chr->parsed)
|
||||
chr->parse_descriptors();
|
||||
for (auto *desc : chr->descriptors) {
|
||||
if (desc->handle == handle)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
97
esphome/components/esp32_ble_client/ble_client_base.h
Normal file
97
esphome/components/esp32_ble_client/ble_client_base.h
Normal file
@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "ble_service.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatt_common_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
void on_scan_end() override {}
|
||||
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;
|
||||
void connect() override;
|
||||
void disconnect();
|
||||
void release_services();
|
||||
|
||||
bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
|
||||
|
||||
void set_address(uint64_t address) {
|
||||
this->address_ = address;
|
||||
if (address == 0) {
|
||||
memset(this->remote_bda_, 0, sizeof(this->remote_bda_));
|
||||
this->address_str_ = "";
|
||||
} else {
|
||||
this->address_str_ = str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t)(this->address_ >> 40) & 0xff,
|
||||
(uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff,
|
||||
(uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff,
|
||||
(uint8_t)(this->address_ >> 0) & 0xff);
|
||||
}
|
||||
}
|
||||
std::string address_str() const { return this->address_str_; }
|
||||
|
||||
BLEService *get_service(espbt::ESPBTUUID uuid);
|
||||
BLEService *get_service(uint16_t uuid);
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t handle);
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t handle);
|
||||
// Get the configuration descriptor for the given characteristic handle.
|
||||
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
||||
|
||||
float parse_char_value(uint8_t *value, uint16_t length);
|
||||
|
||||
int get_gattc_if() const { return this->gattc_if_; }
|
||||
uint8_t *get_remote_bda() { return this->remote_bda_; }
|
||||
esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; }
|
||||
void set_remote_addr_type(esp_ble_addr_type_t address_type) { this->remote_addr_type_ = address_type; }
|
||||
uint16_t get_conn_id() const { return this->conn_id_; }
|
||||
uint64_t get_address() const { return this->address_; }
|
||||
|
||||
uint8_t get_connection_index() const { return this->connection_index_; }
|
||||
|
||||
virtual void set_connection_type(espbt::ConnectionType ct) { this->connection_type_ = ct; }
|
||||
|
||||
protected:
|
||||
int gattc_if_;
|
||||
esp_bd_addr_t remote_bda_;
|
||||
esp_ble_addr_type_t remote_addr_type_;
|
||||
uint16_t conn_id_{0xFFFF};
|
||||
uint64_t address_{0};
|
||||
std::string address_str_{};
|
||||
uint8_t connection_index_;
|
||||
int16_t service_count_{0};
|
||||
uint16_t mtu_{23};
|
||||
espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
|
||||
|
||||
std::vector<BLEService *> services_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
25
esphome/components/esp32_ble_client/ble_descriptor.h
Normal file
25
esphome/components/esp32_ble_client/ble_descriptor.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLECharacteristic;
|
||||
|
||||
class BLEDescriptor {
|
||||
public:
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
|
||||
BLECharacteristic *characteristic;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user