mirror of
https://github.com/esphome/esphome.git
synced 2025-11-18 07:45:56 +00:00
Compare commits
402 Commits
wifi_prio
...
captive_po
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2268b0cb5a | ||
|
|
93e7db7b9c | ||
|
|
1200e9c3ca | ||
|
|
dc277e64f4 | ||
|
|
6aa72f5449 | ||
|
|
e312184a22 | ||
|
|
6d67fd0b81 | ||
|
|
a08d67027d | ||
|
|
0b1dd89fec | ||
|
|
547f69011b | ||
|
|
53bab00858 | ||
|
|
23f85162d0 | ||
|
|
7a238028a7 | ||
|
|
3d6c361037 | ||
|
|
f31a29272e | ||
|
|
adf75630fe | ||
|
|
539597209f | ||
|
|
44eb4c581c | ||
|
|
8b1f3b2b08 | ||
|
|
70e44cd5a6 | ||
|
|
499ad18475 | ||
|
|
0afaf182da | ||
|
|
43f2405dc3 | ||
|
|
41ac12a0e1 | ||
|
|
a6f416a09e | ||
|
|
64b9e23728 | ||
|
|
c155dbb4e6 | ||
|
|
9e1f8d83f8 | ||
|
|
fa0aa6defc | ||
|
|
70366d2124 | ||
|
|
78a69cb744 | ||
|
|
9b14444dad | ||
|
|
8934d4b498 | ||
|
|
9b107e7f2a | ||
|
|
7edfdf9e65 | ||
|
|
11b53800fb | ||
|
|
bbd12609af | ||
|
|
23be236133 | ||
|
|
10bdb47eae | ||
|
|
a38c4e0c6e | ||
|
|
6c6b03bda0 | ||
|
|
9e02e31917 | ||
|
|
3fd58f1a91 | ||
|
|
9151489481 | ||
|
|
f19296ac7f | ||
|
|
36868ee7b1 | ||
|
|
d559f9f52e | ||
|
|
6440b5fbf5 | ||
|
|
97c4914573 | ||
|
|
7ce94c27fe | ||
|
|
eb54c0026d | ||
|
|
fe00e209ff | ||
|
|
aed80732f9 | ||
|
|
aa097a2fe6 | ||
|
|
3b860e784c | ||
|
|
96ee38759d | ||
|
|
986d3c8f13 | ||
|
|
c4c1de915a | ||
|
|
b8c216c646 | ||
|
|
320120883c | ||
|
|
46ea962e4f | ||
|
|
76546bade7 | ||
|
|
8997fb3443 | ||
|
|
af58909bd7 | ||
|
|
094ed59230 | ||
|
|
5edcb62f6e | ||
|
|
8512266068 | ||
|
|
4c9d903773 | ||
|
|
fd0d1c41b7 | ||
|
|
d878e9a8f2 | ||
|
|
02c5f18b5d | ||
|
|
a913e7df33 | ||
|
|
4fc4da6ed2 | ||
|
|
6f4042f401 | ||
|
|
ea2b4c3e25 | ||
|
|
fc546ca3f6 | ||
|
|
6b158e760d | ||
|
|
5710cab972 | ||
|
|
bfea4220dc | ||
|
|
027ede7ca2 | ||
|
|
6d03afecd0 | ||
|
|
eb759efb3d | ||
|
|
7aae4b4fce | ||
|
|
3d9390498b | ||
|
|
d60c358f48 | ||
|
|
1138495efa | ||
|
|
f725877b0e | ||
|
|
26b820272a | ||
|
|
d4648b51ab | ||
|
|
e20d89d190 | ||
|
|
9557c90c20 | ||
|
|
50faae2d3c | ||
|
|
693b27a190 | ||
|
|
d64bcf27b3 | ||
|
|
61520726ad | ||
|
|
47d1081ebc | ||
|
|
d5d61546e7 | ||
|
|
7289a4e2c6 | ||
|
|
0b33ba606c | ||
|
|
554cdbd5a4 | ||
|
|
8ec14bd57c | ||
|
|
a8a3e4d157 | ||
|
|
0b132f9569 | ||
|
|
0d147e5d10 | ||
|
|
a552e1d328 | ||
|
|
46ddd253bd | ||
|
|
730a70ee8b | ||
|
|
0f6963de27 | ||
|
|
1b19e4e8ad | ||
|
|
d096f1192d | ||
|
|
b14bab1fce | ||
|
|
d84976715b | ||
|
|
ae985bfb07 | ||
|
|
88a23acc4b | ||
|
|
950dff1a38 | ||
|
|
61eddfdcda | ||
|
|
13ac071482 | ||
|
|
0f10b9d76f | ||
|
|
c61411c620 | ||
|
|
e8e06db260 | ||
|
|
219206057f | ||
|
|
f6378990cd | ||
|
|
5090762d5a | ||
|
|
e604ec267e | ||
|
|
ae343a94ca | ||
|
|
2229996c22 | ||
|
|
b7d4e01fa2 | ||
|
|
25c7be3970 | ||
|
|
849c9b1625 | ||
|
|
1c10485cee | ||
|
|
8e6747c00e | ||
|
|
d7892f2289 | ||
|
|
b7f6013378 | ||
|
|
fb9e7028a0 | ||
|
|
e9ff4d3c4e | ||
|
|
35b6395449 | ||
|
|
f29b191804 | ||
|
|
734f96140f | ||
|
|
ed60d8668e | ||
|
|
7892adb948 | ||
|
|
96ea6e8059 | ||
|
|
107a0a23b1 | ||
|
|
4081345013 | ||
|
|
3dd570fdd0 | ||
|
|
04f445dfaf | ||
|
|
82d04c1548 | ||
|
|
5989b78e93 | ||
|
|
5727043cec | ||
|
|
1441c7fab2 | ||
|
|
62248b6bba | ||
|
|
86833cbc3c | ||
|
|
6067ca36f7 | ||
|
|
a98d2fa266 | ||
|
|
b7c105125e | ||
|
|
11de948698 | ||
|
|
6ade327cde | ||
|
|
cc1b547ad2 | ||
|
|
d1f3979c7f | ||
|
|
25408dc445 | ||
|
|
f1bc3c68dd | ||
|
|
f8191410e3 | ||
|
|
90ba8ecacd | ||
|
|
2f354a9c32 | ||
|
|
1df996601d | ||
|
|
c25b87350d | ||
|
|
db826b4a2b | ||
|
|
c32891ec02 | ||
|
|
2bf6d48fcf | ||
|
|
51eb15bf8c | ||
|
|
e19a99dd7f | ||
|
|
6666911ebf | ||
|
|
b890f097cf | ||
|
|
5af5a86970 | ||
|
|
3de847014b | ||
|
|
5f10fbc4f6 | ||
|
|
e8f2e91db3 | ||
|
|
e49a943cf7 | ||
|
|
67524e14ee | ||
|
|
6370132cbb | ||
|
|
3c0969d8a4 | ||
|
|
1f408ce41c | ||
|
|
2290eb0dd2 | ||
|
|
e1ed7f7fd0 | ||
|
|
79b97452b7 | ||
|
|
0afcf67c32 | ||
|
|
952bdfaac2 | ||
|
|
ed7e5cd325 | ||
|
|
eefda8ce16 | ||
|
|
9f5a6017af | ||
|
|
20649ce8ce | ||
|
|
a15f46e741 | ||
|
|
050a27a409 | ||
|
|
2592119138 | ||
|
|
8c9d1cc392 | ||
|
|
9b458d25ea | ||
|
|
4e23a7a3e1 | ||
|
|
382483b063 | ||
|
|
1675408161 | ||
|
|
1d8b08dcce | ||
|
|
afed581079 | ||
|
|
ff107a0674 | ||
|
|
72da3d0f1e | ||
|
|
5a2e6697e0 | ||
|
|
799cfe1de4 | ||
|
|
6df0264d51 | ||
|
|
a859ecaad1 | ||
|
|
4f088c93c9 | ||
|
|
a1ab19d127 | ||
|
|
47a3e5e78b | ||
|
|
f196b1c867 | ||
|
|
d869108416 | ||
|
|
2d6618da3c | ||
|
|
47fe84e922 | ||
|
|
735bf9930a | ||
|
|
769137fc09 | ||
|
|
3a5b3ad77d | ||
|
|
859101ddc9 | ||
|
|
f19abab93f | ||
|
|
b8717fee4e | ||
|
|
4eb471b316 | ||
|
|
b017e034ee | ||
|
|
9a2574a2fe | ||
|
|
72513897cc | ||
|
|
20388ce848 | ||
|
|
72185ac01f | ||
|
|
9e8e5fd8aa | ||
|
|
8a71a6bfdc | ||
|
|
29a50da635 | ||
|
|
5f0fa68d73 | ||
|
|
2f39b10baa | ||
|
|
5a550cc579 | ||
|
|
4b58cb4ce6 | ||
|
|
3872a2fd91 | ||
|
|
5d613ada83 | ||
|
|
53dacef4a6 | ||
|
|
24701d95e5 | ||
|
|
08127d0225 | ||
|
|
b28d8a7d22 | ||
|
|
e7a5790276 | ||
|
|
97d2f5ee25 | ||
|
|
9accb16b4a | ||
|
|
8c0c38be21 | ||
|
|
8037dbe2d3 | ||
|
|
6395439d33 | ||
|
|
9de80b635a | ||
|
|
c299361753 | ||
|
|
4b3d3c4ca2 | ||
|
|
8ce4d5cd4f | ||
|
|
c1fb8dae37 | ||
|
|
748aee584a | ||
|
|
d8454e7c0a | ||
|
|
fb00f75192 | ||
|
|
1b487988c9 | ||
|
|
3cbfddcc83 | ||
|
|
1d71b6b93e | ||
|
|
398dba4fc8 | ||
|
|
298813d4fa | ||
|
|
fd308a0687 | ||
|
|
016de16181 | ||
|
|
0d46bc57d6 | ||
|
|
0c506570a6 | ||
|
|
c0507b2ad0 | ||
|
|
7cefb8d92c | ||
|
|
089eb3bc95 | ||
|
|
8de3bcd304 | ||
|
|
32456bee58 | ||
|
|
e104103366 | ||
|
|
304e0a2c65 | ||
|
|
9c871c39b5 | ||
|
|
276ef4ee1d | ||
|
|
dd65e39d16 | ||
|
|
56d141c741 | ||
|
|
47a7f729dd | ||
|
|
904bc11d5b | ||
|
|
a6c8319ade | ||
|
|
7c6f6acf60 | ||
|
|
7806eb980f | ||
|
|
a59888224c | ||
|
|
58ad4759f0 | ||
|
|
87f79290ba | ||
|
|
9326d78439 | ||
|
|
a93887a790 | ||
|
|
d7fa131a8a | ||
|
|
7f40ef971d | ||
|
|
0c127b6998 | ||
|
|
037620d75a | ||
|
|
e0e308c396 | ||
|
|
0ab3d3da4e | ||
|
|
79a4444928 | ||
|
|
572fae5c7d | ||
|
|
5dafaaced4 | ||
|
|
65a303d48f | ||
|
|
00c71b7236 | ||
|
|
0ab0563b52 | ||
|
|
ccf5591d14 | ||
|
|
1c7c559b69 | ||
|
|
6dbd961b3e | ||
|
|
088964de4d | ||
|
|
f0a9ee871b | ||
|
|
f3a3f2e4f7 | ||
|
|
3280ce936a | ||
|
|
efe6e58404 | ||
|
|
ef04903a7a | ||
|
|
a2a05ba23a | ||
|
|
08ea53633f | ||
|
|
d54179fd12 | ||
|
|
fb5b37c17a | ||
|
|
9c6d9badf2 | ||
|
|
55bdbf60c9 | ||
|
|
a2ec7f622c | ||
|
|
c3111b1dd3 | ||
|
|
530fc8f030 | ||
|
|
a8f253eecf | ||
|
|
e85eeb3d99 | ||
|
|
64c79f6363 | ||
|
|
7b60a8a21a | ||
|
|
42fa0b61a7 | ||
|
|
2f91e7bd47 | ||
|
|
3aed5f5eae | ||
|
|
ec79eb3065 | ||
|
|
e589542bd3 | ||
|
|
c804467277 | ||
|
|
8ce7f9dfe8 | ||
|
|
c3967df6ce | ||
|
|
ecb3eecf88 | ||
|
|
f0d1cde35c | ||
|
|
191cf1b03c | ||
|
|
80a7c6d3c3 | ||
|
|
35ecaff089 | ||
|
|
374d039f60 | ||
|
|
e19bdf63a5 | ||
|
|
1bde521380 | ||
|
|
db9af4a862 | ||
|
|
d092e59a79 | ||
|
|
5a77f63103 | ||
|
|
f6ac916bb2 | ||
|
|
7a92565a0c | ||
|
|
93f8e40111 | ||
|
|
bbff660499 | ||
|
|
e7409ac5cd | ||
|
|
661920c51e | ||
|
|
a6b905e148 | ||
|
|
366e95f8d8 | ||
|
|
326edd5082 | ||
|
|
d74fc6347b | ||
|
|
dbbc4f741d | ||
|
|
2d63b69ac1 | ||
|
|
a14e2d4d08 | ||
|
|
300bd420f8 | ||
|
|
2d2472c50b | ||
|
|
2e115baf56 | ||
|
|
b58b706bd6 | ||
|
|
d389ed585e | ||
|
|
1b30346c1e | ||
|
|
6b45debcba | ||
|
|
e42b29659b | ||
|
|
aba9ffccdf | ||
|
|
8e29ae416e | ||
|
|
75c220eeb6 | ||
|
|
a6b7c1f18c | ||
|
|
d2e1fbd76b | ||
|
|
3bb7639470 | ||
|
|
72a6051f0d | ||
|
|
649e27bf62 | ||
|
|
77436b85b2 | ||
|
|
5f0957c81a | ||
|
|
bee174150b | ||
|
|
262f28aec5 | ||
|
|
bf312ad9ec | ||
|
|
55cf0adb18 | ||
|
|
941feeedbe | ||
|
|
4565b126e2 | ||
|
|
64651b5a07 | ||
|
|
f3007a5245 | ||
|
|
0e62c8b3fb | ||
|
|
4160157457 | ||
|
|
75d7578491 | ||
|
|
f28566545f | ||
|
|
dc37321aa9 | ||
|
|
89abd9c817 | ||
|
|
d4d44a5c08 | ||
|
|
b8e4efc1cd | ||
|
|
cf66c4cd3e | ||
|
|
4b60012814 | ||
|
|
c38df0af85 | ||
|
|
bb51c6b6d5 | ||
|
|
b8f972b6f6 | ||
|
|
d87063865c | ||
|
|
682b6711f3 | ||
|
|
066674df19 | ||
|
|
48a33611a1 | ||
|
|
caf6045485 | ||
|
|
bd7d103813 | ||
|
|
6631e2ffb2 | ||
|
|
b80b0eb864 | ||
|
|
7a700ca077 | ||
|
|
130a8b853d | ||
|
|
0f02c75f66 | ||
|
|
99bb6ead7e | ||
|
|
d171406dff | ||
|
|
65fd784fa7 | ||
|
|
b9f208b63a |
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
|
||||
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
|
||||
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
- "docker"
|
||||
# - "lint"
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Check out code from base repository
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
# Always check out from the base repository (esphome/esphome), never from forks
|
||||
# Use the PR's target branch to ensure we run trusted code from the main repo
|
||||
|
||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -132,7 +132,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Restore Python
|
||||
id: restore-python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
component-test-batches: ${{ steps.determine.outputs.component-test-batches }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
# Fetch enough history to find the merge base
|
||||
fetch-depth: 2
|
||||
@@ -237,7 +237,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
@@ -273,7 +273,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && (needs.determine-jobs.outputs.cpp-unit-tests-run-all == 'true' || needs.determine-jobs.outputs.cpp-unit-tests-components != '[]')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -321,7 +321,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -489,7 +489,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -577,7 +577,7 @@ jobs:
|
||||
version: 1.0
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -662,7 +662,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && !startsWith(github.base_ref, 'beta') && !startsWith(github.base_ref, 'release')
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -688,7 +688,7 @@ jobs:
|
||||
skip: ${{ steps.check-script.outputs.skip }}
|
||||
steps:
|
||||
- name: Check out target branch
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
@@ -840,7 +840,7 @@ jobs:
|
||||
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
||||
steps:
|
||||
- name: Check out PR branch
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -908,7 +908,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -54,11 +54,11 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
|
||||
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.4
|
||||
rev: v0.14.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.11.0-dev
|
||||
PROJECT_NUMBER = 2025.12.0-dev
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -16,6 +16,11 @@ from . import (
|
||||
class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
"""Memory analyzer with CLI-specific report generation."""
|
||||
|
||||
# Symbol size threshold for detailed analysis
|
||||
SYMBOL_SIZE_THRESHOLD: int = (
|
||||
100 # Show symbols larger than this in detailed analysis
|
||||
)
|
||||
|
||||
# Column width constants
|
||||
COL_COMPONENT: int = 29
|
||||
COL_FLASH_TEXT: int = 14
|
||||
@@ -192,14 +197,21 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
f"{len(symbols):>{self.COL_CORE_COUNT}} | {percentage:>{self.COL_CORE_PERCENT - 1}.1f}%"
|
||||
)
|
||||
|
||||
# Top 15 largest core symbols
|
||||
# All core symbols above threshold
|
||||
lines.append("")
|
||||
lines.append(f"Top 15 Largest {_COMPONENT_CORE} Symbols:")
|
||||
sorted_core_symbols = sorted(
|
||||
self._esphome_core_symbols, key=lambda x: x[2], reverse=True
|
||||
)
|
||||
large_core_symbols = [
|
||||
(symbol, demangled, size)
|
||||
for symbol, demangled, size in sorted_core_symbols
|
||||
if size > self.SYMBOL_SIZE_THRESHOLD
|
||||
]
|
||||
|
||||
for i, (symbol, demangled, size) in enumerate(sorted_core_symbols[:15]):
|
||||
lines.append(
|
||||
f"{_COMPONENT_CORE} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_core_symbols)} symbols):"
|
||||
)
|
||||
for i, (symbol, demangled, size) in enumerate(large_core_symbols):
|
||||
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
|
||||
|
||||
lines.append("=" * self.TABLE_WIDTH)
|
||||
@@ -269,13 +281,15 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
||||
lines.append(f"Total size: {comp_mem.flash_total:,} B")
|
||||
lines.append("")
|
||||
|
||||
# Show all symbols > 100 bytes for better visibility
|
||||
# Show all symbols above threshold for better visibility
|
||||
large_symbols = [
|
||||
(sym, dem, size) for sym, dem, size in sorted_symbols if size > 100
|
||||
(sym, dem, size)
|
||||
for sym, dem, size in sorted_symbols
|
||||
if size > self.SYMBOL_SIZE_THRESHOLD
|
||||
]
|
||||
|
||||
lines.append(
|
||||
f"{comp_name} Symbols > 100 B ({len(large_symbols)} symbols):"
|
||||
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):"
|
||||
)
|
||||
for i, (symbol, demangled, size) in enumerate(large_symbols):
|
||||
lines.append(f"{i + 1}. {demangled} ({size:,} B)")
|
||||
|
||||
@@ -476,8 +476,9 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
||||
auto *light = static_cast<light::LightState *>(entity);
|
||||
ListEntitiesLightResponse msg;
|
||||
auto traits = light->get_traits();
|
||||
auto supported_modes = traits.get_supported_color_modes();
|
||||
// Pass pointer to ColorModeMask so the iterator can encode actual ColorMode enum values
|
||||
msg.supported_color_modes = &traits.get_supported_color_modes();
|
||||
msg.supported_color_modes = &supported_modes;
|
||||
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
|
||||
msg.min_mireds = traits.get_min_mireds();
|
||||
@@ -1295,8 +1296,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, const char *event_type) {
|
||||
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||
EventResponse::ESTIMATED_SIZE);
|
||||
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||
EventResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single) {
|
||||
@@ -1450,8 +1451,11 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
#ifdef USE_AREAS
|
||||
resp.set_suggested_area(StringRef(App.get_area()));
|
||||
#endif
|
||||
// mac_address must store temporary string - will be valid during send_message call
|
||||
std::string mac_address = get_mac_address_pretty();
|
||||
// Stack buffer for MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
|
||||
char mac_address[18];
|
||||
uint8_t mac[6];
|
||||
get_mac_address_raw(mac);
|
||||
format_mac_addr_upper(mac, mac_address);
|
||||
resp.set_mac_address(StringRef(mac_address));
|
||||
|
||||
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||
@@ -1492,8 +1496,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
|
||||
// bt_mac must store temporary string - will be valid during send_message call
|
||||
std::string bluetooth_mac = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
|
||||
// Stack buffer for Bluetooth MAC address (XX:XX:XX:XX:XX:XX\0 = 18 bytes)
|
||||
char bluetooth_mac[18];
|
||||
bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty(bluetooth_mac);
|
||||
resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
|
||||
@@ -650,21 +650,30 @@ class APIConnection final : public APIServerConnection {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Helper to check if a message type should bypass batching
|
||||
// Returns true if:
|
||||
// 1. It's an UpdateStateResponse (always send immediately to handle cases where
|
||||
// the main loop is blocked, e.g., during OTA updates)
|
||||
// 2. It's an EventResponse (events are edge-triggered - every occurrence matters)
|
||||
// 3. OR: User has opted into immediate sending (should_try_send_immediately = true
|
||||
// AND batch_delay = 0)
|
||||
inline bool should_send_immediately_(uint8_t message_type) const {
|
||||
return (
|
||||
#ifdef USE_UPDATE
|
||||
message_type == UpdateStateResponse::MESSAGE_TYPE ||
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
message_type == EventResponse::MESSAGE_TYPE ||
|
||||
#endif
|
||||
(this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0));
|
||||
}
|
||||
|
||||
// Helper method to send a message either immediately or via batching
|
||||
// Tries immediate send if should_send_immediately_() returns true and buffer has space
|
||||
// Falls back to batching if immediate send fails or isn't applicable
|
||||
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
|
||||
uint8_t estimated_size) {
|
||||
// Try to send immediately if:
|
||||
// 1. It's an UpdateStateResponse (always send immediately to handle cases where
|
||||
// the main loop is blocked, e.g., during OTA updates)
|
||||
// 2. OR: We should try to send immediately (should_try_send_immediately = true)
|
||||
// AND Batch delay is 0 (user has opted in to immediate sending)
|
||||
// 3. AND: Buffer has space available
|
||||
if ((
|
||||
#ifdef USE_UPDATE
|
||||
message_type == UpdateStateResponse::MESSAGE_TYPE ||
|
||||
#endif
|
||||
(this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)) &&
|
||||
this->helper_->can_write_without_blocking()) {
|
||||
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
|
||||
// Now actually encode and send
|
||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
||||
@@ -682,6 +691,27 @@ class APIConnection final : public APIServerConnection {
|
||||
return this->schedule_message_(entity, creator, message_type, estimated_size);
|
||||
}
|
||||
|
||||
// Overload for MessageCreator (used by events which need to capture event_type)
|
||||
bool send_message_smart_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
|
||||
// Try to send immediately if message type should bypass batching and buffer has space
|
||||
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
|
||||
// Now actually encode and send
|
||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type) &&
|
||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Log the message in verbose mode
|
||||
this->log_proto_message_(entity, creator, message_type);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// If immediate send failed, fall through to batching
|
||||
}
|
||||
|
||||
// Fall back to scheduled batching
|
||||
return this->schedule_message_(entity, std::move(creator), message_type, estimated_size);
|
||||
}
|
||||
|
||||
// Helper function to schedule a deferred message with known message type
|
||||
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size);
|
||||
|
||||
@@ -51,13 +51,14 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
||||
return false;
|
||||
if (req.args.size() != sizeof...(Ts))
|
||||
return false;
|
||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||
this->execute_(req.args, std::make_index_sequence<sizeof...(Ts)>{});
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void execute(Ts... x) = 0;
|
||||
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
|
||||
template<typename ArgsContainer, size_t... S>
|
||||
void execute_(const ArgsContainer &args, std::index_sequence<S...> type) {
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
@@ -95,13 +96,14 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
|
||||
return false;
|
||||
if (req.args.size() != sizeof...(Ts))
|
||||
return false;
|
||||
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
|
||||
this->execute_(req.args, std::make_index_sequence<sizeof...(Ts)>{});
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void execute(Ts... x) = 0;
|
||||
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
|
||||
template<typename ArgsContainer, size_t... S>
|
||||
void execute_(const ArgsContainer &args, std::index_sequence<S...> type) {
|
||||
this->execute((get_execute_arg_value<Ts>(args[S]))...);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "bh1750.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
namespace esphome::bh1750 {
|
||||
|
||||
static const char *const TAG = "bh1750.sensor";
|
||||
|
||||
@@ -13,6 +13,31 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
|
||||
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
|
||||
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
|
||||
|
||||
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
|
||||
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
|
||||
|
||||
// Measurement time constants (datasheet values)
|
||||
static constexpr uint16_t MTREG_DEFAULT = 69;
|
||||
static constexpr uint16_t MTREG_MIN = 31;
|
||||
static constexpr uint16_t MTREG_MAX = 254;
|
||||
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
|
||||
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
|
||||
|
||||
// Conversion constants (datasheet formulas)
|
||||
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
|
||||
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
|
||||
|
||||
// MTreg calculation constants
|
||||
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
|
||||
static constexpr int COUNTS_NUMERATOR = 10;
|
||||
static constexpr int COUNTS_DENOMINATOR = 12;
|
||||
|
||||
// MTreg register bit manipulation constants
|
||||
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
|
||||
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
|
||||
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
|
||||
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
|
||||
|
||||
/*
|
||||
bh1750 properties:
|
||||
|
||||
@@ -43,74 +68,7 @@ void BH1750Sensor::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
|
||||
// turn on (after one-shot sensor automatically powers down)
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Power on failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_mtreg_ != mtreg) {
|
||||
// set mtreg
|
||||
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
|
||||
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
|
||||
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Set measurement time failed");
|
||||
active_mtreg_ = 0;
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
active_mtreg_ = mtreg;
|
||||
}
|
||||
|
||||
uint8_t cmd;
|
||||
uint16_t meas_time;
|
||||
switch (mode) {
|
||||
case BH1750_MODE_L:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_L;
|
||||
meas_time = 24 * mtreg / 69;
|
||||
break;
|
||||
case BH1750_MODE_H:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H;
|
||||
meas_time = 180 * mtreg / 69;
|
||||
break;
|
||||
case BH1750_MODE_H2:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H2;
|
||||
meas_time = 180 * mtreg / 69;
|
||||
break;
|
||||
default:
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Start measurement failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
// probably not needed, but adjust for rounding
|
||||
meas_time++;
|
||||
|
||||
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read data failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
lx *= 69.0f / mtreg;
|
||||
if (mode == BH1750_MODE_H2)
|
||||
lx /= 2.0f;
|
||||
|
||||
f(lx);
|
||||
});
|
||||
this->state_ = IDLE;
|
||||
}
|
||||
|
||||
void BH1750Sensor::dump_config() {
|
||||
@@ -124,45 +82,189 @@ void BH1750Sensor::dump_config() {
|
||||
}
|
||||
|
||||
void BH1750Sensor::update() {
|
||||
// first do a quick measurement in L-mode with full range
|
||||
// to find right range
|
||||
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Start coarse measurement to determine optimal mode/mtreg
|
||||
if (this->state_ != IDLE) {
|
||||
// Safety timeout: reset if stuck
|
||||
if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
|
||||
ESP_LOGW(TAG, "Measurement timeout, resetting state");
|
||||
this->state_ = IDLE;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BH1750Mode use_mode;
|
||||
uint8_t use_mtreg;
|
||||
if (val <= 7000) {
|
||||
use_mode = BH1750_MODE_H2;
|
||||
use_mtreg = 254;
|
||||
} else {
|
||||
use_mode = BH1750_MODE_H;
|
||||
// lx = counts / 1.2 * (69 / mtreg)
|
||||
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
|
||||
// -> mtreg = 50000*(10/12)*(69/lx)
|
||||
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
|
||||
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
|
||||
}
|
||||
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
|
||||
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
this->read_lx_(use_mode, use_mtreg, [this](float val) {
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
this->state_ = WAITING_COARSE_MEASUREMENT;
|
||||
this->enable_loop(); // Enable loop while measurement in progress
|
||||
}
|
||||
|
||||
void BH1750Sensor::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
switch (this->state_) {
|
||||
case IDLE:
|
||||
// Disable loop when idle to save cycles
|
||||
this->disable_loop();
|
||||
break;
|
||||
|
||||
case WAITING_COARSE_MEASUREMENT:
|
||||
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
|
||||
this->state_ = READING_COARSE_RESULT;
|
||||
}
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
|
||||
break;
|
||||
|
||||
case READING_COARSE_RESULT: {
|
||||
float lx;
|
||||
if (!this->read_measurement_(lx)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
this->process_coarse_result_(lx);
|
||||
|
||||
// Start fine measurement with optimal settings
|
||||
// fetch millis() again since the read can take a bit
|
||||
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
this->state_ = WAITING_FINE_MEASUREMENT;
|
||||
break;
|
||||
}
|
||||
|
||||
case WAITING_FINE_MEASUREMENT:
|
||||
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
|
||||
this->state_ = READING_FINE_RESULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case READING_FINE_RESULT: {
|
||||
float lx;
|
||||
if (!this->read_measurement_(lx)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
this->status_clear_warning();
|
||||
this->publish_state(val);
|
||||
});
|
||||
});
|
||||
this->publish_state(lx);
|
||||
this->state_ = IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
|
||||
// Power on
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Power on failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set MTreg if changed
|
||||
if (this->active_mtreg_ != mtreg) {
|
||||
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
|
||||
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
|
||||
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Set measurement time failed");
|
||||
this->active_mtreg_ = 0;
|
||||
return false;
|
||||
}
|
||||
this->active_mtreg_ = mtreg;
|
||||
}
|
||||
|
||||
// Start measurement
|
||||
uint8_t cmd;
|
||||
uint16_t meas_time;
|
||||
switch (mode) {
|
||||
case BH1750_MODE_L:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_L;
|
||||
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
case BH1750_MODE_H:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H;
|
||||
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
case BH1750_MODE_H2:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H2;
|
||||
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Start measurement failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store current measurement parameters
|
||||
this->current_mode_ = mode;
|
||||
this->current_mtreg_ = mtreg;
|
||||
this->measurement_start_time_ = now;
|
||||
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BH1750Sensor::read_measurement_(float &lx_out) {
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read data failed");
|
||||
return false;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / RESOLUTION_DIVISOR;
|
||||
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
|
||||
if (this->current_mode_ == BH1750_MODE_H2) {
|
||||
lx /= MODE_H2_DIVISOR;
|
||||
}
|
||||
|
||||
lx_out = lx;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BH1750Sensor::process_coarse_result_(float lx) {
|
||||
if (std::isnan(lx)) {
|
||||
// Use defaults if coarse measurement failed
|
||||
this->fine_mode_ = BH1750_MODE_H2;
|
||||
this->fine_mtreg_ = MTREG_MAX;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
|
||||
this->fine_mode_ = BH1750_MODE_H2;
|
||||
this->fine_mtreg_ = MTREG_MAX;
|
||||
} else {
|
||||
this->fine_mode_ = BH1750_MODE_H;
|
||||
// lx = counts / 1.2 * (69 / mtreg)
|
||||
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
|
||||
// -> mtreg = 50000*(10/12)*(69/lx)
|
||||
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
|
||||
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
|
||||
}
|
||||
|
||||
void BH1750Sensor::fail_and_reset_() {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
this->state_ = IDLE;
|
||||
}
|
||||
|
||||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bh1750
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
namespace esphome::bh1750 {
|
||||
|
||||
enum BH1750Mode {
|
||||
enum BH1750Mode : uint8_t {
|
||||
BH1750_MODE_L,
|
||||
BH1750_MODE_H,
|
||||
BH1750_MODE_H2,
|
||||
@@ -21,13 +20,36 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f);
|
||||
// State machine states
|
||||
enum State : uint8_t {
|
||||
IDLE,
|
||||
WAITING_COARSE_MEASUREMENT,
|
||||
READING_COARSE_RESULT,
|
||||
WAITING_FINE_MEASUREMENT,
|
||||
READING_FINE_RESULT,
|
||||
};
|
||||
|
||||
// 4-byte aligned members
|
||||
uint32_t measurement_start_time_{0};
|
||||
uint32_t measurement_duration_{0};
|
||||
|
||||
// 1-byte members grouped together to minimize padding
|
||||
State state_{IDLE};
|
||||
BH1750Mode current_mode_{BH1750_MODE_L};
|
||||
uint8_t current_mtreg_{31};
|
||||
BH1750Mode fine_mode_{BH1750_MODE_H2};
|
||||
uint8_t fine_mtreg_{254};
|
||||
uint8_t active_mtreg_{0};
|
||||
|
||||
// Helper methods
|
||||
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
|
||||
bool read_measurement_(float &lx_out);
|
||||
void process_coarse_result_(float lx);
|
||||
void fail_and_reset_();
|
||||
};
|
||||
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bh1750
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#include "automation.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary_sensor {
|
||||
namespace esphome::binary_sensor {
|
||||
|
||||
static const char *const TAG = "binary_sensor.automation";
|
||||
|
||||
void binary_sensor::MultiClickTrigger::on_state_(bool state) {
|
||||
void MultiClickTrigger::on_state_(bool state) {
|
||||
// Handle duplicate events
|
||||
if (state == this->last_state_) {
|
||||
return;
|
||||
@@ -67,7 +66,7 @@ void binary_sensor::MultiClickTrigger::on_state_(bool state) {
|
||||
|
||||
*this->at_index_ = *this->at_index_ + 1;
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
|
||||
void MultiClickTrigger::schedule_cooldown_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
|
||||
this->is_in_cooldown_ = true;
|
||||
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
|
||||
@@ -79,7 +78,7 @@ void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
|
||||
this->cancel_timeout("is_valid");
|
||||
this->cancel_timeout("is_not_valid");
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
if (min_length == 0) {
|
||||
this->is_valid_ = true;
|
||||
return;
|
||||
@@ -90,19 +89,19 @@ void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
this->is_valid_ = true;
|
||||
});
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||
this->set_timeout("is_not_valid", max_length, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
});
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::cancel() {
|
||||
void MultiClickTrigger::cancel() {
|
||||
ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
}
|
||||
void binary_sensor::MultiClickTrigger::trigger_() {
|
||||
void MultiClickTrigger::trigger_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
|
||||
this->at_index_.reset();
|
||||
this->cancel_timeout("trigger");
|
||||
@@ -118,5 +117,4 @@ bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) {
|
||||
return length >= min_length && length <= max_length;
|
||||
}
|
||||
}
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace binary_sensor {
|
||||
namespace esphome::binary_sensor {
|
||||
|
||||
struct MultiClickTriggerEvent {
|
||||
bool state;
|
||||
@@ -172,5 +171,4 @@ template<typename... Ts> class BinarySensorInvalidateAction : public Action<Ts..
|
||||
BinarySensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
namespace esphome::binary_sensor {
|
||||
|
||||
static const char *const TAG = "binary_sensor";
|
||||
|
||||
@@ -63,6 +61,4 @@ void BinarySensor::add_filters(std::initializer_list<Filter *> filters) {
|
||||
}
|
||||
bool BinarySensor::is_status_binary_sensor() const { return false; }
|
||||
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
namespace esphome::binary_sensor {
|
||||
|
||||
class BinarySensor;
|
||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
|
||||
@@ -70,5 +68,4 @@ class BinarySensorInitiallyOff : public BinarySensor {
|
||||
bool has_state() const override { return true; }
|
||||
};
|
||||
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
#include "binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
namespace esphome::binary_sensor {
|
||||
|
||||
static const char *const TAG = "sensor.filter";
|
||||
|
||||
@@ -132,6 +130,4 @@ optional<bool> SettleFilter::new_value(bool value) {
|
||||
|
||||
float SettleFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
namespace esphome::binary_sensor {
|
||||
|
||||
class BinarySensor;
|
||||
|
||||
@@ -139,6 +137,4 @@ class SettleFilter : public Filter, public Component {
|
||||
bool steady_{true};
|
||||
};
|
||||
|
||||
} // namespace binary_sensor
|
||||
|
||||
} // namespace esphome
|
||||
} // namespace esphome::binary_sensor
|
||||
|
||||
@@ -130,11 +130,9 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
|
||||
return flags;
|
||||
}
|
||||
|
||||
std::string get_bluetooth_mac_address_pretty() {
|
||||
void get_bluetooth_mac_address_pretty(std::span<char, 18> output) {
|
||||
const uint8_t *mac = esp_bt_dev_get_address();
|
||||
char buf[18];
|
||||
format_mac_addr_upper(mac, buf);
|
||||
return std::string(buf);
|
||||
format_mac_addr_upper(mac, output.data());
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import web_server_base
|
||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AP,
|
||||
CONF_ID,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
@@ -14,6 +17,10 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def AUTO_LOAD() -> list[str]:
|
||||
@@ -50,6 +57,32 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> ConfigType:
|
||||
full_config = fv.full_config.get()
|
||||
wifi_conf = full_config.get("wifi")
|
||||
|
||||
if wifi_conf is None:
|
||||
# This shouldn't happen due to DEPENDENCIES = ["wifi"], but check anyway
|
||||
raise cv.Invalid("Captive portal requires the wifi component to be configured")
|
||||
|
||||
if CONF_AP not in wifi_conf:
|
||||
_LOGGER.warning(
|
||||
"Captive portal is enabled but no WiFi AP is configured. "
|
||||
"The captive portal will not be accessible. "
|
||||
"Add 'ap:' to your WiFi configuration to enable the captive portal."
|
||||
)
|
||||
|
||||
# Register socket needs for DNS server (1 UDP socket)
|
||||
from esphome.components import socket
|
||||
|
||||
socket.consume_sockets(1, "captive_portal")(config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL)
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||
|
||||
@@ -13,14 +13,16 @@ static const char *const TAG = "captive_portal";
|
||||
void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json"));
|
||||
stream->addHeader(ESPHOME_F("cache-control"), ESPHOME_F("public, max-age=0, must-revalidate"));
|
||||
char mac_s[18];
|
||||
const char *mac_str = get_mac_address_pretty_into_buffer(mac_s);
|
||||
#ifdef USE_ESP8266
|
||||
stream->print(ESPHOME_F("{\"mac\":\""));
|
||||
stream->print(get_mac_address_pretty().c_str());
|
||||
stream->print(mac_str);
|
||||
stream->print(ESPHOME_F("\",\"name\":\""));
|
||||
stream->print(App.get_name().c_str());
|
||||
stream->print(ESPHOME_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":[{})", mac_str, App.get_name().c_str());
|
||||
#endif
|
||||
|
||||
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "cover.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
namespace esphome::cover {
|
||||
|
||||
template<typename... Ts> class OpenAction : public Action<Ts...> {
|
||||
public:
|
||||
@@ -131,5 +130,4 @@ class CoverClosedTrigger : public Trigger<> {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
} // namespace esphome::cover
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
namespace esphome::cover {
|
||||
|
||||
static const char *const TAG = "cover";
|
||||
|
||||
@@ -212,5 +211,4 @@ void CoverRestoreState::apply(Cover *cover) {
|
||||
cover->publish_state();
|
||||
}
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
} // namespace esphome::cover
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
#include "cover_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
namespace esphome::cover {
|
||||
|
||||
const extern float COVER_OPEN;
|
||||
const extern float COVER_CLOSED;
|
||||
@@ -157,5 +156,4 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
} // namespace esphome::cover
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace cover {
|
||||
namespace esphome::cover {
|
||||
|
||||
class CoverTraits {
|
||||
public:
|
||||
@@ -26,5 +25,4 @@ class CoverTraits {
|
||||
bool supports_stop_{false};
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
} // namespace esphome
|
||||
} // namespace esphome::cover
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
namespace esphome {
|
||||
namespace dashboard_import {
|
||||
|
||||
static std::string g_package_import_url; // NOLINT
|
||||
static const char *g_package_import_url = ""; // NOLINT
|
||||
|
||||
const std::string &get_package_import_url() { return g_package_import_url; }
|
||||
void set_package_import_url(std::string url) { g_package_import_url = std::move(url); }
|
||||
const char *get_package_import_url() { return g_package_import_url; }
|
||||
void set_package_import_url(const char *url) { g_package_import_url = url; }
|
||||
|
||||
} // namespace dashboard_import
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace dashboard_import {
|
||||
|
||||
const std::string &get_package_import_url();
|
||||
void set_package_import_url(std::string url);
|
||||
const char *get_package_import_url();
|
||||
void set_package_import_url(const char *url);
|
||||
|
||||
} // namespace dashboard_import
|
||||
} // namespace esphome
|
||||
|
||||
@@ -49,9 +49,9 @@ void DebugComponent::dump_config() {
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
#ifdef USE_ESP32
|
||||
this->log_partition_info_(); // Log partition information for ESP32
|
||||
#endif // USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
|
||||
this->log_partition_info_(); // Log partition information
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugComponent::loop() {
|
||||
|
||||
@@ -62,19 +62,19 @@ class DebugComponent : public PollingComponent {
|
||||
sensor::Sensor *cpu_frequency_sensor_{nullptr};
|
||||
#endif // USE_SENSOR
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) || defined(USE_ZEPHYR)
|
||||
/**
|
||||
* @brief Logs information about the device's partition table.
|
||||
*
|
||||
* This function iterates through the ESP32's partition table and logs details
|
||||
* This function iterates through the partition table and logs details
|
||||
* about each partition, including its name, type, subtype, starting address,
|
||||
* and size. The information is useful for diagnosing issues related to flash
|
||||
* memory or verifying the partition configuration dynamically at runtime.
|
||||
*
|
||||
* Only available when compiled for ESP32 platforms.
|
||||
* Only available when compiled for ESP32 and ZEPHYR platforms.
|
||||
*/
|
||||
void log_partition_info_();
|
||||
#endif // USE_ESP32
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *device_info_{nullptr};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <zephyr/drivers/hwinfo.h>
|
||||
#include <hal/nrf_power.h>
|
||||
#include <cstdint>
|
||||
#include <zephyr/storage/flash_map.h>
|
||||
|
||||
#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0]
|
||||
|
||||
@@ -86,6 +87,37 @@ std::string DebugComponent::get_reset_reason_() {
|
||||
|
||||
uint32_t DebugComponent::get_free_heap_() { return INT_MAX; }
|
||||
|
||||
static void fa_cb(const struct flash_area *fa, void *user_data) {
|
||||
#if CONFIG_FLASH_MAP_LABELS
|
||||
const char *fa_label = flash_area_label(fa);
|
||||
|
||||
if (fa_label == nullptr) {
|
||||
fa_label = "-";
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "%2d 0x%0*" PRIxPTR " %-26s %-24.24s 0x%-10x 0x%-12x", (int) fa->fa_id,
|
||||
sizeof(uintptr_t) * 2, (uintptr_t) fa->fa_dev, fa->fa_dev->name, fa_label, (uint32_t) fa->fa_off,
|
||||
fa->fa_size);
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, "%2d 0x%0*" PRIxPTR " %-26s 0x%-10x 0x%-12x", (int) fa->fa_id, sizeof(uintptr_t) * 2,
|
||||
(uintptr_t) fa->fa_dev, fa->fa_dev->name, (uint32_t) fa->fa_off, fa->fa_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugComponent::log_partition_info_() {
|
||||
#if CONFIG_FLASH_MAP_LABELS
|
||||
ESP_LOGCONFIG(TAG, "ID | Device | Device Name "
|
||||
"| Label | Offset | Size");
|
||||
ESP_LOGCONFIG(TAG, "--------------------------------------------"
|
||||
"-----------------------------------------------");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, "ID | Device | Device Name "
|
||||
"| Offset | Size");
|
||||
ESP_LOGCONFIG(TAG, "-----------------------------------------"
|
||||
"------------------------------");
|
||||
#endif
|
||||
flash_area_foreach(fa_cb, nullptr);
|
||||
}
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
std::string supply = "Main supply status: ";
|
||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) {
|
||||
|
||||
@@ -23,7 +23,7 @@ void DS1307Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float DS1307Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -334,12 +334,14 @@ def _is_framework_url(source: str) -> str:
|
||||
# - https://github.com/espressif/arduino-esp32/releases
|
||||
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
|
||||
"recommended": cv.Version(3, 3, 2),
|
||||
"latest": cv.Version(3, 3, 2),
|
||||
"dev": cv.Version(3, 3, 2),
|
||||
"latest": cv.Version(3, 3, 4),
|
||||
"dev": cv.Version(3, 3, 4),
|
||||
}
|
||||
ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
||||
cv.Version(3, 3, 2): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(3, 3, 1): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(3, 3, 4): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(3, 3, 3): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(3, 3, 2): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(3, 3, 1): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(3, 3, 0): cv.Version(55, 3, 30, "2"),
|
||||
cv.Version(3, 2, 1): cv.Version(54, 3, 21, "2"),
|
||||
cv.Version(3, 2, 0): cv.Version(54, 3, 20),
|
||||
@@ -357,8 +359,8 @@ ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
||||
"dev": cv.Version(5, 5, 1),
|
||||
}
|
||||
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "1"),
|
||||
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(5, 4, 3): cv.Version(55, 3, 32),
|
||||
cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"),
|
||||
cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"),
|
||||
@@ -373,14 +375,15 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||
# The platform-espressif32 version
|
||||
# - https://github.com/pioarduino/platform-espressif32/releases
|
||||
PLATFORM_VERSION_LOOKUP = {
|
||||
"recommended": cv.Version(55, 3, 31, "1"),
|
||||
"latest": cv.Version(55, 3, 31, "1"),
|
||||
"dev": cv.Version(55, 3, 31, "1"),
|
||||
"recommended": cv.Version(55, 3, 31, "2"),
|
||||
"latest": cv.Version(55, 3, 31, "2"),
|
||||
"dev": cv.Version(55, 3, 31, "2"),
|
||||
}
|
||||
|
||||
|
||||
def _check_versions(value):
|
||||
value = value.copy()
|
||||
def _check_versions(config):
|
||||
config = config.copy()
|
||||
value = config[CONF_FRAMEWORK]
|
||||
|
||||
if value[CONF_VERSION] in PLATFORM_VERSION_LOOKUP:
|
||||
if CONF_SOURCE in value or CONF_PLATFORM_VERSION in value:
|
||||
@@ -445,7 +448,7 @@ def _check_versions(value):
|
||||
"If there are connectivity or build issues please remove the manual version."
|
||||
)
|
||||
|
||||
return value
|
||||
return config
|
||||
|
||||
|
||||
def _parse_platform_version(value):
|
||||
@@ -495,6 +498,8 @@ def final_validate(config):
|
||||
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
|
||||
errs = []
|
||||
conf_fw = config[CONF_FRAMEWORK]
|
||||
advanced = conf_fw[CONF_ADVANCED]
|
||||
full_config = fv.full_config.get()
|
||||
if pio_options := full_config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS):
|
||||
pio_flash_size_key = "board_upload.flash_size"
|
||||
@@ -511,22 +516,14 @@ def final_validate(config):
|
||||
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
||||
)
|
||||
)
|
||||
if (
|
||||
config[CONF_VARIANT] != VARIANT_ESP32
|
||||
and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK])
|
||||
and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED]
|
||||
):
|
||||
if config[CONF_VARIANT] != VARIANT_ESP32 and advanced[CONF_IGNORE_EFUSE_MAC_CRC]:
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"'{CONF_IGNORE_EFUSE_MAC_CRC}' is not supported on {config[CONF_VARIANT]}",
|
||||
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC],
|
||||
)
|
||||
)
|
||||
if (
|
||||
config.get(CONF_FRAMEWORK, {})
|
||||
.get(CONF_ADVANCED, {})
|
||||
.get(CONF_EXECUTE_FROM_PSRAM)
|
||||
):
|
||||
if advanced[CONF_EXECUTE_FROM_PSRAM]:
|
||||
if config[CONF_VARIANT] != VARIANT_ESP32S3:
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
@@ -542,6 +539,17 @@ def final_validate(config):
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
config[CONF_FLASH_SIZE] == "32MB"
|
||||
and "ota" in full_config
|
||||
and not advanced[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES]
|
||||
):
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"OTA with 32MB flash requires '{CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES}' to be set in the '{CONF_ADVANCED}' section of the esp32 configuration",
|
||||
path=[CONF_FLASH_SIZE],
|
||||
)
|
||||
)
|
||||
if errs:
|
||||
raise cv.MultipleInvalid(errs)
|
||||
|
||||
@@ -596,89 +604,74 @@ def _validate_idf_component(config: ConfigType) -> ConfigType:
|
||||
|
||||
FRAMEWORK_ESP_IDF = "esp-idf"
|
||||
FRAMEWORK_ARDUINO = "arduino"
|
||||
FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TYPE, default=FRAMEWORK_ARDUINO): cv.one_of(
|
||||
FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO
|
||||
),
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_RELEASE): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
||||
cv.string_strict: cv.string_strict
|
||||
},
|
||||
cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of(
|
||||
*LOG_LEVELS_IDF, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of(
|
||||
*ASSERTION_LEVELS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of(
|
||||
*COMPILER_OPTIMIZATIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean,
|
||||
# DHCP server is needed for WiFi AP mode. When WiFi component is used,
|
||||
# it will handle disabling DHCP server when AP is not configured.
|
||||
# Default to false (disabled) when WiFi is not used.
|
||||
cv.OnlyWithout(
|
||||
CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_MDNS_QUERIES, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_DISABLE_VFS_SUPPORT_SELECT, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean,
|
||||
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
|
||||
min=8192, max=32768
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.git_ref,
|
||||
cv.Optional(CONF_REF): cv.string,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
),
|
||||
_validate_idf_component,
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
_check_versions,
|
||||
FRAMEWORK_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TYPE): cv.one_of(FRAMEWORK_ESP_IDF, FRAMEWORK_ARDUINO),
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_RELEASE): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
||||
cv.string_strict: cv.string_strict
|
||||
},
|
||||
cv.Optional(CONF_LOG_LEVEL, default="ERROR"): cv.one_of(
|
||||
*LOG_LEVELS_IDF, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ASSERTION_LEVEL): cv.one_of(
|
||||
*ASSERTION_LEVELS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_COMPILER_OPTIMIZATION, default="SIZE"): cv.one_of(
|
||||
*COMPILER_OPTIMIZATIONS, upper=True
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_LWIP_ASSERT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
||||
# DHCP server is needed for WiFi AP mode. When WiFi component is used,
|
||||
# it will handle disabling DHCP server when AP is not configured.
|
||||
# Default to false (disabled) when WiFi is not used.
|
||||
cv.OnlyWithout(
|
||||
CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_LWIP_MDNS_QUERIES, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_TERMIOS, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_SELECT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_DISABLE_VFS_SUPPORT_DIR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_EXECUTE_FROM_PSRAM, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
|
||||
min=8192, max=32768
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.git_ref,
|
||||
cv.Optional(CONF_REF): cv.string,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH): cv.All(cv.string, cv.source_refresh),
|
||||
}
|
||||
),
|
||||
_validate_idf_component,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -741,11 +734,11 @@ def _show_framework_migration_message(name: str, variant: str) -> None:
|
||||
|
||||
|
||||
def _set_default_framework(config):
|
||||
config = config.copy()
|
||||
if CONF_FRAMEWORK not in config:
|
||||
config = config.copy()
|
||||
|
||||
variant = config[CONF_VARIANT]
|
||||
config[CONF_FRAMEWORK] = FRAMEWORK_SCHEMA({})
|
||||
if CONF_TYPE not in config[CONF_FRAMEWORK]:
|
||||
variant = config[CONF_VARIANT]
|
||||
if variant in ARDUINO_ALLOWED_VARIANTS:
|
||||
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
|
||||
_show_framework_migration_message(
|
||||
@@ -785,6 +778,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
_detect_variant,
|
||||
_set_default_framework,
|
||||
_check_versions,
|
||||
set_core_data,
|
||||
cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT),
|
||||
)
|
||||
@@ -803,9 +797,7 @@ def _configure_lwip_max_sockets(conf: dict) -> None:
|
||||
from esphome.components.socket import KEY_SOCKET_CONSUMERS
|
||||
|
||||
# Check if user manually specified CONFIG_LWIP_MAX_SOCKETS
|
||||
user_max_sockets = conf.get(CONF_SDKCONFIG_OPTIONS, {}).get(
|
||||
"CONFIG_LWIP_MAX_SOCKETS"
|
||||
)
|
||||
user_max_sockets = conf[CONF_SDKCONFIG_OPTIONS].get("CONFIG_LWIP_MAX_SOCKETS")
|
||||
|
||||
socket_consumers: dict[str, int] = CORE.data.get(KEY_SOCKET_CONSUMERS, {})
|
||||
total_sockets = sum(socket_consumers.values())
|
||||
@@ -975,23 +967,18 @@ async def to_code(config):
|
||||
# WiFi component handles its own optimization when AP mode is not used
|
||||
# When using Arduino with Ethernet, DHCP server functions must be available
|
||||
# for the Network library to compile, even if not actively used
|
||||
if (
|
||||
CONF_ENABLE_LWIP_DHCP_SERVER in advanced
|
||||
and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER]
|
||||
and not (
|
||||
conf[CONF_TYPE] == FRAMEWORK_ARDUINO
|
||||
and "ethernet" in CORE.loaded_integrations
|
||||
)
|
||||
if advanced.get(CONF_ENABLE_LWIP_DHCP_SERVER) is False and not (
|
||||
conf[CONF_TYPE] == FRAMEWORK_ARDUINO and "ethernet" in CORE.loaded_integrations
|
||||
):
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
|
||||
if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True):
|
||||
if not advanced[CONF_ENABLE_LWIP_MDNS_QUERIES]:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
|
||||
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
|
||||
if not advanced[CONF_ENABLE_LWIP_BRIDGE_INTERFACE]:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
|
||||
|
||||
_configure_lwip_max_sockets(conf)
|
||||
|
||||
if advanced.get(CONF_EXECUTE_FROM_PSRAM, False):
|
||||
if advanced[CONF_EXECUTE_FROM_PSRAM]:
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True)
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True)
|
||||
|
||||
@@ -1002,23 +989,22 @@ async def to_code(config):
|
||||
# - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default)
|
||||
# - Up to 200% slower under load when all operations queue through tcpip_thread
|
||||
# Enabling this makes ESP-IDF socket performance match Arduino framework.
|
||||
if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True):
|
||||
if advanced[CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING]:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True)
|
||||
if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True):
|
||||
if advanced[CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY]:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True)
|
||||
|
||||
# Disable placing libc locks in IRAM to save RAM
|
||||
# This is safe for ESPHome since no IRAM ISRs (interrupts that run while cache is disabled)
|
||||
# use libc lock APIs. Saves approximately 1.3KB (1,356 bytes) of IRAM.
|
||||
if advanced.get(CONF_DISABLE_LIBC_LOCKS_IN_IRAM, True):
|
||||
if advanced[CONF_DISABLE_LIBC_LOCKS_IN_IRAM]:
|
||||
add_idf_sdkconfig_option("CONFIG_LIBC_LOCKS_PLACE_IN_IRAM", False)
|
||||
|
||||
# Disable VFS support for termios (terminal I/O functions)
|
||||
# ESPHome doesn't use termios functions on ESP32 (only used in host UART driver).
|
||||
# Saves approximately 1.8KB of flash when disabled (default).
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_TERMIOS, True),
|
||||
"CONFIG_VFS_SUPPORT_TERMIOS", not advanced[CONF_DISABLE_VFS_SUPPORT_TERMIOS]
|
||||
)
|
||||
|
||||
# Disable VFS support for select() with file descriptors
|
||||
@@ -1032,8 +1018,7 @@ async def to_code(config):
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_SELECT",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_SELECT, True),
|
||||
"CONFIG_VFS_SUPPORT_SELECT", not advanced[CONF_DISABLE_VFS_SUPPORT_SELECT]
|
||||
)
|
||||
|
||||
# Disable VFS support for directory functions (opendir, readdir, mkdir, etc.)
|
||||
@@ -1046,8 +1031,7 @@ async def to_code(config):
|
||||
else:
|
||||
# No component needs it - allow user to control (default: disabled)
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_VFS_SUPPORT_DIR",
|
||||
not advanced.get(CONF_DISABLE_VFS_SUPPORT_DIR, True),
|
||||
"CONFIG_VFS_SUPPORT_DIR", not advanced[CONF_DISABLE_VFS_SUPPORT_DIR]
|
||||
)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
@@ -1061,7 +1045,7 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option(flag, assertion_level == key)
|
||||
|
||||
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
|
||||
compiler_optimization = advanced.get(CONF_COMPILER_OPTIMIZATION)
|
||||
compiler_optimization = advanced[CONF_COMPILER_OPTIMIZATION]
|
||||
for key, flag in COMPILER_OPTIMIZATIONS.items():
|
||||
add_idf_sdkconfig_option(flag, compiler_optimization == key)
|
||||
|
||||
@@ -1070,18 +1054,20 @@ async def to_code(config):
|
||||
conf[CONF_ADVANCED][CONF_ENABLE_LWIP_ASSERT],
|
||||
)
|
||||
|
||||
if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC):
|
||||
if advanced[CONF_IGNORE_EFUSE_MAC_CRC]:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False)
|
||||
if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES):
|
||||
if advanced[CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES]:
|
||||
_LOGGER.warning(
|
||||
"Using experimental features in ESP-IDF may result in unexpected failures."
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_IDF_EXPERIMENTAL_FEATURES", True)
|
||||
if config[CONF_FLASH_SIZE] == "32MB":
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True
|
||||
)
|
||||
|
||||
cg.add_define(
|
||||
"ESPHOME_LOOP_TASK_STACK_SIZE", advanced.get(CONF_LOOP_TASK_STACK_SIZE)
|
||||
)
|
||||
cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE])
|
||||
|
||||
cg.add_define(
|
||||
"USE_ESP_IDF_VERSION_CODE",
|
||||
|
||||
@@ -638,11 +638,13 @@ void ESP32BLE::dump_config() {
|
||||
io_capability_s = "invalid";
|
||||
break;
|
||||
}
|
||||
char mac_s[18];
|
||||
format_mac_addr_upper(mac_address, mac_s);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"BLE:\n"
|
||||
" MAC address: %s\n"
|
||||
" IO Capability: %s",
|
||||
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
|
||||
mac_s, io_capability_s);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace esphome::esp32_ble_tracker {
|
||||
class ESPBTAdvertiseTrigger : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
||||
void set_addresses(const std::vector<uint64_t> &addresses) { this->address_vec_ = addresses; }
|
||||
void set_addresses(std::initializer_list<uint64_t> addresses) { this->address_vec_ = addresses; }
|
||||
|
||||
bool parse_device(const ESPBTDevice &device) override {
|
||||
uint64_t u64_addr = device.address_uint64();
|
||||
|
||||
@@ -336,7 +336,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
wifi::global_wifi_component->start_connecting(sta);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
@@ -383,6 +383,7 @@ async def to_code(config):
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
cg.add_define("USE_ETHERNET_MANUAL_IP")
|
||||
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
|
||||
|
||||
# Add compile-time define for PHY types with specific code
|
||||
|
||||
@@ -381,7 +381,10 @@ void EthernetComponent::dump_config() {
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Ethernet:");
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Ethernet:\n"
|
||||
" Connected: %s",
|
||||
YESNO(this->is_connected()));
|
||||
this->dump_connect_params_();
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
ESP_LOGCONFIG(TAG,
|
||||
@@ -550,11 +553,14 @@ void EthernetComponent::start_connect_() {
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t info;
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
info.ip = this->manual_ip_->static_ip;
|
||||
info.gw = this->manual_ip_->gateway;
|
||||
info.netmask = this->manual_ip_->subnet;
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
info.ip.addr = 0;
|
||||
info.gw.addr = 0;
|
||||
info.netmask.addr = 0;
|
||||
@@ -575,6 +581,7 @@ void EthernetComponent::start_connect_() {
|
||||
err = esp_netif_set_ip_info(this->eth_netif_, &info);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
||||
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
LwIPLock lock;
|
||||
if (this->manual_ip_->dns1.is_set()) {
|
||||
@@ -587,7 +594,9 @@ void EthernetComponent::start_connect_() {
|
||||
d = this->manual_ip_->dns2;
|
||||
dns_setserver(1, &d);
|
||||
}
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
err = esp_netif_dhcpc_start(this->eth_netif_);
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC start error");
|
||||
@@ -685,7 +694,9 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->cl
|
||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
||||
#endif
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
#endif
|
||||
|
||||
// set_use_address() is guaranteed to be called during component setup by Python code generation,
|
||||
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
|
||||
|
||||
@@ -82,7 +82,9 @@ class EthernetComponent : public Component {
|
||||
void add_phy_register(PHYRegister register_value);
|
||||
#endif
|
||||
void set_type(EthernetType type);
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
#endif
|
||||
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
|
||||
|
||||
network::IPAddresses get_ip_addresses();
|
||||
@@ -137,7 +139,9 @@ class EthernetComponent : public Component {
|
||||
uint8_t mdc_pin_{23};
|
||||
uint8_t mdio_pin_{18};
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
optional<ManualIP> manual_ip_{};
|
||||
#endif
|
||||
uint32_t connect_begin_;
|
||||
|
||||
// Group all uint8_t types together (enums and bools)
|
||||
|
||||
@@ -36,7 +36,6 @@ from esphome.const import (
|
||||
CONF_WEIGHT,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.helpers import cpp_string_escape
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -50,7 +49,6 @@ font_ns = cg.esphome_ns.namespace("font")
|
||||
|
||||
Font = font_ns.class_("Font")
|
||||
Glyph = font_ns.class_("Glyph")
|
||||
GlyphData = font_ns.struct("GlyphData")
|
||||
|
||||
CONF_BPP = "bpp"
|
||||
CONF_EXTRAS = "extras"
|
||||
@@ -463,7 +461,7 @@ FONT_SCHEMA = cv.Schema(
|
||||
)
|
||||
),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData),
|
||||
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(Glyph),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -488,6 +486,8 @@ class GlyphInfo:
|
||||
|
||||
|
||||
def glyph_to_glyphinfo(glyph, font, size, bpp):
|
||||
# Convert to 32 bit unicode codepoint
|
||||
glyph = ord(glyph)
|
||||
scale = 256 // (1 << bpp)
|
||||
if not font.is_scalable:
|
||||
sizes = [pt_to_px(x.size) for x in font.available_sizes]
|
||||
@@ -583,22 +583,15 @@ async def to_code(config):
|
||||
|
||||
# Create the glyph table that points to data in the above array.
|
||||
glyph_initializer = [
|
||||
cg.StructInitializer(
|
||||
GlyphData,
|
||||
(
|
||||
"a_char",
|
||||
cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
|
||||
),
|
||||
(
|
||||
"data",
|
||||
cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
|
||||
),
|
||||
("advance", x.advance),
|
||||
("offset_x", x.offset_x),
|
||||
("offset_y", x.offset_y),
|
||||
("width", x.width),
|
||||
("height", x.height),
|
||||
)
|
||||
[
|
||||
x.glyph,
|
||||
prog_arr + (y - len(x.bitmap_data)),
|
||||
x.advance,
|
||||
x.offset_x,
|
||||
x.offset_y,
|
||||
x.width,
|
||||
x.height,
|
||||
]
|
||||
for (x, y) in zip(
|
||||
glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args]))
|
||||
)
|
||||
|
||||
@@ -6,133 +6,245 @@
|
||||
|
||||
namespace esphome {
|
||||
namespace font {
|
||||
|
||||
static const char *const TAG = "font";
|
||||
|
||||
const uint8_t *Glyph::get_char() const { return this->glyph_data_->a_char; }
|
||||
// Compare the char at the string position with this char.
|
||||
// Return true if this char is less than or equal the other.
|
||||
bool Glyph::compare_to(const uint8_t *str) const {
|
||||
// 1 -> this->char_
|
||||
// 2 -> str
|
||||
for (uint32_t i = 0;; i++) {
|
||||
if (this->glyph_data_->a_char[i] == '\0')
|
||||
return true;
|
||||
if (str[i] == '\0')
|
||||
return false;
|
||||
if (this->glyph_data_->a_char[i] > str[i])
|
||||
return false;
|
||||
if (this->glyph_data_->a_char[i] < str[i])
|
||||
return true;
|
||||
#ifdef USE_LVGL_FONT
|
||||
const uint8_t *Font::get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
|
||||
auto *fe = (Font *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data_(unicode_letter);
|
||||
if (gd == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
// this should not happen
|
||||
return false;
|
||||
}
|
||||
int Glyph::match_length(const uint8_t *str) const {
|
||||
for (uint32_t i = 0;; i++) {
|
||||
if (this->glyph_data_->a_char[i] == '\0')
|
||||
return i;
|
||||
if (str[i] != this->glyph_data_->a_char[i])
|
||||
return 0;
|
||||
}
|
||||
// this should not happen
|
||||
return 0;
|
||||
}
|
||||
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
||||
*x1 = this->glyph_data_->offset_x;
|
||||
*y1 = this->glyph_data_->offset_y;
|
||||
*width = this->glyph_data_->width;
|
||||
*height = this->glyph_data_->height;
|
||||
return gd->data;
|
||||
}
|
||||
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
|
||||
auto *fe = (Font *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data_(unicode_letter);
|
||||
if (gd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
dsc->adv_w = gd->advance;
|
||||
dsc->ofs_x = gd->offset_x;
|
||||
dsc->ofs_y = fe->height_ - gd->height - gd->offset_y - fe->lv_font_.base_line;
|
||||
dsc->box_w = gd->width;
|
||||
dsc->box_h = gd->height;
|
||||
dsc->is_placeholder = 0;
|
||||
dsc->bpp = fe->get_bpp();
|
||||
return true;
|
||||
}
|
||||
|
||||
const Glyph *Font::get_glyph_data_(uint32_t unicode_letter) {
|
||||
if (unicode_letter == this->last_letter_ && this->last_letter_ != 0)
|
||||
return this->last_data_;
|
||||
auto *glyph = this->find_glyph(unicode_letter);
|
||||
if (glyph == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
this->last_data_ = glyph;
|
||||
this->last_letter_ = unicode_letter;
|
||||
return glyph;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Attempt to extract a 32 bit Unicode codepoint from a UTF-8 string.
|
||||
* If successful, return the codepoint and set the length to the number of bytes read.
|
||||
* If the end of the string has been reached and a valid codepoint has not been found, return 0 and set the length to
|
||||
* 0.
|
||||
*
|
||||
* @param utf8_str The input string
|
||||
* @param length Pointer to length storage
|
||||
* @return The extracted code point
|
||||
*/
|
||||
static uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) {
|
||||
// Safely cast to uint8_t* for correct bitwise operations on bytes
|
||||
const uint8_t *current = reinterpret_cast<const uint8_t *>(utf8_str);
|
||||
uint32_t code_point = 0;
|
||||
uint8_t c1 = *current++;
|
||||
|
||||
// check for end of string
|
||||
if (c1 == 0) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- 1-Byte Sequence: 0xxxxxxx (ASCII) ---
|
||||
if (c1 < 0x80) {
|
||||
// Valid ASCII byte.
|
||||
code_point = c1;
|
||||
// Optimization: No need to check for continuation bytes.
|
||||
}
|
||||
// --- 2-Byte Sequence: 110xxxxx 10xxxxxx ---
|
||||
else if ((c1 & 0xE0) == 0xC0) {
|
||||
uint8_t c2 = *current++;
|
||||
|
||||
// Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx)
|
||||
if ((c2 & 0xC0) != 0x80) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
code_point = (c1 & 0x1F) << 6;
|
||||
code_point |= (c2 & 0x3F);
|
||||
|
||||
// Error Check 2: Overlong check (2-byte must be > 0x7F)
|
||||
if (code_point <= 0x7F) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx ---
|
||||
else if ((c1 & 0xF0) == 0xE0) {
|
||||
uint8_t c2 = *current++;
|
||||
uint8_t c3 = *current++;
|
||||
|
||||
// Error Check 1: Check continuation bytes
|
||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
code_point = (c1 & 0x0F) << 12;
|
||||
code_point |= (c2 & 0x3F) << 6;
|
||||
code_point |= (c3 & 0x3F);
|
||||
|
||||
// Error Check 2: Overlong check (3-byte must be > 0x7FF)
|
||||
// Also check for surrogates (0xD800-0xDFFF)
|
||||
if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx ---
|
||||
else if ((c1 & 0xF8) == 0xF0) {
|
||||
uint8_t c2 = *current++;
|
||||
uint8_t c3 = *current++;
|
||||
uint8_t c4 = *current++;
|
||||
|
||||
// Error Check 1: Check continuation bytes
|
||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
code_point = (c1 & 0x07) << 18;
|
||||
code_point |= (c2 & 0x3F) << 12;
|
||||
code_point |= (c3 & 0x3F) << 6;
|
||||
code_point |= (c4 & 0x3F);
|
||||
|
||||
// Error Check 2: Overlong check (4-byte must be > 0xFFFF)
|
||||
// Also check for valid Unicode range (must be <= 0x10FFFF)
|
||||
if (code_point <= 0xFFFF || code_point > 0x10FFFF) {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) ---
|
||||
else {
|
||||
*length = 0;
|
||||
return 0;
|
||||
}
|
||||
*length = current - reinterpret_cast<const uint8_t *>(utf8_str);
|
||||
return code_point;
|
||||
}
|
||||
|
||||
Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
uint8_t bpp)
|
||||
: baseline_(baseline),
|
||||
: glyphs_(ConstVector(data, data_nr)),
|
||||
baseline_(baseline),
|
||||
height_(height),
|
||||
descender_(descender),
|
||||
linegap_(height - baseline - descender),
|
||||
xheight_(xheight),
|
||||
capheight_(capheight),
|
||||
bpp_(bpp) {
|
||||
glyphs_.reserve(data_nr);
|
||||
for (int i = 0; i < data_nr; ++i)
|
||||
glyphs_.emplace_back(&data[i]);
|
||||
#ifdef USE_LVGL_FONT
|
||||
this->lv_font_.dsc = this;
|
||||
this->lv_font_.line_height = this->get_height();
|
||||
this->lv_font_.base_line = this->lv_font_.line_height - this->get_baseline();
|
||||
this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
|
||||
this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
|
||||
this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
|
||||
this->lv_font_.underline_position = -1;
|
||||
this->lv_font_.underline_thickness = 1;
|
||||
#endif
|
||||
}
|
||||
int Font::match_next_glyph(const uint8_t *str, int *match_length) {
|
||||
|
||||
const Glyph *Font::find_glyph(uint32_t codepoint) const {
|
||||
int lo = 0;
|
||||
int hi = this->glyphs_.size() - 1;
|
||||
while (lo != hi) {
|
||||
int mid = (lo + hi + 1) / 2;
|
||||
if (this->glyphs_[mid].compare_to(str)) {
|
||||
if (this->glyphs_[mid].is_less_or_equal(codepoint)) {
|
||||
lo = mid;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
*match_length = this->glyphs_[lo].match_length(str);
|
||||
if (*match_length <= 0)
|
||||
return -1;
|
||||
return lo;
|
||||
auto *result = &this->glyphs_[lo];
|
||||
if (result->code_point == codepoint)
|
||||
return result;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef USE_DISPLAY
|
||||
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
|
||||
*baseline = this->baseline_;
|
||||
*height = this->height_;
|
||||
int i = 0;
|
||||
int min_x = 0;
|
||||
bool has_char = false;
|
||||
int x = 0;
|
||||
while (str[i] != '\0') {
|
||||
int match_length;
|
||||
int glyph_n = this->match_next_glyph((const uint8_t *) str + i, &match_length);
|
||||
if (glyph_n < 0) {
|
||||
for (;;) {
|
||||
size_t length;
|
||||
auto code_point = extract_unicode_codepoint(str, &length);
|
||||
if (length == 0)
|
||||
break;
|
||||
str += length;
|
||||
auto *glyph = this->find_glyph(code_point);
|
||||
if (glyph == nullptr) {
|
||||
// Unknown char, skip
|
||||
if (!this->get_glyphs().empty())
|
||||
x += this->get_glyphs()[0].glyph_data_->advance;
|
||||
i++;
|
||||
if (!this->glyphs_.empty())
|
||||
x += this->glyphs_[0].advance;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Glyph &glyph = this->glyphs_[glyph_n];
|
||||
if (!has_char) {
|
||||
min_x = glyph.glyph_data_->offset_x;
|
||||
min_x = glyph->offset_x;
|
||||
} else {
|
||||
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
|
||||
min_x = std::min(min_x, x + glyph->offset_x);
|
||||
}
|
||||
x += glyph.glyph_data_->advance;
|
||||
x += glyph->advance;
|
||||
|
||||
i += match_length;
|
||||
has_char = true;
|
||||
}
|
||||
*x_offset = min_x;
|
||||
*width = x - min_x;
|
||||
}
|
||||
|
||||
void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) {
|
||||
int i = 0;
|
||||
int x_at = x_start;
|
||||
int scan_x1, scan_y1, scan_width, scan_height;
|
||||
while (text[i] != '\0') {
|
||||
int match_length;
|
||||
int glyph_n = this->match_next_glyph((const uint8_t *) text + i, &match_length);
|
||||
if (glyph_n < 0) {
|
||||
for (;;) {
|
||||
size_t length;
|
||||
auto code_point = extract_unicode_codepoint(text, &length);
|
||||
if (length == 0)
|
||||
break;
|
||||
text += length;
|
||||
auto *glyph = this->find_glyph(code_point);
|
||||
if (glyph == nullptr) {
|
||||
// Unknown char, skip
|
||||
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
|
||||
if (!this->get_glyphs().empty()) {
|
||||
uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->advance;
|
||||
display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color);
|
||||
ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point);
|
||||
if (!this->glyphs_.empty()) {
|
||||
uint8_t glyph_width = this->glyphs_[0].advance;
|
||||
display->rectangle(x_at, y_start, glyph_width, this->height_, color);
|
||||
x_at += glyph_width;
|
||||
}
|
||||
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Glyph &glyph = this->get_glyphs()[glyph_n];
|
||||
glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
|
||||
|
||||
const uint8_t *data = glyph.glyph_data_->data;
|
||||
const int max_x = x_at + scan_x1 + scan_width;
|
||||
const int max_y = y_start + scan_y1 + scan_height;
|
||||
const uint8_t *data = glyph->data;
|
||||
const int max_x = x_at + glyph->offset_x + glyph->width;
|
||||
const int max_y = y_start + glyph->offset_y + glyph->height;
|
||||
|
||||
uint8_t bitmask = 0;
|
||||
uint8_t pixel_data = 0;
|
||||
@@ -145,10 +257,10 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||
auto b_g = (float) background.g;
|
||||
auto b_b = (float) background.b;
|
||||
auto b_w = (float) background.w;
|
||||
for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
|
||||
for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
|
||||
for (int glyph_y = y_start + glyph->offset_y; glyph_y != max_y; glyph_y++) {
|
||||
for (int glyph_x = x_at + glyph->offset_x; glyph_x != max_x; glyph_x++) {
|
||||
uint8_t pixel = 0;
|
||||
for (int bit_num = 0; bit_num != this->bpp_; bit_num++) {
|
||||
for (uint8_t bit_num = 0; bit_num != this->bpp_; bit_num++) {
|
||||
if (bitmask == 0) {
|
||||
pixel_data = progmem_read_byte(data++);
|
||||
bitmask = 0x80;
|
||||
@@ -168,12 +280,9 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||
}
|
||||
}
|
||||
}
|
||||
x_at += glyph.glyph_data_->advance;
|
||||
|
||||
i += match_length;
|
||||
x_at += glyph->advance;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace font
|
||||
} // namespace esphome
|
||||
|
||||
@@ -6,14 +6,30 @@
|
||||
#ifdef USE_DISPLAY
|
||||
#include "esphome/components/display/display.h"
|
||||
#endif
|
||||
#ifdef USE_LVGL_FONT
|
||||
#include <lvgl.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace font {
|
||||
|
||||
class Font;
|
||||
|
||||
struct GlyphData {
|
||||
const uint8_t *a_char;
|
||||
class Glyph {
|
||||
public:
|
||||
constexpr Glyph(uint32_t code_point, const uint8_t *data, int advance, int offset_x, int offset_y, int width,
|
||||
int height)
|
||||
: code_point(code_point),
|
||||
data(data),
|
||||
advance(advance),
|
||||
offset_x(offset_x),
|
||||
offset_y(offset_y),
|
||||
width(width),
|
||||
height(height) {}
|
||||
|
||||
bool is_less_or_equal(uint32_t other) const { return this->code_point <= other; }
|
||||
|
||||
const uint32_t code_point;
|
||||
const uint8_t *data;
|
||||
int advance;
|
||||
int offset_x;
|
||||
@@ -22,26 +38,6 @@ struct GlyphData {
|
||||
int height;
|
||||
};
|
||||
|
||||
class Glyph {
|
||||
public:
|
||||
Glyph(const GlyphData *data) : glyph_data_(data) {}
|
||||
|
||||
const uint8_t *get_char() const;
|
||||
|
||||
bool compare_to(const uint8_t *str) const;
|
||||
|
||||
int match_length(const uint8_t *str) const;
|
||||
|
||||
void scan_area(int *x1, int *y1, int *width, int *height) const;
|
||||
|
||||
const GlyphData *get_glyph_data() const { return this->glyph_data_; }
|
||||
|
||||
protected:
|
||||
friend Font;
|
||||
|
||||
const GlyphData *glyph_data_;
|
||||
};
|
||||
|
||||
class Font
|
||||
#ifdef USE_DISPLAY
|
||||
: public display::BaseFont
|
||||
@@ -50,8 +46,8 @@ class Font
|
||||
public:
|
||||
/** Construct the font with the given glyphs.
|
||||
*
|
||||
* @param data A vector of glyphs, must be sorted lexicographically.
|
||||
* @param data_nr The number of glyphs in data.
|
||||
* @param data A list of glyphs, must be sorted lexicographically.
|
||||
* @param data_nr The number of glyphs
|
||||
* @param baseline The y-offset from the top of the text to the baseline.
|
||||
* @param height The y-offset from the top of the text to the bottom.
|
||||
* @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p).
|
||||
@@ -59,10 +55,10 @@ class Font
|
||||
* @param capheight The height of capital letters, usually measured at the "X" glyph.
|
||||
* @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps.
|
||||
*/
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
|
||||
uint8_t bpp = 1);
|
||||
|
||||
int match_next_glyph(const uint8_t *str, int *match_length);
|
||||
const Glyph *find_glyph(uint32_t codepoint) const;
|
||||
|
||||
#ifdef USE_DISPLAY
|
||||
void print(int x_start, int y_start, display::Display *display, Color color, const char *text,
|
||||
@@ -77,11 +73,14 @@ class Font
|
||||
inline int get_xheight() { return this->xheight_; }
|
||||
inline int get_capheight() { return this->capheight_; }
|
||||
inline int get_bpp() { return this->bpp_; }
|
||||
#ifdef USE_LVGL_FONT
|
||||
const lv_font_t *get_lv_font() const { return &this->lv_font_; }
|
||||
#endif
|
||||
|
||||
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
||||
const ConstVector<Glyph> &get_glyphs() const { return glyphs_; }
|
||||
|
||||
protected:
|
||||
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
|
||||
ConstVector<Glyph> glyphs_;
|
||||
int baseline_;
|
||||
int height_;
|
||||
int descender_;
|
||||
@@ -89,6 +88,14 @@ class Font
|
||||
int xheight_;
|
||||
int capheight_;
|
||||
uint8_t bpp_; // bits per pixel
|
||||
#ifdef USE_LVGL_FONT
|
||||
lv_font_t lv_font_{};
|
||||
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter);
|
||||
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next);
|
||||
const Glyph *get_glyph_data_(uint32_t unicode_letter);
|
||||
uint32_t last_letter_{};
|
||||
const Glyph *last_data_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace font
|
||||
|
||||
@@ -7,10 +7,8 @@ namespace homeassistant {
|
||||
static const char *const TAG = "homeassistant.time";
|
||||
|
||||
void HomeassistantTime::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Home Assistant Time:\n"
|
||||
" Timezone: '%s'",
|
||||
this->timezone_.c_str());
|
||||
ESP_LOGCONFIG(TAG, "Home Assistant Time");
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -231,7 +231,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
wifi::global_wifi_component->start_connecting(sta);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace esphome {
|
||||
namespace ld2410 {
|
||||
|
||||
static const char *const TAG = "ld2410";
|
||||
static const char *const UNKNOWN_MAC = "unknown";
|
||||
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
enum BaudRate : uint8_t {
|
||||
BAUD_RATE_9600 = 1,
|
||||
@@ -181,15 +179,15 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui
|
||||
}
|
||||
|
||||
void LD2410Component::dump_config() {
|
||||
std::string mac_str =
|
||||
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
|
||||
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
|
||||
this->version_[4], this->version_[3], this->version_[2]);
|
||||
char mac_s[18];
|
||||
char version_s[20];
|
||||
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
|
||||
ld24xx::format_version_str(this->version_, version_s);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"LD2410:\n"
|
||||
" Firmware version: %s\n"
|
||||
" MAC address: %s",
|
||||
version.c_str(), mac_str.c_str());
|
||||
version_s, mac_str);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
ESP_LOGCONFIG(TAG, "Binary Sensors:");
|
||||
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
|
||||
@@ -448,12 +446,12 @@ bool LD2410Component::handle_ack_data_() {
|
||||
|
||||
case CMD_QUERY_VERSION: {
|
||||
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
|
||||
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
|
||||
this->version_[4], this->version_[3], this->version_[2]);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
|
||||
char version_s[20];
|
||||
ld24xx::format_version_str(this->version_, version_s);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", version_s);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
this->version_text_sensor_->publish_state(version);
|
||||
this->version_text_sensor_->publish_state(version_s);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -506,9 +504,9 @@ bool LD2410Component::handle_ack_data_() {
|
||||
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
|
||||
}
|
||||
|
||||
std::string mac_str =
|
||||
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
|
||||
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
|
||||
char mac_s[18];
|
||||
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
|
||||
ESP_LOGV(TAG, "MAC address: %s", mac_str);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->mac_text_sensor_ != nullptr) {
|
||||
this->mac_text_sensor_->publish_state(mac_str);
|
||||
|
||||
@@ -31,35 +31,83 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema(
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_FLASH,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
cv.Optional(CONF_LIGHT): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_LIGHTBULB,
|
||||
),
|
||||
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
@@ -73,7 +121,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
@@ -81,7 +135,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_FLASH,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
|
||||
@@ -14,8 +14,6 @@ namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
static const char *const TAG = "ld2412";
|
||||
static const char *const UNKNOWN_MAC = "unknown";
|
||||
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
enum BaudRate : uint8_t {
|
||||
BAUD_RATE_9600 = 1,
|
||||
@@ -200,15 +198,15 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui
|
||||
}
|
||||
|
||||
void LD2412Component::dump_config() {
|
||||
std::string mac_str =
|
||||
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
|
||||
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
|
||||
this->version_[4], this->version_[3], this->version_[2]);
|
||||
char mac_s[18];
|
||||
char version_s[20];
|
||||
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
|
||||
ld24xx::format_version_str(this->version_, version_s);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"LD2412:\n"
|
||||
" Firmware version: %s\n"
|
||||
" MAC address: %s",
|
||||
version.c_str(), mac_str.c_str());
|
||||
version_s, mac_str);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
ESP_LOGCONFIG(TAG, "Binary Sensors:");
|
||||
LOG_BINARY_SENSOR(" ", "DynamicBackgroundCorrectionStatus",
|
||||
@@ -492,12 +490,12 @@ bool LD2412Component::handle_ack_data_() {
|
||||
|
||||
case CMD_QUERY_VERSION: {
|
||||
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
|
||||
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
|
||||
this->version_[4], this->version_[3], this->version_[2]);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
|
||||
char version_s[20];
|
||||
ld24xx::format_version_str(this->version_, version_s);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", version_s);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
this->version_text_sensor_->publish_state(version);
|
||||
this->version_text_sensor_->publish_state(version_s);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -544,9 +542,9 @@ bool LD2412Component::handle_ack_data_() {
|
||||
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
|
||||
}
|
||||
|
||||
std::string mac_str =
|
||||
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
|
||||
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
|
||||
char mac_s[18];
|
||||
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
|
||||
ESP_LOGV(TAG, "MAC address: %s", mac_str);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->mac_text_sensor_ != nullptr) {
|
||||
this->mac_text_sensor_->publish_state(mac_str);
|
||||
|
||||
@@ -31,36 +31,84 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
cv.Optional(CONF_LIGHT): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_LIGHTBULB,
|
||||
unit_of_measurement=UNIT_EMPTY, # No standard unit for this light sensor
|
||||
),
|
||||
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema(
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
filters=[
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_FLASH,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
@@ -74,7 +122,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
@@ -82,7 +136,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
|
||||
{
|
||||
"timeout": {
|
||||
"timeout": cv.TimePeriod(milliseconds=1000),
|
||||
"value": "last",
|
||||
}
|
||||
},
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
|
||||
],
|
||||
icon=ICON_FLASH,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
|
||||
@@ -17,8 +17,6 @@ namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
static const char *const TAG = "ld2450";
|
||||
static const char *const UNKNOWN_MAC = "unknown";
|
||||
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
enum BaudRate : uint8_t {
|
||||
BAUD_RATE_9600 = 1,
|
||||
@@ -192,15 +190,15 @@ void LD2450Component::setup() {
|
||||
}
|
||||
|
||||
void LD2450Component::dump_config() {
|
||||
std::string mac_str =
|
||||
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
|
||||
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
|
||||
this->version_[4], this->version_[3], this->version_[2]);
|
||||
char mac_s[18];
|
||||
char version_s[20];
|
||||
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
|
||||
ld24xx::format_version_str(this->version_, version_s);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"LD2450:\n"
|
||||
" Firmware version: %s\n"
|
||||
" MAC address: %s",
|
||||
version.c_str(), mac_str.c_str());
|
||||
version_s, mac_str);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
ESP_LOGCONFIG(TAG, "Binary Sensors:");
|
||||
LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_);
|
||||
@@ -642,12 +640,12 @@ bool LD2450Component::handle_ack_data_() {
|
||||
|
||||
case CMD_QUERY_VERSION: {
|
||||
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
|
||||
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
|
||||
this->version_[4], this->version_[3], this->version_[2]);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
|
||||
char version_s[20];
|
||||
ld24xx::format_version_str(this->version_, version_s);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", version_s);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
this->version_text_sensor_->publish_state(version);
|
||||
this->version_text_sensor_->publish_state(version_s);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -663,9 +661,9 @@ bool LD2450Component::handle_ack_data_() {
|
||||
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
|
||||
}
|
||||
|
||||
std::string mac_str =
|
||||
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
|
||||
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
|
||||
char mac_s[18];
|
||||
const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
|
||||
ESP_LOGV(TAG, "MAC address: %s", mac_str);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->mac_text_sensor_ != nullptr) {
|
||||
this->mac_text_sensor_->publish_state(mac_str);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#define SUB_SENSOR_WITH_DEDUP(name, dedup_type) \
|
||||
@@ -39,6 +40,27 @@
|
||||
namespace esphome {
|
||||
namespace ld24xx {
|
||||
|
||||
static const char *const UNKNOWN_MAC = "unknown";
|
||||
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
// Helper function to format MAC address with stack allocation
|
||||
// Returns pointer to UNKNOWN_MAC constant or formatted buffer
|
||||
// Buffer must be exactly 18 bytes (17 for "XX:XX:XX:XX:XX:XX" + null terminator)
|
||||
inline const char *format_mac_str(const uint8_t *mac_address, std::span<char, 18> buffer) {
|
||||
if (mac_address_is_valid(mac_address)) {
|
||||
format_mac_addr_upper(mac_address, buffer.data());
|
||||
return buffer.data();
|
||||
}
|
||||
return UNKNOWN_MAC;
|
||||
}
|
||||
|
||||
// Helper function to format firmware version with stack allocation
|
||||
// Buffer must be exactly 20 bytes (format: "x.xxXXXXXX" fits in 11 + null terminator, 20 for safety)
|
||||
inline void format_version_str(const uint8_t *version, std::span<char, 20> buffer) {
|
||||
snprintf(buffer.data(), buffer.size(), VERSION_FMT, version[1], version[0], version[5], version[4], version[3],
|
||||
version[2]);
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
// Helper class to store a sensor with a deduplicator & publish state only when the value changes
|
||||
template<typename T> class SensorWithDedup {
|
||||
|
||||
@@ -52,8 +52,10 @@ static void log_invalid_parameter(const char *name, const LogString *message) {
|
||||
}
|
||||
|
||||
static const LogString *color_mode_to_human(ColorMode color_mode) {
|
||||
if (color_mode == ColorMode::UNKNOWN)
|
||||
return LOG_STR("Unknown");
|
||||
if (color_mode == ColorMode::ON_OFF)
|
||||
return LOG_STR("On/Off");
|
||||
if (color_mode == ColorMode::BRIGHTNESS)
|
||||
return LOG_STR("Brightness");
|
||||
if (color_mode == ColorMode::WHITE)
|
||||
return LOG_STR("White");
|
||||
if (color_mode == ColorMode::COLOR_TEMPERATURE)
|
||||
@@ -68,7 +70,7 @@ static const LogString *color_mode_to_human(ColorMode color_mode) {
|
||||
return LOG_STR("RGB + cold/warm white");
|
||||
if (color_mode == ColorMode::RGB_COLOR_TEMPERATURE)
|
||||
return LOG_STR("RGB + color temperature");
|
||||
return LOG_STR("");
|
||||
return LOG_STR("Unknown");
|
||||
}
|
||||
|
||||
// Helper to log percentage values
|
||||
@@ -406,7 +408,7 @@ void LightCall::transform_parameters_() {
|
||||
}
|
||||
}
|
||||
ColorMode LightCall::compute_color_mode_() {
|
||||
const auto &supported_modes = this->parent_->get_traits().get_supported_color_modes();
|
||||
auto supported_modes = this->parent_->get_traits().get_supported_color_modes();
|
||||
int supported_count = supported_modes.size();
|
||||
|
||||
// Some lights don't support any color modes (e.g. monochromatic light), leave it at unknown.
|
||||
|
||||
@@ -24,6 +24,9 @@ void LightState::setup() {
|
||||
effect->init_internal(this);
|
||||
}
|
||||
|
||||
// Start with loop disabled if idle - respects any effects/transitions set up during initialization
|
||||
this->disable_loop_if_idle_();
|
||||
|
||||
// When supported color temperature range is known, initialize color temperature setting within bounds.
|
||||
auto traits = this->get_traits();
|
||||
float min_mireds = traits.get_min_mireds();
|
||||
@@ -126,6 +129,9 @@ void LightState::loop() {
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
this->target_state_reached_callback_.call();
|
||||
|
||||
// Disable loop if idle (no transformer and no effect)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +139,8 @@ void LightState::loop() {
|
||||
if (this->next_write_) {
|
||||
this->next_write_ = false;
|
||||
this->output_->write_state(this);
|
||||
// Disable loop if idle (no transformer and no effect)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +236,8 @@ void LightState::start_effect_(uint32_t effect_index) {
|
||||
this->active_effect_index_ = effect_index;
|
||||
auto *effect = this->get_active_effect_();
|
||||
effect->start_internal();
|
||||
// Enable loop while effect is active
|
||||
this->enable_loop();
|
||||
}
|
||||
LightEffect *LightState::get_active_effect_() {
|
||||
if (this->active_effect_index_ == 0) {
|
||||
@@ -242,6 +252,8 @@ void LightState::stop_effect_() {
|
||||
effect->stop();
|
||||
}
|
||||
this->active_effect_index_ = 0;
|
||||
// Disable loop if idle (no effect and no transformer)
|
||||
this->disable_loop_if_idle_();
|
||||
}
|
||||
|
||||
void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
|
||||
@@ -251,6 +263,8 @@ void LightState::start_transition_(const LightColorValues &target, uint32_t leng
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
}
|
||||
// Enable loop while transition is active
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
|
||||
@@ -266,6 +280,8 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
||||
if (set_remote_values) {
|
||||
this->remote_values = target;
|
||||
};
|
||||
// Enable loop while flash is active
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||
@@ -277,6 +293,14 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot
|
||||
}
|
||||
this->output_->update_state(this);
|
||||
this->next_write_ = true;
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void LightState::disable_loop_if_idle_() {
|
||||
// Only disable loop if both transformer and effect are inactive, and no pending writes
|
||||
if (this->transformer_ == nullptr && this->get_active_effect_() == nullptr && !this->next_write_) {
|
||||
this->disable_loop();
|
||||
}
|
||||
}
|
||||
|
||||
void LightState::save_remote_values_() {
|
||||
|
||||
@@ -256,6 +256,9 @@ class LightState : public EntityBase, public Component {
|
||||
/// Internal method to save the current remote_values to the preferences
|
||||
void save_remote_values_();
|
||||
|
||||
/// Disable loop if neither transformer nor effect is active
|
||||
void disable_loop_if_idle_();
|
||||
|
||||
/// Store the output to allow effects to have more access.
|
||||
LightOutput *output_;
|
||||
/// The currently active transformer for this light (transition/flash).
|
||||
|
||||
@@ -18,7 +18,8 @@ class LightTraits {
|
||||
public:
|
||||
LightTraits() = default;
|
||||
|
||||
const ColorModeMask &get_supported_color_modes() const { return this->supported_color_modes_; }
|
||||
// Return by value to avoid dangling reference when get_traits() returns a temporary
|
||||
ColorModeMask get_supported_color_modes() const { return this->supported_color_modes_; }
|
||||
void set_supported_color_modes(ColorModeMask supported_color_modes) {
|
||||
this->supported_color_modes_ = supported_color_modes;
|
||||
}
|
||||
|
||||
@@ -365,8 +365,10 @@ async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
|
||||
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG")
|
||||
try:
|
||||
uart_selection(USB_SERIAL_JTAG)
|
||||
cg.add_define("USE_LOGGER_USB_SERIAL_JTAG")
|
||||
|
||||
@@ -65,7 +65,9 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
uint16_t buffer_at = 0; // Initialize buffer position
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
|
||||
MAX_CONSOLE_LOG_MSG_SIZE);
|
||||
this->write_msg_(console_buffer);
|
||||
// Add newline if platform needs it (ESP32 doesn't add via write_msg_)
|
||||
this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
|
||||
this->write_msg_(console_buffer, buffer_at);
|
||||
}
|
||||
|
||||
// Reset the recursion guard for this task
|
||||
@@ -131,18 +133,19 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
|
||||
// Save the offset before calling format_log_to_buffer_with_terminator_
|
||||
// since it will increment tx_buffer_at_ to the end of the formatted string
|
||||
uint32_t msg_start = this->tx_buffer_at_;
|
||||
uint16_t msg_start = this->tx_buffer_at_;
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
|
||||
&this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
|
||||
// Write to console and send callback starting at the msg_start
|
||||
if (this->baud_rate_ > 0) {
|
||||
this->write_msg_(this->tx_buffer_ + msg_start);
|
||||
}
|
||||
size_t msg_length =
|
||||
uint16_t msg_length =
|
||||
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
|
||||
|
||||
// Callbacks get message first (before console write)
|
||||
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
|
||||
|
||||
// Write to console starting at the msg_start
|
||||
this->write_tx_buffer_to_console_(msg_start, &msg_length);
|
||||
|
||||
global_recursion_guard_ = false;
|
||||
}
|
||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||
@@ -209,9 +212,7 @@ void Logger::process_messages_() {
|
||||
// This ensures all log messages appear on the console in a clean, serialized manner
|
||||
// Note: Messages may appear slightly out of order due to async processing, but
|
||||
// this is preferred over corrupted/interleaved console output
|
||||
if (this->baud_rate_ > 0) {
|
||||
this->write_msg_(this->tx_buffer_);
|
||||
}
|
||||
this->write_tx_buffer_to_console_();
|
||||
}
|
||||
} else {
|
||||
// No messages to process, disable loop if appropriate
|
||||
|
||||
@@ -71,6 +71,17 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128;
|
||||
// "0x" + 2 hex digits per byte + '\0'
|
||||
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
|
||||
|
||||
// Platform-specific: does write_msg_ add its own newline?
|
||||
// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, LibreTiny)
|
||||
// Allows single write call with newline included for efficiency
|
||||
// true: write_msg_ adds newline itself via puts()/println() (other platforms)
|
||||
// Newline should NOT be added to buffer
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_LIBRETINY)
|
||||
static constexpr bool WRITE_MSG_ADDS_NEWLINE = false;
|
||||
#else
|
||||
static constexpr bool WRITE_MSG_ADDS_NEWLINE = true;
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
/** Enum for logging UART selection
|
||||
*
|
||||
@@ -173,7 +184,7 @@ class Logger : public Component {
|
||||
|
||||
protected:
|
||||
void process_messages_();
|
||||
void write_msg_(const char *msg);
|
||||
void write_msg_(const char *msg, size_t len);
|
||||
|
||||
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
|
||||
// It's the caller's responsibility to initialize buffer_at (typically to 0)
|
||||
@@ -200,6 +211,35 @@ class Logger : public Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to add newline to buffer for platforms that need it
|
||||
// Modifies buffer_at to include the newline
|
||||
inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
|
||||
if constexpr (!WRITE_MSG_ADDS_NEWLINE) {
|
||||
// Add newline - don't need to maintain null termination
|
||||
// write_msg_ now always receives explicit length, so we can safely overwrite the null terminator
|
||||
// This is safe because:
|
||||
// 1. Callbacks already received the message (before we add newline)
|
||||
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
|
||||
if (*buffer_at < buffer_size) {
|
||||
buffer[(*buffer_at)++] = '\n';
|
||||
} else if (buffer_size > 0) {
|
||||
// Buffer was full - replace last char with newline to ensure it's visible
|
||||
buffer[buffer_size - 1] = '\n';
|
||||
*buffer_at = buffer_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to write tx_buffer_ to console if logging is enabled
|
||||
// INTERNAL USE ONLY - offset > 0 requires length parameter to be non-null
|
||||
inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) {
|
||||
if (this->baud_rate_ > 0) {
|
||||
uint16_t *len_ptr = length ? length : &this->tx_buffer_at_;
|
||||
this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
|
||||
this->write_msg_(this->tx_buffer_ + offset, *len_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to format and send a log message to both console and callbacks
|
||||
inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format,
|
||||
va_list args) {
|
||||
@@ -208,10 +248,11 @@ class Logger : public Component {
|
||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_,
|
||||
this->tx_buffer_size_);
|
||||
|
||||
if (this->baud_rate_ > 0) {
|
||||
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
|
||||
}
|
||||
// Callbacks get message WITHOUT newline (for API/MQTT/syslog)
|
||||
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
|
||||
// Console gets message WITH newline (if platform needs it)
|
||||
this->write_tx_buffer_to_console_();
|
||||
}
|
||||
|
||||
// Write the body of the log message to the buffer
|
||||
@@ -425,7 +466,9 @@ class Logger : public Component {
|
||||
}
|
||||
|
||||
// Update buffer_at with the formatted length (handle truncation)
|
||||
uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
|
||||
// When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
|
||||
// When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
|
||||
uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret;
|
||||
*buffer_at += formatted_len;
|
||||
|
||||
// Remove all trailing newlines right after formatting
|
||||
|
||||
@@ -121,25 +121,23 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
if (
|
||||
#if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG)
|
||||
this->uart_ == UART_SELECTION_USB_CDC
|
||||
#elif defined(USE_LOGGER_USB_SERIAL_JTAG) && !defined(USE_LOGGER_USB_CDC)
|
||||
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#elif defined(USE_LOGGER_USB_CDC) && defined(USE_LOGGER_USB_SERIAL_JTAG)
|
||||
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) {
|
||||
// Length is now always passed explicitly - no strlen() fallback needed
|
||||
|
||||
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
|
||||
// USB CDC/JTAG - single write including newline (already in buffer)
|
||||
// Use fwrite to stdout which goes through VFS to USB console
|
||||
//
|
||||
// Note: These defines indicate the user's YAML configuration choice (hardware_uart: USB_CDC/USB_SERIAL_JTAG).
|
||||
// They are ONLY defined when the user explicitly selects USB as the logger output in their config.
|
||||
// This is compile-time selection, not runtime detection - if USB is configured, it's always used.
|
||||
// There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility
|
||||
// to configure correctly for their hardware. This approach eliminates runtime overhead.
|
||||
fwrite(msg, 1, len, stdout);
|
||||
#else
|
||||
/* DISABLES CODE */ (false) // NOLINT
|
||||
// Regular UART - single write including newline (already in buffer)
|
||||
uart_write_bytes(this->uart_num_, msg, len);
|
||||
#endif
|
||||
) {
|
||||
puts(msg);
|
||||
} else {
|
||||
// Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen
|
||||
size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg);
|
||||
uart_write_bytes(this->uart_num_, msg, len);
|
||||
uart_write_bytes(this->uart_num_, "\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
|
||||
@@ -33,7 +33,10 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) {
|
||||
// Single write with newline already in buffer (added by caller)
|
||||
this->hw_serial_->write(msg, len);
|
||||
}
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) {
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
|
||||
@@ -49,7 +49,7 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
void HOT Logger::write_msg_(const char *msg, size_t len) { this->hw_serial_->write(msg, len); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
|
||||
@@ -27,7 +27,7 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); }
|
||||
|
||||
const LogString *Logger::get_uart_selection_() {
|
||||
switch (this->uart_) {
|
||||
|
||||
@@ -62,7 +62,7 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
void HOT Logger::write_msg_(const char *msg, size_t) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("%s\n", msg);
|
||||
#endif
|
||||
|
||||
@@ -52,15 +52,7 @@ from .schemas import (
|
||||
from .styles import add_top_layer, styles_to_code, theme_to_code
|
||||
from .touchscreens import touchscreen_schema, touchscreens_to_code
|
||||
from .trigger import add_on_boot_triggers, generate_triggers
|
||||
from .types import (
|
||||
FontEngine,
|
||||
IdleTrigger,
|
||||
PlainTrigger,
|
||||
lv_font_t,
|
||||
lv_group_t,
|
||||
lv_style_t,
|
||||
lvgl_ns,
|
||||
)
|
||||
from .types import IdleTrigger, PlainTrigger, lv_font_t, lv_group_t, lv_style_t, lvgl_ns
|
||||
from .widgets import (
|
||||
LvScrActType,
|
||||
Widget,
|
||||
@@ -244,7 +236,6 @@ async def to_code(configs):
|
||||
cg.add_global(lvgl_ns.using)
|
||||
for font in helpers.esphome_fonts_used:
|
||||
await cg.get_variable(font)
|
||||
cg.new_Pvariable(ID(f"{font}_engine", True, type=FontEngine), MockObj(font))
|
||||
default_font = config_0[df.CONF_DEFAULT_FONT]
|
||||
if not lvalid.is_lv_font(default_font):
|
||||
add_define(
|
||||
@@ -256,7 +247,8 @@ async def to_code(configs):
|
||||
type=lv_font_t.operator("ptr").operator("const"),
|
||||
)
|
||||
cg.new_variable(
|
||||
globfont_id, MockObj(await lvalid.lv_font.process(default_font))
|
||||
globfont_id,
|
||||
MockObj(await lvalid.lv_font.process(default_font), "->").get_lv_font(),
|
||||
)
|
||||
add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT)
|
||||
else:
|
||||
@@ -331,7 +323,7 @@ async def to_code(configs):
|
||||
# This must be done after all widgets are created
|
||||
for comp in helpers.lvgl_components_required:
|
||||
cg.add_define(f"USE_LVGL_{comp.upper()}")
|
||||
if "transform_angle" in styles_used:
|
||||
if {"transform_angle", "transform_zoom"} & styles_used:
|
||||
add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
||||
for use in helpers.lv_uses:
|
||||
add_define(f"LV_USE_{use.upper()}")
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#include "lvgl_esphome.h"
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
namespace esphome {
|
||||
namespace lvgl {
|
||||
|
||||
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
|
||||
auto *fe = (FontEngine *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data(unicode_letter);
|
||||
if (gd == nullptr)
|
||||
return nullptr;
|
||||
// esph_log_d(TAG, "Returning bitmap @ %X", (uint32_t)gd->data);
|
||||
|
||||
return gd->data;
|
||||
}
|
||||
|
||||
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
|
||||
auto *fe = (FontEngine *) font->dsc;
|
||||
const auto *gd = fe->get_glyph_data(unicode_letter);
|
||||
if (gd == nullptr)
|
||||
return false;
|
||||
dsc->adv_w = gd->advance;
|
||||
dsc->ofs_x = gd->offset_x;
|
||||
dsc->ofs_y = fe->height - gd->height - gd->offset_y - fe->baseline;
|
||||
dsc->box_w = gd->width;
|
||||
dsc->box_h = gd->height;
|
||||
dsc->is_placeholder = 0;
|
||||
dsc->bpp = fe->bpp;
|
||||
return true;
|
||||
}
|
||||
|
||||
FontEngine::FontEngine(font::Font *esp_font) : font_(esp_font) {
|
||||
this->bpp = esp_font->get_bpp();
|
||||
this->lv_font_.dsc = this;
|
||||
this->lv_font_.line_height = this->height = esp_font->get_height();
|
||||
this->lv_font_.base_line = this->baseline = this->lv_font_.line_height - esp_font->get_baseline();
|
||||
this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
|
||||
this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
|
||||
this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
|
||||
this->lv_font_.underline_position = -1;
|
||||
this->lv_font_.underline_thickness = 1;
|
||||
}
|
||||
|
||||
const lv_font_t *FontEngine::get_lv_font() { return &this->lv_font_; }
|
||||
|
||||
const font::GlyphData *FontEngine::get_glyph_data(uint32_t unicode_letter) {
|
||||
if (unicode_letter == last_letter_)
|
||||
return this->last_data_;
|
||||
uint8_t unicode[5];
|
||||
memset(unicode, 0, sizeof unicode);
|
||||
if (unicode_letter > 0xFFFF) {
|
||||
unicode[0] = 0xF0 + ((unicode_letter >> 18) & 0x7);
|
||||
unicode[1] = 0x80 + ((unicode_letter >> 12) & 0x3F);
|
||||
unicode[2] = 0x80 + ((unicode_letter >> 6) & 0x3F);
|
||||
unicode[3] = 0x80 + (unicode_letter & 0x3F);
|
||||
} else if (unicode_letter > 0x7FF) {
|
||||
unicode[0] = 0xE0 + ((unicode_letter >> 12) & 0xF);
|
||||
unicode[1] = 0x80 + ((unicode_letter >> 6) & 0x3F);
|
||||
unicode[2] = 0x80 + (unicode_letter & 0x3F);
|
||||
} else if (unicode_letter > 0x7F) {
|
||||
unicode[0] = 0xC0 + ((unicode_letter >> 6) & 0x1F);
|
||||
unicode[1] = 0x80 + (unicode_letter & 0x3F);
|
||||
} else {
|
||||
unicode[0] = unicode_letter;
|
||||
}
|
||||
int match_length;
|
||||
int glyph_n = this->font_->match_next_glyph(unicode, &match_length);
|
||||
if (glyph_n < 0)
|
||||
return nullptr;
|
||||
this->last_data_ = this->font_->get_glyphs()[glyph_n].get_glyph_data();
|
||||
this->last_letter_ = unicode_letter;
|
||||
return this->last_data_;
|
||||
}
|
||||
} // namespace lvgl
|
||||
} // namespace esphome
|
||||
#endif // USES_LVGL_FONT
|
||||
@@ -493,6 +493,7 @@ class LvFont(LValidator):
|
||||
return LV_FONTS
|
||||
if is_lv_font(value):
|
||||
return lv_builtin_font(value)
|
||||
add_lv_use("font")
|
||||
fontval = cv.use_id(Font)(value)
|
||||
esphome_fonts_used.add(fontval)
|
||||
return requires_component("font")(fontval)
|
||||
@@ -502,7 +503,9 @@ class LvFont(LValidator):
|
||||
async def process(self, value, args=()):
|
||||
if is_lv_font(value):
|
||||
return literal(f"&lv_font_{value}")
|
||||
return literal(f"{value}_engine->get_lv_font()")
|
||||
if isinstance(value, str):
|
||||
return literal(f"{value}")
|
||||
return await super().process(value, args)
|
||||
|
||||
|
||||
lv_font = LvFont()
|
||||
|
||||
@@ -50,6 +50,14 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
|
||||
static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_332;
|
||||
#endif // LV_COLOR_DEPTH
|
||||
|
||||
#ifdef USE_LVGL_FONT
|
||||
inline void lv_obj_set_style_text_font(lv_obj_t *obj, const font::Font *font, lv_style_selector_t part) {
|
||||
lv_obj_set_style_text_font(obj, font->get_lv_font(), part);
|
||||
}
|
||||
inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) {
|
||||
lv_style_set_text_font(style, font->get_lv_font());
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
// Shortcut / overload, so that the source of an image can easily be updated
|
||||
// from within a lambda.
|
||||
@@ -134,24 +142,6 @@ template<typename... Ts> class ObjUpdateAction : public Action<Ts...> {
|
||||
protected:
|
||||
std::function<void(Ts...)> lamb_;
|
||||
};
|
||||
#ifdef USE_LVGL_FONT
|
||||
class FontEngine {
|
||||
public:
|
||||
FontEngine(font::Font *esp_font);
|
||||
const lv_font_t *get_lv_font();
|
||||
|
||||
const font::GlyphData *get_glyph_data(uint32_t unicode_letter);
|
||||
uint16_t baseline{};
|
||||
uint16_t height{};
|
||||
uint8_t bpp{};
|
||||
|
||||
protected:
|
||||
font::Font *font_{};
|
||||
uint32_t last_letter_{};
|
||||
const font::GlyphData *last_data_{};
|
||||
lv_font_t lv_font_{};
|
||||
};
|
||||
#endif // USE_LVGL_FONT
|
||||
#ifdef USE_LVGL_ANIMIMG
|
||||
void lv_animimg_stop(lv_obj_t *obj);
|
||||
#endif // USE_LVGL_ANIMIMG
|
||||
|
||||
@@ -45,7 +45,6 @@ lv_coord_t = cg.global_ns.namespace("lv_coord_t")
|
||||
lv_event_code_t = cg.global_ns.enum("lv_event_code_t")
|
||||
lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t")
|
||||
lv_key_t = cg.global_ns.enum("lv_key_t")
|
||||
FontEngine = lvgl_ns.class_("FontEngine")
|
||||
PlainTrigger = esphome_ns.class_("Trigger<>", automation.Trigger.template())
|
||||
DrawEndTrigger = esphome_ns.class_(
|
||||
"Trigger<uint32_t, uint32_t>", automation.Trigger.template(cg.uint32, cg.uint32)
|
||||
|
||||
@@ -135,8 +135,7 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
|
||||
#ifdef USE_DASHBOARD_IMPORT
|
||||
MDNS_STATIC_CONST_CHAR(TXT_PACKAGE_IMPORT_URL, "package_import_url");
|
||||
txt_records.push_back(
|
||||
{MDNS_STR(TXT_PACKAGE_IMPORT_URL), MDNS_STR(dashboard_import::get_package_import_url().c_str())});
|
||||
txt_records.push_back({MDNS_STR(TXT_PACKAGE_IMPORT_URL), MDNS_STR(dashboard_import::get_package_import_url())});
|
||||
#endif
|
||||
}
|
||||
#endif // USE_API
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components.psram import is_guaranteed as psram_is_guaranteed
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENABLE_IPV6, CONF_MIN_IPV6_ADDR_COUNT
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
@@ -9,6 +11,13 @@ from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["mdns"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# High performance networking tracking infrastructure
|
||||
# Components can request high performance networking and this configures lwip and WiFi settings
|
||||
KEY_HIGH_PERFORMANCE_NETWORKING = "high_performance_networking"
|
||||
CONF_ENABLE_HIGH_PERFORMANCE = "enable_high_performance"
|
||||
|
||||
network_ns = cg.esphome_ns.namespace("network")
|
||||
IPAddress = network_ns.class_("IPAddress")
|
||||
|
||||
@@ -47,6 +56,55 @@ def ip_address_literal(ip: str | int | None) -> cg.MockObj:
|
||||
return IPAddress(str(ip))
|
||||
|
||||
|
||||
def require_high_performance_networking() -> None:
|
||||
"""Request high performance networking for network and WiFi.
|
||||
|
||||
Call this from components that need optimized network performance for streaming
|
||||
or high-throughput data transfer. This enables high performance mode which
|
||||
configures both lwip TCP settings and WiFi driver settings for improved
|
||||
network performance.
|
||||
|
||||
Settings applied (ESP-IDF only):
|
||||
- lwip: Larger TCP buffers, windows, and mailbox sizes
|
||||
- WiFi: Increased RX/TX buffers, AMPDU aggregation, PSRAM allocation (set by wifi component)
|
||||
|
||||
Configuration is PSRAM-aware:
|
||||
- With PSRAM guaranteed: Aggressive settings (512 RX buffers, 512KB TCP windows)
|
||||
- Without PSRAM: Conservative optimized settings (64 buffers, 65KB TCP windows)
|
||||
|
||||
Example:
|
||||
from esphome.components import network
|
||||
|
||||
def _request_high_performance_networking(config):
|
||||
network.require_high_performance_networking()
|
||||
return config
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
...,
|
||||
_request_high_performance_networking,
|
||||
)
|
||||
"""
|
||||
# Only set up once (idempotent - multiple components can call this)
|
||||
if not CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False):
|
||||
CORE.data[KEY_HIGH_PERFORMANCE_NETWORKING] = True
|
||||
|
||||
|
||||
def has_high_performance_networking() -> bool:
|
||||
"""Check if high performance networking mode is enabled.
|
||||
|
||||
Returns True when high performance networking has been requested by a
|
||||
component or explicitly enabled in the network configuration. This indicates
|
||||
that lwip and WiFi will use optimized buffer sizes and settings.
|
||||
|
||||
This function should be called during code generation (to_code phase) by
|
||||
components that need to apply performance-related settings.
|
||||
|
||||
Returns:
|
||||
bool: True if high performance networking is enabled, False otherwise
|
||||
"""
|
||||
return CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.SplitDefault(
|
||||
@@ -71,6 +129,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_ENABLE_HIGH_PERFORMANCE): cv.All(cv.boolean, cv.only_on_esp32),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -80,6 +139,70 @@ async def to_code(config):
|
||||
cg.add_define("USE_NETWORK")
|
||||
if CORE.using_arduino and CORE.is_esp32:
|
||||
cg.add_library("Networking", None)
|
||||
|
||||
# Apply high performance networking settings
|
||||
# Config can explicitly enable/disable, or default to component-driven behavior
|
||||
enable_high_perf = config.get(CONF_ENABLE_HIGH_PERFORMANCE)
|
||||
component_requested = CORE.data.get(KEY_HIGH_PERFORMANCE_NETWORKING, False)
|
||||
|
||||
# Explicit config overrides component request
|
||||
should_enable = (
|
||||
enable_high_perf if enable_high_perf is not None else component_requested
|
||||
)
|
||||
|
||||
# Log when user explicitly disables but a component requested it
|
||||
if enable_high_perf is False and component_requested:
|
||||
_LOGGER.info(
|
||||
"High performance networking disabled by user configuration (overriding component request)"
|
||||
)
|
||||
|
||||
if CORE.is_esp32 and CORE.using_esp_idf and should_enable:
|
||||
# Check if PSRAM is guaranteed (set by psram component during final validation)
|
||||
psram_guaranteed = psram_is_guaranteed()
|
||||
|
||||
if psram_guaranteed:
|
||||
_LOGGER.info(
|
||||
"Applying high-performance lwip settings (PSRAM guaranteed): 512KB TCP windows, 512 mailbox sizes"
|
||||
)
|
||||
# PSRAM is guaranteed - use aggressive settings
|
||||
# Higher maximum values are allowed because CONFIG_LWIP_WND_SCALE is set to true
|
||||
# CONFIG_LWIP_WND_SCALE can only be enabled if CONFIG_SPIRAM_IGNORE_NOTFOUND isn't set
|
||||
# Based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702
|
||||
|
||||
# Enable window scaling for much larger TCP windows
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_WND_SCALE", True)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RCV_SCALE", 3)
|
||||
|
||||
# Large TCP buffers and windows (requires PSRAM)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 512000)
|
||||
|
||||
# Large mailboxes for high throughput
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 512)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 512)
|
||||
|
||||
# TCP connection limits
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_ACTIVE_TCP", 16)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_LISTENING_TCP", 16)
|
||||
|
||||
# TCP optimizations
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MAXRTX", 12)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SYNMAXRTX", 6)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MSS", 1436)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_MSL", 60000)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_OVERSIZE_MSS", True)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_QUEUE_OOSEQ", True)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Applying optimized lwip settings: 65KB TCP windows, 64 mailbox sizes"
|
||||
)
|
||||
# PSRAM not guaranteed - use more conservative, but still optimized settings
|
||||
# Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64)
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64)
|
||||
|
||||
if (enable_ipv6 := config.get(CONF_ENABLE_IPV6, None)) is not None:
|
||||
cg.add_define("USE_NETWORK_IPV6", enable_ipv6)
|
||||
if enable_ipv6:
|
||||
|
||||
@@ -118,10 +118,10 @@ struct IPAddress {
|
||||
operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); }
|
||||
#endif
|
||||
|
||||
bool is_set() { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
|
||||
bool is_ip4() { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() { return ip_addr_ismulticast(&ip_addr_); }
|
||||
bool is_set() const { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
|
||||
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
|
||||
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
|
||||
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
|
||||
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
|
||||
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
|
||||
|
||||
@@ -25,6 +25,7 @@ from esphome.const import (
|
||||
CONF_FRAMEWORK,
|
||||
CONF_ID,
|
||||
CONF_RESET_PIN,
|
||||
CONF_VOLTAGE,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
@@ -102,6 +103,11 @@ nrf52_ns = cg.esphome_ns.namespace("nrf52")
|
||||
DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component)
|
||||
|
||||
CONF_DFU = "dfu"
|
||||
CONF_DCDC = "dcdc"
|
||||
CONF_REG0 = "reg0"
|
||||
CONF_UICR_ERASE = "uicr_erase"
|
||||
|
||||
VOLTAGE_LEVELS = [1.8, 2.1, 2.4, 2.7, 3.0, 3.3]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
_detect_bootloader,
|
||||
@@ -116,6 +122,16 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DCDC, default=True): cv.boolean,
|
||||
cv.Optional(CONF_REG0): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VOLTAGE): cv.All(
|
||||
cv.voltage,
|
||||
cv.one_of(*VOLTAGE_LEVELS, float=True),
|
||||
),
|
||||
cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -182,6 +198,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if dfu_config := config.get(CONF_DFU):
|
||||
CORE.add_job(_dfu_to_code, dfu_config)
|
||||
zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC])
|
||||
|
||||
if reg0_config := config.get(CONF_REG0):
|
||||
value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE])
|
||||
cg.add_define("USE_NRF52_REG0_VOUT", value)
|
||||
if reg0_config[CONF_UICR_ERASE]:
|
||||
cg.add_define("USE_NRF52_UICR_ERASE")
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
|
||||
|
||||
121
esphome/components/nrf52/uicr.cpp
Normal file
121
esphome/components/nrf52/uicr.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_NRF52_REG0_VOUT
|
||||
#include <zephyr/init.h>
|
||||
#include <hal/nrf_power.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
|
||||
extern "C" {
|
||||
void nvmc_config(uint32_t mode);
|
||||
void nvmc_wait();
|
||||
nrfx_err_t nrfx_nvmc_uicr_erase();
|
||||
}
|
||||
|
||||
namespace esphome::nrf52 {
|
||||
|
||||
enum class StatusFlags : uint8_t {
|
||||
OK = 0x00,
|
||||
NEED_RESET = 0x01,
|
||||
NEED_ERASE = 0x02,
|
||||
};
|
||||
|
||||
constexpr StatusFlags &operator|=(StatusFlags &a, StatusFlags b) {
|
||||
a = static_cast<StatusFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
||||
return a;
|
||||
}
|
||||
|
||||
constexpr bool operator&(StatusFlags a, StatusFlags b) {
|
||||
return (static_cast<uint8_t>(a) & static_cast<uint8_t>(b)) != 0;
|
||||
}
|
||||
|
||||
static bool regout0_ok() {
|
||||
return (NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) == (USE_NRF52_REG0_VOUT << UICR_REGOUT0_VOUT_Pos);
|
||||
}
|
||||
|
||||
static StatusFlags set_regout0() {
|
||||
/* If the board is powered from USB (high voltage mode),
|
||||
* GPIO output voltage is set to 1.8 volts by default.
|
||||
*/
|
||||
if (!regout0_ok()) {
|
||||
nvmc_config(NVMC_CONFIG_WEN_Wen);
|
||||
NRF_UICR->REGOUT0 =
|
||||
(NRF_UICR->REGOUT0 & ~((uint32_t) UICR_REGOUT0_VOUT_Msk)) | (USE_NRF52_REG0_VOUT << UICR_REGOUT0_VOUT_Pos);
|
||||
nvmc_wait();
|
||||
nvmc_config(NVMC_CONFIG_WEN_Ren);
|
||||
return regout0_ok() ? StatusFlags::NEED_RESET : StatusFlags::NEED_ERASE;
|
||||
}
|
||||
return StatusFlags::OK;
|
||||
}
|
||||
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/6a9a6a3e6d0f86918e9286188426a279976645bd/lib/sdk11/components/libraries/bootloader_dfu/dfu_types.h#L61
|
||||
constexpr uint32_t BOOTLOADER_REGION_START = 0x000F4000;
|
||||
constexpr uint32_t BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS = 0x000FE000;
|
||||
|
||||
static bool bootloader_ok() {
|
||||
return NRF_UICR->NRFFW[0] == BOOTLOADER_REGION_START && NRF_UICR->NRFFW[1] == BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS;
|
||||
}
|
||||
|
||||
static StatusFlags fix_bootloader() {
|
||||
if (!bootloader_ok()) {
|
||||
nvmc_config(NVMC_CONFIG_WEN_Wen);
|
||||
NRF_UICR->NRFFW[0] = BOOTLOADER_REGION_START;
|
||||
NRF_UICR->NRFFW[1] = BOOTLOADER_MBR_PARAMS_PAGE_ADDRESS;
|
||||
nvmc_wait();
|
||||
nvmc_config(NVMC_CONFIG_WEN_Ren);
|
||||
return bootloader_ok() ? StatusFlags::NEED_RESET : StatusFlags::NEED_ERASE;
|
||||
}
|
||||
return StatusFlags::OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0]
|
||||
|
||||
static StatusFlags set_uicr() {
|
||||
StatusFlags status = StatusFlags::OK;
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
if (BOOTLOADER_VERSION_REGISTER <= 0x902) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("cannot control regout0 for %#x\n", BOOTLOADER_VERSION_REGISTER);
|
||||
#endif
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
status |= set_regout0();
|
||||
}
|
||||
#ifndef USE_BOOTLOADER_MCUBOOT
|
||||
status |= fix_bootloader();
|
||||
#endif
|
||||
return status;
|
||||
}
|
||||
|
||||
static int board_esphome_init() {
|
||||
StatusFlags status = set_uicr();
|
||||
|
||||
#ifdef USE_NRF52_UICR_ERASE
|
||||
if (status & StatusFlags::NEED_ERASE) {
|
||||
nrfx_err_t ret = nrfx_nvmc_uicr_erase();
|
||||
if (ret != NRFX_SUCCESS) {
|
||||
#ifdef CONFIG_PRINTK
|
||||
printk("nrfx_nvmc_uicr_erase failed %d\n", ret);
|
||||
#endif
|
||||
} else {
|
||||
status |= set_uicr();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (status & StatusFlags::NEED_RESET) {
|
||||
/* a reset is required for changes to take effect */
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace esphome::nrf52
|
||||
|
||||
static int board_esphome_init() { return esphome::nrf52::board_esphome_init(); }
|
||||
|
||||
SYS_INIT(board_esphome_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
||||
|
||||
#endif
|
||||
@@ -1,8 +1,7 @@
|
||||
#include "automation.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
namespace esphome::number {
|
||||
|
||||
static const char *const TAG = "number.automation";
|
||||
|
||||
@@ -52,5 +51,4 @@ void ValueRangeTrigger::on_state_(float state) {
|
||||
this->rtc_.save(&in_range);
|
||||
}
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
namespace esphome::number {
|
||||
|
||||
class NumberStateTrigger : public Trigger<float> {
|
||||
public:
|
||||
@@ -91,5 +90,4 @@ template<typename... Ts> class NumberInRangeCondition : public Condition<Ts...>
|
||||
float max_{NAN};
|
||||
};
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/controller_registry.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
namespace esphome::number {
|
||||
|
||||
static const char *const TAG = "number";
|
||||
|
||||
@@ -43,5 +42,4 @@ void Number::add_on_state_callback(std::function<void(float)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
#include "number_call.h"
|
||||
#include "number_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
namespace esphome::number {
|
||||
|
||||
class Number;
|
||||
void log_number(const char *tag, const char *prefix, const char *type, Number *obj);
|
||||
@@ -53,5 +52,4 @@ class Number : public EntityBase {
|
||||
CallbackManager<void(float)> state_callback_;
|
||||
};
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
#include "number.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
namespace esphome::number {
|
||||
|
||||
static const char *const TAG = "number";
|
||||
|
||||
@@ -125,5 +124,4 @@ void NumberCall::perform() {
|
||||
this->parent_->control(target_value);
|
||||
}
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "number_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
namespace esphome::number {
|
||||
|
||||
class Number;
|
||||
|
||||
@@ -44,5 +43,4 @@ class NumberCall {
|
||||
bool cycle_;
|
||||
};
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "number_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
namespace esphome::number {
|
||||
|
||||
static const char *const TAG = "number";
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
namespace esphome::number {
|
||||
|
||||
enum NumberMode : uint8_t {
|
||||
NUMBER_MODE_AUTO = 0,
|
||||
@@ -35,5 +34,4 @@ class NumberTraits : public EntityBase_DeviceClass, public EntityBase_UnitOfMeas
|
||||
NumberMode mode_{NUMBER_MODE_AUTO};
|
||||
};
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
} // namespace esphome::number
|
||||
|
||||
@@ -23,7 +23,7 @@ void PCF85063Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float PCF85063Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -23,7 +23,7 @@ void PCF8563Component::dump_config() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
float PCF8563Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
@@ -35,6 +35,9 @@ DOMAIN = "psram"
|
||||
|
||||
DEPENDENCIES = [PLATFORM_ESP32]
|
||||
|
||||
# PSRAM availability tracking for cross-component coordination
|
||||
KEY_PSRAM_GUARANTEED = "psram_guaranteed"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
psram_ns = cg.esphome_ns.namespace(DOMAIN)
|
||||
@@ -71,6 +74,23 @@ def supported() -> bool:
|
||||
return variant in SPIRAM_MODES
|
||||
|
||||
|
||||
def is_guaranteed() -> bool:
|
||||
"""Check if PSRAM is guaranteed to be available.
|
||||
|
||||
Returns True when PSRAM is configured with both 'disabled: false' and
|
||||
'ignore_not_found: false', meaning the device will fail to boot if PSRAM
|
||||
is not found. This ensures safe use of high buffer configurations that
|
||||
depend on PSRAM.
|
||||
|
||||
This function should be called during code generation (to_code phase) by
|
||||
components that need to know PSRAM availability for configuration decisions.
|
||||
|
||||
Returns:
|
||||
bool: True if PSRAM is guaranteed, False otherwise
|
||||
"""
|
||||
return CORE.data.get(KEY_PSRAM_GUARANTEED, False)
|
||||
|
||||
|
||||
def validate_psram_mode(config):
|
||||
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
||||
if config[CONF_SPEED] == "120MHZ":
|
||||
@@ -131,7 +151,22 @@ def get_config_schema(config):
|
||||
|
||||
CONFIG_SCHEMA = get_config_schema
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_psram_mode
|
||||
|
||||
def _store_psram_guaranteed(config):
|
||||
"""Store PSRAM guaranteed status in CORE.data for other components.
|
||||
|
||||
PSRAM is "guaranteed" when it will fail if not found, ensuring safe use
|
||||
of high buffer configurations in network/wifi components.
|
||||
|
||||
Called during final validation to ensure the flag is available
|
||||
before any to_code() functions run.
|
||||
"""
|
||||
psram_guaranteed = not config[CONF_DISABLED] and not config[CONF_IGNORE_NOT_FOUND]
|
||||
CORE.data[KEY_PSRAM_GUARANTEED] = psram_guaranteed
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(validate_psram_mode, _store_psram_guaranteed)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -62,6 +62,7 @@ void RX8130Component::update() { this->read_time(); }
|
||||
void RX8130Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "RX8130:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
|
||||
void RX8130Component::read_time() {
|
||||
|
||||
@@ -46,14 +46,14 @@ template<typename... Ts> class Script : public ScriptLogger, public Trigger<Ts..
|
||||
|
||||
// execute this script using a tuple that contains the arguments
|
||||
void execute_tuple(const std::tuple<Ts...> &tuple) {
|
||||
this->execute_tuple_(tuple, typename gens<sizeof...(Ts)>::type());
|
||||
this->execute_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
|
||||
}
|
||||
|
||||
// Internal function to give scripts readable names.
|
||||
void set_name(const LogString *name) { name_ = name; }
|
||||
|
||||
protected:
|
||||
template<int... S> void execute_tuple_(const std::tuple<Ts...> &tuple, seq<S...> /*unused*/) {
|
||||
template<size_t... S> void execute_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
|
||||
this->execute(std::get<S>(tuple)...);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ template<typename... Ts> class QueueingScript : public Script<Ts...>, public Com
|
||||
const size_t queue_capacity = static_cast<size_t>(this->max_runs_ - 1);
|
||||
auto tuple_ptr = std::move(this->var_queue_[this->queue_front_]);
|
||||
this->queue_front_ = (this->queue_front_ + 1) % queue_capacity;
|
||||
this->trigger_tuple_(*tuple_ptr, typename gens<sizeof...(Ts)>::type());
|
||||
this->trigger_tuple_(*tuple_ptr, std::make_index_sequence<sizeof...(Ts)>{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ template<typename... Ts> class QueueingScript : public Script<Ts...>, public Com
|
||||
}
|
||||
}
|
||||
|
||||
template<int... S> void trigger_tuple_(const std::tuple<Ts...> &tuple, seq<S...> /*unused*/) {
|
||||
template<size_t... S> void trigger_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
|
||||
this->trigger(std::get<S>(tuple)...);
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>,
|
||||
|
||||
while (!this->param_queue_.empty()) {
|
||||
auto ¶ms = this->param_queue_.front();
|
||||
this->play_next_tuple_(params, typename gens<sizeof...(Ts)>::type());
|
||||
this->play_next_tuple_(params, std::make_index_sequence<sizeof...(Ts)>{});
|
||||
this->param_queue_.pop_front();
|
||||
}
|
||||
// Queue is now empty - disable loop until next play_complex
|
||||
@@ -321,7 +321,7 @@ template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>,
|
||||
}
|
||||
|
||||
protected:
|
||||
template<int... S> void play_next_tuple_(const std::tuple<Ts...> &tuple, seq<S...> /*unused*/) {
|
||||
template<size_t... S> void play_next_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
|
||||
this->play_next_(std::get<S>(tuple)...);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ void SNTPComponent::dump_config() {
|
||||
for (auto &server : this->servers_) {
|
||||
ESP_LOGCONFIG(TAG, " Server %zu: '%s'", i++, server);
|
||||
}
|
||||
RealTimeClock::dump_config();
|
||||
}
|
||||
void SNTPComponent::update() {
|
||||
#if !defined(USE_ESP32)
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import time as time_
|
||||
from esphome.config_helpers import merge_config
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PLATFORM,
|
||||
CONF_SERVERS,
|
||||
CONF_TIME,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
@@ -12,13 +17,74 @@ from esphome.const import (
|
||||
PLATFORM_RTL87XX,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ["network"]
|
||||
|
||||
CONF_SNTP = "sntp"
|
||||
|
||||
sntp_ns = cg.esphome_ns.namespace("sntp")
|
||||
SNTPComponent = sntp_ns.class_("SNTPComponent", time_.RealTimeClock)
|
||||
|
||||
DEFAULT_SERVERS = ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org"]
|
||||
|
||||
|
||||
def _sntp_final_validate(config: ConfigType) -> None:
|
||||
"""Merge multiple SNTP instances into one, similar to OTA merging behavior."""
|
||||
full_conf = fv.full_config.get()
|
||||
time_confs = full_conf.get(CONF_TIME, [])
|
||||
|
||||
sntp_configs: list[ConfigType] = []
|
||||
other_time_configs: list[ConfigType] = []
|
||||
|
||||
for time_conf in time_confs:
|
||||
if time_conf.get(CONF_PLATFORM) == CONF_SNTP:
|
||||
sntp_configs.append(time_conf)
|
||||
else:
|
||||
other_time_configs.append(time_conf)
|
||||
|
||||
if len(sntp_configs) <= 1:
|
||||
return
|
||||
|
||||
# Merge all SNTP configs into the first one
|
||||
merged = sntp_configs[0]
|
||||
for sntp_conf in sntp_configs[1:]:
|
||||
# Validate that IDs are consistent if manually specified
|
||||
if merged[CONF_ID].is_manual and sntp_conf[CONF_ID].is_manual:
|
||||
raise cv.Invalid(
|
||||
f"Found multiple SNTP configurations but {CONF_ID} is inconsistent"
|
||||
)
|
||||
merged = merge_config(merged, sntp_conf)
|
||||
|
||||
# Deduplicate servers while preserving order
|
||||
servers = merged[CONF_SERVERS]
|
||||
unique_servers = list(dict.fromkeys(servers))
|
||||
|
||||
# Warn if we're dropping servers due to 3-server limit
|
||||
if len(unique_servers) > 3:
|
||||
dropped = unique_servers[3:]
|
||||
unique_servers = unique_servers[:3]
|
||||
_LOGGER.warning(
|
||||
"SNTP supports maximum 3 servers. Dropped excess server(s): %s",
|
||||
dropped,
|
||||
)
|
||||
|
||||
merged[CONF_SERVERS] = unique_servers
|
||||
|
||||
_LOGGER.warning(
|
||||
"Found and merged %d SNTP time configurations into one instance",
|
||||
len(sntp_configs),
|
||||
)
|
||||
|
||||
# Replace time configs with merged SNTP + other time platforms
|
||||
other_time_configs.append(merged)
|
||||
full_conf[CONF_TIME] = other_time_configs
|
||||
fv.full_config.set(full_conf)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
time_.TIME_SCHEMA.extend(
|
||||
{
|
||||
@@ -40,6 +106,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _sntp_final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
servers = config[CONF_SERVERS]
|
||||
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
from esphome import automation, external_files
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, media_player, psram, speaker
|
||||
from esphome.components import audio, esp32, media_player, network, psram, speaker
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
@@ -32,6 +32,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
AUTO_LOAD = ["audio"]
|
||||
DEPENDENCIES = ["network"]
|
||||
|
||||
CODEOWNERS = ["@kahrendt", "@synesthesiam"]
|
||||
DOMAIN = "media_player"
|
||||
@@ -280,6 +281,18 @@ PIPELINE_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _request_high_performance_networking(config):
|
||||
"""Request high performance networking for streaming media.
|
||||
|
||||
Speaker media player streams audio data, so it always benefits from
|
||||
optimized WiFi and lwip settings regardless of codec support.
|
||||
Called during config validation to ensure flags are set before to_code().
|
||||
"""
|
||||
network.require_high_performance_networking()
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
media_player.media_player_schema(SpeakerMediaPlayer).extend(
|
||||
{
|
||||
@@ -304,6 +317,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.only_with_esp_idf,
|
||||
_validate_repeated_speaker,
|
||||
_request_high_performance_networking,
|
||||
)
|
||||
|
||||
|
||||
@@ -321,28 +335,10 @@ FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config):
|
||||
if CORE.data[DOMAIN][config[CONF_ID].id][CONF_CODEC_SUPPORT_ENABLED]:
|
||||
# Compile all supported audio codecs and optimize the wifi settings
|
||||
|
||||
# Compile all supported audio codecs
|
||||
cg.add_define("USE_AUDIO_FLAC_SUPPORT", True)
|
||||
cg.add_define("USE_AUDIO_MP3_SUPPORT", True)
|
||||
|
||||
# Based on https://github.com/espressif/esp-idf/blob/release/v5.4/examples/wifi/iperf/sdkconfig.defaults.esp32
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM", 16)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_TX_BA_WIN", 32)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", True)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_RX_BA_WIN", 32)
|
||||
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_SND_BUF_DEFAULT", 65534)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_WND_DEFAULT", 65534)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCP_RECVMBOX_SIZE", 64)
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_RECVMBOX_SIZE", 64)
|
||||
|
||||
# Allocate wifi buffers in PSRAM
|
||||
esp32.add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True)
|
||||
|
||||
var = await media_player.new_media_player(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -137,7 +137,11 @@ async def to_code(config):
|
||||
cg.add(var.set_arming_night_time(config[CONF_ARMING_NIGHT_TIME]))
|
||||
supports_arm_night = True
|
||||
|
||||
for sensor in config.get(CONF_BINARY_SENSORS, []):
|
||||
if sensors := config.get(CONF_BINARY_SENSORS, []):
|
||||
# Initialize FixedVector with the exact number of sensors
|
||||
cg.add(var.init_sensors(len(sensors)))
|
||||
|
||||
for sensor in sensors:
|
||||
bs = await cg.get_variable(sensor[CONF_INPUT])
|
||||
|
||||
flags = BinarySensorFlags[FLAG_NORMAL]
|
||||
|
||||
@@ -20,10 +20,13 @@ void TemplateAlarmControlPanel::add_sensor(binary_sensor::BinarySensor *sensor,
|
||||
// Save the flags and type. Assign a store index for the per sensor data type.
|
||||
SensorDataStore sd;
|
||||
sd.last_chime_state = false;
|
||||
this->sensor_map_[sensor].flags = flags;
|
||||
this->sensor_map_[sensor].type = type;
|
||||
AlarmSensor alarm_sensor;
|
||||
alarm_sensor.sensor = sensor;
|
||||
alarm_sensor.info.flags = flags;
|
||||
alarm_sensor.info.type = type;
|
||||
alarm_sensor.info.store_index = this->next_store_index_++;
|
||||
this->sensors_.push_back(alarm_sensor);
|
||||
this->sensor_data_.push_back(sd);
|
||||
this->sensor_map_[sensor].store_index = this->next_store_index_++;
|
||||
};
|
||||
|
||||
static const LogString *sensor_type_to_string(AlarmSensorType type) {
|
||||
@@ -45,7 +48,7 @@ void TemplateAlarmControlPanel::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"TemplateAlarmControlPanel:\n"
|
||||
" Current State: %s\n"
|
||||
" Number of Codes: %u\n"
|
||||
" Number of Codes: %zu\n"
|
||||
" Requires Code To Arm: %s\n"
|
||||
" Arming Away Time: %" PRIu32 "s\n"
|
||||
" Arming Home Time: %" PRIu32 "s\n"
|
||||
@@ -58,7 +61,8 @@ void TemplateAlarmControlPanel::dump_config() {
|
||||
(this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000),
|
||||
(this->trigger_time_ / 1000), this->get_supported_features());
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto const &[sensor, info] : this->sensor_map_) {
|
||||
for (const auto &alarm_sensor : this->sensors_) {
|
||||
const uint16_t flags = alarm_sensor.info.flags;
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Binary Sensor:\n"
|
||||
" Name: %s\n"
|
||||
@@ -67,11 +71,10 @@ void TemplateAlarmControlPanel::dump_config() {
|
||||
" Armed night bypass: %s\n"
|
||||
" Auto bypass: %s\n"
|
||||
" Chime mode: %s",
|
||||
sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(info.type)),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO),
|
||||
TRUEFALSE(info.flags & BINARY_SENSOR_MODE_CHIME));
|
||||
alarm_sensor.sensor->get_name().c_str(), LOG_STR_ARG(sensor_type_to_string(alarm_sensor.info.type)),
|
||||
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_ARMED_HOME),
|
||||
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_ARMED_NIGHT),
|
||||
TRUEFALSE(flags & BINARY_SENSOR_MODE_BYPASS_AUTO), TRUEFALSE(flags & BINARY_SENSOR_MODE_CHIME));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -121,7 +124,9 @@ void TemplateAlarmControlPanel::loop() {
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
// Test all of the sensors regardless of the alarm panel state
|
||||
for (auto const &[sensor, info] : this->sensor_map_) {
|
||||
for (const auto &alarm_sensor : this->sensors_) {
|
||||
const auto &info = alarm_sensor.info;
|
||||
auto *sensor = alarm_sensor.sensor;
|
||||
// Check for chime zones
|
||||
if (info.flags & BINARY_SENSOR_MODE_CHIME) {
|
||||
// Look for the transition from closed to open
|
||||
@@ -242,11 +247,11 @@ void TemplateAlarmControlPanel::arm_(optional<std::string> code, alarm_control_p
|
||||
|
||||
void TemplateAlarmControlPanel::bypass_before_arming() {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto const &[sensor, info] : this->sensor_map_) {
|
||||
for (const auto &alarm_sensor : this->sensors_) {
|
||||
// Check for faulted bypass_auto sensors and remove them from monitoring
|
||||
if ((info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor->state)) {
|
||||
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", sensor->get_name().c_str());
|
||||
this->bypassed_sensor_indicies_.push_back(info.store_index);
|
||||
if ((alarm_sensor.info.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (alarm_sensor.sensor->state)) {
|
||||
ESP_LOGW(TAG, "'%s' is faulted and will be automatically bypassed", alarm_sensor.sensor->get_name().c_str());
|
||||
this->bypassed_sensor_indicies_.push_back(alarm_sensor.info.store_index);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
|
||||
|
||||
@@ -49,6 +50,13 @@ struct SensorInfo {
|
||||
uint8_t store_index;
|
||||
};
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
struct AlarmSensor {
|
||||
binary_sensor::BinarySensor *sensor;
|
||||
SensorInfo info;
|
||||
};
|
||||
#endif
|
||||
|
||||
class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControlPanel, public Component {
|
||||
public:
|
||||
TemplateAlarmControlPanel();
|
||||
@@ -63,6 +71,12 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
|
||||
void bypass_before_arming();
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
/** Initialize the sensors vector with the specified capacity.
|
||||
*
|
||||
* @param capacity The number of sensors to allocate space for.
|
||||
*/
|
||||
void init_sensors(size_t capacity) { this->sensors_.init(capacity); }
|
||||
|
||||
/** Add a binary_sensor to the alarm_panel.
|
||||
*
|
||||
* @param sensor The BinarySensor instance.
|
||||
@@ -122,8 +136,8 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl
|
||||
protected:
|
||||
void control(const alarm_control_panel::AlarmControlPanelCall &call) override;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
// This maps a binary sensor to its alarm specific info
|
||||
std::map<binary_sensor::BinarySensor *, SensorInfo> sensor_map_;
|
||||
// List of binary sensors with their alarm-specific info
|
||||
FixedVector<AlarmSensor> sensors_;
|
||||
// a list of automatically bypassed sensors
|
||||
std::vector<uint8_t> bypassed_sensor_indicies_;
|
||||
#endif
|
||||
|
||||
@@ -945,6 +945,10 @@ async def to_code(config):
|
||||
cg.add(var.set_humidity_hysteresis(config[CONF_HUMIDITY_HYSTERESIS]))
|
||||
|
||||
if CONF_PRESET in config:
|
||||
# Separate standard and custom presets, and build preset config variables
|
||||
standard_presets: list[tuple[cg.MockObj, cg.MockObj]] = []
|
||||
custom_presets: list[tuple[str, cg.MockObj]] = []
|
||||
|
||||
for preset_config in config[CONF_PRESET]:
|
||||
name = preset_config[CONF_NAME]
|
||||
standard_preset = None
|
||||
@@ -987,9 +991,39 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
if standard_preset is not None:
|
||||
cg.add(var.set_preset_config(standard_preset, preset_target_variable))
|
||||
standard_presets.append((standard_preset, preset_target_variable))
|
||||
else:
|
||||
cg.add(var.set_custom_preset_config(name, preset_target_variable))
|
||||
custom_presets.append((name, preset_target_variable))
|
||||
|
||||
# Build initializer list for standard presets
|
||||
if standard_presets:
|
||||
cg.add(
|
||||
var.set_preset_config(
|
||||
[
|
||||
cg.StructInitializer(
|
||||
thermostat_ns.struct("ThermostatPresetEntry"),
|
||||
("preset", preset),
|
||||
("config", preset_var),
|
||||
)
|
||||
for preset, preset_var in standard_presets
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# Build initializer list for custom presets
|
||||
if custom_presets:
|
||||
cg.add(
|
||||
var.set_custom_preset_config(
|
||||
[
|
||||
cg.StructInitializer(
|
||||
thermostat_ns.struct("ThermostatCustomPresetEntry"),
|
||||
("name", cg.RawExpression(f'"{name}"')),
|
||||
("config", preset_var),
|
||||
)
|
||||
for name, preset_var in custom_presets
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_DEFAULT_PRESET in config:
|
||||
default_preset_name = config[CONF_DEFAULT_PRESET]
|
||||
|
||||
@@ -53,8 +53,8 @@ void ThermostatClimate::setup() {
|
||||
if (use_default_preset) {
|
||||
if (this->default_preset_ != climate::ClimatePreset::CLIMATE_PRESET_NONE) {
|
||||
this->change_preset_(this->default_preset_);
|
||||
} else if (!this->default_custom_preset_.empty()) {
|
||||
this->change_custom_preset_(this->default_custom_preset_.c_str());
|
||||
} else if (this->default_custom_preset_ != nullptr) {
|
||||
this->change_custom_preset_(this->default_custom_preset_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,16 +319,16 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
||||
if (this->supports_swing_mode_vertical_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
||||
|
||||
for (auto &it : this->preset_config_) {
|
||||
traits.add_supported_preset(it.first);
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
traits.add_supported_preset(entry.preset);
|
||||
}
|
||||
|
||||
// Extract custom preset names from the custom_preset_config_ map
|
||||
// Extract custom preset names from the custom_preset_config_ vector
|
||||
if (!this->custom_preset_config_.empty()) {
|
||||
std::vector<const char *> custom_preset_names;
|
||||
custom_preset_names.reserve(this->custom_preset_config_.size());
|
||||
for (const auto &it : this->custom_preset_config_) {
|
||||
custom_preset_names.push_back(it.first.c_str());
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
custom_preset_names.push_back(entry.name);
|
||||
}
|
||||
traits.set_supported_custom_presets(custom_preset_names);
|
||||
}
|
||||
@@ -1154,12 +1154,18 @@ void ThermostatClimate::dump_preset_config_(const char *preset_name, const Therm
|
||||
}
|
||||
|
||||
void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
|
||||
auto config = this->preset_config_.find(preset);
|
||||
// Linear search through preset configurations
|
||||
const ThermostatClimateTargetTempConfig *config = nullptr;
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
if (entry.preset == preset) {
|
||||
config = &entry.config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (config != this->preset_config_.end()) {
|
||||
if (config != nullptr) {
|
||||
ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
|
||||
if (this->change_preset_internal_(config->second) || (!this->preset.has_value()) ||
|
||||
this->preset.value() != preset) {
|
||||
if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
|
||||
// Fire any preset changed trigger if defined
|
||||
Trigger<> *trig = this->preset_change_trigger_;
|
||||
this->set_preset_(preset);
|
||||
@@ -1178,11 +1184,18 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
|
||||
}
|
||||
|
||||
void ThermostatClimate::change_custom_preset_(const char *custom_preset) {
|
||||
auto config = this->custom_preset_config_.find(custom_preset);
|
||||
// Linear search through custom preset configurations
|
||||
const ThermostatClimateTargetTempConfig *config = nullptr;
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
if (strcmp(entry.name, custom_preset) == 0) {
|
||||
config = &entry.config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (config != this->custom_preset_config_.end()) {
|
||||
if (config != nullptr) {
|
||||
ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
|
||||
if (this->change_preset_internal_(config->second) || !this->has_custom_preset() ||
|
||||
if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
|
||||
strcmp(this->get_custom_preset(), custom_preset) != 0) {
|
||||
// Fire any preset changed trigger if defined
|
||||
Trigger<> *trig = this->preset_change_trigger_;
|
||||
@@ -1247,14 +1260,12 @@ bool ThermostatClimate::change_preset_internal_(const ThermostatClimateTargetTem
|
||||
return something_changed;
|
||||
}
|
||||
|
||||
void ThermostatClimate::set_preset_config(climate::ClimatePreset preset,
|
||||
const ThermostatClimateTargetTempConfig &config) {
|
||||
this->preset_config_[preset] = config;
|
||||
void ThermostatClimate::set_preset_config(std::initializer_list<PresetEntry> presets) {
|
||||
this->preset_config_ = presets;
|
||||
}
|
||||
|
||||
void ThermostatClimate::set_custom_preset_config(const std::string &name,
|
||||
const ThermostatClimateTargetTempConfig &config) {
|
||||
this->custom_preset_config_[name] = config;
|
||||
void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets) {
|
||||
this->custom_preset_config_ = presets;
|
||||
}
|
||||
|
||||
ThermostatClimate::ThermostatClimate()
|
||||
@@ -1293,8 +1304,16 @@ ThermostatClimate::ThermostatClimate()
|
||||
humidity_control_humidify_action_trigger_(new Trigger<>()),
|
||||
humidity_control_off_action_trigger_(new Trigger<>()) {}
|
||||
|
||||
void ThermostatClimate::set_default_preset(const std::string &custom_preset) {
|
||||
this->default_custom_preset_ = custom_preset;
|
||||
void ThermostatClimate::set_default_preset(const char *custom_preset) {
|
||||
// Find the preset in custom_preset_config_ and store pointer from there
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
if (strcmp(entry.name, custom_preset) == 0) {
|
||||
this->default_custom_preset_ = entry.name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If not found, it will be caught during validation
|
||||
this->default_custom_preset_ = nullptr;
|
||||
}
|
||||
|
||||
void ThermostatClimate::set_default_preset(climate::ClimatePreset preset) { this->default_preset_ = preset; }
|
||||
@@ -1605,19 +1624,22 @@ void ThermostatClimate::dump_config() {
|
||||
|
||||
if (!this->preset_config_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Supported PRESETS:");
|
||||
for (auto &it : this->preset_config_) {
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first));
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, it.second);
|
||||
for (const auto &entry : this->preset_config_) {
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset));
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, entry.preset == this->default_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, entry.config);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->custom_preset_config_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
|
||||
for (auto &it : this->custom_preset_config_) {
|
||||
const auto *preset_name = it.first.c_str();
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_custom_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, it.second);
|
||||
for (const auto &entry : this->custom_preset_config_) {
|
||||
const auto *preset_name = entry.name;
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
|
||||
(this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0)
|
||||
? " (default)"
|
||||
: "");
|
||||
this->dump_preset_config_(preset_name, entry.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
namespace thermostat {
|
||||
@@ -72,14 +72,29 @@ struct ThermostatClimateTargetTempConfig {
|
||||
optional<climate::ClimateMode> mode_{};
|
||||
};
|
||||
|
||||
/// Entry for standard preset lookup
|
||||
struct ThermostatPresetEntry {
|
||||
climate::ClimatePreset preset;
|
||||
ThermostatClimateTargetTempConfig config;
|
||||
};
|
||||
|
||||
/// Entry for custom preset lookup
|
||||
struct ThermostatCustomPresetEntry {
|
||||
const char *name;
|
||||
ThermostatClimateTargetTempConfig config;
|
||||
};
|
||||
|
||||
class ThermostatClimate : public climate::Climate, public Component {
|
||||
public:
|
||||
using PresetEntry = ThermostatPresetEntry;
|
||||
using CustomPresetEntry = ThermostatCustomPresetEntry;
|
||||
|
||||
ThermostatClimate();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
void set_default_preset(const std::string &custom_preset);
|
||||
void set_default_preset(const char *custom_preset);
|
||||
void set_default_preset(climate::ClimatePreset preset);
|
||||
void set_on_boot_restore_from(OnBootRestoreFrom on_boot_restore_from);
|
||||
void set_set_point_minimum_differential(float differential);
|
||||
@@ -131,8 +146,8 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
void set_supports_humidification(bool supports_humidification);
|
||||
void set_supports_two_points(bool supports_two_points);
|
||||
|
||||
void set_preset_config(climate::ClimatePreset preset, const ThermostatClimateTargetTempConfig &config);
|
||||
void set_custom_preset_config(const std::string &name, const ThermostatClimateTargetTempConfig &config);
|
||||
void set_preset_config(std::initializer_list<PresetEntry> presets);
|
||||
void set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets);
|
||||
|
||||
Trigger<> *get_cool_action_trigger() const;
|
||||
Trigger<> *get_supplemental_cool_action_trigger() const;
|
||||
@@ -516,9 +531,6 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *prev_swing_mode_trigger_{nullptr};
|
||||
Trigger<> *prev_humidity_control_trigger_{nullptr};
|
||||
|
||||
/// Default custom preset to use on start up
|
||||
std::string default_custom_preset_{};
|
||||
|
||||
/// Climate action timers
|
||||
std::array<ThermostatClimateTimer, THERMOSTAT_TIMER_COUNT> timer_{
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)),
|
||||
@@ -534,9 +546,12 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
};
|
||||
|
||||
/// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc)
|
||||
std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{};
|
||||
FixedVector<PresetEntry> preset_config_{};
|
||||
/// The set of custom preset configurations this thermostat supports (eg. "My Custom Preset")
|
||||
std::map<std::string, ThermostatClimateTargetTempConfig> custom_preset_config_{};
|
||||
FixedVector<CustomPresetEntry> custom_preset_config_{};
|
||||
/// Default custom preset to use on start up (pointer to entry in custom_preset_config_)
|
||||
private:
|
||||
const char *default_custom_preset_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace thermostat
|
||||
|
||||
@@ -23,6 +23,13 @@ namespace time {
|
||||
static const char *const TAG = "time";
|
||||
|
||||
RealTimeClock::RealTimeClock() = default;
|
||||
|
||||
void RealTimeClock::dump_config() {
|
||||
#ifdef USE_TIME_TIMEZONE
|
||||
ESP_LOGCONFIG(TAG, "Timezone: '%s'", this->timezone_.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
||||
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
|
||||
// Update UTC epoch time.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user