mirror of
https://github.com/esphome/esphome.git
synced 2025-11-05 01:21:50 +00:00
Compare commits
249 Commits
copilot/fi
...
ble_tx_pow
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
635cb08e63 | ||
|
|
a92a08c2de | ||
|
|
75595b08be | ||
|
|
3c7aba0681 | ||
|
|
e5d1c30797 | ||
|
|
c171d13c8c | ||
|
|
65d63de9b6 | ||
|
|
9e712e4127 | ||
|
|
9007621fd7 | ||
|
|
c01a26607e | ||
|
|
f6ca70970f | ||
|
|
4dc11f05a7 | ||
|
|
5e508f7461 | ||
|
|
2aceb56606 | ||
|
|
d071a074ef | ||
|
|
7a459c8c20 | ||
|
|
aebd21958a | ||
|
|
c542db8bfe | ||
|
|
d9dcfe66ec | ||
|
|
8517c2e903 | ||
|
|
684384892a | ||
|
|
d560831d79 | ||
|
|
fcc3c8e1b6 | ||
|
|
959ffde60e | ||
|
|
07715dd50f | ||
|
|
03836ee2d2 | ||
|
|
50408d9abb | ||
|
|
0de7259428 | ||
|
|
d054709c2d | ||
|
|
da16887915 | ||
|
|
6da8ec8d55 | ||
|
|
d2752b38c9 | ||
|
|
6004367ee2 | ||
|
|
ecfeb8e4d3 | ||
|
|
456c31262d | ||
|
|
9f02575287 | ||
|
|
07bca6103f | ||
|
|
a58c3950bc | ||
|
|
8fe582309e | ||
|
|
b41a61c76e | ||
|
|
61a5023888 | ||
|
|
4396bc0d1a | ||
|
|
acfce581fa | ||
|
|
88303f39fa | ||
|
|
ca19959d7c | ||
|
|
9737b35579 | ||
|
|
be9c20c357 | ||
|
|
12ba4b142e | ||
|
|
c096c6934d | ||
|
|
17f787fc36 | ||
|
|
5cd9a86dcb | ||
|
|
83fe4b4ff3 | ||
|
|
94accd5abe | ||
|
|
3ca0015284 | ||
|
|
33eddb6035 | ||
|
|
72c58ae36d | ||
|
|
35411d199f | ||
|
|
d45944a9e2 | ||
|
|
86f306ba9e | ||
|
|
1b3b2f6e6f | ||
|
|
2adb993242 | ||
|
|
3ff5b4773b | ||
|
|
2cbf4f30f9 | ||
|
|
56b6dd31f1 | ||
|
|
fc1b49e87d | ||
|
|
0089619518 | ||
|
|
5a6db28f1d | ||
|
|
6819bbd8f8 | ||
|
|
634f687c3e | ||
|
|
e2a9b85924 | ||
|
|
4ccc6aee09 | ||
|
|
0eab908b0e | ||
|
|
3964f9794b | ||
|
|
a45137434b | ||
|
|
9b1ebdb6da | ||
|
|
5a1533bea9 | ||
|
|
0b50ef227b | ||
|
|
0e31bc1a67 | ||
|
|
8e67df8059 | ||
|
|
e1a0949ddb | ||
|
|
c5b2c8d971 | ||
|
|
a8775ba60b | ||
|
|
104906ca11 | ||
|
|
ad5f6f0cfe | ||
|
|
8356f7fcd3 | ||
|
|
225de226b0 | ||
|
|
2aaf951357 | ||
|
|
82718e62e7 | ||
|
|
fd07e1d979 | ||
|
|
4dab9c4400 | ||
|
|
7e23d865e6 | ||
|
|
8f118232e4 | ||
|
|
23554cda06 | ||
|
|
064385eac6 | ||
|
|
6502ed70de | ||
|
|
bb894c3e32 | ||
|
|
c5858b7032 | ||
|
|
99f57ecb73 | ||
|
|
cc6c892678 | ||
|
|
07a98d2525 | ||
|
|
e80f616366 | ||
|
|
46be877594 | ||
|
|
ac8b48a53c | ||
|
|
7fdbd8528a | ||
|
|
80970f972b | ||
|
|
3c7865cd6f | ||
|
|
3a6a66537c | ||
|
|
7118bea031 | ||
|
|
44bd8e5b54 | ||
|
|
efaeb91803 | ||
|
|
761c6c6685 | ||
|
|
1f55486896 | ||
|
|
6818439109 | ||
|
|
0a77423073 | ||
|
|
c29f8d0187 | ||
|
|
2a3f80a82c | ||
|
|
75f3adcd95 | ||
|
|
daf8ec36ab | ||
|
|
6c5632a0b3 | ||
|
|
abecc0e8d8 | ||
|
|
af9ecf3429 | ||
|
|
5fa84439c2 | ||
|
|
5d18afcd99 | ||
|
|
117cffd2b0 | ||
|
|
8ea1a3ed64 | ||
|
|
4f29b3c7aa | ||
|
|
3325592d67 | ||
|
|
0a3ee7d84e | ||
|
|
882237120e | ||
|
|
71efaf097b | ||
|
|
bd60dbb746 | ||
|
|
6b5e43ca72 | ||
|
|
8d61b1e8df | ||
|
|
9c897993bb | ||
|
|
93f9475105 | ||
|
|
95cd224e3e | ||
|
|
b7afeafda9 | ||
|
|
7922462bcf | ||
|
|
46d433775b | ||
|
|
7c4a54de90 | ||
|
|
c3f1596498 | ||
|
|
0d1949a61b | ||
|
|
6a8722f33e | ||
|
|
fff66072d4 | ||
|
|
1c2e1ab3e5 | ||
|
|
68ddd98f5f | ||
|
|
0dda3faed5 | ||
|
|
40c0c36179 | ||
|
|
6b7ced1970 | ||
|
|
ed2b76050b | ||
|
|
113813617d | ||
|
|
c3a209d3f4 | ||
|
|
7ffdaa1f06 | ||
|
|
3a857950bf | ||
|
|
0256e0005e | ||
|
|
c65af68e63 | ||
|
|
ef2121a215 | ||
|
|
bb40b7702d | ||
|
|
6c48f3d719 | ||
|
|
ff52869b4c | ||
|
|
82b7c1224c | ||
|
|
c14c4fb658 | ||
|
|
42aee53dde | ||
|
|
9aa21956c8 | ||
|
|
4c2874a32b | ||
|
|
45b88f2da9 | ||
|
|
8f53961496 | ||
|
|
5cf0e4d9dd | ||
|
|
b70983ed09 | ||
|
|
ffa89eb2d3 | ||
|
|
8b67d6dfec | ||
|
|
581b4ef5a1 | ||
|
|
da02f970d4 | ||
|
|
2fc0a11596 | ||
|
|
5a8f722316 | ||
|
|
279f56141e | ||
|
|
6bfe281d18 | ||
|
|
a1371aea37 | ||
|
|
d5c9c10b3b | ||
|
|
cef39e7c59 | ||
|
|
2b9e1ce315 | ||
|
|
ff9ddb9d68 | ||
|
|
676c51ffa0 | ||
|
|
7e4d09dbd8 | ||
|
|
58504662d8 | ||
|
|
83b69519dd | ||
|
|
d4d1a96f9b | ||
|
|
76fd104fb6 | ||
|
|
c4d1b1317a | ||
|
|
14bc83342f | ||
|
|
a1461c5293 | ||
|
|
73b2db8af5 | ||
|
|
a7a119f576 | ||
|
|
1ba76f5f2e | ||
|
|
37a9ad6a0d | ||
|
|
c0a62c0be1 | ||
|
|
bfb14e1cf9 | ||
|
|
1415e02e40 | ||
|
|
81f907e994 | ||
|
|
61008bc8a9 | ||
|
|
6d66ddd68d | ||
|
|
fc180251be | ||
|
|
ee1d4f27ef | ||
|
|
325ec0a0ae | ||
|
|
6071f4b02c | ||
|
|
083ac8ce8e | ||
|
|
4ceda31f32 | ||
|
|
5021cc6d5f | ||
|
|
2b3e546203 | ||
|
|
1642d34d29 | ||
|
|
8ceb1b9d60 | ||
|
|
d872c8a999 | ||
|
|
99125c045f | ||
|
|
860a5ef5c0 | ||
|
|
b01f03cc24 | ||
|
|
cfb22e33c9 | ||
|
|
96bbb58f34 | ||
|
|
3edd746c6c | ||
|
|
c308e03e92 | ||
|
|
bd2b3b9da5 | ||
|
|
d443a97dd8 | ||
|
|
58a088e06b | ||
|
|
49a46883ed | ||
|
|
bc03538e25 | ||
|
|
969034b61a | ||
|
|
06eb1b6014 | ||
|
|
589d00f17f | ||
|
|
68c0aa4d6d | ||
|
|
2fddb061e1 | ||
|
|
c85eb448e4 | ||
|
|
396c02c6de | ||
|
|
52c4509208 | ||
|
|
d29cae9c3b | ||
|
|
532e3e370f | ||
|
|
da573a217d | ||
|
|
a9b27d1966 | ||
|
|
0aa3c9685e | ||
|
|
93b28447ee | ||
|
|
52634dac2a | ||
|
|
64c94c1440 | ||
|
|
f7bf1ef52c | ||
|
|
fa8c5e880c | ||
|
|
27ba90ea95 | ||
|
|
469246b8d8 | ||
|
|
50f15735dc | ||
|
|
83d9c02a1b | ||
|
|
701e6099aa | ||
|
|
d59476d0e1 | ||
|
|
fbbb791b0d |
@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
|
||||
|
||||
## 2. Core Technologies & Stack
|
||||
|
||||
* **Languages:** Python (>=3.10), C++ (gnu++20)
|
||||
* **Languages:** Python (>=3.11), C++ (gnu++20)
|
||||
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
||||
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
||||
* **Configuration:** YAML.
|
||||
@@ -38,7 +38,7 @@ This document provides essential context for AI models interacting with this pro
|
||||
5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
|
||||
|
||||
* **Platform Support:**
|
||||
1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (S2, S3, C3, etc.) and both IDF and Arduino frameworks.
|
||||
1. **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (Original, C2, C3, C5, C6, H2, P4, S2, S3) with ESP-IDF framework. Arduino framework supports only a subset of the variants (Original, C3, S2, S3).
|
||||
2. **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
|
||||
3. **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
|
||||
4. **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
|
||||
@@ -60,7 +60,7 @@ This document provides essential context for AI models interacting with this pro
|
||||
├── __init__.py # Component configuration schema and code generation
|
||||
├── [component].h # C++ header file (if needed)
|
||||
├── [component].cpp # C++ implementation (if needed)
|
||||
└── [platform]/ # Platform-specific implementations
|
||||
└── [platform]/ # Platform-specific implementations
|
||||
├── __init__.py # Platform-specific configuration
|
||||
├── [platform].h # Platform C++ header
|
||||
└── [platform].cpp # Platform C++ implementation
|
||||
@@ -150,7 +150,8 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Configuration Validation:**
|
||||
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
|
||||
* **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
|
||||
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `cv.only_with_arduino`.
|
||||
* **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `esp32.only_on_variant(...)`, `cv.only_on_esp32`, `cv.only_on_esp8266`, `cv.only_on_rp2040`.
|
||||
* **Framework-Specific:** `cv.only_with_framework(...)`, `cv.only_with_arduino`, `cv.only_with_esp_idf`.
|
||||
* **Schema Extensions:**
|
||||
```python
|
||||
CONFIG_SCHEMA = cv.Schema({ ... })
|
||||
|
||||
@@ -1 +1 @@
|
||||
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273
|
||||
4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9
|
||||
|
||||
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@@ -22,7 +22,7 @@ runs:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
uses: actions/cache/restore@v4.2.4
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
||||
32
.github/workflows/auto-label-pr.yml
vendored
32
.github/workflows/auto-label-pr.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
@@ -105,7 +105,9 @@ jobs:
|
||||
|
||||
// Calculate data from PR files
|
||||
const changedFiles = prFiles.map(file => file.filename);
|
||||
const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||
const totalChanges = totalAdditions + totalDeletions;
|
||||
|
||||
console.log('Current labels:', currentLabels.join(', '));
|
||||
console.log('Changed files:', changedFiles.length);
|
||||
@@ -231,16 +233,21 @@ jobs:
|
||||
// Strategy: PR size detection
|
||||
async function detectPRSize() {
|
||||
const labels = new Set();
|
||||
const testChanges = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||
|
||||
const nonTestChanges = totalChanges - testChanges;
|
||||
|
||||
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
||||
labels.add('small-pr');
|
||||
return labels;
|
||||
}
|
||||
|
||||
const testAdditions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||
const testDeletions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||
|
||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||
|
||||
// Don't add too-big if mega-pr label is already present
|
||||
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
||||
labels.add('too-big');
|
||||
@@ -375,7 +382,7 @@ jobs:
|
||||
const labels = new Set();
|
||||
|
||||
// Check for missing tests
|
||||
if ((allLabels.has('new-component') || allLabels.has('new-platform')) && !allLabels.has('has-tests')) {
|
||||
if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
|
||||
labels.add('needs-tests');
|
||||
}
|
||||
|
||||
@@ -412,10 +419,13 @@ jobs:
|
||||
|
||||
// Too big message
|
||||
if (finalLabels.includes('too-big')) {
|
||||
const testChanges = prFiles
|
||||
const testAdditions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||
const nonTestChanges = totalChanges - testChanges;
|
||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||
const testDeletions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||
|
||||
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
||||
|
||||
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
|
||||
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
- "docker"
|
||||
# - "lint"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
|
||||
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@v4.2.4
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
id: restore-python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -156,12 +156,12 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5.4.3
|
||||
uses: codecov/codecov-action@v5.5.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Save Python virtual environment cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@v4.2.3
|
||||
uses: actions/cache/save@v4.2.4
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
with:
|
||||
# Fetch enough history to find the merge base
|
||||
fetch-depth: 2
|
||||
@@ -214,7 +214,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
python-version: "3.13"
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@v4.2.4
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -300,14 +300,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@v4.2.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
uses: actions/cache/restore@v4.2.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -374,7 +374,7 @@ jobs:
|
||||
sudo apt-get install libsdl2-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
matrix: ${{ steps.split.outputs.components }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Split components into 20 groups
|
||||
id: split
|
||||
run: |
|
||||
@@ -430,7 +430,7 @@ jobs:
|
||||
sudo apt-get install libsdl2-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -459,7 +459,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
24
.github/workflows/needs-docs.yml
vendored
24
.github/workflows/needs-docs.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Needs Docs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for needs-docs label
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number
|
||||
});
|
||||
const needsDocs = labels.find(label => label.name === 'needs-docs');
|
||||
if (needsDocs) {
|
||||
core.setFailed('Pull request needs docs');
|
||||
}
|
||||
18
.github/workflows/release.yml
vendored
18
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
@@ -102,12 +102,12 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@v3.5.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@v3.5.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -168,10 +168,10 @@ jobs:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
uses: actions/download-artifact@v5.0.0
|
||||
with:
|
||||
pattern: digests-*
|
||||
path: /tmp/digests
|
||||
@@ -182,13 +182,13 @@ jobs:
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@v3.5.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
if: matrix.registry == 'ghcr'
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@v3.5.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
30
.github/workflows/status-check-labels.yml
vendored
Normal file
30
.github/workflows/status-check-labels.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Status check labels
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check ${{ matrix.label }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
label:
|
||||
- needs-docs
|
||||
- merge-after-release
|
||||
steps:
|
||||
- name: Check for ${{ matrix.label }} label
|
||||
uses: actions/github-script@v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number
|
||||
});
|
||||
const hasLabel = labels.find(label => label.name === '${{ matrix.label }}');
|
||||
if (hasLabel) {
|
||||
core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}');
|
||||
}
|
||||
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.7
|
||||
rev: v0.12.10
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
22
CODEOWNERS
22
CODEOWNERS
@@ -40,11 +40,11 @@ esphome/components/analog_threshold/* @ianchi
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/anova/* @buxtronix
|
||||
esphome/components/apds9306/* @aodrenah
|
||||
esphome/components/api/* @OttoWinter
|
||||
esphome/components/api/* @esphome/core
|
||||
esphome/components/as5600/* @ammmze
|
||||
esphome/components/as5600/sensor/* @ammmze
|
||||
esphome/components/as7341/* @mrgnr
|
||||
esphome/components/async_tcp/* @OttoWinter
|
||||
esphome/components/async_tcp/* @esphome/core
|
||||
esphome/components/at581x/* @X-Ryl669
|
||||
esphome/components/atc_mithermometer/* @ahpohl
|
||||
esphome/components/atm90e26/* @danieltwagner
|
||||
@@ -69,7 +69,7 @@ esphome/components/bl0939/* @ziceva
|
||||
esphome/components/bl0940/* @tobias-
|
||||
esphome/components/bl0942/* @dbuezas @dwmw2
|
||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||
esphome/components/bluetooth_proxy/* @jesserockz
|
||||
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
||||
esphome/components/bme280_base/* @esphome/core
|
||||
esphome/components/bme280_spi/* @apbodrov
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
@@ -91,7 +91,7 @@ esphome/components/bytebuffer/* @clydebarrow
|
||||
esphome/components/camera/* @DT-art1 @bdraco
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @mreditor97
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/captive_portal/* @esphome/core
|
||||
esphome/components/ccs811/* @habbie
|
||||
esphome/components/cd74hc4067/* @asoehlke
|
||||
esphome/components/ch422g/* @clydebarrow @jesterret
|
||||
@@ -118,7 +118,7 @@ esphome/components/dallas_temp/* @ssieb
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
esphome/components/datetime/* @jesserockz @rfdarter
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/debug/* @esphome/core
|
||||
esphome/components/delonghi/* @grob6000
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dfrobot_sen0395/* @niklasweber
|
||||
@@ -144,9 +144,10 @@ esphome/components/es8156/* @kbx81
|
||||
esphome/components/es8311/* @kahrendt @kroimon
|
||||
esphome/components/es8388/* @P4uLT
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @Rapsssito @jesserockz
|
||||
esphome/components/esp32_ble_client/* @jesserockz
|
||||
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz
|
||||
esphome/components/esp32_ble_client/* @bdraco @jesserockz
|
||||
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
|
||||
esphome/components/esp32_ble_tracker/* @bdraco
|
||||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_can/* @Sympatron
|
||||
esphome/components/esp32_hosted/* @swoboda1337
|
||||
@@ -237,7 +238,7 @@ esphome/components/integration/* @OttoWinter
|
||||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/jsn_sr04t/* @Mafus1
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/json/* @esphome/core
|
||||
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
@@ -245,6 +246,7 @@ esphome/components/kuntze/* @ssieb
|
||||
esphome/components/lc709203f/* @ilikecake
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @regevbr @sebcaps
|
||||
esphome/components/ld2412/* @Rihan9
|
||||
esphome/components/ld2420/* @descipher
|
||||
esphome/components/ld2450/* @hareeshmu
|
||||
esphome/components/ld24xx/* @kbx81
|
||||
@@ -467,7 +469,7 @@ esphome/components/template/event/* @nohat
|
||||
esphome/components/template/fan/* @ssieb
|
||||
esphome/components/text/* @mauritskorse
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
esphome/components/time/* @esphome/core
|
||||
esphome/components/tlc5947/* @rnauber
|
||||
esphome/components/tlc5971/* @IJIJI
|
||||
esphome/components/tm1621/* @Philippe12
|
||||
@@ -511,7 +513,7 @@ esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||
esphome/components/watchdog/* @oarcher
|
||||
esphome/components/waveshare_epaper/* @clydebarrow
|
||||
esphome/components/web_server/ota/* @esphome/core
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/web_server_base/* @esphome/core
|
||||
esphome/components/web_server_idf/* @dentra
|
||||
esphome/components/weikai/* @DrCoolZic
|
||||
esphome/components/weikai_i2c/* @DrCoolZic
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.8.0-dev
|
||||
PROJECT_NUMBER = 2025.9.0-dev
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -90,7 +90,7 @@ def main():
|
||||
def run_command(*cmd, ignore_error: bool = False):
|
||||
print(f"$ {shlex.join(list(cmd))}")
|
||||
if not args.dry_run:
|
||||
rc = subprocess.call(list(cmd))
|
||||
rc = subprocess.call(list(cmd), close_fds=False)
|
||||
if rc != 0 and not ignore_error:
|
||||
print("Command failed")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -9,6 +9,7 @@ import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from typing import Protocol
|
||||
|
||||
import argcomplete
|
||||
|
||||
@@ -44,6 +45,7 @@ from esphome.const import (
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
from esphome.helpers import get_bool_env, indent, is_ip_address
|
||||
from esphome.log import AnsiFore, color, setup_log
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import (
|
||||
get_serial_ports,
|
||||
list_yaml_files,
|
||||
@@ -55,6 +57,23 @@ from esphome.util import (
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ArgsProtocol(Protocol):
|
||||
device: list[str] | None
|
||||
reset: bool
|
||||
username: str | None
|
||||
password: str | None
|
||||
client_id: str | None
|
||||
topic: str | None
|
||||
file: str | None
|
||||
no_logs: bool
|
||||
only_generate: bool
|
||||
show_secrets: bool
|
||||
dashboard: bool
|
||||
configuration: str
|
||||
name: str
|
||||
upload_speed: str | None
|
||||
|
||||
|
||||
def choose_prompt(options, purpose: str = None):
|
||||
if not options:
|
||||
raise EsphomeError(
|
||||
@@ -88,30 +107,57 @@ def choose_prompt(options, purpose: str = None):
|
||||
|
||||
|
||||
def choose_upload_log_host(
|
||||
default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
|
||||
):
|
||||
default: list[str] | str | None,
|
||||
check_default: str | None,
|
||||
show_ota: bool,
|
||||
show_mqtt: bool,
|
||||
show_api: bool,
|
||||
purpose: str | None = None,
|
||||
) -> list[str]:
|
||||
# Convert to list for uniform handling
|
||||
defaults = [default] if isinstance(default, str) else default or []
|
||||
|
||||
# If devices specified, resolve them
|
||||
if defaults:
|
||||
resolved: list[str] = []
|
||||
for device in defaults:
|
||||
if device == "SERIAL":
|
||||
serial_ports = get_serial_ports()
|
||||
if not serial_ports:
|
||||
_LOGGER.warning("No serial ports found, skipping SERIAL device")
|
||||
continue
|
||||
options = [
|
||||
(f"{port.path} ({port.description})", port.path)
|
||||
for port in serial_ports
|
||||
]
|
||||
resolved.append(choose_prompt(options, purpose=purpose))
|
||||
elif device == "OTA":
|
||||
if CORE.address and (
|
||||
(show_ota and "ota" in CORE.config)
|
||||
or (show_api and "api" in CORE.config)
|
||||
):
|
||||
resolved.append(CORE.address)
|
||||
elif show_mqtt and has_mqtt_logging():
|
||||
resolved.append("MQTT")
|
||||
else:
|
||||
resolved.append(device)
|
||||
if not resolved:
|
||||
_LOGGER.error("All specified devices: %s could not be resolved.", defaults)
|
||||
return resolved
|
||||
|
||||
# No devices specified, show interactive chooser
|
||||
options = [
|
||||
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
|
||||
]
|
||||
if default == "SERIAL":
|
||||
return choose_prompt(options, purpose=purpose)
|
||||
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||
if default == "OTA":
|
||||
return CORE.address
|
||||
if (
|
||||
show_mqtt
|
||||
and (mqtt_config := CORE.config.get(CONF_MQTT))
|
||||
and mqtt_logging_enabled(mqtt_config)
|
||||
):
|
||||
if show_mqtt and has_mqtt_logging():
|
||||
mqtt_config = CORE.config[CONF_MQTT]
|
||||
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
|
||||
if default == "OTA":
|
||||
return "MQTT"
|
||||
if default is not None:
|
||||
return default
|
||||
|
||||
if check_default is not None and check_default in [opt[1] for opt in options]:
|
||||
return check_default
|
||||
return choose_prompt(options, purpose=purpose)
|
||||
return [check_default]
|
||||
return [choose_prompt(options, purpose=purpose)]
|
||||
|
||||
|
||||
def mqtt_logging_enabled(mqtt_config):
|
||||
@@ -123,7 +169,14 @@ def mqtt_logging_enabled(mqtt_config):
|
||||
return log_topic.get(CONF_LEVEL, None) != "NONE"
|
||||
|
||||
|
||||
def get_port_type(port):
|
||||
def has_mqtt_logging() -> bool:
|
||||
"""Check if MQTT logging is available."""
|
||||
return (mqtt_config := CORE.config.get(CONF_MQTT)) and mqtt_logging_enabled(
|
||||
mqtt_config
|
||||
)
|
||||
|
||||
|
||||
def get_port_type(port: str) -> str:
|
||||
if port.startswith("/") or port.startswith("COM"):
|
||||
return "SERIAL"
|
||||
if port == "MQTT":
|
||||
@@ -131,7 +184,7 @@ def get_port_type(port):
|
||||
return "NETWORK"
|
||||
|
||||
|
||||
def run_miniterm(config, port, args):
|
||||
def run_miniterm(config: ConfigType, port: str, args) -> int:
|
||||
from aioesphomeapi import LogParser
|
||||
import serial
|
||||
|
||||
@@ -208,7 +261,7 @@ def wrap_to_code(name, comp):
|
||||
return wrapped
|
||||
|
||||
|
||||
def write_cpp(config):
|
||||
def write_cpp(config: ConfigType) -> int:
|
||||
if not get_bool_env(ENV_NOGITIGNORE):
|
||||
writer.write_gitignore()
|
||||
|
||||
@@ -216,7 +269,7 @@ def write_cpp(config):
|
||||
return write_cpp_file()
|
||||
|
||||
|
||||
def generate_cpp_contents(config):
|
||||
def generate_cpp_contents(config: ConfigType) -> None:
|
||||
_LOGGER.info("Generating C++ source...")
|
||||
|
||||
for name, component, conf in iter_component_configs(CORE.config):
|
||||
@@ -227,7 +280,7 @@ def generate_cpp_contents(config):
|
||||
CORE.flush_tasks()
|
||||
|
||||
|
||||
def write_cpp_file():
|
||||
def write_cpp_file() -> int:
|
||||
code_s = indent(CORE.cpp_main_section)
|
||||
writer.write_cpp(code_s)
|
||||
|
||||
@@ -238,7 +291,7 @@ def write_cpp_file():
|
||||
return 0
|
||||
|
||||
|
||||
def compile_program(args, config):
|
||||
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
from esphome import platformio_api
|
||||
|
||||
_LOGGER.info("Compiling app...")
|
||||
@@ -249,7 +302,9 @@ def compile_program(args, config):
|
||||
return 0 if idedata is not None else 1
|
||||
|
||||
|
||||
def upload_using_esptool(config, port, file, speed):
|
||||
def upload_using_esptool(
|
||||
config: ConfigType, port: str, file: str, speed: int
|
||||
) -> str | int:
|
||||
from esphome import platformio_api
|
||||
|
||||
first_baudrate = speed or config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
|
||||
@@ -314,7 +369,7 @@ def upload_using_esptool(config, port, file, speed):
|
||||
return run_esptool(115200)
|
||||
|
||||
|
||||
def upload_using_platformio(config, port):
|
||||
def upload_using_platformio(config: ConfigType, port: str):
|
||||
from esphome import platformio_api
|
||||
|
||||
upload_args = ["-t", "upload", "-t", "nobuild"]
|
||||
@@ -323,7 +378,7 @@ def upload_using_platformio(config, port):
|
||||
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
|
||||
|
||||
|
||||
def check_permissions(port):
|
||||
def check_permissions(port: str):
|
||||
if os.name == "posix" and get_port_type(port) == "SERIAL":
|
||||
# Check if we can open selected serial port
|
||||
if not os.access(port, os.F_OK):
|
||||
@@ -341,7 +396,7 @@ def check_permissions(port):
|
||||
)
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str:
|
||||
try:
|
||||
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||
if getattr(module, "upload_program")(config, args, host):
|
||||
@@ -356,7 +411,7 @@ def upload_program(config, args, host):
|
||||
return upload_using_esptool(config, host, file, args.upload_speed)
|
||||
|
||||
if CORE.target_platform in (PLATFORM_RP2040):
|
||||
return upload_using_platformio(config, args.device)
|
||||
return upload_using_platformio(config, host)
|
||||
|
||||
if CORE.is_libretiny:
|
||||
return upload_using_platformio(config, host)
|
||||
@@ -379,9 +434,12 @@ def upload_program(config, args, host):
|
||||
remote_port = int(ota_conf[CONF_PORT])
|
||||
password = ota_conf.get(CONF_PASSWORD, "")
|
||||
|
||||
# Check if we should use MQTT for address resolution
|
||||
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
|
||||
devices: list[str] = args.device or []
|
||||
if (
|
||||
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
||||
and (not args.device or args.device in ("MQTT", "OTA"))
|
||||
and (not devices or host in ("MQTT", "OTA"))
|
||||
and (
|
||||
((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
|
||||
or get_port_type(host) == "MQTT"
|
||||
@@ -399,24 +457,29 @@ def upload_program(config, args, host):
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
|
||||
|
||||
def show_logs(config, args, port):
|
||||
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
|
||||
if "logger" not in config:
|
||||
raise EsphomeError("Logger is not configured!")
|
||||
|
||||
port = devices[0]
|
||||
|
||||
if get_port_type(port) == "SERIAL":
|
||||
check_permissions(port)
|
||||
return run_miniterm(config, port, args)
|
||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||
addresses_to_use = devices
|
||||
if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
|
||||
from esphome import mqtt
|
||||
|
||||
port = mqtt.get_esphome_device_ip(
|
||||
mqtt_address = mqtt.get_esphome_device_ip(
|
||||
config, args.username, args.password, args.client_id
|
||||
)[0]
|
||||
addresses_to_use = [mqtt_address]
|
||||
|
||||
from esphome.components.api.client import run_logs
|
||||
|
||||
return run_logs(config, port)
|
||||
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
||||
return run_logs(config, addresses_to_use)
|
||||
if get_port_type(port) in ("NETWORK", "MQTT") and "mqtt" in config:
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.show_logs(
|
||||
@@ -426,7 +489,7 @@ def show_logs(config, args, port):
|
||||
raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
|
||||
|
||||
|
||||
def clean_mqtt(config, args):
|
||||
def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.clear_topic(
|
||||
@@ -434,13 +497,13 @@ def clean_mqtt(config, args):
|
||||
)
|
||||
|
||||
|
||||
def command_wizard(args):
|
||||
def command_wizard(args: ArgsProtocol) -> int | None:
|
||||
from esphome import wizard
|
||||
|
||||
return wizard.wizard(args.configuration)
|
||||
|
||||
|
||||
def command_config(args, config):
|
||||
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
if not CORE.verbose:
|
||||
config = strip_default_ids(config)
|
||||
output = yaml_util.dump(config, args.show_secrets)
|
||||
@@ -455,7 +518,7 @@ def command_config(args, config):
|
||||
return 0
|
||||
|
||||
|
||||
def command_vscode(args):
|
||||
def command_vscode(args: ArgsProtocol) -> int | None:
|
||||
from esphome import vscode
|
||||
|
||||
logging.disable(logging.INFO)
|
||||
@@ -463,7 +526,7 @@ def command_vscode(args):
|
||||
vscode.read_config(args)
|
||||
|
||||
|
||||
def command_compile(args, config):
|
||||
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
@@ -477,8 +540,9 @@ def command_compile(args, config):
|
||||
return 0
|
||||
|
||||
|
||||
def command_upload(args, config):
|
||||
port = choose_upload_log_host(
|
||||
def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
# Get devices, resolving special identifiers like OTA
|
||||
devices = choose_upload_log_host(
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
@@ -486,14 +550,22 @@ def command_upload(args, config):
|
||||
show_api=False,
|
||||
purpose="uploading",
|
||||
)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
return 0
|
||||
|
||||
# Try each device until one succeeds
|
||||
exit_code = 1
|
||||
for device in devices:
|
||||
_LOGGER.info("Uploading to %s", device)
|
||||
exit_code = upload_program(config, args, device)
|
||||
if exit_code == 0:
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
return 0
|
||||
if len(devices) > 1:
|
||||
_LOGGER.warning("Failed to upload to %s", device)
|
||||
|
||||
return exit_code
|
||||
|
||||
|
||||
def command_discover(args, config):
|
||||
def command_discover(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
if "mqtt" in config:
|
||||
from esphome import mqtt
|
||||
|
||||
@@ -502,8 +574,9 @@ def command_discover(args, config):
|
||||
raise EsphomeError("No discover method configured (mqtt)")
|
||||
|
||||
|
||||
def command_logs(args, config):
|
||||
port = choose_upload_log_host(
|
||||
def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
# Get devices, resolving special identifiers like OTA
|
||||
devices = choose_upload_log_host(
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=False,
|
||||
@@ -511,10 +584,10 @@ def command_logs(args, config):
|
||||
show_api=True,
|
||||
purpose="logging",
|
||||
)
|
||||
return show_logs(config, args, port)
|
||||
return show_logs(config, args, devices)
|
||||
|
||||
|
||||
def command_run(args, config):
|
||||
def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
@@ -531,7 +604,8 @@ def command_run(args, config):
|
||||
program_path = idedata.raw["prog_path"]
|
||||
return run_external_process(program_path)
|
||||
|
||||
port = choose_upload_log_host(
|
||||
# Get devices, resolving special identifiers like OTA
|
||||
devices = choose_upload_log_host(
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
show_ota=True,
|
||||
@@ -539,39 +613,53 @@ def command_run(args, config):
|
||||
show_api=True,
|
||||
purpose="uploading",
|
||||
)
|
||||
exit_code = upload_program(config, args, port)
|
||||
if exit_code != 0:
|
||||
|
||||
# Try each device for upload until one succeeds
|
||||
successful_device: str | None = None
|
||||
for device in devices:
|
||||
_LOGGER.info("Uploading to %s", device)
|
||||
exit_code = upload_program(config, args, device)
|
||||
if exit_code == 0:
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
successful_device = device
|
||||
break
|
||||
if len(devices) > 1:
|
||||
_LOGGER.warning("Failed to upload to %s", device)
|
||||
|
||||
if successful_device is None:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully uploaded program.")
|
||||
|
||||
if args.no_logs:
|
||||
return 0
|
||||
port = choose_upload_log_host(
|
||||
default=args.device,
|
||||
check_default=port,
|
||||
|
||||
# For logs, prefer the device we successfully uploaded to
|
||||
devices = choose_upload_log_host(
|
||||
default=successful_device,
|
||||
check_default=successful_device,
|
||||
show_ota=False,
|
||||
show_mqtt=True,
|
||||
show_api=True,
|
||||
purpose="logging",
|
||||
)
|
||||
return show_logs(config, args, port)
|
||||
return show_logs(config, args, devices)
|
||||
|
||||
|
||||
def command_clean_mqtt(args, config):
|
||||
def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
return clean_mqtt(config, args)
|
||||
|
||||
|
||||
def command_mqtt_fingerprint(args, config):
|
||||
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.get_fingerprint(config)
|
||||
|
||||
|
||||
def command_version(args):
|
||||
def command_version(args: ArgsProtocol) -> int | None:
|
||||
safe_print(f"Version: {const.__version__}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_clean(args, config):
|
||||
def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
try:
|
||||
writer.clean_build()
|
||||
except OSError as err:
|
||||
@@ -581,13 +669,13 @@ def command_clean(args, config):
|
||||
return 0
|
||||
|
||||
|
||||
def command_dashboard(args):
|
||||
def command_dashboard(args: ArgsProtocol) -> int | None:
|
||||
from esphome.dashboard import dashboard
|
||||
|
||||
return dashboard.start_dashboard(args)
|
||||
|
||||
|
||||
def command_update_all(args):
|
||||
def command_update_all(args: ArgsProtocol) -> int | None:
|
||||
import click
|
||||
|
||||
success = {}
|
||||
@@ -634,7 +722,7 @@ def command_update_all(args):
|
||||
return failed
|
||||
|
||||
|
||||
def command_idedata(args, config):
|
||||
def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
import json
|
||||
|
||||
from esphome import platformio_api
|
||||
@@ -650,7 +738,7 @@ def command_idedata(args, config):
|
||||
return 0
|
||||
|
||||
|
||||
def command_rename(args, config):
|
||||
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
for c in args.name:
|
||||
if c not in ALLOWED_NAME_CHARS:
|
||||
print(
|
||||
@@ -767,6 +855,12 @@ POST_CONFIG_ACTIONS = {
|
||||
"discover": command_discover,
|
||||
}
|
||||
|
||||
SIMPLE_CONFIG_ACTIONS = [
|
||||
"clean",
|
||||
"clean-mqtt",
|
||||
"config",
|
||||
]
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
options_parser = argparse.ArgumentParser(add_help=False)
|
||||
@@ -854,7 +948,8 @@ def parse_args(argv):
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
action="append",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--upload_speed",
|
||||
@@ -876,7 +971,8 @@ def parse_args(argv):
|
||||
)
|
||||
parser_logs.add_argument(
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
action="append",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||
)
|
||||
parser_logs.add_argument(
|
||||
"--reset",
|
||||
@@ -905,7 +1001,8 @@ def parse_args(argv):
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--device",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
|
||||
action="append",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--upload_speed",
|
||||
@@ -1032,6 +1129,13 @@ def parse_args(argv):
|
||||
arguments = argv[1:]
|
||||
|
||||
argcomplete.autocomplete(parser)
|
||||
|
||||
if len(arguments) > 0 and arguments[0] in SIMPLE_CONFIG_ACTIONS:
|
||||
args, unknown_args = parser.parse_known_args(arguments)
|
||||
if unknown_args:
|
||||
_LOGGER.warning("Ignored unrecognized arguments: %s", unknown_args)
|
||||
return args
|
||||
|
||||
return parser.parse_args(arguments)
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from esphome.const import (
|
||||
CONF_EQUATION,
|
||||
CONF_HUMIDITY,
|
||||
CONF_TEMPERATURE,
|
||||
ICON_WATER,
|
||||
DEVICE_CLASS_ABSOLUTE_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_GRAMS_PER_CUBIC_METER,
|
||||
)
|
||||
@@ -27,8 +27,8 @@ EQUATION = {
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER,
|
||||
icon=ICON_WATER,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_ABSOLUTE_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
|
||||
@@ -36,6 +36,7 @@ from esphome.const import (
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
@@ -51,6 +52,20 @@ CONF_POWER_GAIN = "power_gain"
|
||||
|
||||
CONF_NEUTRAL = "neutral"
|
||||
|
||||
# Tuple of power channel phases
|
||||
POWER_PHASES = (CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C)
|
||||
|
||||
# Tuple of sensor types that can be configured for power channels
|
||||
POWER_SENSOR_TYPES = (
|
||||
CONF_CURRENT,
|
||||
CONF_VOLTAGE,
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
)
|
||||
|
||||
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NeutralChannel),
|
||||
@@ -150,7 +165,64 @@ POWER_CHANNEL_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
|
||||
def prefix_sensor_name(
|
||||
sensor_conf: ConfigType,
|
||||
channel_name: str,
|
||||
channel_config: ConfigType,
|
||||
sensor_type: str,
|
||||
) -> None:
|
||||
"""Helper to prefix sensor name with channel name.
|
||||
|
||||
Args:
|
||||
sensor_conf: The sensor configuration (dict or string)
|
||||
channel_name: The channel name to prefix with
|
||||
channel_config: The channel configuration to update
|
||||
sensor_type: The sensor type key in the channel config
|
||||
"""
|
||||
if isinstance(sensor_conf, dict) and CONF_NAME in sensor_conf:
|
||||
sensor_name = sensor_conf[CONF_NAME]
|
||||
if sensor_name and not sensor_name.startswith(channel_name):
|
||||
sensor_conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||
elif isinstance(sensor_conf, str):
|
||||
# Simple value case - convert to dict with prefixed name
|
||||
channel_config[sensor_type] = {CONF_NAME: f"{channel_name} {sensor_conf}"}
|
||||
|
||||
|
||||
def process_channel_sensors(
|
||||
config: ConfigType, channel_key: str, sensor_types: tuple
|
||||
) -> None:
|
||||
"""Process sensors for a channel and prefix their names.
|
||||
|
||||
Args:
|
||||
config: The main configuration
|
||||
channel_key: The channel key (e.g., CONF_PHASE_A, CONF_NEUTRAL)
|
||||
sensor_types: Tuple of sensor types to process for this channel
|
||||
"""
|
||||
if not (channel_config := config.get(channel_key)) or not (
|
||||
channel_name := channel_config.get(CONF_NAME)
|
||||
):
|
||||
return
|
||||
|
||||
for sensor_type in sensor_types:
|
||||
if sensor_conf := channel_config.get(sensor_type):
|
||||
prefix_sensor_name(sensor_conf, channel_name, channel_config, sensor_type)
|
||||
|
||||
|
||||
def preprocess_channels(config: ConfigType) -> ConfigType:
|
||||
"""Preprocess channel configurations to add channel name prefix to sensor names."""
|
||||
# Process power channels
|
||||
for channel in POWER_PHASES:
|
||||
process_channel_sensors(config, channel, POWER_SENSOR_TYPES)
|
||||
|
||||
# Process neutral channel
|
||||
process_channel_sensors(config, CONF_NEUTRAL, (CONF_CURRENT,))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
preprocess_channels,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7880),
|
||||
@@ -167,7 +239,7 @@ CONFIG_SCHEMA = (
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x38))
|
||||
.extend(i2c.i2c_device_schema(0x38)),
|
||||
)
|
||||
|
||||
|
||||
@@ -188,15 +260,7 @@ async def neutral_channel(config):
|
||||
async def power_channel(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
for sensor_type in [
|
||||
CONF_CURRENT,
|
||||
CONF_VOLTAGE,
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
]:
|
||||
for sensor_type in POWER_SENSOR_TYPES:
|
||||
if conf := config.get(sensor_type):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{sensor_type}")(sens))
|
||||
@@ -216,44 +280,6 @@ async def power_channel(config):
|
||||
return var
|
||||
|
||||
|
||||
def final_validate(config):
|
||||
for channel in [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]:
|
||||
if channel := config.get(channel):
|
||||
channel_name = channel.get(CONF_NAME)
|
||||
|
||||
for sensor_type in [
|
||||
CONF_CURRENT,
|
||||
CONF_VOLTAGE,
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
]:
|
||||
if conf := channel.get(sensor_type):
|
||||
sensor_name = conf.get(CONF_NAME)
|
||||
if (
|
||||
sensor_name
|
||||
and channel_name
|
||||
and not sensor_name.startswith(channel_name)
|
||||
):
|
||||
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||
|
||||
if channel := config.get(CONF_NEUTRAL):
|
||||
channel_name = channel.get(CONF_NAME)
|
||||
if conf := channel.get(CONF_CURRENT):
|
||||
sensor_name = conf.get(CONF_NAME)
|
||||
if (
|
||||
sensor_name
|
||||
and channel_name
|
||||
and not sensor_name.startswith(channel_name)
|
||||
):
|
||||
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
|
||||
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
||||
uint8_t rev_newaddress = ~newaddress;
|
||||
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
|
||||
data[4] = calc_crc8_(data, 4);
|
||||
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
||||
if (!this->write_bytes(REG_ADDRESS, data)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->status_set_warning();
|
||||
@@ -109,7 +109,7 @@ bool AGS10Component::set_zero_point_with_current_resistance() { return this->set
|
||||
|
||||
bool AGS10Component::set_zero_point_with(uint16_t value) {
|
||||
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
|
||||
data[4] = calc_crc8_(data, 4);
|
||||
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
||||
if (!this->write_bytes(REG_CALIBRATION, data)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->status_set_warning();
|
||||
@@ -184,7 +184,7 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
||||
auto res = *data;
|
||||
auto crc_byte = res[len];
|
||||
|
||||
if (crc_byte != calc_crc8_(res, len)) {
|
||||
if (crc_byte != crc8(res.data(), len, 0xFF, 0x31, true)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
||||
return optional<std::array<uint8_t, N>>();
|
||||
@@ -192,20 +192,5 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> dat, uint8_t num) {
|
||||
uint8_t i, byte1, crc = 0xFF;
|
||||
for (byte1 = 0; byte1 < num; byte1++) {
|
||||
crc ^= (dat[byte1]);
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x31;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
} // namespace ags10
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ags10 {
|
||||
@@ -99,16 +99,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
* Read, checks and returns data from the sensor.
|
||||
*/
|
||||
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
||||
|
||||
/**
|
||||
* Calculates CRC8 value.
|
||||
*
|
||||
* CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1)
|
||||
*
|
||||
* @param[in] dat the data buffer
|
||||
* @param num number of bytes in the buffer
|
||||
*/
|
||||
template<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
|
||||
};
|
||||
|
||||
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
|
||||
@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
@@ -29,22 +29,6 @@ namespace am2315c {
|
||||
|
||||
static const char *const TAG = "am2315c";
|
||||
|
||||
uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) {
|
||||
uint8_t crc = 0xFF;
|
||||
while (len--) {
|
||||
crc ^= *data++;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (crc & 0x80) {
|
||||
crc <<= 1;
|
||||
crc ^= 0x31;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool AM2315C::reset_register_(uint8_t reg) {
|
||||
// code based on demo code sent by www.aosong.com
|
||||
// no further documentation.
|
||||
@@ -86,7 +70,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
|
||||
humidity = raw * 9.5367431640625e-5;
|
||||
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||
temperature = raw * 1.9073486328125e-4 - 50;
|
||||
return this->crc8_(data, 6) == data[6];
|
||||
return crc8(data, 6, 0xFF, 0x31, true) == data[6];
|
||||
}
|
||||
|
||||
void AM2315C::setup() {
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
// SOFTWARE.
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am2315c {
|
||||
@@ -39,7 +39,6 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
protected:
|
||||
uint8_t crc8_(uint8_t *data, uint8_t len);
|
||||
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
||||
bool reset_register_(uint8_t reg);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from esphome.core import CORE, coroutine_with_priority
|
||||
DOMAIN = "api"
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["socket"]
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
api_ns = cg.esphome_ns.namespace("api")
|
||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
|
||||
@@ -321,6 +321,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||
)
|
||||
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||
serv = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
cg.add(var.set_service("esphome.tag_scanned"))
|
||||
|
||||
@@ -1438,11 +1438,11 @@ message BluetoothLERawAdvertisementsResponse {
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
option (no_delay) = true;
|
||||
|
||||
repeated BluetoothLERawAdvertisement advertisements = 1;
|
||||
repeated BluetoothLERawAdvertisement advertisements = 1 [(fixed_array_with_length_define) = "BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE"];
|
||||
}
|
||||
|
||||
enum BluetoothDeviceRequestType {
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0 [deprecated = true]; // V1 removed, use V3 variants
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
||||
|
||||
@@ -289,16 +289,26 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
|
||||
return 0; // Doesn't fit
|
||||
}
|
||||
|
||||
// Allocate buffer space - pass payload size, allocation functions add header/footer space
|
||||
ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
|
||||
: conn->allocate_batch_message_buffer(calculated_size);
|
||||
|
||||
// Get buffer size after allocation (which includes header padding)
|
||||
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
|
||||
size_t size_before_encode = shared_buf.size();
|
||||
|
||||
if (is_single || conn->flags_.batch_first_message) {
|
||||
// Single message or first batch message
|
||||
conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size);
|
||||
if (conn->flags_.batch_first_message) {
|
||||
conn->flags_.batch_first_message = false;
|
||||
}
|
||||
} else {
|
||||
// Batch message second or later
|
||||
// Add padding for previous message footer + this message header
|
||||
size_t current_size = shared_buf.size();
|
||||
shared_buf.reserve(current_size + total_calculated_size);
|
||||
shared_buf.resize(current_size + footer_size + header_padding);
|
||||
}
|
||||
|
||||
// Encode directly into buffer
|
||||
msg.encode(buffer);
|
||||
size_t size_before_encode = shared_buf.size();
|
||||
msg.encode({&shared_buf});
|
||||
|
||||
// Calculate actual encoded size (not including header that was already added)
|
||||
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
||||
@@ -455,9 +465,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
||||
resp.cold_white = values.get_cold_white();
|
||||
resp.warm_white = values.get_warm_white();
|
||||
if (light->supports_effects()) {
|
||||
// get_effect_name() returns temporary std::string - must store it
|
||||
std::string effect_name = light->get_effect_name();
|
||||
resp.set_effect(StringRef(effect_name));
|
||||
resp.set_effect(light->get_effect_name_ref());
|
||||
}
|
||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -1415,9 +1423,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||
|
||||
// get_compilation_time() returns temporary std::string - must store it
|
||||
std::string compilation_time = App.get_compilation_time();
|
||||
resp.set_compilation_time(StringRef(compilation_time));
|
||||
resp.set_compilation_time(App.get_compilation_time_ref());
|
||||
|
||||
// Compile-time StringRef constants for manufacturers
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
@@ -1620,14 +1626,6 @@ bool APIConnection::schedule_batch_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
|
||||
|
||||
ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
|
||||
ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
|
||||
this->flags_.batch_first_message = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
void APIConnection::process_batch_() {
|
||||
// Ensure PacketInfo remains trivially destructible for our placement new approach
|
||||
static_assert(std::is_trivially_destructible<PacketInfo>::value,
|
||||
@@ -1735,7 +1733,7 @@ void APIConnection::process_batch_() {
|
||||
}
|
||||
remaining_size -= payload_size;
|
||||
// Calculate where the next message's header padding will start
|
||||
// Current buffer size + footer space (that prepare_message_buffer will add for this message)
|
||||
// Current buffer size + footer space for this message
|
||||
current_offset = shared_buf.size() + footer_size;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HO
|
||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
||||
#endif
|
||||
|
||||
class APIConnection : public APIServerConnection {
|
||||
class APIConnection final : public APIServerConnection {
|
||||
public:
|
||||
friend class APIServer;
|
||||
friend class ListEntitiesIterator;
|
||||
@@ -252,44 +252,21 @@ class APIConnection : public APIServerConnection {
|
||||
|
||||
// Get header padding size - used for both reserve and insert
|
||||
uint8_t header_padding = this->helper_->frame_header_padding();
|
||||
|
||||
// Get shared buffer from parent server
|
||||
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
||||
this->prepare_first_message_buffer(shared_buf, header_padding,
|
||||
reserve_size + header_padding + this->helper_->frame_footer_size());
|
||||
return {&shared_buf};
|
||||
}
|
||||
|
||||
void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
|
||||
shared_buf.clear();
|
||||
// Reserve space for header padding + message + footer
|
||||
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
||||
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
||||
shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
|
||||
shared_buf.reserve(total_size);
|
||||
// Resize to add header padding so message encoding starts at the correct position
|
||||
shared_buf.resize(header_padding);
|
||||
return {&shared_buf};
|
||||
}
|
||||
|
||||
// Prepare buffer for next message in batch
|
||||
ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
|
||||
// Get reference to shared buffer (it maintains state between batch messages)
|
||||
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
||||
|
||||
if (is_first_message) {
|
||||
shared_buf.clear();
|
||||
}
|
||||
|
||||
size_t current_size = shared_buf.size();
|
||||
|
||||
// Calculate padding to add:
|
||||
// - First message: just header padding
|
||||
// - Subsequent messages: footer for previous message + header padding for this message
|
||||
size_t padding_to_add = is_first_message
|
||||
? this->helper_->frame_header_padding()
|
||||
: this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
|
||||
|
||||
// Reserve space for padding + message
|
||||
shared_buf.reserve(current_size + padding_to_add + message_size);
|
||||
|
||||
// Resize to add the padding bytes
|
||||
shared_buf.resize(current_size + padding_to_add);
|
||||
|
||||
return {&shared_buf};
|
||||
}
|
||||
|
||||
bool try_to_clear_buffer(bool log_out_of_space);
|
||||
@@ -297,10 +274,6 @@ class APIConnection : public APIServerConnection {
|
||||
|
||||
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
|
||||
|
||||
// Buffer allocator methods for batch processing
|
||||
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
|
||||
ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size);
|
||||
|
||||
protected:
|
||||
// Helper function to handle authentication completion
|
||||
void complete_authentication_();
|
||||
@@ -328,9 +301,17 @@ class APIConnection : public APIServerConnection {
|
||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||
// Set common fields that are shared by all entity types
|
||||
msg.key = entity->get_object_id_hash();
|
||||
// IMPORTANT: get_object_id() may return a temporary std::string
|
||||
std::string object_id = entity->get_object_id();
|
||||
msg.set_object_id(StringRef(object_id));
|
||||
// Try to use static reference first to avoid allocation
|
||||
StringRef static_ref = entity->get_object_id_ref_for_api_();
|
||||
// Store dynamic string outside the if-else to maintain lifetime
|
||||
std::string object_id;
|
||||
if (!static_ref.empty()) {
|
||||
msg.set_object_id(static_ref);
|
||||
} else {
|
||||
// Dynamic case - need to allocate
|
||||
object_id = entity->get_object_id();
|
||||
msg.set_object_id(StringRef(object_id));
|
||||
}
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.set_name(entity->get_name());
|
||||
|
||||
@@ -156,7 +156,9 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
|
||||
}
|
||||
|
||||
// Try to send directly if no buffered data
|
||||
ssize_t sent = this->socket_->writev(iov, iovcnt);
|
||||
// Optimize for single iovec case (common for plaintext API)
|
||||
ssize_t sent =
|
||||
(iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
|
||||
|
||||
if (sent == -1) {
|
||||
APIError err = this->handle_socket_write_error_();
|
||||
|
||||
@@ -104,9 +104,9 @@ class APIFrameHelper {
|
||||
// The buffer contains all messages with appropriate padding before each
|
||||
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
||||
// Get the frame header padding required by this protocol
|
||||
virtual uint8_t frame_header_padding() = 0;
|
||||
uint8_t frame_header_padding() const { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
virtual uint8_t frame_footer_size() = 0;
|
||||
uint8_t frame_footer_size() const { return frame_footer_size_; }
|
||||
// Check if socket has data ready to read
|
||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
class APINoiseFrameHelper : public APIFrameHelper {
|
||||
class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
public:
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
|
||||
const ClientInfo *client_info)
|
||||
@@ -25,10 +25,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||
// Get the frame header padding required by this protocol
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
|
||||
protected:
|
||||
APIError state_action_();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||
public:
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
||||
: APIFrameHelper(std::move(socket), client_info) {
|
||||
@@ -22,9 +22,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
|
||||
protected:
|
||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||
|
||||
@@ -30,6 +30,7 @@ extend google.protobuf.FieldOptions {
|
||||
optional bool no_zero_copy = 50008 [default=false];
|
||||
optional bool fixed_array_skip_zero = 50009 [default=false];
|
||||
optional string fixed_array_size_define = 50010;
|
||||
optional string fixed_array_with_length_define = 50011;
|
||||
|
||||
// container_pointer: Zero-copy optimization for repeated fields.
|
||||
//
|
||||
|
||||
@@ -1843,12 +1843,14 @@ void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->data_len);
|
||||
}
|
||||
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->advertisements) {
|
||||
buffer.encode_message(1, it, true);
|
||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||
buffer.encode_message(1, this->advertisements[i], true);
|
||||
}
|
||||
}
|
||||
void BluetoothLERawAdvertisementsResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_repeated_message(1, this->advertisements);
|
||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||
size.add_message_object_force(1, this->advertisements[i]);
|
||||
}
|
||||
}
|
||||
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1534,9 +1534,9 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
|
||||
}
|
||||
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse");
|
||||
for (const auto &it : this->advertisements) {
|
||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||
out.append(" advertisements: ");
|
||||
it.dump_to(out);
|
||||
this->advertisements[i].dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
||||
async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
"""Run the logs command in the event loop."""
|
||||
conf = config["api"]
|
||||
name = config["esphome"]["name"]
|
||||
@@ -39,13 +39,21 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
||||
noise_psk: str | None = None
|
||||
if (encryption := conf.get(CONF_ENCRYPTION)) and (key := encryption.get(CONF_KEY)):
|
||||
noise_psk = key
|
||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||
|
||||
if len(addresses) == 1:
|
||||
_LOGGER.info("Starting log output from %s using esphome API", addresses[0])
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Starting log output from %s using esphome API", " or ".join(addresses)
|
||||
)
|
||||
|
||||
cli = APIClient(
|
||||
address,
|
||||
addresses[0], # Primary address for compatibility
|
||||
port,
|
||||
password,
|
||||
client_info=f"ESPHome Logs {__version__}",
|
||||
noise_psk=noise_psk,
|
||||
addresses=addresses, # Pass all addresses for automatic retry
|
||||
)
|
||||
dashboard = CORE.dashboard
|
||||
|
||||
@@ -66,7 +74,7 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
||||
await stop()
|
||||
|
||||
|
||||
def run_logs(config: dict[str, Any], address: str) -> None:
|
||||
def run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
"""Run the logs command."""
|
||||
with contextlib.suppress(KeyboardInterrupt):
|
||||
asyncio.run(async_run_logs(config, address))
|
||||
asyncio.run(async_run_logs(config, addresses))
|
||||
|
||||
@@ -56,6 +56,14 @@ class CustomAPIDevice {
|
||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
#else
|
||||
template<typename T, typename... Ts>
|
||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||
static_assert(
|
||||
sizeof(T) == 0,
|
||||
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Register a custom native API service that will show up in Home Assistant.
|
||||
@@ -81,6 +89,12 @@ class CustomAPIDevice {
|
||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
#else
|
||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||
static_assert(
|
||||
sizeof(T) == 0,
|
||||
"register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
@@ -135,6 +149,22 @@ class CustomAPIDevice {
|
||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
static_assert(sizeof(T) == 0,
|
||||
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
||||
"of your YAML configuration");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id,
|
||||
const std::string &attribute = "") {
|
||||
static_assert(sizeof(T) == 0,
|
||||
"subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section "
|
||||
"of your YAML configuration");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
@@ -222,6 +252,28 @@ class CustomAPIDevice {
|
||||
}
|
||||
global_api_server->send_homeassistant_service_call(resp);
|
||||
}
|
||||
#else
|
||||
template<typename T = void> void call_homeassistant_service(const std::string &service_name) {
|
||||
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
|
||||
"section of your YAML configuration");
|
||||
}
|
||||
|
||||
template<typename T = void>
|
||||
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' "
|
||||
"section of your YAML configuration");
|
||||
}
|
||||
|
||||
template<typename T = void> void fire_homeassistant_event(const std::string &event_name) {
|
||||
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
|
||||
"section of your YAML configuration");
|
||||
}
|
||||
|
||||
template<typename T = void>
|
||||
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||
static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' "
|
||||
"section of your YAML configuration");
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -8,74 +8,70 @@ namespace esphome::api {
|
||||
static const char *const TAG = "api.proto";
|
||||
|
||||
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
uint32_t i = 0;
|
||||
bool error = false;
|
||||
while (i < length) {
|
||||
const uint8_t *ptr = buffer;
|
||||
const uint8_t *end = buffer + length;
|
||||
|
||||
while (ptr < end) {
|
||||
uint32_t consumed;
|
||||
auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
|
||||
// Parse field header
|
||||
auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
|
||||
break;
|
||||
ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t field_type = (res->as_uint32()) & 0b111;
|
||||
uint32_t field_id = (res->as_uint32()) >> 3;
|
||||
i += consumed;
|
||||
uint32_t tag = res->as_uint32();
|
||||
uint32_t field_type = tag & 0b111;
|
||||
uint32_t field_id = tag >> 3;
|
||||
ptr += consumed;
|
||||
|
||||
switch (field_type) {
|
||||
case 0: { // VarInt
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
if (!this->decode_varint(field_id, *res)) {
|
||||
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
|
||||
}
|
||||
i += consumed;
|
||||
ptr += consumed;
|
||||
break;
|
||||
}
|
||||
case 2: { // Length-delimited
|
||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
||||
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
||||
if (!res.has_value()) {
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
i += consumed;
|
||||
if (field_length > length - i) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
ptr += consumed;
|
||||
if (ptr + field_length > end) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
|
||||
if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
|
||||
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
|
||||
}
|
||||
i += field_length;
|
||||
ptr += field_length;
|
||||
break;
|
||||
}
|
||||
case 5: { // 32-bit
|
||||
if (length - i < 4) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
if (ptr + 4 > end) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
|
||||
uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
|
||||
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
||||
}
|
||||
i += 4;
|
||||
ptr += 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
if (error) {
|
||||
break;
|
||||
ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,23 @@
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
// Helper functions for ZigZag encoding/decoding
|
||||
inline constexpr uint32_t encode_zigzag32(int32_t value) {
|
||||
return (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||
}
|
||||
|
||||
inline constexpr uint64_t encode_zigzag64(int64_t value) {
|
||||
return (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
||||
}
|
||||
|
||||
inline constexpr int32_t decode_zigzag32(uint32_t value) {
|
||||
return (value & 1) ? static_cast<int32_t>(~(value >> 1)) : static_cast<int32_t>(value >> 1);
|
||||
}
|
||||
|
||||
inline constexpr int64_t decode_zigzag64(uint64_t value) {
|
||||
return (value & 1) ? static_cast<int64_t>(~(value >> 1)) : static_cast<int64_t>(value >> 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* StringRef Ownership Model for API Protocol Messages
|
||||
* ===================================================
|
||||
@@ -87,33 +104,25 @@ class ProtoVarInt {
|
||||
return {}; // Incomplete or invalid varint
|
||||
}
|
||||
|
||||
uint16_t as_uint16() const { return this->value_; }
|
||||
uint32_t as_uint32() const { return this->value_; }
|
||||
uint64_t as_uint64() const { return this->value_; }
|
||||
bool as_bool() const { return this->value_; }
|
||||
int32_t as_int32() const {
|
||||
constexpr uint16_t as_uint16() const { return this->value_; }
|
||||
constexpr uint32_t as_uint32() const { return this->value_; }
|
||||
constexpr uint64_t as_uint64() const { return this->value_; }
|
||||
constexpr bool as_bool() const { return this->value_; }
|
||||
constexpr int32_t as_int32() const {
|
||||
// Not ZigZag encoded
|
||||
return static_cast<int32_t>(this->as_int64());
|
||||
}
|
||||
int64_t as_int64() const {
|
||||
constexpr int64_t as_int64() const {
|
||||
// Not ZigZag encoded
|
||||
return static_cast<int64_t>(this->value_);
|
||||
}
|
||||
int32_t as_sint32() const {
|
||||
constexpr int32_t as_sint32() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1) {
|
||||
return static_cast<int32_t>(~(this->value_ >> 1));
|
||||
} else {
|
||||
return static_cast<int32_t>(this->value_ >> 1);
|
||||
}
|
||||
return decode_zigzag32(static_cast<uint32_t>(this->value_));
|
||||
}
|
||||
int64_t as_sint64() const {
|
||||
constexpr int64_t as_sint64() const {
|
||||
// with ZigZag encoding
|
||||
if (this->value_ & 1) {
|
||||
return static_cast<int64_t>(~(this->value_ >> 1));
|
||||
} else {
|
||||
return static_cast<int64_t>(this->value_ >> 1);
|
||||
}
|
||||
return decode_zigzag64(this->value_);
|
||||
}
|
||||
/**
|
||||
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
||||
@@ -309,22 +318,10 @@ class ProtoWriteBuffer {
|
||||
this->encode_uint64(field_id, static_cast<uint64_t>(value), force);
|
||||
}
|
||||
void encode_sint32(uint32_t field_id, int32_t value, bool force = false) {
|
||||
uint32_t uvalue;
|
||||
if (value < 0) {
|
||||
uvalue = ~(value << 1);
|
||||
} else {
|
||||
uvalue = value << 1;
|
||||
}
|
||||
this->encode_uint32(field_id, uvalue, force);
|
||||
this->encode_uint32(field_id, encode_zigzag32(value), force);
|
||||
}
|
||||
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
uint64_t uvalue;
|
||||
if (value < 0) {
|
||||
uvalue = ~(value << 1);
|
||||
} else {
|
||||
uvalue = value << 1;
|
||||
}
|
||||
this->encode_uint64(field_id, uvalue, force);
|
||||
this->encode_uint64(field_id, encode_zigzag64(value), force);
|
||||
}
|
||||
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
@@ -395,7 +392,7 @@ class ProtoSize {
|
||||
* @param value The uint32_t value to calculate size for
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static inline uint32_t varint(uint32_t value) {
|
||||
static constexpr uint32_t varint(uint32_t value) {
|
||||
// Optimized varint size calculation using leading zeros
|
||||
// Each 7 bits requires one byte in the varint encoding
|
||||
if (value < 128)
|
||||
@@ -419,7 +416,7 @@ class ProtoSize {
|
||||
* @param value The uint64_t value to calculate size for
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static inline uint32_t varint(uint64_t value) {
|
||||
static constexpr uint32_t varint(uint64_t value) {
|
||||
// Handle common case of values fitting in uint32_t (vast majority of use cases)
|
||||
if (value <= UINT32_MAX) {
|
||||
return varint(static_cast<uint32_t>(value));
|
||||
@@ -450,7 +447,7 @@ class ProtoSize {
|
||||
* @param value The int32_t value to calculate size for
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static inline uint32_t varint(int32_t value) {
|
||||
static constexpr uint32_t varint(int32_t value) {
|
||||
// Negative values are sign-extended to 64 bits in protocol buffers,
|
||||
// which always results in a 10-byte varint for negative int32
|
||||
if (value < 0) {
|
||||
@@ -466,7 +463,7 @@ class ProtoSize {
|
||||
* @param value The int64_t value to calculate size for
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static inline uint32_t varint(int64_t value) {
|
||||
static constexpr uint32_t varint(int64_t value) {
|
||||
// For int64_t, we convert to uint64_t and calculate the size
|
||||
// This works because the bit pattern determines the encoding size,
|
||||
// and we've handled negative int32 values as a special case above
|
||||
@@ -480,7 +477,7 @@ class ProtoSize {
|
||||
* @param type The wire type value (from the WireType enum in the protobuf spec)
|
||||
* @return The number of bytes needed to encode the field ID and wire type
|
||||
*/
|
||||
static inline uint32_t field(uint32_t field_id, uint32_t type) {
|
||||
static constexpr uint32_t field(uint32_t field_id, uint32_t type) {
|
||||
uint32_t tag = (field_id << 3) | (type & 0b111);
|
||||
return varint(tag);
|
||||
}
|
||||
@@ -607,9 +604,8 @@ class ProtoSize {
|
||||
*/
|
||||
inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
|
||||
// Always calculate size when force is true
|
||||
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
|
||||
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||
total_size_ += field_id_size + varint(zigzag);
|
||||
// ZigZag encoding for sint32
|
||||
total_size_ += field_id_size + varint(encode_zigzag32(value));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_DIRECTION,
|
||||
CONF_HYSTERESIS,
|
||||
CONF_ID,
|
||||
CONF_POWER_MODE,
|
||||
CONF_RANGE,
|
||||
)
|
||||
|
||||
@@ -57,7 +58,6 @@ FAST_FILTER = {
|
||||
CONF_RAW_ANGLE = "raw_angle"
|
||||
CONF_RAW_POSITION = "raw_position"
|
||||
CONF_WATCHDOG = "watchdog"
|
||||
CONF_POWER_MODE = "power_mode"
|
||||
CONF_SLOW_FILTER = "slow_filter"
|
||||
CONF_FAST_FILTER = "fast_filter"
|
||||
CONF_START_POSITION = "start_position"
|
||||
|
||||
@@ -24,7 +24,6 @@ AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingCompone
|
||||
CONF_RAW_ANGLE = "raw_angle"
|
||||
CONF_RAW_POSITION = "raw_position"
|
||||
CONF_WATCHDOG = "watchdog"
|
||||
CONF_POWER_MODE = "power_mode"
|
||||
CONF_SLOW_FILTER = "slow_filter"
|
||||
CONF_FAST_FILTER = "fast_filter"
|
||||
CONF_PWM_FREQUENCY = "pwm_frequency"
|
||||
|
||||
@@ -10,7 +10,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
|
||||
@@ -110,6 +110,8 @@ void ATM90E32Component::update() {
|
||||
|
||||
void ATM90E32Component::setup() {
|
||||
this->spi_setup();
|
||||
this->cs_summary_ = this->cs_->dump_summary();
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
|
||||
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||
uint16_t high_thresh = 0;
|
||||
@@ -130,9 +132,9 @@ void ATM90E32Component::setup() {
|
||||
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
||||
}
|
||||
|
||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||
delay(6); // Wait for the minimum 5ms + 1ms
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A, false); // Perform soft reset
|
||||
delay(6); // Wait for the minimum 5ms + 1ms
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||
if (!this->validate_spi_read_(0x55AA, "setup()")) {
|
||||
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
|
||||
this->mark_failed();
|
||||
@@ -156,16 +158,17 @@ void ATM90E32Component::setup() {
|
||||
|
||||
if (this->enable_offset_calibration_) {
|
||||
// Initialize flash storage for offset calibrations
|
||||
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_->dump_summary());
|
||||
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
|
||||
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
||||
this->restore_offset_calibrations_();
|
||||
|
||||
// Initialize flash storage for power offset calibrations
|
||||
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_->dump_summary());
|
||||
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
|
||||
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
||||
this->restore_power_offset_calibrations_();
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Power & Voltage/Current offset calibration is disabled. Using config file values.");
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
|
||||
cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->write16_(this->voltage_offset_registers[phase],
|
||||
static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
|
||||
@@ -180,21 +183,18 @@ void ATM90E32Component::setup() {
|
||||
|
||||
if (this->enable_gain_calibration_) {
|
||||
// Initialize flash storage for gain calibration
|
||||
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_->dump_summary());
|
||||
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
|
||||
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
||||
this->restore_gain_calibrations_();
|
||||
|
||||
if (this->using_saved_calibrations_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored gain calibration from memory.");
|
||||
} else {
|
||||
if (!this->using_saved_calibrations_) {
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration is disabled. Using config file values.");
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||
@@ -213,6 +213,122 @@ void ATM90E32Component::setup() {
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||
}
|
||||
|
||||
void ATM90E32Component::log_calibration_status_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
|
||||
bool offset_mismatch = false;
|
||||
bool power_mismatch = false;
|
||||
bool gain_mismatch = false;
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
offset_mismatch |= this->offset_calibration_mismatch_[phase];
|
||||
power_mismatch |= this->power_offset_calibration_mismatch_[phase];
|
||||
gain_mismatch |= this->gain_calibration_mismatch_[phase];
|
||||
}
|
||||
|
||||
if (offset_mismatch) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ===================== Offset mismatch: using flash values =====================", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
|
||||
this->config_offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].voltage_offset_,
|
||||
this->config_offset_phase_[phase].current_offset_, this->offset_phase_[phase].current_offset_);
|
||||
}
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||
}
|
||||
if (power_mismatch) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ================= Power offset mismatch: using flash values =================", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_active_power|offset_reactive_power|", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
|
||||
this->config_power_offset_phase_[phase].active_power_offset,
|
||||
this->power_offset_phase_[phase].active_power_offset,
|
||||
this->config_power_offset_phase_[phase].reactive_power_offset,
|
||||
this->power_offset_phase_[phase].reactive_power_offset);
|
||||
}
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||
}
|
||||
if (gain_mismatch) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ====================== Gain mismatch: using flash values =====================", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6u | %6u | %6u | %6u |", cs, 'A' + phase,
|
||||
this->config_gain_phase_[phase].voltage_gain, this->gain_phase_[phase].voltage_gain,
|
||||
this->config_gain_phase_[phase].current_gain, this->gain_phase_[phase].current_gain);
|
||||
}
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||
}
|
||||
if (!this->enable_offset_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
|
||||
cs);
|
||||
} else if (this->restored_offset_calibration_ && !offset_mismatch) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============== Restored offset calibration from memory ==============", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\\n", cs);
|
||||
}
|
||||
|
||||
if (this->restored_power_offset_calibration_ && !power_mismatch) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restored power offset calibration from memory ============", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||
this->power_offset_phase_[phase].active_power_offset,
|
||||
this->power_offset_phase_[phase].reactive_power_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
}
|
||||
if (!this->enable_gain_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
||||
} else if (this->restored_gain_calibration_ && !gain_mismatch) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restoring saved gain calibrations to registers ============", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
|
||||
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\\n", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration loaded and verified successfully.\n", cs);
|
||||
}
|
||||
this->calibration_message_printed_ = true;
|
||||
}
|
||||
|
||||
void ATM90E32Component::dump_config() {
|
||||
ESP_LOGCONFIG("", "ATM90E32:");
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
@@ -255,6 +371,10 @@ void ATM90E32Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
|
||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||
if (this->restored_offset_calibration_ || this->restored_power_offset_calibration_ ||
|
||||
this->restored_gain_calibration_ || !this->enable_offset_calibration_ || !this->enable_gain_calibration_) {
|
||||
this->log_calibration_status_();
|
||||
}
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
@@ -263,19 +383,17 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
|
||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
||||
// Default is 143FH (20ms, 63ms)
|
||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||
this->enable();
|
||||
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
|
||||
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
uint8_t data[2];
|
||||
uint16_t output;
|
||||
this->enable();
|
||||
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1ms is plenty
|
||||
this->write_byte(addrh);
|
||||
this->write_byte(addrl);
|
||||
this->read_array(data, 2);
|
||||
this->disable();
|
||||
|
||||
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
||||
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
|
||||
this->transfer_array(data, 4);
|
||||
uint16_t output = encode_uint16(data[2], data[3]);
|
||||
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
||||
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
|
||||
this->disable();
|
||||
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -292,13 +410,19 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||
return val;
|
||||
}
|
||||
|
||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
|
||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val, bool validate) {
|
||||
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
|
||||
uint8_t addrh = ((a_register >> 8) & 0x03);
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
uint8_t data[4] = {addrh, addrl, uint8_t((val >> 8) & 0xFF), uint8_t(val & 0xFF)};
|
||||
this->enable();
|
||||
this->write_byte16(a_register);
|
||||
this->write_byte16(val);
|
||||
delay_microseconds_safe(1); // ensure CS setup time
|
||||
this->write_array(data, 4);
|
||||
delay_microseconds_safe(1); // allow clock to settle before raising CS
|
||||
this->disable();
|
||||
this->validate_spi_read_(val, "write16()");
|
||||
delay_microseconds_safe(1); // ensure minimum CS high time
|
||||
if (validate)
|
||||
this->validate_spi_read_(val, "write16()");
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
|
||||
@@ -441,8 +565,10 @@ float ATM90E32Component::get_chip_temperature_() {
|
||||
}
|
||||
|
||||
void ATM90E32Component::run_gain_calibrations() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->enable_gain_calibration_) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Gain calibration is disabled! Enable it first with enable_gain_calibration: true");
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Gain calibration is disabled! Enable it first with enable_gain_calibration: true",
|
||||
cs);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -454,12 +580,14 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
|
||||
this->get_reference_current(2)};
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] ");
|
||||
ESP_LOGI(TAG, "[CALIBRATION] ========================= Gain Calibration =========================");
|
||||
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
|
||||
ESP_LOGI(TAG,
|
||||
"[CALIBRATION] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |");
|
||||
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ========================= Gain Calibration =========================", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(
|
||||
TAG,
|
||||
"[CALIBRATION][%s] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |",
|
||||
cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
float measured_voltage = this->get_phase_voltage_avg_(phase);
|
||||
@@ -476,22 +604,22 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
|
||||
// Voltage calibration
|
||||
if (ref_voltage <= 0.0f) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: reference voltage is 0.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: reference voltage is 0.", cs,
|
||||
phase_labels[phase]);
|
||||
} else if (measured_voltage == 0.0f) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: measured voltage is 0.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: measured voltage is 0.", cs,
|
||||
phase_labels[phase]);
|
||||
} else {
|
||||
uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
|
||||
if (new_voltage_gain == 0) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Voltage gain would be 0. Check reference and measured voltage.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Voltage gain would be 0. Check reference and measured voltage.", cs,
|
||||
phase_labels[phase]);
|
||||
} else {
|
||||
if (new_voltage_gain >= 65535) {
|
||||
ESP_LOGW(
|
||||
TAG,
|
||||
"[CALIBRATION] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage transformer.",
|
||||
phase_labels[phase]);
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage "
|
||||
"transformer.",
|
||||
cs, phase_labels[phase]);
|
||||
new_voltage_gain = 65535;
|
||||
}
|
||||
this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
|
||||
@@ -501,20 +629,20 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
|
||||
// Current calibration
|
||||
if (ref_current == 0.0f) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: reference current is 0.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: reference current is 0.", cs,
|
||||
phase_labels[phase]);
|
||||
} else if (measured_current == 0.0f) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: measured current is 0.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: measured current is 0.", cs,
|
||||
phase_labels[phase]);
|
||||
} else {
|
||||
uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
|
||||
if (new_current_gain == 0) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain would be 0. Check reference and measured current.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain would be 0. Check reference and measured current.", cs,
|
||||
phase_labels[phase]);
|
||||
} else {
|
||||
if (new_current_gain >= 65535) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
|
||||
phase_labels[phase]);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
|
||||
cs, phase_labels[phase]);
|
||||
new_current_gain = 65535;
|
||||
}
|
||||
this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
|
||||
@@ -523,13 +651,13 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
}
|
||||
|
||||
// Final row output
|
||||
ESP_LOGI(TAG, "[CALIBRATION] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |",
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |", cs,
|
||||
'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
|
||||
did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
|
||||
did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] =====================================================================\n");
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
this->save_gain_calibration_to_memory_();
|
||||
this->write_gains_to_registers_();
|
||||
@@ -537,54 +665,108 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
}
|
||||
|
||||
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||
global_preferences->sync();
|
||||
if (success) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration saved to memory.");
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration saved to memory.", cs);
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION] Failed to save gain calibration to memory!");
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save gain calibration to memory!", cs);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::save_offset_calibration_to_memory_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
bool success = this->offset_pref_.save(&this->offset_phase_);
|
||||
global_preferences->sync();
|
||||
if (success) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
this->restored_offset_calibration_ = true;
|
||||
for (bool &phase : this->offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Offset calibration saved to memory.", cs);
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save offset calibration to memory!", cs);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::save_power_offset_calibration_to_memory_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
bool success = this->power_offset_pref_.save(&this->power_offset_phase_);
|
||||
global_preferences->sync();
|
||||
if (success) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
this->restored_power_offset_calibration_ = true;
|
||||
for (bool &phase : this->power_offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offset calibration saved to memory.", cs);
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save power offset calibration to memory!", cs);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::run_offset_calibrations() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->enable_offset_calibration_) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Offset calibration is disabled! Enable it first with enable_offset_calibration: true");
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] Offset calibration is disabled! Enable it first with enable_offset_calibration: true",
|
||||
cs);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ======================== Offset Calibration ========================", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
int16_t voltage_offset = calibrate_offset(phase, true);
|
||||
int16_t current_offset = calibrate_offset(phase, false);
|
||||
|
||||
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage: %d, offset_current: %d", 'A' + phase, voltage_offset,
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
|
||||
current_offset);
|
||||
}
|
||||
|
||||
this->offset_pref_.save(&this->offset_phase_); // Save to flash
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==================================================================\n", cs);
|
||||
|
||||
this->save_offset_calibration_to_memory_();
|
||||
}
|
||||
|
||||
void ATM90E32Component::run_power_offset_calibrations() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->enable_offset_calibration_) {
|
||||
ESP_LOGW(
|
||||
TAG,
|
||||
"[CALIBRATION] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true");
|
||||
"[CALIBRATION][%s] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true",
|
||||
cs);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ===================== Power Offset Calibration =====================", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
int16_t active_offset = calibrate_power_offset(phase, false);
|
||||
int16_t reactive_offset = calibrate_power_offset(phase, true);
|
||||
|
||||
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
|
||||
active_offset, reactive_offset);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
|
||||
reactive_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
this->power_offset_pref_.save(&this->power_offset_phase_); // Save to flash
|
||||
this->save_power_offset_calibration_to_memory_();
|
||||
}
|
||||
|
||||
void ATM90E32Component::write_gains_to_registers_() {
|
||||
@@ -631,102 +813,276 @@ void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t
|
||||
}
|
||||
|
||||
void ATM90E32Component::restore_gain_calibrations_() {
|
||||
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Restoring saved gain calibrations to registers:");
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
uint16_t v_gain = this->gain_phase_[phase].voltage_gain;
|
||||
uint16_t i_gain = this->gain_phase_[phase].current_gain;
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase, v_gain, i_gain);
|
||||
}
|
||||
|
||||
this->write_gains_to_registers_();
|
||||
|
||||
if (this->verify_gain_writes_()) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration loaded and verified successfully.");
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION] Gain verification failed! Calibration may not be applied correctly.");
|
||||
}
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGW(TAG, "[CALIBRATION] No stored gain calibrations found. Using config file values.");
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
for (uint8_t i = 0; i < 3; ++i) {
|
||||
this->config_gain_phase_[i].voltage_gain = this->phase_[i].voltage_gain_;
|
||||
this->config_gain_phase_[i].current_gain = this->phase_[i].ct_gain_;
|
||||
this->gain_phase_[i] = this->config_gain_phase_[i];
|
||||
}
|
||||
|
||||
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
|
||||
bool all_zero = true;
|
||||
bool same_as_config = true;
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
const auto &cfg = this->config_gain_phase_[phase];
|
||||
const auto &saved = this->gain_phase_[phase];
|
||||
if (saved.voltage_gain != 0 || saved.current_gain != 0)
|
||||
all_zero = false;
|
||||
if (saved.voltage_gain != cfg.voltage_gain || saved.current_gain != cfg.current_gain)
|
||||
same_as_config = false;
|
||||
}
|
||||
|
||||
if (!all_zero && !same_as_config) {
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
bool mismatch = false;
|
||||
if (this->has_config_voltage_gain_[phase] &&
|
||||
this->gain_phase_[phase].voltage_gain != this->config_gain_phase_[phase].voltage_gain)
|
||||
mismatch = true;
|
||||
if (this->has_config_current_gain_[phase] &&
|
||||
this->gain_phase_[phase].current_gain != this->config_gain_phase_[phase].current_gain)
|
||||
mismatch = true;
|
||||
if (mismatch)
|
||||
this->gain_calibration_mismatch_[phase] = true;
|
||||
}
|
||||
|
||||
this->write_gains_to_registers_();
|
||||
|
||||
if (this->verify_gain_writes_()) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
this->restored_gain_calibration_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Gain verification failed! Calibration may not be applied correctly.", cs);
|
||||
}
|
||||
}
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
for (uint8_t i = 0; i < 3; ++i)
|
||||
this->gain_phase_[i] = this->config_gain_phase_[i];
|
||||
this->write_gains_to_registers_();
|
||||
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored gain calibrations found. Using config file values.", cs);
|
||||
}
|
||||
|
||||
void ATM90E32Component::restore_offset_calibrations_() {
|
||||
if (this->offset_pref_.load(&this->offset_phase_)) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored offset calibration from memory.");
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
for (uint8_t i = 0; i < 3; ++i)
|
||||
this->config_offset_phase_[i] = this->offset_phase_[i];
|
||||
|
||||
bool have_data = this->offset_pref_.load(&this->offset_phase_);
|
||||
bool all_zero = true;
|
||||
if (have_data) {
|
||||
for (auto &phase : this->offset_phase_) {
|
||||
if (phase.voltage_offset_ != 0 || phase.current_offset_ != 0) {
|
||||
all_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (have_data && !all_zero) {
|
||||
this->restored_offset_calibration_ = true;
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
auto &offset = this->offset_phase_[phase];
|
||||
write_offsets_to_registers_(phase, offset.voltage_offset_, offset.current_offset_);
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage:: %d, offset_current: %d", 'A' + phase,
|
||||
offset.voltage_offset_, offset.current_offset_);
|
||||
bool mismatch = false;
|
||||
if (this->has_config_voltage_offset_[phase] &&
|
||||
offset.voltage_offset_ != this->config_offset_phase_[phase].voltage_offset_)
|
||||
mismatch = true;
|
||||
if (this->has_config_current_offset_[phase] &&
|
||||
offset.current_offset_ != this->config_offset_phase_[phase].current_offset_)
|
||||
mismatch = true;
|
||||
if (mismatch)
|
||||
this->offset_calibration_mismatch_[phase] = true;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] No stored offset calibrations found. Using default values.");
|
||||
for (uint8_t phase = 0; phase < 3; phase++)
|
||||
this->offset_phase_[phase] = this->config_offset_phase_[phase];
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored offset calibrations found. Using default values.", cs);
|
||||
}
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
write_offsets_to_registers_(phase, this->offset_phase_[phase].voltage_offset_,
|
||||
this->offset_phase_[phase].current_offset_);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::restore_power_offset_calibrations_() {
|
||||
if (this->power_offset_pref_.load(&this->power_offset_phase_)) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored power offset calibration from memory.");
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
for (uint8_t i = 0; i < 3; ++i)
|
||||
this->config_power_offset_phase_[i] = this->power_offset_phase_[i];
|
||||
|
||||
bool have_data = this->power_offset_pref_.load(&this->power_offset_phase_);
|
||||
bool all_zero = true;
|
||||
if (have_data) {
|
||||
for (auto &phase : this->power_offset_phase_) {
|
||||
if (phase.active_power_offset != 0 || phase.reactive_power_offset != 0) {
|
||||
all_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (have_data && !all_zero) {
|
||||
this->restored_power_offset_calibration_ = true;
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
auto &offset = this->power_offset_phase_[phase];
|
||||
write_power_offsets_to_registers_(phase, offset.active_power_offset, offset.reactive_power_offset);
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
|
||||
offset.active_power_offset, offset.reactive_power_offset);
|
||||
bool mismatch = false;
|
||||
if (this->has_config_active_power_offset_[phase] &&
|
||||
offset.active_power_offset != this->config_power_offset_phase_[phase].active_power_offset)
|
||||
mismatch = true;
|
||||
if (this->has_config_reactive_power_offset_[phase] &&
|
||||
offset.reactive_power_offset != this->config_power_offset_phase_[phase].reactive_power_offset)
|
||||
mismatch = true;
|
||||
if (mismatch)
|
||||
this->power_offset_calibration_mismatch_[phase] = true;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] No stored power offsets found. Using default values.");
|
||||
for (uint8_t phase = 0; phase < 3; ++phase)
|
||||
this->power_offset_phase_[phase] = this->config_power_offset_phase_[phase];
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored power offsets found. Using default values.", cs);
|
||||
}
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
write_power_offsets_to_registers_(phase, this->power_offset_phase_[phase].active_power_offset,
|
||||
this->power_offset_phase_[phase].reactive_power_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::clear_gain_calibrations() {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values");
|
||||
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
|
||||
gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->using_saved_calibrations_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
|
||||
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored gain calibrations and restoring config-defined values", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
|
||||
if (success) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibrations cleared. Config values restored:");
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase,
|
||||
gain_phase_[phase].voltage_gain, gain_phase_[phase].current_gain);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[CALIBRATION] Failed to clear gain calibrations!");
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
uint16_t voltage_gain = this->phase_[phase].voltage_gain_;
|
||||
uint16_t current_gain = this->phase_[phase].ct_gain_;
|
||||
|
||||
this->config_gain_phase_[phase].voltage_gain = voltage_gain;
|
||||
this->config_gain_phase_[phase].current_gain = current_gain;
|
||||
this->gain_phase_[phase].voltage_gain = voltage_gain;
|
||||
this->gain_phase_[phase].current_gain = current_gain;
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase, voltage_gain, current_gain);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
||||
|
||||
GainCalibration zero_gains[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
bool success = this->gain_calibration_pref_.save(&zero_gains);
|
||||
global_preferences->sync();
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
this->restored_gain_calibration_ = false;
|
||||
for (bool &phase : this->gain_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
if (!success) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear gain calibrations!", cs);
|
||||
}
|
||||
|
||||
this->write_gains_to_registers_(); // Apply them to the chip immediately
|
||||
}
|
||||
|
||||
void ATM90E32Component::clear_offset_calibrations() {
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
this->write_offsets_to_registers_(phase, 0, 0);
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->restored_offset_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
||||
return;
|
||||
}
|
||||
|
||||
this->offset_pref_.save(&this->offset_phase_); // Save cleared values to flash memory
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored offset calibrations and restoring config-defined values", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Offsets cleared.");
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
int16_t voltage_offset =
|
||||
this->has_config_voltage_offset_[phase] ? this->config_offset_phase_[phase].voltage_offset_ : 0;
|
||||
int16_t current_offset =
|
||||
this->has_config_current_offset_[phase] ? this->config_offset_phase_[phase].current_offset_ : 0;
|
||||
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
|
||||
current_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
||||
|
||||
OffsetCalibration zero_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
this->offset_pref_.save(&zero_offsets); // Clear stored values in flash
|
||||
global_preferences->sync();
|
||||
|
||||
this->restored_offset_calibration_ = false;
|
||||
for (bool &phase : this->offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Offsets cleared.", cs);
|
||||
}
|
||||
|
||||
void ATM90E32Component::clear_power_offset_calibrations() {
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
this->write_power_offsets_to_registers_(phase, 0, 0);
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->restored_power_offset_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||
this->power_offset_phase_[phase].active_power_offset,
|
||||
this->power_offset_phase_[phase].reactive_power_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
return;
|
||||
}
|
||||
|
||||
this->power_offset_pref_.save(&this->power_offset_phase_);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored power offsets and restoring config-defined values", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Power offsets cleared.");
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
int16_t active_offset =
|
||||
this->has_config_active_power_offset_[phase] ? this->config_power_offset_phase_[phase].active_power_offset : 0;
|
||||
int16_t reactive_offset = this->has_config_reactive_power_offset_[phase]
|
||||
? this->config_power_offset_phase_[phase].reactive_power_offset
|
||||
: 0;
|
||||
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
|
||||
reactive_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
PowerOffsetCalibration zero_power_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
this->power_offset_pref_.save(&zero_power_offsets);
|
||||
global_preferences->sync();
|
||||
|
||||
this->restored_power_offset_calibration_ = false;
|
||||
for (bool &phase : this->power_offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offsets cleared.", cs);
|
||||
}
|
||||
|
||||
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
||||
@@ -747,20 +1103,21 @@ int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
||||
|
||||
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
|
||||
const uint8_t num_reads = 5;
|
||||
uint64_t total_value = 0;
|
||||
int64_t total_value = 0;
|
||||
|
||||
for (uint8_t i = 0; i < num_reads; ++i) {
|
||||
uint32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
|
||||
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
||||
int32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
|
||||
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
||||
total_value += reading;
|
||||
}
|
||||
|
||||
const uint32_t average_value = total_value / num_reads;
|
||||
const uint32_t power_offset = ~average_value + 1;
|
||||
int32_t average_value = total_value / num_reads;
|
||||
int32_t power_offset = -average_value;
|
||||
return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
|
||||
}
|
||||
|
||||
bool ATM90E32Component::verify_gain_writes_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
bool success = true;
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
|
||||
@@ -768,7 +1125,7 @@ bool ATM90E32Component::verify_gain_writes_() {
|
||||
|
||||
if (read_voltage != this->gain_phase_[phase].voltage_gain ||
|
||||
read_current != this->gain_phase_[phase].current_gain) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION] Mismatch detected for Phase %s!", phase_labels[phase]);
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Mismatch detected for Phase %s!", cs, phase_labels[phase]);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
@@ -791,16 +1148,16 @@ void ATM90E32Component::check_phase_status() {
|
||||
status += "Phase Loss; ";
|
||||
|
||||
auto *sensor = this->phase_status_text_sensor_[phase];
|
||||
const char *phase_name = sensor ? sensor->get_name().c_str() : "Unknown Phase";
|
||||
if (sensor == nullptr)
|
||||
continue;
|
||||
|
||||
if (!status.empty()) {
|
||||
status.pop_back(); // remove space
|
||||
status.pop_back(); // remove semicolon
|
||||
ESP_LOGW(TAG, "%s: %s", phase_name, status.c_str());
|
||||
if (sensor != nullptr)
|
||||
sensor->publish_state(status);
|
||||
ESP_LOGW(TAG, "%s: %s", sensor->get_name().c_str(), status.c_str());
|
||||
sensor->publish_state(status);
|
||||
} else {
|
||||
if (sensor != nullptr)
|
||||
sensor->publish_state("Okay");
|
||||
sensor->publish_state("Okay");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -817,9 +1174,12 @@ void ATM90E32Component::check_freq_status() {
|
||||
} else {
|
||||
freq_status = "Normal";
|
||||
}
|
||||
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
|
||||
|
||||
if (this->freq_status_text_sensor_ != nullptr) {
|
||||
if (freq_status == "Normal") {
|
||||
ESP_LOGD(TAG, "Frequency status: %s", freq_status.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
|
||||
}
|
||||
this->freq_status_text_sensor_->publish_state(freq_status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,15 +61,29 @@ class ATM90E32Component : public PollingComponent,
|
||||
this->phase_[phase].harmonic_active_power_sensor_ = obj;
|
||||
}
|
||||
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
|
||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||
void set_voltage_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].voltage_offset_ = offset; }
|
||||
void set_current_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].current_offset_ = offset; }
|
||||
void set_volt_gain(int phase, uint16_t gain) {
|
||||
this->phase_[phase].voltage_gain_ = gain;
|
||||
this->has_config_voltage_gain_[phase] = true;
|
||||
}
|
||||
void set_ct_gain(int phase, uint16_t gain) {
|
||||
this->phase_[phase].ct_gain_ = gain;
|
||||
this->has_config_current_gain_[phase] = true;
|
||||
}
|
||||
void set_voltage_offset(uint8_t phase, int16_t offset) {
|
||||
this->offset_phase_[phase].voltage_offset_ = offset;
|
||||
this->has_config_voltage_offset_[phase] = true;
|
||||
}
|
||||
void set_current_offset(uint8_t phase, int16_t offset) {
|
||||
this->offset_phase_[phase].current_offset_ = offset;
|
||||
this->has_config_current_offset_[phase] = true;
|
||||
}
|
||||
void set_active_power_offset(uint8_t phase, int16_t offset) {
|
||||
this->power_offset_phase_[phase].active_power_offset = offset;
|
||||
this->has_config_active_power_offset_[phase] = true;
|
||||
}
|
||||
void set_reactive_power_offset(uint8_t phase, int16_t offset) {
|
||||
this->power_offset_phase_[phase].reactive_power_offset = offset;
|
||||
this->has_config_reactive_power_offset_[phase] = true;
|
||||
}
|
||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
||||
@@ -127,7 +141,7 @@ class ATM90E32Component : public PollingComponent,
|
||||
#endif
|
||||
uint16_t read16_(uint16_t a_register);
|
||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||
void write16_(uint16_t a_register, uint16_t val);
|
||||
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
|
||||
float get_local_phase_voltage_(uint8_t phase);
|
||||
float get_local_phase_current_(uint8_t phase);
|
||||
float get_local_phase_active_power_(uint8_t phase);
|
||||
@@ -159,12 +173,15 @@ class ATM90E32Component : public PollingComponent,
|
||||
void restore_offset_calibrations_();
|
||||
void restore_power_offset_calibrations_();
|
||||
void restore_gain_calibrations_();
|
||||
void save_offset_calibration_to_memory_();
|
||||
void save_gain_calibration_to_memory_();
|
||||
void save_power_offset_calibration_to_memory_();
|
||||
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
|
||||
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
|
||||
void write_gains_to_registers_();
|
||||
bool verify_gain_writes_();
|
||||
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
||||
void log_calibration_status_();
|
||||
|
||||
struct ATM90E32Phase {
|
||||
uint16_t voltage_gain_{0};
|
||||
@@ -204,19 +221,33 @@ class ATM90E32Component : public PollingComponent,
|
||||
int16_t current_offset_{0};
|
||||
} offset_phase_[3];
|
||||
|
||||
OffsetCalibration config_offset_phase_[3];
|
||||
|
||||
struct PowerOffsetCalibration {
|
||||
int16_t active_power_offset{0};
|
||||
int16_t reactive_power_offset{0};
|
||||
} power_offset_phase_[3];
|
||||
|
||||
PowerOffsetCalibration config_power_offset_phase_[3];
|
||||
|
||||
struct GainCalibration {
|
||||
uint16_t voltage_gain{1};
|
||||
uint16_t current_gain{1};
|
||||
} gain_phase_[3];
|
||||
|
||||
GainCalibration config_gain_phase_[3];
|
||||
|
||||
bool has_config_voltage_offset_[3]{false, false, false};
|
||||
bool has_config_current_offset_[3]{false, false, false};
|
||||
bool has_config_active_power_offset_[3]{false, false, false};
|
||||
bool has_config_reactive_power_offset_[3]{false, false, false};
|
||||
bool has_config_voltage_gain_[3]{false, false, false};
|
||||
bool has_config_current_gain_[3]{false, false, false};
|
||||
|
||||
ESPPreferenceObject offset_pref_;
|
||||
ESPPreferenceObject power_offset_pref_;
|
||||
ESPPreferenceObject gain_calibration_pref_;
|
||||
std::string cs_summary_;
|
||||
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
@@ -231,6 +262,13 @@ class ATM90E32Component : public PollingComponent,
|
||||
bool peak_current_signed_{false};
|
||||
bool enable_offset_calibration_{false};
|
||||
bool enable_gain_calibration_{false};
|
||||
bool restored_offset_calibration_{false};
|
||||
bool restored_power_offset_calibration_{false};
|
||||
bool restored_gain_calibration_{false};
|
||||
bool calibration_message_printed_{false};
|
||||
bool offset_calibration_mismatch_[3]{false, false, false};
|
||||
bool power_offset_calibration_mismatch_[3]{false, false, false};
|
||||
bool gain_calibration_mismatch_[3]{false, false, false};
|
||||
};
|
||||
|
||||
} // namespace atm90e32
|
||||
|
||||
@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
|
||||
i2c::ErrorCode err;
|
||||
uint8_t data[8]{};
|
||||
|
||||
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false);
|
||||
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD));
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(data, sizeof(data));
|
||||
ERROR_CHECK(err);
|
||||
|
||||
@@ -7,6 +7,19 @@ namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "binary_sensor";
|
||||
|
||||
// Function implementation of LOG_BINARY_SENSOR macro to reduce code size
|
||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) {
|
||||
if (obj == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||
|
||||
if (!obj->get_device_class().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void BinarySensor::publish_state(bool new_state) {
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(new_state);
|
||||
|
||||
@@ -10,13 +10,10 @@ namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
}
|
||||
class BinarySensor;
|
||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
|
||||
|
||||
#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||
|
||||
#define SUB_BINARY_SENSOR(name) \
|
||||
protected: \
|
||||
|
||||
@@ -286,6 +286,7 @@ async def remove_bond_to_code(config, action_id, template_arg, args):
|
||||
async def to_code(config):
|
||||
# Register the loggers this component needs
|
||||
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
|
||||
cg.add_define("USE_ESP32_BLE_UUID")
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
@@ -63,6 +63,6 @@ def to_code(config):
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
||||
yield output.register_output(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
yield cg.register_component(var, config)
|
||||
await output.register_output(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_ble, esp32_ble_client, esp32_ble_tracker
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components.esp32_ble import BTLoggers
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ACTIVE, CONF_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.log import AnsiFore, color
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
||||
DEPENDENCIES = ["api", "esp32"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CODEOWNERS = ["@jesserockz", "@bdraco"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CONNECTION_SLOTS = "connection_slots"
|
||||
CONF_CACHE_SERVICES = "cache_services"
|
||||
@@ -41,6 +47,27 @@ def validate_connections(config):
|
||||
esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
|
||||
config
|
||||
)
|
||||
|
||||
# Warn about connection slot waste when using Arduino framework
|
||||
if CORE.using_arduino and connection_slots:
|
||||
_LOGGER.warning(
|
||||
"Bluetooth Proxy with active connections on Arduino framework has suboptimal performance.\n"
|
||||
"If BLE connections fail, they can waste connection slots for 10 seconds because\n"
|
||||
"Arduino doesn't allow configuring the BLE connection timeout (fixed at 30s).\n"
|
||||
"ESP-IDF framework allows setting it to 20s to match client timeouts.\n"
|
||||
"\n"
|
||||
"To switch to ESP-IDF, add this to your YAML:\n"
|
||||
" esp32:\n"
|
||||
" framework:\n"
|
||||
" type: esp-idf\n"
|
||||
"\n"
|
||||
"For detailed migration instructions, see:\n"
|
||||
"%s",
|
||||
color(
|
||||
AnsiFore.BLUE, "https://esphome.io/guides/esp32_arduino_to_idf.html"
|
||||
),
|
||||
)
|
||||
|
||||
return {
|
||||
**config,
|
||||
CONF_CONNECTIONS: [CONNECTION_SCHEMA({}) for _ in range(connection_slots)],
|
||||
@@ -91,6 +118,12 @@ async def to_code(config):
|
||||
connection_count = len(config.get(CONF_CONNECTIONS, []))
|
||||
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", connection_count)
|
||||
|
||||
# Define batch size for BLE advertisements
|
||||
# Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
||||
# 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
|
||||
# This achieves ~97% WiFi MTU utilization while staying under the limit
|
||||
cg.add_define("BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE", 16)
|
||||
|
||||
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)
|
||||
|
||||
@@ -12,16 +12,30 @@ namespace esphome::bluetooth_proxy {
|
||||
|
||||
static const char *const TAG = "bluetooth_proxy.connection";
|
||||
|
||||
// This function is allocation-free and directly packs UUIDs into the output array
|
||||
// using precalculated constants for the Bluetooth base UUID
|
||||
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
|
||||
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
|
||||
out[0] = ((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]);
|
||||
out[1] = ((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]);
|
||||
// Bluetooth base UUID: 00000000-0000-1000-8000-00805F9B34FB
|
||||
// out[0] = bytes 8-15 (big-endian)
|
||||
// - For 128-bit UUIDs: use bytes 8-15 as-is
|
||||
// - For 16/32-bit UUIDs: insert into bytes 12-15, use 0x00001000 for bytes 8-11
|
||||
out[0] = uuid_source.len == ESP_UUID_LEN_128
|
||||
? (((uint64_t) uuid_source.uuid.uuid128[15] << 56) | ((uint64_t) uuid_source.uuid.uuid128[14] << 48) |
|
||||
((uint64_t) uuid_source.uuid.uuid128[13] << 40) | ((uint64_t) uuid_source.uuid.uuid128[12] << 32) |
|
||||
((uint64_t) uuid_source.uuid.uuid128[11] << 24) | ((uint64_t) uuid_source.uuid.uuid128[10] << 16) |
|
||||
((uint64_t) uuid_source.uuid.uuid128[9] << 8) | ((uint64_t) uuid_source.uuid.uuid128[8]))
|
||||
: (((uint64_t) (uuid_source.len == ESP_UUID_LEN_16 ? uuid_source.uuid.uuid16 : uuid_source.uuid.uuid32)
|
||||
<< 32) |
|
||||
0x00001000ULL); // Base UUID bytes 8-11
|
||||
// out[1] = bytes 0-7 (big-endian)
|
||||
// - For 128-bit UUIDs: use bytes 0-7 as-is
|
||||
// - For 16/32-bit UUIDs: use precalculated base UUID constant
|
||||
out[1] = uuid_source.len == ESP_UUID_LEN_128
|
||||
? ((uint64_t) uuid_source.uuid.uuid128[7] << 56) | ((uint64_t) uuid_source.uuid.uuid128[6] << 48) |
|
||||
((uint64_t) uuid_source.uuid.uuid128[5] << 40) | ((uint64_t) uuid_source.uuid.uuid128[4] << 32) |
|
||||
((uint64_t) uuid_source.uuid.uuid128[3] << 24) | ((uint64_t) uuid_source.uuid.uuid128[2] << 16) |
|
||||
((uint64_t) uuid_source.uuid.uuid128[1] << 8) | ((uint64_t) uuid_source.uuid.uuid128[0])
|
||||
: 0x800000805F9B34FBULL; // Base UUID bytes 0-7: 80-00-00-80-5F-9B-34-FB
|
||||
}
|
||||
|
||||
// Helper to fill UUID in the appropriate format based on client support and UUID type
|
||||
@@ -80,9 +94,11 @@ void BluetoothConnection::dump_config() {
|
||||
|
||||
void BluetoothConnection::update_allocated_slot_(uint64_t find_value, uint64_t set_value) {
|
||||
auto &allocated = this->proxy_->connections_free_response_.allocated;
|
||||
auto *it = std::find(allocated.begin(), allocated.end(), find_value);
|
||||
if (it != allocated.end()) {
|
||||
*it = set_value;
|
||||
for (auto &slot : allocated) {
|
||||
if (slot == find_value) {
|
||||
slot = set_value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,13 +121,24 @@ void BluetoothConnection::set_address(uint64_t address) {
|
||||
void BluetoothConnection::loop() {
|
||||
BLEClientBase::loop();
|
||||
|
||||
// Early return if no active connection or not in service discovery phase
|
||||
if (this->address_ == 0 || this->send_service_ < 0 || this->send_service_ > this->service_count_) {
|
||||
// Early return if no active connection
|
||||
if (this->address_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle service discovery
|
||||
this->send_service_for_discovery_();
|
||||
// Handle service discovery if in valid range
|
||||
if (this->send_service_ >= 0 && this->send_service_ <= this->service_count_) {
|
||||
this->send_service_for_discovery_();
|
||||
}
|
||||
|
||||
// Check if we should disable the loop
|
||||
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
|
||||
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
|
||||
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
|
||||
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->send_service_ == DONE_SENDING_SERVICES)) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
||||
@@ -125,7 +152,7 @@ void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
||||
// to detect incomplete service discovery rather than relying on us to
|
||||
// tell them about a partial list.
|
||||
this->set_address(0);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->send_service_ = INIT_SENDING_SERVICES;
|
||||
this->proxy_->send_connections_free();
|
||||
}
|
||||
|
||||
@@ -133,10 +160,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
if (this->send_service_ >= this->service_count_) {
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->proxy_->send_gatt_services_done(this->address_);
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
this->release_services();
|
||||
}
|
||||
this->release_services();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,8 +209,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
||||
|
||||
if (char_count_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_,
|
||||
this->address_str().c_str(), char_count_status);
|
||||
this->log_connection_error_("esp_ble_gattc_get_attr_count", char_count_status);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
@@ -220,8 +243,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
break;
|
||||
}
|
||||
if (char_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
|
||||
this->address_str().c_str(), char_status);
|
||||
this->log_connection_error_("esp_ble_gattc_get_all_char", char_status);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
@@ -244,8 +266,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
||||
|
||||
if (desc_count_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
|
||||
this->connection_index_, this->address_str().c_str(), char_result.char_handle, desc_count_status);
|
||||
this->log_connection_error_("esp_ble_gattc_get_attr_count", desc_count_status);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
@@ -266,8 +287,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
break;
|
||||
}
|
||||
if (desc_status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_,
|
||||
this->address_str().c_str(), desc_status);
|
||||
this->log_connection_error_("esp_ble_gattc_get_all_descr", desc_status);
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
return;
|
||||
}
|
||||
@@ -321,6 +341,33 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
void BluetoothConnection::log_connection_error_(const char *operation, esp_gatt_status_t status) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str().c_str(), operation,
|
||||
status);
|
||||
}
|
||||
|
||||
void BluetoothConnection::log_connection_warning_(const char *operation, esp_err_t err) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] %s failed, err=%d", this->connection_index_, this->address_str().c_str(), operation, err);
|
||||
}
|
||||
|
||||
void BluetoothConnection::log_gatt_not_connected_(const char *action, const char *type) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot %s GATT %s, not connected.", this->connection_index_, this->address_str().c_str(),
|
||||
action, type);
|
||||
}
|
||||
|
||||
void BluetoothConnection::log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error %s for handle 0x%2X, status=%d", this->connection_index_, this->address_str().c_str(),
|
||||
operation, handle, status);
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::check_and_log_error_(const char *operation, esp_err_t err) {
|
||||
if (err != ESP_OK) {
|
||||
this->log_connection_warning_(operation, err);
|
||||
return err;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -328,10 +375,19 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
this->reset_connection_(param->disconnect.reason);
|
||||
// Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
|
||||
// This prevents race condition where we mark slot as free before controller cleanup is complete
|
||||
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(),
|
||||
param->disconnect.reason);
|
||||
// Send disconnection notification but don't free the slot yet
|
||||
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CLOSE_EVT: {
|
||||
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(),
|
||||
param->close.reason);
|
||||
// Now the GATT connection is fully closed and controller resources are freed
|
||||
// Safe to mark the connection slot as available
|
||||
this->reset_connection_(param->close.reason);
|
||||
break;
|
||||
}
|
||||
@@ -361,8 +417,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
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);
|
||||
this->log_gatt_operation_error_("reading char/descriptor", param->read.handle, param->read.status);
|
||||
this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
@@ -376,8 +431,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
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);
|
||||
this->log_gatt_operation_error_("writing char/descriptor", param->write.handle, param->write.status);
|
||||
this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
|
||||
break;
|
||||
}
|
||||
@@ -389,9 +443,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
}
|
||||
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);
|
||||
this->log_gatt_operation_error_("unregistering notifications", param->unreg_for_notify.handle,
|
||||
param->unreg_for_notify.status);
|
||||
this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
@@ -403,8 +456,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
}
|
||||
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);
|
||||
this->log_gatt_operation_error_("registering notifications", param->reg_for_notify.handle,
|
||||
param->reg_for_notify.status);
|
||||
this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
@@ -450,8 +503,7 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
|
||||
|
||||
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());
|
||||
this->log_gatt_not_connected_("read", "characteristic");
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
@@ -459,18 +511,12 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
||||
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;
|
||||
return this->check_and_log_error_("esp_ble_gattc_read_char", err);
|
||||
}
|
||||
|
||||
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());
|
||||
this->log_gatt_not_connected_("write", "characteristic");
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||
@@ -479,36 +525,24 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::
|
||||
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;
|
||||
return this->check_and_log_error_("esp_ble_gattc_write_char", err);
|
||||
}
|
||||
|
||||
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());
|
||||
this->log_gatt_not_connected_("read", "descriptor");
|
||||
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;
|
||||
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
|
||||
}
|
||||
|
||||
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());
|
||||
this->log_gatt_not_connected_("write", "descriptor");
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||
@@ -517,18 +551,12 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri
|
||||
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;
|
||||
return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
|
||||
}
|
||||
|
||||
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());
|
||||
this->log_gatt_not_connected_("notify", "characteristic");
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
@@ -536,22 +564,13 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
|
||||
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 this->check_and_log_error_("esp_ble_gattc_register_for_notify", err);
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
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);
|
||||
return this->check_and_log_error_("esp_ble_gattc_unregister_for_notify", err);
|
||||
}
|
||||
|
||||
esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() {
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
|
||||
|
||||
class BluetoothProxy;
|
||||
|
||||
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||
class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
@@ -33,13 +33,18 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||
void send_service_for_discovery_();
|
||||
void reset_connection_(esp_err_t reason);
|
||||
void update_allocated_slot_(uint64_t find_value, uint64_t set_value);
|
||||
void log_connection_error_(const char *operation, esp_gatt_status_t status);
|
||||
void log_connection_warning_(const char *operation, esp_err_t err);
|
||||
void log_gatt_not_connected_(const char *action, const char *type);
|
||||
void log_gatt_operation_error_(const char *operation, uint16_t handle, esp_gatt_status_t status);
|
||||
esp_err_t check_and_log_error_(const char *operation, esp_err_t err);
|
||||
|
||||
// Memory optimized layout for 32-bit systems
|
||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||
BluetoothProxy *proxy_;
|
||||
|
||||
// Group 2: 2-byte types
|
||||
int16_t send_service_{-2}; // Needs to handle negative values and service count
|
||||
int16_t send_service_{-3}; // -3 = INIT_SENDING_SERVICES, -2 = DONE_SENDING_SERVICES, >=0 = service index
|
||||
|
||||
// Group 3: 1-byte types
|
||||
bool seen_mtu_or_services_{false};
|
||||
|
||||
@@ -11,12 +11,8 @@ namespace esphome::bluetooth_proxy {
|
||||
|
||||
static const char *const TAG = "bluetooth_proxy";
|
||||
|
||||
// Batch size for BLE advertisements to maximize WiFi efficiency
|
||||
// Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
||||
// Most advertisements are 20-30 bytes, allowing even more to fit per packet
|
||||
// 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
|
||||
// This achieves ~97% WiFi MTU utilization while staying under the limit
|
||||
static constexpr size_t FLUSH_BATCH_SIZE = 16;
|
||||
// BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE is defined during code generation
|
||||
// It sets the batch size for BLE advertisements to maximize WiFi efficiency
|
||||
|
||||
// Verify BLE advertisement data array size matches the BLE specification (31 bytes adv + 31 bytes scan response)
|
||||
static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62,
|
||||
@@ -25,18 +21,8 @@ static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62
|
||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
||||
|
||||
void BluetoothProxy::setup() {
|
||||
// Pre-allocate response object
|
||||
this->response_ = std::make_unique<api::BluetoothLERawAdvertisementsResponse>();
|
||||
|
||||
// Reserve capacity but start with size 0
|
||||
// Reserve 50% since we'll grow naturally and flush at FLUSH_BATCH_SIZE
|
||||
this->response_->advertisements.reserve(FLUSH_BATCH_SIZE / 2);
|
||||
|
||||
// Don't pre-allocate pool - let it grow only if needed in busy environments
|
||||
// Many devices in quiet areas will never need the overflow pool
|
||||
|
||||
this->connections_free_response_.limit = this->connections_.size();
|
||||
this->connections_free_response_.free = this->connections_.size();
|
||||
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
||||
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
||||
|
||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||
if (this->api_connection_ != nullptr) {
|
||||
@@ -53,6 +39,26 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
|
||||
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
void BluetoothProxy::log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, state: %s", connection->get_connection_index(),
|
||||
connection->address_str().c_str(), espbt::client_state_to_string(state));
|
||||
}
|
||||
|
||||
void BluetoothProxy::log_connection_info_(BluetoothConnection *connection, const char *message) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connecting %s", connection->get_connection_index(), connection->address_str().c_str(),
|
||||
message);
|
||||
}
|
||||
|
||||
void BluetoothProxy::log_not_connected_gatt_(const char *action, const char *type) {
|
||||
ESP_LOGW(TAG, "Cannot %s GATT %s, not connected", action, type);
|
||||
}
|
||||
|
||||
void BluetoothProxy::handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action,
|
||||
const char *type) {
|
||||
this->log_not_connected_gatt_(action, type);
|
||||
this->send_gatt_error(address, handle, ESP_GATT_NOT_CONNECTED);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
// This method should never be called since bluetooth_proxy always uses raw advertisements
|
||||
@@ -65,39 +71,27 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
|
||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
||||
return false;
|
||||
|
||||
auto &advertisements = this->response_->advertisements;
|
||||
auto &advertisements = this->response_.advertisements;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
auto &result = scan_results[i];
|
||||
uint8_t length = result.adv_data_len + result.scan_rsp_len;
|
||||
|
||||
// Check if we need to expand the vector
|
||||
if (this->advertisement_count_ >= advertisements.size()) {
|
||||
if (this->advertisement_pool_.empty()) {
|
||||
// No room in pool, need to allocate
|
||||
advertisements.emplace_back();
|
||||
} else {
|
||||
// Pull from pool
|
||||
advertisements.push_back(std::move(this->advertisement_pool_.back()));
|
||||
this->advertisement_pool_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the data directly at current position
|
||||
auto &adv = advertisements[this->advertisement_count_];
|
||||
auto &adv = advertisements[this->response_.advertisements_len];
|
||||
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
|
||||
adv.rssi = result.rssi;
|
||||
adv.address_type = result.ble_addr_type;
|
||||
adv.data_len = length;
|
||||
std::memcpy(adv.data, result.ble_adv, length);
|
||||
|
||||
this->advertisement_count_++;
|
||||
this->response_.advertisements_len++;
|
||||
|
||||
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
|
||||
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
|
||||
|
||||
// Flush if we have reached FLUSH_BATCH_SIZE
|
||||
if (this->advertisement_count_ >= FLUSH_BATCH_SIZE) {
|
||||
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
|
||||
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
|
||||
this->flush_pending_advertisements();
|
||||
}
|
||||
}
|
||||
@@ -106,40 +100,31 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
|
||||
}
|
||||
|
||||
void BluetoothProxy::flush_pending_advertisements() {
|
||||
if (this->advertisement_count_ == 0 || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
||||
if (this->response_.advertisements_len == 0 || !api::global_api_server->is_connected() ||
|
||||
this->api_connection_ == nullptr)
|
||||
return;
|
||||
|
||||
auto &advertisements = this->response_->advertisements;
|
||||
|
||||
// Return any items beyond advertisement_count_ to the pool
|
||||
if (advertisements.size() > this->advertisement_count_) {
|
||||
// Move unused items back to pool
|
||||
this->advertisement_pool_.insert(this->advertisement_pool_.end(),
|
||||
std::make_move_iterator(advertisements.begin() + this->advertisement_count_),
|
||||
std::make_move_iterator(advertisements.end()));
|
||||
|
||||
// Resize to actual count
|
||||
advertisements.resize(this->advertisement_count_);
|
||||
}
|
||||
|
||||
// Send the message
|
||||
this->api_connection_->send_message(*this->response_, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
|
||||
this->api_connection_->send_message(this->response_, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
|
||||
|
||||
// Reset count - existing items will be overwritten in next batch
|
||||
this->advertisement_count_ = 0;
|
||||
ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
|
||||
|
||||
// Reset the length for the next batch
|
||||
this->response_.advertisements_len = 0;
|
||||
}
|
||||
|
||||
void BluetoothProxy::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Bluetooth Proxy:\n"
|
||||
" Active: %s\n"
|
||||
" Connections: %d",
|
||||
YESNO(this->active_), this->connections_.size());
|
||||
YESNO(this->active_), this->connection_count_);
|
||||
}
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
|
||||
for (auto *connection : this->connections_) {
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
@@ -162,7 +147,8 @@ esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_par
|
||||
}
|
||||
|
||||
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
||||
for (auto *connection : this->connections_) {
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() == address)
|
||||
return connection;
|
||||
}
|
||||
@@ -170,9 +156,10 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
|
||||
if (!reserve)
|
||||
return nullptr;
|
||||
|
||||
for (auto *connection : this->connections_) {
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() == 0) {
|
||||
connection->send_service_ = DONE_SENDING_SERVICES;
|
||||
connection->send_service_ = INIT_SENDING_SERVICES;
|
||||
connection->set_address(address);
|
||||
// All connections must start at INIT
|
||||
// We only set the state if we allocate the connection
|
||||
@@ -189,8 +176,7 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
|
||||
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: {
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE: {
|
||||
auto *connection = this->get_connection_(msg.address, true);
|
||||
if (connection == nullptr) {
|
||||
ESP_LOGW(TAG, "No free connections available");
|
||||
@@ -199,23 +185,10 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||
}
|
||||
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());
|
||||
this->log_connection_request_ignored_(connection, connection->state());
|
||||
this->send_device_connection(msg.address, true);
|
||||
this->send_connections_free();
|
||||
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) {
|
||||
if (connection->disconnect_pending()) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection request while pending disconnect, cancelling pending disconnect",
|
||||
@@ -223,29 +196,18 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||
connection->cancel_pending_disconnect();
|
||||
return;
|
||||
}
|
||||
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());
|
||||
this->log_connection_request_ignored_(connection, connection->state());
|
||||
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());
|
||||
this->log_connection_request_ignored_(connection, connection->state());
|
||||
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) {
|
||||
this->log_connection_info_(connection, "v3 with cache");
|
||||
} else { // 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());
|
||||
this->log_connection_info_(connection, "v3 without cache");
|
||||
}
|
||||
if (msg.has_address_type) {
|
||||
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
||||
@@ -307,14 +269,18 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||
|
||||
break;
|
||||
}
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
||||
ESP_LOGE(TAG, "V1 connections removed");
|
||||
this->send_device_connection(msg.address, false);
|
||||
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");
|
||||
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "characteristic");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,8 +293,7 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms
|
||||
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");
|
||||
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "characteristic");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -341,8 +306,7 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
|
||||
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");
|
||||
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "read", "descriptor");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -355,8 +319,7 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead
|
||||
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");
|
||||
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "write", "descriptor");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -369,8 +332,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
|
||||
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");
|
||||
this->send_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
|
||||
this->handle_gatt_not_connected_(msg.address, 0, "get", "services");
|
||||
return;
|
||||
}
|
||||
if (!connection->service_count_) {
|
||||
@@ -378,16 +340,14 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer
|
||||
this->send_gatt_services_done(msg.address);
|
||||
return;
|
||||
}
|
||||
if (connection->send_service_ ==
|
||||
DONE_SENDING_SERVICES) // Only start sending services if we're not already sending them
|
||||
if (connection->send_service_ == INIT_SENDING_SERVICES) // Start sending services if not started yet
|
||||
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");
|
||||
this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||
this->handle_gatt_not_connected_(msg.address, msg.handle, "notify", "characteristic");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
@@ -22,6 +23,7 @@ namespace esphome::bluetooth_proxy {
|
||||
|
||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||
static const int DONE_SENDING_SERVICES = -2;
|
||||
static const int INIT_SENDING_SERVICES = -3;
|
||||
|
||||
using namespace esp32_ble_client;
|
||||
|
||||
@@ -48,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
||||
};
|
||||
|
||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
friend class BluetoothConnection; // Allow connection to update connections_free_response_
|
||||
public:
|
||||
BluetoothProxy();
|
||||
@@ -63,8 +65,10 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
void register_connection(BluetoothConnection *connection) {
|
||||
this->connections_.push_back(connection);
|
||||
connection->proxy_ = this;
|
||||
if (this->connection_count_ < BLUETOOTH_PROXY_MAX_CONNECTIONS) {
|
||||
this->connections_[this->connection_count_++] = connection;
|
||||
connection->proxy_ = this;
|
||||
}
|
||||
}
|
||||
|
||||
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
||||
@@ -133,17 +137,20 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
||||
|
||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||
void log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state);
|
||||
void log_connection_info_(BluetoothConnection *connection, const char *message);
|
||||
void log_not_connected_gatt_(const char *action, const char *type);
|
||||
void handle_gatt_not_connected_(uint64_t address, uint16_t handle, const char *action, const char *type);
|
||||
|
||||
// Memory optimized layout for 32-bit systems
|
||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||
api::APIConnection *api_connection_{nullptr};
|
||||
|
||||
// Group 2: Container types (typically 12 bytes on 32-bit)
|
||||
std::vector<BluetoothConnection *> connections_{};
|
||||
// Group 2: Fixed-size array of connection pointers
|
||||
std::array<BluetoothConnection *, BLUETOOTH_PROXY_MAX_CONNECTIONS> connections_{};
|
||||
|
||||
// BLE advertisement batching
|
||||
std::vector<api::BluetoothLERawAdvertisement> advertisement_pool_;
|
||||
std::unique_ptr<api::BluetoothLERawAdvertisementsResponse> response_;
|
||||
api::BluetoothLERawAdvertisementsResponse response_;
|
||||
|
||||
// Group 3: 4-byte types
|
||||
uint32_t last_advertisement_flush_time_{0};
|
||||
@@ -153,7 +160,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
|
||||
// Group 4: 1-byte types grouped together
|
||||
bool active_;
|
||||
uint8_t advertisement_count_{0};
|
||||
uint8_t connection_count_{0};
|
||||
// 2 bytes used, 2 bytes padding
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <esphome/components/sensor/sensor.h>
|
||||
#include <esphome/core/component.h>
|
||||
|
||||
#define BME280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
|
||||
|
||||
namespace esphome {
|
||||
namespace bme280_base {
|
||||
|
||||
@@ -98,18 +100,18 @@ void BME280Component::setup() {
|
||||
|
||||
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x60) {
|
||||
this->error_code_ = WRONG_CHIP_ID;
|
||||
this->mark_failed();
|
||||
this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a soft reset.
|
||||
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
|
||||
this->mark_failed();
|
||||
this->mark_failed("Reset failed");
|
||||
return;
|
||||
}
|
||||
// Wait until the NVM data has finished loading.
|
||||
@@ -118,14 +120,12 @@ void BME280Component::setup() {
|
||||
do { // NOLINT
|
||||
delay(2);
|
||||
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
|
||||
ESP_LOGW(TAG, "Error reading status register.");
|
||||
this->mark_failed();
|
||||
this->mark_failed("Error reading status register");
|
||||
return;
|
||||
}
|
||||
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
|
||||
if (status & BME280_STATUS_IM_UPDATE) {
|
||||
ESP_LOGW(TAG, "Timeout loading NVM.");
|
||||
this->mark_failed();
|
||||
this->mark_failed("Timeout loading NVM");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,26 +153,26 @@ void BME280Component::setup() {
|
||||
|
||||
uint8_t humid_control_val = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
|
||||
this->mark_failed();
|
||||
this->mark_failed("Read humidity control");
|
||||
return;
|
||||
}
|
||||
humid_control_val &= ~0b00000111;
|
||||
humid_control_val |= this->humidity_oversampling_ & 0b111;
|
||||
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
|
||||
this->mark_failed();
|
||||
this->mark_failed("Write humidity control");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed();
|
||||
this->mark_failed("Read config");
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
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();
|
||||
this->mark_failed("Write config");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ void BME280Component::dump_config() {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
break;
|
||||
case WRONG_CHIP_ID:
|
||||
ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
|
||||
ESP_LOGE(TAG, BME280_ERROR_WRONG_CHIP_ID);
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
@@ -223,21 +223,21 @@ void BME280Component::update() {
|
||||
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
|
||||
uint8_t data[8];
|
||||
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
|
||||
ESP_LOGW(TAG, "Error reading registers.");
|
||||
ESP_LOGW(TAG, "Error reading registers");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
int32_t t_fine = 0;
|
||||
float const temperature = this->read_temperature_(data, &t_fine);
|
||||
if (std::isnan(temperature)) {
|
||||
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
|
||||
ESP_LOGW(TAG, "Invalid temperature");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
float const pressure = this->read_pressure_(data, t_fine);
|
||||
float const humidity = this->read_humidity_(data, t_fine);
|
||||
|
||||
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
||||
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa Humidity=%.1f%%", temperature, pressure, humidity);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
|
||||
@@ -28,7 +28,7 @@ const float BME680_GAS_LOOKUP_TABLE_1[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
const float BME680_GAS_LOOKUP_TABLE_2[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8,
|
||||
-0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
|
||||
static const char *oversampling_to_str(BME680Oversampling oversampling) {
|
||||
[[maybe_unused]] static const char *oversampling_to_str(BME680Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case BME680_OVERSAMPLING_NONE:
|
||||
return "None";
|
||||
@@ -47,7 +47,7 @@ static const char *oversampling_to_str(BME680Oversampling oversampling) {
|
||||
}
|
||||
}
|
||||
|
||||
static const char *iir_filter_to_str(BME680IIRFilter filter) {
|
||||
[[maybe_unused]] static const char *iir_filter_to_str(BME680IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case BME680_IIR_FILTER_OFF:
|
||||
return "OFF";
|
||||
|
||||
@@ -203,7 +203,7 @@ void BMI160Component::dump_config() {
|
||||
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
|
||||
uint8_t raw_data[len * 2];
|
||||
// read using read_register because we have little-endian data, and read_bytes_16 will swap it
|
||||
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true);
|
||||
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#define BMP280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp280_base {
|
||||
|
||||
@@ -61,25 +63,25 @@ void BMP280Component::setup() {
|
||||
|
||||
// Read the chip id twice, to work around a bug where the first read is 0.
|
||||
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
|
||||
if (!this->read_byte(0xD0, &chip_id)) {
|
||||
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
if (!this->read_byte(0xD0, &chip_id)) {
|
||||
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||
return;
|
||||
}
|
||||
if (chip_id != 0x58) {
|
||||
this->error_code_ = WRONG_CHIP_ID;
|
||||
this->mark_failed();
|
||||
this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a soft reset.
|
||||
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
||||
this->mark_failed();
|
||||
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
||||
this->mark_failed("Reset failed");
|
||||
return;
|
||||
}
|
||||
// Wait until the NVM data has finished loading.
|
||||
@@ -87,15 +89,13 @@ void BMP280Component::setup() {
|
||||
uint8_t retry = 5;
|
||||
do {
|
||||
delay(2);
|
||||
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
|
||||
ESP_LOGW(TAG, "Error reading status register.");
|
||||
this->mark_failed();
|
||||
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
|
||||
this->mark_failed("Error reading status register");
|
||||
return;
|
||||
}
|
||||
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
|
||||
if (status & BMP280_STATUS_IM_UPDATE) {
|
||||
ESP_LOGW(TAG, "Timeout loading NVM.");
|
||||
this->mark_failed();
|
||||
this->mark_failed("Timeout loading NVM");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,15 +115,15 @@ void BMP280Component::setup() {
|
||||
this->calibration_.p9 = this->read_s16_le_(0x9E);
|
||||
|
||||
uint8_t config_register = 0;
|
||||
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed();
|
||||
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
||||
this->mark_failed("Read config");
|
||||
return;
|
||||
}
|
||||
config_register &= ~0b11111100;
|
||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed();
|
||||
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
||||
this->mark_failed("Write config");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ void BMP280Component::dump_config() {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
break;
|
||||
case WRONG_CHIP_ID:
|
||||
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?");
|
||||
ESP_LOGE(TAG, BMP280_ERROR_WRONG_CHIP_ID);
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
@@ -159,7 +159,7 @@ void BMP280Component::update() {
|
||||
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||
meas_value |= 0b01; // Forced mode
|
||||
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
|
||||
if (!this->bmp_write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@@ -172,13 +172,13 @@ void BMP280Component::update() {
|
||||
int32_t t_fine = 0;
|
||||
float temperature = this->read_temperature_(&t_fine);
|
||||
if (std::isnan(temperature)) {
|
||||
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values.");
|
||||
ESP_LOGW(TAG, "Invalid temperature");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
float pressure = this->read_pressure_(t_fine);
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
|
||||
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa", temperature, pressure);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
@@ -188,9 +188,10 @@ void BMP280Component::update() {
|
||||
}
|
||||
|
||||
float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
|
||||
uint8_t data[3]{};
|
||||
if (!this->bmp_read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
|
||||
return NAN;
|
||||
ESP_LOGV(TAG, "Read temperature data, raw: %02X %02X %02X", data[0], data[1], data[2]);
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
if (adc == 0x80000) {
|
||||
@@ -212,7 +213,7 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||
|
||||
float BMP280Component::read_pressure_(int32_t t_fine) {
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
|
||||
if (!this->bmp_read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
|
||||
return NAN;
|
||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||
adc >>= 4;
|
||||
@@ -258,12 +259,12 @@ void BMP280Component::set_pressure_oversampling(BMP280Oversampling pressure_over
|
||||
void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
|
||||
uint8_t data = 0;
|
||||
this->read_byte(a_register, &data);
|
||||
this->bmp_read_byte(a_register, &data);
|
||||
return data;
|
||||
}
|
||||
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
|
||||
uint16_t data = 0;
|
||||
this->read_byte_16(a_register, &data);
|
||||
this->bmp_read_byte_16(a_register, &data);
|
||||
return (data >> 8) | (data << 8);
|
||||
}
|
||||
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
||||
|
||||
@@ -67,12 +67,12 @@ class BMP280Component : public PollingComponent {
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
|
||||
virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
|
||||
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||
virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0;
|
||||
|
||||
protected:
|
||||
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
|
||||
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
|
||||
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||
virtual bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) = 0;
|
||||
|
||||
/// Read the temperature value and store the calculated ambient temperature in t_fine.
|
||||
float read_temperature_(int32_t *t_fine);
|
||||
/// Read the pressure value in hPa using the provided t_fine value.
|
||||
|
||||
@@ -5,19 +5,6 @@
|
||||
namespace esphome {
|
||||
namespace bmp280_i2c {
|
||||
|
||||
bool BMP280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) {
|
||||
return I2CDevice::read_byte(a_register, data);
|
||||
};
|
||||
bool BMP280I2CComponent::write_byte(uint8_t a_register, uint8_t data) {
|
||||
return I2CDevice::write_byte(a_register, data);
|
||||
};
|
||||
bool BMP280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
return I2CDevice::read_bytes(a_register, data, len);
|
||||
};
|
||||
bool BMP280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
|
||||
return I2CDevice::read_byte_16(a_register, data);
|
||||
};
|
||||
|
||||
void BMP280I2CComponent::dump_config() {
|
||||
LOG_I2C_DEVICE(this);
|
||||
BMP280Component::dump_config();
|
||||
|
||||
@@ -11,10 +11,12 @@ static const char *const TAG = "bmp280_i2c.sensor";
|
||||
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
|
||||
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
|
||||
public:
|
||||
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
|
||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
|
||||
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
|
||||
return read_bytes(a_register, data, len);
|
||||
}
|
||||
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override { return read_byte_16(a_register, data); }
|
||||
void dump_config() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ void BMP280SPIComponent::setup() {
|
||||
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
|
||||
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
|
||||
|
||||
bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
|
||||
bool BMP280SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
|
||||
this->enable();
|
||||
this->transfer_byte(set_bit(a_register, 7));
|
||||
*data = this->transfer_byte(0);
|
||||
@@ -36,7 +36,7 @@ bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
|
||||
bool BMP280SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
|
||||
this->enable();
|
||||
this->transfer_byte(clear_bit(a_register, 7));
|
||||
this->transfer_byte(data);
|
||||
@@ -44,7 +44,7 @@ bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
bool BMP280SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
this->enable();
|
||||
this->transfer_byte(set_bit(a_register, 7));
|
||||
this->read_array(data, len);
|
||||
@@ -52,7 +52,7 @@ bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t le
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
|
||||
bool BMP280SPIComponent::bmp_read_byte_16(uint8_t a_register, uint16_t *data) {
|
||||
this->enable();
|
||||
this->transfer_byte(set_bit(a_register, 7));
|
||||
((uint8_t *) data)[1] = this->transfer_byte(0);
|
||||
|
||||
@@ -10,10 +10,10 @@ class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
||||
void setup() override;
|
||||
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
|
||||
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
|
||||
bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
|
||||
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override;
|
||||
};
|
||||
|
||||
} // namespace bmp280_spi
|
||||
|
||||
@@ -6,6 +6,19 @@ namespace button {
|
||||
|
||||
static const char *const TAG = "button";
|
||||
|
||||
// Function implementation of LOG_BUTTON macro to reduce code size
|
||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj) {
|
||||
if (obj == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||
|
||||
if (!obj->get_icon().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Button::press() {
|
||||
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
|
||||
this->press_action();
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
namespace esphome {
|
||||
namespace button {
|
||||
|
||||
#define LOG_BUTTON(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
}
|
||||
class Button;
|
||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj);
|
||||
|
||||
#define LOG_BUTTON(prefix, type, obj) log_button(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||
|
||||
#define SUB_BUTTON(name) \
|
||||
protected: \
|
||||
|
||||
@@ -14,7 +14,7 @@ from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
||||
DEPENDENCIES = ["wifi"]
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
|
||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
|
||||
|
||||
@@ -153,8 +153,8 @@ void CCS811Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CCS811");
|
||||
LOG_I2C_DEVICE(this)
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
|
||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
|
||||
LOG_SENSOR(" ", "CO2 Sensor", this->co2_);
|
||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
|
||||
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
|
||||
if (this->baseline_) {
|
||||
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
|
||||
|
||||
@@ -91,7 +91,7 @@ bool CH422GComponent::read_inputs_() {
|
||||
|
||||
// Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
|
||||
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
|
||||
auto err = this->bus_->write(reg, &value, 1);
|
||||
auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
|
||||
return false;
|
||||
@@ -102,7 +102,7 @@ bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
|
||||
|
||||
uint8_t CH422GComponent::read_reg_(uint8_t reg) {
|
||||
uint8_t value;
|
||||
auto err = this->bus_->read(reg, &value, 1);
|
||||
auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
|
||||
return 0;
|
||||
|
||||
@@ -327,7 +327,7 @@ void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callb
|
||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||
|
||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^
|
||||
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_preference_hash() ^
|
||||
RESTORE_STATE_VERSION);
|
||||
ClimateDeviceRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
|
||||
@@ -228,9 +228,9 @@ async def cover_stop_to_code(config, action_id, template_arg, args):
|
||||
|
||||
|
||||
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
|
||||
def cover_toggle_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
async def cover_toggle_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)
|
||||
|
||||
|
||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
|
||||
@@ -99,43 +99,39 @@ const optional<float> &CoverCall::get_tilt() const { return this->tilt_; }
|
||||
const optional<bool> &CoverCall::get_toggle() const { return this->toggle_; }
|
||||
void CoverCall::validate_() {
|
||||
auto traits = this->parent_->get_traits();
|
||||
const char *name = this->parent_->get_name().c_str();
|
||||
|
||||
if (this->position_.has_value()) {
|
||||
auto pos = *this->position_;
|
||||
if (!traits.get_supports_position() && pos != COVER_OPEN && pos != COVER_CLOSED) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support setting position!", this->parent_->get_name().c_str());
|
||||
ESP_LOGW(TAG, "'%s': position unsupported", name);
|
||||
this->position_.reset();
|
||||
} else if (pos < 0.0f || pos > 1.0f) {
|
||||
ESP_LOGW(TAG, "'%s' - Position %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), pos);
|
||||
ESP_LOGW(TAG, "'%s': position %.2f out of range", name, pos);
|
||||
this->position_ = clamp(pos, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
auto tilt = *this->tilt_;
|
||||
if (!traits.get_supports_tilt()) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support tilt!", this->parent_->get_name().c_str());
|
||||
ESP_LOGW(TAG, "'%s': tilt unsupported", name);
|
||||
this->tilt_.reset();
|
||||
} else if (tilt < 0.0f || tilt > 1.0f) {
|
||||
ESP_LOGW(TAG, "'%s' - Tilt %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), tilt);
|
||||
ESP_LOGW(TAG, "'%s': tilt %.2f out of range", name, tilt);
|
||||
this->tilt_ = clamp(tilt, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
if (!traits.get_supports_toggle()) {
|
||||
ESP_LOGW(TAG, "'%s' - This cover device does not support toggle!", this->parent_->get_name().c_str());
|
||||
ESP_LOGW(TAG, "'%s': toggle unsupported", name);
|
||||
this->toggle_.reset();
|
||||
}
|
||||
}
|
||||
if (this->stop_) {
|
||||
if (this->position_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set position when stopping a cover!");
|
||||
if (this->position_.has_value() || this->tilt_.has_value() || this->toggle_.has_value()) {
|
||||
ESP_LOGW(TAG, "'%s': cannot position/tilt/toggle when stopping", name);
|
||||
this->position_.reset();
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set tilt when stopping a cover!");
|
||||
this->tilt_.reset();
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
ESP_LOGW(TAG, "Cannot set toggle when stopping a cover!");
|
||||
this->toggle_.reset();
|
||||
}
|
||||
}
|
||||
@@ -198,7 +194,7 @@ void Cover::publish_state(bool save) {
|
||||
}
|
||||
}
|
||||
optional<CoverRestoreState> Cover::restore_state_() {
|
||||
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_object_id_hash());
|
||||
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_preference_hash());
|
||||
CoverRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
|
||||
@@ -13,7 +13,7 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["logger"]
|
||||
|
||||
CONF_DEBUG_ID = "debug_id"
|
||||
@@ -48,6 +48,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
if CORE.using_zephyr:
|
||||
zephyr_add_prj_conf("HWINFO", True)
|
||||
# gdb thread support
|
||||
zephyr_add_prj_conf("DEBUG_THREAD_INFO", True)
|
||||
# RTT
|
||||
zephyr_add_prj_conf("USE_SEGGER_RTT", True)
|
||||
zephyr_add_prj_conf("RTT_CONSOLE", True)
|
||||
zephyr_add_prj_conf("LOG", True)
|
||||
zephyr_add_prj_conf("LOG_BLOCK_IN_THREAD", True)
|
||||
zephyr_add_prj_conf("LOG_BUFFER_SIZE", 4096)
|
||||
zephyr_add_prj_conf("SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL", True)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#ifdef USE_ESP32
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "deep_sleep_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -74,11 +76,24 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
||||
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
|
||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
|
||||
} else if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLDOWN) {
|
||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
|
||||
}
|
||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||
gpio_hold_en(gpio_pin);
|
||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
|
||||
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
|
||||
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
|
||||
gpio_deep_sleep_hold_en();
|
||||
#endif
|
||||
bool level = !this->wakeup_pin_->is_inverted();
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||
level = !level;
|
||||
}
|
||||
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
|
||||
esp_sleep_enable_ext0_wakeup(gpio_pin, level);
|
||||
}
|
||||
if (this->ext1_wakeup_.has_value()) {
|
||||
esp_sleep_enable_ext1_wakeup(this->ext1_wakeup_->mask, this->ext1_wakeup_->wakeup_mode);
|
||||
@@ -102,6 +117,19 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
if (this->sleep_duration_.has_value())
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
||||
if (this->wakeup_pin_->get_flags() && gpio::FLAG_PULLUP) {
|
||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
|
||||
} else if (this->wakeup_pin_->get_flags() && gpio::FLAG_PULLDOWN) {
|
||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
|
||||
}
|
||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||
gpio_hold_en(gpio_pin);
|
||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
|
||||
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
|
||||
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
|
||||
gpio_deep_sleep_hold_en();
|
||||
#endif
|
||||
bool level = !this->wakeup_pin_->is_inverted();
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||
level = !level;
|
||||
|
||||
@@ -12,6 +12,8 @@ from esphome.const import (
|
||||
CONF_ROTATION,
|
||||
CONF_TO,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
SCHEDULER_DONT_RUN,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
@@ -67,6 +69,18 @@ BASIC_DISPLAY_SCHEMA = cv.Schema(
|
||||
}
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
|
||||
|
||||
def _validate_test_card(config):
|
||||
if (
|
||||
config.get(CONF_SHOW_TEST_CARD, False)
|
||||
and config.get(CONF_UPDATE_INTERVAL, False) == SCHEDULER_DONT_RUN
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"`{CONF_SHOW_TEST_CARD}: True` cannot be used with `{CONF_UPDATE_INTERVAL}: never` because this combination will not show a test_card."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ROTATION): validate_rotation,
|
||||
@@ -94,6 +108,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
||||
}
|
||||
)
|
||||
FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card)
|
||||
|
||||
|
||||
async def setup_display_core_(var, config):
|
||||
@@ -200,7 +215,6 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
|
||||
page = await cg.get_variable(config[CONF_PAGE_ID])
|
||||
var = cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
cg.add(var.set_page(page))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ void DutyTimeSensor::setup() {
|
||||
uint32_t seconds = 0;
|
||||
|
||||
if (this->restore_) {
|
||||
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_object_id_hash());
|
||||
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_preference_hash());
|
||||
this->pref_.load(&seconds);
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) {
|
||||
crc16 = calc_crc16_(address, 6);
|
||||
address[5] = crc16 & 0xFF;
|
||||
address[6] = (crc16 >> 8) & 0xFF;
|
||||
this->write(address, 7, true);
|
||||
this->write(address, 7);
|
||||
}
|
||||
|
||||
float EE895Component::read_float_() {
|
||||
|
||||
@@ -15,6 +15,7 @@ from esphome.const import (
|
||||
CONF_FRAMEWORK,
|
||||
CONF_IGNORE_EFUSE_CUSTOM_MAC,
|
||||
CONF_IGNORE_EFUSE_MAC_CRC,
|
||||
CONF_LOG_LEVEL,
|
||||
CONF_NAME,
|
||||
CONF_PATH,
|
||||
CONF_PLATFORM_VERSION,
|
||||
@@ -79,6 +80,15 @@ CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
||||
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
||||
CONF_RELEASE = "release"
|
||||
|
||||
LOG_LEVELS_IDF = [
|
||||
"NONE",
|
||||
"ERROR",
|
||||
"WARN",
|
||||
"INFO",
|
||||
"DEBUG",
|
||||
"VERBOSE",
|
||||
]
|
||||
|
||||
ASSERTION_LEVELS = {
|
||||
"DISABLE": "CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE",
|
||||
"ENABLE": "CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE",
|
||||
@@ -623,6 +633,9 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
||||
cv.string_strict: cv.string_strict
|
||||
},
|
||||
cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of(
|
||||
*LOG_LEVELS_IDF, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of(
|
||||
@@ -811,8 +824,9 @@ async def to_code(config):
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_build_flag("-DUSE_ESP32")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
|
||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
|
||||
variant = config[CONF_VARIANT]
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}")
|
||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant])
|
||||
cg.add_define(ThreadModel.MULTI_ATOMICS)
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
@@ -846,6 +860,7 @@ async def to_code(config):
|
||||
cg.add_platformio_option(
|
||||
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
|
||||
)
|
||||
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
|
||||
add_idf_sdkconfig_option(
|
||||
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
|
||||
)
|
||||
@@ -937,6 +952,10 @@ async def to_code(config):
|
||||
),
|
||||
)
|
||||
|
||||
add_idf_sdkconfig_option(
|
||||
f"CONFIG_LOG_DEFAULT_LEVEL_{conf[CONF_LOG_LEVEL]}", True
|
||||
)
|
||||
|
||||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cinttypes>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32 {
|
||||
@@ -156,20 +157,23 @@ class ESP32Preferences : public ESPPreferences {
|
||||
return failed == 0;
|
||||
}
|
||||
bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
|
||||
NVSData stored_data{};
|
||||
size_t actual_len;
|
||||
esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len);
|
||||
if (err != 0) {
|
||||
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.resize(actual_len);
|
||||
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.data.data(), &actual_len);
|
||||
// Check size first before allocating memory
|
||||
if (actual_len != to_save.data.size()) {
|
||||
return true;
|
||||
}
|
||||
auto stored_data = std::make_unique<uint8_t[]>(actual_len);
|
||||
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len);
|
||||
if (err != 0) {
|
||||
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
|
||||
return true;
|
||||
}
|
||||
return to_save.data != stored_data.data;
|
||||
return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0;
|
||||
}
|
||||
|
||||
bool reset() override {
|
||||
|
||||
@@ -3,15 +3,33 @@ import re
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32S3,
|
||||
add_idf_sdkconfig_option,
|
||||
const,
|
||||
get_esp32_variant,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
|
||||
from esphome.const import (
|
||||
CONF_ENABLE_ON_BOOT,
|
||||
CONF_ESPHOME,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_NAME_ADD_MAC_SUFFIX,
|
||||
CONF_TX_POWER,
|
||||
)
|
||||
from esphome.core import CORE, TimePeriod
|
||||
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
|
||||
from esphome.cpp_types import MockObj
|
||||
import esphome.final_validate as fv
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CODEOWNERS = ["@jesserockz", "@Rapsssito"]
|
||||
CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"]
|
||||
DOMAIN = "esp32_ble"
|
||||
|
||||
|
||||
class BTLoggers(Enum):
|
||||
@@ -115,9 +133,11 @@ def register_bt_logger(*loggers: BTLoggers) -> None:
|
||||
|
||||
CONF_BLE_ID = "ble_id"
|
||||
CONF_IO_CAPABILITY = "io_capability"
|
||||
CONF_ADVERTISING = "advertising"
|
||||
CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time"
|
||||
CONF_DISABLE_BT_LOGS = "disable_bt_logs"
|
||||
CONF_CONNECTION_TIMEOUT = "connection_timeout"
|
||||
CONF_MAX_NOTIFICATIONS = "max_notifications"
|
||||
|
||||
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
@@ -143,7 +163,8 @@ IO_CAPABILITY = {
|
||||
|
||||
esp_power_level_t = cg.global_ns.enum("esp_power_level_t")
|
||||
|
||||
TX_POWER_LEVELS = {
|
||||
# Power level mappings for code generation - ESP32 classic
|
||||
TX_POWER_LEVELS_ESP32 = {
|
||||
-12: esp_power_level_t.ESP_PWR_LVL_N12,
|
||||
-9: esp_power_level_t.ESP_PWR_LVL_N9,
|
||||
-6: esp_power_level_t.ESP_PWR_LVL_N6,
|
||||
@@ -154,6 +175,53 @@ TX_POWER_LEVELS = {
|
||||
9: esp_power_level_t.ESP_PWR_LVL_P9,
|
||||
}
|
||||
|
||||
# Power level mappings for code generation - Extended variants
|
||||
TX_POWER_LEVELS_EXT = {
|
||||
-24: esp_power_level_t.ESP_PWR_LVL_N24,
|
||||
-21: esp_power_level_t.ESP_PWR_LVL_N21,
|
||||
-18: esp_power_level_t.ESP_PWR_LVL_N18,
|
||||
-15: esp_power_level_t.ESP_PWR_LVL_N15,
|
||||
-12: esp_power_level_t.ESP_PWR_LVL_N12,
|
||||
-9: esp_power_level_t.ESP_PWR_LVL_N9,
|
||||
-6: esp_power_level_t.ESP_PWR_LVL_N6,
|
||||
-3: esp_power_level_t.ESP_PWR_LVL_N3,
|
||||
0: esp_power_level_t.ESP_PWR_LVL_N0,
|
||||
3: esp_power_level_t.ESP_PWR_LVL_P3,
|
||||
6: esp_power_level_t.ESP_PWR_LVL_P6,
|
||||
9: esp_power_level_t.ESP_PWR_LVL_P9,
|
||||
12: esp_power_level_t.ESP_PWR_LVL_P12,
|
||||
15: esp_power_level_t.ESP_PWR_LVL_P15,
|
||||
18: esp_power_level_t.ESP_PWR_LVL_P18,
|
||||
20: esp_power_level_t.ESP_PWR_LVL_P20,
|
||||
}
|
||||
|
||||
|
||||
def _get_tx_power_levels() -> dict[str, MockObj]:
|
||||
variant = get_esp32_variant()
|
||||
if variant in [
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32S3,
|
||||
]:
|
||||
return TX_POWER_LEVELS_EXT
|
||||
return TX_POWER_LEVELS_ESP32
|
||||
|
||||
|
||||
def validate_tx_power(value: int) -> int:
|
||||
value = cv.decibel(value)
|
||||
power_levels = _get_tx_power_levels()
|
||||
if value not in power_levels:
|
||||
raise cv.Invalid(
|
||||
f"TX power {value}dBm is not valid. "
|
||||
f"Valid values are: {', '.join(str(v) + 'dBm' for v in sorted(power_levels.keys()))}"
|
||||
)
|
||||
# Return just the dBm value, we'll map it to enum in to_code
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLE),
|
||||
@@ -161,7 +229,9 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
|
||||
IO_CAPABILITY, lower=True
|
||||
),
|
||||
cv.Optional(CONF_TX_POWER): validate_tx_power,
|
||||
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_ADVERTISING, default=False): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ADVERTISING_CYCLE_TIME, default="10s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
@@ -173,6 +243,11 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.positive_time_period_seconds,
|
||||
cv.Range(min=TimePeriod(seconds=10), max=TimePeriod(seconds=180)),
|
||||
),
|
||||
cv.SplitDefault(CONF_MAX_NOTIFICATIONS, esp32_idf=12): cv.All(
|
||||
cv.only_with_esp_idf,
|
||||
cv.positive_int,
|
||||
cv.Range(min=1, max=64),
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -245,6 +320,9 @@ async def to_code(config):
|
||||
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
|
||||
if (name := config.get(CONF_NAME)) is not None:
|
||||
cg.add(var.set_name(name))
|
||||
if (tx_power := config.get(CONF_TX_POWER)) is not None:
|
||||
# The validation already returned the enum value
|
||||
cg.add(var.set_tx_power(_get_tx_power_levels()[tx_power]))
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
@@ -271,9 +349,26 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds
|
||||
)
|
||||
# Increase GATT client connection retry count for problematic devices
|
||||
# Default in ESP-IDF is 3, we increase to 10 for better reliability with
|
||||
# low-power/timing-sensitive devices
|
||||
add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
|
||||
|
||||
# Set the maximum number of notification registrations
|
||||
# This controls how many BLE characteristics can have notifications enabled
|
||||
# across all connections for a single GATT client interface
|
||||
# https://github.com/esphome/issues/issues/6808
|
||||
if CONF_MAX_NOTIFICATIONS in config:
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_BT_GATTC_NOTIF_REG_MAX", config[CONF_MAX_NOTIFICATIONS]
|
||||
)
|
||||
|
||||
cg.add_define("USE_ESP32_BLE")
|
||||
|
||||
if config[CONF_ADVERTISING]:
|
||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
||||
cg.add_define("USE_ESP32_BLE_UUID")
|
||||
|
||||
|
||||
@automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({}))
|
||||
async def ble_enabled_to_code(config, condition_id, template_arg, args):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "ble.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -53,6 +53,7 @@ void ESP32BLE::disable() {
|
||||
|
||||
bool ESP32BLE::is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; }
|
||||
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
void ESP32BLE::advertising_start() {
|
||||
this->advertising_init_();
|
||||
if (!this->is_active())
|
||||
@@ -88,6 +89,7 @@ void ESP32BLE::advertising_remove_service_uuid(ESPBTUUID uuid) {
|
||||
this->advertising_->remove_service_uuid(uuid);
|
||||
this->advertising_start();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ESP32BLE::ble_pre_setup_() {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
@@ -98,6 +100,7 @@ bool ESP32BLE::ble_pre_setup_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
void ESP32BLE::advertising_init_() {
|
||||
if (this->advertising_ != nullptr)
|
||||
return;
|
||||
@@ -107,6 +110,7 @@ void ESP32BLE::advertising_init_() {
|
||||
this->advertising_->set_min_preferred_interval(0x06);
|
||||
this->advertising_->set_appearance(this->appearance_);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ESP32BLE::ble_setup_() {
|
||||
esp_err_t err;
|
||||
@@ -208,6 +212,15 @@ bool ESP32BLE::ble_setup_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set TX power for all BLE operations (advertising, scanning, connections)
|
||||
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, this->tx_power_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
|
||||
// Continue anyway as this is not critical
|
||||
} else {
|
||||
ESP_LOGD(TAG, "BLE TX power set to level %d", this->tx_power_);
|
||||
}
|
||||
|
||||
// BLE takes some time to be fully set up, 200ms should be more than enough
|
||||
delay(200); // NOLINT
|
||||
|
||||
@@ -302,7 +315,7 @@ void ESP32BLE::loop() {
|
||||
case BLEEvent::GATTS: {
|
||||
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
|
||||
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
|
||||
esp_ble_gatts_cb_param_t *param = ble_event->event_.gatts.gatts_param;
|
||||
esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
|
||||
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
|
||||
for (auto *gatts_handler : this->gatts_event_handlers_) {
|
||||
gatts_handler->gatts_event_handler(event, gatts_if, param);
|
||||
@@ -312,7 +325,7 @@ void ESP32BLE::loop() {
|
||||
case BLEEvent::GATTC: {
|
||||
esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
|
||||
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
|
||||
esp_ble_gattc_cb_param_t *param = ble_event->event_.gattc.gattc_param;
|
||||
esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
|
||||
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
|
||||
for (auto *gattc_handler : this->gattc_event_handlers_) {
|
||||
gattc_handler->gattc_event_handler(event, gattc_if, param);
|
||||
@@ -394,9 +407,11 @@ void ESP32BLE::loop() {
|
||||
this->ble_event_pool_.release(ble_event);
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
if (this->advertising_ != nullptr) {
|
||||
this->advertising_->loop();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Log dropped events periodically
|
||||
uint16_t dropped = this->ble_events_.get_and_reset_dropped_count();
|
||||
@@ -514,11 +529,106 @@ void ESP32BLE::dump_config() {
|
||||
io_capability_s = "invalid";
|
||||
break;
|
||||
}
|
||||
// Convert TX power level to dBm for display
|
||||
int tx_power_dbm = 0;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
// ESP32 classic power levels (0-7)
|
||||
switch (this->tx_power_) {
|
||||
case 0:
|
||||
tx_power_dbm = -12;
|
||||
break; // ESP_PWR_LVL_N12
|
||||
case 1:
|
||||
tx_power_dbm = -9;
|
||||
break; // ESP_PWR_LVL_N9
|
||||
case 2:
|
||||
tx_power_dbm = -6;
|
||||
break; // ESP_PWR_LVL_N6
|
||||
case 3:
|
||||
tx_power_dbm = -3;
|
||||
break; // ESP_PWR_LVL_N3
|
||||
case 4:
|
||||
tx_power_dbm = 0;
|
||||
break; // ESP_PWR_LVL_N0
|
||||
case 5:
|
||||
tx_power_dbm = 3;
|
||||
break; // ESP_PWR_LVL_P3
|
||||
case 6:
|
||||
tx_power_dbm = 6;
|
||||
break; // ESP_PWR_LVL_P6
|
||||
case 7:
|
||||
tx_power_dbm = 9;
|
||||
break; // ESP_PWR_LVL_P9
|
||||
default:
|
||||
tx_power_dbm = 0;
|
||||
break;
|
||||
}
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || \
|
||||
defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || \
|
||||
defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// Extended power levels for C2/C3/C5/C6/H2/S3 (0-15)
|
||||
switch (this->tx_power_) {
|
||||
case 0:
|
||||
tx_power_dbm = -24;
|
||||
break; // ESP_PWR_LVL_N24
|
||||
case 1:
|
||||
tx_power_dbm = -21;
|
||||
break; // ESP_PWR_LVL_N21
|
||||
case 2:
|
||||
tx_power_dbm = -18;
|
||||
break; // ESP_PWR_LVL_N18
|
||||
case 3:
|
||||
tx_power_dbm = -15;
|
||||
break; // ESP_PWR_LVL_N15
|
||||
case 4:
|
||||
tx_power_dbm = -12;
|
||||
break; // ESP_PWR_LVL_N12
|
||||
case 5:
|
||||
tx_power_dbm = -9;
|
||||
break; // ESP_PWR_LVL_N9
|
||||
case 6:
|
||||
tx_power_dbm = -6;
|
||||
break; // ESP_PWR_LVL_N6
|
||||
case 7:
|
||||
tx_power_dbm = -3;
|
||||
break; // ESP_PWR_LVL_N3
|
||||
case 8:
|
||||
tx_power_dbm = 0;
|
||||
break; // ESP_PWR_LVL_N0
|
||||
case 9:
|
||||
tx_power_dbm = 3;
|
||||
break; // ESP_PWR_LVL_P3
|
||||
case 10:
|
||||
tx_power_dbm = 6;
|
||||
break; // ESP_PWR_LVL_P6
|
||||
case 11:
|
||||
tx_power_dbm = 9;
|
||||
break; // ESP_PWR_LVL_P9
|
||||
case 12:
|
||||
tx_power_dbm = 12;
|
||||
break; // ESP_PWR_LVL_P12
|
||||
case 13:
|
||||
tx_power_dbm = 15;
|
||||
break; // ESP_PWR_LVL_P15
|
||||
case 14:
|
||||
tx_power_dbm = 18;
|
||||
break; // ESP_PWR_LVL_P18
|
||||
case 15:
|
||||
tx_power_dbm = 20;
|
||||
break; // ESP_PWR_LVL_P20
|
||||
default:
|
||||
tx_power_dbm = 0;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
// Unknown variant
|
||||
tx_power_dbm = 0;
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"BLE:\n"
|
||||
" MAC address: %s\n"
|
||||
" IO Capability: %s",
|
||||
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
|
||||
" IO Capability: %s\n"
|
||||
" TX Power: %d dBm",
|
||||
format_mac_address_pretty(mac_address).c_str(), io_capability_s, tx_power_dbm);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "ble_advertising.h"
|
||||
#include "esphome/core/defines.h" // Must be included before conditional includes
|
||||
|
||||
#include "ble_uuid.h"
|
||||
#include "ble_scan_result.h"
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
#include "ble_advertising.h"
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "ble_event.h"
|
||||
@@ -17,27 +20,21 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_bt.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
|
||||
namespace esphome::esp32_ble {
|
||||
|
||||
// Maximum number of BLE scan results to buffer
|
||||
// Sized to handle bursts of advertisements while allowing for processing delays
|
||||
// With 16 advertisements per batch and some safety margin:
|
||||
// - Without PSRAM: 24 entries (1.5× batch size)
|
||||
// - With PSRAM: 36 entries (2.25× batch size)
|
||||
// The reduced structure size (~80 bytes vs ~400 bytes) allows for larger buffers
|
||||
// Maximum size of the BLE event queue
|
||||
// Increased to absorb the ring buffer capacity from esp32_ble_tracker
|
||||
#ifdef USE_PSRAM
|
||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 36;
|
||||
static constexpr uint8_t MAX_BLE_QUEUE_SIZE = 100; // 64 + 36 (ring buffer size with PSRAM)
|
||||
#else
|
||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 24;
|
||||
static constexpr uint8_t MAX_BLE_QUEUE_SIZE = 88; // 64 + 24 (ring buffer size without PSRAM)
|
||||
#endif
|
||||
|
||||
// Maximum size of the BLE event queue - must be power of 2 for lock-free queue
|
||||
static constexpr size_t MAX_BLE_QUEUE_SIZE = 64;
|
||||
|
||||
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
@@ -98,6 +95,7 @@ class BLEStatusEventHandler {
|
||||
class ESP32BLE : public Component {
|
||||
public:
|
||||
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
|
||||
void set_tx_power(esp_power_level_t tx_power) { this->tx_power_ = tx_power; }
|
||||
|
||||
void set_advertising_cycle_time(uint32_t advertising_cycle_time) {
|
||||
this->advertising_cycle_time_ = advertising_cycle_time;
|
||||
@@ -113,6 +111,7 @@ class ESP32BLE : public Component {
|
||||
float get_setup_priority() const override;
|
||||
void set_name(const std::string &name) { this->name_ = name; }
|
||||
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
void advertising_start();
|
||||
void advertising_set_service_data(const std::vector<uint8_t> &data);
|
||||
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
|
||||
@@ -120,6 +119,7 @@ class ESP32BLE : public Component {
|
||||
void advertising_add_service_uuid(ESPBTUUID uuid);
|
||||
void advertising_remove_service_uuid(ESPBTUUID uuid);
|
||||
void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
|
||||
#endif
|
||||
|
||||
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
|
||||
void register_gap_scan_event_handler(GAPScanEventHandler *handler) {
|
||||
@@ -140,7 +140,9 @@ class ESP32BLE : public Component {
|
||||
bool ble_setup_();
|
||||
bool ble_dismantle_();
|
||||
bool ble_pre_setup_();
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
void advertising_init_();
|
||||
#endif
|
||||
|
||||
private:
|
||||
template<typename... Args> friend void enqueue_ble_event(Args... args);
|
||||
@@ -160,7 +162,9 @@ class ESP32BLE : public Component {
|
||||
optional<std::string> name_;
|
||||
|
||||
// 4-byte aligned members
|
||||
BLEAdvertising *advertising_{}; // 4 bytes (pointer)
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
BLEAdvertising *advertising_{}; // 4 bytes (pointer)
|
||||
#endif
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum)
|
||||
uint32_t advertising_cycle_time_{}; // 4 bytes
|
||||
|
||||
@@ -170,6 +174,7 @@ class ESP32BLE : public Component {
|
||||
// 1-byte aligned members (grouped together to minimize padding)
|
||||
BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; // 1 byte (uint8_t enum)
|
||||
bool enable_on_boot_{}; // 1 byte
|
||||
esp_power_level_t tx_power_{ESP_PWR_LVL_P9}; // 1 byte (default: +9 dBm)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ble_advertising.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -161,4 +162,5 @@ void BLEAdvertising::register_raw_advertisement_callback(std::function<void(bool
|
||||
|
||||
} // namespace esphome::esp32_ble
|
||||
|
||||
#endif
|
||||
#endif // USE_ESP32_BLE_ADVERTISING
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_ADVERTISING
|
||||
|
||||
#include <esp_bt.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
@@ -56,4 +59,5 @@ class BLEAdvertising {
|
||||
|
||||
} // namespace esphome::esp32_ble
|
||||
|
||||
#endif
|
||||
#endif // USE_ESP32_BLE_ADVERTISING
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cstddef> // for offsetof
|
||||
#include <vector>
|
||||
|
||||
#include <cstring> // for memcpy
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
@@ -62,10 +61,24 @@ static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.rssi) == sizeof(es
|
||||
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t),
|
||||
"remote_addr must follow rssi in read_rssi_cmpl");
|
||||
|
||||
// Param struct sizes on ESP32
|
||||
static constexpr size_t GATTC_PARAM_SIZE = 28;
|
||||
static constexpr size_t GATTS_PARAM_SIZE = 32;
|
||||
|
||||
// Maximum size for inline storage of data
|
||||
// GATTC: 80 - 28 (param) - 8 (other fields) = 44 bytes for data
|
||||
// GATTS: 80 - 32 (param) - 8 (other fields) = 40 bytes for data
|
||||
static constexpr size_t GATTC_INLINE_DATA_SIZE = 44;
|
||||
static constexpr size_t GATTS_INLINE_DATA_SIZE = 40;
|
||||
|
||||
// Verify param struct sizes
|
||||
static_assert(sizeof(esp_ble_gattc_cb_param_t) == GATTC_PARAM_SIZE, "GATTC param size unexpected");
|
||||
static_assert(sizeof(esp_ble_gatts_cb_param_t) == GATTS_PARAM_SIZE, "GATTS param size unexpected");
|
||||
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
// This class stores each event with minimal memory usage.
|
||||
// GAP events (99% of traffic) don't have the vector overhead.
|
||||
// GATTC/GATTS events use heap allocation for their param and data.
|
||||
// GAP events (99% of traffic) don't have the heap allocation overhead.
|
||||
// GATTC/GATTS events use heap allocation for their param and inline storage for small data.
|
||||
//
|
||||
// Event flow:
|
||||
// 1. ESP-IDF BLE stack calls our static handlers in the BLE task context
|
||||
@@ -112,21 +125,21 @@ class BLEEvent {
|
||||
this->init_gap_data_(e, p);
|
||||
}
|
||||
|
||||
// Constructor for GATTC events - uses heap allocation
|
||||
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
|
||||
// The param pointer from ESP-IDF is only valid during the callback execution.
|
||||
// Since BLE events are processed asynchronously in the main loop, we must create
|
||||
// our own copy to ensure the data remains valid until the event is processed.
|
||||
// Constructor for GATTC events - param stored inline, data may use heap
|
||||
// IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
|
||||
// is only valid during the callback execution. Since BLE events are processed
|
||||
// asynchronously in the main loop, we store our own copy inline to ensure
|
||||
// the data remains valid until the event is processed.
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->type_ = GATTC;
|
||||
this->init_gattc_data_(e, i, p);
|
||||
}
|
||||
|
||||
// Constructor for GATTS events - uses heap allocation
|
||||
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
|
||||
// The param pointer from ESP-IDF is only valid during the callback execution.
|
||||
// Since BLE events are processed asynchronously in the main loop, we must create
|
||||
// our own copy to ensure the data remains valid until the event is processed.
|
||||
// Constructor for GATTS events - param stored inline, data may use heap
|
||||
// IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
|
||||
// is only valid during the callback execution. Since BLE events are processed
|
||||
// asynchronously in the main loop, we store our own copy inline to ensure
|
||||
// the data remains valid until the event is processed.
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->type_ = GATTS;
|
||||
this->init_gatts_data_(e, i, p);
|
||||
@@ -136,25 +149,32 @@ class BLEEvent {
|
||||
~BLEEvent() { this->release(); }
|
||||
|
||||
// Default constructor for pre-allocation in pool
|
||||
BLEEvent() : type_(GAP) {}
|
||||
BLEEvent() : event_{}, type_(GAP) {}
|
||||
|
||||
// Invoked on return to EventPool - clean up any heap-allocated data
|
||||
void release() {
|
||||
if (this->type_ == GAP) {
|
||||
return;
|
||||
}
|
||||
if (this->type_ == GATTC) {
|
||||
delete this->event_.gattc.gattc_param;
|
||||
delete this->event_.gattc.data;
|
||||
this->event_.gattc.gattc_param = nullptr;
|
||||
this->event_.gattc.data = nullptr;
|
||||
return;
|
||||
}
|
||||
if (this->type_ == GATTS) {
|
||||
delete this->event_.gatts.gatts_param;
|
||||
delete this->event_.gatts.data;
|
||||
this->event_.gatts.gatts_param = nullptr;
|
||||
this->event_.gatts.data = nullptr;
|
||||
switch (this->type_) {
|
||||
case GAP:
|
||||
// GAP events don't have heap allocations
|
||||
break;
|
||||
case GATTC:
|
||||
// Param is now stored inline, only delete heap data if it was heap-allocated
|
||||
if (!this->event_.gattc.is_inline && this->event_.gattc.data.heap_data != nullptr) {
|
||||
delete[] this->event_.gattc.data.heap_data;
|
||||
}
|
||||
// Clear critical fields to prevent issues if type changes
|
||||
this->event_.gattc.is_inline = false;
|
||||
this->event_.gattc.data.heap_data = nullptr;
|
||||
break;
|
||||
case GATTS:
|
||||
// Param is now stored inline, only delete heap data if it was heap-allocated
|
||||
if (!this->event_.gatts.is_inline && this->event_.gatts.data.heap_data != nullptr) {
|
||||
delete[] this->event_.gatts.data.heap_data;
|
||||
}
|
||||
// Clear critical fields to prevent issues if type changes
|
||||
this->event_.gatts.is_inline = false;
|
||||
this->event_.gatts.data.heap_data = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,20 +226,30 @@ class BLEEvent {
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
|
||||
std::vector<uint8_t> *data; // Heap-allocated
|
||||
} gattc; // 16 bytes (pointers only)
|
||||
esp_ble_gattc_cb_param_t gattc_param; // Stored inline (28 bytes)
|
||||
esp_gattc_cb_event_t gattc_event; // 4 bytes
|
||||
union {
|
||||
uint8_t *heap_data; // 4 bytes when heap-allocated
|
||||
uint8_t inline_data[GATTC_INLINE_DATA_SIZE]; // 44 bytes when stored inline
|
||||
} data; // 44 bytes total
|
||||
uint16_t data_len; // 2 bytes
|
||||
esp_gatt_if_t gattc_if; // 1 byte
|
||||
bool is_inline; // 1 byte - true when data is stored inline
|
||||
} gattc; // Total: 80 bytes
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
|
||||
std::vector<uint8_t> *data; // Heap-allocated
|
||||
} gatts; // 16 bytes (pointers only)
|
||||
} event_; // 80 bytes
|
||||
esp_ble_gatts_cb_param_t gatts_param; // Stored inline (32 bytes)
|
||||
esp_gatts_cb_event_t gatts_event; // 4 bytes
|
||||
union {
|
||||
uint8_t *heap_data; // 4 bytes when heap-allocated
|
||||
uint8_t inline_data[GATTS_INLINE_DATA_SIZE]; // 40 bytes when stored inline
|
||||
} data; // 40 bytes total
|
||||
uint16_t data_len; // 2 bytes
|
||||
esp_gatt_if_t gatts_if; // 1 byte
|
||||
bool is_inline; // 1 byte - true when data is stored inline
|
||||
} gatts; // Total: 80 bytes
|
||||
} event_; // 80 bytes
|
||||
|
||||
ble_event_t type_;
|
||||
|
||||
@@ -233,6 +263,29 @@ class BLEEvent {
|
||||
const esp_ble_sec_t &security() const { return event_.gap.security; }
|
||||
|
||||
private:
|
||||
// Helper to copy data with inline storage optimization
|
||||
template<typename EventStruct, size_t InlineSize>
|
||||
void copy_data_with_inline_storage_(EventStruct &event, const uint8_t *src_data, uint16_t len,
|
||||
uint8_t **param_value_ptr) {
|
||||
event.data_len = len;
|
||||
if (len > 0) {
|
||||
if (len <= InlineSize) {
|
||||
event.is_inline = true;
|
||||
memcpy(event.data.inline_data, src_data, len);
|
||||
*param_value_ptr = event.data.inline_data;
|
||||
} else {
|
||||
event.is_inline = false;
|
||||
event.data.heap_data = new uint8_t[len];
|
||||
memcpy(event.data.heap_data, src_data, len);
|
||||
*param_value_ptr = event.data.heap_data;
|
||||
}
|
||||
} else {
|
||||
event.is_inline = false;
|
||||
event.data.heap_data = nullptr;
|
||||
*param_value_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize GAP event data
|
||||
void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->event_.gap.gap_event = e;
|
||||
@@ -317,35 +370,38 @@ class BLEEvent {
|
||||
this->event_.gattc.gattc_if = i;
|
||||
|
||||
if (p == nullptr) {
|
||||
this->event_.gattc.gattc_param = nullptr;
|
||||
this->event_.gattc.data = nullptr;
|
||||
// Zero out the param struct when null
|
||||
memset(&this->event_.gattc.gattc_param, 0, sizeof(this->event_.gattc.gattc_param));
|
||||
this->event_.gattc.is_inline = false;
|
||||
this->event_.gattc.data.heap_data = nullptr;
|
||||
this->event_.gattc.data_len = 0;
|
||||
return; // Invalid event, but we can't log in header file
|
||||
}
|
||||
|
||||
// Heap-allocate param and data
|
||||
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
||||
// while GAP events (99%) are stored inline to minimize memory usage
|
||||
// IMPORTANT: This heap allocation provides clear ownership semantics:
|
||||
// - The BLEEvent owns the allocated memory for its lifetime
|
||||
// - The data remains valid from the BLE callback context until processed in the main loop
|
||||
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
|
||||
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
|
||||
// Copy param struct inline (no heap allocation!)
|
||||
// GATTC/GATTS events are rare (<1% of events) but we can still store them inline
|
||||
// along with small data payloads, eliminating all heap allocations for typical BLE operations
|
||||
// CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
|
||||
// is only valid during the callback and will be reused/freed after we return
|
||||
this->event_.gattc.gattc_param = *p;
|
||||
|
||||
// Copy data for events that need it
|
||||
// The param struct contains pointers (e.g., notify.value) that point to temporary buffers.
|
||||
// We must copy this data to ensure it remains valid when the event is processed later.
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
|
||||
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
|
||||
copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
|
||||
this->event_.gattc, p->notify.value, p->notify.value_len, &this->event_.gattc.gattc_param.notify.value);
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
|
||||
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
|
||||
copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
|
||||
this->event_.gattc, p->read.value, p->read.value_len, &this->event_.gattc.gattc_param.read.value);
|
||||
break;
|
||||
default:
|
||||
this->event_.gattc.data = nullptr;
|
||||
this->event_.gattc.is_inline = false;
|
||||
this->event_.gattc.data.heap_data = nullptr;
|
||||
this->event_.gattc.data_len = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -356,30 +412,33 @@ class BLEEvent {
|
||||
this->event_.gatts.gatts_if = i;
|
||||
|
||||
if (p == nullptr) {
|
||||
this->event_.gatts.gatts_param = nullptr;
|
||||
this->event_.gatts.data = nullptr;
|
||||
// Zero out the param struct when null
|
||||
memset(&this->event_.gatts.gatts_param, 0, sizeof(this->event_.gatts.gatts_param));
|
||||
this->event_.gatts.is_inline = false;
|
||||
this->event_.gatts.data.heap_data = nullptr;
|
||||
this->event_.gatts.data_len = 0;
|
||||
return; // Invalid event, but we can't log in header file
|
||||
}
|
||||
|
||||
// Heap-allocate param and data
|
||||
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
|
||||
// while GAP events (99%) are stored inline to minimize memory usage
|
||||
// IMPORTANT: This heap allocation provides clear ownership semantics:
|
||||
// - The BLEEvent owns the allocated memory for its lifetime
|
||||
// - The data remains valid from the BLE callback context until processed in the main loop
|
||||
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
|
||||
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
|
||||
// Copy param struct inline (no heap allocation!)
|
||||
// GATTC/GATTS events are rare (<1% of events) but we can still store them inline
|
||||
// along with small data payloads, eliminating all heap allocations for typical BLE operations
|
||||
// CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
|
||||
// is only valid during the callback and will be reused/freed after we return
|
||||
this->event_.gatts.gatts_param = *p;
|
||||
|
||||
// Copy data for events that need it
|
||||
// The param struct contains pointers (e.g., write.value) that point to temporary buffers.
|
||||
// We must copy this data to ensure it remains valid when the event is processed later.
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
|
||||
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
|
||||
copy_data_with_inline_storage_<decltype(this->event_.gatts), GATTS_INLINE_DATA_SIZE>(
|
||||
this->event_.gatts, p->write.value, p->write.len, &this->event_.gatts.gatts_param.write.value);
|
||||
break;
|
||||
default:
|
||||
this->event_.gatts.data = nullptr;
|
||||
this->event_.gatts.is_inline = false;
|
||||
this->event_.gatts.data.heap_data = nullptr;
|
||||
this->event_.gatts.data_len = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -389,6 +448,15 @@ class BLEEvent {
|
||||
// The gap member in the union should be 80 bytes (including the gap_event enum)
|
||||
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes");
|
||||
|
||||
// Verify GATTC and GATTS structs don't exceed GAP struct size
|
||||
// This ensures the union size is determined by GAP (the most common event type)
|
||||
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gattc)) <=
|
||||
sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)),
|
||||
"gattc_event struct exceeds gap_event size - union size would increase");
|
||||
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gatts)) <=
|
||||
sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)),
|
||||
"gatts_event struct exceeds gap_event size - union size would increase");
|
||||
|
||||
// Verify esp_ble_sec_t fits within our union
|
||||
static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult");
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ble_uuid.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_UUID
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
@@ -190,4 +191,5 @@ std::string ESPBTUUID::to_string() const {
|
||||
|
||||
} // namespace esphome::esp32_ble
|
||||
|
||||
#endif
|
||||
#endif // USE_ESP32_BLE_UUID
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_UUID
|
||||
|
||||
#include <string>
|
||||
#include <esp_bt_defs.h>
|
||||
@@ -42,4 +44,5 @@ class ESPBTUUID {
|
||||
|
||||
} // namespace esphome::esp32_ble
|
||||
|
||||
#endif
|
||||
#endif // USE_ESP32_BLE_UUID
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -65,6 +65,8 @@ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_ESP32_BLE_UUID")
|
||||
|
||||
uuid = config[CONF_UUID].hex
|
||||
uuid_arr = [
|
||||
cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2)
|
||||
@@ -82,6 +84,8 @@ async def to_code(config):
|
||||
cg.add(var.set_measured_power(config[CONF_MEASURED_POWER]))
|
||||
cg.add(var.set_tx_power(config[CONF_TX_POWER]))
|
||||
|
||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
|
||||
|
||||
@@ -2,7 +2,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import esp32_ble_tracker
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_tracker"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CODEOWNERS = ["@jesserockz", "@bdraco"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client")
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
namespace esphome::esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
|
||||
@@ -93,7 +93,7 @@ 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
|
||||
} // namespace esphome::esp32_ble_client
|
||||
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
@@ -8,8 +11,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
namespace esphome::esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -33,7 +35,7 @@ class BLECharacteristic {
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::esp32_ble_client
|
||||
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatt_defs.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
namespace esphome::esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
|
||||
@@ -79,40 +79,7 @@ void BLEClientBase::dump_config() {
|
||||
" Address: %s\n"
|
||||
" Auto-Connect: %s",
|
||||
this->address_str().c_str(), TRUEFALSE(this->auto_connect_));
|
||||
std::string state_name;
|
||||
switch (this->state()) {
|
||||
case espbt::ClientState::INIT:
|
||||
state_name = "INIT";
|
||||
break;
|
||||
case espbt::ClientState::DISCONNECTING:
|
||||
state_name = "DISCONNECTING";
|
||||
break;
|
||||
case espbt::ClientState::IDLE:
|
||||
state_name = "IDLE";
|
||||
break;
|
||||
case espbt::ClientState::SEARCHING:
|
||||
state_name = "SEARCHING";
|
||||
break;
|
||||
case espbt::ClientState::DISCOVERED:
|
||||
state_name = "DISCOVERED";
|
||||
break;
|
||||
case espbt::ClientState::READY_TO_CONNECT:
|
||||
state_name = "READY_TO_CONNECT";
|
||||
break;
|
||||
case espbt::ClientState::CONNECTING:
|
||||
state_name = "CONNECTING";
|
||||
break;
|
||||
case espbt::ClientState::CONNECTED:
|
||||
state_name = "CONNECTED";
|
||||
break;
|
||||
case espbt::ClientState::ESTABLISHED:
|
||||
state_name = "ESTABLISHED";
|
||||
break;
|
||||
default:
|
||||
state_name = "UNKNOWN_STATE";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " State: %s", state_name.c_str());
|
||||
ESP_LOGCONFIG(TAG, " State: %s", espbt::client_state_to_string(this->state()));
|
||||
if (this->status_ == ESP_GATT_NO_RESOURCES) {
|
||||
ESP_LOGE(TAG, " Failed due to no resources. Try to reduce number of BLE clients in config.");
|
||||
} else if (this->status_ != ESP_GATT_OK) {
|
||||
@@ -141,64 +108,35 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
||||
#endif
|
||||
|
||||
void BLEClientBase::connect() {
|
||||
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
|
||||
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_.c_str(),
|
||||
this->remote_addr_type_);
|
||||
this->paired_ = false;
|
||||
|
||||
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);
|
||||
|
||||
// Always set connection parameters to ensure stable operation
|
||||
// Use FAST for all V3 connections (better latency and reliability)
|
||||
// Use MEDIUM for V1/legacy connections (balanced performance)
|
||||
uint16_t min_interval, max_interval, timeout;
|
||||
const char *param_type;
|
||||
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
min_interval = FAST_MIN_CONN_INTERVAL;
|
||||
max_interval = FAST_MAX_CONN_INTERVAL;
|
||||
timeout = FAST_CONN_TIMEOUT;
|
||||
param_type = "fast";
|
||||
} else {
|
||||
min_interval = MEDIUM_MIN_CONN_INTERVAL;
|
||||
max_interval = MEDIUM_MAX_CONN_INTERVAL;
|
||||
timeout = MEDIUM_CONN_TIMEOUT;
|
||||
param_type = "medium";
|
||||
}
|
||||
|
||||
auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval,
|
||||
0, // latency: 0
|
||||
timeout);
|
||||
if (param_ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param_ret);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%d] [%s] Set %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
||||
}
|
||||
// Determine connection parameters based on connection type
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
// V3 without cache needs fast params for service discovery
|
||||
this->set_conn_params_(FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT, "fast");
|
||||
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
// V3 with cache can use medium params
|
||||
this->set_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
|
||||
}
|
||||
// For V1/Legacy, don't set params - use ESP-IDF defaults
|
||||
|
||||
// Open the connection
|
||||
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
||||
this->handle_connection_result_(ret);
|
||||
}
|
||||
|
||||
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
|
||||
|
||||
void BLEClientBase::disconnect() {
|
||||
if (this->state_ == espbt::ClientState::IDLE) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already idle.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
return;
|
||||
}
|
||||
if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already disconnecting.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Disconnect requested, but already %s", this->connection_index_, this->address_str_.c_str(),
|
||||
espbt::client_state_to_string(this->state_));
|
||||
return;
|
||||
}
|
||||
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Disconnecting before connected, disconnect scheduled.", this->connection_index_,
|
||||
ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
this->want_disconnect_ = true;
|
||||
return;
|
||||
@@ -211,13 +149,11 @@ void BLEClientBase::unconditional_disconnect() {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(),
|
||||
this->conn_id_);
|
||||
if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Tried to disconnect while already disconnecting.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
this->log_error_("Already disconnecting");
|
||||
return;
|
||||
}
|
||||
if (this->conn_id_ == UNSET_CONN_ID) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] No connection ID set, cannot disconnect.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
this->log_error_("conn id unset, cannot disconnect");
|
||||
return;
|
||||
}
|
||||
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||
@@ -229,8 +165,7 @@ void BLEClientBase::unconditional_disconnect() {
|
||||
// In the future we might consider App.reboot() here since
|
||||
// the BLE stack is in an indeterminate state.
|
||||
//
|
||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(),
|
||||
err);
|
||||
this->log_gattc_warning_("esp_ble_gattc_close", err);
|
||||
}
|
||||
|
||||
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
|
||||
@@ -243,9 +178,11 @@ void BLEClientBase::unconditional_disconnect() {
|
||||
}
|
||||
|
||||
void BLEClientBase::release_services() {
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
#endif
|
||||
#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
|
||||
esp_ble_gattc_cache_clean(this->remote_bda_);
|
||||
#endif
|
||||
@@ -255,6 +192,70 @@ void BLEClientBase::log_event_(const char *name) {
|
||||
ESP_LOGD(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), name);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_gattc_event_(const char *name) {
|
||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_%s_EVT", this->connection_index_, this->address_str_.c_str(), name);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_gattc_warning_(const char *operation, esp_gatt_status_t status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_.c_str(), operation,
|
||||
status);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_gattc_warning_(const char *operation, esp_err_t err) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] %s error, status=%d", this->connection_index_, this->address_str_.c_str(), operation, err);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_connection_params_(const char *param_type) {
|
||||
ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
||||
}
|
||||
|
||||
void BLEClientBase::handle_connection_result_(esp_err_t ret) {
|
||||
if (ret) {
|
||||
this->log_gattc_warning_("esp_ble_gattc_open", ret);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_state(espbt::ClientState::CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientBase::log_error_(const char *message) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_error_(const char *message, int code) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_.c_str(), message, code);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_warning_(const char *message) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
|
||||
}
|
||||
|
||||
void BLEClientBase::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency,
|
||||
uint16_t timeout, const char *param_type) {
|
||||
esp_ble_conn_update_params_t conn_params = {{0}};
|
||||
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
|
||||
conn_params.min_int = min_interval;
|
||||
conn_params.max_int = max_interval;
|
||||
conn_params.latency = latency;
|
||||
conn_params.timeout = timeout;
|
||||
this->log_connection_params_(param_type);
|
||||
esp_err_t err = esp_ble_gap_update_conn_params(&conn_params);
|
||||
if (err != ESP_OK) {
|
||||
this->log_gattc_warning_("esp_ble_gap_update_conn_params", err);
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientBase::set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||
const char *param_type) {
|
||||
// Set preferred connection parameters before connecting
|
||||
// These will be used when establishing the connection
|
||||
this->log_connection_params_(param_type);
|
||||
esp_err_t err = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval, latency, timeout);
|
||||
if (err != ESP_OK) {
|
||||
this->log_gattc_warning_("esp_ble_gap_set_prefer_conn_params", err);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -272,8 +273,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
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);
|
||||
this->log_error_("gattc app registration failed status", param->reg.status);
|
||||
this->status_ = param->reg.status;
|
||||
this->mark_failed();
|
||||
}
|
||||
@@ -282,30 +282,28 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (!this->check_addr(param->open.remote_bda))
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_OPEN_EVT");
|
||||
this->conn_id_ = param->open.conn_id;
|
||||
this->log_gattc_event_("OPEN");
|
||||
// conn_id was already set in ESP_GATTC_CONNECT_EVT
|
||||
this->service_count_ = 0;
|
||||
|
||||
// ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
|
||||
// error, if the error occurred at the BTA/GATT layer. This can result in the event
|
||||
// arriving after we've already transitioned to IDLE state.
|
||||
if (this->state_ == espbt::ClientState::IDLE) {
|
||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
|
||||
this->address_str_.c_str(), param->open.status);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->state_ != espbt::ClientState::CONNECTING) {
|
||||
// This should not happen but lets log it in case it does
|
||||
// because it means we have a bad assumption about how the
|
||||
// ESP BT stack works.
|
||||
if (this->state_ == espbt::ClientState::CONNECTED) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while already connected, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->open.status);
|
||||
} else if (this->state_ == espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while already established, status=%d",
|
||||
this->connection_index_, this->address_str_.c_str(), param->open.status);
|
||||
} else if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while disconnecting, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->open.status);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while not in connecting state, status=%d",
|
||||
this->connection_index_, this->address_str_.c_str(), param->open.status);
|
||||
}
|
||||
ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
|
||||
this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status);
|
||||
}
|
||||
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->log_gattc_warning_("Connection open", param->open.status);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
@@ -317,27 +315,34 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->conn_id_ = UNSET_CONN_ID;
|
||||
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);
|
||||
}
|
||||
// MTU negotiation already started in ESP_GATTC_CONNECT_EVT
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Using cached services", this->connection_index_, this->address_str_.c_str());
|
||||
// Cached connections already connected with medium parameters, no update needed
|
||||
// only set our state, subclients might have more stuff to do yet.
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str());
|
||||
// For V3_WITHOUT_CACHE, we already set fast params before connecting
|
||||
// No need to update them again here
|
||||
this->log_event_("Searching for services");
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
if (!this->check_addr(param->connect.remote_bda))
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_CONNECT_EVT");
|
||||
this->log_gattc_event_("CONNECT");
|
||||
this->conn_id_ = param->connect.conn_id;
|
||||
// Start MTU negotiation immediately as recommended by ESP-IDF examples
|
||||
// (gatt_client, ble_throughput) which call esp_ble_gattc_send_mtu_req in
|
||||
// ESP_GATTC_CONNECT_EVT instead of waiting for ESP_GATTC_OPEN_EVT.
|
||||
// This saves ~3ms in the connection process.
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id);
|
||||
if (ret) {
|
||||
this->log_gattc_warning_("esp_ble_gattc_send_mtu_req", ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
@@ -346,8 +351,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
// Check if we were disconnected while waiting for service discovery
|
||||
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
|
||||
this->state_ == espbt::ClientState::CONNECTED) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
this->log_warning_("Remote closed during discovery");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_,
|
||||
this->address_str_.c_str(), param->disconnect.reason);
|
||||
@@ -374,7 +378,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
case ESP_GATTC_CLOSE_EVT: {
|
||||
if (this->conn_id_ != param->close.conn_id)
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_CLOSE_EVT");
|
||||
this->log_gattc_event_("CLOSE");
|
||||
this->release_services();
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
this->conn_id_ = UNSET_CONN_ID;
|
||||
@@ -386,79 +390,73 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
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.
|
||||
// as they use the ESP APIs to get services.
|
||||
break;
|
||||
}
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
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);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
if (this->conn_id_ != param->search_cmpl.conn_id)
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_SEARCH_CMPL_EVT");
|
||||
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->log_gattc_event_("SEARCH_CMPL");
|
||||
// For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
|
||||
// This balances performance with bandwidth usage after the critical discovery phase
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
|
||||
} else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_.c_str());
|
||||
|
||||
// For V3 connections, restore to medium connection parameters after service discovery
|
||||
// This balances performance with bandwidth usage after the critical discovery phase
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
esp_ble_conn_update_params_t conn_params = {{0}};
|
||||
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
|
||||
conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL;
|
||||
conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL;
|
||||
conn_params.latency = 0;
|
||||
conn_params.timeout = MEDIUM_CONN_TIMEOUT;
|
||||
ESP_LOGD(TAG, "[%d] [%s] Restored medium conn params after service discovery", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
esp_ble_gap_update_conn_params(&conn_params);
|
||||
}
|
||||
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_DESCR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_READ_DESCR_EVT");
|
||||
this->log_gattc_event_("READ_DESCR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_WRITE_DESCR_EVT");
|
||||
this->log_gattc_event_("WRITE_DESCR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||
if (this->conn_id_ != param->write.conn_id)
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_WRITE_CHAR_EVT");
|
||||
this->log_gattc_event_("WRITE_CHAR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (this->conn_id_ != param->read.conn_id)
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_READ_CHAR_EVT");
|
||||
this->log_gattc_event_("READ_CHAR");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (this->conn_id_ != param->notify.conn_id)
|
||||
return false;
|
||||
this->log_event_("ESP_GATTC_NOTIFY_EVT");
|
||||
this->log_gattc_event_("NOTIFY");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->log_event_("ESP_GATTC_REG_FOR_NOTIFY_EVT");
|
||||
this->log_gattc_event_("REG_FOR_NOTIFY");
|
||||
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
|
||||
@@ -470,8 +468,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
esp_gatt_status_t descr_status = esp_ble_gattc_get_descr_by_char_handle(
|
||||
this->gattc_if_, this->conn_id_, 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);
|
||||
this->log_gattc_warning_("esp_ble_gattc_get_descr_by_char_handle", descr_status);
|
||||
break;
|
||||
}
|
||||
esp_gattc_char_elem_t char_result;
|
||||
@@ -479,8 +476,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, 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);
|
||||
this->log_gattc_warning_("esp_ble_gattc_get_all_char", char_status);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -494,12 +490,16 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
ESP_LOGD(TAG, "Wrote notify descriptor %d, properties=%d", notify_en, char_result.properties);
|
||||
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);
|
||||
this->log_gattc_warning_("esp_ble_gattc_write_char_descr", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
this->log_gattc_event_("UNREG_FOR_NOTIFY");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// ideally would check all other events for matching conn_id
|
||||
ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event);
|
||||
@@ -528,16 +528,14 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
|
||||
return;
|
||||
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(),
|
||||
ESP_LOGI(TAG, "[%d] [%s] auth complete 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);
|
||||
this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
this->paired_ = true;
|
||||
ESP_LOGD(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);
|
||||
ESP_LOGD(TAG, "[%d] [%s] auth success type = %d 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;
|
||||
|
||||
@@ -603,6 +601,7 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
return NAN;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (svc->uuid == uuid)
|
||||
@@ -679,8 +678,8 @@ BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) {
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::esp32_ble_client
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
#include "ble_service.h"
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
@@ -16,8 +18,7 @@
|
||||
#include <esp_gatt_common_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
namespace esphome::esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -66,8 +67,9 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
(uint8_t) (this->address_ >> 0) & 0xff);
|
||||
}
|
||||
}
|
||||
std::string address_str() const { return this->address_str_; }
|
||||
const std::string &address_str() const { return this->address_str_; }
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
BLEService *get_service(espbt::ESPBTUUID uuid);
|
||||
BLEService *get_service(uint16_t uuid);
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
||||
@@ -78,6 +80,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
BLEDescriptor *get_descriptor(uint16_t handle);
|
||||
// Get the configuration descriptor for the given characteristic handle.
|
||||
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
||||
#endif
|
||||
|
||||
float parse_char_value(uint8_t *value, uint16_t length);
|
||||
|
||||
@@ -104,7 +107,9 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
|
||||
// Group 2: Container types (grouped for memory optimization)
|
||||
std::string address_str_{};
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
std::vector<BLEService *> services_;
|
||||
#endif
|
||||
|
||||
// Group 3: 4-byte types
|
||||
int gattc_if_;
|
||||
@@ -127,9 +132,21 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
// 6 bytes used, 2 bytes padding
|
||||
|
||||
void log_event_(const char *name);
|
||||
void log_gattc_event_(const char *name);
|
||||
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||
const char *param_type);
|
||||
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
|
||||
const char *param_type);
|
||||
void log_gattc_warning_(const char *operation, esp_gatt_status_t status);
|
||||
void log_gattc_warning_(const char *operation, esp_err_t err);
|
||||
void log_connection_params_(const char *param_type);
|
||||
void handle_connection_result_(esp_err_t ret);
|
||||
// Compact error logging helpers to reduce flash usage
|
||||
void log_error_(const char *message);
|
||||
void log_error_(const char *message, int code);
|
||||
void log_warning_(const char *message);
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::esp32_ble_client
|
||||
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
namespace esphome::esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -19,7 +21,7 @@ class BLEDescriptor {
|
||||
BLECharacteristic *characteristic;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::esp32_ble_client
|
||||
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
namespace esphome::esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
|
||||
@@ -71,7 +71,7 @@ void BLEService::parse_characteristics() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::esp32_ble_client
|
||||
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
@@ -8,8 +11,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
namespace esphome::esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
@@ -30,7 +32,7 @@ class BLEService {
|
||||
BLECharacteristic *get_characteristic(uint16_t uuid);
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
} // namespace esphome::esp32_ble_client
|
||||
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -529,6 +529,7 @@ async def to_code_characteristic(service_var, char_conf):
|
||||
async def to_code(config):
|
||||
# Register the loggers this component needs
|
||||
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
|
||||
cg.add_define("USE_ESP32_BLE_UUID")
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
@@ -571,6 +572,7 @@ async def to_code(config):
|
||||
config[CONF_ON_DISCONNECT],
|
||||
)
|
||||
cg.add_define("USE_ESP32_BLE_SERVER")
|
||||
cg.add_define("USE_ESP32_BLE_ADVERTISING")
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ from esphome.types import ConfigType
|
||||
|
||||
AUTO_LOAD = ["esp32_ble"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CODEOWNERS = ["@bdraco"]
|
||||
|
||||
KEY_ESP32_BLE_TRACKER = "esp32_ble_tracker"
|
||||
KEY_USED_CONNECTION_SLOTS = "used_connection_slots"
|
||||
@@ -354,11 +355,6 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
|
||||
)
|
||||
# CONFIG_BT_GATTC_NOTIF_REG_MAX controls the number of
|
||||
# max notifications in 5.x, setting CONFIG_BT_ACL_CONNECTIONS
|
||||
# is enough in 4.x
|
||||
# https://github.com/esphome/issues/issues/6808
|
||||
add_idf_sdkconfig_option("CONFIG_BT_GATTC_NOTIF_REG_MAX", 9)
|
||||
|
||||
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
|
||||
cg.add_define("USE_ESP32_BLE_CLIENT")
|
||||
@@ -377,6 +373,7 @@ async def _add_ble_features():
|
||||
# Add feature-specific defines based on what's needed
|
||||
if BLEFeatures.ESP_BT_DEVICE in _required_features:
|
||||
cg.add_define("USE_ESP32_BLE_DEVICE")
|
||||
cg.add_define("USE_ESP32_BLE_UUID")
|
||||
|
||||
|
||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
|
||||
|
||||
@@ -80,14 +80,17 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>,
|
||||
ESPBTUUID uuid_;
|
||||
};
|
||||
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
|
||||
class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
||||
|
||||
#ifdef USE_ESP32_BLE_DEVICE
|
||||
bool parse_device(const ESPBTDevice &device) override { return false; }
|
||||
#endif
|
||||
void on_scan_end() override { this->trigger(); }
|
||||
};
|
||||
#endif // USE_ESP32_BLE_DEVICE
|
||||
|
||||
template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
|
||||
public:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user