mirror of
https://github.com/esphome/esphome.git
synced 2026-10-19 04:32:50 +00:00
Compare commits
465
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7df9b5ea2f | ||
|
|
d3e7bb119c | ||
|
|
dd45f8a6f8 | ||
|
|
ba7d3bd770 | ||
|
|
dc553aae55 | ||
|
|
f7b9646705 | ||
|
|
0ae24f7536 | ||
|
|
41cc96bbb8 | ||
|
|
6b03d68fac | ||
|
|
640d677913 | ||
|
|
8567e69d46 | ||
|
|
4d187a2bb7 | ||
|
|
073394b1eb | ||
|
|
02bfa07819 | ||
|
|
fdc163a30d | ||
|
|
bc44e8b550 | ||
|
|
e894397503 | ||
|
|
f1a23a02c2 | ||
|
|
95dc9a749b | ||
|
|
59963dd246 | ||
|
|
79fd5edc7f | ||
|
|
5d2433f69a | ||
|
|
1b49e79bf8 | ||
|
|
4e4056d790 | ||
|
|
e5f5ca07c4 | ||
|
|
a4a09043ec | ||
|
|
a613073ee3 | ||
|
|
8416a29862 | ||
|
|
9ab0fa9669 | ||
|
|
05aeb2f094 | ||
|
|
c859c347cb | ||
|
|
7ab24bf850 | ||
|
|
cfbdd58e4a | ||
|
|
8084916e30 | ||
|
|
9d02ab3734 | ||
|
|
3442bb6624 | ||
|
|
23d13d3a6e | ||
|
|
5cd7bf5993 | ||
|
|
f7b80e6d44 | ||
|
|
588fde91b2 | ||
|
|
94e6bd90d8 | ||
|
|
456e3d8979 | ||
|
|
76ba7f0c88 | ||
|
|
0652e6c3fb | ||
|
|
dbad1d4629 | ||
|
|
dd8315059c | ||
|
|
822bbb85dc | ||
|
|
daea315b13 | ||
|
|
52b274c5e8 | ||
|
|
b5716a70c2 | ||
|
|
3eb8155ed2 | ||
|
|
11368a0b16 | ||
|
|
cf0e64f84c | ||
|
|
d98ddeeb56 | ||
|
|
0701329ea7 | ||
|
|
f5ce33a8de | ||
|
|
f89a603582 | ||
|
|
279f68c432 | ||
|
|
b6ee8f8035 | ||
|
|
cea872be0b | ||
|
|
558de5ebee | ||
|
|
aba0c5d7ee | ||
|
|
4c17ee7393 | ||
|
|
24237a07c9 | ||
|
|
d4d5ee87cb | ||
|
|
56e6e332cb | ||
|
|
7ac679aa04 | ||
|
|
8a09e49e31 | ||
|
|
3485297e6e | ||
|
|
541562451e | ||
|
|
824834f847 | ||
|
|
bc6fecfadb | ||
|
|
a2434f9aa3 | ||
|
|
466081acc1 | ||
|
|
d9b9ce2909 | ||
|
|
a0f0949078 | ||
|
|
6f4d0e9488 | ||
|
|
65e8345ab8 | ||
|
|
de5c2cda09 | ||
|
|
9adb7e92e1 | ||
|
|
c7d6a2e898 | ||
|
|
6188778538 | ||
|
|
2affc04e62 | ||
|
|
6513b176f4 | ||
|
|
646f386f12 | ||
|
|
6a463c801e | ||
|
|
d9795399ad | ||
|
|
a648822027 | ||
|
|
c6244fa4b9 | ||
|
|
f6746b4977 | ||
|
|
f1f1a77151 | ||
|
|
ba1af627fd | ||
|
|
b82dfd7e64 | ||
|
|
7de2bcb584 | ||
|
|
d5d11e1077 | ||
|
|
ab66b20944 | ||
|
|
baf1682635 | ||
|
|
eea11834f8 | ||
|
|
1d86cc39e9 | ||
|
|
686252f151 | ||
|
|
f5c15f66a5 | ||
|
|
9d6b50320d | ||
|
|
b903c54be4 | ||
|
|
63b4a59588 | ||
|
|
adc6255fd4 | ||
|
|
b28e7a1357 | ||
|
|
e7ba9a5726 | ||
|
|
2586e78127 | ||
|
|
23f6dedb42 | ||
|
|
10f30ddb59 | ||
|
|
96a66f3ddd | ||
|
|
5bf8ceac66 | ||
|
|
4d42352ec5 | ||
|
|
479232b6c0 | ||
|
|
0d596a20f8 | ||
|
|
5ca272b262 | ||
|
|
ff0d4347d6 | ||
|
|
e03db60a52 | ||
|
|
b4bd95b2ac | ||
|
|
fe21e3ad16 | ||
|
|
85108bfd84 | ||
|
|
92bfd0ea7e | ||
|
|
542de8f03a | ||
|
|
06864917a5 | ||
|
|
bcfe6fd1d2 | ||
|
|
e395e626ec | ||
|
|
f9b5eca2b9 | ||
|
|
920295527f | ||
|
|
9c79fa7430 | ||
|
|
4b2f8122e3 | ||
|
|
052f5a4cc6 | ||
|
|
78bf74eafb | ||
|
|
b1c145dd5f | ||
|
|
3055343b6d | ||
|
|
70f64374d4 | ||
|
|
1362e52aa7 | ||
|
|
e51e0e683f | ||
|
|
7910d02b71 | ||
|
|
f224cb37d6 | ||
|
|
79813a1eac | ||
|
|
9066ad6cd0 | ||
|
|
f5e4c0356f | ||
|
|
3c634ecfd8 | ||
|
|
57eeca6341 | ||
|
|
23ba80c9c7 | ||
|
|
73bbd75d3f | ||
|
|
0a7763d902 | ||
|
|
7aeed8455e | ||
|
|
8eba5a1c28 | ||
|
|
0f552a8eef | ||
|
|
31c488e5b0 | ||
|
|
a8ddb2535e | ||
|
|
1ee4940d4d | ||
|
|
640ef1bb17 | ||
|
|
42edfe0681 | ||
|
|
6a6d263c0d | ||
|
|
68a5e33182 | ||
|
|
883af036a9 | ||
|
|
8f003dfa3b | ||
|
|
6d3792e5c5 | ||
|
|
c73ec262e9 | ||
|
|
33cf262616 | ||
|
|
7a1bfed81f | ||
|
|
12c03d539a | ||
|
|
24fc86c2c4 | ||
|
|
5e4990badc | ||
|
|
16568c0453 | ||
|
|
677c089614 | ||
|
|
6db2e781ce | ||
|
|
3296bccd3c | ||
|
|
0323fd36d4 | ||
|
|
1ba323e9a3 | ||
|
|
6f940a486b | ||
|
|
82cffe81f3 | ||
|
|
887e3c302f | ||
|
|
2560a4436e | ||
|
|
6dec759261 | ||
|
|
1c0d406cbb | ||
|
|
ae6ebd2d9d | ||
|
|
13a8efd694 | ||
|
|
678975b6b2 | ||
|
|
006f5f0fc9 | ||
|
|
cf96c53279 | ||
|
|
72413494b0 | ||
|
|
51dbd3a63f | ||
|
|
19561d44a2 | ||
|
|
1661f84deb | ||
|
|
f0035c1db6 | ||
|
|
b78f00a037 | ||
|
|
40809ffa10 | ||
|
|
e4d8913680 | ||
|
|
d234a4d51e | ||
|
|
3cc89a35a2 | ||
|
|
15aac378df | ||
|
|
0e04b230fe | ||
|
|
f3f79c301b | ||
|
|
923700dcc9 | ||
|
|
19dce8840d | ||
|
|
e040647530 | ||
|
|
ab5a4b4f04 | ||
|
|
cb1b4b0bd7 | ||
|
|
597a4e46b1 | ||
|
|
e68a499429 | ||
|
|
a56c85949a | ||
|
|
4eeb110247 | ||
|
|
194273955c | ||
|
|
490d478e54 | ||
|
|
1520d228bc | ||
|
|
61ac0cb753 | ||
|
|
2b02602a49 | ||
|
|
cad670785b | ||
|
|
980fed9e96 | ||
|
|
f670993fe9 | ||
|
|
fb20966353 | ||
|
|
74d0a90308 | ||
|
|
fef3804aa5 | ||
|
|
e66ef45df7 | ||
|
|
1f53ccb572 | ||
|
|
b63794b53c | ||
|
|
a69c648c6f | ||
|
|
29090d50a7 | ||
|
|
9b1a75b0ed | ||
|
|
b7ddd1daa2 | ||
|
|
6002c28a8d | ||
|
|
718b6651fe | ||
|
|
6191b7553e | ||
|
|
082a0b746a | ||
|
|
8d8bd73112 | ||
|
|
3a3707926d | ||
|
|
80dc93d6ce | ||
|
|
40af90ad25 | ||
|
|
4097f45677 | ||
|
|
039f07d332 | ||
|
|
cdcc7bbf74 | ||
|
|
9af9414dcc | ||
|
|
cf334e8758 | ||
|
|
888043762e | ||
|
|
7af96c8d36 | ||
|
|
a778baddca | ||
|
|
fc441646f4 | ||
|
|
4a47a984a0 | ||
|
|
8735aee847 | ||
|
|
c8eaf518ba | ||
|
|
bb8c23805a | ||
|
|
f334b9ea11 | ||
|
|
666cf169b9 | ||
|
|
a058937fe0 | ||
|
|
417fdd9dc7 | ||
|
|
bc388b1de5 | ||
|
|
9820896430 | ||
|
|
4258b226a4 | ||
|
|
5be0aca6d2 | ||
|
|
1110857ffa | ||
|
|
9a97419cfc | ||
|
|
ca74e71348 | ||
|
|
00303fb72b | ||
|
|
aa02a0996a | ||
|
|
59b49d34cc | ||
|
|
862047386c | ||
|
|
bbd05e4811 | ||
|
|
a02fa71dc8 | ||
|
|
99fd1bca3e | ||
|
|
2d73ea24ca | ||
|
|
645144ad4d | ||
|
|
88378ab354 | ||
|
|
f91ac5ed1d | ||
|
|
3b319240c6 | ||
|
|
d9b3f299e0 | ||
|
|
6f2d2a2001 | ||
|
|
8b9d51cf93 | ||
|
|
66eec7af15 | ||
|
|
60e511282a | ||
|
|
e4be4c7fcb | ||
|
|
99bf080092 | ||
|
|
0017b9c439 | ||
|
|
ad1b6ab37e | ||
|
|
acf7257293 | ||
|
|
b36c5441cb | ||
|
|
feedea4954 | ||
|
|
4f9cd7821a | ||
|
|
31e0a31c65 | ||
|
|
e09cab94d4 | ||
|
|
e708c85100 | ||
|
|
d4922fb446 | ||
|
|
cfbbd13b86 | ||
|
|
8f0e032b5e | ||
|
|
0dc10d9f30 | ||
|
|
c4aa8fa9c5 | ||
|
|
9b83f37b78 | ||
|
|
a1a4474a01 | ||
|
|
3a06318b65 | ||
|
|
2f9e37d6a9 | ||
|
|
fe2e541dc8 | ||
|
|
474c2791fe | ||
|
|
7421b165e8 | ||
|
|
52fc5f2c75 | ||
|
|
4f539d3391 | ||
|
|
aad7907c37 | ||
|
|
fb8f679be9 | ||
|
|
84435e7d81 | ||
|
|
d0473438d9 | ||
|
|
2ee13d59ac | ||
|
|
bccdbc23c4 | ||
|
|
4b5375145a | ||
|
|
c6e3a5a165 | ||
|
|
63cb75726b | ||
|
|
b070c828b3 | ||
|
|
2d56559f04 | ||
|
|
8691a43879 | ||
|
|
7acbe82c27 | ||
|
|
1a0b43449e | ||
|
|
860d819e33 | ||
|
|
f5c79eeec0 | ||
|
|
f8f25c1ad4 | ||
|
|
22536f9338 | ||
|
|
c64a7fb678 | ||
|
|
6793dad8ed | ||
|
|
3dd9a28ed2 | ||
|
|
5a93de82bf | ||
|
|
fcf8fe2c11 | ||
|
|
4447f8a065 | ||
|
|
911bd3dadb | ||
|
|
03f264775f | ||
|
|
fe5a155c13 | ||
|
|
25eaf1d025 | ||
|
|
5328e5f5e0 | ||
|
|
de0a0b2af4 | ||
|
|
a5178a0f3d | ||
|
|
483b9ebe2c | ||
|
|
3cc4df4b3a | ||
|
|
8a49ba7e98 | ||
|
|
b130c79267 | ||
|
|
b0d5681022 | ||
|
|
b15bc083f6 | ||
|
|
690227f6d8 | ||
|
|
46b12b43b2 | ||
|
|
ba9d54b8ad | ||
|
|
7169c0c930 | ||
|
|
e3fcf173c2 | ||
|
|
6d33b0401d | ||
|
|
78f39c7fee | ||
|
|
79ad67b56f | ||
|
|
b6c79fdc9a | ||
|
|
cbe89c940d | ||
|
|
4f1be0fcb2 | ||
|
|
25a30fc472 | ||
|
|
2d286031a4 | ||
|
|
995109aa4f | ||
|
|
5def4d46ca | ||
|
|
a531ea07a2 | ||
|
|
984de58853 | ||
|
|
09ba060fd5 | ||
|
|
d0eddc8fc8 | ||
|
|
bf176633b2 | ||
|
|
26398747a1 | ||
|
|
6491eaeaae | ||
|
|
7096546d83 | ||
|
|
1c31e9d28d | ||
|
|
f595d8677c | ||
|
|
416d0c46ac | ||
|
|
97235a20b8 | ||
|
|
61446e3e4f | ||
|
|
1440998f3c | ||
|
|
4c9a44301d | ||
|
|
54cb4c711a | ||
|
|
7b018b9dca | ||
|
|
65fef7d5d8 | ||
|
|
87d4bea24b | ||
|
|
d938bc9913 | ||
|
|
a27c73b44e | ||
|
|
0c0015ef88 | ||
|
|
4724aea42e | ||
|
|
d970e9292e | ||
|
|
797682cccc | ||
|
|
a58f3d34aa | ||
|
|
3fd8f00e97 | ||
|
|
12b016e3d2 | ||
|
|
8617368294 | ||
|
|
6479e079d2 | ||
|
|
ff97353767 | ||
|
|
b324a7c6e0 | ||
|
|
99070187b8 | ||
|
|
dff793a949 | ||
|
|
4075c15bea | ||
|
|
2d37c6f58b | ||
|
|
d49741dc10 | ||
|
|
8ac184228d | ||
|
|
96c6120849 | ||
|
|
f7b4ea9f0e | ||
|
|
7962b52d2a | ||
|
|
bfb7e76b2e | ||
|
|
0ed997da92 | ||
|
|
3ed1bc7e20 | ||
|
|
5d4b436fc0 | ||
|
|
1346342250 | ||
|
|
36bcce563a | ||
|
|
e500ece4d5 | ||
|
|
2de4f4cf81 | ||
|
|
e9f0a53ec9 | ||
|
|
3bae9e4ee2 | ||
|
|
788d2bc631 | ||
|
|
afba431600 | ||
|
|
5e67258818 | ||
|
|
879c5cfc5b | ||
|
|
b4cb164e5d | ||
|
|
af9c4cb778 | ||
|
|
852fb0c5a5 | ||
|
|
9a855b4377 | ||
|
|
eb423be9d2 | ||
|
|
54f6fb0eb2 | ||
|
|
826571518a | ||
|
|
56a7a11162 | ||
|
|
2accef437f | ||
|
|
5a3e95b161 | ||
|
|
7e32770c5a | ||
|
|
3dc608c772 | ||
|
|
5a11f8d7c3 | ||
|
|
94ed17815c | ||
|
|
6f5d7475f0 | ||
|
|
568c1ae494 | ||
|
|
cb47dfcf0e | ||
|
|
35474aae3d | ||
|
|
ea5832a410 | ||
|
|
1d69ddc09e | ||
|
|
e742e64fb0 | ||
|
|
52079dd3f3 | ||
|
|
3fac97867e | ||
|
|
6c43247395 | ||
|
|
e8c5cd56a8 | ||
|
|
9208804707 | ||
|
|
199cc08270 | ||
|
|
6121bb3bcf | ||
|
|
c2d830ebd3 | ||
|
|
a706fa9e6b | ||
|
|
08ed25833d | ||
|
|
3062e1f844 | ||
|
|
39f41656b9 | ||
|
|
987618b49e | ||
|
|
e7f2159f0e | ||
|
|
f327cbbcfe | ||
|
|
3cccdca771 | ||
|
|
5baad05926 | ||
|
|
e111f1130c | ||
|
|
4d534dc420 | ||
|
|
0fd0f72550 | ||
|
|
4a34315fc7 | ||
|
|
4a00cebd61 | ||
|
|
cd0a0f3fca | ||
|
|
23afdc1d9b | ||
|
|
ef4d06e0ea | ||
|
|
83d689d944 | ||
|
|
203a7f70a3 | ||
|
|
7d904b6330 | ||
|
|
ac1190094a | ||
|
|
ed88002c37 | ||
|
|
ef0819f983 | ||
|
|
7187acec04 | ||
|
|
25ddc94d5d | ||
|
|
5502f620d4 | ||
|
|
99c7f9e625 | ||
|
|
35d41adf86 | ||
|
|
662c7db222 | ||
|
|
6b465443bc | ||
|
|
fc270cb90f | ||
|
|
46be513a39 |
@@ -53,7 +53,7 @@ module.exports = async ({ github, context }) => {
|
||||
});
|
||||
const currentLabels = currentLabelsData.map(label => label.name);
|
||||
const managedLabels = currentLabels.filter(label =>
|
||||
label.startsWith('component: ') || MANAGED_LABELS.includes(label)
|
||||
label.startsWith('component: ') || label.startsWith('platform: ') || MANAGED_LABELS.includes(label)
|
||||
);
|
||||
|
||||
// Check for mega-PR early - if present, skip most automatic labeling
|
||||
|
||||
@@ -219,5 +219,6 @@ jobs:
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "${{ github.workspace }}/docker/test_configs:/config" \
|
||||
-e ESPHOME_LDGEN_STRICT=1 \
|
||||
"ghcr.io/esphome/esphome-amd64:${{ needs.check-docker.outputs.tag }}" \
|
||||
compile "${{ matrix.id }}.yaml"
|
||||
|
||||
+35
-26
@@ -286,7 +286,7 @@ jobs:
|
||||
fi
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
- name: Run prek
|
||||
uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0
|
||||
uses: j178/prek-action@1382ad72dea72f9d53832f2bcb65b3e87a947204 # v3.0.1
|
||||
with:
|
||||
prek-version: ${{ steps.prek.outputs.version }}
|
||||
# This job only runs on pull requests, so nothing ever populates
|
||||
@@ -299,7 +299,7 @@ jobs:
|
||||
# Pushes any fixes the hooks made back to the pull request. This step
|
||||
# must keep its default name: the GitHub App that performs the push
|
||||
# locates the workflow run by that name.
|
||||
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
|
||||
- uses: pre-commit-ci/lite-action@062bca0919bc9d6e66755cc05074b70c77e111fc # v1.2.0
|
||||
if: always()
|
||||
with:
|
||||
msg: apply automatic formatting fixes
|
||||
@@ -626,7 +626,7 @@ jobs:
|
||||
apt-get install -y libc6-dbg
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@373d6868929f444bc08d901fd0eb0ad52a8875ea # v5.2.1
|
||||
uses: CodSpeedHQ/action@c4fd08a3a159bd0cc208da1e0edf32b8c47d75e5 # v5.4.0
|
||||
with:
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -893,14 +893,20 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 IDF 1/3
|
||||
options: --environment esp32-idf-tidy --split-num 3 --split-at 1
|
||||
name: Run script/clang-tidy for ESP32 IDF 1/5
|
||||
options: --environment esp32-idf-tidy --split-num 5 --split-at 1
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 IDF 2/3
|
||||
options: --environment esp32-idf-tidy --split-num 3 --split-at 2
|
||||
name: Run script/clang-tidy for ESP32 IDF 2/5
|
||||
options: --environment esp32-idf-tidy --split-num 5 --split-at 2
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 IDF 3/3
|
||||
options: --environment esp32-idf-tidy --split-num 3 --split-at 3
|
||||
name: Run script/clang-tidy for ESP32 IDF 3/5
|
||||
options: --environment esp32-idf-tidy --split-num 5 --split-at 3
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 IDF 4/5
|
||||
options: --environment esp32-idf-tidy --split-num 5 --split-at 4
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 IDF 5/5
|
||||
options: --environment esp32-idf-tidy --split-num 5 --split-at 5
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
@@ -1225,7 +1231,14 @@ jobs:
|
||||
|
||||
if [ -n "$compile_csv" ]; then
|
||||
# Run compilation with grouping and isolation
|
||||
python3 script/test_build_components.py -e compile -c "$compile_csv" -f --isolate "$directly_changed_csv"
|
||||
# The bootloader has no ESPHome code and these builds never
|
||||
# flash; the check_idf_py batch keeps the full build so the
|
||||
# native sub-build and the equivalence check stay covered.
|
||||
skip_flag="--skip-bootloader"
|
||||
if [[ "${{ matrix.batch.check_idf_py }}" == "true" ]]; then
|
||||
skip_flag=""
|
||||
fi
|
||||
python3 script/test_build_components.py -e compile -c "$compile_csv" -f --isolate "$directly_changed_csv" $skip_flag
|
||||
if [[ "${{ matrix.batch.check_idf_py }}" == "true" ]]; then
|
||||
# The real idf.py must find nothing to configure or build in a
|
||||
# tree built above; catches drift on ESP-IDF bumps.
|
||||
@@ -1409,31 +1422,27 @@ jobs:
|
||||
echo "::warning::ci_memory_impact_extract.py not found on target branch, skipping memory impact analysis"
|
||||
fi
|
||||
|
||||
# Check if test files exist on the target branch for the requested
|
||||
# components and platform. When a PR adds new test files for a platform,
|
||||
# the target branch won't have them yet, so skip instead of failing.
|
||||
# This check must be done here (not in determine-jobs.py) because
|
||||
# determine-jobs runs on the PR branch and cannot see what the target
|
||||
# branch has.
|
||||
# The build below uses --base-only, so skip unless the target branch has
|
||||
# test.<platform>.yaml for every requested component (test-*.yaml
|
||||
# variants alone compile nothing, and a partial set would compare a
|
||||
# smaller target build against the PR build). Checked here, not in
|
||||
# determine-jobs.py, because determine-jobs runs on the PR branch and
|
||||
# cannot see the target branch.
|
||||
- name: Check for test files on target branch
|
||||
id: check-tests
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
run: |
|
||||
components='${{ toJSON(fromJSON(needs.determine-jobs.outputs.memory_impact).components) }}'
|
||||
platform="${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}"
|
||||
found=false
|
||||
missing=""
|
||||
for component in $(echo "$components" | jq -r '.[]'); do
|
||||
# Check for test files matching the platform (test.platform.yaml or test-*.platform.yaml)
|
||||
for f in tests/components/${component}/test*.${platform}.yaml; do
|
||||
if [ -f "$f" ]; then
|
||||
found=true
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
if [ ! -f "tests/components/${component}/test.${platform}.yaml" ]; then
|
||||
missing="${missing} ${component}"
|
||||
fi
|
||||
done
|
||||
if [ "$found" = false ]; then
|
||||
if [ -n "$missing" ]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "::warning::No test files found on target branch for platform ${platform}, skipping memory impact analysis"
|
||||
echo "::warning::No test.${platform}.yaml on target branch for${missing}, skipping memory impact analysis"
|
||||
else
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
@@ -125,14 +125,20 @@ jobs:
|
||||
}
|
||||
|
||||
async function getEsphomeAndComponentChanges(github, owner, repo, prNumber) {
|
||||
const changedFiles = await github.rest.pulls.listFiles({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
pull_number: prNumber,
|
||||
});
|
||||
const changedFiles = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
}
|
||||
);
|
||||
|
||||
const esphomeChanges = changedFiles.data
|
||||
.filter(file => file.filename !== "esphome/core/defines.h" && file.filename.startsWith('esphome/'))
|
||||
// Files used only for development and CI, which do not affect use as an external component
|
||||
const ignoredFiles = ["esphome/core/defines.h", "esphome/idf_component.yml"];
|
||||
const esphomeChanges = changedFiles
|
||||
.filter(file => !ignoredFiles.includes(file.filename) && file.filename.startsWith('esphome/'))
|
||||
.map(file => {
|
||||
const match = file.filename.match(/esphome\/([^/]+)/);
|
||||
return match ? match[1] : null;
|
||||
@@ -144,7 +150,7 @@ jobs:
|
||||
}
|
||||
|
||||
const uniqueEsphomeChanges = [...new Set(esphomeChanges)];
|
||||
const componentChanges = changedFiles.data
|
||||
const componentChanges = changedFiles
|
||||
.filter(file => file.filename.startsWith('esphome/components/'))
|
||||
.map(file => {
|
||||
const match = file.filename.match(/esphome\/components\/([^/]+)\//);
|
||||
|
||||
@@ -7,10 +7,6 @@ on:
|
||||
permissions:
|
||||
pull-requests: read # issues.listLabelsOnIssue to detect blocking labels (needs-docs, needs-developer-docs, merge-after-release, chained-pr)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check blocking labels
|
||||
|
||||
@@ -15,6 +15,8 @@ on:
|
||||
# base branch checkout, so fork code never runs with the token.
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- requirements_dev.txt
|
||||
- requirements_test.txt
|
||||
|
||||
@@ -10,7 +10,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.16.9
|
||||
rev: v0.16.10
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -177,6 +177,13 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
Pick the primitive by cadence: under 250 ms use a gated `loop()`; 500 ms and above use
|
||||
`set_interval`. Full reasoning, including why `set_interval` costs more below 500 ms:
|
||||
https://developers.esphome.io/architecture/components/advanced/#quick-rule-of-thumb
|
||||
* **Scheduler ids:** name a timer only when it must be cancelled or replaced, and use a
|
||||
`static constexpr uint32_t` id, never a string. Ids are per component instance and cannot clash with other
|
||||
components, so number them from 0 and keep all of a component's ids together in one place.
|
||||
```cpp
|
||||
static constexpr uint32_t READ_TIMEOUT_ID = 0;
|
||||
this->set_timeout(READ_TIMEOUT_ID, 50, [this]() { this->read_(); });
|
||||
```
|
||||
* **Don't override a default with the same value:** if a base class method already returns what you
|
||||
want, do not override it. `Component::get_setup_priority()` returns `setup_priority::DATA`, so a
|
||||
component that wants `DATA` should simply leave it alone.
|
||||
@@ -230,6 +237,10 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
```
|
||||
|
||||
* **Component Metadata:**
|
||||
- `DOMAIN`: Required in every component's `__init__.py`, set to the component's own name as a plain
|
||||
string (e.g. `DOMAIN = "my_component"`). Other code refers to the component through it, e.g.
|
||||
`from esphome.components.my_component import DOMAIN`. Keep it in alphabetical order with the other
|
||||
metadata constants. CI (`lint_component_domain`) fails without it.
|
||||
- `DEPENDENCIES`: List of required components
|
||||
- `AUTO_LOAD`: Components to automatically load
|
||||
- `CONFLICTS_WITH`: Incompatible components
|
||||
@@ -243,6 +254,7 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_KEY, CONF_ID
|
||||
|
||||
DOMAIN = "my_component"
|
||||
CONF_PARAM = "param" # A constant that does not yet exist in esphome/const.py
|
||||
|
||||
my_component_ns = cg.esphome_ns.namespace("my_component")
|
||||
@@ -384,6 +396,25 @@ file does, and it is the authority when they disagree. The most useful starting
|
||||
)
|
||||
```
|
||||
|
||||
When the callback's parameters differ from the automation's arguments, or the trigger should only
|
||||
fire for some values, pass `params`, `forward` and `when`; the helper then generates a capture-less
|
||||
lambda (stored inline, nothing allocated). Name the parent with `automation.parent_ref(var)` and build
|
||||
`forward` from `MockObj` calls, not f-strings of C++:
|
||||
```python
|
||||
parent = automation.parent_ref(var)
|
||||
index = cg.RawExpression("index")
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_on_state_callback",
|
||||
[(cg.StringRef, "x"), (cg.size_t, "i")],
|
||||
conf,
|
||||
params=[(cg.size_t, "index")],
|
||||
forward=[cg.StringRef(parent.option_at(index)), index],
|
||||
)
|
||||
```
|
||||
`build_parent_callback_automation`, `build_trigger_callback`, `build_callback_automations` and
|
||||
`build_trigger_automations` cover the other shapes; see their docstrings in `esphome/automation.py`.
|
||||
|
||||
**C++ -- no trigger class needed.** The callback registration method must be templatized to accept both `std::function` and lightweight forwarder structs (which avoid heap allocation):
|
||||
```cpp
|
||||
class MyComponent : public Component {
|
||||
|
||||
+14
@@ -187,10 +187,13 @@ esphome/components/esp32_rmt/* @jesserockz
|
||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/esp_ldo/* @clydebarrow
|
||||
esphome/components/espectre/* @francescopace
|
||||
esphome/components/esphome/ota/* @esphome/core
|
||||
esphome/components/espnow/* @jesserockz
|
||||
esphome/components/espnow/packet_transport/* @EasilyBoredEngineer
|
||||
esphome/components/ethernet_info/* @gtjadsonsantos
|
||||
esphome/components/event/* @nohat
|
||||
esphome/components/exponential_moving_average/* @clydebarrow
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/ezo_pmp/* @carlos-sarmiento
|
||||
@@ -245,8 +248,11 @@ esphome/components/hmac_md5/* @dwmw2
|
||||
esphome/components/hmac_sha256/* @dwmw2
|
||||
esphome/components/hoermann_hcp/* @zweckj
|
||||
esphome/components/homeassistant/* @esphome/core @OttoWinter
|
||||
esphome/components/homeassistant/button/* @jesserockz
|
||||
esphome/components/homeassistant/number/* @landonr
|
||||
esphome/components/homeassistant/select/* @jesserockz
|
||||
esphome/components/homeassistant/switch/* @Links2004
|
||||
esphome/components/homeassistant/text/* @jesserockz
|
||||
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/honeywellabp2_i2c/* @jpfaff
|
||||
@@ -282,6 +288,7 @@ esphome/components/inkplate/* @jesserockz @JosipKuci
|
||||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/ir_rf_base/* @bdraco @kbx81
|
||||
esphome/components/ir_rf_proxy/* @kbx81
|
||||
esphome/components/it8951/* @koosoli @limengdu @Passific
|
||||
esphome/components/jsn_sr04t/* @Mafus1
|
||||
@@ -512,6 +519,7 @@ esphome/components/sound_level/* @kahrendt
|
||||
esphome/components/spa06_base/* @danielkent-net
|
||||
esphome/components/spa06_i2c/* @danielkent-net
|
||||
esphome/components/spa06_spi/* @danielkent-net
|
||||
esphome/components/spd2010/* @grischard
|
||||
esphome/components/speaker/* @jesserockz @kahrendt
|
||||
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
|
||||
esphome/components/speaker_source/* @kahrendt
|
||||
@@ -541,6 +549,7 @@ esphome/components/st7735/* @SenexCrenshaw
|
||||
esphome/components/st7789v/* @kbx81
|
||||
esphome/components/st7920/* @marsjan155
|
||||
esphome/components/statsd/* @Links2004
|
||||
esphome/components/stcc4/* @j9brown
|
||||
esphome/components/stts22h/* @B48D81EFCC
|
||||
esphome/components/substitutions/* @esphome/core
|
||||
esphome/components/sun/* @OttoWinter
|
||||
@@ -554,10 +563,12 @@ esphome/components/syslog/* @clydebarrow
|
||||
esphome/components/systa_bus/* @Mat931
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tas2780/* @remcom
|
||||
esphome/components/tas58xx/* @mrtoy-me @remcom
|
||||
esphome/components/tc74/* @sethgirvan
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tca9555/* @mobrembski
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/tcp_uart/* @Bascht74
|
||||
esphome/components/tee501/* @Stock-M
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/tem3200/* @bakerkj
|
||||
@@ -599,11 +610,13 @@ esphome/components/uart/button/* @ssieb
|
||||
esphome/components/uart/event/* @eoasmxd
|
||||
esphome/components/uart/packet_transport/* @clydebarrow
|
||||
esphome/components/uart_mux/* @kbx81
|
||||
esphome/components/uart_tcp/* @Bascht74
|
||||
esphome/components/udp/* @clydebarrow
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ufm01/* @ljungqvist
|
||||
esphome/components/ultrasonic/* @ssieb @swoboda1337
|
||||
esphome/components/unicode/* @esphome/core
|
||||
esphome/components/update/* @jesserockz
|
||||
esphome/components/uponor_smatrix/* @kroimon
|
||||
esphome/components/usb_cdc_acm/* @kbx81
|
||||
@@ -647,6 +660,7 @@ esphome/components/xiaomi_body_scale/* @dckiller51
|
||||
esphome/components/xiaomi_hhccjcy10/* @fariouche
|
||||
esphome/components/xiaomi_lywsd02mmc/* @juanluss31
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mccgq02hl/* @ahpohl @morph027
|
||||
esphome/components/xiaomi_mhoc303/* @drug123
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||
|
||||
@@ -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 = 2026.10.0-dev
|
||||
PROJECT_NUMBER = 2026.11.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
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.17.2
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.23.0
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
+84
-164
@@ -7,6 +7,7 @@ import importlib
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
@@ -18,7 +19,6 @@ from typing import TYPE_CHECKING, Protocol
|
||||
from esphome import const, platform_hooks
|
||||
from esphome.build_helpers.native import analysis_backend, native_backend
|
||||
from esphome.const import (
|
||||
ALLOWED_NAME_CHARS,
|
||||
ARGUMENT_HELP_DEVICE,
|
||||
BUNDLE_EXTENSION,
|
||||
CONF_API,
|
||||
@@ -36,7 +36,6 @@ from esphome.const import (
|
||||
CONF_LOGGER,
|
||||
CONF_MDNS,
|
||||
CONF_MQTT,
|
||||
CONF_NAME,
|
||||
CONF_NAME_ADD_MAC_SUFFIX,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
@@ -56,12 +55,13 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
from esphome.enum import StrEnum
|
||||
from esphome.helpers import get_bool_env, indent, is_ip_address
|
||||
from esphome.helpers import IS_MACOS, get_bool_env, indent, is_ip_address
|
||||
from esphome.log import AnsiFore, color, setup_log
|
||||
from esphome.stacktrace import LogLineProcessor
|
||||
from esphome.types import ConfigType
|
||||
from esphome.upload_targets import PortType, get_port_type
|
||||
from esphome.util import (
|
||||
ESPHOME_COMMAND,
|
||||
PICOTOOL_PACKAGE,
|
||||
FlashImage,
|
||||
detect_rp2040_bootsel,
|
||||
@@ -85,7 +85,6 @@ if TYPE_CHECKING:
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ESPHOME_COMMAND = [sys.executable, "-m", "esphome"]
|
||||
|
||||
# Maximum buffer size for serial log reading to prevent unbounded memory growth
|
||||
SERIAL_BUFFER_MAX_SIZE = 65536
|
||||
@@ -150,6 +149,7 @@ class ArgsProtocol(Protocol):
|
||||
file: str | None
|
||||
no_logs: bool
|
||||
only_generate: bool
|
||||
skip_bootloader: bool
|
||||
show_secrets: bool
|
||||
dashboard: bool
|
||||
configuration: str
|
||||
@@ -829,6 +829,14 @@ def write_cpp_file() -> int:
|
||||
|
||||
|
||||
def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
if CORE.skip_bootloader and not (CORE.is_esp32 and CORE.using_toolchain_esp_idf):
|
||||
# Debug only: an orchestrator cannot see YAML toolchain overrides,
|
||||
# so this is its expected no-op, and a full build is safe.
|
||||
_LOGGER.debug(
|
||||
"--skip-bootloader ignored: only supported on ESP32 with the "
|
||||
"esp-idf toolchain"
|
||||
)
|
||||
CORE.skip_bootloader = False
|
||||
# Keep this gate here, NOT in config validation: device-builder needs
|
||||
# `esphome config` to keep succeeding with placeholders so onboarding can run.
|
||||
if CONF_WIFI in config:
|
||||
@@ -859,7 +867,10 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
return rc
|
||||
|
||||
# Create factory.bin, ota.bin, and firmware.elf copy
|
||||
toolchain.create_factory_bin()
|
||||
if not toolchain.create_factory_bin():
|
||||
# A build whose factory image could not be produced must not
|
||||
# exit 0; downloads would serve an image from an older build.
|
||||
return 1
|
||||
toolchain.create_ota_bin()
|
||||
toolchain.create_elf_copy()
|
||||
from esphome.build_helpers.idedata import warn_if_idedata_missing
|
||||
@@ -977,8 +988,9 @@ def upload_using_esptool(
|
||||
# and partitions included where the target needs them)
|
||||
image = native.get_factory_firmware_path()
|
||||
if not image.is_file():
|
||||
hint = getattr(native, "missing_image_hint", lambda: None)()
|
||||
raise EsphomeError(
|
||||
f"{image} does not exist; compile the configuration first"
|
||||
hint or f"{image} does not exist; compile the configuration first"
|
||||
)
|
||||
flash_images = [FlashImage(path=image, offset="0x0")]
|
||||
else:
|
||||
@@ -1289,10 +1301,9 @@ def _choose_ota_platform(config: ConfigType, requested: str | None) -> str:
|
||||
The native API uses challenge-response auth with MD5/SHA256 hashing of a
|
||||
server-issued nonce, so the password is never sent over the wire; the
|
||||
``web_server`` path uses HTTP Basic auth which transmits credentials in
|
||||
cleartext over the LAN. (The native path also supports gzip compression
|
||||
on ESP8266, where flash space is tight; on ESP32/RP2040/LibreTiny the
|
||||
backend reports ``supports_compression() == false`` and the firmware is
|
||||
sent uncompressed regardless of which platform is used.) Falls back to
|
||||
cleartext over the LAN. (The native path also compresses the upload:
|
||||
gzip on ESP8266 and RP2040, which inflate it at reboot, and a deflate
|
||||
stream on ESP32/LibreTiny, which inflate it as it arrives.) Falls back to
|
||||
``web_server`` only when that is the only available platform.
|
||||
"""
|
||||
# Use a dict (insertion-ordered) instead of a list so error messages and
|
||||
@@ -1301,9 +1312,9 @@ def _choose_ota_platform(config: ConfigType, requested: str | None) -> str:
|
||||
# platform's final-validate hook merges duplicates anyway.
|
||||
available: dict[str, None] = {}
|
||||
for ota_item in config.get(CONF_OTA, []):
|
||||
platform = ota_item.get(CONF_PLATFORM)
|
||||
if platform in (CONF_ESPHOME, CONF_WEB_SERVER):
|
||||
available[platform] = None
|
||||
ota_platform = ota_item.get(CONF_PLATFORM)
|
||||
if ota_platform in (CONF_ESPHOME, CONF_WEB_SERVER):
|
||||
available[ota_platform] = None
|
||||
|
||||
if not available:
|
||||
raise EsphomeError(
|
||||
@@ -1381,6 +1392,12 @@ def _upload_via_native_api(
|
||||
ota_type = espota2.OTA_TYPE_UPDATE_PARTITION_TABLE
|
||||
elif getattr(args, "bootloader", False):
|
||||
check_partition_access("--bootloader")
|
||||
if (
|
||||
getattr(args, "file", None) is None
|
||||
and (native := native_backend())
|
||||
and (hint := getattr(native, "missing_image_hint", lambda: None)())
|
||||
):
|
||||
raise EsphomeError(hint)
|
||||
binary = CORE.bootloader_bin
|
||||
ota_type = espota2.OTA_TYPE_UPDATE_BOOTLOADER
|
||||
if getattr(args, "file", None) is not None:
|
||||
@@ -1756,6 +1773,18 @@ def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
|
||||
|
||||
def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
if (
|
||||
CORE.skip_bootloader
|
||||
and CORE.is_esp32
|
||||
and CORE.using_toolchain_esp_idf
|
||||
and any(
|
||||
get_port_type(device) == PortType.SERIAL for device in (args.device or [])
|
||||
)
|
||||
):
|
||||
# Fail before the compile: the result could never flash over serial.
|
||||
# Elsewhere the flag is ignored, so serial stays fine there.
|
||||
_LOGGER.error("--skip-bootloader builds cannot be flashed over serial")
|
||||
return 1
|
||||
exit_code = write_cpp(config)
|
||||
if exit_code != 0:
|
||||
return exit_code
|
||||
@@ -1917,7 +1946,10 @@ def run_multiple_configs(
|
||||
safe_print()
|
||||
|
||||
cmd = command_builder(f)
|
||||
rc = run_external_process(*cmd)
|
||||
# The parent already logged the Intel macOS warning; children skip it.
|
||||
rc = run_external_process(
|
||||
*cmd, env={**os.environ, _INTEL_MACOS_WARNED_ENV: "1"}
|
||||
)
|
||||
|
||||
if rc == 0:
|
||||
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
|
||||
@@ -2104,155 +2136,9 @@ def command_analyze_memory(args: ArgsProtocol, config: ConfigType) -> int:
|
||||
|
||||
|
||||
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import yaml_util
|
||||
from esphome.cli.rename import command_rename as run
|
||||
|
||||
new_name = args.name
|
||||
for c in new_name:
|
||||
if c not in ALLOWED_NAME_CHARS:
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"'{c}' is an invalid character for names. Valid characters are: "
|
||||
f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
# Load existing yaml file
|
||||
raw_contents = CORE.config_path.read_text(encoding="utf-8")
|
||||
|
||||
yaml = yaml_util.load_yaml(CORE.config_path)
|
||||
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED, "Complex YAML files cannot be automatically renamed."
|
||||
)
|
||||
)
|
||||
return 1
|
||||
old_name = yaml[CONF_ESPHOME][CONF_NAME]
|
||||
match = re.match(r"^\$\{?([a-zA-Z0-9_]+)\}?$", old_name)
|
||||
if match is None:
|
||||
# Only swap the ``name:`` line that sits directly under the
|
||||
# top-level ``esphome:`` block. A naked ``re.sub`` would
|
||||
# also clobber any other ``name:`` line whose value happens
|
||||
# to match (e.g. a sensor / output / wifi entry sharing the
|
||||
# device's hostname), silently rewriting unrelated user
|
||||
# configuration. The pattern anchors:
|
||||
# - at the start of the line so ``friendly_name:``,
|
||||
# ``device_name:`` etc. don't match the trailing ``name:``
|
||||
# substring; and
|
||||
# - at the end of the value (lookahead for whitespace +
|
||||
# comment + EOL) so ``old_name`` doesn't match as a
|
||||
# prefix of a longer value (``kitchen`` vs ``kitchen2``).
|
||||
name_pattern = re.compile(
|
||||
rf"^(\s*)name:\s+[\"']?{re.escape(old_name)}[\"']?(?=\s*(?:#|$))"
|
||||
)
|
||||
out_lines: list[str] = []
|
||||
in_esphome_block = False
|
||||
for line in raw_contents.splitlines(keepends=True):
|
||||
if line and not line[0].isspace() and line.strip():
|
||||
in_esphome_block = line.lstrip().startswith("esphome:")
|
||||
out_lines.append(line)
|
||||
continue
|
||||
if in_esphome_block:
|
||||
line = name_pattern.sub(rf'\1name: "{new_name}"', line, count=1)
|
||||
out_lines.append(line)
|
||||
new_raw = "".join(out_lines)
|
||||
else:
|
||||
old_name = yaml[CONF_SUBSTITUTIONS][match.group(1)]
|
||||
if (
|
||||
len(
|
||||
re.findall(
|
||||
rf"^\s+{match.group(1)}:\s+[\"']?{old_name}[\"']?",
|
||||
raw_contents,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
)
|
||||
> 1
|
||||
):
|
||||
safe_print(
|
||||
color(AnsiFore.BOLD_RED, "Too many matches in YAML to safely rename")
|
||||
)
|
||||
return 1
|
||||
|
||||
new_raw = re.sub(
|
||||
rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
|
||||
f'\\1: "{new_name}"',
|
||||
raw_contents,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
|
||||
# ``new_name == old_name`` (after substitution resolution) is
|
||||
# a no-op rewrite that would still queue a pointless re-flash.
|
||||
# Catch it before the path-equality check below — covers the
|
||||
# case where the config filename doesn't match the device name
|
||||
# (e.g. ``weird-file.yaml`` whose ``esphome.name`` is
|
||||
# ``kitchen``; running ``esphome rename weird-file.yaml kitchen``
|
||||
# would otherwise just re-flash the same hostname).
|
||||
if new_name == old_name:
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"'{new_name}' is already the device's name.",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
|
||||
new_path: Path = CORE.config_dir / (new_name + ".yaml")
|
||||
if new_path.resolve() == CORE.config_path.resolve():
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"'{new_name}' is already the device's name.",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
if new_path.exists():
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"Cannot rename: {new_path} already exists. "
|
||||
"Refusing to overwrite an existing configuration.",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
safe_print(
|
||||
f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
|
||||
)
|
||||
print()
|
||||
|
||||
new_path.write_text(new_raw, encoding="utf-8")
|
||||
|
||||
rc = run_external_process(*ESPHOME_COMMAND, "config", str(new_path))
|
||||
if rc != 0:
|
||||
safe_print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
|
||||
new_path.unlink()
|
||||
return 1
|
||||
|
||||
cli_args = [
|
||||
"run",
|
||||
str(new_path),
|
||||
"--no-logs",
|
||||
"--device",
|
||||
CORE.address,
|
||||
]
|
||||
|
||||
if args.dashboard:
|
||||
cli_args.insert(0, "--dashboard")
|
||||
|
||||
try:
|
||||
rc = run_external_process(*ESPHOME_COMMAND, *cli_args)
|
||||
except KeyboardInterrupt:
|
||||
rc = 1
|
||||
if rc != 0:
|
||||
new_path.unlink()
|
||||
return 1
|
||||
|
||||
if CORE.config_path != new_path:
|
||||
CORE.config_path.unlink()
|
||||
|
||||
safe_print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
|
||||
print()
|
||||
return 0
|
||||
return run(args, config)
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
@@ -2287,6 +2173,15 @@ SIMPLE_CONFIG_ACTIONS = [
|
||||
]
|
||||
|
||||
|
||||
def _add_skip_bootloader_arg(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
"--skip-bootloader",
|
||||
help="Do not build the bootloader or the factory image; "
|
||||
"the result can only be flashed over OTA.",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
|
||||
def _add_states_args(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add mutually exclusive ``--states``/``--no-states`` flags to a parser.
|
||||
|
||||
@@ -2428,6 +2323,7 @@ def parse_args(argv):
|
||||
help="Only generate source code, do not compile.",
|
||||
action="store_true",
|
||||
)
|
||||
_add_skip_bootloader_arg(parser_compile)
|
||||
|
||||
parser_upload = subparsers.add_parser(
|
||||
"upload",
|
||||
@@ -2491,7 +2387,7 @@ def parse_args(argv):
|
||||
"-r",
|
||||
action="store_true",
|
||||
help="Reset the device before starting serial logs.",
|
||||
default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
|
||||
default=get_bool_env("ESPHOME_SERIAL_LOGGING_RESET"),
|
||||
)
|
||||
_add_states_args(parser_logs)
|
||||
|
||||
@@ -2524,6 +2420,7 @@ def parse_args(argv):
|
||||
parser_run.add_argument(
|
||||
"--no-logs", help="Disable starting logs.", action="store_true"
|
||||
)
|
||||
_add_skip_bootloader_arg(parser_run)
|
||||
|
||||
_add_states_args(parser_run)
|
||||
|
||||
@@ -2532,7 +2429,7 @@ def parse_args(argv):
|
||||
"-r",
|
||||
action="store_true",
|
||||
help="Reset the device before starting serial logs.",
|
||||
default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
|
||||
default=get_bool_env("ESPHOME_SERIAL_LOGGING_RESET"),
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--ota-platform",
|
||||
@@ -2721,6 +2618,27 @@ def _warn_if_source_tree_mismatch() -> None:
|
||||
)
|
||||
|
||||
|
||||
_INTEL_MACOS_REMOVAL = "2027.6.0"
|
||||
_INTEL_MACOS_WARNED_ENV = "ESPHOME_INTEL_MACOS_WARNED"
|
||||
|
||||
|
||||
def _warn_if_intel_macos() -> None:
|
||||
"""Warn that Intel (x86_64) Python on macOS loses support by _INTEL_MACOS_REMOVAL."""
|
||||
if (
|
||||
not IS_MACOS
|
||||
or platform.machine() != "x86_64"
|
||||
or _INTEL_MACOS_WARNED_ENV in os.environ
|
||||
):
|
||||
return
|
||||
_LOGGER.warning(
|
||||
"Support for Intel Macs will end in ESPHome %s or earlier. The Python "
|
||||
"packages ESPHome depends on have stopped publishing Intel macOS builds, "
|
||||
"so future releases will not install on this machine. On an Apple "
|
||||
"Silicon Mac, switch to a native arm64 Python.",
|
||||
_INTEL_MACOS_REMOVAL,
|
||||
)
|
||||
|
||||
|
||||
def run_esphome(argv):
|
||||
from esphome.address_cache import AddressCache
|
||||
|
||||
@@ -2740,6 +2658,7 @@ def run_esphome(argv):
|
||||
|
||||
setup_log(log_level=args.log_level)
|
||||
_warn_if_source_tree_mismatch()
|
||||
_warn_if_intel_macos()
|
||||
|
||||
if args.command in PRE_CONFIG_ACTIONS:
|
||||
try:
|
||||
@@ -2783,6 +2702,7 @@ def run_esphome(argv):
|
||||
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
CORE.skip_bootloader = getattr(args, "skip_bootloader", False)
|
||||
if args.toolchain is not None:
|
||||
# CLI toolchain wins over esp32.toolchain in YAML.
|
||||
CORE.toolchain = args.toolchain
|
||||
|
||||
@@ -148,6 +148,13 @@ class AddressCache:
|
||||
continue
|
||||
hostname, ips = arg.split("=", 1)
|
||||
# Normalize hostname for consistent lookups
|
||||
normalized = normalize_hostname(hostname)
|
||||
cache[normalized] = [ip.strip() for ip in ips.split(",")]
|
||||
normalized = normalize_hostname(hostname.strip())
|
||||
addresses = [ip for value in ips.split(",") if (ip := value.strip())]
|
||||
if not normalized or not addresses:
|
||||
_LOGGER.warning(
|
||||
"Invalid cache entry: %s (hostname and at least one address are required)",
|
||||
arg,
|
||||
)
|
||||
continue
|
||||
cache[normalized] = addresses
|
||||
return cache
|
||||
|
||||
@@ -24,9 +24,10 @@ from esphome.core import EsphomeError, Version
|
||||
from esphome.framework_helpers import str_to_lst_of_str
|
||||
from esphome.platformio.registry import (
|
||||
Download,
|
||||
PackageSpec,
|
||||
Resolver,
|
||||
get_systype,
|
||||
install_package,
|
||||
install_packages,
|
||||
prefetch_packages,
|
||||
)
|
||||
|
||||
@@ -48,40 +49,40 @@ class FrameworkRelease(NamedTuple):
|
||||
# Arduino core version -> its build in esphome-libs/arduino-esp8266
|
||||
FRAMEWORK_RELEASES: dict[Version, FrameworkRelease] = {
|
||||
Version(3, 1, 2): FrameworkRelease(
|
||||
"3.1.2-esphome.1",
|
||||
"e80751e3123676b967143e39c61f2d8693946db4c7806f2a83dcaaf797ecd582",
|
||||
37189311,
|
||||
"3.1.2-esphome.3",
|
||||
"3af533a85163804e164500a5b254dc23b922080e25ecb7dc37b3dd8ac84651db",
|
||||
37185650,
|
||||
),
|
||||
}
|
||||
|
||||
TOOLCHAIN_PACKAGE = "toolchain-xtensa-lx106-elf"
|
||||
# gcc 10.3, the toolchain Arduino core 3.x builds with; the build
|
||||
# generator's compile flags are tuned to it.
|
||||
TOOLCHAIN_VERSION = "10.3.0-esphome.2"
|
||||
TOOLCHAIN_VERSION = "10.3.0-esphome.3"
|
||||
_TOOLCHAIN_RELEASES = (
|
||||
"https://github.com/esphome-libs/xtensa-lx106-elf-toolchain/releases/"
|
||||
)
|
||||
# Registry system tag -> (sha256, size) of that host's archive
|
||||
TOOLCHAIN_BUILDS: dict[str, tuple[str, int]] = {
|
||||
"darwin_arm64": (
|
||||
"849cede44d4d5c6ea0f14099783239f559f46327bea314281814f2652b486201",
|
||||
60830321,
|
||||
"ad0ea929238d9b527c43e0d20257acc2a5680bdb0d2b1cf8f2c99169786217e9",
|
||||
60747260,
|
||||
),
|
||||
"darwin_x86_64": (
|
||||
"ca69904daabf0c5983b372423e5e62f49182a793e992c052e94666852470c897",
|
||||
64149487,
|
||||
"997090cac80c8eb59604ec52b73fb3568e5f2d9b2971f83c3f2a5c545c9c949c",
|
||||
64065752,
|
||||
),
|
||||
"linux_aarch64": (
|
||||
"60a49a4f082bf246544bd409a9517dbbcab19bb30ac9decbee544b896aaccbd6",
|
||||
67573397,
|
||||
"403f14d0d5755682fcd6eaea1e4c457a1d8cad30690e52fb0c804d8db266b908",
|
||||
67487067,
|
||||
),
|
||||
"linux_x86_64": (
|
||||
"1fba33ca1494ec79f2776e0e37eca93282d30f8bb9992f5f4f9a655d6fff1db4",
|
||||
68431336,
|
||||
"64f21fa3ba736b363dc8c234dbbb39e005e574e33de08a78e9a00c55a64ae706",
|
||||
68343668,
|
||||
),
|
||||
"windows_amd64": (
|
||||
"af9066b0e5bf036f04f2bd9d08b89b81a7f183c57dac0abcaff71dd861cf5f3b",
|
||||
67664137,
|
||||
"1e62abf8df041ce5158f5770dffef955f2f981ce7f7adee88fcd5a8197705aa6",
|
||||
67536405,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -151,14 +152,14 @@ def check_and_install(framework_version: Version) -> InstalledPaths:
|
||||
toolchain_path = get_toolchain_path()
|
||||
# One spec per package: the prefetch and the installs must agree
|
||||
specs = (
|
||||
(
|
||||
PackageSpec(
|
||||
FRAMEWORK_PACKAGE,
|
||||
release.tag,
|
||||
framework_path,
|
||||
ESPHOME_ARDUINO8266_FRAMEWORK_MIRRORS,
|
||||
("cores/esp8266", "tools/sdk", "libraries"),
|
||||
),
|
||||
(
|
||||
PackageSpec(
|
||||
TOOLCHAIN_PACKAGE,
|
||||
TOOLCHAIN_VERSION,
|
||||
toolchain_path,
|
||||
@@ -174,18 +175,10 @@ def check_and_install(framework_version: Version) -> InstalledPaths:
|
||||
resolvers[FRAMEWORK_PACKAGE] = release.download
|
||||
if not ESPHOME_ARDUINO8266_TOOLCHAIN_MIRRORS:
|
||||
resolvers[TOOLCHAIN_PACKAGE] = toolchain_download
|
||||
# Fetch both archives at once; the installs below verify and extract
|
||||
prefetch_packages([spec[:4] for spec in specs], downloads_dir, resolvers)
|
||||
for name, version, dest, mirrors, expect in specs:
|
||||
install_package(
|
||||
name,
|
||||
version,
|
||||
dest,
|
||||
mirrors,
|
||||
downloads_dir,
|
||||
expect=expect,
|
||||
resolve=resolvers.get(name),
|
||||
)
|
||||
# Fetch both archives at once; the installs verify and extract them.
|
||||
# One spec list for both, so the two phases cannot drift.
|
||||
prefetch_packages(specs, downloads_dir, resolvers)
|
||||
install_packages(specs, downloads_dir, resolvers)
|
||||
return InstalledPaths(
|
||||
framework=framework_path, toolchain=toolchain_path, ninja=ninja_path
|
||||
)
|
||||
|
||||
+205
-89
@@ -1,4 +1,4 @@
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
import string
|
||||
@@ -21,9 +21,9 @@ from esphome.const import (
|
||||
CONF_TYPE_ID,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
)
|
||||
from esphome.core import CORE, ID, EsphomeError, Lambda
|
||||
from esphome.core import CORE, ID, EsphomeError, HexInt, Lambda
|
||||
from esphome.cpp_generator import (
|
||||
FlashStringLiteral,
|
||||
Expression,
|
||||
LambdaExpression,
|
||||
MockObj,
|
||||
MockObjClass,
|
||||
@@ -35,6 +35,32 @@ from esphome.types import ConfigType, SafeExpType
|
||||
from esphome.util import Registry
|
||||
|
||||
|
||||
def progmem_bytes(name: str, data: bytes | list[int]) -> MockObj:
|
||||
"""Shared PROGMEM table for constant bytes; equal payloads share one, empty is nullptr."""
|
||||
if not data:
|
||||
return cg.nullptr
|
||||
return cg.shared_progmem_array(
|
||||
name, cg.uint8, cg.ArrayInitializer(*(HexInt(x) for x in data))
|
||||
)
|
||||
|
||||
|
||||
async def templatable_bytes(
|
||||
value: Any,
|
||||
args: TemplateArgsType,
|
||||
set_template: MockObj,
|
||||
set_static: MockObj,
|
||||
table_name: str,
|
||||
) -> None:
|
||||
"""Set a TemplatableBytes: a lambda via set_template, constant bytes via set_static."""
|
||||
if cg.is_template(value):
|
||||
fn = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(set_template(fn))
|
||||
elif len(value) > 0xFFFF:
|
||||
raise EsphomeError(f"Byte payload is {len(value)} bytes; the maximum is 65535")
|
||||
else:
|
||||
cg.add(set_static(progmem_bytes(table_name, value), len(value)))
|
||||
|
||||
|
||||
def maybe_simple_id(*validators):
|
||||
"""Allow a raw ID to be specified in place of a config block.
|
||||
If the value that's being validated is a dictionary, it's passed as-is to the specified validators. Otherwise, it's
|
||||
@@ -219,9 +245,7 @@ ApplyCondition = cg.esphome_ns.class_("ApplyCondition", Condition)
|
||||
|
||||
def flash_string(config: ConfigType, value: str) -> str:
|
||||
"""Default renderer for ``std::string`` constants; copies the literal out of flash on ESP8266."""
|
||||
if CORE.is_esp8266:
|
||||
return f"progmem_string({FlashStringLiteral(value)})"
|
||||
return str(cg.safe_exp(value))
|
||||
return str(cg.progmem_string(value))
|
||||
|
||||
|
||||
def literal_with_length(config: ConfigType, value: str) -> str:
|
||||
@@ -233,13 +257,24 @@ def literal_with_length(config: ConfigType, value: str) -> str:
|
||||
return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}"
|
||||
|
||||
|
||||
def string_ref_literal(config: ConfigType, value: str) -> str:
|
||||
"""Renderer for a ``StringRef`` comparison: a flash literal on ESP8266, else ``StringRef(literal, length)``."""
|
||||
if CORE.is_esp8266:
|
||||
return str(cg.FlashStringLiteral(value))
|
||||
return f"StringRef({literal_with_length(config, value)})"
|
||||
|
||||
|
||||
def _names_parent(text: str) -> bool:
|
||||
return any(f == "parent" for _, f, _, _ in string.Formatter().parse(text))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApplyCall:
|
||||
"""One statement from config keys, e.g. ``"set_range({}, {})"`` with ``((CONF_LOW, cg.float_), ...)``.
|
||||
|
||||
Each arg is ``(conf_key, type_)`` or ``(conf_key, type_, const_fn)``. A ``conf_key`` may be a
|
||||
path into nested sections. A plain ``str`` ``type_`` is raw C++ type text and may use
|
||||
``{parent}``. ``const_fn(config, value)`` renders a constant's argument text; a lambda or an
|
||||
path into nested sections. ``target`` and a plain ``str`` ``type_`` (raw C++ type text) may
|
||||
name the parent object as ``{parent}``. ``const_fn(config, value)`` renders a constant's argument text; a lambda or an
|
||||
id bypasses it. The statement is skipped when none of its keys is set, always emitted when it
|
||||
has no keys, and a partial set is a config error.
|
||||
"""
|
||||
@@ -251,13 +286,13 @@ class ApplyCall:
|
||||
fields = [
|
||||
f for _, f, _, _ in string.Formatter().parse(self.target) if f is not None
|
||||
]
|
||||
if any(fields):
|
||||
if any(f not in ("", "parent") for f in fields):
|
||||
raise ValueError(
|
||||
f"apply target {self.target!r}: only bare {{}} placeholders"
|
||||
f"apply target {self.target!r}: only {{}} and {{parent}} placeholders"
|
||||
)
|
||||
if len(fields) != len(self.args):
|
||||
if (count := fields.count("")) != len(self.args):
|
||||
raise ValueError(
|
||||
f"apply target {self.target!r} has {len(fields)} "
|
||||
f"apply target {self.target!r} has {count} "
|
||||
f"placeholder(s) for {len(self.args)} config key(s)"
|
||||
)
|
||||
if any(len(arg) not in (2, 3) for arg in self.args):
|
||||
@@ -265,6 +300,12 @@ class ApplyCall:
|
||||
f"apply target {self.target!r}: each arg is (conf_key, type_[, const_fn])"
|
||||
)
|
||||
|
||||
@property
|
||||
def names_parent(self) -> bool:
|
||||
return _names_parent(self.target) or any(
|
||||
isinstance(arg[1], str) and _names_parent(arg[1]) for arg in self.args
|
||||
)
|
||||
|
||||
@property
|
||||
def members(self) -> list[tuple[Any, Any, Any]]:
|
||||
"""Each arg as ``(conf_key, type_, const_fn or None)``."""
|
||||
@@ -339,9 +380,16 @@ def _check_key_in_schema(
|
||||
schema = schema.schema[markers[part]]
|
||||
|
||||
|
||||
def parent_ref(var: MockObj) -> MockObj:
|
||||
"""``var`` named from global scope, so a trigger argument cannot shadow it.
|
||||
|
||||
Also how a generated callback names its Automation.
|
||||
"""
|
||||
return MockObj(f"::{var}", "->")
|
||||
|
||||
|
||||
async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
|
||||
# Global-scope qualified so a trigger arg named like the id cannot shadow it.
|
||||
return f"::{await cg.get_variable(config[id_key])}"
|
||||
return str(parent_ref(await cg.get_variable(config[id_key])))
|
||||
|
||||
|
||||
def _apply_lambda_args(args: TemplateArgsType) -> TemplateArgsType:
|
||||
@@ -373,7 +421,7 @@ async def _render_values(
|
||||
members: list[tuple[Any, Any, Any]],
|
||||
values: list[Any],
|
||||
config: ConfigType,
|
||||
parent: str,
|
||||
parent: str | None,
|
||||
lambda_args: TemplateArgsType,
|
||||
compare: bool = False,
|
||||
) -> list[str]:
|
||||
@@ -451,12 +499,12 @@ def register_apply_action(
|
||||
statements: list[str] = []
|
||||
for target, members in statements_spec:
|
||||
values = _apply_values(config, members)
|
||||
if members and all(value is None for value in values):
|
||||
if not _apply_call_active(members, values):
|
||||
continue
|
||||
exprs = await _render_values(
|
||||
name, target, members, values, config, parent, lambda_args
|
||||
)
|
||||
statements.append(f"{receiver}{target.format(*exprs)};")
|
||||
statements.append(f"{receiver}{target.format(*exprs, parent=parent)};")
|
||||
if call:
|
||||
statements = [
|
||||
f"auto apply_call = {parent}->{call}();",
|
||||
@@ -470,6 +518,27 @@ def register_apply_action(
|
||||
register_action(name, ApplyAction, schema, synchronous=True)(builder)
|
||||
|
||||
|
||||
def _apply_call_active(members: list[tuple[Any, Any, Any]], values: list[Any]) -> bool:
|
||||
"""An ``ApplyCall`` is emitted unless it has keys and none of them is set."""
|
||||
return not members or any(value is not None for value in values)
|
||||
|
||||
|
||||
async def _render_check(
|
||||
name: str,
|
||||
target: str,
|
||||
members: list[tuple[Any, Any, Any]],
|
||||
values: list[Any],
|
||||
config: ConfigType,
|
||||
parent: str | None,
|
||||
lambda_args: TemplateArgsType,
|
||||
) -> str:
|
||||
"""Render a boolean check with ``values`` compared against config."""
|
||||
exprs = await _render_values(
|
||||
name, target, members, values, config, parent, lambda_args, compare=True
|
||||
)
|
||||
return target.format(*exprs, parent=parent)
|
||||
|
||||
|
||||
def register_apply_condition(
|
||||
name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID
|
||||
) -> None:
|
||||
@@ -495,22 +564,16 @@ def register_apply_condition(
|
||||
) -> MockObj:
|
||||
parent = await _apply_parent(config, id_key)
|
||||
lambda_args = _apply_lambda_args(args)
|
||||
exprs = await _render_values(
|
||||
name,
|
||||
call.target,
|
||||
members,
|
||||
_apply_values(config, members),
|
||||
config,
|
||||
parent,
|
||||
lambda_args,
|
||||
compare=True,
|
||||
values = _apply_values(config, members)
|
||||
check = await _render_check(
|
||||
name, call.target, members, values, config, parent, lambda_args
|
||||
)
|
||||
return _apply_function(
|
||||
condition_id,
|
||||
cg.bool_,
|
||||
template_arg,
|
||||
lambda_args,
|
||||
[f"return {parent}->{call.target.format(*exprs)};"],
|
||||
[f"return {parent}->{check};"],
|
||||
)
|
||||
|
||||
register_condition(name, ApplyCondition, schema)(builder)
|
||||
@@ -646,52 +709,20 @@ XorCondition = cg.esphome_ns.class_("XorCondition", Condition)
|
||||
|
||||
|
||||
@register_condition("and", AndCondition, validate_condition_list)
|
||||
async def and_condition_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("or", OrCondition, validate_condition_list)
|
||||
async def or_condition_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("all", AndCondition, validate_condition_list)
|
||||
async def all_condition_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("or", OrCondition, validate_condition_list)
|
||||
@register_condition("any", OrCondition, validate_condition_list)
|
||||
async def any_condition_to_code(
|
||||
@register_condition("xor", XorCondition, validate_condition_list)
|
||||
async def condition_group_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
# A group of one is that condition for and, or and xor; skip the wrapper.
|
||||
if len(conditions) == 1:
|
||||
return conditions[0]
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
@@ -708,19 +739,6 @@ async def not_condition_to_code(
|
||||
return cg.new_Pvariable(condition_id, template_arg, condition)
|
||||
|
||||
|
||||
@register_condition("xor", XorCondition, validate_condition_list)
|
||||
async def xor_condition_to_code(
|
||||
config: ConfigType,
|
||||
condition_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
conditions = await build_condition_list(config, template_arg, args)
|
||||
return cg.new_Pvariable(
|
||||
condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions
|
||||
)
|
||||
|
||||
|
||||
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
|
||||
async def lambda_condition_to_code(
|
||||
config: ConfigType,
|
||||
@@ -1030,23 +1048,78 @@ def has_non_synchronous_actions(actions: ConfigType) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
async def build_automation(
|
||||
trigger: MockObj, args: TemplateArgsType, config: ConfigType
|
||||
async def _new_automation(
|
||||
args: TemplateArgsType, config: ConfigType, *ctor_args: MockObj
|
||||
) -> MockObj:
|
||||
arg_types = [arg[0] for arg in args]
|
||||
templ = cg.TemplateArguments(*arg_types)
|
||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
|
||||
"""Create the Automation for ``config`` with its actions."""
|
||||
templ = cg.TemplateArguments(*(arg[0] for arg in args))
|
||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, *ctor_args)
|
||||
actions = await build_action_list(config[CONF_THEN], templ, args)
|
||||
cg.add(obj.add_actions(actions))
|
||||
return obj
|
||||
|
||||
|
||||
async def build_automation(
|
||||
trigger: MockObj, args: TemplateArgsType, config: ConfigType
|
||||
) -> MockObj:
|
||||
return await _new_automation(args, config, trigger)
|
||||
|
||||
|
||||
async def build_trigger_callback(
|
||||
args: TemplateArgsType,
|
||||
config: ConfigType,
|
||||
params: TemplateArgsType,
|
||||
forward: Sequence[str | Expression] | None = None,
|
||||
when: str | ApplyCall | None = None,
|
||||
parent: MockObj | None = None,
|
||||
) -> LambdaExpression:
|
||||
"""Build the Automation for ``config`` and return a stateless callback that triggers it.
|
||||
|
||||
``params`` are the parent callback's parameters, ``forward`` the expressions passed to
|
||||
``trigger()`` (default: the parameter names; write the parent as ``parent_ref(var)``),
|
||||
``when`` a filter the callback returns early on, skipped like any ``ApplyCall`` when none
|
||||
of its keys is set. ``when`` may name ``parent`` as ``{parent}``, e.g.
|
||||
``"{parent}->is_fully_open()"``.
|
||||
"""
|
||||
members: list[tuple[Any, Any, Any]] = []
|
||||
if when is not None:
|
||||
call = when if isinstance(when, ApplyCall) else ApplyCall(when)
|
||||
members = call.members
|
||||
if parent is None and call.names_parent:
|
||||
raise ValueError(f"trigger filter {call.target!r} names {{parent}}")
|
||||
obj = await _new_automation(args, config)
|
||||
lambda_args = _apply_lambda_args(params)
|
||||
statements: list[str] = []
|
||||
if when is not None:
|
||||
values = _apply_values(config, members)
|
||||
if _apply_call_active(members, values):
|
||||
check = await _render_check(
|
||||
"trigger filter",
|
||||
call.target,
|
||||
members,
|
||||
values,
|
||||
config,
|
||||
None if parent is None else str(parent_ref(parent)),
|
||||
lambda_args,
|
||||
)
|
||||
statements.append(f"if (!({check}))\n return;")
|
||||
if forward is None:
|
||||
forward = [name for _, name in params]
|
||||
statements.append(f"{parent_ref(obj)}->trigger({', '.join(map(str, forward))});")
|
||||
return LambdaExpression(
|
||||
["\n".join(statements)], lambda_args, capture="", return_type=cg.void
|
||||
)
|
||||
|
||||
|
||||
async def build_callback_automation(
|
||||
parent: MockObj,
|
||||
callback_method: str,
|
||||
args: TemplateArgsType,
|
||||
config: ConfigType,
|
||||
forwarder: MockObj | MockObjClass | None = None,
|
||||
params: TemplateArgsType | None = None,
|
||||
forward: Sequence[str | Expression] | None = None,
|
||||
when: str | ApplyCall | None = None,
|
||||
) -> None:
|
||||
"""Build an Automation and register it as a callback on the parent.
|
||||
|
||||
@@ -1058,6 +1131,9 @@ async def build_callback_automation(
|
||||
pointer-sized (single Automation* field) to fit inline in Callback::ctx_
|
||||
and avoid heap allocation.
|
||||
|
||||
With ``params``, ``forward`` or ``when`` the callback is instead the stateless
|
||||
lambda of ``build_trigger_callback``; ``forwarder`` cannot be combined with them.
|
||||
|
||||
:param parent: The component object (e.g., button, sensor).
|
||||
:param callback_method: Name of the callback method (e.g., "add_on_press_callback").
|
||||
:param args: Automation template args as list of (type, name) tuples.
|
||||
@@ -1066,22 +1142,56 @@ async def build_callback_automation(
|
||||
TriggerForwarder<Ts...>. Pass any struct type whose aggregate init takes
|
||||
a single Automation pointer (e.g., TriggerOnTrueForwarder).
|
||||
"""
|
||||
arg_types = [arg[0] for arg in args]
|
||||
templ = cg.TemplateArguments(*arg_types)
|
||||
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ)
|
||||
actions = await build_action_list(config[CONF_THEN], templ, args)
|
||||
cg.add(obj.add_actions(actions))
|
||||
if params is not None or forward is not None or when is not None:
|
||||
if forwarder is not None:
|
||||
raise ValueError(
|
||||
"forwarder cannot be combined with params, forward or when"
|
||||
)
|
||||
callback = await build_trigger_callback(
|
||||
args, config, args if params is None else params, forward, when, parent
|
||||
)
|
||||
cg.add(getattr(parent, callback_method)(callback))
|
||||
return
|
||||
obj = await _new_automation(args, config)
|
||||
# Use template forwarder structs for deduplication. The compiler generates
|
||||
# one operator() per forwarder type; different automation pointers are just
|
||||
# data in the struct.
|
||||
if forwarder is None:
|
||||
forwarder = TriggerForwarder.template(templ)
|
||||
forwarder = TriggerForwarder.template(*(arg[0] for arg in args))
|
||||
# RawExpression for aggregate init — both forwarder and obj are codegen
|
||||
# MockObjs (not user input), and there's no Expression type for positional
|
||||
# aggregate initialization (StructInitializer uses named fields).
|
||||
cg.add(getattr(parent, callback_method)(cg.RawExpression(f"{forwarder}{{{obj}}}")))
|
||||
|
||||
|
||||
async def build_parent_callback_automation(
|
||||
parent: MockObj, callback_method: str, arg: tuple[Any, str], config: ConfigType
|
||||
) -> None:
|
||||
"""Register an Automation that receives ``parent`` from a callback that carries nothing.
|
||||
|
||||
``arg`` is the automation's ``(type, name)``, e.g. ``(Fan.operator("ptr"), "x")``.
|
||||
"""
|
||||
await build_callback_automation(
|
||||
parent, callback_method, [arg], config, params=[], forward=[parent_ref(parent)]
|
||||
)
|
||||
|
||||
|
||||
async def build_trigger_automations(
|
||||
parent: MockObj | None,
|
||||
config: ConfigType,
|
||||
entries: tuple[tuple[str, TemplateArgsType], ...],
|
||||
) -> None:
|
||||
"""Instantiate each entry's Trigger class, with ``parent`` when given, and build its automations.
|
||||
|
||||
``entries`` are ``(conf_key, args)`` pairs; the class comes from the entry's ``CONF_TRIGGER_ID``.
|
||||
"""
|
||||
ctor_args = () if parent is None else (parent,)
|
||||
for conf_key, args in entries:
|
||||
for conf in config.get(conf_key, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], *ctor_args)
|
||||
await build_automation(trigger, args, conf)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CallbackAutomation:
|
||||
"""A single callback automation entry for build_callback_automations."""
|
||||
@@ -1090,6 +1200,9 @@ class CallbackAutomation:
|
||||
callback_method: str
|
||||
args: TemplateArgsType = field(default_factory=list)
|
||||
forwarder: MockObj | MockObjClass | None = None
|
||||
params: TemplateArgsType | None = None
|
||||
forward: Sequence[str | Expression] | None = None
|
||||
when: str | ApplyCall | None = None
|
||||
|
||||
|
||||
async def build_callback_automations(
|
||||
@@ -1111,4 +1224,7 @@ async def build_callback_automations(
|
||||
entry.args,
|
||||
conf,
|
||||
forwarder=entry.forwarder,
|
||||
params=entry.params,
|
||||
forward=entry.forward,
|
||||
when=entry.when,
|
||||
)
|
||||
|
||||
@@ -54,6 +54,7 @@ from esphome.components.esp8266.const import (
|
||||
KEY_ESP8266,
|
||||
KEY_FLASH_SIZE,
|
||||
KEY_SCANF_FLOAT,
|
||||
THROW_STUBS_HEADER,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.framework_helpers import get_project_cxx_compile_flags
|
||||
@@ -1065,10 +1066,10 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
" rspfile_content = $in_newline",
|
||||
" description = LINK $out",
|
||||
"rule elf2bin",
|
||||
# --flash_size deliberately stays board-derived, as under
|
||||
# PlatformIO (which reads upload.maximum_size, not the ldscript).
|
||||
# --flash_size follows PlatformIO: ldscript filename first, then
|
||||
# upload.maximum_size, so the header always matches the layout.
|
||||
# -W: the framework's own elf2bin.py trips SyntaxWarning on 3.12+.
|
||||
f" command = $python -W ignore::SyntaxWarning {_q(framework / 'tools' / 'elf2bin.py')} --eboot {_q(framework / 'bootloaders' / 'eboot' / 'eboot.elf')} --app $in --flash_mode {flash_mode} --flash_freq {_FLASH_FREQ_MHZ} --flash_size {_flash_size_str(BOARDS[board][KEY_FLASH_SIZE])} --path {_q(toolchain_bin)} --out $out",
|
||||
f" command = $python -W ignore::SyntaxWarning {_q(framework / 'tools' / 'elf2bin.py')} --eboot {_q(framework / 'bootloaders' / 'eboot' / 'eboot.elf')} --app $in --flash_mode {flash_mode} --flash_freq {_FLASH_FREQ_MHZ} --flash_size {_elf2bin_flash_size(board, flash_ld_name)} --path {_q(toolchain_bin)} --out $out",
|
||||
" description = BIN $out",
|
||||
"rule copy",
|
||||
" command = $python $buildtool copy $in $out",
|
||||
@@ -1087,32 +1088,8 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
if "USE_ESP8266_WAVEFORM_STUBS" in flag_defines:
|
||||
core_exclude |= _CORE_EXCLUDE_WAVEFORM
|
||||
|
||||
archives = []
|
||||
# variant_dir existence was already enforced with the include dirs
|
||||
variant_sources = collect_sources(variant_dir)
|
||||
if variant_sources:
|
||||
objs = compile_edges(lines, variant_sources, variant_dir, "variant")
|
||||
lines.append(f"build libFrameworkArduinoVariant.a: ar {' '.join(objs)}")
|
||||
archives.append("libFrameworkArduinoVariant.a")
|
||||
|
||||
core_objs = compile_edges(
|
||||
lines, collect_sources(core_dir, core_exclude), core_dir, "core"
|
||||
)
|
||||
if not core_objs:
|
||||
# An empty archive would link into a wall of undefined references
|
||||
# (app_entry, the exception vectors) far from the cause
|
||||
raise EsphomeError(
|
||||
f"{_INCOMPLETE_INSTALL}: no core sources in {core_dir}; {_CLEAN_HINT}"
|
||||
)
|
||||
lines.append(f"build libFrameworkArduino.a: ar {' '.join(core_objs)}")
|
||||
archives.append("libFrameworkArduino.a")
|
||||
|
||||
lib_archives, direct_objs = library_edges(lines, libraries)
|
||||
archives += lib_archives
|
||||
|
||||
# One source of truth with the PlatformIO path: esp8266/__init__ pins
|
||||
# build_src_flags (the throw_stubs force-include); -include paths
|
||||
# resolve against the source root
|
||||
# esp8266/__init__ pins build_src_flags (the throw_stubs force-include), as on
|
||||
# the PlatformIO path; -include paths resolve against the source root
|
||||
src_other: list[str] = []
|
||||
src_includes: list[str] = []
|
||||
src_it = iter(
|
||||
@@ -1131,7 +1108,45 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool:
|
||||
src_includes.append(tok[len("-include") :])
|
||||
else:
|
||||
src_other.append(_shell_token(tok))
|
||||
# The throw stubs abort: none anywhere with exceptions on, else the core and
|
||||
# libraries get them too (src keeps them after its pch include)
|
||||
if config.exceptions:
|
||||
src_includes = [h for h in src_includes if h != THROW_STUBS_HEADER]
|
||||
include_flags = [f"-include {_q(src_dir / h)}" for h in src_includes]
|
||||
framework_flags = ""
|
||||
if THROW_STUBS_HEADER in src_includes:
|
||||
stubs_flag = include_flags[src_includes.index(THROW_STUBS_HEADER)]
|
||||
lines.append(f"frameworkflags = {stubs_flag}")
|
||||
framework_flags = "$frameworkflags"
|
||||
archives = []
|
||||
# variant_dir existence was already enforced with the include dirs
|
||||
variant_sources = collect_sources(variant_dir)
|
||||
if variant_sources:
|
||||
objs = compile_edges(
|
||||
lines, variant_sources, variant_dir, "variant", flags=framework_flags
|
||||
)
|
||||
lines.append(f"build libFrameworkArduinoVariant.a: ar {' '.join(objs)}")
|
||||
archives.append("libFrameworkArduinoVariant.a")
|
||||
|
||||
core_objs = compile_edges(
|
||||
lines,
|
||||
collect_sources(core_dir, core_exclude),
|
||||
core_dir,
|
||||
"core",
|
||||
flags=framework_flags,
|
||||
)
|
||||
if not core_objs:
|
||||
# An empty archive would link into a wall of undefined references
|
||||
# (app_entry, the exception vectors) far from the cause
|
||||
raise EsphomeError(
|
||||
f"{_INCOMPLETE_INSTALL}: no core sources in {core_dir}; {_CLEAN_HINT}"
|
||||
)
|
||||
lines.append(f"build libFrameworkArduino.a: ar {' '.join(core_objs)}")
|
||||
archives.append("libFrameworkArduino.a")
|
||||
|
||||
lib_archives, direct_objs = library_edges(lines, libraries, framework_flags)
|
||||
archives += lib_archives
|
||||
|
||||
# One shared variable instead of repeating the flags line on every src
|
||||
# edge (hundreds of edges in a real project)
|
||||
lines.append(f"srcflags = {' '.join(src_other + include_flags)}")
|
||||
@@ -1189,3 +1204,28 @@ def _flash_size_str(flash_size: int) -> str:
|
||||
"""Flash size argument for elf2bin (e.g. ``4M``, ``512K``)."""
|
||||
mb = 1024 * 1024
|
||||
return f"{flash_size // mb}M" if flash_size >= mb else f"{flash_size // 1024}K"
|
||||
|
||||
|
||||
# Same pattern PlatformIO's _get_flash_size applies to the ldscript path
|
||||
_LD_FLASH_SIZE_RE = re.compile(r"\.flash\.(\d+[mk]).*\.ld")
|
||||
# The framework elf2bin.py's --flash_size choices
|
||||
_ELF2BIN_FLASH_SIZES = frozenset({"256K", "512K", "1M", "2M", "4M", "8M", "16M"})
|
||||
|
||||
|
||||
def _elf2bin_flash_size(board: str, flash_ld_name: str) -> str:
|
||||
"""Image-header flash size as PlatformIO derives it: ldscript filename,
|
||||
else board_upload.maximum_size, else the board table. The SDK clamps the
|
||||
chip to the header size at boot, so a header smaller than the linked
|
||||
layout breaks OTA writes on the running device."""
|
||||
if match := _LD_FLASH_SIZE_RE.search(flash_ld_name):
|
||||
token = match.group(1)
|
||||
base = 1024 if token[-1] == "k" else 1024 * 1024
|
||||
return _flash_size_str(int(token[:-1]) * base)
|
||||
if max_size := _pio_option("board_upload.maximum_size", ""):
|
||||
if (
|
||||
not max_size.isdigit()
|
||||
or (size := _flash_size_str(int(max_size))) not in _ELF2BIN_FLASH_SIZES
|
||||
):
|
||||
raise EsphomeError(f"Invalid board_upload.maximum_size value {max_size!r}")
|
||||
return size
|
||||
return _flash_size_str(BOARDS[board][KEY_FLASH_SIZE])
|
||||
|
||||
+232
-12
@@ -3,6 +3,7 @@
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
from esphome.build_helpers import pch
|
||||
@@ -20,7 +21,7 @@ from esphome.framework_helpers import (
|
||||
get_project_cxx_compile_flags,
|
||||
get_project_link_flags,
|
||||
)
|
||||
from esphome.helpers import mkdir_p, write_file_if_changed
|
||||
from esphome.helpers import get_bool_env, mkdir_p, write_file_if_changed
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -35,6 +36,83 @@ list(FILTER esphome_cxx_compile_options EXCLUDE REGEX "^-std=")
|
||||
list(APPEND esphome_cxx_compile_options "-std={standard}")
|
||||
idf_build_set_property(CXX_COMPILE_OPTIONS "${{esphome_cxx_compile_options}}")"""
|
||||
|
||||
# Drops the app archive from ldgen's inputs so app-only edits skip the
|
||||
# sections.ld regeneration. Safe: no mapping fragment references it
|
||||
# (run_compile re-checks each build). Filters only the top-level call;
|
||||
# the prior definition stays reachable with an underscore prefix.
|
||||
_LDGEN_OVERRIDE = """\
|
||||
if(COMMAND __ldgen_get_lib_deps_of_target)
|
||||
set_property(GLOBAL PROPERTY ESPHOME_LDGEN_ARMED 1)
|
||||
function(__ldgen_get_lib_deps_of_target target out_list_var)
|
||||
if(NOT COMMAND ___ldgen_get_lib_deps_of_target)
|
||||
message(FATAL_ERROR "ESPHome ldgen override lost the original "
|
||||
"implementation; set ESPHOME_LDGEN_FULL_DEPS=1 and rebuild.")
|
||||
endif()
|
||||
___ldgen_get_lib_deps_of_target(${target} ${out_list_var})
|
||||
if(out_list_var STREQUAL "ldgen_libraries")
|
||||
set_property(GLOBAL PROPERTY ESPHOME_LDGEN_FILTERED 1)
|
||||
list(LENGTH ${out_list_var} esphome_ldgen_before)
|
||||
list(REMOVE_ITEM ${out_list_var} idf::src __idf_src)
|
||||
list(LENGTH ${out_list_var} esphome_ldgen_after)
|
||||
if(esphome_ldgen_before EQUAL esphome_ldgen_after)
|
||||
message(@SEVERITY@ "ESPHome ldgen app archive exclusion matched "
|
||||
"nothing; app edits will regenerate sections.ld.")
|
||||
endif()
|
||||
endif()
|
||||
set(${out_list_var} "${${out_list_var}}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
else()
|
||||
message(@MISSING@ "ESPHome ldgen override target not found; "
|
||||
"app edits will regenerate sections.ld.")
|
||||
endif()"""
|
||||
|
||||
# lwip sources that compile to empty objects with the option off (their own
|
||||
# #if guard). (option, regex valid for both Python and CMake); a source is
|
||||
# only dropped when its option is defined and off, so a renamed option
|
||||
# keeps it.
|
||||
LWIP_EMPTY_SOURCES: tuple[tuple[str, str], ...] = (
|
||||
("CONFIG_LWIP_PPP_SUPPORT", "/netif/ppp/"),
|
||||
("CONFIG_LWIP_IPV6", "/core/ipv6/"),
|
||||
("CONFIG_LWIP_AUTOIP", "/core/ipv4/autoip[.]c$"),
|
||||
("CONFIG_LWIP_STATS", "/core/stats[.]c$"),
|
||||
)
|
||||
# Drift guard only: keep every lwip source.
|
||||
LWIP_FULL_SOURCES_ENV = "ESPHOME_LWIP_FULL_SOURCES"
|
||||
|
||||
# Drops the empty objects after project(), once the lwip target exists.
|
||||
_LWIP_EMPTY_SOURCES_FILTER = f"""\
|
||||
idf_build_get_property(esphome_build_components BUILD_COMPONENTS)
|
||||
if(lwip IN_LIST esphome_build_components AND NOT DEFINED ENV{{{LWIP_FULL_SOURCES_ENV}}})
|
||||
idf_component_get_property(esphome_lwip_lib lwip COMPONENT_LIB)
|
||||
get_target_property(esphome_lwip_srcs ${{esphome_lwip_lib}} SOURCES)
|
||||
@FILTERS@
|
||||
set_property(TARGET ${{esphome_lwip_lib}} PROPERTY SOURCES ${{esphome_lwip_srcs}})
|
||||
endif()"""
|
||||
|
||||
|
||||
def lwip_empty_source_gate(option: str, regex: str) -> str:
|
||||
return (
|
||||
f" if(DEFINED {option} AND NOT {option})\n"
|
||||
f' list(FILTER esphome_lwip_srcs EXCLUDE REGEX "{regex}")\n'
|
||||
" endif()"
|
||||
)
|
||||
|
||||
|
||||
def _lwip_empty_sources_filter() -> str:
|
||||
gates = "\n".join(lwip_empty_source_gate(*entry) for entry in LWIP_EMPTY_SOURCES)
|
||||
return _LWIP_EMPTY_SOURCES_FILTER.replace("@FILTERS@", gates)
|
||||
|
||||
|
||||
# Runs after project() so the walk has happened; catches the remaining
|
||||
# silent path where the top-level out-var was renamed.
|
||||
_LDGEN_OVERRIDE_CHECK = """\
|
||||
get_property(esphome_ldgen_armed GLOBAL PROPERTY ESPHOME_LDGEN_ARMED)
|
||||
get_property(esphome_ldgen_filtered GLOBAL PROPERTY ESPHOME_LDGEN_FILTERED)
|
||||
if(esphome_ldgen_armed AND NOT esphome_ldgen_filtered)
|
||||
message(@SEVERITY@ "ESPHome ldgen override never filtered the app "
|
||||
"archive; app edits will regenerate sections.ld.")
|
||||
endif()"""
|
||||
|
||||
|
||||
def get_available_components() -> list[str] | None:
|
||||
"""List the built-in ESP-IDF components from ``project_description.json``.
|
||||
@@ -80,6 +158,114 @@ def _cmake_quote(value: str) -> str:
|
||||
return f'"{escaped}"'
|
||||
|
||||
|
||||
# CONFIG_APP_BUILD_BOOTLOADER is hidden and force-selected, so it can only be
|
||||
# cleared at the CMake level (the same state IDF's RAM-app build type uses).
|
||||
# The macro is IDF's __build_process_project_includes plus a few added lines;
|
||||
# the flag is ignored and the bootloader builds as usual if IDF changes it.
|
||||
IDF_BOOTLOADER_OVERRIDE = """\
|
||||
# ESPHome bootloader skip switch; see esphome/espidf/toolchain.py.
|
||||
if(ESPHOME_SKIP_BOOTLOADER)
|
||||
macro(__build_process_project_includes)
|
||||
idf_build_get_property(sdkconfig_cmake SDKCONFIG_CMAKE)
|
||||
include(${sdkconfig_cmake})
|
||||
set(CONFIG_APP_BUILD_BOOTLOADER "")
|
||||
# bt's CMakeLists reads the lowercase idf_target that the (now
|
||||
# skipped) bootloader project_include leaks; keep it defined, or
|
||||
# its empty TARGET_SRC_NAME sends file(GLOB_RECURSE) across /.
|
||||
idf_build_get_property(idf_target IDF_TARGET)
|
||||
# partition_table's V1 ECDSA signing reads this key, which the
|
||||
# skipped bootloader project_include also sets.
|
||||
get_filename_component(SECURE_BOOT_SIGNING_KEY "${CONFIG_SECURE_BOOT_SIGNING_KEY}" ABSOLUTE BASE_DIR "${project_dir}")
|
||||
idf_build_get_property(build_properties __BUILD_PROPERTIES)
|
||||
foreach(build_property ${build_properties})
|
||||
idf_build_get_property(val ${build_property})
|
||||
set(${build_property} "${val}")
|
||||
endforeach()
|
||||
idf_build_get_property(build_component_targets __BUILD_COMPONENT_TARGETS)
|
||||
foreach(component_target ${build_component_targets})
|
||||
__component_get_property(dir ${component_target} COMPONENT_DIR)
|
||||
__component_get_property(_name ${component_target} COMPONENT_NAME)
|
||||
set(COMPONENT_NAME ${_name})
|
||||
set(COMPONENT_DIR ${dir})
|
||||
set(COMPONENT_PATH ${dir})
|
||||
if(EXISTS ${COMPONENT_DIR}/project_include.cmake)
|
||||
include(${COMPONENT_DIR}/project_include.cmake)
|
||||
endif()
|
||||
endforeach()
|
||||
endmacro()
|
||||
endif()
|
||||
"""
|
||||
|
||||
# The lines the override adds to IDF's macro; idf_macro_matches() below
|
||||
# strips them before comparing with the live macro.
|
||||
BOOTLOADER_OVERRIDE_ADDED_LINES = (
|
||||
'set(CONFIG_APP_BUILD_BOOTLOADER "")',
|
||||
"idf_build_get_property(idf_target IDF_TARGET)",
|
||||
(
|
||||
"get_filename_component(SECURE_BOOT_SIGNING_KEY"
|
||||
' "${CONFIG_SECURE_BOOT_SIGNING_KEY}" ABSOLUTE BASE_DIR "${project_dir}")'
|
||||
),
|
||||
)
|
||||
|
||||
_MACRO = re.compile(
|
||||
r"macro\(__build_process_project_includes\)(.*?)endmacro\(\)", re.DOTALL
|
||||
)
|
||||
|
||||
|
||||
def _normalized_macro(text: str) -> list[str] | None:
|
||||
"""The macro body as comment-free, whitespace-collapsed lines."""
|
||||
if (match := _MACRO.search(text)) is None:
|
||||
return None
|
||||
return [
|
||||
re.sub(r"\s+", " ", line)
|
||||
for raw in match.group(1).splitlines()
|
||||
if (line := raw.split("#", 1)[0].strip())
|
||||
]
|
||||
|
||||
|
||||
_EXPECTED_MACRO = [
|
||||
line
|
||||
for line in _normalized_macro(IDF_BOOTLOADER_OVERRIDE)
|
||||
if line not in BOOTLOADER_OVERRIDE_ADDED_LINES
|
||||
]
|
||||
|
||||
|
||||
def idf_macro_matches(idf_path: Path) -> bool:
|
||||
"""Whether IDF's macro still matches the copy the override replays."""
|
||||
build_cmake = idf_path / "tools" / "cmake" / "build.cmake"
|
||||
live = _normalized_macro(build_cmake.read_text(encoding="utf-8"))
|
||||
return live == _EXPECTED_MACRO
|
||||
|
||||
|
||||
def _cmake_args_for_write(minimal: bool) -> dict[str, str]:
|
||||
"""The cg.add_cmake_arg() values for one project write.
|
||||
|
||||
The discovery (minimal) write lets every built-in component register so
|
||||
the list it finds, cached per target, serves any exclusion set; the full
|
||||
write drops the excluded names from that list and passes all of them to
|
||||
IDF. Names outside the framework's components directory (managed
|
||||
components) stay excluded on the discovery write too, since the list
|
||||
only holds built-in ones. A checkout supplied through IDF_PATH caches
|
||||
nothing, so its discovery keeps every exclusion.
|
||||
"""
|
||||
from esphome.espidf.toolchain import _esphome_manages_idf, _get_idf_path
|
||||
|
||||
args = dict(CORE.cmake_args)
|
||||
if (
|
||||
not minimal
|
||||
or not _esphome_manages_idf()
|
||||
or not (excluded := args.get("EXCLUDE_COMPONENTS"))
|
||||
):
|
||||
return args
|
||||
root = _get_idf_path() / "components"
|
||||
outside = [name for name in excluded.split(";") if not (root / name).is_dir()]
|
||||
if outside:
|
||||
args["EXCLUDE_COMPONENTS"] = ";".join(outside)
|
||||
else:
|
||||
del args["EXCLUDE_COMPONENTS"]
|
||||
return args
|
||||
|
||||
|
||||
def get_project_cmakelists(
|
||||
minimal: bool = False, builtin_components: list[str] | None = None
|
||||
) -> str:
|
||||
@@ -125,13 +311,28 @@ def get_project_cmakelists(
|
||||
else ""
|
||||
)
|
||||
|
||||
# Stops the ~3s sections.ld regeneration on app-only edits; see
|
||||
# _LDGEN_OVERRIDE. ESPHOME_LDGEN_FULL_DEPS=1 restores stock behavior;
|
||||
# ESPHOME_LDGEN_STRICT=1 (CI) fails the configure when an IDF bump
|
||||
# breaks the override instead of degrading to stock deps.
|
||||
if get_bool_env("ESPHOME_LDGEN_FULL_DEPS"):
|
||||
ldgen_override = ""
|
||||
ldgen_override_check = ""
|
||||
else:
|
||||
strict = get_bool_env("ESPHOME_LDGEN_STRICT")
|
||||
severity = "FATAL_ERROR" if strict else "WARNING"
|
||||
missing = "FATAL_ERROR" if strict else "STATUS"
|
||||
ldgen_override = _LDGEN_OVERRIDE.replace("@SEVERITY@", severity).replace(
|
||||
"@MISSING@", missing
|
||||
)
|
||||
ldgen_override_check = _LDGEN_OVERRIDE_CHECK.replace("@SEVERITY@", severity)
|
||||
|
||||
# CMake variables registered via cg.add_cmake_arg(). Emitted before
|
||||
# include(project.cmake) so values like EXCLUDE_COMPONENTS are already
|
||||
# set when project.cmake seeds the component list, and on minimal
|
||||
# (discovery) writes too so excluded components never register.
|
||||
# set when project.cmake seeds the component list.
|
||||
cmake_args = "\n".join(
|
||||
f"set({name} {_cmake_quote(value)})"
|
||||
for name, value in sorted(CORE.cmake_args.items())
|
||||
for name, value in sorted(_cmake_args_for_write(minimal).items())
|
||||
)
|
||||
|
||||
# Per-project list exposed as a CMake variable so converted PIO libs
|
||||
@@ -154,10 +355,10 @@ def get_project_cmakelists(
|
||||
# component's REQUIRES including real IDF components). Referenced by
|
||||
# src/CMakeLists and by each converted PIO lib's CMakeLists. Skipped
|
||||
# on minimal writes because project_description.json may be stale.
|
||||
# Excluded components are dropped here as well: a stale
|
||||
# project_description.json from a build without exclusions may still
|
||||
# list them, and requiring an excluded component pulls it back into
|
||||
# the build (IDF requirement expansion overrides EXCLUDE_COMPONENTS).
|
||||
# Excluded components are dropped here as well: the discovery list
|
||||
# (cached per target) still holds them, and requiring an excluded
|
||||
# component pulls it back into the build (IDF requirement expansion
|
||||
# overrides EXCLUDE_COMPONENTS).
|
||||
# Derived from the EXCLUDE_COMPONENTS cmake arg emitted above so the
|
||||
# two can never disagree within one generated file.
|
||||
builtin_components_property = (
|
||||
@@ -202,6 +403,9 @@ set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
|
||||
|
||||
include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
||||
|
||||
{IDF_BOOTLOADER_OVERRIDE}
|
||||
{ldgen_override}
|
||||
|
||||
{cpp_standard_options}
|
||||
|
||||
{cxx_compile_options}
|
||||
@@ -214,14 +418,31 @@ include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
||||
|
||||
project({CORE.name})
|
||||
|
||||
{ldgen_override_check}
|
||||
|
||||
{_lwip_empty_sources_filter()}
|
||||
|
||||
# Emit per-memory-type JSON size data for ESPHome to read post-build.
|
||||
# json2 stays small; raw dumps every symbol (~2s on a large map) and
|
||||
# this command runs inside the link edge, blocking everything downstream.
|
||||
# IDF's size target depends on the map, so ninja has to know the link
|
||||
# writes it for size to build in the same run as all. IDF declares the
|
||||
# map itself since espressif/esp-idf#19201 (and in cmakev2), and ninja
|
||||
# rejects two rules for one output, so declare it here only when IDF has
|
||||
# not. add_custom_command(OUTPUT) marks its outputs GENERATED.
|
||||
get_source_file_property(esphome_map_declared
|
||||
${{CMAKE_BINARY_DIR}}/${{CMAKE_PROJECT_NAME}}.map GENERATED)
|
||||
if(esphome_map_declared)
|
||||
set(esphome_map_byproducts)
|
||||
else()
|
||||
set(esphome_map_byproducts BYPRODUCTS ${{CMAKE_BINARY_DIR}}/${{CMAKE_PROJECT_NAME}}.map)
|
||||
endif()
|
||||
add_custom_command(
|
||||
TARGET ${{CMAKE_PROJECT_NAME}}.elf POST_BUILD
|
||||
COMMAND ${{PYTHON}} -m esp_idf_size {size_ng_flag} --format=json2
|
||||
-o ${{CMAKE_BINARY_DIR}}/esp_idf_size.json
|
||||
${{CMAKE_PROJECT_NAME}}.map
|
||||
${{esphome_map_byproducts}}
|
||||
WORKING_DIRECTORY ${{CMAKE_BINARY_DIR}}
|
||||
VERBATIM
|
||||
)
|
||||
@@ -370,10 +591,9 @@ def write_project(
|
||||
)
|
||||
|
||||
# Snapshot the exclusion set so has_outdated_files() can trigger a
|
||||
# discovery reconfigure when it changes. Excluded components never
|
||||
# register in project_description.json, so re-including one (e.g. a
|
||||
# config gains mqtt) requires a fresh discovery pass before the
|
||||
# ESPHOME_PROJECT_BUILTIN_COMPONENTS property can list it.
|
||||
# reconfigure when it changes. The ESPHOME_PROJECT_BUILTIN_COMPONENTS
|
||||
# property drops excluded names, so re-including one (e.g. a config
|
||||
# gains mqtt) needs a reconfigure to rebuild it from the discovery list.
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("exclude_components.esphomeinternal"),
|
||||
";".join(get_excluded_builtin_components()),
|
||||
|
||||
@@ -207,10 +207,12 @@ def compile_edges(
|
||||
|
||||
|
||||
def library_edges(
|
||||
lines: list[str], libraries: list[ArduinoLibrary]
|
||||
lines: list[str], libraries: list[ArduinoLibrary], extra_flags: str = ""
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""Emit every library's compile and archive edges.
|
||||
|
||||
``extra_flags`` (raw ninja text) precede each library's own flags.
|
||||
|
||||
Returns the archive names and the objects that link directly. A
|
||||
library's own include dirs lead its compile lines, as PlatformIO searched
|
||||
them first: the include path is one global list, so another library's
|
||||
@@ -232,7 +234,7 @@ def library_edges(
|
||||
lib.sources,
|
||||
common_parent(lib.sources),
|
||||
f"lib/{lib.name}",
|
||||
flags=" ".join(_shell_token(f) for f in lib.flags),
|
||||
flags=" ".join(filter(None, [extra_flags, *map(_shell_token, lib.flags)])),
|
||||
own_includes=" ".join(f"-I{_q(d)}" for d in lib.include_dirs),
|
||||
)
|
||||
if not lib.lib_archive:
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
"""Commands of the esphome command line, one module each, imported by
|
||||
__main__ only when they run so that startup stays light."""
|
||||
@@ -0,0 +1,164 @@
|
||||
"""``esphome rename``."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from esphome import yaml_edit, yaml_util
|
||||
from esphome.const import (
|
||||
ALLOWED_NAME_CHARS,
|
||||
CONF_ESPHOME,
|
||||
CONF_NAME,
|
||||
CONF_SUBSTITUTIONS,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError
|
||||
from esphome.log import AnsiFore, color
|
||||
from esphome.types import ConfigType
|
||||
from esphome.util import ESPHOME_COMMAND, run_external_process, safe_print
|
||||
|
||||
|
||||
def _revert(new_path: Path, why: str) -> int:
|
||||
"""Say why the rename stopped and take the new file back; an orphan the
|
||||
next attempt would trip over is reported."""
|
||||
safe_print(color(AnsiFore.BOLD_RED, f"Rename failed: {why}"))
|
||||
try:
|
||||
new_path.unlink(missing_ok=True)
|
||||
except OSError as err:
|
||||
safe_print(color(AnsiFore.BOLD_RED, f"Could not remove {new_path}: {err}"))
|
||||
return 1
|
||||
|
||||
|
||||
def command_rename(args: argparse.Namespace, config: ConfigType) -> int | None:
|
||||
"""Rename the device: a new file with the name line rewritten, validated
|
||||
and installed, then the old file removed."""
|
||||
new_name = args.name
|
||||
for c in new_name:
|
||||
if c not in ALLOWED_NAME_CHARS:
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"'{c}' is an invalid character for names. Valid characters are: "
|
||||
f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
|
||||
yaml = yaml_util.load_yaml(CORE.config_path)
|
||||
|
||||
def name_edit() -> tuple[str, yaml_edit.LineEdit]:
|
||||
"""The name and the line to rewrite: the name's own line, or the
|
||||
substitution's line it comes from, as a plain value in this file."""
|
||||
esphome_conf = yaml.get(CONF_ESPHOME)
|
||||
if not isinstance(esphome_conf, dict) or CONF_NAME not in esphome_conf:
|
||||
raise EsphomeError(f"no '{CONF_ESPHOME}: {CONF_NAME}:' in the file")
|
||||
old_name = str(esphome_conf[CONF_NAME])
|
||||
mapping, field = esphome_conf, CONF_NAME
|
||||
if match := re.match(r"^\$\{?([a-zA-Z0-9_]+)\}?$", old_name):
|
||||
mapping, field = yaml.get(CONF_SUBSTITUTIONS), match.group(1)
|
||||
if not isinstance(mapping, dict) or field not in mapping:
|
||||
raise EsphomeError(f"the substitution '{field}' is not in the file")
|
||||
old_name = str(mapping[field])
|
||||
# Only read here; the rewritten text goes to a new file, so the
|
||||
# source may live anywhere the config path points to
|
||||
source = yaml_edit.source_of(mapping, field)
|
||||
if source is None or source[0].resolve() != CORE.config_path.resolve():
|
||||
raise EsphomeError(f"'{field}' was not read from {CORE.config_path}")
|
||||
doc, line_no = source
|
||||
text = yaml_edit.line_at(doc, line_no)
|
||||
if (line_match := yaml_edit.field_line_re(field, old_name).match(text)) is None:
|
||||
raise EsphomeError(f"'{field}' is not a plain value on {doc}:{line_no + 1}")
|
||||
# The new value is always quoted, whatever the old line had
|
||||
return old_name, yaml_edit.LineEdit(
|
||||
doc, line_no, text, yaml_edit.rewrite(line_match, new_name, '"')
|
||||
)
|
||||
|
||||
try:
|
||||
old_name, edit = name_edit()
|
||||
except EsphomeError as err:
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"Complex YAML files cannot be automatically renamed: {err}",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
|
||||
# ``new_name == old_name`` (after substitution resolution) is
|
||||
# a no-op rewrite that would still queue a pointless re-flash.
|
||||
# Catch it before the path-equality check below — covers the
|
||||
# case where the config filename doesn't match the device name
|
||||
# (e.g. ``weird-file.yaml`` whose ``esphome.name`` is
|
||||
# ``kitchen``; running ``esphome rename weird-file.yaml kitchen``
|
||||
# would otherwise just re-flash the same hostname).
|
||||
if new_name == old_name:
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"'{new_name}' is already the device's name.",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
|
||||
new_path: Path = CORE.config_dir / (new_name + ".yaml")
|
||||
if new_path.resolve() == CORE.config_path.resolve():
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"'{new_name}' is already the device's name.",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
if new_path.exists():
|
||||
safe_print(
|
||||
color(
|
||||
AnsiFore.BOLD_RED,
|
||||
f"Cannot rename: {new_path} already exists. "
|
||||
"Refusing to overwrite an existing configuration.",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
safe_print(
|
||||
f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
|
||||
)
|
||||
print()
|
||||
|
||||
try:
|
||||
yaml_edit.write_keeping_mode(
|
||||
new_path,
|
||||
yaml_edit.rewritten_text(yaml_edit.read_text(CORE.config_path), [edit]),
|
||||
like=CORE.config_path,
|
||||
)
|
||||
except EsphomeError as err:
|
||||
return _revert(new_path, str(err))
|
||||
|
||||
if run_external_process(*ESPHOME_COMMAND, "config", str(new_path)) != 0:
|
||||
return _revert(new_path, "the new configuration does not validate")
|
||||
|
||||
cli_args = [
|
||||
"run",
|
||||
str(new_path),
|
||||
"--no-logs",
|
||||
"--device",
|
||||
CORE.address,
|
||||
]
|
||||
|
||||
if args.dashboard:
|
||||
cli_args.insert(0, "--dashboard")
|
||||
|
||||
try:
|
||||
rc = run_external_process(*ESPHOME_COMMAND, *cli_args)
|
||||
except KeyboardInterrupt:
|
||||
rc = 1
|
||||
if rc != 0:
|
||||
return _revert(
|
||||
new_path,
|
||||
"the install did not finish; the device may already run the new name",
|
||||
)
|
||||
|
||||
CORE.config_path.unlink()
|
||||
|
||||
safe_print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
|
||||
print()
|
||||
return 0
|
||||
@@ -31,15 +31,19 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
add_global,
|
||||
add_library,
|
||||
add_platformio_option,
|
||||
extern_progmem_array,
|
||||
get_variable,
|
||||
get_variable_with_full_id,
|
||||
is_static_pointer,
|
||||
is_template,
|
||||
new_Pvariable,
|
||||
new_variable,
|
||||
process_lambda,
|
||||
progmem_array,
|
||||
progmem_string,
|
||||
safe_exp,
|
||||
set_cpp_standard,
|
||||
shared_progmem_array,
|
||||
statement,
|
||||
static_const_array,
|
||||
static_function,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@MrSuicideParrot"]
|
||||
DOMAIN = "a01nyub"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::a01nyub {
|
||||
|
||||
static const char *const TAG = "a01nyub.sensor";
|
||||
ESPHOME_LOG_TAG(TAG, "a01nyub.sensor");
|
||||
|
||||
void A01nyubComponent::loop() {
|
||||
uint8_t data;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@TH-Braemer"]
|
||||
DOMAIN = "a02yyuw"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::a02yyuw {
|
||||
|
||||
static const char *const TAG = "a02yyuw.sensor";
|
||||
ESPHOME_LOG_TAG(TAG, "a02yyuw.sensor");
|
||||
|
||||
void A02yyuwComponent::loop() {
|
||||
uint8_t data;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DOMAIN = "a4988"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::a4988 {
|
||||
|
||||
static const char *const TAG = "a4988.stepper";
|
||||
ESPHOME_LOG_TAG(TAG, "a4988.stepper");
|
||||
|
||||
void A4988::setup() {
|
||||
if (this->sleep_pin_ != nullptr) {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@DAVe3283"]
|
||||
DOMAIN = "absolute_humidity"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::absolute_humidity {
|
||||
|
||||
static const char *const TAG{"absolute_humidity.sensor"};
|
||||
ESPHOME_LOG_TAG(TAG, "absolute_humidity.sensor");
|
||||
|
||||
void AbsoluteHumidityComponent::setup() {
|
||||
this->temperature_sensor_->add_on_state_callback([this](float state) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DOMAIN = "ac_dimmer"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
static const char *const TAG = "ac_dimmer";
|
||||
ESPHOME_LOG_TAG(TAG, "ac_dimmer");
|
||||
|
||||
// Global array to store dimmer objects
|
||||
static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "esp_clk_tree.h"
|
||||
#include "soc/clk_tree_defs.h"
|
||||
|
||||
static const char *const TAG = "hw_timer_esp_idf";
|
||||
ESPHOME_LOG_TAG(TAG, "hw_timer_esp_idf");
|
||||
|
||||
namespace esphome::ac_dimmer {
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
DOMAIN = "adalight"
|
||||
|
||||
adalight_ns = cg.esphome_ns.namespace("adalight")
|
||||
AdalightLightEffect = adalight_ns.class_(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::adalight {
|
||||
|
||||
static const char *const TAG = "adalight_light_effect";
|
||||
ESPHOME_LOG_TAG(TAG, "adalight_light_effect");
|
||||
|
||||
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
|
||||
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
|
||||
|
||||
@@ -22,6 +22,7 @@ from esphome.core import CORE
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DOMAIN = "adc"
|
||||
|
||||
adc_ns = cg.esphome_ns.namespace("adc")
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
ESPHOME_LOG_TAG(TAG, "adc");
|
||||
|
||||
const LogString *sampling_mode_to_str(SamplingMode mode) {
|
||||
switch (mode) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
ESPHOME_LOG_TAG(TAG, "adc");
|
||||
|
||||
adc_oneshot_unit_handle_t ADCSensor::shared_adc_handles[2] = {nullptr, nullptr};
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ ADC_MODE(ADC_VCC)
|
||||
|
||||
namespace esphome::adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
ESPHOME_LOG_TAG(TAG, "adc");
|
||||
|
||||
void ADCSensor::setup() {
|
||||
#ifndef USE_ADC_SENSOR_VCC
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
ESPHOME_LOG_TAG(TAG, "adc");
|
||||
|
||||
void ADCSensor::setup() {
|
||||
#ifndef USE_ADC_SENSOR_VCC
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
namespace esphome::adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
ESPHOME_LOG_TAG(TAG, "adc");
|
||||
|
||||
// The on-die temperature sensor sits on the last ADC channel: input 4 on RP2040
|
||||
// and RP2350A, but input 8 on RP2350B, which has eight external channels rather
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
ESPHOME_LOG_TAG(TAG, "adc");
|
||||
|
||||
void ADCSensor::setup() {
|
||||
if (!adc_is_ready_dt(this->channel_)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["spi"]
|
||||
DOMAIN = "adc128s102"
|
||||
MULTI_CONF = True
|
||||
CODEOWNERS = ["@DeerMaximum"]
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::adc128s102 {
|
||||
|
||||
static const char *const TAG = "adc128s102";
|
||||
ESPHOME_LOG_TAG(TAG, "adc128s102");
|
||||
|
||||
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::adc128s102 {
|
||||
|
||||
static const char *const TAG = "adc128s102.sensor";
|
||||
ESPHOME_LOG_TAG(TAG, "adc128s102.sensor");
|
||||
|
||||
ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DOMAIN = "addressable_light"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::addressable_light {
|
||||
|
||||
static const char *const TAG = "addressable_light.display";
|
||||
ESPHOME_LOG_TAG(TAG, "addressable_light.display");
|
||||
|
||||
int AddressableLightDisplay::get_width_internal() { return this->width_; }
|
||||
int AddressableLightDisplay::get_height_internal() { return this->height_; }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DOMAIN = "ade7880"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
namespace esphome::ade7880 {
|
||||
|
||||
static const char *const TAG = "ade7880";
|
||||
ESPHOME_LOG_TAG(TAG, "ade7880");
|
||||
|
||||
void IRAM_ATTR ADE7880Store::gpio_intr(ADE7880Store *arg) { arg->reset_done = true; }
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@angelnu"]
|
||||
DOMAIN = "ade7953"
|
||||
|
||||
@@ -27,6 +27,7 @@ from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@angelnu"]
|
||||
DOMAIN = "ade7953_base"
|
||||
|
||||
CONF_CURRENT_A = "current_a"
|
||||
CONF_CURRENT_B = "current_b"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::ade7953_base {
|
||||
|
||||
static const char *const TAG = "ade7953";
|
||||
ESPHOME_LOG_TAG(TAG, "ade7953");
|
||||
|
||||
constexpr uint16_t CONFIG_DEFAULT = 0x8004u;
|
||||
constexpr uint16_t CONFIG_LOCK_BIT = 0x8000u;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@angelnu"]
|
||||
DOMAIN = "ade7953_i2c"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::ade7953_i2c {
|
||||
|
||||
static const char *const TAG = "ade7953";
|
||||
ESPHOME_LOG_TAG(TAG, "ade7953");
|
||||
|
||||
void AdE7953I2c::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADE7953_i2c:");
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@angelnu"]
|
||||
DOMAIN = "ade7953_spi"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::ade7953_spi {
|
||||
|
||||
static const char *const TAG = "ade7953";
|
||||
ESPHOME_LOG_TAG(TAG, "ade7953");
|
||||
|
||||
// Datasheet requires at least 1.2µs after clearing CONFIG LOCK_BIT before raising CS
|
||||
constexpr uint8_t CONFIG_LOCK_SETTLE_US = 2;
|
||||
|
||||
@@ -5,6 +5,7 @@ from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
DOMAIN = "ads1115"
|
||||
MULTI_CONF = True
|
||||
|
||||
ads1115_ns = cg.esphome_ns.namespace("ads1115")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::ads1115 {
|
||||
|
||||
static const char *const TAG = "ads1115";
|
||||
ESPHOME_LOG_TAG(TAG, "ads1115");
|
||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::ads1115 {
|
||||
|
||||
static const char *const TAG = "ads1115.sensor";
|
||||
ESPHOME_LOG_TAG(TAG, "ads1115.sensor");
|
||||
|
||||
float ADS1115Sensor::sample() {
|
||||
return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->resolution_, this->samplerate_);
|
||||
|
||||
@@ -6,6 +6,7 @@ from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@solomondg1"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
DOMAIN = "ads1118"
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_ADS1118_ID = "ads1118_id"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::ads1118 {
|
||||
|
||||
static const char *const TAG = "ads1118";
|
||||
ESPHOME_LOG_TAG(TAG, "ads1118");
|
||||
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
|
||||
|
||||
void ADS1118::setup() {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::ads1118 {
|
||||
|
||||
static const char *const TAG = "ads1118.sensor";
|
||||
ESPHOME_LOG_TAG(TAG, "ads1118.sensor");
|
||||
|
||||
void ADS1118Sensor::dump_config() {
|
||||
LOG_SENSOR(" ", "ADS1118 Sensor", this);
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@mak-42"]
|
||||
DOMAIN = "ags10"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome::ags10 {
|
||||
static const char *const TAG = "ags10";
|
||||
ESPHOME_LOG_TAG(TAG, "ags10");
|
||||
|
||||
// Data acquisition.
|
||||
static const uint8_t REG_TVOC = 0x00;
|
||||
|
||||
@@ -62,16 +62,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||
cg.add(var.set_tvoc(sens))
|
||||
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await sensor.new_sensor(version_config)
|
||||
cg.add(var.set_version(sens))
|
||||
|
||||
if resistance_config := config.get(CONF_RESISTANCE):
|
||||
sens = await sensor.new_sensor(resistance_config)
|
||||
cg.add(var.set_resistance(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TVOC, var.set_tvoc)
|
||||
await sensors(CONF_VERSION, var.set_version)
|
||||
await sensors(CONF_RESISTANCE, var.set_resistance)
|
||||
|
||||
|
||||
AGS10_NEW_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value(
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DOMAIN = "aht10"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace esphome::aht10 {
|
||||
|
||||
static const char *const TAG = "aht10";
|
||||
ESPHOME_LOG_TAG(TAG, "aht10");
|
||||
static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00};
|
||||
static const uint8_t AHT20_INITIALIZE_CMD[] = {0xBE, 0x08, 0x00};
|
||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
||||
|
||||
@@ -57,10 +57,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await i2c.register_i2c_device(var, config)
|
||||
cg.add(var.set_variant(config[CONF_VARIANT]))
|
||||
|
||||
if temperature := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DOMAIN = "aic3204"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::aic3204 {
|
||||
|
||||
static const char *const TAG = "aic3204";
|
||||
ESPHOME_LOG_TAG(TAG, "aic3204");
|
||||
|
||||
#define ERROR_CHECK(err, msg) \
|
||||
if (!(err)) { \
|
||||
|
||||
@@ -6,6 +6,7 @@ from esphome.types import ConfigType
|
||||
|
||||
AUTO_LOAD = ["ble_device_base"]
|
||||
CODEOWNERS = ["@jeromelaban"]
|
||||
DOMAIN = "airthings_ble"
|
||||
|
||||
airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble")
|
||||
AirthingsListener = airthings_ble_ns.class_(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::airthings_ble {
|
||||
|
||||
static const char *const TAG = "airthings_ble";
|
||||
ESPHOME_LOG_TAG(TAG, "airthings_ble");
|
||||
|
||||
bool AirthingsListener::parse_device(const ble_device_base::ESPBTDevice &device) {
|
||||
for (auto &it : device.get_manufacturer_datas()) {
|
||||
|
||||
@@ -24,6 +24,7 @@ from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@ncareau", "@jeromelaban"]
|
||||
DOMAIN = "airthings_wave_base"
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
|
||||
namespace esphome::airthings_wave_base {
|
||||
|
||||
static const char *const TAG = "airthings_wave_base";
|
||||
ESPHOME_LOG_TAG(TAG, "airthings_wave_base");
|
||||
|
||||
static constexpr uint32_t BATTERY_READ_TIMEOUT_ID = 0;
|
||||
static constexpr uint32_t RESPONSE_TIMEOUT_ID = 1;
|
||||
|
||||
void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
@@ -181,7 +184,7 @@ void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) {
|
||||
// read the battery again at the configured update interval
|
||||
if (this->battery_update_interval_ != this->update_interval_) {
|
||||
this->read_battery_next_update_ = false;
|
||||
this->set_timeout("battery", this->battery_update_interval_,
|
||||
this->set_timeout(BATTERY_READ_TIMEOUT_ID, this->battery_update_interval_,
|
||||
[this]() { this->read_battery_next_update_ = true; });
|
||||
}
|
||||
}
|
||||
@@ -204,7 +207,7 @@ void AirthingsWaveBase::response_received_() {
|
||||
}
|
||||
|
||||
void AirthingsWaveBase::set_response_timeout_() {
|
||||
this->set_timeout("response_timeout", 30 * 1000, [this]() {
|
||||
this->set_timeout(RESPONSE_TIMEOUT_ID, 30 * 1000, [this]() {
|
||||
this->responses_pending_ = 1;
|
||||
this->response_received_();
|
||||
});
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@ncareau"]
|
||||
DOMAIN = "airthings_wave_mini"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::airthings_wave_mini {
|
||||
|
||||
static const char *const TAG = "airthings_wave_mini";
|
||||
ESPHOME_LOG_TAG(TAG, "airthings_wave_mini");
|
||||
|
||||
void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) {
|
||||
auto *value = (WaveMiniReadings *) raw_value;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@jeromelaban", "@precurse"]
|
||||
DOMAIN = "airthings_wave_plus"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace esphome::airthings_wave_plus {
|
||||
|
||||
static const char *const TAG = "airthings_wave_plus";
|
||||
ESPHOME_LOG_TAG(TAG, "airthings_wave_plus");
|
||||
|
||||
void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
|
||||
auto *value = (WavePlusReadings *) raw_value;
|
||||
|
||||
@@ -21,6 +21,7 @@ from esphome.core.entity_helpers import (
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
CODEOWNERS = ["@grahambrown11", "@hwstar"]
|
||||
DOMAIN = "alarm_control_panel"
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CONF_ON_TRIGGERED = "on_triggered"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
static const char *const TAG = "alarm_control_panel";
|
||||
ESPHOME_LOG_TAG(TAG, "alarm_control_panel");
|
||||
|
||||
AlarmControlPanelCall AlarmControlPanel::make_call() { return AlarmControlPanelCall(this); }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::alarm_control_panel {
|
||||
|
||||
static const char *const TAG = "alarm_control_panel";
|
||||
ESPHOME_LOG_TAG(TAG, "alarm_control_panel");
|
||||
|
||||
AlarmControlPanelCall::AlarmControlPanelCall(AlarmControlPanel *parent) : parent_(parent) {}
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@jan-hofmeier"]
|
||||
DOMAIN = "alpha3"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::alpha3 {
|
||||
|
||||
static const char *const TAG = "alpha3";
|
||||
ESPHOME_LOG_TAG(TAG, "alpha3");
|
||||
|
||||
void Alpha3::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ALPHA3");
|
||||
|
||||
@@ -74,26 +74,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if flow_config := config.get(CONF_FLOW):
|
||||
sens = await sensor.new_sensor(flow_config)
|
||||
cg.add(var.set_flow_sensor(sens))
|
||||
|
||||
if head_config := config.get(CONF_HEAD):
|
||||
sens = await sensor.new_sensor(head_config)
|
||||
cg.add(var.set_head_sensor(sens))
|
||||
|
||||
if power_config := config.get(CONF_POWER):
|
||||
sens = await sensor.new_sensor(power_config)
|
||||
cg.add(var.set_power_sensor(sens))
|
||||
|
||||
if current_config := config.get(CONF_CURRENT):
|
||||
sens = await sensor.new_sensor(current_config)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
|
||||
if speed_config := config.get(CONF_SPEED):
|
||||
sens = await sensor.new_sensor(speed_config)
|
||||
cg.add(var.set_speed_sensor(sens))
|
||||
|
||||
if voltage_config := config.get(CONF_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_FLOW, var.set_flow_sensor)
|
||||
await sensors(CONF_HEAD, var.set_head_sensor)
|
||||
await sensors(CONF_POWER, var.set_power_sensor)
|
||||
await sensors(CONF_CURRENT, var.set_current_sensor)
|
||||
await sensors(CONF_SPEED, var.set_speed_sensor)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@swoboda1337"]
|
||||
DOMAIN = "am2315c"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
namespace esphome::am2315c {
|
||||
|
||||
static const char *const TAG = "am2315c";
|
||||
ESPHOME_LOG_TAG(TAG, "am2315c");
|
||||
|
||||
bool AM2315C::reset_register_(uint8_t reg) {
|
||||
// code based on demo code sent by www.aosong.com
|
||||
|
||||
@@ -46,10 +46,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DOMAIN = "am2320"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace esphome::am2320 {
|
||||
|
||||
static const char *const TAG = "am2320";
|
||||
ESPHOME_LOG_TAG(TAG, "am2320");
|
||||
|
||||
void AM2320Component::update() {
|
||||
uint8_t data[8];
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
|
||||
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DOMAIN = "am43"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::am43 {
|
||||
|
||||
static const char *const TAG = "am43_cover";
|
||||
ESPHOME_LOG_TAG(TAG, "am43_cover");
|
||||
|
||||
using namespace esphome::cover;
|
||||
|
||||
|
||||
@@ -48,10 +48,6 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if battery_level_config := config.get(CONF_BATTERY_LEVEL):
|
||||
sens = await sensor.new_sensor(battery_level_config)
|
||||
cg.add(var.set_battery(sens))
|
||||
|
||||
if illuminance_config := config.get(CONF_ILLUMINANCE):
|
||||
sens = await sensor.new_sensor(illuminance_config)
|
||||
cg.add(var.set_illuminance(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_BATTERY_LEVEL, var.set_battery)
|
||||
await sensors(CONF_ILLUMINANCE, var.set_illuminance)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace esphome::am43 {
|
||||
|
||||
static const char *const TAG = "am43";
|
||||
ESPHOME_LOG_TAG(TAG, "am43");
|
||||
|
||||
void Am43::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AM43");
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
CODEOWNERS = ["@ianchi"]
|
||||
DOMAIN = "analog_threshold"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace esphome::analog_threshold {
|
||||
|
||||
static const char *const TAG = "analog_threshold.binary_sensor";
|
||||
ESPHOME_LOG_TAG(TAG, "analog_threshold.binary_sensor");
|
||||
|
||||
void AnalogThresholdBinarySensor::setup() {
|
||||
float sensor_value = this->sensor_->get_state();
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DOMAIN = "anova"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace esphome::anova {
|
||||
|
||||
static const char *const TAG = "anova";
|
||||
ESPHOME_LOG_TAG(TAG, "anova");
|
||||
|
||||
using namespace esphome::climate;
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
|
||||
|
||||
CODEOWNERS = ["@aodrenah"]
|
||||
DOMAIN = "apds9306"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace esphome::apds9306 {
|
||||
|
||||
static const char *const TAG = "apds9306";
|
||||
ESPHOME_LOG_TAG(TAG, "apds9306");
|
||||
|
||||
enum { // APDS9306 registers
|
||||
APDS9306_MAIN_CTRL = 0x00,
|
||||
|
||||
@@ -5,6 +5,7 @@ from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
DOMAIN = "apds9960"
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_APDS9960_ID = "apds9960_id"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user