mirror of
https://github.com/esphome/esphome.git
synced 2025-09-12 06:28:43 +00:00
Compare commits
278 Commits
optimize_e
...
dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4390fd80a3 | ||
![]() |
4813c5134e | ||
![]() |
bbef0e173e | ||
![]() |
3240e19a7c | ||
![]() |
ac0cd946f0 | ||
![]() |
61bac6c6e6 | ||
![]() |
5fd64c5c89 | ||
![]() |
625f108183 | ||
![]() |
c45efe8f40 | ||
![]() |
fe1371f4dc | ||
![]() |
e3f8a36eaa | ||
![]() |
41f0d1c622 | ||
![]() |
6469bb168d | ||
![]() |
7a869a33f0 | ||
![]() |
af0da3f897 | ||
![]() |
32e4eb26ad | ||
![]() |
10aae33979 | ||
![]() |
56e85b3ef9 | ||
![]() |
55dd12c66b | ||
![]() |
9dd17b464d | ||
![]() |
2401f81be3 | ||
![]() |
52a7e26c6d | ||
![]() |
d729dc20a8 | ||
![]() |
d3b7a9687b | ||
![]() |
9d7fc11108 | ||
![]() |
7969627d3e | ||
![]() |
82d2e367d4 | ||
![]() |
972aa691e4 | ||
![]() |
ac61b8f893 | ||
![]() |
d9f625e5c8 | ||
![]() |
e218f16f0f | ||
![]() |
422d209786 | ||
![]() |
e972e1f8c2 | ||
![]() |
cfb90b7b18 | ||
![]() |
8976ea2436 | ||
![]() |
01ff09064d | ||
![]() |
39212f0d7f | ||
![]() |
8993f4e6b4 | ||
![]() |
7adad0ee49 | ||
![]() |
dd8815ec9d | ||
![]() |
59e62a1f44 | ||
![]() |
f5f84fe825 | ||
![]() |
90c2fdd565 | ||
![]() |
f6d69231e8 | ||
![]() |
5cc0e21bc7 | ||
![]() |
703b592793 | ||
![]() |
75c9430d91 | ||
![]() |
e5bba00deb | ||
![]() |
8d90f13e97 | ||
![]() |
666e33e70b | ||
![]() |
7eaaa4e426 | ||
![]() |
166ad942ef | ||
![]() |
0ff08bbc09 | ||
![]() |
6e2bcabbc9 | ||
![]() |
afa191ae41 | ||
![]() |
93da52c4d2 | ||
![]() |
0cc0979674 | ||
![]() |
629f1e94f1 | ||
![]() |
8c28f346c7 | ||
![]() |
3cf36e2f94 | ||
![]() |
1ac07c96b1 | ||
![]() |
91228c82e6 | ||
![]() |
28d16728d3 | ||
![]() |
f24a182ba2 | ||
![]() |
0065fe1516 | ||
![]() |
148fa698cc | ||
![]() |
b25506b045 | ||
![]() |
0c737fc4df | ||
![]() |
a8b8507ffc | ||
![]() |
c33bb3a8a9 | ||
![]() |
4d09932320 | ||
![]() |
e018b15641 | ||
![]() |
3fd469cfe8 | ||
![]() |
1359142106 | ||
![]() |
487ba4dad0 | ||
![]() |
694c590eb6 | ||
![]() |
b74463c3e6 | ||
![]() |
98e8a0c201 | ||
![]() |
91b2f75d04 | ||
![]() |
f1806046a9 | ||
![]() |
5b283d6d38 | ||
![]() |
1340665ac7 | ||
![]() |
1510db277c | ||
![]() |
a49669ee58 | ||
![]() |
09b40b882e | ||
![]() |
0069163d31 | ||
![]() |
86c2af4882 | ||
![]() |
b4b795dcaf | ||
![]() |
b8ed7ec145 | ||
![]() |
365a427b57 | ||
![]() |
e327ae8c95 | ||
![]() |
4c2f356b35 | ||
![]() |
e55bce83e3 | ||
![]() |
ba2433197e | ||
![]() |
c471bdb446 | ||
![]() |
cbac9caa52 | ||
![]() |
edf7094662 | ||
![]() |
25489b6009 | ||
![]() |
dc45a613f3 | ||
![]() |
e0617e01e0 | ||
![]() |
c7ee727af4 | ||
![]() |
c5b2a9e24b | ||
![]() |
101d553df9 | ||
![]() |
8fb6420b1c | ||
![]() |
c03d978b46 | ||
![]() |
2d3cdf60ba | ||
![]() |
a29fef166b | ||
![]() |
9fe94f1201 | ||
![]() |
1b8978a89a | ||
![]() |
6f188d1284 | ||
![]() |
a1a336783e | ||
![]() |
c55bc93f70 | ||
![]() |
de998f2f39 | ||
![]() |
950299e52b | ||
![]() |
23c6650902 | ||
![]() |
5759692627 | ||
![]() |
0ab65c225e | ||
![]() |
8aeb6d3ba2 | ||
![]() |
c3359edb33 | ||
![]() |
4d681ffe3d | ||
![]() |
68628a85b1 | ||
![]() |
086f1982fa | ||
![]() |
5ba1c32242 | ||
![]() |
d2b23ba3a7 | ||
![]() |
83fbd77c4a | ||
![]() |
1a054299d4 | ||
![]() |
e3fb9c2a78 | ||
![]() |
d1276dc6df | ||
![]() |
f286bc57f3 | ||
![]() |
ed48282d09 | ||
![]() |
2ddd8c72d6 | ||
![]() |
d0b4bc48e4 | ||
![]() |
77dbe77117 | ||
![]() |
6daeffcefd | ||
![]() |
6d834c019d | ||
![]() |
905e2906fe | ||
![]() |
a25b544c3b | ||
![]() |
da21174c6d | ||
![]() |
e29f0ee7f8 | ||
![]() |
983b3cb879 | ||
![]() |
fd568d9af3 | ||
![]() |
ca72286386 | ||
![]() |
dea68bebd8 | ||
![]() |
ef98f67b41 | ||
![]() |
6a92b691a0 | ||
![]() |
bc960cf6d2 | ||
![]() |
461ce69296 | ||
![]() |
6a20e6f9ad | ||
![]() |
cde00a1f4c | ||
![]() |
5dc691874b | ||
![]() |
c526ab9a3f | ||
![]() |
07875a8b1e | ||
![]() |
ba4789970c | ||
![]() |
015977cfdf | ||
![]() |
e513c0f004 | ||
![]() |
a11970aee0 | ||
![]() |
4ab37b069b | ||
![]() |
b6bb6699d1 | ||
![]() |
078eaff9a8 | ||
![]() |
a7786b75a0 | ||
![]() |
d4c11dac8c | ||
![]() |
2f2f2f7d15 | ||
![]() |
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 |
@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
|
|
||||||
## 2. Core Technologies & Stack
|
## 2. Core Technologies & Stack
|
||||||
|
|
||||||
* **Languages:** Python (>=3.10), C++ (gnu++20)
|
* **Languages:** Python (>=3.11), C++ (gnu++20)
|
||||||
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
* **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
|
||||||
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
* **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
|
||||||
* **Configuration:** YAML.
|
* **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.
|
5. **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
|
||||||
|
|
||||||
* **Platform Support:**
|
* **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.
|
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.
|
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.
|
4. **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
|
||||||
@@ -150,7 +150,8 @@ This document provides essential context for AI models interacting with this pro
|
|||||||
* **Configuration Validation:**
|
* **Configuration Validation:**
|
||||||
* **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
|
* **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)`.
|
* **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:**
|
* **Schema Extensions:**
|
||||||
```python
|
```python
|
||||||
CONFIG_SCHEMA = cv.Schema({ ... })
|
CONFIG_SCHEMA = cv.Schema({ ... })
|
||||||
|
@@ -1 +1 @@
|
|||||||
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273
|
4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9
|
||||||
|
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@@ -17,7 +17,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Set up Python ${{ inputs.python-version }}
|
- name: Set up Python ${{ inputs.python-version }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
|
32
.github/workflows/auto-label-pr.yml
vendored
32
.github/workflows/auto-label-pr.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Auto Label PR
|
- name: Auto Label PR
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
@@ -105,7 +105,9 @@ jobs:
|
|||||||
|
|
||||||
// Calculate data from PR files
|
// Calculate data from PR files
|
||||||
const changedFiles = prFiles.map(file => file.filename);
|
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('Current labels:', currentLabels.join(', '));
|
||||||
console.log('Changed files:', changedFiles.length);
|
console.log('Changed files:', changedFiles.length);
|
||||||
@@ -231,16 +233,21 @@ jobs:
|
|||||||
// Strategy: PR size detection
|
// Strategy: PR size detection
|
||||||
async function detectPRSize() {
|
async function detectPRSize() {
|
||||||
const labels = new Set();
|
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) {
|
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
||||||
labels.add('small-pr');
|
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
|
// Don't add too-big if mega-pr label is already present
|
||||||
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
||||||
labels.add('too-big');
|
labels.add('too-big');
|
||||||
@@ -375,7 +382,7 @@ jobs:
|
|||||||
const labels = new Set();
|
const labels = new Set();
|
||||||
|
|
||||||
// Check for missing tests
|
// 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');
|
labels.add('needs-tests');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,10 +419,13 @@ jobs:
|
|||||||
|
|
||||||
// Too big message
|
// Too big message
|
||||||
if (finalLabels.includes('too-big')) {
|
if (finalLabels.includes('too-big')) {
|
||||||
const testChanges = prFiles
|
const testAdditions = prFiles
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||||
const nonTestChanges = totalChanges - testChanges;
|
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 tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||||
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
||||||
|
6
.github/workflows/ci-api-proto.yml
vendored
6
.github/workflows/ci-api-proto.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- if: failure()
|
- if: failure()
|
||||||
name: Review PR
|
name: Review PR
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
await github.rest.pulls.createReview({
|
await github.rest.pulls.createReview({
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
esphome/components/api/api_pb2_service.*
|
esphome/components/api/api_pb2_service.*
|
||||||
- if: success()
|
- if: success()
|
||||||
name: Dismiss review
|
name: Dismiss review
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
let reviews = await github.rest.pulls.listReviews({
|
let reviews = await github.rest.pulls.listReviews({
|
||||||
|
6
.github/workflows/ci-clang-tidy-hash.yml
vendored
6
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v5.0.0
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
|
|
||||||
- if: failure()
|
- if: failure()
|
||||||
name: Request changes
|
name: Request changes
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
await github.rest.pulls.createReview({
|
await github.rest.pulls.createReview({
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
|
|
||||||
- if: success()
|
- if: success()
|
||||||
name: Dismiss review
|
name: Dismiss review
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
let reviews = await github.rest.pulls.listReviews({
|
let reviews = await github.rest.pulls.listReviews({
|
||||||
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5.0.0
|
- uses: actions/checkout@v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v5.4.3
|
uses: codecov/codecov-action@v5.5.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Save Python virtual environment cache
|
- name: Save Python virtual environment cache
|
||||||
@@ -217,7 +217,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5.0.0
|
uses: actions/checkout@v5.0.0
|
||||||
- name: Set up Python 3.13
|
- name: Set up Python 3.13
|
||||||
id: python
|
id: python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
|
@@ -25,7 +25,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Request reviews from component codeowners
|
- name: Request reviews from component codeowners
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const owner = context.repo.owner;
|
const owner = context.repo.owner;
|
||||||
|
2
.github/workflows/external-component-bot.yml
vendored
2
.github/workflows/external-component-bot.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Add external component comment
|
- name: Add external component comment
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
2
.github/workflows/issue-codeowner-notify.yml
vendored
2
.github/workflows/issue-codeowner-notify.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Notify codeowners for component issues
|
- name: Notify codeowners for component issues
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const owner = context.repo.owner;
|
const owner = context.repo.owner;
|
||||||
|
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');
|
|
||||||
}
|
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5.0.0
|
- uses: actions/checkout@v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
pip3 install build
|
pip3 install build
|
||||||
python3 -m build
|
python3 -m build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: pypa/gh-action-pypi-publish@v1.12.4
|
uses: pypa/gh-action-pypi-publish@v1.13.0
|
||||||
with:
|
with:
|
||||||
skip-existing: true
|
skip-existing: true
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5.0.0
|
- uses: actions/checkout@v5.0.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ jobs:
|
|||||||
- deploy-manifest
|
- deploy-manifest
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Workflow
|
- name: Trigger Workflow
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
@@ -246,7 +246,7 @@ jobs:
|
|||||||
environment: ${{ needs.init.outputs.deploy_env }}
|
environment: ${{ needs.init.outputs.deploy_env }}
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Workflow
|
- name: Trigger Workflow
|
||||||
uses: actions/github-script@v7.0.1
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
|
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9.1.0
|
- uses: actions/stale@v10.0.0
|
||||||
with:
|
with:
|
||||||
days-before-pr-stale: 90
|
days-before-pr-stale: 90
|
||||||
days-before-pr-close: 7
|
days-before-pr-close: 7
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
close-issues:
|
close-issues:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9.1.0
|
- uses: actions/stale@v10.0.0
|
||||||
with:
|
with:
|
||||||
days-before-pr-stale: -1
|
days-before-pr-stale: -1
|
||||||
days-before-pr-close: -1
|
days-before-pr-close: -1
|
||||||
|
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@v8.0.0
|
||||||
|
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 }}');
|
||||||
|
}
|
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
path: lib/home-assistant
|
path: lib/home-assistant
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5.6.0
|
uses: actions/setup-python@v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.13
|
python-version: 3.13
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ ci:
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.12.9
|
rev: v0.13.0
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
34
CODEOWNERS
34
CODEOWNERS
@@ -66,7 +66,7 @@ esphome/components/binary_sensor/* @esphome/core
|
|||||||
esphome/components/bk72xx/* @kuba2k2
|
esphome/components/bk72xx/* @kuba2k2
|
||||||
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
|
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
|
||||||
esphome/components/bl0939/* @ziceva
|
esphome/components/bl0939/* @ziceva
|
||||||
esphome/components/bl0940/* @tobias-
|
esphome/components/bl0940/* @dan-s-github @tobias-
|
||||||
esphome/components/bl0942/* @dbuezas @dwmw2
|
esphome/components/bl0942/* @dbuezas @dwmw2
|
||||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||||
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
||||||
@@ -88,7 +88,8 @@ esphome/components/bp1658cj/* @Cossid
|
|||||||
esphome/components/bp5758d/* @Cossid
|
esphome/components/bp5758d/* @Cossid
|
||||||
esphome/components/button/* @esphome/core
|
esphome/components/button/* @esphome/core
|
||||||
esphome/components/bytebuffer/* @clydebarrow
|
esphome/components/bytebuffer/* @clydebarrow
|
||||||
esphome/components/camera/* @DT-art1 @bdraco
|
esphome/components/camera/* @bdraco @DT-art1
|
||||||
|
esphome/components/camera_encoder/* @DT-art1
|
||||||
esphome/components/canbus/* @danielschramm @mvturnho
|
esphome/components/canbus/* @danielschramm @mvturnho
|
||||||
esphome/components/cap1188/* @mreditor97
|
esphome/components/cap1188/* @mreditor97
|
||||||
esphome/components/captive_portal/* @esphome/core
|
esphome/components/captive_portal/* @esphome/core
|
||||||
@@ -144,9 +145,9 @@ esphome/components/es8156/* @kbx81
|
|||||||
esphome/components/es8311/* @kahrendt @kroimon
|
esphome/components/es8311/* @kahrendt @kroimon
|
||||||
esphome/components/es8388/* @P4uLT
|
esphome/components/es8388/* @P4uLT
|
||||||
esphome/components/esp32/* @esphome/core
|
esphome/components/esp32/* @esphome/core
|
||||||
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz
|
esphome/components/esp32_ble/* @bdraco @jesserockz @Rapsssito
|
||||||
esphome/components/esp32_ble_client/* @bdraco @jesserockz
|
esphome/components/esp32_ble_client/* @bdraco @jesserockz
|
||||||
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
|
esphome/components/esp32_ble_server/* @clydebarrow @jesserockz @Rapsssito
|
||||||
esphome/components/esp32_ble_tracker/* @bdraco
|
esphome/components/esp32_ble_tracker/* @bdraco
|
||||||
esphome/components/esp32_camera_web_server/* @ayufan
|
esphome/components/esp32_camera_web_server/* @ayufan
|
||||||
esphome/components/esp32_can/* @Sympatron
|
esphome/components/esp32_can/* @Sympatron
|
||||||
@@ -166,7 +167,7 @@ esphome/components/ezo_pmp/* @carlos-sarmiento
|
|||||||
esphome/components/factory_reset/* @anatoly-savchenkov
|
esphome/components/factory_reset/* @anatoly-savchenkov
|
||||||
esphome/components/fastled_base/* @OttoWinter
|
esphome/components/fastled_base/* @OttoWinter
|
||||||
esphome/components/feedback/* @ianchi
|
esphome/components/feedback/* @ianchi
|
||||||
esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh
|
esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
|
||||||
esphome/components/font/* @clydebarrow @esphome/core
|
esphome/components/font/* @clydebarrow @esphome/core
|
||||||
esphome/components/fs3000/* @kahrendt
|
esphome/components/fs3000/* @kahrendt
|
||||||
esphome/components/ft5x06/* @clydebarrow
|
esphome/components/ft5x06/* @clydebarrow
|
||||||
@@ -202,7 +203,7 @@ esphome/components/heatpumpir/* @rob-deutsch
|
|||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||||
esphome/components/hm3301/* @freekode
|
esphome/components/hm3301/* @freekode
|
||||||
esphome/components/hmac_md5/* @dwmw2
|
esphome/components/hmac_md5/* @dwmw2
|
||||||
esphome/components/homeassistant/* @OttoWinter @esphome/core
|
esphome/components/homeassistant/* @esphome/core @OttoWinter
|
||||||
esphome/components/homeassistant/number/* @landonr
|
esphome/components/homeassistant/number/* @landonr
|
||||||
esphome/components/homeassistant/switch/* @Links2004
|
esphome/components/homeassistant/switch/* @Links2004
|
||||||
esphome/components/honeywell_hih_i2c/* @Benichou34
|
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||||
@@ -227,13 +228,13 @@ esphome/components/iaqcore/* @yozik04
|
|||||||
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
|
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
|
||||||
esphome/components/improv_base/* @esphome/core
|
esphome/components/improv_base/* @esphome/core
|
||||||
esphome/components/improv_serial/* @esphome/core
|
esphome/components/improv_serial/* @esphome/core
|
||||||
esphome/components/ina226/* @Sergio303 @latonita
|
esphome/components/ina226/* @latonita @Sergio303
|
||||||
esphome/components/ina260/* @mreditor97
|
esphome/components/ina260/* @mreditor97
|
||||||
esphome/components/ina2xx_base/* @latonita
|
esphome/components/ina2xx_base/* @latonita
|
||||||
esphome/components/ina2xx_i2c/* @latonita
|
esphome/components/ina2xx_i2c/* @latonita
|
||||||
esphome/components/ina2xx_spi/* @latonita
|
esphome/components/ina2xx_spi/* @latonita
|
||||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||||
esphome/components/inkplate6/* @jesserockz
|
esphome/components/inkplate/* @jesserockz @JosipKuci
|
||||||
esphome/components/integration/* @OttoWinter
|
esphome/components/integration/* @OttoWinter
|
||||||
esphome/components/internal_temperature/* @Mat931
|
esphome/components/internal_temperature/* @Mat931
|
||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
@@ -276,8 +277,8 @@ esphome/components/max7219digit/* @rspaargaren
|
|||||||
esphome/components/max9611/* @mckaymatthew
|
esphome/components/max9611/* @mckaymatthew
|
||||||
esphome/components/mcp23008/* @jesserockz
|
esphome/components/mcp23008/* @jesserockz
|
||||||
esphome/components/mcp23017/* @jesserockz
|
esphome/components/mcp23017/* @jesserockz
|
||||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
esphome/components/mcp23s08/* @jesserockz @SenexCrenshaw
|
||||||
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
|
esphome/components/mcp23s17/* @jesserockz @SenexCrenshaw
|
||||||
esphome/components/mcp23x08_base/* @jesserockz
|
esphome/components/mcp23x08_base/* @jesserockz
|
||||||
esphome/components/mcp23x17_base/* @jesserockz
|
esphome/components/mcp23x17_base/* @jesserockz
|
||||||
esphome/components/mcp23xxx_base/* @jesserockz
|
esphome/components/mcp23xxx_base/* @jesserockz
|
||||||
@@ -298,6 +299,7 @@ esphome/components/mics_4514/* @jesserockz
|
|||||||
esphome/components/midea/* @dudanov
|
esphome/components/midea/* @dudanov
|
||||||
esphome/components/midea_ir/* @dudanov
|
esphome/components/midea_ir/* @dudanov
|
||||||
esphome/components/mipi_dsi/* @clydebarrow
|
esphome/components/mipi_dsi/* @clydebarrow
|
||||||
|
esphome/components/mipi_rgb/* @clydebarrow
|
||||||
esphome/components/mipi_spi/* @clydebarrow
|
esphome/components/mipi_spi/* @clydebarrow
|
||||||
esphome/components/mitsubishi/* @RubyBailey
|
esphome/components/mitsubishi/* @RubyBailey
|
||||||
esphome/components/mixer/speaker/* @kahrendt
|
esphome/components/mixer/speaker/* @kahrendt
|
||||||
@@ -341,7 +343,7 @@ esphome/components/ota/* @esphome/core
|
|||||||
esphome/components/output/* @esphome/core
|
esphome/components/output/* @esphome/core
|
||||||
esphome/components/packet_transport/* @clydebarrow
|
esphome/components/packet_transport/* @clydebarrow
|
||||||
esphome/components/pca6416a/* @Mat931
|
esphome/components/pca6416a/* @Mat931
|
||||||
esphome/components/pca9554/* @clydebarrow @hwstar
|
esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
|
||||||
esphome/components/pcf85063/* @brogon
|
esphome/components/pcf85063/* @brogon
|
||||||
esphome/components/pcf8563/* @KoenBreeman
|
esphome/components/pcf8563/* @KoenBreeman
|
||||||
esphome/components/pi4ioe5v6408/* @jesserockz
|
esphome/components/pi4ioe5v6408/* @jesserockz
|
||||||
@@ -352,9 +354,9 @@ esphome/components/pm2005/* @andrewjswan
|
|||||||
esphome/components/pmsa003i/* @sjtrny
|
esphome/components/pmsa003i/* @sjtrny
|
||||||
esphome/components/pmsx003/* @ximex
|
esphome/components/pmsx003/* @ximex
|
||||||
esphome/components/pmwcs3/* @SeByDocKy
|
esphome/components/pmwcs3/* @SeByDocKy
|
||||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
esphome/components/pn532/* @jesserockz @OttoWinter
|
||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
|
esphome/components/pn532_i2c/* @jesserockz @OttoWinter
|
||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
|
esphome/components/pn532_spi/* @jesserockz @OttoWinter
|
||||||
esphome/components/pn7150/* @jesserockz @kbx81
|
esphome/components/pn7150/* @jesserockz @kbx81
|
||||||
esphome/components/pn7150_i2c/* @jesserockz @kbx81
|
esphome/components/pn7150_i2c/* @jesserockz @kbx81
|
||||||
esphome/components/pn7160/* @jesserockz @kbx81
|
esphome/components/pn7160/* @jesserockz @kbx81
|
||||||
@@ -363,7 +365,7 @@ esphome/components/pn7160_spi/* @jesserockz @kbx81
|
|||||||
esphome/components/power_supply/* @esphome/core
|
esphome/components/power_supply/* @esphome/core
|
||||||
esphome/components/preferences/* @esphome/core
|
esphome/components/preferences/* @esphome/core
|
||||||
esphome/components/psram/* @esphome/core
|
esphome/components/psram/* @esphome/core
|
||||||
esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
|
esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
|
||||||
esphome/components/pvvx_mithermometer/* @pasiz
|
esphome/components/pvvx_mithermometer/* @pasiz
|
||||||
esphome/components/pylontech/* @functionpointer
|
esphome/components/pylontech/* @functionpointer
|
||||||
esphome/components/qmp6988/* @andrewpc
|
esphome/components/qmp6988/* @andrewpc
|
||||||
@@ -404,7 +406,7 @@ esphome/components/sensirion_common/* @martgras
|
|||||||
esphome/components/sensor/* @esphome/core
|
esphome/components/sensor/* @esphome/core
|
||||||
esphome/components/sfa30/* @ghsensdev
|
esphome/components/sfa30/* @ghsensdev
|
||||||
esphome/components/sgp40/* @SenexCrenshaw
|
esphome/components/sgp40/* @SenexCrenshaw
|
||||||
esphome/components/sgp4x/* @SenexCrenshaw @martgras
|
esphome/components/sgp4x/* @martgras @SenexCrenshaw
|
||||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
||||||
esphome/components/sht3xd/* @mrtoy-me
|
esphome/components/sht3xd/* @mrtoy-me
|
||||||
esphome/components/sht4x/* @sjtrny
|
esphome/components/sht4x/* @sjtrny
|
||||||
|
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.9.0-dev
|
PROJECT_NUMBER = 2025.10.0-dev
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# 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
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
@@ -132,14 +132,17 @@ def choose_upload_log_host(
|
|||||||
]
|
]
|
||||||
resolved.append(choose_prompt(options, purpose=purpose))
|
resolved.append(choose_prompt(options, purpose=purpose))
|
||||||
elif device == "OTA":
|
elif device == "OTA":
|
||||||
if (show_ota and "ota" in CORE.config) or (
|
if CORE.address and (
|
||||||
show_api and "api" in CORE.config
|
(show_ota and "ota" in CORE.config)
|
||||||
|
or (show_api and "api" in CORE.config)
|
||||||
):
|
):
|
||||||
resolved.append(CORE.address)
|
resolved.append(CORE.address)
|
||||||
elif show_mqtt and has_mqtt_logging():
|
elif show_mqtt and has_mqtt_logging():
|
||||||
resolved.append("MQTT")
|
resolved.append("MQTT")
|
||||||
else:
|
else:
|
||||||
resolved.append(device)
|
resolved.append(device)
|
||||||
|
if not resolved:
|
||||||
|
_LOGGER.error("All specified devices: %s could not be resolved.", defaults)
|
||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
# No devices specified, show interactive chooser
|
# No devices specified, show interactive chooser
|
||||||
@@ -223,7 +226,9 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
|
|||||||
.replace(b"\n", b"")
|
.replace(b"\n", b"")
|
||||||
.decode("utf8", "backslashreplace")
|
.decode("utf8", "backslashreplace")
|
||||||
)
|
)
|
||||||
time_str = datetime.now().time().strftime("[%H:%M:%S]")
|
time_ = datetime.now()
|
||||||
|
nanoseconds = time_.microsecond // 1000
|
||||||
|
time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
|
||||||
safe_print(parser.parse_line(line, time_str))
|
safe_print(parser.parse_line(line, time_str))
|
||||||
|
|
||||||
backtrace_state = platformio_api.process_stacktrace(
|
backtrace_state = platformio_api.process_stacktrace(
|
||||||
@@ -393,27 +398,29 @@ def check_permissions(port: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str:
|
def upload_program(
|
||||||
|
config: ConfigType, args: ArgsProtocol, devices: list[str]
|
||||||
|
) -> tuple[int, str | None]:
|
||||||
|
host = devices[0]
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||||
if getattr(module, "upload_program")(config, args, host):
|
if getattr(module, "upload_program")(config, args, host):
|
||||||
return 0
|
return 0, host
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if get_port_type(host) == "SERIAL":
|
if get_port_type(host) == "SERIAL":
|
||||||
check_permissions(host)
|
check_permissions(host)
|
||||||
|
|
||||||
|
exit_code = 1
|
||||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||||
file = getattr(args, "file", None)
|
file = getattr(args, "file", None)
|
||||||
return upload_using_esptool(config, host, file, args.upload_speed)
|
exit_code = upload_using_esptool(config, host, file, args.upload_speed)
|
||||||
|
elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
|
||||||
|
exit_code = upload_using_platformio(config, host)
|
||||||
|
# else: Unknown target platform, exit_code remains 1
|
||||||
|
|
||||||
if CORE.target_platform in (PLATFORM_RP2040):
|
return exit_code, host if exit_code == 0 else None
|
||||||
return upload_using_platformio(config, host)
|
|
||||||
|
|
||||||
if CORE.is_libretiny:
|
|
||||||
return upload_using_platformio(config, host)
|
|
||||||
|
|
||||||
return 1 # Unknown target platform
|
|
||||||
|
|
||||||
ota_conf = {}
|
ota_conf = {}
|
||||||
for ota_item in config.get(CONF_OTA, []):
|
for ota_item in config.get(CONF_OTA, []):
|
||||||
@@ -430,10 +437,10 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
|
|||||||
|
|
||||||
remote_port = int(ota_conf[CONF_PORT])
|
remote_port = int(ota_conf[CONF_PORT])
|
||||||
password = ota_conf.get(CONF_PASSWORD, "")
|
password = ota_conf.get(CONF_PASSWORD, "")
|
||||||
|
binary = args.file if getattr(args, "file", None) is not None else CORE.firmware_bin
|
||||||
|
|
||||||
# Check if we should use MQTT for address resolution
|
# Check if we should use MQTT for address resolution
|
||||||
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
|
# This happens when no device was specified, or the current host is "MQTT"/"OTA"
|
||||||
devices: list[str] = args.device or []
|
|
||||||
if (
|
if (
|
||||||
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
CONF_MQTT in config # pylint: disable=too-many-boolean-expressions
|
||||||
and (not devices or host in ("MQTT", "OTA"))
|
and (not devices or host in ("MQTT", "OTA"))
|
||||||
@@ -444,17 +451,23 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
|
|||||||
):
|
):
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
host = mqtt.get_esphome_device_ip(
|
devices = [
|
||||||
|
mqtt.get_esphome_device_ip(
|
||||||
config, args.username, args.password, args.client_id
|
config, args.username, args.password, args.client_id
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
|
||||||
if getattr(args, "file", None) is not None:
|
return espota2.run_ota(devices, remote_port, password, binary)
|
||||||
return espota2.run_ota(host, remote_port, password, args.file)
|
|
||||||
|
|
||||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
|
||||||
|
|
||||||
|
|
||||||
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
|
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
|
||||||
|
try:
|
||||||
|
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||||
|
if getattr(module, "show_logs")(config, args, devices):
|
||||||
|
return 0
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
if "logger" not in config:
|
if "logger" not in config:
|
||||||
raise EsphomeError("Logger is not configured!")
|
raise EsphomeError("Logger is not configured!")
|
||||||
|
|
||||||
@@ -476,7 +489,7 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
|
|||||||
from esphome.components.api.client import run_logs
|
from esphome.components.api.client import run_logs
|
||||||
|
|
||||||
return run_logs(config, addresses_to_use)
|
return run_logs(config, addresses_to_use)
|
||||||
if get_port_type(port) == "MQTT" and "mqtt" in config:
|
if get_port_type(port) in ("NETWORK", "MQTT") and "mqtt" in config:
|
||||||
from esphome import mqtt
|
from esphome import mqtt
|
||||||
|
|
||||||
return mqtt.show_logs(
|
return mqtt.show_logs(
|
||||||
@@ -548,17 +561,11 @@ def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
purpose="uploading",
|
purpose="uploading",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Try each device until one succeeds
|
exit_code, _ = upload_program(config, args, devices)
|
||||||
exit_code = 1
|
|
||||||
for device in devices:
|
|
||||||
_LOGGER.info("Uploading to %s", device)
|
|
||||||
exit_code = upload_program(config, args, device)
|
|
||||||
if exit_code == 0:
|
if exit_code == 0:
|
||||||
_LOGGER.info("Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
return 0
|
else:
|
||||||
if len(devices) > 1:
|
_LOGGER.warning("Failed to upload to %s", devices)
|
||||||
_LOGGER.warning("Failed to upload to %s", device)
|
|
||||||
|
|
||||||
return exit_code
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
@@ -611,19 +618,11 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
purpose="uploading",
|
purpose="uploading",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Try each device for upload until one succeeds
|
exit_code, successful_device = upload_program(config, args, devices)
|
||||||
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:
|
if exit_code == 0:
|
||||||
_LOGGER.info("Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
successful_device = device
|
else:
|
||||||
break
|
_LOGGER.warning("Failed to upload to %s", devices)
|
||||||
if len(devices) > 1:
|
|
||||||
_LOGGER.warning("Failed to upload to %s", device)
|
|
||||||
|
|
||||||
if successful_device is None:
|
|
||||||
return exit_code
|
return exit_code
|
||||||
|
|
||||||
if args.no_logs:
|
if args.no_logs:
|
||||||
|
@@ -61,11 +61,10 @@ void AbsoluteHumidityComponent::loop() {
|
|||||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
||||||
}
|
}
|
||||||
if (no_humidity) {
|
if (no_humidity) {
|
||||||
ESP_LOGW(TAG, "No valid state from temperature sensor!");
|
ESP_LOGW(TAG, "No valid state from humidity sensor!");
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
|
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
this->status_set_warning();
|
this->status_set_warning(LOG_STR("Unable to calculate absolute humidity."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +86,8 @@ void AbsoluteHumidityComponent::loop() {
|
|||||||
es = es_wobus(temperature_c);
|
es = es_wobus(temperature_c);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
|
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
this->status_set_error();
|
this->status_set_error("Invalid saturation vapor pressure equation selection!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
|
||||||
|
@@ -11,15 +11,8 @@ from esphome.components.esp32.const import (
|
|||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
from esphome.config_helpers import filter_source_files_from_platform
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
|
||||||
CONF_ANALOG,
|
|
||||||
CONF_INPUT,
|
|
||||||
CONF_NUMBER,
|
|
||||||
PLATFORM_ESP8266,
|
|
||||||
PlatformFramework,
|
|
||||||
)
|
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
@@ -273,21 +266,3 @@ def validate_adc_pin(value):
|
|||||||
)(value)
|
)(value)
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
|
||||||
{
|
|
||||||
"adc_sensor_esp32.cpp": {
|
|
||||||
PlatformFramework.ESP32_ARDUINO,
|
|
||||||
PlatformFramework.ESP32_IDF,
|
|
||||||
},
|
|
||||||
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
|
||||||
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
|
||||||
"adc_sensor_libretiny.cpp": {
|
|
||||||
PlatformFramework.BK72XX_ARDUINO,
|
|
||||||
PlatformFramework.RTL87XX_ARDUINO,
|
|
||||||
PlatformFramework.LN882X_ARDUINO,
|
|
||||||
},
|
|
||||||
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@@ -241,6 +241,8 @@ float ADCSensor::sample_autorange_() {
|
|||||||
cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
|
cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
|
||||||
|
|
||||||
err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
|
||||||
|
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
|
||||||
#else
|
#else
|
||||||
adc_cali_line_fitting_config_t cali_config = {
|
adc_cali_line_fitting_config_t cali_config = {
|
||||||
.unit_id = this->adc_unit_,
|
.unit_id = this->adc_unit_,
|
||||||
@@ -251,10 +253,14 @@ float ADCSensor::sample_autorange_() {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
|
||||||
|
(err == ESP_OK) ? "SUCCESS" : "FAILED", err);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int raw;
|
int raw;
|
||||||
err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
|
err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: Raw ADC read %s, value=%d (err=%d)", atten,
|
||||||
|
(err == ESP_OK) ? "SUCCESS" : "FAILED", raw, err);
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
|
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
|
||||||
@@ -275,8 +281,10 @@ float ADCSensor::sample_autorange_() {
|
|||||||
err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
|
err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
|
||||||
if (err == ESP_OK) {
|
if (err == ESP_OK) {
|
||||||
voltage = voltage_mv / 1000.0f;
|
voltage = voltage_mv / 1000.0f;
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: CALIBRATED - raw=%d -> %dmV -> %.6fV", atten, raw, voltage_mv, voltage);
|
||||||
} else {
|
} else {
|
||||||
voltage = raw * 3.3f / 4095.0f;
|
voltage = raw * 3.3f / 4095.0f;
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: UNCALIBRATED FALLBACK - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
|
||||||
}
|
}
|
||||||
// Clean up calibration handle
|
// Clean up calibration handle
|
||||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
|
||||||
@@ -287,6 +295,7 @@ float ADCSensor::sample_autorange_() {
|
|||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
voltage = raw * 3.3f / 4095.0f;
|
voltage = raw * 3.3f / 4095.0f;
|
||||||
|
ESP_LOGVV(TAG, "Autorange atten=%d: NO CALIBRATION - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {raw, voltage};
|
return {raw, voltage};
|
||||||
@@ -324,18 +333,32 @@ float ADCSensor::sample_autorange_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int adc_half = 2048;
|
const int adc_half = 2048;
|
||||||
uint32_t c12 = std::min(raw12, adc_half);
|
const uint32_t c12 = std::min(raw12, adc_half);
|
||||||
uint32_t c6 = adc_half - std::abs(raw6 - adc_half);
|
|
||||||
uint32_t c2 = adc_half - std::abs(raw2 - adc_half);
|
const int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
|
||||||
uint32_t c0 = std::min(4095 - raw0, adc_half);
|
const uint32_t c6 = (c6_signed > 0) ? c6_signed : 0; // Clamp to prevent underflow
|
||||||
uint32_t csum = c12 + c6 + c2 + c0;
|
|
||||||
|
const int32_t c2_signed = adc_half - std::abs(raw2 - adc_half);
|
||||||
|
const uint32_t c2 = (c2_signed > 0) ? c2_signed : 0; // Clamp to prevent underflow
|
||||||
|
|
||||||
|
const uint32_t c0 = std::min(4095 - raw0, adc_half);
|
||||||
|
const uint32_t csum = c12 + c6 + c2 + c0;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Autorange summary:");
|
||||||
|
ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
|
||||||
|
ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
|
||||||
|
ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
|
||||||
|
|
||||||
if (csum == 0) {
|
if (csum == 0) {
|
||||||
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
|
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
|
||||||
return NAN;
|
return NAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
|
const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
|
||||||
|
ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
|
||||||
|
c0, csum, final_result);
|
||||||
|
|
||||||
|
return final_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace adc
|
} // namespace adc
|
||||||
|
@@ -9,6 +9,7 @@ from esphome.components.zephyr import (
|
|||||||
zephyr_add_prj_conf,
|
zephyr_add_prj_conf,
|
||||||
zephyr_add_user,
|
zephyr_add_user,
|
||||||
)
|
)
|
||||||
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ATTENUATION,
|
CONF_ATTENUATION,
|
||||||
@@ -20,6 +21,7 @@ from esphome.const import (
|
|||||||
PLATFORM_NRF52,
|
PLATFORM_NRF52,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
|
PlatformFramework,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
@@ -174,3 +176,21 @@ async def to_code(config):
|
|||||||
}};
|
}};
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||||
|
{
|
||||||
|
"adc_sensor_esp32.cpp": {
|
||||||
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
|
PlatformFramework.ESP32_IDF,
|
||||||
|
},
|
||||||
|
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||||
|
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||||
|
"adc_sensor_libretiny.cpp": {
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
|
},
|
||||||
|
"adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
|
|||||||
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
||||||
uint8_t rev_newaddress = ~newaddress;
|
uint8_t rev_newaddress = ~newaddress;
|
||||||
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
|
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)) {
|
if (!this->write_bytes(REG_ADDRESS, data)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->status_set_warning();
|
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) {
|
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};
|
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)) {
|
if (!this->write_bytes(REG_CALIBRATION, data)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->status_set_warning();
|
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 res = *data;
|
||||||
auto crc_byte = res[len];
|
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;
|
this->error_code_ = CRC_CHECK_FAILED;
|
||||||
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
||||||
return optional<std::array<uint8_t, N>>();
|
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;
|
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 ags10
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ags10 {
|
namespace ags10 {
|
||||||
@@ -99,16 +99,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
|||||||
* Read, checks and returns data from the sensor.
|
* 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);
|
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> {
|
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||||
|
@@ -96,7 +96,7 @@ void AHT10Component::read_data_() {
|
|||||||
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
|
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
|
||||||
}
|
}
|
||||||
if (this->read(data, 6) != i2c::ERROR_OK) {
|
if (this->read(data, 6) != i2c::ERROR_OK) {
|
||||||
this->status_set_warning("Read failed, will retry");
|
this->status_set_warning(LOG_STR("Read failed, will retry"));
|
||||||
this->restart_read_();
|
this->restart_read_();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ void AHT10Component::read_data_() {
|
|||||||
} else {
|
} else {
|
||||||
ESP_LOGD(TAG, "Invalid humidity, retrying");
|
ESP_LOGD(TAG, "Invalid humidity, retrying");
|
||||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||||
}
|
}
|
||||||
this->restart_read_();
|
this->restart_read_();
|
||||||
return;
|
return;
|
||||||
@@ -144,7 +144,7 @@ void AHT10Component::update() {
|
|||||||
return;
|
return;
|
||||||
this->start_time_ = millis();
|
this->start_time_ = millis();
|
||||||
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
|
||||||
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
|
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->restart_read_();
|
this->restart_read_();
|
||||||
|
@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_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])
|
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)
|
||||||
|
@@ -13,7 +13,7 @@ from esphome.const import (
|
|||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
@@ -345,6 +345,6 @@ async def alarm_control_panel_is_armed_to_code(
|
|||||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(alarm_control_panel_ns.using)
|
cg.add_global(alarm_control_panel_ns.using)
|
||||||
|
@@ -29,22 +29,6 @@ namespace am2315c {
|
|||||||
|
|
||||||
static const char *const TAG = "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) {
|
bool AM2315C::reset_register_(uint8_t reg) {
|
||||||
// code based on demo code sent by www.aosong.com
|
// code based on demo code sent by www.aosong.com
|
||||||
// no further documentation.
|
// no further documentation.
|
||||||
@@ -86,7 +70,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
|
|||||||
humidity = raw * 9.5367431640625e-5;
|
humidity = raw * 9.5367431640625e-5;
|
||||||
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||||
temperature = raw * 1.9073486328125e-4 - 50;
|
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() {
|
void AM2315C::setup() {
|
||||||
|
@@ -21,9 +21,9 @@
|
|||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace am2315c {
|
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; }
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint8_t crc8_(uint8_t *data, uint8_t len);
|
|
||||||
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
||||||
bool reset_register_(uint8_t reg);
|
bool reset_register_(uint8_t reg);
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ from esphome.const import (
|
|||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VARIABLES,
|
CONF_VARIABLES,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
DOMAIN = "api"
|
DOMAIN = "api"
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
@@ -134,7 +134,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(40.0)
|
@coroutine_with_priority(CoroPriority.WEB)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
@@ -321,6 +321,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
|||||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
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])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||||
cg.add(var.set_service("esphome.tag_scanned"))
|
cg.add(var.set_service("esphome.tag_scanned"))
|
||||||
|
@@ -818,6 +818,7 @@ message GetTimeResponse {
|
|||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
|
||||||
fixed32 epoch_seconds = 1;
|
fixed32 epoch_seconds = 1;
|
||||||
|
string timezone = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== USER-DEFINES SERVICES ====================
|
// ==================== USER-DEFINES SERVICES ====================
|
||||||
@@ -1712,6 +1713,7 @@ message BluetoothScannerStateResponse {
|
|||||||
|
|
||||||
BluetoothScannerState state = 1;
|
BluetoothScannerState state = 1;
|
||||||
BluetoothScannerMode mode = 2;
|
BluetoothScannerMode mode = 2;
|
||||||
|
BluetoothScannerMode configured_mode = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothScannerSetModeRequest {
|
message BluetoothScannerSetModeRequest {
|
||||||
|
@@ -112,7 +112,7 @@ void APIConnection::start() {
|
|||||||
APIError err = this->helper_->init();
|
APIError err = this->helper_->init();
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
this->log_warning_("Helper init failed", err);
|
this->log_warning_(LOG_STR("Helper init failed"), err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->client_info_.peername = helper_->getpeername();
|
this->client_info_.peername = helper_->getpeername();
|
||||||
@@ -159,7 +159,7 @@ void APIConnection::loop() {
|
|||||||
break;
|
break;
|
||||||
} else if (err != APIError::OK) {
|
} else if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
this->log_warning_("Reading failed", err);
|
this->log_warning_(LOG_STR("Reading failed"), err);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this->last_traffic_ = now;
|
this->last_traffic_ = now;
|
||||||
@@ -465,9 +465,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
|||||||
resp.cold_white = values.get_cold_white();
|
resp.cold_white = values.get_cold_white();
|
||||||
resp.warm_white = values.get_warm_white();
|
resp.warm_white = values.get_warm_white();
|
||||||
if (light->supports_effects()) {
|
if (light->supports_effects()) {
|
||||||
// get_effect_name() returns temporary std::string - must store it
|
resp.set_effect(light->get_effect_name_ref());
|
||||||
std::string effect_name = light->get_effect_name();
|
|
||||||
resp.set_effect(StringRef(effect_name));
|
|
||||||
}
|
}
|
||||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
@@ -1072,8 +1070,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
|
|||||||
|
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||||
if (homeassistant::global_homeassistant_time != nullptr)
|
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||||
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
|
||||||
|
#ifdef USE_TIME_TIMEZONE
|
||||||
|
if (!value.timezone.empty() && value.timezone != homeassistant::global_homeassistant_time->get_timezone()) {
|
||||||
|
homeassistant::global_homeassistant_time->set_timezone(value.timezone);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1425,9 +1429,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
|||||||
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||||
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||||
|
|
||||||
// get_compilation_time() returns temporary std::string - must store it
|
resp.set_compilation_time(App.get_compilation_time_ref());
|
||||||
std::string compilation_time = App.get_compilation_time();
|
|
||||||
resp.set_compilation_time(StringRef(compilation_time));
|
|
||||||
|
|
||||||
// Compile-time StringRef constants for manufacturers
|
// Compile-time StringRef constants for manufacturers
|
||||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||||
@@ -1569,7 +1571,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
|||||||
return false;
|
return false;
|
||||||
if (err != APIError::OK) {
|
if (err != APIError::OK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
this->log_warning_("Packet write failed", err);
|
this->log_warning_(LOG_STR("Packet write failed"), err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Do not set last_traffic_ on send
|
// Do not set last_traffic_ on send
|
||||||
@@ -1756,7 +1758,7 @@ void APIConnection::process_batch_() {
|
|||||||
std::span<const PacketInfo>(packet_info, packet_count));
|
std::span<const PacketInfo>(packet_info, packet_count));
|
||||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
this->log_warning_("Batch write failed", err);
|
this->log_warning_(LOG_STR("Batch write failed"), err);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@@ -1834,11 +1836,14 @@ void APIConnection::process_state_subscriptions_() {
|
|||||||
}
|
}
|
||||||
#endif // USE_API_HOMEASSISTANT_STATES
|
#endif // USE_API_HOMEASSISTANT_STATES
|
||||||
|
|
||||||
void APIConnection::log_warning_(const char *message, APIError err) {
|
void APIConnection::log_warning_(const LogString *message, APIError err) {
|
||||||
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
|
ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), LOG_STR_ARG(message),
|
||||||
|
LOG_STR_ARG(api_error_to_logstr(err)), errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
|
void APIConnection::log_socket_operation_failed_(APIError err) {
|
||||||
|
this->log_warning_(LOG_STR("Socket operation failed"), err);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
#endif
|
#endif
|
||||||
|
@@ -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
|
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class APIConnection : public APIServerConnection {
|
class APIConnection final : public APIServerConnection {
|
||||||
public:
|
public:
|
||||||
friend class APIServer;
|
friend class APIServer;
|
||||||
friend class ListEntitiesIterator;
|
friend class ListEntitiesIterator;
|
||||||
@@ -301,9 +301,17 @@ class APIConnection : public APIServerConnection {
|
|||||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||||
// Set common fields that are shared by all entity types
|
// Set common fields that are shared by all entity types
|
||||||
msg.key = entity->get_object_id_hash();
|
msg.key = entity->get_object_id_hash();
|
||||||
// IMPORTANT: get_object_id() may return a temporary std::string
|
// Try to use static reference first to avoid allocation
|
||||||
std::string object_id = entity->get_object_id();
|
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));
|
msg.set_object_id(StringRef(object_id));
|
||||||
|
}
|
||||||
|
|
||||||
if (entity->has_own_name()) {
|
if (entity->has_own_name()) {
|
||||||
msg.set_name(entity->get_name());
|
msg.set_name(entity->get_name());
|
||||||
@@ -724,7 +732,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to log API errors with errno
|
// Helper function to log API errors with errno
|
||||||
void log_warning_(const char *message, APIError err);
|
void log_warning_(const LogString *message, APIError err);
|
||||||
// Specific helper for duplicated error message
|
// Specific helper for duplicated error message
|
||||||
void log_socket_operation_failed_(APIError err);
|
void log_socket_operation_failed_(APIError err);
|
||||||
};
|
};
|
||||||
|
@@ -23,59 +23,59 @@ static const char *const TAG = "api.frame_helper";
|
|||||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *api_error_to_str(APIError err) {
|
const LogString *api_error_to_logstr(APIError err) {
|
||||||
// not using switch to ensure compiler doesn't try to build a big table out of it
|
// not using switch to ensure compiler doesn't try to build a big table out of it
|
||||||
if (err == APIError::OK) {
|
if (err == APIError::OK) {
|
||||||
return "OK";
|
return LOG_STR("OK");
|
||||||
} else if (err == APIError::WOULD_BLOCK) {
|
} else if (err == APIError::WOULD_BLOCK) {
|
||||||
return "WOULD_BLOCK";
|
return LOG_STR("WOULD_BLOCK");
|
||||||
} else if (err == APIError::BAD_INDICATOR) {
|
} else if (err == APIError::BAD_INDICATOR) {
|
||||||
return "BAD_INDICATOR";
|
return LOG_STR("BAD_INDICATOR");
|
||||||
} else if (err == APIError::BAD_DATA_PACKET) {
|
} else if (err == APIError::BAD_DATA_PACKET) {
|
||||||
return "BAD_DATA_PACKET";
|
return LOG_STR("BAD_DATA_PACKET");
|
||||||
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
} else if (err == APIError::TCP_NODELAY_FAILED) {
|
||||||
return "TCP_NODELAY_FAILED";
|
return LOG_STR("TCP_NODELAY_FAILED");
|
||||||
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
} else if (err == APIError::TCP_NONBLOCKING_FAILED) {
|
||||||
return "TCP_NONBLOCKING_FAILED";
|
return LOG_STR("TCP_NONBLOCKING_FAILED");
|
||||||
} else if (err == APIError::CLOSE_FAILED) {
|
} else if (err == APIError::CLOSE_FAILED) {
|
||||||
return "CLOSE_FAILED";
|
return LOG_STR("CLOSE_FAILED");
|
||||||
} else if (err == APIError::SHUTDOWN_FAILED) {
|
} else if (err == APIError::SHUTDOWN_FAILED) {
|
||||||
return "SHUTDOWN_FAILED";
|
return LOG_STR("SHUTDOWN_FAILED");
|
||||||
} else if (err == APIError::BAD_STATE) {
|
} else if (err == APIError::BAD_STATE) {
|
||||||
return "BAD_STATE";
|
return LOG_STR("BAD_STATE");
|
||||||
} else if (err == APIError::BAD_ARG) {
|
} else if (err == APIError::BAD_ARG) {
|
||||||
return "BAD_ARG";
|
return LOG_STR("BAD_ARG");
|
||||||
} else if (err == APIError::SOCKET_READ_FAILED) {
|
} else if (err == APIError::SOCKET_READ_FAILED) {
|
||||||
return "SOCKET_READ_FAILED";
|
return LOG_STR("SOCKET_READ_FAILED");
|
||||||
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
} else if (err == APIError::SOCKET_WRITE_FAILED) {
|
||||||
return "SOCKET_WRITE_FAILED";
|
return LOG_STR("SOCKET_WRITE_FAILED");
|
||||||
} else if (err == APIError::OUT_OF_MEMORY) {
|
} else if (err == APIError::OUT_OF_MEMORY) {
|
||||||
return "OUT_OF_MEMORY";
|
return LOG_STR("OUT_OF_MEMORY");
|
||||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||||
return "CONNECTION_CLOSED";
|
return LOG_STR("CONNECTION_CLOSED");
|
||||||
}
|
}
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||||
return "BAD_HANDSHAKE_PACKET_LEN";
|
return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
|
||||||
return "HANDSHAKESTATE_READ_FAILED";
|
return LOG_STR("HANDSHAKESTATE_READ_FAILED");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
|
||||||
return "HANDSHAKESTATE_WRITE_FAILED";
|
return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
} else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
|
||||||
return "HANDSHAKESTATE_BAD_STATE";
|
return LOG_STR("HANDSHAKESTATE_BAD_STATE");
|
||||||
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
} else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
|
||||||
return "CIPHERSTATE_DECRYPT_FAILED";
|
return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
|
||||||
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
} else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
|
||||||
return "CIPHERSTATE_ENCRYPT_FAILED";
|
return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
|
||||||
return "HANDSHAKESTATE_SETUP_FAILED";
|
return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
|
||||||
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
} else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
|
||||||
return "HANDSHAKESTATE_SPLIT_FAILED";
|
return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
|
||||||
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
||||||
return "BAD_HANDSHAKE_ERROR_BYTE";
|
return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return "UNKNOWN";
|
return LOG_STR("UNKNOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default implementation for loop - handles sending buffered data
|
// Default implementation for loop - handles sending buffered data
|
||||||
|
@@ -66,7 +66,7 @@ enum class APIError : uint16_t {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *api_error_to_str(APIError err);
|
const LogString *api_error_to_logstr(APIError err);
|
||||||
|
|
||||||
class APIFrameHelper {
|
class APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
@@ -104,9 +104,9 @@ class APIFrameHelper {
|
|||||||
// The buffer contains all messages with appropriate padding before each
|
// The buffer contains all messages with appropriate padding before each
|
||||||
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
||||||
// Get the frame header padding required by this protocol
|
// 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
|
// 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
|
// Check if socket has data ready to read
|
||||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||||
|
|
||||||
|
@@ -10,10 +10,18 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#include <pgmspace.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
static const char *const TAG = "api.noise";
|
static const char *const TAG = "api.noise";
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
|
||||||
|
#else
|
||||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||||
|
#endif
|
||||||
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||||
|
|
||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
|
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
|
||||||
@@ -27,42 +35,42 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Convert a noise error code to a readable error
|
/// Convert a noise error code to a readable error
|
||||||
std::string noise_err_to_str(int err) {
|
const LogString *noise_err_to_logstr(int err) {
|
||||||
if (err == NOISE_ERROR_NO_MEMORY)
|
if (err == NOISE_ERROR_NO_MEMORY)
|
||||||
return "NO_MEMORY";
|
return LOG_STR("NO_MEMORY");
|
||||||
if (err == NOISE_ERROR_UNKNOWN_ID)
|
if (err == NOISE_ERROR_UNKNOWN_ID)
|
||||||
return "UNKNOWN_ID";
|
return LOG_STR("UNKNOWN_ID");
|
||||||
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
||||||
return "UNKNOWN_NAME";
|
return LOG_STR("UNKNOWN_NAME");
|
||||||
if (err == NOISE_ERROR_MAC_FAILURE)
|
if (err == NOISE_ERROR_MAC_FAILURE)
|
||||||
return "MAC_FAILURE";
|
return LOG_STR("MAC_FAILURE");
|
||||||
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
||||||
return "NOT_APPLICABLE";
|
return LOG_STR("NOT_APPLICABLE");
|
||||||
if (err == NOISE_ERROR_SYSTEM)
|
if (err == NOISE_ERROR_SYSTEM)
|
||||||
return "SYSTEM";
|
return LOG_STR("SYSTEM");
|
||||||
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
||||||
return "REMOTE_KEY_REQUIRED";
|
return LOG_STR("REMOTE_KEY_REQUIRED");
|
||||||
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
||||||
return "LOCAL_KEY_REQUIRED";
|
return LOG_STR("LOCAL_KEY_REQUIRED");
|
||||||
if (err == NOISE_ERROR_PSK_REQUIRED)
|
if (err == NOISE_ERROR_PSK_REQUIRED)
|
||||||
return "PSK_REQUIRED";
|
return LOG_STR("PSK_REQUIRED");
|
||||||
if (err == NOISE_ERROR_INVALID_LENGTH)
|
if (err == NOISE_ERROR_INVALID_LENGTH)
|
||||||
return "INVALID_LENGTH";
|
return LOG_STR("INVALID_LENGTH");
|
||||||
if (err == NOISE_ERROR_INVALID_PARAM)
|
if (err == NOISE_ERROR_INVALID_PARAM)
|
||||||
return "INVALID_PARAM";
|
return LOG_STR("INVALID_PARAM");
|
||||||
if (err == NOISE_ERROR_INVALID_STATE)
|
if (err == NOISE_ERROR_INVALID_STATE)
|
||||||
return "INVALID_STATE";
|
return LOG_STR("INVALID_STATE");
|
||||||
if (err == NOISE_ERROR_INVALID_NONCE)
|
if (err == NOISE_ERROR_INVALID_NONCE)
|
||||||
return "INVALID_NONCE";
|
return LOG_STR("INVALID_NONCE");
|
||||||
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
||||||
return "INVALID_PRIVATE_KEY";
|
return LOG_STR("INVALID_PRIVATE_KEY");
|
||||||
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
||||||
return "INVALID_PUBLIC_KEY";
|
return LOG_STR("INVALID_PUBLIC_KEY");
|
||||||
if (err == NOISE_ERROR_INVALID_FORMAT)
|
if (err == NOISE_ERROR_INVALID_FORMAT)
|
||||||
return "INVALID_FORMAT";
|
return LOG_STR("INVALID_FORMAT");
|
||||||
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
||||||
return "INVALID_SIGNATURE";
|
return LOG_STR("INVALID_SIGNATURE");
|
||||||
return to_string(err);
|
return LOG_STR("UNKNOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the frame helper, returns OK if successful.
|
/// Initialize the frame helper, returns OK if successful.
|
||||||
@@ -75,7 +83,11 @@ APIError APINoiseFrameHelper::init() {
|
|||||||
// init prologue
|
// init prologue
|
||||||
size_t old_size = prologue_.size();
|
size_t old_size = prologue_.size();
|
||||||
prologue_.resize(old_size + PROLOGUE_INIT_LEN);
|
prologue_.resize(old_size + PROLOGUE_INIT_LEN);
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||||
|
#else
|
||||||
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||||
|
#endif
|
||||||
|
|
||||||
state_ = State::CLIENT_HELLO;
|
state_ = State::CLIENT_HELLO;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
@@ -83,18 +95,18 @@ APIError APINoiseFrameHelper::init() {
|
|||||||
// Helper for handling handshake frame errors
|
// Helper for handling handshake frame errors
|
||||||
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
||||||
if (aerr == APIError::BAD_INDICATOR) {
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
send_explicit_handshake_reject_("Bad indicator byte");
|
send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
|
||||||
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||||
send_explicit_handshake_reject_("Bad handshake packet len");
|
send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
|
||||||
}
|
}
|
||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for handling noise library errors
|
// Helper for handling noise library errors
|
||||||
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
|
APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
state_ = State::FAILED;
|
state_ = State::FAILED;
|
||||||
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
|
HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
|
||||||
return api_err;
|
return api_err;
|
||||||
}
|
}
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
@@ -279,11 +291,11 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frame.empty()) {
|
if (frame.empty()) {
|
||||||
send_explicit_handshake_reject_("Empty handshake message");
|
send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
} else if (frame[0] != 0x00) {
|
} else if (frame[0] != 0x00) {
|
||||||
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
||||||
send_explicit_handshake_reject_("Bad handshake error byte");
|
send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
|
||||||
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,8 +305,10 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
// Special handling for MAC failure
|
// Special handling for MAC failure
|
||||||
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
|
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
|
||||||
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
|
: LOG_STR("Handshake error"));
|
||||||
|
return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
|
||||||
|
APIError::HANDSHAKESTATE_READ_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
aerr = check_handshake_finished_();
|
aerr = check_handshake_finished_();
|
||||||
@@ -307,8 +321,8 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
||||||
|
|
||||||
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
||||||
APIError aerr_write =
|
APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
|
||||||
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
|
APIError::HANDSHAKESTATE_WRITE_FAILED);
|
||||||
if (aerr_write != APIError::OK)
|
if (aerr_write != APIError::OK)
|
||||||
return aerr_write;
|
return aerr_write;
|
||||||
buffer[0] = 0x00; // success
|
buffer[0] = 0x00; // success
|
||||||
@@ -331,15 +345,31 @@ APIError APINoiseFrameHelper::state_action_() {
|
|||||||
}
|
}
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
|
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
|
||||||
|
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||||
|
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
||||||
|
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
data.resize(reason.length() + 1);
|
data.resize(reason_len + 1);
|
||||||
|
data[0] = 0x01; // failure
|
||||||
|
|
||||||
|
// Copy error message from PROGMEM
|
||||||
|
if (reason_len > 0) {
|
||||||
|
memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Normal memory access
|
||||||
|
const char *reason_str = LOG_STR_ARG(reason);
|
||||||
|
size_t reason_len = strlen(reason_str);
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
data.resize(reason_len + 1);
|
||||||
data[0] = 0x01; // failure
|
data[0] = 0x01; // failure
|
||||||
|
|
||||||
// Copy error message in bulk
|
// Copy error message in bulk
|
||||||
if (!reason.empty()) {
|
if (reason_len > 0) {
|
||||||
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
std::memcpy(data.data() + 1, reason_str, reason_len);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// temporarily remove failed state
|
// temporarily remove failed state
|
||||||
auto orig_state = state_;
|
auto orig_state = state_;
|
||||||
@@ -368,7 +398,8 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
noise_buffer_init(mbuf);
|
noise_buffer_init(mbuf);
|
||||||
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
||||||
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
||||||
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
|
APIError decrypt_err =
|
||||||
|
handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||||
if (decrypt_err != APIError::OK)
|
if (decrypt_err != APIError::OK)
|
||||||
return decrypt_err;
|
return decrypt_err;
|
||||||
|
|
||||||
@@ -450,7 +481,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
|
|||||||
4 + packet.payload_size + frame_footer_size_);
|
4 + packet.payload_size + frame_footer_size_);
|
||||||
|
|
||||||
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||||
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
APIError aerr =
|
||||||
|
handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
@@ -504,25 +536,27 @@ APIError APINoiseFrameHelper::init_handshake_() {
|
|||||||
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
||||||
|
|
||||||
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
||||||
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
APIError aerr =
|
||||||
|
handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
const auto &psk = ctx_->get_psk();
|
const auto &psk = ctx_->get_psk();
|
||||||
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
||||||
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
|
||||||
|
APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
||||||
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
// set_prologue copies it into handshakestate, so we can get rid of it now
|
// set_prologue copies it into handshakestate, so we can get rid of it now
|
||||||
prologue_ = {};
|
prologue_ = {};
|
||||||
|
|
||||||
err = noise_handshakestate_start(handshake_);
|
err = noise_handshakestate_start(handshake_);
|
||||||
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
@@ -540,7 +574,8 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
|||||||
return APIError::HANDSHAKESTATE_BAD_STATE;
|
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||||
}
|
}
|
||||||
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
||||||
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
APIError aerr =
|
||||||
|
handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
||||||
if (aerr != APIError::OK)
|
if (aerr != APIError::OK)
|
||||||
return aerr;
|
return aerr;
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
class APINoiseFrameHelper : public APIFrameHelper {
|
class APINoiseFrameHelper final : public APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
|
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
|
||||||
const ClientInfo *client_info)
|
const ClientInfo *client_info)
|
||||||
@@ -25,10 +25,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) 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:
|
protected:
|
||||||
APIError state_action_();
|
APIError state_action_();
|
||||||
@@ -36,9 +32,9 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
|||||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||||
APIError init_handshake_();
|
APIError init_handshake_();
|
||||||
APIError check_handshake_finished_();
|
APIError check_handshake_finished_();
|
||||||
void send_explicit_handshake_reject_(const std::string &reason);
|
void send_explicit_handshake_reject_(const LogString *reason);
|
||||||
APIError handle_handshake_frame_error_(APIError aerr);
|
APIError handle_handshake_frame_error_(APIError aerr);
|
||||||
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
|
APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err);
|
||||||
|
|
||||||
// Pointers first (4 bytes each)
|
// Pointers first (4 bytes each)
|
||||||
NoiseHandshakeState *handshake_{nullptr};
|
NoiseHandshakeState *handshake_{nullptr};
|
||||||
|
@@ -10,6 +10,10 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#include <pgmspace.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
static const char *const TAG = "api.plaintext";
|
static const char *const TAG = "api.plaintext";
|
||||||
@@ -197,11 +201,20 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
|||||||
// We must send at least 3 bytes to be read, so we add
|
// We must send at least 3 bytes to be read, so we add
|
||||||
// a message after the indicator byte to ensures its long
|
// a message after the indicator byte to ensures its long
|
||||||
// enough and can aid in debugging.
|
// enough and can aid in debugging.
|
||||||
const char msg[] = "\x00"
|
static constexpr uint8_t INDICATOR_MSG_SIZE = 19;
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
static const char MSG_PROGMEM[] PROGMEM = "\x00"
|
||||||
"Bad indicator byte";
|
"Bad indicator byte";
|
||||||
|
char msg[INDICATOR_MSG_SIZE];
|
||||||
|
memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
|
||||||
iov[0].iov_base = (void *) msg;
|
iov[0].iov_base = (void *) msg;
|
||||||
iov[0].iov_len = 19;
|
#else
|
||||||
this->write_raw_(iov, 1, 19);
|
static const char MSG[] = "\x00"
|
||||||
|
"Bad indicator byte";
|
||||||
|
iov[0].iov_base = (void *) MSG;
|
||||||
|
#endif
|
||||||
|
iov[0].iov_len = INDICATOR_MSG_SIZE;
|
||||||
|
this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
|
||||||
}
|
}
|
||||||
return aerr;
|
return aerr;
|
||||||
}
|
}
|
||||||
@@ -235,8 +248,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
|||||||
|
|
||||||
for (const auto &packet : packets) {
|
for (const auto &packet : packets) {
|
||||||
// Calculate varint sizes for header layout
|
// Calculate varint sizes for header layout
|
||||||
uint8_t size_varint_len = api::ProtoSize::varint(packet.payload_size);
|
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
|
||||||
uint8_t type_varint_len = api::ProtoSize::varint(packet.message_type);
|
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
|
||||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||||
|
|
||||||
// Calculate where to start writing the header
|
// Calculate where to start writing the header
|
||||||
@@ -271,8 +284,9 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
|||||||
buf_start[header_offset] = 0x00; // indicator
|
buf_start[header_offset] = 0x00; // indicator
|
||||||
|
|
||||||
// Encode varints directly into buffer
|
// Encode varints directly into buffer
|
||||||
encode_varint_unchecked(buf_start + header_offset + 1, packet.payload_size);
|
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||||
encode_varint_unchecked(buf_start + header_offset + 1 + size_varint_len, packet.message_type);
|
ProtoVarInt(packet.message_type)
|
||||||
|
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||||
|
|
||||||
// Add iovec for this packet (header + payload)
|
// Add iovec for this packet (header + payload)
|
||||||
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
||||||
: APIFrameHelper(std::move(socket), client_info) {
|
: APIFrameHelper(std::move(socket), client_info) {
|
||||||
@@ -22,9 +22,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
|||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) 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:
|
protected:
|
||||||
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||||
|
@@ -901,6 +901,16 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
|
switch (field_id) {
|
||||||
|
case 2:
|
||||||
|
this->timezone = value.as_string();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case 1:
|
case 1:
|
||||||
@@ -911,8 +921,14 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
|
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
void GetTimeResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->epoch_seconds); }
|
buffer.encode_fixed32(1, this->epoch_seconds);
|
||||||
|
buffer.encode_string(2, this->timezone_ref_);
|
||||||
|
}
|
||||||
|
void GetTimeResponse::calculate_size(ProtoSize &size) const {
|
||||||
|
size.add_fixed32(1, this->epoch_seconds);
|
||||||
|
size.add_length(1, this->timezone_ref_.size());
|
||||||
|
}
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
|
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_string(1, this->name_ref_);
|
buffer.encode_string(1, this->name_ref_);
|
||||||
@@ -2153,10 +2169,12 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
|
|||||||
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
|
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
|
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
|
||||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
||||||
|
buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
|
||||||
}
|
}
|
||||||
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
|
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->state));
|
size.add_uint32(1, static_cast<uint32_t>(this->state));
|
||||||
size.add_uint32(1, static_cast<uint32_t>(this->mode));
|
size.add_uint32(1, static_cast<uint32_t>(this->mode));
|
||||||
|
size.add_uint32(1, static_cast<uint32_t>(this->configured_mode));
|
||||||
}
|
}
|
||||||
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1110,7 +1110,17 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
|
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
|
||||||
void GetTimeResponse::dump_to(std::string &out) const { dump_field(out, "epoch_seconds", this->epoch_seconds); }
|
void GetTimeResponse::dump_to(std::string &out) const {
|
||||||
|
MessageDumpHelper helper(out, "GetTimeResponse");
|
||||||
|
dump_field(out, "epoch_seconds", this->epoch_seconds);
|
||||||
|
out.append(" timezone: ");
|
||||||
|
if (!this->timezone_ref_.empty()) {
|
||||||
|
out.append("'").append(this->timezone_ref_.c_str()).append("'");
|
||||||
|
} else {
|
||||||
|
out.append("'").append(this->timezone).append("'");
|
||||||
|
}
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
|
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
|
MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
|
||||||
@@ -1135,7 +1145,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "string_", this->string_);
|
dump_field(out, "string_", this->string_);
|
||||||
dump_field(out, "int_", this->int_);
|
dump_field(out, "int_", this->int_);
|
||||||
for (const auto it : this->bool_array) {
|
for (const auto it : this->bool_array) {
|
||||||
dump_field(out, "bool_array", it, 4);
|
dump_field(out, "bool_array", static_cast<bool>(it), 4);
|
||||||
}
|
}
|
||||||
for (const auto &it : this->int_array) {
|
for (const auto &it : this->int_array) {
|
||||||
dump_field(out, "int_array", it, 4);
|
dump_field(out, "int_array", it, 4);
|
||||||
@@ -1704,6 +1714,7 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
|
|||||||
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
|
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
|
||||||
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
|
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
|
||||||
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
|
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
|
||||||
|
dump_field(out, "configured_mode", static_cast<enums::BluetoothScannerMode>(this->configured_mode));
|
||||||
}
|
}
|
||||||
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
|
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
|
||||||
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
|
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
|
||||||
|
@@ -62,9 +62,11 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
|||||||
time_ = datetime.now()
|
time_ = datetime.now()
|
||||||
message: bytes = msg.message
|
message: bytes = msg.message
|
||||||
text = message.decode("utf8", "backslashreplace")
|
text = message.decode("utf8", "backslashreplace")
|
||||||
for parsed_msg in parse_log_message(
|
nanoseconds = time_.microsecond // 1000
|
||||||
text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]"
|
timestamp = (
|
||||||
):
|
f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
|
||||||
|
)
|
||||||
|
for parsed_msg in parse_log_message(text, timestamp):
|
||||||
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
|
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
|
||||||
|
|
||||||
stop = await async_run(cli, on_log, name=name)
|
stop = await async_run(cli, on_log, name=name)
|
||||||
|
@@ -8,74 +8,70 @@ namespace esphome::api {
|
|||||||
static const char *const TAG = "api.proto";
|
static const char *const TAG = "api.proto";
|
||||||
|
|
||||||
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||||
uint32_t i = 0;
|
const uint8_t *ptr = buffer;
|
||||||
bool error = false;
|
const uint8_t *end = buffer + length;
|
||||||
while (i < length) {
|
|
||||||
|
while (ptr < end) {
|
||||||
uint32_t consumed;
|
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()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
|
ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t field_type = (res->as_uint32()) & 0b111;
|
uint32_t tag = res->as_uint32();
|
||||||
uint32_t field_id = (res->as_uint32()) >> 3;
|
uint32_t field_type = tag & 0b111;
|
||||||
i += consumed;
|
uint32_t field_id = tag >> 3;
|
||||||
|
ptr += consumed;
|
||||||
|
|
||||||
switch (field_type) {
|
switch (field_type) {
|
||||||
case 0: { // VarInt
|
case 0: { // VarInt
|
||||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
|
ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (!this->decode_varint(field_id, *res)) {
|
if (!this->decode_varint(field_id, *res)) {
|
||||||
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
|
ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
|
||||||
}
|
}
|
||||||
i += consumed;
|
ptr += consumed;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2: { // Length-delimited
|
case 2: { // Length-delimited
|
||||||
res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
|
res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
|
||||||
if (!res.has_value()) {
|
if (!res.has_value()) {
|
||||||
ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
|
ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
uint32_t field_length = res->as_uint32();
|
uint32_t field_length = res->as_uint32();
|
||||||
i += consumed;
|
ptr += consumed;
|
||||||
if (field_length > length - i) {
|
if (ptr + field_length > end) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
|
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
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);
|
ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
|
||||||
}
|
}
|
||||||
i += field_length;
|
ptr += field_length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 5: { // 32-bit
|
case 5: { // 32-bit
|
||||||
if (length - i < 4) {
|
if (ptr + 4 > end) {
|
||||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
|
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
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))) {
|
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
|
||||||
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
|
||||||
}
|
}
|
||||||
i += 4;
|
ptr += 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
|
ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
|
||||||
error = true;
|
return;
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -124,6 +124,34 @@ class ProtoVarInt {
|
|||||||
// with ZigZag encoding
|
// with ZigZag encoding
|
||||||
return decode_zigzag64(this->value_);
|
return decode_zigzag64(this->value_);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
||||||
|
*
|
||||||
|
* @param buffer The pre-allocated buffer to write the encoded varint to
|
||||||
|
* @param len The size of the buffer in bytes
|
||||||
|
*
|
||||||
|
* @note The caller is responsible for ensuring the buffer is large enough
|
||||||
|
* to hold the encoded value. Use ProtoSize::varint() to calculate
|
||||||
|
* the exact size needed before calling this method.
|
||||||
|
* @note No bounds checking is performed for performance reasons.
|
||||||
|
*/
|
||||||
|
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
|
||||||
|
uint64_t val = this->value_;
|
||||||
|
if (val <= 0x7F) {
|
||||||
|
buffer[0] = val;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t i = 0;
|
||||||
|
while (val && i < len) {
|
||||||
|
uint8_t temp = val & 0x7F;
|
||||||
|
val >>= 7;
|
||||||
|
if (val) {
|
||||||
|
buffer[i++] = temp | 0x80;
|
||||||
|
} else {
|
||||||
|
buffer[i++] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
void encode(std::vector<uint8_t> &out) {
|
void encode(std::vector<uint8_t> &out) {
|
||||||
uint64_t val = this->value_;
|
uint64_t val = this->value_;
|
||||||
if (val <= 0x7F) {
|
if (val <= 0x7F) {
|
||||||
@@ -302,28 +330,6 @@ class ProtoWriteBuffer {
|
|||||||
std::vector<uint8_t> *buffer_;
|
std::vector<uint8_t> *buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Encode a uint16_t value as a varint directly to a buffer without bounds checking
|
|
||||||
*
|
|
||||||
* @param buffer The pre-allocated buffer to write the encoded varint to
|
|
||||||
* @param value The uint16_t value to encode (0-65535)
|
|
||||||
*
|
|
||||||
* @note The caller is responsible for ensuring the buffer is large enough (max 3 bytes for uint16_t)
|
|
||||||
* @note No bounds checking is performed for performance reasons
|
|
||||||
*/
|
|
||||||
inline void encode_varint_unchecked(uint8_t *buffer, uint16_t value) {
|
|
||||||
if (value < 128) {
|
|
||||||
buffer[0] = value;
|
|
||||||
} else if (value < 16384) {
|
|
||||||
buffer[0] = (value & 0x7F) | 0x80;
|
|
||||||
buffer[1] = value >> 7;
|
|
||||||
} else {
|
|
||||||
buffer[0] = (value & 0x7F) | 0x80;
|
|
||||||
buffer[1] = ((value >> 7) & 0x7F) | 0x80;
|
|
||||||
buffer[2] = value >> 14;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class ProtoSize;
|
class ProtoSize;
|
||||||
|
|
||||||
@@ -380,33 +386,6 @@ class ProtoSize {
|
|||||||
|
|
||||||
uint32_t get_size() const { return total_size_; }
|
uint32_t get_size() const { return total_size_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates the size in bytes needed to encode a uint8_t value as a varint
|
|
||||||
*
|
|
||||||
* @param value The uint8_t value to calculate size for
|
|
||||||
* @return The number of bytes needed to encode the value (1 or 2)
|
|
||||||
*/
|
|
||||||
static constexpr uint8_t varint(uint8_t value) {
|
|
||||||
// For uint8_t (0-255), we need at most 2 bytes
|
|
||||||
return (value < 128) ? 1 : 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates the size in bytes needed to encode a uint16_t value as a varint
|
|
||||||
*
|
|
||||||
* @param value The uint16_t value to calculate size for
|
|
||||||
* @return The number of bytes needed to encode the value (1-3)
|
|
||||||
*/
|
|
||||||
static constexpr uint8_t varint(uint16_t value) {
|
|
||||||
// For uint16_t (0-65535), we need at most 3 bytes
|
|
||||||
if (value < 128)
|
|
||||||
return 1; // 7 bits
|
|
||||||
else if (value < 16384)
|
|
||||||
return 2; // 14 bits
|
|
||||||
else
|
|
||||||
return 3; // 15-16 bits
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
||||||
*
|
*
|
||||||
@@ -416,9 +395,11 @@ class ProtoSize {
|
|||||||
static constexpr uint32_t varint(uint32_t value) {
|
static constexpr uint32_t varint(uint32_t value) {
|
||||||
// Optimized varint size calculation using leading zeros
|
// Optimized varint size calculation using leading zeros
|
||||||
// Each 7 bits requires one byte in the varint encoding
|
// Each 7 bits requires one byte in the varint encoding
|
||||||
if (value < 128) {
|
if (value < 128)
|
||||||
return 1; // 7 bits, common case for small values
|
return 1; // 7 bits, common case for small values
|
||||||
} else if (value < 16384) {
|
|
||||||
|
// For larger values, count bytes needed based on the position of the highest bit set
|
||||||
|
if (value < 16384) {
|
||||||
return 2; // 14 bits
|
return 2; // 14 bits
|
||||||
} else if (value < 2097152) {
|
} else if (value < 2097152) {
|
||||||
return 3; // 21 bits
|
return 3; // 21 bits
|
||||||
@@ -792,7 +773,7 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa
|
|||||||
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
|
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
|
||||||
|
|
||||||
// Write the length varint directly
|
// Write the length varint directly
|
||||||
encode_varint_unchecked(this->buffer_->data() + begin, static_cast<uint16_t>(msg_length_bytes));
|
ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
|
||||||
|
|
||||||
// Now encode the message content - it will append to the buffer
|
// Now encode the message content - it will append to the buffer
|
||||||
value.encode(*this);
|
value.encode(*this);
|
||||||
|
@@ -8,7 +8,7 @@ from esphome.const import (
|
|||||||
PLATFORM_LN882X,
|
PLATFORM_LN882X,
|
||||||
PLATFORM_RTL87XX,
|
PLATFORM_RTL87XX,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(200.0)
|
@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
if CORE.is_esp32 or CORE.is_libretiny:
|
if CORE.is_esp32 or CORE.is_libretiny:
|
||||||
# https://github.com/ESP32Async/AsyncTCP
|
# https://github.com/ESP32Async/AsyncTCP
|
||||||
|
@@ -16,6 +16,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_POWER_FACTOR,
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
|
DEVICE_CLASS_REACTIVE_POWER,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
ICON_CURRENT_AC,
|
ICON_CURRENT_AC,
|
||||||
ICON_LIGHTBULB,
|
ICON_LIGHTBULB,
|
||||||
@@ -78,6 +79,7 @@ CONFIG_SCHEMA = (
|
|||||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||||
icon=ICON_LIGHTBULB,
|
icon=ICON_LIGHTBULB,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
|
device_class=DEVICE_CLASS_REACTIVE_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||||
|
@@ -382,20 +382,15 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
|
|||||||
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
||||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
||||||
// Default is 143FH (20ms, 63ms)
|
// Default is 143FH (20ms, 63ms)
|
||||||
uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) {
|
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 addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||||
uint8_t addrl = (a_register & 0xFF);
|
uint8_t addrl = (a_register & 0xFF);
|
||||||
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
|
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
|
||||||
this->transfer_array(data, 4);
|
this->transfer_array(data, 4);
|
||||||
uint16_t output = encode_uint16(data[2], data[3]);
|
uint16_t output = encode_uint16(data[2], data[3]);
|
||||||
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
uint16_t output = this->read16_transaction_(a_register);
|
|
||||||
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
|
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
|
||||||
this->disable();
|
this->disable();
|
||||||
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
|
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
|
||||||
@@ -403,14 +398,8 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||||
this->enable();
|
const uint16_t val_h = this->read16_(addr_h);
|
||||||
delay_microseconds_safe(1);
|
const uint16_t val_l = this->read16_(addr_l);
|
||||||
const uint16_t val_h = this->read16_transaction_(addr_h);
|
|
||||||
delay_microseconds_safe(1);
|
|
||||||
const uint16_t val_l = this->read16_transaction_(addr_l);
|
|
||||||
delay_microseconds_safe(1);
|
|
||||||
this->disable();
|
|
||||||
delay_microseconds_safe(1);
|
|
||||||
const int32_t val = (val_h << 16) | val_l;
|
const int32_t val = (val_h << 16) | val_l;
|
||||||
|
|
||||||
ESP_LOGVV(TAG,
|
ESP_LOGVV(TAG,
|
||||||
|
@@ -140,7 +140,6 @@ class ATM90E32Component : public PollingComponent,
|
|||||||
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
|
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
|
||||||
#endif
|
#endif
|
||||||
uint16_t read16_(uint16_t a_register);
|
uint16_t read16_(uint16_t a_register);
|
||||||
uint16_t read16_transaction_(uint16_t a_register);
|
|
||||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||||
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
|
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
|
||||||
float get_local_phase_voltage_(uint8_t phase);
|
float get_local_phase_voltage_(uint8_t phase);
|
||||||
|
@@ -17,10 +17,12 @@ from esphome.const import (
|
|||||||
CONF_REACTIVE_POWER,
|
CONF_REACTIVE_POWER,
|
||||||
CONF_REVERSE_ACTIVE_ENERGY,
|
CONF_REVERSE_ACTIVE_ENERGY,
|
||||||
CONF_VOLTAGE,
|
CONF_VOLTAGE,
|
||||||
|
DEVICE_CLASS_APPARENT_POWER,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_POWER_FACTOR,
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
|
DEVICE_CLASS_REACTIVE_POWER,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
@@ -100,13 +102,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
|||||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||||
icon=ICON_LIGHTBULB,
|
icon=ICON_LIGHTBULB,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
device_class=DEVICE_CLASS_POWER,
|
device_class=DEVICE_CLASS_REACTIVE_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
|
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
device_class=DEVICE_CLASS_POWER,
|
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||||
|
@@ -2,7 +2,7 @@ from esphome import automation
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_MIC_GAIN
|
from esphome.const import CONF_ID, CONF_MIC_GAIN
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@kbx81"]
|
CODEOWNERS = ["@kbx81"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -35,7 +35,7 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define("USE_AUDIO_ADC")
|
cg.add_define("USE_AUDIO_ADC")
|
||||||
cg.add_global(audio_adc_ns.using)
|
cg.add_global(audio_adc_ns.using)
|
||||||
|
@@ -3,7 +3,7 @@ from esphome.automation import maybe_simple_id
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_VOLUME
|
from esphome.const import CONF_ID, CONF_VOLUME
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
CODEOWNERS = ["@kbx81"]
|
CODEOWNERS = ["@kbx81"]
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
|
|||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define("USE_AUDIO_DAC")
|
cg.add_define("USE_AUDIO_DAC")
|
||||||
cg.add_global(audio_dac_ns.using)
|
cg.add_global(audio_dac_ns.using)
|
||||||
|
@@ -12,7 +12,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
|
|||||||
|
|
||||||
#define ERROR_CHECK(err) \
|
#define ERROR_CHECK(err) \
|
||||||
if ((err) != i2c::ERROR_OK) { \
|
if ((err) != i2c::ERROR_OK) { \
|
||||||
this->status_set_warning("Failed to communicate"); \
|
this->status_set_warning(LOG_STR("Failed to communicate")); \
|
||||||
return; \
|
return; \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
|
|||||||
i2c::ErrorCode err;
|
i2c::ErrorCode err;
|
||||||
uint8_t data[8]{};
|
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);
|
ERROR_CHECK(err);
|
||||||
err = this->read(data, sizeof(data));
|
err = this->read(data, sizeof(data));
|
||||||
ERROR_CHECK(err);
|
ERROR_CHECK(err);
|
||||||
|
@@ -493,7 +493,7 @@ void BedJetHub::dump_config() {
|
|||||||
" ble_client.app_id: %d\n"
|
" ble_client.app_id: %d\n"
|
||||||
" ble_client.conn_id: %d",
|
" ble_client.conn_id: %d",
|
||||||
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
|
this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
|
||||||
LOG_UPDATE_INTERVAL(this)
|
LOG_UPDATE_INTERVAL(this);
|
||||||
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
|
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
|
||||||
for (auto *child : this->children_) {
|
for (auto *child : this->children_) {
|
||||||
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());
|
ESP_LOGCONFIG(TAG, " - %s", child->describe().c_str());
|
||||||
|
@@ -59,7 +59,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_VIBRATION,
|
DEVICE_CLASS_VIBRATION,
|
||||||
DEVICE_CLASS_WINDOW,
|
DEVICE_CLASS_WINDOW,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
@@ -652,7 +652,7 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
|
|||||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(binary_sensor_ns.using)
|
cg.add_global(binary_sensor_ns.using)
|
||||||
|
|
||||||
|
@@ -7,6 +7,19 @@ namespace binary_sensor {
|
|||||||
|
|
||||||
static const char *const TAG = "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_ref().empty()) {
|
||||||
|
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BinarySensor::publish_state(bool new_state) {
|
void BinarySensor::publish_state(bool new_state) {
|
||||||
if (this->filter_list_ == nullptr) {
|
if (this->filter_list_ == nullptr) {
|
||||||
this->send_state_internal(new_state);
|
this->send_state_internal(new_state);
|
||||||
|
@@ -10,13 +10,10 @@ namespace esphome {
|
|||||||
|
|
||||||
namespace binary_sensor {
|
namespace binary_sensor {
|
||||||
|
|
||||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
|
class BinarySensor;
|
||||||
if ((obj) != nullptr) { \
|
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
|
||||||
if (!(obj)->get_device_class().empty()) { \
|
#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SUB_BINARY_SENSOR(name) \
|
#define SUB_BINARY_SENSOR(name) \
|
||||||
protected: \
|
protected: \
|
||||||
|
@@ -1 +1,6 @@
|
|||||||
CODEOWNERS = ["@tobias-"]
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
CODEOWNERS = ["@tobias-", "@dan-s-github"]
|
||||||
|
|
||||||
|
CONF_BL0940_ID = "bl0940_id"
|
||||||
|
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
||||||
|
@@ -7,28 +7,26 @@ namespace bl0940 {
|
|||||||
|
|
||||||
static const char *const TAG = "bl0940";
|
static const char *const TAG = "bl0940";
|
||||||
|
|
||||||
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
|
|
||||||
static const uint8_t BL0940_FULL_PACKET = 0xAA;
|
static const uint8_t BL0940_FULL_PACKET = 0xAA;
|
||||||
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
|
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to en doc but 0x55 in cn doc
|
||||||
|
|
||||||
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
|
|
||||||
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
|
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
|
||||||
static const uint8_t BL0940_REG_MODE = 0x18;
|
static const uint8_t BL0940_REG_MODE = 0x18;
|
||||||
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
|
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
|
||||||
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
|
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
|
||||||
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
|
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
|
||||||
|
|
||||||
const uint8_t BL0940_INIT[5][6] = {
|
static const uint8_t BL0940_INIT[5][5] = {
|
||||||
// Reset to default
|
// Reset to default
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
{BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
||||||
// Enable User Operation Write
|
// Enable User Operation Write
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
{BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
||||||
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
|
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
{BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
||||||
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
|
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
{BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
||||||
// 0x181C = Half cycle, Fast RMS threshold 6172
|
// 0x181C = Half cycle, Fast RMS threshold 6172
|
||||||
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
{BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
||||||
|
|
||||||
void BL0940::loop() {
|
void BL0940::loop() {
|
||||||
DataPacket buffer;
|
DataPacket buffer;
|
||||||
@@ -36,8 +34,8 @@ void BL0940::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
|
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
|
||||||
if (validate_checksum(&buffer)) {
|
if (this->validate_checksum_(&buffer)) {
|
||||||
received_package_(&buffer);
|
this->received_package_(&buffer);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
|
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
|
||||||
@@ -46,35 +44,151 @@ void BL0940::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BL0940::validate_checksum(const DataPacket *data) {
|
bool BL0940::validate_checksum_(DataPacket *data) {
|
||||||
uint8_t checksum = BL0940_READ_COMMAND;
|
uint8_t checksum = this->read_command_;
|
||||||
// Whole package but checksum
|
// Whole package but checksum
|
||||||
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
|
uint8_t *raw = (uint8_t *) data;
|
||||||
checksum += data->raw[i];
|
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
|
||||||
|
checksum += raw[i];
|
||||||
}
|
}
|
||||||
checksum ^= 0xFF;
|
checksum ^= 0xFF;
|
||||||
if (checksum != data->checksum) {
|
if (checksum != data->checksum) {
|
||||||
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
||||||
}
|
}
|
||||||
return checksum == data->checksum;
|
return checksum == data->checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BL0940::update() {
|
void BL0940::update() {
|
||||||
this->flush();
|
this->flush();
|
||||||
this->write_byte(BL0940_READ_COMMAND);
|
this->write_byte(this->read_command_);
|
||||||
this->write_byte(BL0940_FULL_PACKET);
|
this->write_byte(BL0940_FULL_PACKET);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BL0940::setup() {
|
void BL0940::setup() {
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
// add calibration callbacks
|
||||||
|
if (this->voltage_calibration_number_ != nullptr) {
|
||||||
|
this->voltage_calibration_number_->add_on_state_callback(
|
||||||
|
[this](float state) { this->voltage_calibration_callback_(state); });
|
||||||
|
if (this->voltage_calibration_number_->has_state()) {
|
||||||
|
this->voltage_calibration_callback_(this->voltage_calibration_number_->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->current_calibration_number_ != nullptr) {
|
||||||
|
this->current_calibration_number_->add_on_state_callback(
|
||||||
|
[this](float state) { this->current_calibration_callback_(state); });
|
||||||
|
if (this->current_calibration_number_->has_state()) {
|
||||||
|
this->current_calibration_callback_(this->current_calibration_number_->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->power_calibration_number_ != nullptr) {
|
||||||
|
this->power_calibration_number_->add_on_state_callback(
|
||||||
|
[this](float state) { this->power_calibration_callback_(state); });
|
||||||
|
if (this->power_calibration_number_->has_state()) {
|
||||||
|
this->power_calibration_callback_(this->power_calibration_number_->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->energy_calibration_number_ != nullptr) {
|
||||||
|
this->energy_calibration_number_->add_on_state_callback(
|
||||||
|
[this](float state) { this->energy_calibration_callback_(state); });
|
||||||
|
if (this->energy_calibration_number_->has_state()) {
|
||||||
|
this->energy_calibration_callback_(this->energy_calibration_number_->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// calculate calibrated reference values
|
||||||
|
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
|
||||||
|
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
|
||||||
|
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
|
||||||
|
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
|
||||||
|
|
||||||
for (auto *i : BL0940_INIT) {
|
for (auto *i : BL0940_INIT) {
|
||||||
this->write_array(i, 6);
|
this->write_byte(this->write_command_), this->write_array(i, 5);
|
||||||
delay(1);
|
delay(1);
|
||||||
}
|
}
|
||||||
this->flush();
|
this->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
|
float BL0940::calculate_power_reference_() {
|
||||||
auto tb = (float) (temperature.h << 8 | temperature.l);
|
// calculate power reference based on voltage and current reference
|
||||||
|
return this->voltage_reference_cal_ * this->current_reference_cal_ * 4046 / 324004 / 79931;
|
||||||
|
}
|
||||||
|
|
||||||
|
float BL0940::calculate_energy_reference_() {
|
||||||
|
// formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
|
||||||
|
// or: power_reference_ * 3600000 / (1638.4 * 256)
|
||||||
|
return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
|
||||||
|
|
||||||
|
void BL0940::reset_calibration() {
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) {
|
||||||
|
this->current_calibration_number_->make_call().set_value(0).perform();
|
||||||
|
}
|
||||||
|
if (this->voltage_calibration_number_ != nullptr && this->voltage_cal_ != 1) {
|
||||||
|
this->voltage_calibration_number_->make_call().set_value(0).perform();
|
||||||
|
}
|
||||||
|
if (this->power_calibration_number_ != nullptr && this->power_cal_ != 1) {
|
||||||
|
this->power_calibration_number_->make_call().set_value(0).perform();
|
||||||
|
}
|
||||||
|
if (this->energy_calibration_number_ != nullptr && this->energy_cal_ != 1) {
|
||||||
|
this->energy_calibration_number_->make_call().set_value(0).perform();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ESP_LOGD(TAG, "external calibration values restored to initial state");
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0940::current_calibration_callback_(float state) {
|
||||||
|
this->current_cal_ = this->calculate_calibration_value_(state);
|
||||||
|
ESP_LOGV(TAG, "update current calibration state: %f", this->current_cal_);
|
||||||
|
this->recalibrate_();
|
||||||
|
}
|
||||||
|
void BL0940::voltage_calibration_callback_(float state) {
|
||||||
|
this->voltage_cal_ = this->calculate_calibration_value_(state);
|
||||||
|
ESP_LOGV(TAG, "update voltage calibration state: %f", this->voltage_cal_);
|
||||||
|
this->recalibrate_();
|
||||||
|
}
|
||||||
|
void BL0940::power_calibration_callback_(float state) {
|
||||||
|
this->power_cal_ = this->calculate_calibration_value_(state);
|
||||||
|
ESP_LOGV(TAG, "update power calibration state: %f", this->power_cal_);
|
||||||
|
this->recalibrate_();
|
||||||
|
}
|
||||||
|
void BL0940::energy_calibration_callback_(float state) {
|
||||||
|
this->energy_cal_ = this->calculate_calibration_value_(state);
|
||||||
|
ESP_LOGV(TAG, "update energy calibration state: %f", this->energy_cal_);
|
||||||
|
this->recalibrate_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0940::recalibrate_() {
|
||||||
|
ESP_LOGV(TAG, "Recalibrating reference values");
|
||||||
|
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
|
||||||
|
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
|
||||||
|
|
||||||
|
if (this->voltage_cal_ != 1 || this->current_cal_ != 1) {
|
||||||
|
this->power_reference_ = this->calculate_power_reference_();
|
||||||
|
}
|
||||||
|
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
|
||||||
|
|
||||||
|
if (this->voltage_cal_ != 1 || this->current_cal_ != 1 || this->power_cal_ != 1) {
|
||||||
|
this->energy_reference_ = this->calculate_energy_reference_();
|
||||||
|
}
|
||||||
|
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG,
|
||||||
|
"Recalibrated reference values:\n"
|
||||||
|
"Voltage: %f\n, Current: %f\n, Power: %f\n, Energy: %f\n",
|
||||||
|
this->voltage_reference_cal_, this->current_reference_cal_, this->power_reference_cal_,
|
||||||
|
this->energy_reference_cal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) const {
|
||||||
|
auto tb = (float) temperature;
|
||||||
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
|
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
|
||||||
if (sensor != nullptr) {
|
if (sensor != nullptr) {
|
||||||
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
|
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
|
||||||
@@ -87,33 +201,40 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
|
|||||||
return converted_temp;
|
return converted_temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BL0940::received_package_(const DataPacket *data) const {
|
void BL0940::received_package_(DataPacket *data) {
|
||||||
// Bad header
|
// Bad header
|
||||||
if (data->frame_header != BL0940_PACKET_HEADER) {
|
if (data->frame_header != BL0940_PACKET_HEADER) {
|
||||||
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
|
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
|
// cf_cnt is only 24 bits, so track overflows
|
||||||
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
|
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
|
||||||
float watt = (float) to_int32_t(data->watt) / power_reference_;
|
cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
|
||||||
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
|
if (cf_cnt < this->prev_cf_cnt_) {
|
||||||
float total_energy_consumption = (float) cf_cnt / energy_reference_;
|
cf_cnt += 0x1000000;
|
||||||
|
}
|
||||||
|
this->prev_cf_cnt_ = cf_cnt;
|
||||||
|
|
||||||
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
|
float v_rms = (uint24_t) data->v_rms / this->voltage_reference_cal_;
|
||||||
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
|
float i_rms = (uint24_t) data->i_rms / this->current_reference_cal_;
|
||||||
|
float watt = (int24_t) data->watt / this->power_reference_cal_;
|
||||||
|
float total_energy_consumption = cf_cnt / this->energy_reference_cal_;
|
||||||
|
|
||||||
if (voltage_sensor_ != nullptr) {
|
float tps1 = update_temp_(this->internal_temperature_sensor_, data->tps1);
|
||||||
voltage_sensor_->publish_state(v_rms);
|
float tps2 = update_temp_(this->external_temperature_sensor_, data->tps2);
|
||||||
|
|
||||||
|
if (this->voltage_sensor_ != nullptr) {
|
||||||
|
this->voltage_sensor_->publish_state(v_rms);
|
||||||
}
|
}
|
||||||
if (current_sensor_ != nullptr) {
|
if (this->current_sensor_ != nullptr) {
|
||||||
current_sensor_->publish_state(i_rms);
|
this->current_sensor_->publish_state(i_rms);
|
||||||
}
|
}
|
||||||
if (power_sensor_ != nullptr) {
|
if (this->power_sensor_ != nullptr) {
|
||||||
power_sensor_->publish_state(watt);
|
this->power_sensor_->publish_state(watt);
|
||||||
}
|
}
|
||||||
if (energy_sensor_ != nullptr) {
|
if (this->energy_sensor_ != nullptr) {
|
||||||
energy_sensor_->publish_state(total_energy_consumption);
|
this->energy_sensor_->publish_state(total_energy_consumption);
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
|
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
|
||||||
@@ -121,7 +242,27 @@ void BL0940::received_package_(const DataPacket *data) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
|
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
|
||||||
ESP_LOGCONFIG(TAG, "BL0940:");
|
ESP_LOGCONFIG(TAG,
|
||||||
|
"BL0940:\n"
|
||||||
|
" LEGACY MODE: %s\n"
|
||||||
|
" READ CMD: 0x%02X\n"
|
||||||
|
" WRITE CMD: 0x%02X\n"
|
||||||
|
" ------------------\n"
|
||||||
|
" Current reference: %f\n"
|
||||||
|
" Energy reference: %f\n"
|
||||||
|
" Power reference: %f\n"
|
||||||
|
" Voltage reference: %f\n",
|
||||||
|
TRUEFALSE(this->legacy_mode_enabled_), this->read_command_, this->write_command_,
|
||||||
|
this->current_reference_, this->energy_reference_, this->power_reference_, this->voltage_reference_);
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
|
"BL0940:\n"
|
||||||
|
" Current calibration: %f\n"
|
||||||
|
" Energy calibration: %f\n"
|
||||||
|
" Power calibration: %f\n"
|
||||||
|
" Voltage calibration: %f\n",
|
||||||
|
this->current_cal_, this->energy_cal_, this->power_cal_, this->voltage_cal_);
|
||||||
|
#endif
|
||||||
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
|
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
|
||||||
LOG_SENSOR("", "Current", this->current_sensor_);
|
LOG_SENSOR("", "Current", this->current_sensor_);
|
||||||
LOG_SENSOR("", "Power", this->power_sensor_);
|
LOG_SENSOR("", "Power", this->power_sensor_);
|
||||||
@@ -130,9 +271,5 @@ void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexit
|
|||||||
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
|
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
|
||||||
|
|
||||||
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
|
||||||
|
|
||||||
} // namespace bl0940
|
} // namespace bl0940
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@@ -1,66 +1,48 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/core/datatypes.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_BUTTON
|
||||||
|
#include "esphome/components/button/button.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
#include "esphome/components/number/number.h"
|
||||||
|
#endif
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bl0940 {
|
namespace bl0940 {
|
||||||
|
|
||||||
static const float BL0940_PREF = 1430;
|
|
||||||
static const float BL0940_UREF = 33000;
|
|
||||||
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
|
|
||||||
|
|
||||||
// Measured to 297J per click according to power consumption of 5 minutes
|
|
||||||
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
|
|
||||||
static const float BL0940_EREF = 3.6e6 / 297;
|
|
||||||
|
|
||||||
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
|
||||||
uint8_t l;
|
|
||||||
uint8_t m;
|
|
||||||
uint8_t h;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
|
||||||
uint8_t l;
|
|
||||||
uint8_t h;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
|
||||||
uint8_t l;
|
|
||||||
uint8_t m;
|
|
||||||
int8_t h;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
// Caveat: All these values are big endian (low - middle - high)
|
// Caveat: All these values are big endian (low - middle - high)
|
||||||
|
struct DataPacket {
|
||||||
union DataPacket { // NOLINT(altera-struct-pack-align)
|
uint8_t frame_header; // Packet header (0x58 in EN docs, 0x55 in CN docs and Tasmota tests)
|
||||||
uint8_t raw[35];
|
uint24_le_t i_fast_rms; // Fast RMS current
|
||||||
struct {
|
uint24_le_t i_rms; // RMS current
|
||||||
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
|
uint24_t RESERVED0; // Reserved
|
||||||
ube24_t i_fast_rms; // 0x00
|
uint24_le_t v_rms; // RMS voltage
|
||||||
ube24_t i_rms; // 0x04
|
uint24_t RESERVED1; // Reserved
|
||||||
ube24_t RESERVED0; // reserved
|
int24_le_t watt; // Active power (can be negative for bidirectional measurement)
|
||||||
ube24_t v_rms; // 0x06
|
uint24_t RESERVED2; // Reserved
|
||||||
ube24_t RESERVED1; // reserved
|
uint24_le_t cf_cnt; // Energy pulse count
|
||||||
sbe24_t watt; // 0x08
|
uint24_t RESERVED3; // Reserved
|
||||||
ube24_t RESERVED2; // reserved
|
uint16_le_t tps1; // Internal temperature sensor 1
|
||||||
ube24_t cf_cnt; // 0x0A
|
uint8_t RESERVED4; // Reserved (should be 0x00)
|
||||||
ube24_t RESERVED3; // reserved
|
uint16_le_t tps2; // Internal temperature sensor 2
|
||||||
ube16_t tps1; // 0x0c
|
uint8_t RESERVED5; // Reserved (should be 0x00)
|
||||||
uint8_t RESERVED4; // value of 0x00
|
uint8_t checksum; // Packet checksum
|
||||||
ube16_t tps2; // 0x0c
|
|
||||||
uint8_t RESERVED5; // value of 0x00
|
|
||||||
uint8_t checksum; // checksum
|
|
||||||
};
|
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
class BL0940 : public PollingComponent, public uart::UARTDevice {
|
class BL0940 : public PollingComponent, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
|
// Sensor setters
|
||||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||||
|
|
||||||
|
// Temperature sensor setters
|
||||||
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
|
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
|
||||||
internal_temperature_sensor_ = internal_temperature_sensor;
|
internal_temperature_sensor_ = internal_temperature_sensor;
|
||||||
}
|
}
|
||||||
@@ -68,42 +50,105 @@ class BL0940 : public PollingComponent, public uart::UARTDevice {
|
|||||||
external_temperature_sensor_ = external_temperature_sensor;
|
external_temperature_sensor_ = external_temperature_sensor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() override;
|
// Configuration setters
|
||||||
|
void set_legacy_mode(bool enable) { this->legacy_mode_enabled_ = enable; }
|
||||||
|
void set_read_command(uint8_t read_command) { this->read_command_ = read_command; }
|
||||||
|
void set_write_command(uint8_t write_command) { this->write_command_ = write_command; }
|
||||||
|
|
||||||
|
// Reference value setters (used for calibration and conversion)
|
||||||
|
void set_current_reference(float current_ref) { this->current_reference_ = current_ref; }
|
||||||
|
void set_energy_reference(float energy_ref) { this->energy_reference_ = energy_ref; }
|
||||||
|
void set_power_reference(float power_ref) { this->power_reference_ = power_ref; }
|
||||||
|
void set_voltage_reference(float voltage_ref) { this->voltage_reference_ = voltage_ref; }
|
||||||
|
|
||||||
|
#ifdef USE_NUMBER
|
||||||
|
// Calibration number setters (for Home Assistant number entities)
|
||||||
|
void set_current_calibration_number(number::Number *num) { this->current_calibration_number_ = num; }
|
||||||
|
void set_voltage_calibration_number(number::Number *num) { this->voltage_calibration_number_ = num; }
|
||||||
|
void set_power_calibration_number(number::Number *num) { this->power_calibration_number_ = num; }
|
||||||
|
void set_energy_calibration_number(number::Number *num) { this->energy_calibration_number_ = num; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_BUTTON
|
||||||
|
// Resets all calibration values to defaults (can be triggered by a button)
|
||||||
|
void reset_calibration();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Core component methods
|
||||||
|
void loop() override;
|
||||||
void update() override;
|
void update() override;
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
sensor::Sensor *voltage_sensor_{nullptr};
|
// --- Sensor pointers ---
|
||||||
sensor::Sensor *current_sensor_{nullptr};
|
sensor::Sensor *voltage_sensor_{nullptr}; // Voltage sensor
|
||||||
// NB This may be negative as the circuits is seemingly able to measure
|
sensor::Sensor *current_sensor_{nullptr}; // Current sensor
|
||||||
// power in both directions
|
sensor::Sensor *power_sensor_{nullptr}; // Power sensor (can be negative for bidirectional)
|
||||||
sensor::Sensor *power_sensor_{nullptr};
|
sensor::Sensor *energy_sensor_{nullptr}; // Energy sensor
|
||||||
sensor::Sensor *energy_sensor_{nullptr};
|
sensor::Sensor *internal_temperature_sensor_{nullptr}; // Internal temperature sensor
|
||||||
sensor::Sensor *internal_temperature_sensor_{nullptr};
|
sensor::Sensor *external_temperature_sensor_{nullptr}; // External temperature sensor
|
||||||
sensor::Sensor *external_temperature_sensor_{nullptr};
|
|
||||||
|
|
||||||
// Max difference between two measurements of the temperature. Used to avoid noise.
|
#ifdef USE_NUMBER
|
||||||
float max_temperature_diff_{0};
|
// --- Calibration number entities (for dynamic calibration via HA UI) ---
|
||||||
// Divide by this to turn into Watt
|
number::Number *voltage_calibration_number_{nullptr};
|
||||||
float power_reference_ = BL0940_PREF;
|
number::Number *current_calibration_number_{nullptr};
|
||||||
// Divide by this to turn into Volt
|
number::Number *power_calibration_number_{nullptr};
|
||||||
float voltage_reference_ = BL0940_UREF;
|
number::Number *energy_calibration_number_{nullptr};
|
||||||
// Divide by this to turn into Ampere
|
#endif
|
||||||
float current_reference_ = BL0940_IREF;
|
|
||||||
// Divide by this to turn into kWh
|
|
||||||
float energy_reference_ = BL0940_EREF;
|
|
||||||
|
|
||||||
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
|
// --- Internal state ---
|
||||||
|
uint32_t prev_cf_cnt_ = 0; // Previous energy pulse count (for energy calculation)
|
||||||
|
float max_temperature_diff_{0}; // Max allowed temperature difference between two measurements (noise filter)
|
||||||
|
|
||||||
static uint32_t to_uint32_t(ube24_t input);
|
// --- Reference values for conversion ---
|
||||||
|
float power_reference_; // Divider for raw power to get Watts
|
||||||
|
float power_reference_cal_; // Calibrated power reference
|
||||||
|
float voltage_reference_; // Divider for raw voltage to get Volts
|
||||||
|
float voltage_reference_cal_; // Calibrated voltage reference
|
||||||
|
float current_reference_; // Divider for raw current to get Amperes
|
||||||
|
float current_reference_cal_; // Calibrated current reference
|
||||||
|
float energy_reference_; // Divider for raw energy to get kWh
|
||||||
|
float energy_reference_cal_; // Calibrated energy reference
|
||||||
|
|
||||||
static int32_t to_int32_t(sbe24_t input);
|
// --- Home Assistant calibration values (multipliers, default 1) ---
|
||||||
|
float current_cal_{1};
|
||||||
|
float voltage_cal_{1};
|
||||||
|
float power_cal_{1};
|
||||||
|
float energy_cal_{1};
|
||||||
|
|
||||||
static bool validate_checksum(const DataPacket *data);
|
// --- Protocol commands ---
|
||||||
|
uint8_t read_command_;
|
||||||
|
uint8_t write_command_;
|
||||||
|
|
||||||
void received_package_(const DataPacket *data) const;
|
// --- Mode flags ---
|
||||||
|
bool legacy_mode_enabled_ = true;
|
||||||
|
|
||||||
|
// --- Methods ---
|
||||||
|
// Converts packed temperature value to float and updates the sensor
|
||||||
|
float update_temp_(sensor::Sensor *sensor, uint16_le_t packed_temperature) const;
|
||||||
|
|
||||||
|
// Validates the checksum of a received data packet
|
||||||
|
bool validate_checksum_(DataPacket *data);
|
||||||
|
|
||||||
|
// Handles a received data packet
|
||||||
|
void received_package_(DataPacket *data);
|
||||||
|
|
||||||
|
// Calculates reference values for calibration and conversion
|
||||||
|
float calculate_energy_reference_();
|
||||||
|
float calculate_power_reference_();
|
||||||
|
float calculate_calibration_value_(float state);
|
||||||
|
|
||||||
|
// Calibration update callbacks (used with number entities)
|
||||||
|
void current_calibration_callback_(float state);
|
||||||
|
void voltage_calibration_callback_(float state);
|
||||||
|
void power_calibration_callback_(float state);
|
||||||
|
void energy_calibration_callback_(float state);
|
||||||
|
void reset_calibration_callback_();
|
||||||
|
|
||||||
|
// Recalculates all reference values after calibration changes
|
||||||
|
void recalibrate_();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bl0940
|
} // namespace bl0940
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
27
esphome/components/bl0940/button/__init__.py
Normal file
27
esphome/components/bl0940/button/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import button
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
|
||||||
|
|
||||||
|
from .. import CONF_BL0940_ID, bl0940_ns
|
||||||
|
from ..sensor import BL0940
|
||||||
|
|
||||||
|
CalibrationResetButton = bl0940_ns.class_(
|
||||||
|
"CalibrationResetButton", button.Button, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
button.button_schema(
|
||||||
|
CalibrationResetButton,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
icon=ICON_RESTART,
|
||||||
|
)
|
||||||
|
.extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await button.new_button(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await cg.register_parented(var, config[CONF_BL0940_ID])
|
@@ -0,0 +1,20 @@
|
|||||||
|
#include "calibration_reset_button.h"
|
||||||
|
#include "../bl0940.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
static const char *const TAG = "bl0940.button.calibration_reset";
|
||||||
|
|
||||||
|
void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); }
|
||||||
|
|
||||||
|
void CalibrationResetButton::press_action() {
|
||||||
|
ESP_LOGI(TAG, "Resetting calibration defaults...");
|
||||||
|
this->parent_->reset_calibration();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
19
esphome/components/bl0940/button/calibration_reset_button.h
Normal file
19
esphome/components/bl0940/button/calibration_reset_button.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/button/button.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
class BL0940; // Forward declaration of BL0940 class
|
||||||
|
|
||||||
|
class CalibrationResetButton : public button::Button, public Component, public Parented<BL0940> {
|
||||||
|
public:
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void press_action() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
94
esphome/components/bl0940/number/__init__.py
Normal file
94
esphome/components/bl0940/number/__init__.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import number
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_MAX_VALUE,
|
||||||
|
CONF_MIN_VALUE,
|
||||||
|
CONF_MODE,
|
||||||
|
CONF_RESTORE_VALUE,
|
||||||
|
CONF_STEP,
|
||||||
|
ENTITY_CATEGORY_CONFIG,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .. import CONF_BL0940_ID, bl0940_ns
|
||||||
|
from ..sensor import BL0940
|
||||||
|
|
||||||
|
# Define calibration types
|
||||||
|
CONF_CURRENT_CALIBRATION = "current_calibration"
|
||||||
|
CONF_VOLTAGE_CALIBRATION = "voltage_calibration"
|
||||||
|
CONF_POWER_CALIBRATION = "power_calibration"
|
||||||
|
CONF_ENERGY_CALIBRATION = "energy_calibration"
|
||||||
|
|
||||||
|
BL0940Number = bl0940_ns.class_("BL0940Number")
|
||||||
|
|
||||||
|
CalibrationNumber = bl0940_ns.class_(
|
||||||
|
"CalibrationNumber", number.Number, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_min_max(config):
|
||||||
|
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
|
||||||
|
raise cv.Invalid("max_value must be greater than min_value")
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
CALIBRATION_SCHEMA = cv.All(
|
||||||
|
number.number_schema(
|
||||||
|
CalibrationNumber,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES),
|
||||||
|
cv.Optional(CONF_MAX_VALUE, default=10): cv.All(
|
||||||
|
cv.float_, cv.Range(max=50)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_MIN_VALUE, default=-10): cv.All(
|
||||||
|
cv.float_, cv.Range(min=-50)
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
|
||||||
|
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
|
validate_min_max,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configuration schema for BL0940 numbers
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BL0940Number),
|
||||||
|
cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940),
|
||||||
|
cv.Optional(CONF_CURRENT_CALIBRATION): CALIBRATION_SCHEMA,
|
||||||
|
cv.Optional(CONF_VOLTAGE_CALIBRATION): CALIBRATION_SCHEMA,
|
||||||
|
cv.Optional(CONF_POWER_CALIBRATION): CALIBRATION_SCHEMA,
|
||||||
|
cv.Optional(CONF_ENERGY_CALIBRATION): CALIBRATION_SCHEMA,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
# Get the BL0940 component instance
|
||||||
|
bl0940 = await cg.get_variable(config[CONF_BL0940_ID])
|
||||||
|
|
||||||
|
# Process all calibration types
|
||||||
|
for cal_type, setter_method in [
|
||||||
|
(CONF_CURRENT_CALIBRATION, "set_current_calibration_number"),
|
||||||
|
(CONF_VOLTAGE_CALIBRATION, "set_voltage_calibration_number"),
|
||||||
|
(CONF_POWER_CALIBRATION, "set_power_calibration_number"),
|
||||||
|
(CONF_ENERGY_CALIBRATION, "set_energy_calibration_number"),
|
||||||
|
]:
|
||||||
|
if conf := config.get(cal_type):
|
||||||
|
var = await number.new_number(
|
||||||
|
conf,
|
||||||
|
min_value=conf.get(CONF_MIN_VALUE),
|
||||||
|
max_value=conf.get(CONF_MAX_VALUE),
|
||||||
|
step=conf.get(CONF_STEP),
|
||||||
|
)
|
||||||
|
await cg.register_component(var, conf)
|
||||||
|
|
||||||
|
if restore_value := config.get(CONF_RESTORE_VALUE):
|
||||||
|
cg.add(var.set_restore_value(restore_value))
|
||||||
|
cg.add(getattr(bl0940, setter_method)(var))
|
29
esphome/components/bl0940/number/calibration_number.cpp
Normal file
29
esphome/components/bl0940/number/calibration_number.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include "calibration_number.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
static const char *const TAG = "bl0940.number";
|
||||||
|
|
||||||
|
void CalibrationNumber::setup() {
|
||||||
|
float value = 0.0f;
|
||||||
|
if (this->restore_value_) {
|
||||||
|
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
|
||||||
|
if (!this->pref_.load(&value)) {
|
||||||
|
value = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->publish_state(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalibrationNumber::control(float value) {
|
||||||
|
this->publish_state(value);
|
||||||
|
if (this->restore_value_)
|
||||||
|
this->pref_.save(&value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalibrationNumber::dump_config() { LOG_NUMBER("", "Calibration Number", this); }
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
26
esphome/components/bl0940/number/calibration_number.h
Normal file
26
esphome/components/bl0940/number/calibration_number.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/number/number.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0940 {
|
||||||
|
|
||||||
|
class CalibrationNumber : public number::Number, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void control(float value) override;
|
||||||
|
bool restore_value_{true};
|
||||||
|
|
||||||
|
ESPPreferenceObject pref_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bl0940
|
||||||
|
} // namespace esphome
|
@@ -8,6 +8,7 @@ from esphome.const import (
|
|||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INTERNAL_TEMPERATURE,
|
CONF_INTERNAL_TEMPERATURE,
|
||||||
CONF_POWER,
|
CONF_POWER,
|
||||||
|
CONF_REFERENCE_VOLTAGE,
|
||||||
CONF_VOLTAGE,
|
CONF_VOLTAGE,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
@@ -23,12 +24,133 @@ from esphome.const import (
|
|||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from . import bl0940_ns
|
||||||
|
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
|
|
||||||
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
|
||||||
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
|
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
|
||||||
|
|
||||||
|
CONF_LEGACY_MODE = "legacy_mode"
|
||||||
|
|
||||||
|
CONF_READ_COMMAND = "read_command"
|
||||||
|
CONF_WRITE_COMMAND = "write_command"
|
||||||
|
|
||||||
|
CONF_RESISTOR_SHUNT = "resistor_shunt"
|
||||||
|
CONF_RESISTOR_ONE = "resistor_one"
|
||||||
|
CONF_RESISTOR_TWO = "resistor_two"
|
||||||
|
|
||||||
|
CONF_CURRENT_REFERENCE = "current_reference"
|
||||||
|
CONF_ENERGY_REFERENCE = "energy_reference"
|
||||||
|
CONF_POWER_REFERENCE = "power_reference"
|
||||||
|
CONF_VOLTAGE_REFERENCE = "voltage_reference"
|
||||||
|
|
||||||
|
DEFAULT_BL0940_READ_COMMAND = 0x58
|
||||||
|
DEFAULT_BL0940_WRITE_COMMAND = 0xA1
|
||||||
|
|
||||||
|
# Values according to BL0940 application note:
|
||||||
|
# https://www.belling.com.cn/media/file_object/bel_product/BL0940/guide/BL0940_APPNote_TSSOP14_V1.04_EN.pdf
|
||||||
|
DEFAULT_BL0940_VREF = 1.218 # Vref = 1.218
|
||||||
|
DEFAULT_BL0940_RL = 1 # RL = 1 mΩ
|
||||||
|
DEFAULT_BL0940_R1 = 0.51 # R1 = 0.51 kΩ
|
||||||
|
DEFAULT_BL0940_R2 = 1950 # R2 = 5 x 390 kΩ -> 1950 kΩ
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# values from initial implementation
|
||||||
|
DEFAULT_BL0940_LEGACY_READ_COMMAND = 0x50
|
||||||
|
DEFAULT_BL0940_LEGACY_WRITE_COMMAND = 0xA0
|
||||||
|
|
||||||
|
DEFAULT_BL0940_LEGACY_UREF = 33000
|
||||||
|
DEFAULT_BL0940_LEGACY_IREF = 275000
|
||||||
|
DEFAULT_BL0940_LEGACY_PREF = 1430
|
||||||
|
# Measured to 297J per click according to power consumption of 5 minutes
|
||||||
|
# Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
|
||||||
|
DEFAULT_BL0940_LEGACY_EREF = 3.6e6 / 297
|
||||||
|
# ----------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# methods to calculate voltage and current reference values
|
||||||
|
def calculate_voltage_reference(vref, r_one, r_two):
|
||||||
|
# formula: 79931 / Vref * (R1 * 1000) / (R1 + R2)
|
||||||
|
return 79931 / vref * (r_one * 1000) / (r_one + r_two)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_current_reference(vref, r_shunt):
|
||||||
|
# formula: 324004 * RL / Vref
|
||||||
|
return 324004 * r_shunt / vref
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_power_reference(voltage_reference, current_reference):
|
||||||
|
# calculate power reference based on voltage and current reference
|
||||||
|
return voltage_reference * current_reference * 4046 / 324004 / 79931
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_energy_reference(power_reference):
|
||||||
|
# formula: power_reference * 3600000 / (1638.4 * 256)
|
||||||
|
return power_reference * 3600000 / (1638.4 * 256)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_legacy_mode(config):
|
||||||
|
# Only allow schematic calibration options if legacy_mode is False
|
||||||
|
if config.get(CONF_LEGACY_MODE, True):
|
||||||
|
forbidden = [
|
||||||
|
CONF_REFERENCE_VOLTAGE,
|
||||||
|
CONF_RESISTOR_SHUNT,
|
||||||
|
CONF_RESISTOR_ONE,
|
||||||
|
CONF_RESISTOR_TWO,
|
||||||
|
]
|
||||||
|
for key in forbidden:
|
||||||
|
if key in config:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Option '{key}' is only allowed when legacy_mode: false"
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def set_command_defaults(config):
|
||||||
|
# Set defaults for read_command and write_command based on legacy_mode
|
||||||
|
legacy = config.get(CONF_LEGACY_MODE, True)
|
||||||
|
if legacy:
|
||||||
|
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_LEGACY_READ_COMMAND)
|
||||||
|
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_LEGACY_WRITE_COMMAND)
|
||||||
|
else:
|
||||||
|
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_READ_COMMAND)
|
||||||
|
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_WRITE_COMMAND)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def set_reference_values(config):
|
||||||
|
# Set default reference values based on legacy_mode
|
||||||
|
if config.get(CONF_LEGACY_MODE, True):
|
||||||
|
config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
|
||||||
|
config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
|
||||||
|
config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
|
||||||
|
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
|
||||||
|
else:
|
||||||
|
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
|
||||||
|
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
|
||||||
|
r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2)
|
||||||
|
r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL)
|
||||||
|
|
||||||
|
config.setdefault(
|
||||||
|
CONF_VOLTAGE_REFERENCE, calculate_voltage_reference(vref, r_one, r_two)
|
||||||
|
)
|
||||||
|
config.setdefault(
|
||||||
|
CONF_CURRENT_REFERENCE, calculate_current_reference(vref, r_shunt)
|
||||||
|
)
|
||||||
|
config.setdefault(
|
||||||
|
CONF_POWER_REFERENCE,
|
||||||
|
calculate_power_reference(
|
||||||
|
config.get(CONF_VOLTAGE_REFERENCE), config.get(CONF_CURRENT_REFERENCE)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
config.setdefault(
|
||||||
|
CONF_ENERGY_REFERENCE,
|
||||||
|
calculate_energy_reference(config.get(CONF_POWER_REFERENCE)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -69,10 +191,24 @@ CONFIG_SCHEMA = (
|
|||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_LEGACY_MODE, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_READ_COMMAND): cv.hex_uint8_t,
|
||||||
|
cv.Optional(CONF_WRITE_COMMAND): cv.hex_uint8_t,
|
||||||
|
cv.Optional(CONF_REFERENCE_VOLTAGE): cv.float_,
|
||||||
|
cv.Optional(CONF_RESISTOR_SHUNT): cv.float_,
|
||||||
|
cv.Optional(CONF_RESISTOR_ONE): cv.float_,
|
||||||
|
cv.Optional(CONF_RESISTOR_TWO): cv.float_,
|
||||||
|
cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
|
||||||
|
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
|
||||||
|
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
|
||||||
|
cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.polling_component_schema("60s"))
|
.extend(cv.polling_component_schema("60s"))
|
||||||
.extend(uart.UART_DEVICE_SCHEMA)
|
.extend(uart.UART_DEVICE_SCHEMA)
|
||||||
|
.add_extra(validate_legacy_mode)
|
||||||
|
.add_extra(set_command_defaults)
|
||||||
|
.add_extra(set_reference_values)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -99,3 +235,16 @@ async def to_code(config):
|
|||||||
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
|
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
|
||||||
sens = await sensor.new_sensor(external_temperature_config)
|
sens = await sensor.new_sensor(external_temperature_config)
|
||||||
cg.add(var.set_external_temperature_sensor(sens))
|
cg.add(var.set_external_temperature_sensor(sens))
|
||||||
|
|
||||||
|
# enable legacy mode
|
||||||
|
cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE)))
|
||||||
|
|
||||||
|
# Set bl0940 commands after validator has determined which defaults to use if not set
|
||||||
|
cg.add(var.set_read_command(config.get(CONF_READ_COMMAND)))
|
||||||
|
cg.add(var.set_write_command(config.get(CONF_WRITE_COMMAND)))
|
||||||
|
|
||||||
|
# Set reference values after validator has set the values either from defaults or calculated
|
||||||
|
cg.add(var.set_current_reference(config.get(CONF_CURRENT_REFERENCE)))
|
||||||
|
cg.add(var.set_voltage_reference(config.get(CONF_VOLTAGE_REFERENCE)))
|
||||||
|
cg.add(var.set_power_reference(config.get(CONF_POWER_REFERENCE)))
|
||||||
|
cg.add(var.set_energy_reference(config.get(CONF_ENERGY_REFERENCE)))
|
||||||
|
@@ -149,7 +149,7 @@ void BL0942::setup() {
|
|||||||
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
|
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
|
||||||
|
|
||||||
if (this->read_reg_(BL0942_REG_MODE) != mode)
|
if (this->read_reg_(BL0942_REG_MODE) != mode)
|
||||||
this->status_set_warning("BL0942 setup failed!");
|
this->status_set_warning(LOG_STR("BL0942 setup failed!"));
|
||||||
|
|
||||||
this->flush();
|
this->flush();
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
cg.add(
|
cg.add(
|
||||||
@@ -63,6 +63,6 @@ def to_code(config):
|
|||||||
)
|
)
|
||||||
cg.add(var.set_char_uuid128(uuid128))
|
cg.add(var.set_char_uuid128(uuid128))
|
||||||
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
||||||
yield output.register_output(var, config)
|
await output.register_output(var, config)
|
||||||
yield ble_client.register_ble_node(var, config)
|
await ble_client.register_ble_node(var, config)
|
||||||
yield cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
@@ -80,7 +80,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
||||||
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||||
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
|
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
|
||||||
cv.only_with_esp_idf, cv.boolean
|
cv.only_with_esp_idf, cv.boolean
|
||||||
),
|
),
|
||||||
|
@@ -375,10 +375,19 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_CLOSE_EVT: {
|
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);
|
this->reset_connection_(param->close.reason);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
|
|||||||
|
|
||||||
class BluetoothProxy;
|
class BluetoothProxy;
|
||||||
|
|
||||||
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
|
||||||
public:
|
public:
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
@@ -24,6 +24,9 @@ void BluetoothProxy::setup() {
|
|||||||
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
||||||
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
|
||||||
|
|
||||||
|
// Capture the configured scan mode from YAML before any API changes
|
||||||
|
this->configured_scan_active_ = this->parent_->get_scan_active();
|
||||||
|
|
||||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||||
if (this->api_connection_ != nullptr) {
|
if (this->api_connection_ != nullptr) {
|
||||||
this->send_bluetooth_scanner_state_(state);
|
this->send_bluetooth_scanner_state_(state);
|
||||||
@@ -36,6 +39,9 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
|
|||||||
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
|
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
|
||||||
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
||||||
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
|
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
|
||||||
|
resp.configured_mode = this->configured_scan_active_
|
||||||
|
? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
||||||
|
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
|
||||||
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
|
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +189,12 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
|||||||
this->send_device_connection(msg.address, false);
|
this->send_device_connection(msg.address, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!msg.has_address_type) {
|
||||||
|
ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
|
||||||
|
connection->address_str().c_str());
|
||||||
|
this->send_device_connection(msg.address, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (connection->state() == espbt::ClientState::CONNECTED ||
|
if (connection->state() == espbt::ClientState::CONNECTED ||
|
||||||
connection->state() == espbt::ClientState::ESTABLISHED) {
|
connection->state() == espbt::ClientState::ESTABLISHED) {
|
||||||
this->log_connection_request_ignored_(connection, connection->state());
|
this->log_connection_request_ignored_(connection, connection->state());
|
||||||
@@ -209,13 +221,9 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
|||||||
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
|
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
|
||||||
this->log_connection_info_(connection, "v3 without cache");
|
this->log_connection_info_(connection, "v3 without cache");
|
||||||
}
|
}
|
||||||
if (msg.has_address_type) {
|
|
||||||
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
||||||
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
||||||
connection->set_state(espbt::ClientState::DISCOVERED);
|
connection->set_state(espbt::ClientState::DISCOVERED);
|
||||||
} else {
|
|
||||||
connection->set_state(espbt::ClientState::SEARCHING);
|
|
||||||
}
|
|
||||||
this->send_connections_free();
|
this->send_connections_free();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
|||||||
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
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_
|
friend class BluetoothConnection; // Allow connection to update connections_free_response_
|
||||||
public:
|
public:
|
||||||
BluetoothProxy();
|
BluetoothProxy();
|
||||||
@@ -161,7 +161,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
// Group 4: 1-byte types grouped together
|
// Group 4: 1-byte types grouped together
|
||||||
bool active_;
|
bool active_;
|
||||||
uint8_t connection_count_{0};
|
uint8_t connection_count_{0};
|
||||||
// 2 bytes used, 2 bytes padding
|
bool configured_scan_active_{false}; // Configured scan mode from YAML
|
||||||
|
// 3 bytes used, 1 byte padding
|
||||||
};
|
};
|
||||||
|
|
||||||
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
@@ -203,7 +203,7 @@ void BMI160Component::dump_config() {
|
|||||||
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
|
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
|
||||||
uint8_t raw_data[len * 2];
|
uint8_t raw_data[len * 2];
|
||||||
// read using read_register because we have little-endian data, and read_bytes_16 will swap it
|
// 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) {
|
if (err != i2c::ERROR_OK) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@@ -63,12 +63,12 @@ void BMP280Component::setup() {
|
|||||||
|
|
||||||
// Read the chip id twice, to work around a bug where the first read is 0.
|
// 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
|
// 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->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this->read_byte(0xD0, &chip_id)) {
|
if (!this->bmp_read_byte(0xD0, &chip_id)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
|
||||||
return;
|
return;
|
||||||
@@ -80,7 +80,7 @@ void BMP280Component::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send a soft reset.
|
// Send a soft reset.
|
||||||
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
||||||
this->mark_failed("Reset failed");
|
this->mark_failed("Reset failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ void BMP280Component::setup() {
|
|||||||
uint8_t retry = 5;
|
uint8_t retry = 5;
|
||||||
do {
|
do {
|
||||||
delay(2);
|
delay(2);
|
||||||
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
|
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
|
||||||
this->mark_failed("Error reading status register");
|
this->mark_failed("Error reading status register");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -115,14 +115,14 @@ void BMP280Component::setup() {
|
|||||||
this->calibration_.p9 = this->read_s16_le_(0x9E);
|
this->calibration_.p9 = this->read_s16_le_(0x9E);
|
||||||
|
|
||||||
uint8_t config_register = 0;
|
uint8_t config_register = 0;
|
||||||
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
|
||||||
this->mark_failed("Read config");
|
this->mark_failed("Read config");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
config_register &= ~0b11111100;
|
config_register &= ~0b11111100;
|
||||||
config_register |= 0b000 << 5; // 0.5 ms standby time
|
config_register |= 0b000 << 5; // 0.5 ms standby time
|
||||||
config_register |= (this->iir_filter_ & 0b111) << 2;
|
config_register |= (this->iir_filter_ & 0b111) << 2;
|
||||||
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
|
||||||
this->mark_failed("Write config");
|
this->mark_failed("Write config");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ void BMP280Component::update() {
|
|||||||
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
|
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||||
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
|
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||||
meas_value |= 0b01; // Forced mode
|
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();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -188,9 +188,10 @@ void BMP280Component::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float BMP280Component::read_temperature_(int32_t *t_fine) {
|
float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||||
uint8_t data[3];
|
uint8_t data[3]{};
|
||||||
if (!this->read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
|
if (!this->bmp_read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
|
||||||
return NAN;
|
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);
|
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||||
adc >>= 4;
|
adc >>= 4;
|
||||||
if (adc == 0x80000) {
|
if (adc == 0x80000) {
|
||||||
@@ -212,7 +213,7 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
|
|||||||
|
|
||||||
float BMP280Component::read_pressure_(int32_t t_fine) {
|
float BMP280Component::read_pressure_(int32_t t_fine) {
|
||||||
uint8_t data[3];
|
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;
|
return NAN;
|
||||||
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
|
||||||
adc >>= 4;
|
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; }
|
void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
|
||||||
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
|
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
|
||||||
uint8_t data = 0;
|
uint8_t data = 0;
|
||||||
this->read_byte(a_register, &data);
|
this->bmp_read_byte(a_register, &data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
|
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
|
||||||
uint16_t data = 0;
|
uint16_t data = 0;
|
||||||
this->read_byte_16(a_register, &data);
|
this->bmp_read_byte_16(a_register, &data);
|
||||||
return (data >> 8) | (data << 8);
|
return (data >> 8) | (data << 8);
|
||||||
}
|
}
|
||||||
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
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;
|
float get_setup_priority() const override;
|
||||||
void update() 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:
|
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.
|
/// Read the temperature value and store the calculated ambient temperature in t_fine.
|
||||||
float read_temperature_(int32_t *t_fine);
|
float read_temperature_(int32_t *t_fine);
|
||||||
/// Read the pressure value in hPa using the provided t_fine value.
|
/// Read the pressure value in hPa using the provided t_fine value.
|
||||||
|
@@ -5,19 +5,6 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bmp280_i2c {
|
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() {
|
void BMP280I2CComponent::dump_config() {
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
BMP280Component::dump_config();
|
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.
|
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
|
||||||
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
|
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
|
||||||
bool write_byte(uint8_t a_register, uint8_t data) override;
|
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
|
||||||
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
bool bmp_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;
|
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;
|
void dump_config() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@ void BMP280SPIComponent::setup() {
|
|||||||
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
|
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
|
||||||
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
|
// 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->enable();
|
||||||
this->transfer_byte(set_bit(a_register, 7));
|
this->transfer_byte(set_bit(a_register, 7));
|
||||||
*data = this->transfer_byte(0);
|
*data = this->transfer_byte(0);
|
||||||
@@ -36,7 +36,7 @@ bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
|
|||||||
return true;
|
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->enable();
|
||||||
this->transfer_byte(clear_bit(a_register, 7));
|
this->transfer_byte(clear_bit(a_register, 7));
|
||||||
this->transfer_byte(data);
|
this->transfer_byte(data);
|
||||||
@@ -44,7 +44,7 @@ bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
|
|||||||
return true;
|
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->enable();
|
||||||
this->transfer_byte(set_bit(a_register, 7));
|
this->transfer_byte(set_bit(a_register, 7));
|
||||||
this->read_array(data, len);
|
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;
|
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->enable();
|
||||||
this->transfer_byte(set_bit(a_register, 7));
|
this->transfer_byte(set_bit(a_register, 7));
|
||||||
((uint8_t *) data)[1] = this->transfer_byte(0);
|
((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,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
||||||
void setup() override;
|
void setup() override;
|
||||||
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
|
||||||
bool write_byte(uint8_t a_register, uint8_t data) override;
|
bool bmp_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 bmp_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_16(uint8_t a_register, uint16_t *data) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bmp280_spi
|
} // namespace bmp280_spi
|
||||||
|
@@ -17,7 +17,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_RESTART,
|
DEVICE_CLASS_RESTART,
|
||||||
DEVICE_CLASS_UPDATE,
|
DEVICE_CLASS_UPDATE,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
|
|
||||||
@@ -134,6 +134,6 @@ async def button_press_to_code(config, action_id, template_arg, args):
|
|||||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(100.0)
|
@coroutine_with_priority(CoroPriority.CORE)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(button_ns.using)
|
cg.add_global(button_ns.using)
|
||||||
|
@@ -6,6 +6,19 @@ namespace button {
|
|||||||
|
|
||||||
static const char *const TAG = "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_ref().empty()) {
|
||||||
|
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Button::press() {
|
void Button::press() {
|
||||||
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
|
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
|
||||||
this->press_action();
|
this->press_action();
|
||||||
|
@@ -7,13 +7,10 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace button {
|
namespace button {
|
||||||
|
|
||||||
#define LOG_BUTTON(prefix, type, obj) \
|
class Button;
|
||||||
if ((obj) != nullptr) { \
|
void log_button(const char *tag, const char *prefix, const char *type, Button *obj);
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
|
||||||
if (!(obj)->get_icon().empty()) { \
|
#define LOG_BUTTON(prefix, type, obj) log_button(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SUB_BUTTON(name) \
|
#define SUB_BUTTON(name) \
|
||||||
protected: \
|
protected: \
|
||||||
|
18
esphome/components/camera/buffer.h
Normal file
18
esphome/components/camera/buffer.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace esphome::camera {
|
||||||
|
|
||||||
|
/// Interface for a generic buffer that stores image data.
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
/// Returns a pointer to the buffer's data.
|
||||||
|
virtual uint8_t *get_data_buffer() = 0;
|
||||||
|
/// Returns the length of the buffer in bytes.
|
||||||
|
virtual size_t get_data_length() = 0;
|
||||||
|
virtual ~Buffer() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::camera
|
20
esphome/components/camera/buffer_impl.cpp
Normal file
20
esphome/components/camera/buffer_impl.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include "buffer_impl.h"
|
||||||
|
|
||||||
|
namespace esphome::camera {
|
||||||
|
|
||||||
|
BufferImpl::BufferImpl(size_t size) {
|
||||||
|
this->data_ = this->allocator_.allocate(size);
|
||||||
|
this->size_ = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferImpl::BufferImpl(CameraImageSpec *spec) {
|
||||||
|
this->data_ = this->allocator_.allocate(spec->bytes_per_image());
|
||||||
|
this->size_ = spec->bytes_per_image();
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferImpl::~BufferImpl() {
|
||||||
|
if (this->data_ != nullptr)
|
||||||
|
this->allocator_.deallocate(this->data_, this->size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::camera
|
26
esphome/components/camera/buffer_impl.h
Normal file
26
esphome/components/camera/buffer_impl.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "buffer.h"
|
||||||
|
#include "camera.h"
|
||||||
|
|
||||||
|
namespace esphome::camera {
|
||||||
|
|
||||||
|
/// Default implementation of Buffer Interface.
|
||||||
|
/// Uses a RAMAllocator for memory reservation.
|
||||||
|
class BufferImpl : public Buffer {
|
||||||
|
public:
|
||||||
|
explicit BufferImpl(size_t size);
|
||||||
|
explicit BufferImpl(CameraImageSpec *spec);
|
||||||
|
// -------- Buffer --------
|
||||||
|
uint8_t *get_data_buffer() override { return data_; }
|
||||||
|
size_t get_data_length() override { return size_; }
|
||||||
|
// ------------------------
|
||||||
|
~BufferImpl() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
RAMAllocator<uint8_t> allocator_;
|
||||||
|
size_t size_{};
|
||||||
|
uint8_t *data_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::camera
|
@@ -15,6 +15,26 @@ namespace camera {
|
|||||||
*/
|
*/
|
||||||
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
|
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
|
||||||
|
|
||||||
|
/// Enumeration of different pixel formats.
|
||||||
|
enum PixelFormat : uint8_t {
|
||||||
|
PIXEL_FORMAT_GRAYSCALE = 0, ///< 8-bit grayscale.
|
||||||
|
PIXEL_FORMAT_RGB565, ///< 16-bit RGB (5-6-5).
|
||||||
|
PIXEL_FORMAT_BGR888, ///< RGB pixel data in 8-bit format, stored as B, G, R (1 byte each).
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Returns string name for a given PixelFormat.
|
||||||
|
inline const char *to_string(PixelFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case PIXEL_FORMAT_GRAYSCALE:
|
||||||
|
return "PIXEL_FORMAT_GRAYSCALE";
|
||||||
|
case PIXEL_FORMAT_RGB565:
|
||||||
|
return "PIXEL_FORMAT_RGB565";
|
||||||
|
case PIXEL_FORMAT_BGR888:
|
||||||
|
return "PIXEL_FORMAT_BGR888";
|
||||||
|
}
|
||||||
|
return "PIXEL_FORMAT_UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
/** Abstract camera image base class.
|
/** Abstract camera image base class.
|
||||||
* Encapsulates the JPEG encoded data and it is shared among
|
* Encapsulates the JPEG encoded data and it is shared among
|
||||||
* all connected clients.
|
* all connected clients.
|
||||||
@@ -43,6 +63,29 @@ class CameraImageReader {
|
|||||||
virtual ~CameraImageReader() {}
|
virtual ~CameraImageReader() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Specification of a caputured camera image.
|
||||||
|
/// This struct defines the format and size details for images captured
|
||||||
|
/// or processed by a camera component.
|
||||||
|
struct CameraImageSpec {
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
PixelFormat format;
|
||||||
|
size_t bytes_per_pixel() {
|
||||||
|
switch (format) {
|
||||||
|
case PIXEL_FORMAT_GRAYSCALE:
|
||||||
|
return 1;
|
||||||
|
case PIXEL_FORMAT_RGB565:
|
||||||
|
return 2;
|
||||||
|
case PIXEL_FORMAT_BGR888:
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
size_t bytes_per_row() { return bytes_per_pixel() * width; }
|
||||||
|
size_t bytes_per_image() { return bytes_per_pixel() * width * height; }
|
||||||
|
};
|
||||||
|
|
||||||
/** Abstract camera base class. Collaborates with API.
|
/** Abstract camera base class. Collaborates with API.
|
||||||
* 1) API server starts and installs callback (add_image_callback)
|
* 1) API server starts and installs callback (add_image_callback)
|
||||||
* which is called by the camera when a new image is available.
|
* which is called by the camera when a new image is available.
|
||||||
|
69
esphome/components/camera/encoder.h
Normal file
69
esphome/components/camera/encoder.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "buffer.h"
|
||||||
|
#include "camera.h"
|
||||||
|
|
||||||
|
namespace esphome::camera {
|
||||||
|
|
||||||
|
/// Result codes from the encoder used to control camera pipeline flow.
|
||||||
|
enum EncoderError : uint8_t {
|
||||||
|
ENCODER_ERROR_SUCCESS = 0, ///< Encoding succeeded, continue pipeline normally.
|
||||||
|
ENCODER_ERROR_SKIP_FRAME, ///< Skip current frame, try again on next frame.
|
||||||
|
ENCODER_ERROR_RETRY_FRAME, ///< Retry current frame, after buffer growth or for incremental encoding.
|
||||||
|
ENCODER_ERROR_CONFIGURATION ///< Fatal config error, shut down pipeline.
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Converts EncoderError to string.
|
||||||
|
inline const char *to_string(EncoderError error) {
|
||||||
|
switch (error) {
|
||||||
|
case ENCODER_ERROR_SUCCESS:
|
||||||
|
return "ENCODER_ERROR_SUCCESS";
|
||||||
|
case ENCODER_ERROR_SKIP_FRAME:
|
||||||
|
return "ENCODER_ERROR_SKIP_FRAME";
|
||||||
|
case ENCODER_ERROR_RETRY_FRAME:
|
||||||
|
return "ENCODER_ERROR_RETRY_FRAME";
|
||||||
|
case ENCODER_ERROR_CONFIGURATION:
|
||||||
|
return "ENCODER_ERROR_CONFIGURATION";
|
||||||
|
}
|
||||||
|
return "ENCODER_ERROR_INVALID";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for an encoder buffer supporting resizing and variable-length data.
|
||||||
|
class EncoderBuffer {
|
||||||
|
public:
|
||||||
|
/// Sets logical buffer size, reallocates if needed.
|
||||||
|
/// @param size Required size in bytes.
|
||||||
|
/// @return true on success, false on allocation failure.
|
||||||
|
virtual bool set_buffer_size(size_t size) = 0;
|
||||||
|
|
||||||
|
/// Returns a pointer to the buffer data.
|
||||||
|
virtual uint8_t *get_data() const = 0;
|
||||||
|
|
||||||
|
/// Returns number of bytes currently used.
|
||||||
|
virtual size_t get_size() const = 0;
|
||||||
|
|
||||||
|
/// Returns total allocated buffer size.
|
||||||
|
virtual size_t get_max_size() const = 0;
|
||||||
|
|
||||||
|
virtual ~EncoderBuffer() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Interface for image encoders used in a camera pipeline.
|
||||||
|
class Encoder {
|
||||||
|
public:
|
||||||
|
/// Encodes pixel data from a previous camera pipeline stage.
|
||||||
|
/// @param spec Specification of the input pixel data.
|
||||||
|
/// @param pixels Image pixels in RGB or grayscale format, as specified in @p spec.
|
||||||
|
/// @return EncoderError Indicating the result of the encoding operation.
|
||||||
|
virtual EncoderError encode_pixels(CameraImageSpec *spec, Buffer *pixels) = 0;
|
||||||
|
|
||||||
|
/// Returns the encoder's output buffer.
|
||||||
|
/// @return Pointer to an EncoderBuffer containing encoded data.
|
||||||
|
virtual EncoderBuffer *get_output_buffer() = 0;
|
||||||
|
|
||||||
|
/// Prints the encoder's configuration to the log.
|
||||||
|
virtual void dump_config() = 0;
|
||||||
|
virtual ~Encoder() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::camera
|
62
esphome/components/camera_encoder/__init__.py
Normal file
62
esphome/components/camera_encoder/__init__.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components.esp32 import add_idf_component
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TYPE
|
||||||
|
from esphome.core import CORE
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
|
CODEOWNERS = ["@DT-art1"]
|
||||||
|
|
||||||
|
AUTO_LOAD = ["camera"]
|
||||||
|
|
||||||
|
CONF_BUFFER_EXPAND_SIZE = "buffer_expand_size"
|
||||||
|
CONF_ENCODER_BUFFER_ID = "encoder_buffer_id"
|
||||||
|
CONF_QUALITY = "quality"
|
||||||
|
|
||||||
|
ESP32_CAMERA_ENCODER = "esp32_camera"
|
||||||
|
|
||||||
|
camera_ns = cg.esphome_ns.namespace("camera")
|
||||||
|
camera_encoder_ns = cg.esphome_ns.namespace("camera_encoder")
|
||||||
|
|
||||||
|
Encoder = camera_ns.class_("Encoder")
|
||||||
|
EncoderBufferImpl = camera_encoder_ns.class_("EncoderBufferImpl")
|
||||||
|
|
||||||
|
ESP32CameraJPEGEncoder = camera_encoder_ns.class_("ESP32CameraJPEGEncoder", Encoder)
|
||||||
|
|
||||||
|
MAX_JPEG_BUFFER_SIZE_2MB = 2 * 1024 * 1024
|
||||||
|
|
||||||
|
ESP32_CAMERA_ENCODER_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(ESP32CameraJPEGEncoder),
|
||||||
|
cv.Optional(CONF_QUALITY, default=80): cv.int_range(1, 100),
|
||||||
|
cv.Optional(CONF_BUFFER_SIZE, default=4096): cv.int_range(
|
||||||
|
1024, MAX_JPEG_BUFFER_SIZE_2MB
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BUFFER_EXPAND_SIZE, default=1024): cv.int_range(
|
||||||
|
0, MAX_JPEG_BUFFER_SIZE_2MB
|
||||||
|
),
|
||||||
|
cv.GenerateID(CONF_ENCODER_BUFFER_ID): cv.declare_id(EncoderBufferImpl),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.typed_schema(
|
||||||
|
{
|
||||||
|
ESP32_CAMERA_ENCODER: ESP32_CAMERA_ENCODER_SCHEMA,
|
||||||
|
},
|
||||||
|
default_type=ESP32_CAMERA_ENCODER,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config: ConfigType) -> None:
|
||||||
|
buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID])
|
||||||
|
cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
||||||
|
if config[CONF_TYPE] == ESP32_CAMERA_ENCODER:
|
||||||
|
if CORE.using_esp_idf:
|
||||||
|
add_idf_component(name="espressif/esp32-camera", ref="2.1.0")
|
||||||
|
cg.add_build_flag("-DUSE_ESP32_CAMERA_JPEG_ENCODER")
|
||||||
|
var = cg.new_Pvariable(
|
||||||
|
config[CONF_ID],
|
||||||
|
config[CONF_QUALITY],
|
||||||
|
buffer,
|
||||||
|
)
|
||||||
|
cg.add(var.set_buffer_expand_size(config[CONF_BUFFER_EXPAND_SIZE]))
|
23
esphome/components/camera_encoder/encoder_buffer_impl.cpp
Normal file
23
esphome/components/camera_encoder/encoder_buffer_impl.cpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#include "encoder_buffer_impl.h"
|
||||||
|
|
||||||
|
namespace esphome::camera_encoder {
|
||||||
|
|
||||||
|
bool EncoderBufferImpl::set_buffer_size(size_t size) {
|
||||||
|
if (size > this->capacity_) {
|
||||||
|
uint8_t *p = this->allocator_.reallocate(this->data_, size);
|
||||||
|
if (p == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this->data_ = p;
|
||||||
|
this->capacity_ = size;
|
||||||
|
}
|
||||||
|
this->size_ = size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EncoderBufferImpl::~EncoderBufferImpl() {
|
||||||
|
if (this->data_ != nullptr)
|
||||||
|
this->allocator_.deallocate(this->data_, this->capacity_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::camera_encoder
|
25
esphome/components/camera_encoder/encoder_buffer_impl.h
Normal file
25
esphome/components/camera_encoder/encoder_buffer_impl.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/camera/encoder.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome::camera_encoder {
|
||||||
|
|
||||||
|
class EncoderBufferImpl : public camera::EncoderBuffer {
|
||||||
|
public:
|
||||||
|
// --- EncoderBuffer ---
|
||||||
|
bool set_buffer_size(size_t size) override;
|
||||||
|
uint8_t *get_data() const override { return this->data_; }
|
||||||
|
size_t get_size() const override { return this->size_; }
|
||||||
|
size_t get_max_size() const override { return this->capacity_; }
|
||||||
|
// ----------------------
|
||||||
|
~EncoderBufferImpl() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
RAMAllocator<uint8_t> allocator_;
|
||||||
|
size_t capacity_{};
|
||||||
|
size_t size_{};
|
||||||
|
uint8_t *data_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::camera_encoder
|
@@ -0,0 +1,82 @@
|
|||||||
|
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
|
||||||
|
|
||||||
|
#include "esp32_camera_jpeg_encoder.h"
|
||||||
|
|
||||||
|
namespace esphome::camera_encoder {
|
||||||
|
|
||||||
|
static const char *const TAG = "camera_encoder";
|
||||||
|
|
||||||
|
ESP32CameraJPEGEncoder::ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output) {
|
||||||
|
this->quality_ = quality;
|
||||||
|
this->output_ = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera::EncoderError ESP32CameraJPEGEncoder::encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) {
|
||||||
|
this->bytes_written_ = 0;
|
||||||
|
this->out_of_output_memory_ = false;
|
||||||
|
bool success = fmt2jpg_cb(pixels->get_data_buffer(), pixels->get_data_length(), spec->width, spec->height,
|
||||||
|
to_internal_(spec->format), this->quality_, callback_, this);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
return camera::ENCODER_ERROR_CONFIGURATION;
|
||||||
|
|
||||||
|
if (this->out_of_output_memory_) {
|
||||||
|
if (this->buffer_expand_size_ <= 0)
|
||||||
|
return camera::ENCODER_ERROR_SKIP_FRAME;
|
||||||
|
|
||||||
|
size_t current_size = this->output_->get_max_size();
|
||||||
|
size_t new_size = this->output_->get_max_size() + this->buffer_expand_size_;
|
||||||
|
if (!this->output_->set_buffer_size(new_size)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to expand output buffer.");
|
||||||
|
this->buffer_expand_size_ = 0;
|
||||||
|
return camera::ENCODER_ERROR_SKIP_FRAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Output buffer expanded (%u -> %u).", current_size, this->output_->get_max_size());
|
||||||
|
return camera::ENCODER_ERROR_RETRY_FRAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->output_->set_buffer_size(this->bytes_written_);
|
||||||
|
return camera::ENCODER_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32CameraJPEGEncoder::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
|
"ESP32 Camera JPEG Encoder:\n"
|
||||||
|
" Size: %zu\n"
|
||||||
|
" Quality: %d\n"
|
||||||
|
" Expand: %d\n",
|
||||||
|
this->output_->get_max_size(), this->quality_, this->buffer_expand_size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ESP32CameraJPEGEncoder::callback_(void *arg, size_t index, const void *data, size_t len) {
|
||||||
|
ESP32CameraJPEGEncoder *that = reinterpret_cast<ESP32CameraJPEGEncoder *>(arg);
|
||||||
|
uint8_t *buffer = that->output_->get_data();
|
||||||
|
size_t buffer_length = that->output_->get_max_size();
|
||||||
|
if (index + len > buffer_length) {
|
||||||
|
that->out_of_output_memory_ = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(&buffer[index], data, len);
|
||||||
|
that->bytes_written_ += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pixformat_t ESP32CameraJPEGEncoder::to_internal_(camera::PixelFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case camera::PIXEL_FORMAT_GRAYSCALE:
|
||||||
|
return PIXFORMAT_GRAYSCALE;
|
||||||
|
case camera::PIXEL_FORMAT_RGB565:
|
||||||
|
return PIXFORMAT_RGB565;
|
||||||
|
// Internal representation for RGB is in byte order: B, G, R
|
||||||
|
case camera::PIXEL_FORMAT_BGR888:
|
||||||
|
return PIXFORMAT_RGB888;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PIXFORMAT_GRAYSCALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::camera_encoder
|
||||||
|
|
||||||
|
#endif
|
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
|
||||||
|
|
||||||
|
#include <esp_camera.h>
|
||||||
|
|
||||||
|
#include "esphome/components/camera/encoder.h"
|
||||||
|
|
||||||
|
namespace esphome::camera_encoder {
|
||||||
|
|
||||||
|
/// Encoder that uses the software-based JPEG implementation from Espressif's esp32-camera component.
|
||||||
|
class ESP32CameraJPEGEncoder : public camera::Encoder {
|
||||||
|
public:
|
||||||
|
/// Constructs a ESP32CameraJPEGEncoder instance.
|
||||||
|
/// @param quality Sets the quality of the encoded image (1-100).
|
||||||
|
/// @param output Pointer to preallocated output buffer.
|
||||||
|
ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output);
|
||||||
|
/// Sets the number of bytes to expand the output buffer on underflow during encoding.
|
||||||
|
/// @param buffer_expand_size Number of bytes to expand the buffer.
|
||||||
|
void set_buffer_expand_size(size_t buffer_expand_size) { this->buffer_expand_size_ = buffer_expand_size; }
|
||||||
|
// -------- Encoder --------
|
||||||
|
camera::EncoderError encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) override;
|
||||||
|
camera::EncoderBuffer *get_output_buffer() override { return output_; }
|
||||||
|
void dump_config() override;
|
||||||
|
// -------------------------
|
||||||
|
protected:
|
||||||
|
static size_t callback_(void *arg, size_t index, const void *data, size_t len);
|
||||||
|
pixformat_t to_internal_(camera::PixelFormat format);
|
||||||
|
|
||||||
|
camera::EncoderBuffer *output_{};
|
||||||
|
size_t buffer_expand_size_{};
|
||||||
|
size_t bytes_written_{};
|
||||||
|
uint8_t quality_{};
|
||||||
|
bool out_of_output_memory_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::camera_encoder
|
||||||
|
|
||||||
|
#endif
|
@@ -10,7 +10,7 @@ from esphome.const import (
|
|||||||
PLATFORM_LN882X,
|
PLATFORM_LN882X,
|
||||||
PLATFORM_RTL87XX,
|
PLATFORM_RTL87XX,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
||||||
DEPENDENCIES = ["wifi"]
|
DEPENDENCIES = ["wifi"]
|
||||||
@@ -40,7 +40,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(64.0)
|
@coroutine_with_priority(CoroPriority.COMMUNICATION)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||||
|
|
||||||
|
@@ -7,103 +7,83 @@ namespace esphome {
|
|||||||
namespace captive_portal {
|
namespace captive_portal {
|
||||||
|
|
||||||
const uint8_t INDEX_GZ[] PROGMEM = {
|
const uint8_t INDEX_GZ[] PROGMEM = {
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xdd, 0x58, 0x6d, 0x6f, 0xdb, 0x38, 0x12, 0xfe, 0xde,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e,
|
||||||
0x5f, 0x31, 0xa7, 0x36, 0x6b, 0x6b, 0x1b, 0x51, 0x22, 0xe5, 0xb7, 0xd8, 0x92, 0x16, 0x69, 0xae, 0x8b, 0x5d, 0xa0,
|
0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36,
|
||||||
0xdd, 0x2d, 0x90, 0x6c, 0xef, 0x43, 0x51, 0x20, 0xb4, 0x34, 0xb2, 0xd8, 0x48, 0xa4, 0x4e, 0xa4, 0x5f, 0x52, 0xc3,
|
0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf,
|
||||||
0xf7, 0xdb, 0x0f, 0x94, 0x6c, 0xc7, 0xe9, 0x35, 0x87, 0xeb, 0xe2, 0x0e, 0x87, 0xdd, 0x18, 0x21, 0x86, 0xe4, 0xcc,
|
0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a,
|
||||||
0x70, 0xe6, 0xf1, 0x0c, 0x67, 0xcc, 0xe8, 0x2f, 0x99, 0x4a, 0xcd, 0x7d, 0x8d, 0x50, 0x98, 0xaa, 0x4c, 0x22, 0x3b,
|
0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68,
|
||||||
0x42, 0xc9, 0xe5, 0x22, 0x46, 0x99, 0x44, 0x05, 0xf2, 0x2c, 0x89, 0x2a, 0x34, 0x1c, 0xd2, 0x82, 0x37, 0x1a, 0x4d,
|
0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5,
|
||||||
0xfc, 0xdb, 0xcd, 0x8f, 0xde, 0x04, 0xfc, 0x24, 0x2a, 0x85, 0xbc, 0x83, 0x06, 0xcb, 0x58, 0xa4, 0x4a, 0x42, 0xd1,
|
0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22,
|
||||||
0x60, 0x1e, 0x67, 0xdc, 0xf0, 0xa9, 0xa8, 0xf8, 0x02, 0x2d, 0x43, 0x2b, 0x26, 0x79, 0x85, 0xf1, 0x4a, 0xe0, 0xba,
|
0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52,
|
||||||
0x56, 0x8d, 0x81, 0x54, 0x49, 0x83, 0xd2, 0xc4, 0xce, 0x5a, 0x64, 0xa6, 0x88, 0x33, 0x5c, 0x89, 0x14, 0xbd, 0x76,
|
0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06,
|
||||||
0x72, 0x2e, 0xa4, 0x30, 0x82, 0x97, 0x9e, 0x4e, 0x79, 0x89, 0x31, 0x3d, 0x5f, 0x6a, 0x6c, 0xda, 0x09, 0x9f, 0x97,
|
0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a,
|
||||||
0x18, 0x4b, 0xe5, 0xf8, 0x49, 0xa4, 0xd3, 0x46, 0xd4, 0x06, 0xac, 0xbd, 0x71, 0xa5, 0xb2, 0x65, 0x89, 0x89, 0xef,
|
0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0,
|
||||||
0x73, 0xad, 0xd1, 0x68, 0x5f, 0xc8, 0x0c, 0x37, 0x64, 0x14, 0x86, 0x29, 0xe3, 0xe3, 0x9c, 0x7c, 0xd2, 0xcf, 0x32,
|
0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84,
|
||||||
0x95, 0x2e, 0x2b, 0x94, 0x86, 0x94, 0x2a, 0xe5, 0x46, 0x28, 0x49, 0x34, 0xf2, 0x26, 0x2d, 0xe2, 0x38, 0x76, 0x7e,
|
0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7,
|
||||||
0xd0, 0x7c, 0x85, 0xce, 0x77, 0xdf, 0xf5, 0x8f, 0x4c, 0x0b, 0x34, 0xaf, 0x4b, 0xb4, 0xa4, 0x7e, 0x75, 0x7f, 0xc3,
|
0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05,
|
||||||
0x17, 0xbf, 0xf0, 0x0a, 0xfb, 0x0e, 0xd7, 0x22, 0x43, 0xc7, 0xfd, 0x10, 0x7c, 0x24, 0xda, 0xdc, 0x97, 0x48, 0x32,
|
0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6,
|
||||||
0xa1, 0xeb, 0x92, 0xdf, 0xc7, 0xce, 0xbc, 0x54, 0xe9, 0x9d, 0xe3, 0xce, 0xf2, 0xa5, 0x4c, 0xad, 0x72, 0xd0, 0x7d,
|
0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0,
|
||||||
0x74, 0xb7, 0x25, 0x1a, 0x30, 0xf1, 0x5b, 0x6e, 0x0a, 0x52, 0xf1, 0x4d, 0xbf, 0x23, 0x84, 0xec, 0xb3, 0xef, 0xfb,
|
0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7,
|
||||||
0xf8, 0x92, 0x06, 0x81, 0x7b, 0xde, 0x0e, 0x81, 0xeb, 0xd3, 0x20, 0x98, 0x35, 0x68, 0x96, 0x8d, 0x04, 0xde, 0xbf,
|
0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b,
|
||||||
0x8d, 0x6a, 0x6e, 0x0a, 0xc8, 0x62, 0xa7, 0xa2, 0x8c, 0x04, 0xc1, 0x04, 0xe8, 0x05, 0x61, 0x43, 0x8f, 0x52, 0x12,
|
0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e,
|
||||||
0x7a, 0x74, 0x98, 0x8e, 0xbd, 0x21, 0xd0, 0x81, 0x37, 0x04, 0xc6, 0xc8, 0x10, 0x82, 0xcf, 0x0e, 0xe4, 0xa2, 0x2c,
|
0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34,
|
||||||
0x63, 0x47, 0x2a, 0x89, 0x0e, 0x68, 0xd3, 0xa8, 0x3b, 0x8c, 0x9d, 0x74, 0xd9, 0x34, 0x28, 0xcd, 0x95, 0x2a, 0x55,
|
0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b,
|
||||||
0xe3, 0xf8, 0xc9, 0x33, 0x78, 0xf4, 0xf7, 0xcd, 0x47, 0x98, 0x86, 0x4b, 0x9d, 0xab, 0xa6, 0x8a, 0x9d, 0xf6, 0x4b,
|
0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1,
|
||||||
0xe9, 0xbf, 0xd8, 0x9a, 0x1d, 0xd8, 0xc1, 0x3d, 0xd9, 0xf4, 0x54, 0x23, 0x16, 0x42, 0xc6, 0x0e, 0x65, 0x40, 0x27,
|
0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37,
|
||||||
0x8e, 0x9f, 0xdc, 0xba, 0xbb, 0x23, 0x26, 0xdc, 0x62, 0xb2, 0xf7, 0x52, 0xf5, 0x3f, 0xdc, 0x46, 0x7a, 0xb5, 0x80,
|
0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac,
|
||||||
0x4d, 0x55, 0x4a, 0x1d, 0x3b, 0x85, 0x31, 0xf5, 0xd4, 0xf7, 0xd7, 0xeb, 0x35, 0x59, 0x87, 0x44, 0x35, 0x0b, 0x9f,
|
0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3,
|
||||||
0x05, 0x41, 0xe0, 0xeb, 0xd5, 0xc2, 0x81, 0x2e, 0x3e, 0x1c, 0x36, 0x70, 0xa0, 0x40, 0xb1, 0x28, 0x4c, 0x4b, 0x27,
|
0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68,
|
||||||
0x2f, 0xb6, 0xb8, 0x8b, 0x2c, 0x47, 0x72, 0xfb, 0xf1, 0xe4, 0x14, 0x71, 0x72, 0x0a, 0xfe, 0x70, 0x82, 0x66, 0xef,
|
0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc,
|
||||||
0xad, 0x35, 0x6a, 0xcc, 0x19, 0x30, 0x08, 0xda, 0x0f, 0xf3, 0x2c, 0xbd, 0x9f, 0x79, 0x5f, 0xcc, 0xe0, 0x64, 0x06,
|
0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c,
|
||||||
0x0c, 0x9e, 0x01, 0xb0, 0x6a, 0xe4, 0x5d, 0x1c, 0xc5, 0xa9, 0xdd, 0x5e, 0xd1, 0xe0, 0x61, 0xc1, 0xca, 0xfc, 0x34,
|
0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93,
|
||||||
0x3a, 0x9d, 0x7b, 0xec, 0xbd, 0x65, 0xb0, 0xd8, 0x1f, 0x85, 0x3c, 0x56, 0xd0, 0xf7, 0x23, 0x3e, 0x84, 0xe1, 0x7e,
|
0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c,
|
||||||
0x65, 0xe8, 0x59, 0xfa, 0x38, 0xb3, 0x27, 0xc1, 0x70, 0xc5, 0x0a, 0x5a, 0x79, 0x23, 0x6f, 0xc8, 0x43, 0x08, 0xf7,
|
0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18,
|
||||||
0x26, 0x85, 0x10, 0xae, 0x58, 0x31, 0x7a, 0x3f, 0x3a, 0x5d, 0xf3, 0xc2, 0xcf, 0x3d, 0x0b, 0xf3, 0xd4, 0x71, 0x1e,
|
0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06,
|
||||||
0x30, 0x50, 0xa7, 0x18, 0x90, 0x4f, 0x4a, 0xc8, 0xbe, 0xe3, 0xb8, 0xbb, 0x1c, 0x4d, 0x5a, 0xf4, 0x1d, 0x3f, 0x55,
|
0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c,
|
||||||
0x32, 0x17, 0x0b, 0xf2, 0x49, 0x2b, 0xe9, 0xb8, 0xc4, 0x14, 0x28, 0xfb, 0x07, 0x51, 0x2b, 0x88, 0xed, 0x4e, 0xff,
|
0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef,
|
||||||
0xcb, 0x1d, 0xe3, 0x6e, 0x8f, 0xf9, 0x61, 0x84, 0x29, 0x31, 0x36, 0xc4, 0x66, 0xf4, 0xf9, 0x71, 0x75, 0xae, 0xb2,
|
0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2,
|
||||||
0xfb, 0x27, 0x52, 0xa7, 0xa0, 0x5d, 0xde, 0x08, 0x29, 0xb1, 0xb9, 0xc1, 0x8d, 0x89, 0x9d, 0xb7, 0x97, 0x57, 0x70,
|
0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9,
|
||||||
0x99, 0x65, 0x0d, 0x6a, 0x3d, 0x05, 0xe7, 0xa5, 0x21, 0x15, 0x4f, 0xff, 0x73, 0x5d, 0xf4, 0x91, 0xae, 0xbf, 0x89,
|
0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8,
|
||||||
0x1f, 0x05, 0xfc, 0x82, 0x66, 0xad, 0x9a, 0xbb, 0xbd, 0x36, 0x6b, 0xda, 0xcc, 0x66, 0x60, 0x13, 0x1b, 0xc2, 0x6b,
|
0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc,
|
||||||
0x4d, 0x74, 0x29, 0x52, 0xec, 0x53, 0x97, 0x54, 0xbc, 0x7e, 0xf0, 0x4a, 0x1e, 0x80, 0xba, 0x8d, 0x32, 0xb1, 0x82,
|
0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca,
|
||||||
0xb4, 0xe4, 0x5a, 0xc7, 0x8e, 0xec, 0x54, 0x39, 0xb0, 0x4f, 0x1b, 0x25, 0xd3, 0x52, 0xa4, 0x77, 0xb1, 0xf3, 0x95,
|
0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f,
|
||||||
0x1b, 0xe2, 0xd5, 0xfd, 0xcf, 0x59, 0xbf, 0xa7, 0xb5, 0xc8, 0x7a, 0x2e, 0x59, 0xf1, 0x72, 0x89, 0x10, 0x83, 0x29,
|
0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0,
|
||||||
0x84, 0x7e, 0x30, 0x70, 0xf6, 0xa4, 0x58, 0xad, 0xef, 0x7a, 0x2e, 0xc9, 0x55, 0xba, 0xd4, 0x7d, 0xd7, 0x39, 0x64,
|
0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f,
|
||||||
0x69, 0xc4, 0xbb, 0x3b, 0xd4, 0x79, 0xee, 0x7c, 0x61, 0x91, 0x57, 0x62, 0x6e, 0x9c, 0x87, 0x6c, 0x7e, 0xb1, 0xd5,
|
0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c,
|
||||||
0x7d, 0x49, 0x1a, 0xad, 0x85, 0xbb, 0x3b, 0x2e, 0x46, 0xba, 0xe6, 0xf2, 0x4b, 0x41, 0x6b, 0xa0, 0x4d, 0x1a, 0x49,
|
0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d,
|
||||||
0x2c, 0x65, 0x33, 0xa7, 0xe6, 0xf2, 0x78, 0xa0, 0xcf, 0x0f, 0xe4, 0x8b, 0xad, 0xe8, 0x4b, 0x7b, 0x4b, 0xde, 0x1d,
|
0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf,
|
||||||
0x35, 0x46, 0x7e, 0x26, 0x56, 0xc9, 0xed, 0xce, 0x7d, 0xf0, 0xe3, 0xef, 0x4b, 0x6c, 0xee, 0xaf, 0xb1, 0xc4, 0xd4,
|
0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d,
|
||||||
0xa8, 0xa6, 0xef, 0x3c, 0x97, 0x68, 0x1c, 0xb7, 0x73, 0xf8, 0xa7, 0x9b, 0xb7, 0x6f, 0x62, 0xd5, 0x6f, 0xdc, 0xf3,
|
0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6,
|
||||||
0xa7, 0xb8, 0x6d, 0xb5, 0xf8, 0xd0, 0x60, 0xf9, 0x8f, 0xb8, 0x67, 0xeb, 0x45, 0xef, 0xa3, 0xe3, 0x92, 0xd6, 0xdf,
|
0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5,
|
||||||
0xdb, 0x87, 0xa2, 0x61, 0x13, 0xfb, 0xe5, 0xa6, 0x2a, 0xcf, 0xad, 0x87, 0xde, 0x68, 0xe8, 0xee, 0x6e, 0x77, 0xee,
|
0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b,
|
||||||
0xce, 0x9d, 0x45, 0x7e, 0x77, 0xef, 0x27, 0x51, 0x7b, 0x05, 0x27, 0xdf, 0x6f, 0xe7, 0x6a, 0xe3, 0x69, 0xf1, 0x59,
|
0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3,
|
||||||
0xc8, 0xc5, 0x54, 0xc8, 0x02, 0x1b, 0x61, 0x76, 0x99, 0x58, 0x9d, 0x0b, 0x59, 0x2f, 0xcd, 0xb6, 0xe6, 0x59, 0x66,
|
0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69,
|
||||||
0x77, 0x86, 0xf5, 0x66, 0x96, 0x2b, 0x69, 0x2c, 0x27, 0x4e, 0x29, 0x56, 0xbb, 0x6e, 0xbf, 0xbd, 0x5b, 0xa6, 0x17,
|
0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95,
|
||||||
0xc3, 0xb3, 0x9d, 0x0d, 0xb8, 0xad, 0xc1, 0x8d, 0xf1, 0x78, 0x29, 0x16, 0x72, 0x9a, 0xa2, 0x34, 0xd8, 0x74, 0x42,
|
0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9,
|
||||||
0x39, 0xaf, 0x44, 0x79, 0x3f, 0xd5, 0x5c, 0x6a, 0x4f, 0x63, 0x23, 0xf2, 0xdd, 0x7c, 0x69, 0x8c, 0x92, 0xdb, 0xb9,
|
0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e,
|
||||||
0x6a, 0x32, 0x6c, 0xa6, 0xc1, 0xac, 0x23, 0xbc, 0x86, 0x67, 0x62, 0xa9, 0xa7, 0x24, 0x6c, 0xb0, 0x9a, 0xcd, 0x79,
|
0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62,
|
||||||
0x7a, 0xb7, 0x68, 0xd4, 0x52, 0x66, 0x5e, 0x6a, 0x6f, 0xe1, 0xe9, 0x73, 0x9a, 0xf3, 0x10, 0xd3, 0xd9, 0x7e, 0x96,
|
0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7,
|
||||||
0xe7, 0xf9, 0xac, 0x14, 0x12, 0xbd, 0xee, 0x56, 0x9b, 0x32, 0x32, 0xb0, 0x62, 0x27, 0x66, 0x12, 0x66, 0x17, 0x3a,
|
0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97,
|
||||||
0x1b, 0x69, 0x10, 0x9c, 0xcd, 0x0e, 0xee, 0x04, 0xb3, 0x74, 0xd9, 0x68, 0xd5, 0x4c, 0x6b, 0x25, 0xac, 0x99, 0xbb,
|
0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee,
|
||||||
0x8a, 0x0b, 0x79, 0x6a, 0xbd, 0x0d, 0x93, 0xd9, 0xbe, 0x3c, 0x4d, 0x85, 0x6c, 0x8f, 0x69, 0x8b, 0xd4, 0xac, 0x12,
|
0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11,
|
||||||
0xb2, 0x2b, 0xb2, 0x53, 0x36, 0x0a, 0xea, 0xcd, 0x8e, 0xec, 0x03, 0x64, 0x7b, 0xe0, 0xce, 0x4b, 0xdc, 0xcc, 0x3e,
|
0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b,
|
||||||
0x2d, 0xb5, 0x11, 0xf9, 0xbd, 0xb7, 0x2f, 0xd2, 0x53, 0x5d, 0xf3, 0x14, 0xbd, 0x39, 0x9a, 0x35, 0xa2, 0x9c, 0xb5,
|
0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9,
|
||||||
0x67, 0x78, 0xc2, 0x60, 0xa5, 0xf7, 0x38, 0x1d, 0xd5, 0xb4, 0x01, 0xfa, 0x58, 0xd7, 0xbf, 0xe3, 0xb6, 0xb1, 0xb8,
|
0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93,
|
||||||
0xad, 0x78, 0xb3, 0x10, 0xd2, 0x9b, 0x2b, 0x63, 0x54, 0x35, 0xf5, 0xc6, 0xf5, 0x66, 0xb6, 0x5f, 0xb2, 0xca, 0xa6,
|
0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97,
|
||||||
0xd4, 0x9a, 0xd9, 0xd6, 0xde, 0x03, 0xde, 0xb4, 0xde, 0x80, 0x56, 0xa5, 0xc8, 0xf6, 0x7c, 0x2d, 0x0b, 0x04, 0x47,
|
0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19,
|
||||||
0x78, 0xe8, 0xb0, 0xde, 0x80, 0x5d, 0x3b, 0x40, 0x3d, 0xc8, 0x27, 0x9c, 0x06, 0x5f, 0xf9, 0x46, 0xb2, 0x3c, 0x67,
|
0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc,
|
||||||
0xf3, 0xfc, 0x88, 0x94, 0x2d, 0xa1, 0x3b, 0xb1, 0x8f, 0x0a, 0x36, 0xa8, 0x37, 0xb3, 0xc3, 0x77, 0x33, 0xa8, 0x37,
|
0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2,
|
||||||
0x3b, 0xd1, 0xa6, 0xc5, 0xf6, 0x44, 0x4b, 0x1b, 0xaa, 0xd3, 0x65, 0x53, 0xf6, 0x9d, 0xaf, 0x84, 0xee, 0x59, 0x78,
|
0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc,
|
||||||
0xf5, 0x50, 0xe2, 0x7a, 0x4f, 0x97, 0xb8, 0x1e, 0xd8, 0xa6, 0xe8, 0x95, 0xda, 0xc4, 0xbd, 0xb6, 0xd8, 0x0c, 0x80,
|
0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e,
|
||||||
0x0d, 0x7a, 0x67, 0xe1, 0xeb, 0xb3, 0xf0, 0xea, 0xbf, 0x52, 0xbb, 0x7e, 0x77, 0xe1, 0xfa, 0x86, 0xaa, 0xf5, 0x8d,
|
0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e,
|
||||||
0x15, 0xab, 0xf3, 0xce, 0x3a, 0x7f, 0x16, 0xbe, 0x76, 0xdc, 0x9d, 0x20, 0x5a, 0x2c, 0xe8, 0xff, 0x02, 0xda, 0x7f,
|
0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9,
|
||||||
0xc5, 0x31, 0xbc, 0xa4, 0x13, 0x72, 0x01, 0xed, 0xd0, 0x41, 0x44, 0xc2, 0x09, 0x8c, 0xaf, 0x06, 0x64, 0x40, 0xc1,
|
0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3,
|
||||||
0xb6, 0x43, 0x23, 0x18, 0x93, 0xc9, 0x05, 0xd0, 0x11, 0x09, 0xc7, 0x40, 0x19, 0x30, 0x4a, 0x86, 0x6f, 0x58, 0x48,
|
0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5,
|
||||||
0x46, 0x43, 0x18, 0x5f, 0xb1, 0x80, 0x84, 0x0c, 0x3a, 0xde, 0x11, 0x61, 0x0c, 0x42, 0xcb, 0x12, 0x56, 0x01, 0xb0,
|
0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37,
|
||||||
0x34, 0x24, 0xc1, 0x18, 0x02, 0x18, 0x91, 0xe0, 0x82, 0x4c, 0x46, 0x30, 0x21, 0x63, 0x0a, 0x8c, 0x0c, 0x86, 0xa5,
|
0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f,
|
||||||
0x37, 0x24, 0x14, 0x46, 0x24, 0x1c, 0xf1, 0x09, 0x19, 0x84, 0xd0, 0x0e, 0x1d, 0x1c, 0x63, 0xc2, 0x98, 0x47, 0x02,
|
0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22,
|
||||||
0xfa, 0x26, 0x24, 0x6c, 0x0c, 0x63, 0x32, 0x18, 0x5c, 0xd2, 0x11, 0xb9, 0x18, 0x40, 0x37, 0x76, 0xf0, 0x52, 0x06,
|
0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68,
|
||||||
0xc3, 0xa7, 0x40, 0x63, 0x7f, 0x5e, 0xd0, 0x42, 0xc2, 0x28, 0x84, 0xe4, 0x62, 0xc2, 0x6d, 0x5f, 0xca, 0xa0, 0x1b,
|
0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
||||||
0x3b, 0xdc, 0x28, 0x85, 0xe0, 0x77, 0x63, 0x16, 0xfe, 0x79, 0x31, 0xa3, 0x16, 0x01, 0x46, 0x06, 0xe1, 0x25, 0x0d,
|
|
||||||
0xc9, 0x08, 0xda, 0xa1, 0x3b, 0x9b, 0x32, 0x98, 0x5c, 0x5d, 0xc0, 0x04, 0x46, 0x64, 0x34, 0x81, 0x0b, 0x18, 0x5a,
|
|
||||||
0x74, 0x2f, 0xc8, 0x64, 0xd0, 0x09, 0x79, 0x8c, 0x7c, 0x2b, 0x8c, 0x83, 0x3f, 0x30, 0x8c, 0x4f, 0xf9, 0xf4, 0x07,
|
|
||||||
0x76, 0xe9, 0xff, 0x71, 0x05, 0x45, 0x7e, 0xd7, 0x86, 0x45, 0x7e, 0xf7, 0x3c, 0x60, 0xbb, 0xa8, 0x24, 0xb2, 0xdd,
|
|
||||||
0x48, 0x12, 0x15, 0x14, 0x44, 0x16, 0x57, 0x3c, 0x4d, 0x4e, 0x5a, 0xfd, 0xc8, 0x2f, 0xe8, 0x61, 0xab, 0xa0, 0xc9,
|
|
||||||
0xa3, 0xc6, 0xbd, 0xdb, 0x6b, 0x2b, 0x7d, 0x72, 0x53, 0x20, 0xbc, 0xbe, 0x7e, 0x07, 0x6b, 0x51, 0x96, 0x20, 0xd5,
|
|
||||||
0x1a, 0x4c, 0x73, 0x0f, 0x46, 0xd9, 0x57, 0x03, 0x89, 0xa9, 0xb1, 0xa4, 0x29, 0x10, 0xf6, 0x7d, 0x04, 0x21, 0x24,
|
|
||||||
0x9a, 0x37, 0xc9, 0xbb, 0x12, 0xb9, 0x46, 0x58, 0x88, 0x15, 0x82, 0x30, 0xa0, 0x55, 0x85, 0x60, 0x84, 0x1d, 0x8e,
|
|
||||||
0x82, 0x2d, 0x5f, 0xe4, 0x77, 0x87, 0x74, 0x8d, 0xb2, 0xc8, 0x62, 0x89, 0x26, 0xd9, 0x77, 0xc4, 0x51, 0x11, 0x76,
|
|
||||||
0x56, 0x5d, 0xa3, 0x31, 0x42, 0x2e, 0xac, 0x55, 0x61, 0x12, 0xd9, 0x5f, 0xb7, 0xc0, 0xdb, 0xdf, 0x0c, 0xb1, 0xbf,
|
|
||||||
0x16, 0xb9, 0xb0, 0x6f, 0x06, 0x49, 0xd4, 0x76, 0x91, 0x56, 0x83, 0x6d, 0x64, 0xba, 0x07, 0x8e, 0x96, 0x2a, 0x51,
|
|
||||||
0x2e, 0x4c, 0x11, 0x87, 0x0c, 0xea, 0x92, 0xa7, 0x58, 0xa8, 0x32, 0xc3, 0x26, 0xbe, 0xbe, 0xfe, 0xf9, 0xaf, 0xf6,
|
|
||||||
0x35, 0xc4, 0x9a, 0x70, 0x94, 0xac, 0xf5, 0x5d, 0x27, 0x68, 0x89, 0xbd, 0xdc, 0x68, 0xd0, 0xbd, 0x6b, 0xd4, 0x5c,
|
|
||||||
0xeb, 0xb5, 0x6a, 0xb2, 0x47, 0x5a, 0xde, 0x1d, 0x16, 0xf7, 0x9a, 0xda, 0xff, 0xb6, 0x1f, 0xed, 0x84, 0xf4, 0x72,
|
|
||||||
0x5e, 0x09, 0x93, 0x5c, 0xf3, 0x15, 0x46, 0x7e, 0xb7, 0x91, 0x44, 0xbe, 0x75, 0xa0, 0xe3, 0x2d, 0xf6, 0x32, 0x05,
|
|
||||||
0x4d, 0x7e, 0xbd, 0xb9, 0x84, 0xdf, 0xea, 0x8c, 0x1b, 0xec, 0xb0, 0x6f, 0xbd, 0xac, 0xd0, 0x14, 0x2a, 0x8b, 0xdf,
|
|
||||||
0xfd, 0x7a, 0x7d, 0x73, 0xf4, 0x78, 0xd9, 0x32, 0x01, 0xca, 0xb4, 0x7b, 0x6f, 0x59, 0x96, 0x46, 0xd4, 0xbc, 0x31,
|
|
||||||
0xad, 0x5a, 0xcf, 0x66, 0xc7, 0xc1, 0xa3, 0x76, 0x3f, 0x17, 0x25, 0x76, 0x4e, 0xed, 0x05, 0xfd, 0x04, 0xbe, 0x66,
|
|
||||||
0xe3, 0xe1, 0xec, 0x2f, 0xac, 0xf4, 0xbb, 0x00, 0xf2, 0xbb, 0x68, 0xf2, 0xdb, 0xd7, 0xa8, 0x7f, 0x02, 0x14, 0xee,
|
|
||||||
0xbc, 0x64, 0x9d, 0x12, 0x00, 0x00};
|
|
||||||
|
|
||||||
} // namespace captive_portal
|
} // namespace captive_portal
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@@ -11,17 +11,35 @@ namespace captive_portal {
|
|||||||
static const char *const TAG = "captive_portal";
|
static const char *const TAG = "captive_portal";
|
||||||
|
|
||||||
void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
|
void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
|
||||||
AsyncResponseStream *stream = request->beginResponseStream("application/json");
|
AsyncResponseStream *stream = request->beginResponseStream(F("application/json"));
|
||||||
stream->addHeader("cache-control", "public, max-age=0, must-revalidate");
|
stream->addHeader(F("cache-control"), F("public, max-age=0, must-revalidate"));
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
stream->print(F("{\"mac\":\""));
|
||||||
|
stream->print(get_mac_address_pretty().c_str());
|
||||||
|
stream->print(F("\",\"name\":\""));
|
||||||
|
stream->print(App.get_name().c_str());
|
||||||
|
stream->print(F("\",\"aps\":[{}"));
|
||||||
|
#else
|
||||||
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str());
|
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str());
|
||||||
|
#endif
|
||||||
|
|
||||||
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
|
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
|
||||||
if (scan.get_is_hidden())
|
if (scan.get_is_hidden())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Assumes no " in ssid, possible unicode isses?
|
// Assumes no " in ssid, possible unicode isses?
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
stream->print(F(",{\"ssid\":\""));
|
||||||
|
stream->print(scan.get_ssid().c_str());
|
||||||
|
stream->print(F("\",\"rssi\":"));
|
||||||
|
stream->print(scan.get_rssi());
|
||||||
|
stream->print(F(",\"lock\":"));
|
||||||
|
stream->print(scan.get_with_auth());
|
||||||
|
stream->print(F("}"));
|
||||||
|
#else
|
||||||
stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
|
stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
|
||||||
scan.get_with_auth());
|
scan.get_with_auth());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
stream->print(F("]}"));
|
stream->print(F("]}"));
|
||||||
request->send(stream);
|
request->send(stream);
|
||||||
@@ -34,7 +52,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
|
|||||||
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
||||||
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
|
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
|
||||||
wifi::global_wifi_component->start_scanning();
|
wifi::global_wifi_component->start_scanning();
|
||||||
request->redirect("/?save");
|
request->redirect(F("/?save"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CaptivePortal::setup() {
|
void CaptivePortal::setup() {
|
||||||
@@ -53,18 +71,23 @@ void CaptivePortal::start() {
|
|||||||
this->dns_server_ = make_unique<DNSServer>();
|
this->dns_server_ = make_unique<DNSServer>();
|
||||||
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||||
this->dns_server_->start(53, "*", ip);
|
this->dns_server_->start(53, F("*"), ip);
|
||||||
// Re-enable loop() when DNS server is started
|
// Re-enable loop() when DNS server is started
|
||||||
this->enable_loop();
|
this->enable_loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
|
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
|
||||||
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
|
if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
|
||||||
req->send(404, "text/html", "File not found");
|
req->send(404, F("text/html"), F("File not found"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
String url = F("http://");
|
||||||
|
url += wifi::global_wifi_component->wifi_soft_ap_ip().str().c_str();
|
||||||
|
#else
|
||||||
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str();
|
auto url = "http://" + wifi::global_wifi_component->wifi_soft_ap_ip().str();
|
||||||
|
#endif
|
||||||
req->redirect(url.c_str());
|
req->redirect(url.c_str());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,19 +96,19 @@ void CaptivePortal::start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
|
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
|
||||||
if (req->url() == "/") {
|
if (req->url() == F("/")) {
|
||||||
#ifndef USE_ESP8266
|
#ifndef USE_ESP8266
|
||||||
auto *response = req->beginResponse(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
|
auto *response = req->beginResponse(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
|
||||||
#else
|
#else
|
||||||
auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
|
auto *response = req->beginResponse_P(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ));
|
||||||
#endif
|
#endif
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
response->addHeader(F("Content-Encoding"), F("gzip"));
|
||||||
req->send(response);
|
req->send(response);
|
||||||
return;
|
return;
|
||||||
} else if (req->url() == "/config.json") {
|
} else if (req->url() == F("/config.json")) {
|
||||||
this->handle_config(req);
|
this->handle_config(req);
|
||||||
return;
|
return;
|
||||||
} else if (req->url() == "/wifisave") {
|
} else if (req->url() == F("/wifisave")) {
|
||||||
this->handle_wifisave(req);
|
this->handle_wifisave(req);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -45,11 +45,11 @@ class CaptivePortal : public AsyncWebHandler, public Component {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (request->method() == HTTP_GET) {
|
if (request->method() == HTTP_GET) {
|
||||||
if (request->url() == "/")
|
if (request->url() == F("/"))
|
||||||
return true;
|
return true;
|
||||||
if (request->url() == "/config.json")
|
if (request->url() == F("/config.json"))
|
||||||
return true;
|
return true;
|
||||||
if (request->url() == "/wifisave")
|
if (request->url() == F("/wifisave"))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user