Compare commits

...

87 Commits

Author SHA1 Message Date
J. Nick Koston
df2936f686 Merge branch 'dev' into parition_callbacks 2025-12-17 16:40:30 -07:00
Jonathan Swoboda
2b337aa306 [esp32_camera] Fix I2C driver conflict with other components (#12533)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-17 17:37:59 -05:00
Jonathan Swoboda
4ddaff4027 [esp32] Dynamically embed managed component server certificates (#12509)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-17 17:26:56 -05:00
J. Nick Koston
91c504061b [select] Eliminate string allocation in state callbacks (#12505)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 12:19:26 -10:00
Jonathan Swoboda
dc8f7abce2 [bme68x_bsec2_i2c] Add MULTI_CONF support for multiple sensors (#12535)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 17:07:42 -05:00
Jonathan Swoboda
3d673ac55e [ci] Check changed headers in clang-tidy when using --changed (#12540)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 11:13:18 -10:00
Jack Wilsdon
b02696edc0 [pm1006] Fix "never" update interval detection (#12529) 2025-12-18 07:40:31 +11:00
Anna Oake
f9720026d0 [cc1101] Fix default frequencies (#12539) 2025-12-17 14:19:18 -05:00
Jonathan Swoboda
d7b04a3d18 [nextion] Fix clang-tidy error on Zephyr for HTTPClient (#12538)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 13:59:49 -05:00
Jonathan Swoboda
0e71fa97a7 [spi] Add SPIInterface stub for clang-tidy on unsupported platforms (#12532)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 12:18:25 -05:00
J. Nick Koston
42e061c9ae [text] Avoid string copies in callbacks by passing const ref (#12504) 2025-12-17 12:00:19 -05:00
J. Nick Koston
94763ebdab [libretiny] Store preference keys as uint32_t, convert to string only at FlashDB boundary (#12500) 2025-12-17 11:59:40 -05:00
J. Nick Koston
f32bb618ac [esp32] Store preference keys as uint32_t, convert to string only at NVS boundary (#12494) 2025-12-17 11:59:35 -05:00
Edward Firmo
0707f383a6 [nextion] Use ESP-IDF for ESP32 Arduino (#9429)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 11:45:17 -05:00
Piotr Szulc
e91c6a79ea [deep_sleep] Deep sleep for BK72xx (#12267)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 11:45:05 -05:00
J. Nick Koston
63fc8b4e5a [core] Refactor str_snake_case and str_sanitize to use constexpr helpers (#12454) 2025-12-17 11:30:12 -05:00
J. Nick Koston
ab73ed76b8 [esphome] Improve OTA field alignment to save 4 bytes on 32-bit (#12461) 2025-12-17 11:29:58 -05:00
J. Nick Koston
bf6a03d1cf [factory_reset] Optimize memory by storing interval as uint16_t seconds (#12462) 2025-12-17 11:29:51 -05:00
J. Nick Koston
9928ab09cf [time] Convert to C++17 nested namespace syntax (#12463) 2025-12-17 11:29:43 -05:00
Thomas Rupprecht
56c1691d72 [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-16 22:52:28 -05:00
Roger Fachini
a065990ab9 [update] Add check action to trigger update checks (#12415) 2025-12-16 22:20:12 -05:00
Stuart Parmenter
084f517a20 [hub75] Add set_brightness action (#12521) 2025-12-16 22:12:33 -05:00
Jonathan Swoboda
1122ec354f [esp32] Add OTA rollback support (#12460)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:07:57 -05:00
Jonathan Swoboda
431183eebc [ledc,mqtt,resampler] Remove unnecessary ESP-IDF framework restrictions (#12442)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:07:09 -05:00
Jonathan Swoboda
08beaf8750 [esp32] Remove Arduino-specific code from core.cpp (#12501)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:06:12 -05:00
Jonathan Swoboda
18814f12dc [http_request] Use ESP-IDF for ESP32 Arduino (#12428)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:44:14 -05:00
Jonathan Swoboda
9cd888cef6 [spi] Use ESP-IDF driver for ESP32 Arduino (#12420)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:44:01 -05:00
Thomas Rupprecht
9727c7135c [openthread] channel range, fix typo, use C++17 nested namespace syntax (#12422) 2025-12-16 19:43:18 -05:00
Thomas Rupprecht
93621d85b0 [climate] Improve temperature unit regex (#12032) 2025-12-16 19:43:10 -05:00
Thomas Rupprecht
046ea922e8 [esp32] improve types and variable naming (#12423) 2025-12-16 19:42:52 -05:00
Jeff Zigler
fab4efb469 [esp32] Fix serial logging on h2, c2 & c61 (#12522)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-16 19:42:12 -05:00
Jonathan Swoboda
efc5672567 Merge branch 'release' into dev 2025-12-16 18:57:37 -05:00
Jonathan Swoboda
5e630e9255 Merge branch 'beta' into dev 2025-12-16 11:26:08 -05:00
Jonathan Swoboda
1897551b28 [uart] Fix UART on default UART0 pins for ESP-IDF (#12519)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 10:17:17 -05:00
J. Nick Koston
ead60bc5c4 [socket] Fix getpeername() returning local address instead of remote in LWIP raw TCP (#12475) 2025-12-16 00:48:30 -06:00
Jonathan Swoboda
7fe8e53f82 Merge branch 'beta' into dev 2025-12-15 19:01:12 -05:00
David Woodhouse
839139df36 Add FNV-1a hash functions (#12502) 2025-12-15 20:23:54 +00:00
dependabot[bot]
24d7e9dd23 Bump tornado from 6.5.3 to 6.5.4 (#12508)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:08:16 +00:00
dependabot[bot]
1214bb6bad Bump aioesphomeapi from 43.2.1 to 43.3.0 (#12507)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:07:20 +00:00
J. Nick Koston
ab0ca3006a Merge branch 'dev' into parition_callbacks 2025-12-15 12:20:54 -06:00
Pascal Vizeli
260ffba2a5 [http_request] Fix infinite loop when server doesn't send Content-Length header (#12480)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 12:54:12 -05:00
Jonathan Swoboda
2e899dd010 [esp32] Support all IDF component version operators in shorthand syntax (#12499)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 12:07:02 -05:00
David Woodhouse
61cbd07e1d Add hmac-sha256 support (#12437)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-15 10:55:03 -06:00
Jonathan Swoboda
450962850a [remote_base] Fix crash when ABBWelcome action has no data field (#12493)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 09:29:51 -05:00
Jonathan Swoboda
6b088caf5d Merge branch 'beta' into dev 2025-12-14 19:18:10 -05:00
J. Nick Koston
ffce80f96c [wifi] Fix WiFi recovery after failed connection attempts (#12483) 2025-12-14 16:26:34 -06:00
mbohdal
fa5b14fad4 [ethernet] fix used pins validation in configuration of RMII pins (#12486) 2025-12-14 16:40:08 -05:00
guillempages
cee532a1e3 [core] Use Arduino string macros only on ESP8266 (#12471) 2025-12-15 07:15:19 +11:00
J. Nick Koston
8524b894d6 [ota] Match client timeout to device timeout to prevent premature failures (#12484) 2025-12-14 13:47:11 -06:00
J. Nick Koston
3a5e708c13 [web_server_idf] Always enable LRU purge to prevent socket exhaustion (#12481) 2025-12-14 13:31:19 -06:00
J. Nick Koston
96e418a8ca [wifi_signal] Skip publishing disconnected RSSI value (#12482) 2025-12-14 13:31:07 -06:00
J. Nick Koston
780a407b10 [dashboard] Add ESPHOME_TRUSTED_DOMAINS support to events WebSocket (#12479) 2025-12-14 13:30:55 -06:00
Jonathan Swoboda
cfc0d8bdfc [cc1101] Add packet mode support (#12474)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 13:22:55 -05:00
Jonathan Swoboda
786d7266f5 [core] Fix polling_component_schema and type consistency (#12478)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 12:47:52 -05:00
Clyde Stubbs
ede64a9f47 [packet_transport] Ensure retransmission at update intervals (#12472)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-14 12:47:15 -05:00
J. Nick Koston
e0ce66e011 [core] Fix CORE.raw_config not updated after package merge (#12456) 2025-12-13 07:38:31 -06:00
David Woodhouse
6fce0a6104 Add host platform support to MD5 component (#12458) 2025-12-13 02:50:34 +00:00
David Woodhouse
ff7651875e Add HMAC-MD5 component tests (#12459) 2025-12-12 19:19:31 -06:00
David Woodhouse
1a43a06dd4 Add USE_SHA256 define to sha256 component to enable tests (#12457) 2025-12-12 19:15:50 -06:00
dependabot[bot]
51b187954a Bump ruff from 0.14.8 to 0.14.9 (#12448)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-12-12 19:20:06 +00:00
dependabot[bot]
9126b32c35 Bump actions/cache from 4.3.0 to 5.0.1 in /.github/actions/restore-python (#12453)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:17:08 -06:00
dependabot[bot]
4993bb2f49 Bump github/codeql-action from 4.31.7 to 4.31.8 (#12451)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:16:41 -06:00
dependabot[bot]
2b40af3459 Bump actions/cache from 4.3.0 to 5.0.1 (#12450)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:16:29 -06:00
dependabot[bot]
b3e967a233 Bump actions/download-artifact from 6.0.0 to 7.0.0 (#12449)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:15:41 -06:00
dependabot[bot]
26a08e3ae3 Bump actions/upload-artifact from 5.0.0 to 6.0.0 (#12452)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 13:15:28 -06:00
Jonathan Swoboda
64d650c65c Merge branch 'beta' into dev 2025-12-12 12:15:52 -05:00
Jonathan Swoboda
d30d8156c1 [http_request] Skip update check when network not connected (#12418)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 10:31:17 -05:00
dependabot[bot]
8d1e68c4c1 Bump tornado from 6.5.2 to 6.5.3 (#12430)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-11 17:53:12 -06:00
J. Nick Koston
74218bc742 [api] Release prologue memory after noise handshake completes (#12412) 2025-12-10 19:33:22 -06:00
J. Nick Koston
369cc70fdf [climate] Save 48 bytes per entity by conditionally compiling visual overrides (#12406) 2025-12-10 19:10:42 -06:00
dependabot[bot]
1f0a27b181 Bump codecov/codecov-action from 5.5.1 to 5.5.2 (#12408)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 22:34:24 +01:00
dependabot[bot]
22918d3bd5 Bump peter-evans/create-pull-request from 7.0.11 to 8.0.0 (#12409)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 22:21:29 +01:00
J. Nick Koston
7a9fce90cb [text] Add integration tests for text command API (#12401) 2025-12-10 12:13:40 -05:00
dependabot[bot]
d1d376ebc8 Bump actions/create-github-app-token from 2.2.0 to 2.2.1 (#12370)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 13:05:01 +01:00
J. Nick Koston
c124d72ea9 [esp8266] Eliminate up to 16ms socket latency (#12397) 2025-12-10 03:45:27 +00:00
J. Nick Koston
567e82cfec [api] Fix potential buffer overflow in noise PSK base64 decode (#12395) 2025-12-10 04:20:23 +01:00
J. Nick Koston
b1f9100b02 [core] Add constexpr parse_hex_char helper and simplify parse_hex (#12394) 2025-12-10 04:20:08 +01:00
J. Nick Koston
d0fbc82f47 [esp32_ble_client] Use stack-based MAC formatting in auth logging (#12393)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-10 04:19:52 +01:00
J. Nick Koston
03c391bd43 [light] Add zero-copy support for API effect commands (#12384) 2025-12-10 04:19:29 +01:00
Jonathan Swoboda
5601a2b686 Merge branch 'beta' into dev 2025-12-09 21:34:12 -05:00
Jonathan Swoboda
84d5348bd8 Bump version to 2026.1.0-dev 2025-12-09 20:08:35 -05:00
J. Nick Koston
4634eb3ce4 Merge upstream/dev into parition_callbacks
Resolved conflicts in text_sensor.cpp by preserving the PartitionedCallbackManager
implementation while adding back pragma directives for deprecation warnings.
2025-12-02 15:25:25 -06:00
J. Nick Koston
f19bbbd1c5 Merge remote-tracking branch 'origin/parition_callbacks' into parition_callbacks 2025-11-09 23:20:01 -06:00
J. Nick Koston
0f136a888c Merge branch 'dev' into parition_callbacks and address Copilot review
- Resolved conflicts in sensor.cpp and text_sensor.cpp to keep the
  PartitionedCallbackManager approach from this branch
- Fixed platform-dependent pointer size documentation (4 bytes on 32-bit, 8 bytes on 64-bit)
- Fixed potential integer underflow in add_first comparison
- Added documentation explaining asymmetric API design rationale
2025-11-09 23:19:02 -06:00
J. Nick Koston
6feaa8dd13 preserve order 2025-11-09 23:10:06 -06:00
J. Nick Koston
4c3931363f Merge remote-tracking branch 'origin/dev' into parition_callbacks 2025-11-09 22:57:10 -06:00
J. Nick Koston
9a2fc8aa51 part 2025-11-07 23:44:43 -06:00
134 changed files with 1576 additions and 628 deletions

View File

@@ -1 +1 @@
766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07
6857423aecf90accd0a8bf584d36ee094a4938f872447a4efc05a2efc6dc6481

View File

@@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: venv
# yamllint disable-line rule:line-length

View File

@@ -26,7 +26,7 @@ jobs:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}

View File

@@ -62,7 +62,7 @@ jobs:
run: git diff
- if: failure()
name: Archive artifacts
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: generated-proto-files
path: |

View File

@@ -47,7 +47,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: venv
# yamllint disable-line rule:line-length
@@ -152,12 +152,12 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: venv
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -193,7 +193,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Restore components graph cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: .temp/components_graph.json
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
@@ -223,7 +223,7 @@ jobs:
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
- name: Save components graph cache
if: github.ref == 'refs/heads/dev'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: .temp/components_graph.json
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
@@ -245,7 +245,7 @@ jobs:
python-version: "3.13"
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: venv
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
@@ -334,14 +334,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
@@ -413,14 +413,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
@@ -502,14 +502,14 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
@@ -735,7 +735,7 @@ jobs:
- name: Restore cached memory analysis
id: cache-memory-analysis
if: steps.check-script.outputs.skip != 'true'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: memory-analysis-target.json
key: ${{ steps.cache-key.outputs.cache-key }}
@@ -759,7 +759,7 @@ jobs:
- name: Cache platformio
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
@@ -800,7 +800,7 @@ jobs:
- name: Save memory analysis to cache
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: memory-analysis-target.json
key: ${{ steps.cache-key.outputs.cache-key }}
@@ -821,7 +821,7 @@ jobs:
fi
- name: Upload memory analysis JSON
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: memory-analysis-target
path: memory-analysis-target.json
@@ -847,7 +847,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Cache platformio
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.platformio
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
@@ -885,7 +885,7 @@ jobs:
--platform "$platform"
- name: Upload memory analysis JSON
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: memory-analysis-pr
path: memory-analysis-pr.json
@@ -915,13 +915,13 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Download target analysis JSON
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: memory-analysis-target
path: ./memory-analysis
continue-on-error: true
- name: Download PR analysis JSON
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: memory-analysis-pr
path: ./memory-analysis

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
category: "/language:${{matrix.language}}"

View File

@@ -138,7 +138,7 @@ jobs:
# version: ${{ needs.init.outputs.tag }}
- name: Upload digests
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: digests-${{ matrix.platform.arch }}
path: /tmp/digests
@@ -171,7 +171,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Download digests
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: digests-*
path: /tmp/digests
@@ -221,7 +221,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
@@ -256,7 +256,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
@@ -287,7 +287,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2.2.0
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}

View File

@@ -41,7 +41,7 @@ jobs:
python script/run-in-env.py pre-commit run --all-files
- name: Commit changes
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@openhomefoundation.org>

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.8
rev: v0.14.9
hooks:
# Run the linter.
- id: ruff

View File

@@ -215,6 +215,7 @@ esphome/components/hlk_fm22x/* @OnFreund
esphome/components/hlw8032/* @rici4kubicek
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/hmac_sha256/* @dwmw2
esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2025.12.0
PROJECT_NUMBER = 2026.1.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

View File

@@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(ADE7880),
cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All(
cv.frequency, cv.Range(min=45.0, max=66.0)
cv.frequency, cv.float_range(min=45.0, max=66.0)
),
cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema,
cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema,

View File

@@ -539,7 +539,8 @@ APIError APINoiseFrameHelper::init_handshake_() {
if (aerr != APIError::OK)
return aerr;
// set_prologue copies it into handshakestate, so we can get rid of it now
prologue_ = {};
// Use swap idiom to actually release memory (= {} only clears size, not capacity)
std::vector<uint8_t>().swap(prologue_);
err = noise_handshakestate_start(handshake_);
aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);

View File

@@ -11,6 +11,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
AUTO_LOAD = ["bme68x_bsec2"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c")
BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_(

View File

@@ -165,7 +165,7 @@ CONFIG_MAP = {
CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0),
CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False),
CONF_DC_BLOCKING_FILTER: cv.boolean,
CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)),
CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)),
CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)),
CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)),
CONF_CHANNEL: cv.uint8_t,

View File

@@ -99,11 +99,11 @@ CC1101Component::CC1101Component() {
this->state_.FS_AUTOCAL = 1;
// Default Settings
this->set_frequency(433920);
this->set_if_frequency(153);
this->set_filter_bandwidth(203);
this->set_frequency(433920000);
this->set_if_frequency(153000);
this->set_filter_bandwidth(203000);
this->set_channel(0);
this->set_channel_spacing(200);
this->set_channel_spacing(200000);
this->set_symbol_rate(5000);
this->set_sync_mode(SyncMode::SYNC_MODE_NONE);
this->set_carrier_sense_above_threshold(true);

View File

@@ -117,9 +117,7 @@ CONF_MIN_HUMIDITY = "min_humidity"
CONF_MAX_HUMIDITY = "max_humidity"
CONF_TARGET_HUMIDITY = "target_humidity"
visual_temperature = cv.float_with_unit(
"visual_temperature", "(°C|° C|°|C|°K|° K|K|°F|° F|F)?"
)
visual_temperature = cv.float_with_unit("visual_temperature", "(°|(° ?)?[CKF])?")
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema(
@@ -275,10 +273,13 @@ async def setup_climate_core_(var, config):
visual = config[CONF_VISUAL]
if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None:
cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES")
cg.add(var.set_visual_min_temperature_override(min_temp))
if (max_temp := visual.get(CONF_MAX_TEMPERATURE)) is not None:
cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES")
cg.add(var.set_visual_max_temperature_override(max_temp))
if (temp_step := visual.get(CONF_TEMPERATURE_STEP)) is not None:
cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES")
cg.add(
var.set_visual_temperature_step_override(
temp_step[CONF_TARGET_TEMPERATURE],
@@ -286,8 +287,10 @@ async def setup_climate_core_(var, config):
)
)
if (min_humidity := visual.get(CONF_MIN_HUMIDITY)) is not None:
cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES")
cg.add(var.set_visual_min_humidity_override(min_humidity))
if (max_humidity := visual.get(CONF_MAX_HUMIDITY)) is not None:
cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES")
cg.add(var.set_visual_max_humidity_override(max_humidity))
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:

View File

@@ -473,26 +473,28 @@ void Climate::publish_state() {
ClimateTraits Climate::get_traits() {
auto traits = this->traits();
if (this->visual_min_temperature_override_.has_value()) {
traits.set_visual_min_temperature(*this->visual_min_temperature_override_);
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
if (!std::isnan(this->visual_min_temperature_override_)) {
traits.set_visual_min_temperature(this->visual_min_temperature_override_);
}
if (this->visual_max_temperature_override_.has_value()) {
traits.set_visual_max_temperature(*this->visual_max_temperature_override_);
if (!std::isnan(this->visual_max_temperature_override_)) {
traits.set_visual_max_temperature(this->visual_max_temperature_override_);
}
if (this->visual_target_temperature_step_override_.has_value()) {
traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_);
traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_);
if (!std::isnan(this->visual_target_temperature_step_override_)) {
traits.set_visual_target_temperature_step(this->visual_target_temperature_step_override_);
traits.set_visual_current_temperature_step(this->visual_current_temperature_step_override_);
}
if (this->visual_min_humidity_override_.has_value()) {
traits.set_visual_min_humidity(*this->visual_min_humidity_override_);
if (!std::isnan(this->visual_min_humidity_override_)) {
traits.set_visual_min_humidity(this->visual_min_humidity_override_);
}
if (this->visual_max_humidity_override_.has_value()) {
traits.set_visual_max_humidity(*this->visual_max_humidity_override_);
if (!std::isnan(this->visual_max_humidity_override_)) {
traits.set_visual_max_humidity(this->visual_max_humidity_override_);
}
#endif
return traits;
}
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) {
this->visual_min_temperature_override_ = visual_min_temperature_override;
}
@@ -513,6 +515,7 @@ void Climate::set_visual_min_humidity_override(float visual_min_humidity_overrid
void Climate::set_visual_max_humidity_override(float visual_max_humidity_override) {
this->visual_max_humidity_override_ = visual_max_humidity_override;
}
#endif
ClimateCall Climate::make_call() { return ClimateCall(this); }

View File

@@ -213,11 +213,13 @@ class Climate : public EntityBase {
*/
ClimateTraits get_traits();
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
void set_visual_min_temperature_override(float visual_min_temperature_override);
void set_visual_max_temperature_override(float visual_max_temperature_override);
void set_visual_temperature_step_override(float target, float current);
void set_visual_min_humidity_override(float visual_min_humidity_override);
void set_visual_max_humidity_override(float visual_max_humidity_override);
#endif
/// Check if a custom fan mode is currently active.
bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; }
@@ -321,12 +323,14 @@ class Climate : public EntityBase {
CallbackManager<void(Climate &)> state_callback_{};
CallbackManager<void(ClimateCall &)> control_callback_{};
ESPPreferenceObject rtc_;
optional<float> visual_min_temperature_override_{};
optional<float> visual_max_temperature_override_{};
optional<float> visual_target_temperature_step_override_{};
optional<float> visual_current_temperature_step_override_{};
optional<float> visual_min_humidity_override_{};
optional<float> visual_max_humidity_override_{};
#ifdef USE_CLIMATE_VISUAL_OVERRIDES
float visual_min_temperature_override_{NAN};
float visual_max_temperature_override_{NAN};
float visual_target_temperature_step_override_{NAN};
float visual_current_temperature_step_override_{NAN};
float visual_min_humidity_override_{NAN};
float visual_max_humidity_override_{NAN};
#endif
private:
/** The active custom fan mode (private - enforces use of safe setters).

View File

@@ -7,7 +7,7 @@ namespace copy {
static const char *const TAG = "copy.select";
void CopySelect::setup() {
source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); });
source_->add_on_state_callback([this](size_t index) { this->publish_state(index); });
traits.set_options(source_->traits.get_options());

View File

@@ -1,4 +1,4 @@
from esphome import automation, pins
from esphome import automation, core, pins
import esphome.codegen as cg
from esphome.components import esp32, time
from esphome.components.esp32 import (
@@ -23,16 +23,20 @@ from esphome.const import (
CONF_MINUTE,
CONF_MODE,
CONF_NUMBER,
CONF_PIN,
CONF_PINS,
CONF_RUN_DURATION,
CONF_SECOND,
CONF_SLEEP_DURATION,
CONF_TIME_ID,
CONF_WAKEUP_PIN,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.core import CORE
from esphome.types import ConfigType
WAKEUP_PINS = {
VARIANT_ESP32: [
@@ -113,7 +117,7 @@ WAKEUP_PINS = {
}
def validate_pin_number(value):
def validate_pin_number_esp32(value: ConfigType) -> ConfigType:
valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32])
if value[CONF_NUMBER] not in valid_pins:
raise cv.Invalid(
@@ -122,6 +126,51 @@ def validate_pin_number(value):
return value
def validate_pin_number(value: ConfigType) -> ConfigType:
if not CORE.is_esp32:
return value
return validate_pin_number_esp32(value)
def validate_wakeup_pin(
value: ConfigType | list[ConfigType],
) -> list[ConfigType]:
if not isinstance(value, list):
processed_pins: list[ConfigType] = [{CONF_PIN: value}]
else:
processed_pins = list(value)
for i, pin_config in enumerate(processed_pins):
# now validate each item
validated_pin = WAKEUP_PIN_SCHEMA(pin_config)
validate_pin_number(validated_pin[CONF_PIN])
processed_pins[i] = validated_pin
return processed_pins
def validate_config(config: ConfigType) -> ConfigType:
# right now only BK72XX supports the list format for wakeup pins
if CORE.is_bk72xx:
if CONF_WAKEUP_PIN_MODE in config:
wakeup_pins = config.get(CONF_WAKEUP_PIN, [])
if len(wakeup_pins) > 1:
raise cv.Invalid(
"You need to remove the global wakeup_pin_mode and define it per pin"
)
if wakeup_pins:
wakeup_pins[0][CONF_WAKEUP_PIN_MODE] = config.pop(CONF_WAKEUP_PIN_MODE)
elif (
isinstance(config.get(CONF_WAKEUP_PIN), list)
and len(config[CONF_WAKEUP_PIN]) > 1
):
raise cv.Invalid(
"Your platform does not support providing multiple entries in wakeup_pin"
)
return config
def _validate_ex1_wakeup_mode(value):
if value == "ALL_LOW":
esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value)
@@ -141,6 +190,15 @@ def _validate_ex1_wakeup_mode(value):
return value
def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
if not CORE.is_bk72xx:
return value
max_duration = core.TimePeriod(hours=36)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
return value
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component)
EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action)
@@ -186,6 +244,13 @@ WAKEUP_CAUSES_SCHEMA = cv.Schema(
}
)
WAKEUP_PIN_SCHEMA = cv.Schema(
{
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.enum(WAKEUP_PIN_MODES, upper=True),
}
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -194,14 +259,15 @@ CONFIG_SCHEMA = cv.All(
cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA),
cv.positive_time_period_milliseconds,
),
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_WAKEUP_PIN): cv.All(
cv.only_on_esp32,
pins.internal_gpio_input_pin_schema,
validate_pin_number,
cv.Optional(CONF_SLEEP_DURATION): cv.All(
cv.positive_time_period_milliseconds,
_validate_sleep_duration,
),
cv.Optional(CONF_WAKEUP_PIN): validate_wakeup_pin,
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All(
cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True
cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]),
cv.enum(WAKEUP_PIN_MODES),
upper=True,
),
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
cv.only_on_esp32,
@@ -212,7 +278,8 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_PINS): cv.ensure_list(
pins.internal_gpio_input_pin_schema, validate_pin_number
pins.internal_gpio_input_pin_schema,
validate_pin_number_esp32,
),
cv.Required(CONF_MODE): cv.All(
cv.enum(EXT1_WAKEUP_MODES, upper=True),
@@ -238,7 +305,8 @@ CONFIG_SCHEMA = cv.All(
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
validate_config,
)
@@ -249,8 +317,21 @@ async def to_code(config):
if CONF_SLEEP_DURATION in config:
cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION]))
if CONF_WAKEUP_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_WAKEUP_PIN])
cg.add(var.set_wakeup_pin(pin))
pins_as_list = config.get(CONF_WAKEUP_PIN, [])
if CORE.is_bk72xx:
cg.add(var.init_wakeup_pins_(len(pins_as_list)))
for item in pins_as_list:
cg.add(
var.add_wakeup_pin(
await cg.gpio_pin_expression(item[CONF_PIN]),
item.get(
CONF_WAKEUP_PIN_MODE, WakeupPinMode.WAKEUP_PIN_MODE_IGNORE
),
)
)
else:
pin = await cg.gpio_pin_expression(pins_as_list[0][CONF_PIN])
cg.add(var.set_wakeup_pin(pin))
if CONF_WAKEUP_PIN_MODE in config:
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
if CONF_RUN_DURATION in config:
@@ -305,7 +386,10 @@ DEEP_SLEEP_ENTER_SCHEMA = cv.All(
cv.Schema(
{
cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable(
cv.positive_time_period_milliseconds
cv.All(
cv.positive_time_period_milliseconds,
_validate_sleep_duration,
)
),
# Only on ESP32 due to how long the RTC on ESP8266 can stay asleep
cv.Exclusive(CONF_UNTIL, "time"): cv.All(
@@ -363,5 +447,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_IDF,
},
"deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"deep_sleep_bk72xx.cpp": {PlatformFramework.BK72XX_ARDUINO},
}
)

View File

@@ -0,0 +1,64 @@
#ifdef USE_BK72XX
#include "deep_sleep_component.h"
#include "esphome/core/log.h"
namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep.bk72xx";
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {
for (const WakeUpPinItem &item : this->wakeup_pins_) {
LOG_PIN(" Wakeup Pin: ", item.wakeup_pin);
}
}
bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pinItem) const {
return (pinItem.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pinItem.wakeup_pin != nullptr &&
!this->sleep_duration_.has_value() && (pinItem.wakeup_level == get_real_pin_state_(*pinItem.wakeup_pin)));
}
bool DeepSleepComponent::prepare_to_sleep_() {
if (wakeup_pins_.size() > 0) {
for (WakeUpPinItem &item : this->wakeup_pins_) {
if (pin_prevents_sleep_(item)) {
// Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) {
this->status_set_warning();
ESP_LOGV(TAG, "Waiting for pin to switch state to enter deep sleep...");
}
this->next_enter_deep_sleep_ = true;
return false;
}
}
}
return true;
}
void DeepSleepComponent::deep_sleep_() {
for (WakeUpPinItem &item : this->wakeup_pins_) {
if (item.wakeup_pin_mode == WAKEUP_PIN_MODE_INVERT_WAKEUP) {
if (item.wakeup_level == get_real_pin_state_(*item.wakeup_pin)) {
item.wakeup_level = !item.wakeup_level;
}
}
ESP_LOGI(TAG, "Wake-up on P%u %s (%d)", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
static_cast<int32_t>(item.wakeup_pin_mode));
}
if (this->sleep_duration_.has_value())
lt_deep_sleep_config_timer((*this->sleep_duration_ / 1000) & 0xFFFFFFFF);
for (WakeUpPinItem &item : this->wakeup_pins_) {
lt_deep_sleep_config_gpio(1 << item.wakeup_pin->get_pin(), item.wakeup_level);
lt_deep_sleep_keep_floating_gpio(1 << item.wakeup_pin->get_pin(), true);
}
lt_deep_sleep_enter();
}
} // namespace esphome::deep_sleep
#endif // USE_BK72XX

View File

@@ -19,7 +19,7 @@
namespace esphome {
namespace deep_sleep {
#ifdef USE_ESP32
#if defined(USE_ESP32) || defined(USE_BK72XX)
/** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32
* and the scenario occurs that the wakeup pin is already in the wakeup state.
@@ -33,7 +33,17 @@ enum WakeupPinMode {
*/
WAKEUP_PIN_MODE_INVERT_WAKEUP,
};
#endif
#if defined(USE_BK72XX)
struct WakeUpPinItem {
InternalGPIOPin *wakeup_pin;
WakeupPinMode wakeup_pin_mode;
bool wakeup_level;
};
#endif // USE_BK72XX
#ifdef USE_ESP32
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
struct Ext1Wakeup {
uint64_t mask;
@@ -75,6 +85,13 @@ class DeepSleepComponent : public Component {
void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
#endif // USE_ESP32
#if defined(USE_BK72XX)
void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); }
void add_wakeup_pin(InternalGPIOPin *wakeup_pin, WakeupPinMode wakeup_pin_mode) {
this->wakeup_pins_.emplace_back(WakeUpPinItem{wakeup_pin, wakeup_pin_mode, !wakeup_pin->is_inverted()});
}
#endif // USE_BK72XX
#if defined(USE_ESP32)
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
@@ -114,7 +131,17 @@ class DeepSleepComponent : public Component {
bool prepare_to_sleep_();
void deep_sleep_();
#ifdef USE_BK72XX
bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const;
bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); }
#endif // USE_BK72XX
optional<uint64_t> sleep_duration_;
#ifdef USE_BK72XX
FixedVector<WakeUpPinItem> wakeup_pins_;
#endif // USE_BK72XX
#ifdef USE_ESP32
InternalGPIOPin *wakeup_pin_;
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
@@ -124,8 +151,10 @@ class DeepSleepComponent : public Component {
#endif
optional<bool> touch_wakeup_;
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
#endif // USE_ESP32
optional<uint32_t> run_duration_;
bool next_enter_deep_sleep_{false};
bool prevent_{false};

View File

@@ -13,6 +13,7 @@ from esphome.const import (
CONF_ADVANCED,
CONF_BOARD,
CONF_COMPONENTS,
CONF_DISABLED,
CONF_ESPHOME,
CONF_FRAMEWORK,
CONF_IGNORE_EFUSE_CUSTOM_MAC,
@@ -24,6 +25,7 @@ from esphome.const import (
CONF_PLATFORMIO_OPTIONS,
CONF_REF,
CONF_REFRESH,
CONF_SAFE_MODE,
CONF_SOURCE,
CONF_TYPE,
CONF_VARIANT,
@@ -81,6 +83,7 @@ CONF_ASSERTION_LEVEL = "assertion_level"
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
CONF_RELEASE = "release"
@@ -118,8 +121,8 @@ ARDUINO_ALLOWED_VARIANTS = [
]
def get_cpu_frequencies(*frequencies):
return [str(x) + "MHZ" for x in frequencies]
def get_cpu_frequencies(*frequencies: int) -> list[str]:
return [f"{frequency}MHZ" for frequency in frequencies]
CPU_FREQUENCIES = {
@@ -136,7 +139,7 @@ CPU_FREQUENCIES = {
}
# Make sure not missed here if a new variant added.
assert all(v in CPU_FREQUENCIES for v in VARIANTS)
assert all(variant in CPU_FREQUENCIES for variant in VARIANTS)
FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values()))
@@ -250,10 +253,10 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
def add_idf_component(
*,
name: str,
repo: str = None,
ref: str = None,
path: str = None,
refresh: TimePeriod = None,
repo: str | None = None,
ref: str | None = None,
path: str | None = None,
refresh: TimePeriod | None = None,
components: list[str] | None = None,
submodules: list[str] | None = None,
):
@@ -334,7 +337,7 @@ def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
def _is_framework_url(source: str) -> str:
def _is_framework_url(source: str) -> bool:
# platformio accepts many URL schemes for framework repositories and archives including http, https, git, file, and symlink
import urllib.parse
@@ -571,6 +574,13 @@ def final_validate(config):
path=[CONF_FLASH_SIZE],
)
)
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
safe_mode_config = full_config.get(CONF_SAFE_MODE)
if safe_mode_config is None or safe_mode_config.get(CONF_DISABLED, False):
_LOGGER.warning(
"OTA rollback requires safe_mode, disabling rollback support"
)
advanced[CONF_ENABLE_OTA_ROLLBACK] = False
if errs:
raise cv.MultipleInvalid(errs)
@@ -691,6 +701,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
min=8192, max=32768
),
cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean,
}
),
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
@@ -971,29 +982,14 @@ async def to_code(config):
cg.add_platformio_option("framework", "arduino, espidf")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
cg.add_platformio_option(
"board_build.embed_txtfiles",
[
"managed_components/espressif__esp_insights/server_certs/https_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt",
],
)
cg.add_define(
"USE_ARDUINO_VERSION_CODE",
cg.RawExpression(
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
),
)
add_idf_sdkconfig_option(
"CONFIG_ARDUINO_LOOP_STACK_SIZE",
conf[CONF_ADVANCED][CONF_LOOP_TASK_STACK_SIZE],
)
add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
if get_esp32_variant() == VARIANT_ESP32S2:
@@ -1164,6 +1160,11 @@ async def to_code(config):
"CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True
)
# Enable OTA rollback support
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
add_idf_sdkconfig_option("CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE", True)
cg.add_define("USE_OTA_ROLLBACK")
cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE])
cg.add_define(
@@ -1193,7 +1194,7 @@ APP_PARTITION_SIZES = {
}
def get_arduino_partition_csv(flash_size):
def get_arduino_partition_csv(flash_size: str):
app_partition_size = APP_PARTITION_SIZES[flash_size]
eeprom_partition_size = 0x1000 # 4 KB
spiffs_partition_size = 0xF000 # 60 KB
@@ -1213,7 +1214,7 @@ spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size:
"""
def get_idf_partition_csv(flash_size):
def get_idf_partition_csv(flash_size: str):
app_partition_size = APP_PARTITION_SIZES[flash_size]
return f"""\

View File

@@ -4,25 +4,20 @@
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "preferences.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_clk_tree.h>
#include <esp_cpu.h>
#include <esp_idf_version.h>
#include <esp_ota_ops.h>
#include <esp_task_wdt.h>
#include <esp_timer.h>
#include <soc/rtc.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <hal/cpu_hal.h>
void setup(); // NOLINT(readability-redundant-declaration)
void loop(); // NOLINT(readability-redundant-declaration)
#ifdef USE_ARDUINO
#include <Esp.h>
#else
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
#include <esp_clk_tree.h>
#endif
void setup();
void loop();
#endif
// Weak stub for initArduino - overridden when the Arduino component is present
extern "C" __attribute__((weak)) void initArduino() {}
namespace esphome {
@@ -41,29 +36,13 @@ void arch_restart() {
void arch_init() {
// Enable the task watchdog only on the loop task (from which we're currently running)
#if defined(USE_ESP_IDF)
esp_task_wdt_add(nullptr);
// Idle task watchdog is disabled on ESP-IDF
#elif defined(USE_ARDUINO)
enableLoopWDT();
// Disable idle task watchdog on the core we're using (Arduino pins the task to a core)
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0
disableCore0WDT();
#endif
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1
disableCore1WDT();
#endif
#endif
// If the bootloader was compiled with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE the current
// partition will get rolled back unless it is marked as valid.
esp_ota_img_states_t state;
const esp_partition_t *running = esp_ota_get_running_partition();
if (esp_ota_get_state_partition(running, &state) == ESP_OK) {
if (state == ESP_OTA_IMG_PENDING_VERIFY) {
esp_ota_mark_app_valid_cancel_rollback();
}
}
// Handle OTA rollback: mark partition valid immediately unless USE_OTA_ROLLBACK is enabled,
// in which case safe_mode will mark it valid after confirming successful boot.
#ifndef USE_OTA_ROLLBACK
esp_ota_mark_app_valid_cancel_rollback();
#endif
}
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
@@ -71,21 +50,10 @@ uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
uint32_t arch_get_cpu_freq_hz() {
uint32_t freq = 0;
#ifdef USE_ESP_IDF
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
#else
rtc_cpu_freq_config_t config;
rtc_clk_cpu_freq_get_config(&config);
freq = config.freq_mhz * 1000000U;
#endif
#elif defined(USE_ARDUINO)
freq = ESP.getCpuFreqMHz() * 1000000;
#endif
return freq;
}
#ifdef USE_ESP_IDF
TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void loop_task(void *pv_params) {
@@ -96,6 +64,7 @@ void loop_task(void *pv_params) {
}
extern "C" void app_main() {
initArduino();
esp32::setup_preferences();
#if CONFIG_FREERTOS_UNICORE
xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle);
@@ -103,11 +72,6 @@ extern "C" void app_main() {
xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1);
#endif
}
#endif // USE_ESP_IDF
#ifdef USE_ARDUINO
extern "C" void init() { esp32::setup_preferences(); }
#endif // USE_ARDUINO
} // namespace esphome

View File

@@ -5,6 +5,7 @@ import json # noqa: E402
import os # noqa: E402
import pathlib # noqa: E402
import shutil # noqa: E402
from glob import glob # noqa: E402
def merge_factory_bin(source, target, env):
@@ -126,3 +127,14 @@ def esp32_copy_ota_bin(source, target, env):
# Run merge first, then ota copy second
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821
# Find server certificates in managed components and generate .S files.
# Workaround for PlatformIO not processing target_add_binary_data() from managed component CMakeLists.
project_dir = env.subst("$PROJECT_DIR")
managed_components = os.path.join(project_dir, "managed_components")
if os.path.isdir(managed_components):
for cert_file in glob(os.path.join(managed_components, "**/server_certs/*.crt"), recursive=True):
try:
env.FileToAsm(cert_file, FILE_TYPE="TEXT")
except Exception as e:
print(f"Error processing {os.path.basename(cert_file)}: {e}")

View File

@@ -4,26 +4,28 @@
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
#include <nvs_flash.h>
#include <cstring>
#include <cinttypes>
#include <vector>
#include <string>
#include <cstring>
#include <memory>
#include <vector>
namespace esphome {
namespace esp32 {
static const char *const TAG = "esp32.preferences";
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
static constexpr size_t KEY_BUFFER_SIZE = 12;
struct NVSData {
std::string key;
uint32_t key;
std::unique_ptr<uint8_t[]> data;
size_t len;
void set_data(const uint8_t *src, size_t size) {
data = std::make_unique<uint8_t[]>(size);
memcpy(data.get(), src, size);
len = size;
this->data = std::make_unique<uint8_t[]>(size);
memcpy(this->data.get(), src, size);
this->len = size;
}
};
@@ -31,27 +33,27 @@ static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-n
class ESP32PreferenceBackend : public ESPPreferenceBackend {
public:
std::string key;
uint32_t key;
uint32_t nvs_handle;
bool save(const uint8_t *data, size_t len) override {
// try find in pending saves and update that
for (auto &obj : s_pending_save) {
if (obj.key == key) {
if (obj.key == this->key) {
obj.set_data(data, len);
return true;
}
}
NVSData save{};
save.key = key;
save.key = this->key;
save.set_data(data, len);
s_pending_save.emplace_back(std::move(save));
ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len);
ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
return true;
}
bool load(uint8_t *data, size_t len) override {
// try find in pending saves and load from that
for (auto &obj : s_pending_save) {
if (obj.key == key) {
if (obj.key == this->key) {
if (obj.len != len) {
// size mismatch
return false;
@@ -61,22 +63,24 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
}
}
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
size_t actual_len;
esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len);
esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
return false;
}
if (actual_len != len) {
ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
return false;
}
err = nvs_get_blob(nvs_handle, key.c_str(), data, &len);
err = nvs_get_blob(this->nvs_handle, key_str, data, &len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
return false;
} else {
ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key.c_str(), len);
ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len);
}
return true;
}
@@ -103,14 +107,12 @@ class ESP32Preferences : public ESPPreferences {
}
}
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
return make_preference(length, type);
return this->make_preference(length, type);
}
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->nvs_handle = nvs_handle;
uint32_t keyval = type;
pref->key = str_sprintf("%" PRIu32, keyval);
pref->nvs_handle = this->nvs_handle;
pref->key = type;
return ESPPreferenceObject(pref);
}
@@ -123,17 +125,19 @@ class ESP32Preferences : public ESPPreferences {
// goal try write all pending saves even if one fails
int cached = 0, written = 0, failed = 0;
esp_err_t last_err = ESP_OK;
std::string last_key{};
uint32_t last_key = 0;
// go through vector from back to front (makes erase easier/more efficient)
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
const auto &save = s_pending_save[i];
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str());
if (is_changed(nvs_handle, save)) {
esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.get(), save.len);
ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len);
ESP_LOGVV(TAG, "Checking if NVS data %" PRIu32 " has changed", save.key);
if (this->is_changed(this->nvs_handle, save)) {
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len);
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", save.key.c_str(), save.len, esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.len, esp_err_to_name(err));
failed++;
last_err = err;
last_key = save.key;
@@ -141,7 +145,7 @@ class ESP32Preferences : public ESPPreferences {
}
written++;
} else {
ESP_LOGV(TAG, "NVS data not changed skipping %s len=%zu", save.key.c_str(), save.len);
ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.len);
cached++;
}
s_pending_save.erase(s_pending_save.begin() + i);
@@ -149,12 +153,12 @@ class ESP32Preferences : public ESPPreferences {
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
if (failed > 0) {
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%s", failed, esp_err_to_name(last_err),
last_key.c_str());
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err),
last_key);
}
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
esp_err_t err = nvs_commit(nvs_handle);
esp_err_t err = nvs_commit(this->nvs_handle);
if (err != 0) {
ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
return false;
@@ -163,10 +167,13 @@ class ESP32Preferences : public ESPPreferences {
return failed == 0;
}
bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, to_save.key);
size_t actual_len;
esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len);
esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
return true;
}
// Check size first before allocating memory
@@ -174,9 +181,9 @@ class ESP32Preferences : public ESPPreferences {
return true;
}
auto stored_data = std::make_unique<uint8_t[]>(actual_len);
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len);
err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
return true;
}
return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0;

View File

@@ -524,10 +524,9 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
case ESP_GAP_BLE_AUTH_CMPL_EVT:
if (!this->check_addr(param->ble_security.auth_cmpl.bd_addr))
return;
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_,
format_hex(bd_addr, 6).c_str());
char addr_str[MAC_ADDR_STR_LEN];
format_mac_addr_upper(param->ble_security.auth_cmpl.bd_addr, addr_str);
ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_, addr_str);
if (!param->ble_security.auth_cmpl.success) {
this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason);
} else {

View File

@@ -3,7 +3,7 @@ import logging
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.esp32 import add_idf_component
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
from esphome.components.psram import DOMAIN as psram_domain
import esphome.config_validation as cv
from esphome.const import (
@@ -186,7 +186,7 @@ CONFIG_SCHEMA = cv.All(
{
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number,
cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All(
cv.frequency, cv.Range(min=8e6, max=20e6)
cv.frequency, cv.float_range(min=8e6, max=20e6)
),
}
),
@@ -352,6 +352,8 @@ async def to_code(config):
cg.add_define("USE_CAMERA")
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True)
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False)
for conf in config.get(CONF_ON_STREAM_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -16,7 +16,7 @@ def valid_pwm_pin(value):
esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm")
ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component)
SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action)
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6))
CONFIG_SCHEMA = cv.All(
output.FLOAT_OUTPUT_SCHEMA.extend(

View File

@@ -80,6 +80,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
#ifdef USE_OTA_PASSWORD
std::string password_;
std::unique_ptr<uint8_t[]> auth_buf_;
#endif // USE_OTA_PASSWORD
std::unique_ptr<socket::Socket> server_;
@@ -93,7 +94,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
uint8_t handshake_buf_pos_{0};
uint8_t ota_features_{0};
#ifdef USE_OTA_PASSWORD
std::unique_ptr<uint8_t[]> auth_buf_;
uint8_t auth_buf_pos_{0};
uint8_t auth_type_{0}; // Store auth type to know which hasher to use
#endif // USE_OTA_PASSWORD

View File

@@ -50,7 +50,9 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(): cv.declare_id(FactoryResetComponent),
cv.Optional(CONF_MAX_DELAY, default="10s"): cv.All(
cv.positive_time_period_seconds,
cv.Range(min=cv.TimePeriod(milliseconds=1000)),
cv.Range(
min=cv.TimePeriod(seconds=1), max=cv.TimePeriod(seconds=65535)
),
),
cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int,
cv.Optional(CONF_ON_INCREMENT): validate_automation(
@@ -82,7 +84,7 @@ async def to_code(config):
var = cg.new_Pvariable(
config[CONF_ID],
reset_count,
config[CONF_MAX_DELAY].total_milliseconds,
config[CONF_MAX_DELAY].total_seconds,
)
await cg.register_component(var, config)
for conf in config.get(CONF_ON_INCREMENT, []):

View File

@@ -8,8 +8,7 @@
#if !defined(USE_RP2040) && !defined(USE_HOST)
namespace esphome {
namespace factory_reset {
namespace esphome::factory_reset {
static const char *const TAG = "factory_reset";
static const uint32_t POWER_CYCLES_KEY = 0xFA5C0DE;
@@ -33,10 +32,10 @@ void FactoryResetComponent::dump_config() {
this->flash_.load(&count);
ESP_LOGCONFIG(TAG, "Factory Reset by Reset:");
ESP_LOGCONFIG(TAG,
" Max interval between resets %" PRIu32 " seconds\n"
" Max interval between resets: %u seconds\n"
" Current count: %u\n"
" Factory reset after %u resets",
this->max_interval_ / 1000, count, this->required_count_);
this->max_interval_, count, this->required_count_);
}
void FactoryResetComponent::save_(uint8_t count) {
@@ -61,8 +60,8 @@ void FactoryResetComponent::setup() {
}
this->save_(count);
ESP_LOGD(TAG, "Power on reset detected, incremented count to %u", count);
this->set_timeout(this->max_interval_, [this]() {
ESP_LOGD(TAG, "No reset in the last %" PRIu32 " seconds, resetting count", this->max_interval_ / 1000);
this->set_timeout(static_cast<uint32_t>(this->max_interval_) * 1000, [this]() {
ESP_LOGD(TAG, "No reset in the last %u seconds, resetting count", this->max_interval_);
this->save_(0); // reset count
});
} else {
@@ -70,7 +69,6 @@ void FactoryResetComponent::setup() {
}
}
} // namespace factory_reset
} // namespace esphome
} // namespace esphome::factory_reset
#endif // !defined(USE_RP2040) && !defined(USE_HOST)

View File

@@ -9,12 +9,11 @@
#include <esp_system.h>
#endif
namespace esphome {
namespace factory_reset {
namespace esphome::factory_reset {
class FactoryResetComponent : public Component {
public:
FactoryResetComponent(uint8_t required_count, uint32_t max_interval)
: required_count_(required_count), max_interval_(max_interval) {}
FactoryResetComponent(uint8_t required_count, uint16_t max_interval)
: max_interval_(max_interval), required_count_(required_count) {}
void dump_config() override;
void setup() override;
@@ -26,9 +25,9 @@ class FactoryResetComponent : public Component {
~FactoryResetComponent() = default;
void save_(uint8_t count);
ESPPreferenceObject flash_{}; // saves the number of fast power cycles
uint8_t required_count_; // The number of boot attempts before fast boot is enabled
uint32_t max_interval_; // max interval between power cycles
CallbackManager<void(uint8_t, uint8_t)> increment_callback_{};
uint16_t max_interval_; // max interval between power cycles in seconds
uint8_t required_count_; // The number of boot attempts before fast boot is enabled
};
class FastBootTrigger : public Trigger<uint8_t, uint8_t> {
@@ -37,7 +36,6 @@ class FastBootTrigger : public Trigger<uint8_t, uint8_t> {
parent->add_increment_callback([this](uint8_t current, uint8_t target) { this->trigger(current, target); });
}
};
} // namespace factory_reset
} // namespace esphome
} // namespace esphome::factory_reset
#endif // !defined(USE_RP2040) && !defined(USE_HOST)

View File

@@ -1,2 +1,6 @@
import esphome.config_validation as cv
AUTO_LOAD = ["md5"]
CODEOWNERS = ["@dwmw2"]
CONFIG_SCHEMA = cv.Schema({})

View File

@@ -0,0 +1,6 @@
import esphome.config_validation as cv
AUTO_LOAD = ["sha256"]
CODEOWNERS = ["@dwmw2"]
CONFIG_SCHEMA = cv.Schema({})

View File

@@ -0,0 +1,102 @@
#include <cstdio>
#include <cstring>
#include "hmac_sha256.h"
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
#include "esphome/core/helpers.h"
namespace esphome::hmac_sha256 {
constexpr size_t SHA256_DIGEST_SIZE = 32;
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
HmacSHA256::~HmacSHA256() { mbedtls_md_free(&this->ctx_); }
void HmacSHA256::init(const uint8_t *key, size_t len) {
mbedtls_md_init(&this->ctx_);
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_md_setup(&this->ctx_, md_info, 1); // 1 = HMAC mode
mbedtls_md_hmac_starts(&this->ctx_, key, len);
}
void HmacSHA256::add(const uint8_t *data, size_t len) { mbedtls_md_hmac_update(&this->ctx_, data, len); }
void HmacSHA256::calculate() { mbedtls_md_hmac_finish(&this->ctx_, this->digest_); }
void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); }
void HmacSHA256::get_hex(char *output) {
for (size_t i = 0; i < SHA256_DIGEST_SIZE; i++) {
sprintf(output + (i * 2), "%02x", this->digest_[i]);
}
}
bool HmacSHA256::equals_bytes(const uint8_t *expected) {
return memcmp(this->digest_, expected, SHA256_DIGEST_SIZE) == 0;
}
bool HmacSHA256::equals_hex(const char *expected) {
char hex_output[SHA256_DIGEST_SIZE * 2 + 1];
this->get_hex(hex_output);
hex_output[SHA256_DIGEST_SIZE * 2] = '\0';
return strncmp(hex_output, expected, SHA256_DIGEST_SIZE * 2) == 0;
}
#else
HmacSHA256::~HmacSHA256() = default;
// HMAC block size for SHA256 (RFC 2104)
constexpr size_t HMAC_BLOCK_SIZE = 64;
void HmacSHA256::init(const uint8_t *key, size_t len) {
uint8_t ipad[HMAC_BLOCK_SIZE], opad[HMAC_BLOCK_SIZE];
memset(ipad, 0, sizeof(ipad));
if (len > HMAC_BLOCK_SIZE) {
sha256::SHA256 keysha256;
keysha256.init();
keysha256.add(key, len);
keysha256.calculate();
keysha256.get_bytes(ipad);
} else {
memcpy(ipad, key, len);
}
memcpy(opad, ipad, sizeof(opad));
for (size_t i = 0; i < HMAC_BLOCK_SIZE; i++) {
ipad[i] ^= 0x36;
opad[i] ^= 0x5c;
}
this->ihash_.init();
this->ihash_.add(ipad, sizeof(ipad));
this->ohash_.init();
this->ohash_.add(opad, sizeof(opad));
}
void HmacSHA256::add(const uint8_t *data, size_t len) { this->ihash_.add(data, len); }
void HmacSHA256::calculate() {
uint8_t ibytes[32];
this->ihash_.calculate();
this->ihash_.get_bytes(ibytes);
this->ohash_.add(ibytes, sizeof(ibytes));
this->ohash_.calculate();
}
void HmacSHA256::get_bytes(uint8_t *output) { this->ohash_.get_bytes(output); }
void HmacSHA256::get_hex(char *output) { this->ohash_.get_hex(output); }
bool HmacSHA256::equals_bytes(const uint8_t *expected) { return this->ohash_.equals_bytes(expected); }
bool HmacSHA256::equals_hex(const char *expected) { return this->ohash_.equals_hex(expected); }
#endif // USE_ESP32 || USE_LIBRETINY
} // namespace esphome::hmac_sha256
#endif

View File

@@ -0,0 +1,59 @@
#pragma once
#include "esphome/core/defines.h"
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
#include <string>
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#include "mbedtls/md.h"
#else
#include "esphome/components/sha256/sha256.h"
#endif
namespace esphome::hmac_sha256 {
class HmacSHA256 {
public:
HmacSHA256() = default;
~HmacSHA256();
/// Initialize a new HMAC-SHA256 digest computation.
void init(const uint8_t *key, size_t len);
void init(const char *key, size_t len) { this->init((const uint8_t *) key, len); }
void init(const std::string &key) { this->init(key.c_str(), key.length()); }
/// Add bytes of data for the digest.
void add(const uint8_t *data, size_t len);
void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); }
/// Compute the digest, based on the provided data.
void calculate();
/// Retrieve the HMAC-SHA256 digest as bytes.
/// The output must be able to hold 32 bytes or more.
void get_bytes(uint8_t *output);
/// Retrieve the HMAC-SHA256 digest as hex characters.
/// The output must be able to hold 64 bytes or more.
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (32 bytes).
bool equals_bytes(const uint8_t *expected);
/// Compare the digest against a provided hex-encoded digest (64 bytes).
bool equals_hex(const char *expected);
protected:
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
static constexpr size_t SHA256_DIGEST_SIZE = 32;
mbedtls_md_context_t ctx_{};
uint8_t digest_[SHA256_DIGEST_SIZE]{};
#else
sha256::SHA256 ihash_;
sha256::SHA256 ohash_;
#endif
};
} // namespace esphome::hmac_sha256
#endif

View File

@@ -69,9 +69,6 @@ def validate_url(value):
def validate_ssl_verification(config):
error_message = ""
if CORE.is_esp32 and not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
error_message = "ESPHome supports certificate verification only via ESP-IDF"
if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
error_message = "ESPHome does not support certificate verification on RP2040"
@@ -93,9 +90,9 @@ def validate_ssl_verification(config):
def _declare_request_class(value):
if CORE.is_host:
return cv.declare_id(HttpRequestHost)(value)
if CORE.using_esp_idf:
if CORE.is_esp32:
return cv.declare_id(HttpRequestIDF)(value)
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
if CORE.is_esp8266 or CORE.is_rp2040:
return cv.declare_id(HttpRequestArduino)(value)
return NotImplementedError
@@ -121,11 +118,11 @@ CONFIG_SCHEMA = cv.All(
cv.positive_not_null_time_period,
cv.positive_time_period_milliseconds,
),
cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32=512): cv.All(
cv.uint16_t, cv.only_on_esp32
),
cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32=512): cv.All(
cv.uint16_t, cv.only_on_esp32
),
cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.All(
cv.file_,
@@ -158,25 +155,20 @@ async def to_code(config):
cg.add(var.set_watchdog_timeout(timeout_ms))
if CORE.is_esp32:
if CORE.using_esp_idf:
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
esp32.add_idf_sdkconfig_option(
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
config.get(CONF_VERIFY_SSL),
)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_INSECURE",
not config.get(CONF_VERIFY_SSL),
)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
not config.get(CONF_VERIFY_SSL),
)
else:
cg.add_library("NetworkClientSecure", None)
cg.add_library("HTTPClient", None)
if config.get(CONF_VERIFY_SSL):
esp32.add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_INSECURE",
not config.get(CONF_VERIFY_SSL),
)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
not config.get(CONF_VERIFY_SSL),
)
if CORE.is_esp8266:
cg.add_library("ESP8266HTTPClient", None)
if CORE.is_rp2040 and CORE.using_arduino:
@@ -327,13 +319,15 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"http_request_host.cpp": {PlatformFramework.HOST_NATIVE},
"http_request_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"http_request_idf.cpp": {PlatformFramework.ESP32_IDF},
"http_request_idf.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
}
)

View File

@@ -4,8 +4,7 @@
#include <cinttypes>
namespace esphome {
namespace http_request {
namespace esphome::http_request {
static const char *const TAG = "http_request";
@@ -42,5 +41,4 @@ std::string HttpContainer::get_response_header(const std::string &header_name) {
}
}
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request

View File

@@ -15,8 +15,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
struct Header {
std::string name;
@@ -305,5 +304,4 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
size_t max_response_buffer_size_{SIZE_MAX};
};
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request

View File

@@ -1,6 +1,6 @@
#include "http_request_arduino.h"
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
@@ -9,8 +9,7 @@
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
static const char *const TAG = "http_request.arduino";
@@ -75,8 +74,6 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
container->client_.setInsecure();
}
bool status = container->client_.begin(url.c_str());
#elif defined(USE_ESP32)
bool status = container->client_.begin(url.c_str());
#endif
App.feed_wdt();
@@ -90,9 +87,6 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
container->client_.setReuse(true);
container->client_.setTimeout(this->timeout_);
#if defined(USE_ESP32)
container->client_.setConnectTimeout(this->timeout_);
#endif
if (this->useragent_ != nullptr) {
container->client_.setUserAgent(this->useragent_);
@@ -177,7 +171,6 @@ void HttpContainerArduino::end() {
this->client_.end();
}
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_ARDUINO
#endif // USE_ARDUINO && !USE_ESP32

View File

@@ -2,9 +2,9 @@
#include "http_request.h"
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ESP32) || defined(USE_RP2040)
#if defined(USE_RP2040)
#include <HTTPClient.h>
#include <WiFiClient.h>
#endif
@@ -15,8 +15,7 @@
#endif
#endif
namespace esphome {
namespace http_request {
namespace esphome::http_request {
class HttpRequestArduino;
class HttpContainerArduino : public HttpContainer {
@@ -36,7 +35,6 @@ class HttpRequestArduino : public HttpRequestComponent {
const std::set<std::string> &collect_headers) override;
};
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_ARDUINO
#endif // USE_ARDUINO && !USE_ESP32

View File

@@ -12,8 +12,7 @@
#include "esphome/core/application.h"
#include "esphome/core/log.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
static const char *const TAG = "http_request.host";
@@ -139,7 +138,6 @@ void HttpContainerHost::end() {
this->bytes_read_ = 0;
}
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_HOST

View File

@@ -2,8 +2,8 @@
#ifdef USE_HOST
#include "http_request.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
class HttpRequestHost;
class HttpContainerHost : public HttpContainer {
@@ -27,7 +27,6 @@ class HttpRequestHost : public HttpRequestComponent {
const char *ca_path_{};
};
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_HOST

View File

@@ -1,6 +1,6 @@
#include "http_request_idf.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
@@ -14,8 +14,7 @@
#include "esp_task_wdt.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
static const char *const TAG = "http_request.idf";
@@ -245,7 +244,6 @@ void HttpContainerIDF::feed_wdt() {
}
}
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -2,15 +2,14 @@
#include "http_request.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include <esp_event.h>
#include <esp_http_client.h>
#include <esp_netif.h>
#include <esp_tls.h>
namespace esphome {
namespace http_request {
namespace esphome::http_request {
class HttpContainerIDF : public HttpContainer {
public:
@@ -48,7 +47,6 @@ class HttpRequestIDF : public HttpRequestComponent {
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
};
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -1,6 +1,6 @@
from typing import Any
from esphome import pins
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import display
from esphome.components.esp32 import add_idf_component
@@ -17,6 +17,8 @@ from esphome.const import (
CONF_OE_PIN,
CONF_UPDATE_INTERVAL,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -135,6 +137,7 @@ CLOCK_SPEEDS = {
HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display)
Hub75Config = cg.global_ns.struct("Hub75Config")
Hub75Pins = cg.global_ns.struct("Hub75Pins")
SetBrightnessAction = hub75_ns.class_("SetBrightnessAction", automation.Action)
def _merge_board_pins(config: ConfigType) -> ConfigType:
@@ -576,3 +579,27 @@ async def to_code(config: ConfigType) -> None:
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))
@automation.register_action(
"hub75.set_brightness",
SetBrightnessAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(HUB75Display),
cv.Required(CONF_BRIGHTNESS): cv.templatable(cv.int_range(min=0, max=255)),
},
key=CONF_BRIGHTNESS,
),
)
async def hub75_set_brightness_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, cg.uint8)
cg.add(var.set_brightness(template_))
return var

View File

@@ -179,7 +179,7 @@ void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, co
}
}
void HUB75Display::set_brightness(int brightness) {
void HUB75Display::set_brightness(uint8_t brightness) {
this->brightness_ = brightness;
this->enabled_ = (brightness > 0);
if (this->driver_ != nullptr) {

View File

@@ -5,6 +5,7 @@
#include <utility>
#include "esphome/components/display/display_buffer.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@@ -34,7 +35,7 @@ class HUB75Display : public display::Display {
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
// Brightness control (runtime mutable)
void set_brightness(int brightness);
void set_brightness(uint8_t brightness);
protected:
// Display internal methods
@@ -46,10 +47,17 @@ class HUB75Display : public display::Display {
Hub75Config config_; // Immutable configuration
// Runtime state (mutable)
int brightness_{128};
uint8_t brightness_{128};
bool enabled_{false};
};
template<typename... Ts> class SetBrightnessAction : public Action<Ts...>, public Parented<HUB75Display> {
public:
TEMPLATABLE_VALUE(uint8_t, brightness)
void play(const Ts &...x) override { this->parent_->set_brightness(this->brightness_.value(x...)); }
};
} // namespace esphome::hub75
#endif

View File

@@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All(
nrf52="100kHz",
): cv.All(
cv.frequency,
cv.Range(min=0, min_included=False),
cv.float_range(min=0, min_included=False),
),
cv.Optional(CONF_TIMEOUT): cv.All(
cv.only_with_framework(["arduino", "esp-idf"]),

View File

@@ -45,10 +45,12 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(LEDCOutput),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
cv.frequency, cv.float_range(min=0, min_included=False)
),
cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
cv.Optional(CONF_PHASE_ANGLE): cv.All(
cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0)
cv.angle, cv.float_range(min=0.0, max=360.0)
),
}
).extend(cv.COMPONENT_SCHEMA)

View File

@@ -4,24 +4,27 @@
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
#include <flashdb.h>
#include <cinttypes>
#include <cstring>
#include <memory>
#include <string>
namespace esphome {
namespace libretiny {
static const char *const TAG = "lt.preferences";
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
static constexpr size_t KEY_BUFFER_SIZE = 12;
struct NVSData {
std::string key;
uint32_t key;
std::unique_ptr<uint8_t[]> data;
size_t len;
void set_data(const uint8_t *src, size_t size) {
data = std::make_unique<uint8_t[]>(size);
memcpy(data.get(), src, size);
len = size;
this->data = std::make_unique<uint8_t[]>(size);
memcpy(this->data.get(), src, size);
this->len = size;
}
};
@@ -29,30 +32,30 @@ static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-n
class LibreTinyPreferenceBackend : public ESPPreferenceBackend {
public:
std::string key;
uint32_t key;
fdb_kvdb_t db;
fdb_blob_t blob;
bool save(const uint8_t *data, size_t len) override {
// try find in pending saves and update that
for (auto &obj : s_pending_save) {
if (obj.key == key) {
if (obj.key == this->key) {
obj.set_data(data, len);
return true;
}
}
NVSData save{};
save.key = key;
save.key = this->key;
save.set_data(data, len);
s_pending_save.emplace_back(std::move(save));
ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len);
ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
return true;
}
bool load(uint8_t *data, size_t len) override {
// try find in pending saves and load from that
for (auto &obj : s_pending_save) {
if (obj.key == key) {
if (obj.key == this->key) {
if (obj.len != len) {
// size mismatch
return false;
@@ -62,13 +65,15 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend {
}
}
fdb_blob_make(blob, data, len);
size_t actual_len = fdb_kv_get_blob(db, key.c_str(), blob);
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
fdb_blob_make(this->blob, data, len);
size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob);
if (actual_len != len) {
ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
return false;
} else {
ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key.c_str(), len);
ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len);
}
return true;
}
@@ -90,16 +95,14 @@ class LibreTinyPreferences : public ESPPreferences {
}
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
return make_preference(length, type);
return this->make_preference(length, type);
}
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->db = &db;
pref->blob = &blob;
uint32_t keyval = type;
pref->key = str_sprintf("%u", keyval);
pref->db = &this->db;
pref->blob = &this->blob;
pref->key = type;
return ESPPreferenceObject(pref);
}
@@ -112,18 +115,20 @@ class LibreTinyPreferences : public ESPPreferences {
// goal try write all pending saves even if one fails
int cached = 0, written = 0, failed = 0;
fdb_err_t last_err = FDB_NO_ERR;
std::string last_key{};
uint32_t last_key = 0;
// go through vector from back to front (makes erase easier/more efficient)
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
const auto &save = s_pending_save[i];
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", save.key.c_str());
if (is_changed(&db, save)) {
ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len);
fdb_blob_make(&blob, save.data.get(), save.len);
fdb_err_t err = fdb_kv_set_blob(&db, save.key.c_str(), &blob);
ESP_LOGVV(TAG, "Checking if FDB data %" PRIu32 " has changed", save.key);
if (this->is_changed(&this->db, save)) {
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
fdb_blob_make(&this->blob, save.data.get(), save.len);
fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob);
if (err != FDB_NO_ERR) {
ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", save.key.c_str(), save.len, err);
ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.len, err);
failed++;
last_err = err;
last_key = save.key;
@@ -131,7 +136,7 @@ class LibreTinyPreferences : public ESPPreferences {
}
written++;
} else {
ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%zu", save.key.c_str(), save.len);
ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.len);
cached++;
}
s_pending_save.erase(s_pending_save.begin() + i);
@@ -139,17 +144,20 @@ class LibreTinyPreferences : public ESPPreferences {
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
if (failed > 0) {
ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%s", failed, last_err, last_key.c_str());
ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key);
}
return failed == 0;
}
bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) {
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, to_save.key);
struct fdb_kv kv;
fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv);
fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv);
if (kvp == nullptr) {
ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str());
ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str);
return true;
}
@@ -160,10 +168,10 @@ class LibreTinyPreferences : public ESPPreferences {
// Allocate buffer on heap to avoid stack allocation for large data
auto stored_data = std::make_unique<uint8_t[]>(kv.value_len);
fdb_blob_make(&blob, stored_data.get(), kv.value_len);
size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob);
fdb_blob_make(&this->blob, stored_data.get(), kv.value_len);
size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob);
if (actual_len != kv.value_len) {
ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len);
ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", key_str, actual_len, kv.value_len);
return true;
}

View File

@@ -14,7 +14,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
cv.frequency, cv.float_range(min=0, min_included=False)
),
}
).extend(cv.COMPONENT_SCHEMA)

View File

@@ -241,9 +241,12 @@ CONFIG_SCHEMA = cv.All(
CONF_HARDWARE_UART,
esp8266=UART0,
esp32=UART0,
esp32_c2=UART0,
esp32_c3=USB_SERIAL_JTAG,
esp32_c5=USB_SERIAL_JTAG,
esp32_c6=USB_SERIAL_JTAG,
esp32_c61=USB_SERIAL_JTAG,
esp32_h2=USB_SERIAL_JTAG,
esp32_p4=USB_SERIAL_JTAG,
esp32_s2=USB_CDC,
esp32_s3=USB_SERIAL_JTAG,

View File

@@ -1,7 +1,17 @@
import esphome.codegen as cg
from esphome.core import CORE
from esphome.helpers import IS_MACOS
CODEOWNERS = ["@esphome/core"]
async def to_code(config):
cg.add_define("USE_MD5")
# Add OpenSSL library for host platform
if CORE.is_host:
if IS_MACOS:
# macOS needs special handling for Homebrew OpenSSL
cg.add_build_flag("-I/opt/homebrew/opt/openssl/include")
cg.add_build_flag("-L/opt/homebrew/opt/openssl/lib")
cg.add_build_flag("-lcrypto")

View File

@@ -39,6 +39,44 @@ void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
#endif // USE_RP2040
#ifdef USE_HOST
MD5Digest::~MD5Digest() {
if (this->ctx_) {
EVP_MD_CTX_free(this->ctx_);
}
}
void MD5Digest::init() {
if (this->ctx_) {
EVP_MD_CTX_free(this->ctx_);
}
this->ctx_ = EVP_MD_CTX_new();
EVP_DigestInit_ex(this->ctx_, EVP_md5(), nullptr);
this->calculated_ = false;
memset(this->digest_, 0, 16);
}
void MD5Digest::add(const uint8_t *data, size_t len) {
if (!this->ctx_) {
this->init();
}
EVP_DigestUpdate(this->ctx_, data, len);
}
void MD5Digest::calculate() {
if (!this->ctx_) {
this->init();
}
if (!this->calculated_) {
unsigned int len = 16;
EVP_DigestFinal_ex(this->ctx_, this->digest_, &len);
this->calculated_ = true;
}
}
#else
MD5Digest::~MD5Digest() = default;
#endif // USE_HOST
} // namespace md5
} // namespace esphome
#endif

View File

@@ -5,6 +5,10 @@
#include "esphome/core/hash_base.h"
#ifdef USE_HOST
#include <openssl/evp.h>
#endif
#ifdef USE_ESP32
#include "esp_rom_md5.h"
#define MD5_CTX_TYPE md5_context_t
@@ -31,7 +35,7 @@ namespace md5 {
class MD5Digest : public HashBase {
public:
MD5Digest() = default;
~MD5Digest() override = default;
~MD5Digest() override;
/// Initialize a new MD5 digest computation.
void init() override;
@@ -47,7 +51,12 @@ class MD5Digest : public HashBase {
size_t get_size() const override { return 16; }
protected:
#ifdef USE_HOST
EVP_MD_CTX *ctx_{nullptr};
bool calculated_{false};
#else
MD5_CTX_TYPE ctx_{};
#endif
};
} // namespace md5

View File

@@ -233,11 +233,11 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_PASSWORD, default=""): cv.string,
cv.Optional(CONF_CLEAN_SESSION, default=False): cv.boolean,
cv.Optional(CONF_CLIENT_ID): cv.string,
cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32=False): cv.All(
cv.boolean, cv.only_on_esp32
),
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
cv.string, cv.only_with_esp_idf
cv.string, cv.only_on_esp32
),
cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All(
cv.string, cv.only_on_esp32
@@ -245,8 +245,8 @@ CONFIG_SCHEMA = cv.All(
cv.Inclusive(CONF_CLIENT_CERTIFICATE_KEY, "cert-key-pair"): cv.All(
cv.string, cv.only_on_esp32
),
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32=False): cv.All(
cv.boolean, cv.only_on_esp32
),
cv.Optional(CONF_DISCOVERY, default=True): cv.Any(
cv.boolean, cv.one_of("CLEAN", upper=True)

View File

@@ -21,8 +21,7 @@ void MQTTSelectComponent::setup() {
call.set_option(state);
call.perform();
});
this->select_->add_on_state_callback(
[this](const std::string &state, size_t index) { this->publish_state(this->select_->option_at(index)); });
this->select_->add_on_state_callback([this](size_t index) { this->publish_state(this->select_->option_at(index)); });
}
void MQTTSelectComponent::dump_config() {

View File

@@ -13,14 +13,16 @@ CONF_SEND_TO_NEXTION = "send_to_nextion"
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"nextion_upload_arduino.cpp": {
"nextion_upload_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"nextion_upload_arduino.cpp": {
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"nextion_upload_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -154,14 +154,11 @@ async def to_code(config):
cg.add_define("USE_NEXTION_TFT_UPLOAD")
cg.add(var.set_tft_url(config[CONF_TFT_URL]))
if CORE.is_esp32:
if CORE.using_arduino:
cg.add_library("NetworkClientSecure", None)
cg.add_library("HTTPClient", None)
esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True
)
elif CORE.is_esp8266 and CORE.using_arduino:
elif CORE.is_esp8266:
cg.add_library("ESP8266HTTPClient", None)
if CONF_TOUCH_SLEEP_TIMEOUT in config:

View File

@@ -13,17 +13,12 @@
#include "esphome/components/display/display_color_utils.h"
#ifdef USE_NEXTION_TFT_UPLOAD
#ifdef USE_ARDUINO
#ifdef USE_ESP32
#include <HTTPClient.h>
#endif // USE_ESP32
#ifdef USE_ESP8266
#include <esp_http_client.h>
#elif defined(USE_ESP8266)
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#endif // USE_ESP8266
#elif defined(USE_ESP_IDF)
#include <esp_http_client.h>
#endif // ARDUINO vs USE_ESP_IDF
#endif // USE_ESP32 vs USE_ESP8266
#endif // USE_NEXTION_TFT_UPLOAD
namespace esphome {
@@ -1078,7 +1073,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
#ifdef USE_NEXTION_TFT_UPLOAD
/**
* Set the tft file URL. https seems problematic with Arduino..
* Set the tft file URL.
*/
void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; }
@@ -1422,16 +1417,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
uint32_t original_baud_rate_ = 0;
bool upload_first_chunk_sent_ = false;
#ifdef USE_ARDUINO
/**
* will request chunk_size chunks from the web server
* and send each to the nextion
* @param HTTPClient http_client HTTP client handler.
* @param int range_start Position of next byte to transfer.
* @return position of last byte transferred, -1 for failure.
*/
int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start);
#elif defined(USE_ESP_IDF)
#ifdef USE_ESP32
/**
* will request 4096 bytes chunks from the web server
* and send each to Nextion
@@ -1440,7 +1426,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
* @return position of last byte transferred, -1 for failure.
*/
int upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start);
#endif // USE_ARDUINO vs USE_ESP_IDF
#elif defined(USE_ARDUINO)
/**
* will request chunk_size chunks from the web server
* and send each to the nextion
* @param HTTPClient http_client HTTP client handler.
* @param int range_start Position of next byte to transfer.
* @return position of last byte transferred, -1 for failure.
*/
int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start);
#endif // USE_ESP32 vs USE_ARDUINO
/**
* Ends the upload process, restart Nextion and, if successful,
@@ -1450,12 +1445,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
*/
bool upload_end_(bool successful);
/**
* Returns the ESP Free Heap memory. This is framework independent.
* @return Free Heap in bytes.
*/
uint32_t get_free_heap_();
#endif // USE_NEXTION_TFT_UPLOAD
bool check_connect_();

View File

@@ -1,7 +1,7 @@
#include "nextion.h"
#ifdef USE_NEXTION_TFT_UPLOAD
#ifdef USE_ARDUINO
#ifndef USE_ESP32
#include <cinttypes>
#include "esphome/components/network/util.h"
@@ -10,10 +10,6 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion.upload.arduino";
@@ -21,23 +17,17 @@ static const char *const TAG = "nextion.upload.arduino";
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
inline uint32_t Nextion::get_free_heap_() {
#if defined(USE_ESP32)
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#elif defined(USE_ESP8266)
return EspClass::getFreeHeap();
#endif // USE_ESP32 vs USE_ESP8266
}
int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
uint32_t range_size = this->tft_size_ - range_start;
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());
uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1;
ESP_LOGD(TAG, "Range start: %" PRIu32, range_start);
if (range_size <= 0 or range_end <= range_start) {
ESP_LOGD(TAG, "Range end: %" PRIu32, range_end);
ESP_LOGD(TAG, "Range size: %" PRIu32, range_size);
ESP_LOGE(TAG, "Invalid range");
ESP_LOGD(TAG,
"Range end: %" PRIu32 "\n"
"Range size: %" PRIu32,
range_end, range_size);
return -1;
}
@@ -95,14 +85,8 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
this->recv_ret_string_(recv_string, upload_first_chunk_sent_ ? 500 : 5000, true);
this->content_length_ -= read_len;
const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_;
#if defined(USE_ESP32) && defined(USE_PSRAM)
ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 "+%" PRIu32 ")", upload_percentage,
this->content_length_, static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)),
static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)));
#else
ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_,
this->get_free_heap_());
#endif
EspClass::getFreeHeap());
upload_first_chunk_sent_ = true;
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
ESP_LOGD(TAG, "Recv: [%s]",
@@ -148,9 +132,11 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
}
bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "TFT upload requested");
ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse));
ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str());
ESP_LOGD(TAG,
"TFT upload requested\n"
"Exit reparse: %s\n"
"URL: %s",
YESNO(exit_reparse), this->tft_url_.c_str());
if (this->connection_state_.is_updating_) {
ESP_LOGW(TAG, "Upload in progress");
@@ -180,15 +166,14 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client
ESP_LOGV(TAG, "Init HTTP client");
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG,
"Init HTTP client\n"
"Heap: %" PRIu32,
EspClass::getFreeHeap());
HTTPClient http_client;
http_client.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along
bool begin_status = false;
#ifdef USE_ESP32
begin_status = http_client.begin(this->tft_url_.c_str());
#endif
#ifdef USE_ESP8266
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
http_client.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
@@ -256,22 +241,24 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
this->send_command_("sleep=0");
this->send_command_("dim=100");
delay(250); // NOLINT
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());
App.feed_wdt();
char command[128];
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
// If it fails for any reason a power cycle of the display will be needed
sprintf(command, "whmi-wris %d,%d,1", this->content_length_, baud_rate);
snprintf(command, sizeof(command), "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate);
// Clear serial receive buffer
ESP_LOGV(TAG, "Clear RX buffer");
this->reset_(false);
delay(250); // NOLINT
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG, "Upload cmd: %s", command);
ESP_LOGV(TAG,
"Heap: %" PRIu32 "\n"
"Upload cmd: %s",
EspClass::getFreeHeap(), command);
this->send_command_(command);
if (baud_rate != this->original_baud_rate_) {
@@ -290,7 +277,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "Upload resp: [%s] %zu B",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
response.length());
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());
if (response.find(0x05) != std::string::npos) {
ESP_LOGV(TAG, "Upload prep done");
@@ -302,10 +289,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
return this->upload_end_(false);
}
ESP_LOGD(TAG, "Upload TFT:");
ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str());
ESP_LOGD(TAG, " Size: %d bytes", this->content_length_);
ESP_LOGD(TAG, " Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGD(TAG,
"Upload TFT:\n"
" URL: %s\n"
" Size: %d bytes\n"
" Heap: %" PRIu32,
this->tft_url_.c_str(), this->content_length_, EspClass::getFreeHeap());
// Proceed with the content download as before
@@ -322,7 +311,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
return this->upload_end_(false);
}
App.feed_wdt();
ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, this->get_free_heap_(), this->content_length_);
ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, EspClass::getFreeHeap(), this->content_length_);
}
ESP_LOGD(TAG, "Upload complete");
@@ -356,5 +345,5 @@ WiFiClient *Nextion::get_wifi_client_() {
} // namespace nextion
} // namespace esphome
#endif // USE_ARDUINO
#endif // NOT USE_ESP32
#endif // USE_NEXTION_TFT_UPLOAD

View File

@@ -1,7 +1,7 @@
#include "nextion.h"
#ifdef USE_NEXTION_TFT_UPLOAD
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#include <esp_http_client.h>
@@ -14,7 +14,7 @@
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion.upload.idf";
static const char *const TAG = "nextion.upload.esp32";
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
@@ -25,8 +25,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1;
ESP_LOGD(TAG, "Range start: %" PRIu32, range_start);
if (range_size <= 0 or range_end <= range_start) {
ESP_LOGD(TAG, "Range end: %" PRIu32, range_end);
ESP_LOGD(TAG, "Range size: %" PRIu32, range_size);
ESP_LOGD(TAG,
"Range end: %" PRIu32 "\n"
"Range size: %" PRIu32,
range_end, range_size);
ESP_LOGE(TAG, "Invalid range");
return -1;
}
@@ -151,9 +153,11 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
}
bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "TFT upload requested");
ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse));
ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str());
ESP_LOGD(TAG,
"TFT upload requested\n"
"Exit reparse: %s\n"
"URL: %s",
YESNO(exit_reparse), this->tft_url_.c_str());
if (this->connection_state_.is_updating_) {
ESP_LOGW(TAG, "Upload in progress");
@@ -183,8 +187,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client
ESP_LOGV(TAG, "Init HTTP client");
ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG,
"Init HTTP client\n"
"Heap: %" PRIu32,
esp_get_free_heap_size());
esp_http_client_config_t config = {
.url = this->tft_url_.c_str(),
.cert_pem = nullptr,
@@ -208,8 +214,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
}
// Perform the HTTP request
ESP_LOGV(TAG, "Check connection");
ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG,
"Check connection\n"
"Heap: %" PRIu32,
esp_get_free_heap_size());
err = esp_http_client_perform(http_client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "HTTP failed: %s", esp_err_to_name(err));
@@ -218,8 +226,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
}
// Check the HTTP Status Code
ESP_LOGV(TAG, "Check status");
ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG,
"Check status\n"
"Heap: %" PRIu32,
esp_get_free_heap_size());
int status_code = esp_http_client_get_status_code(http_client);
if (status_code != 200 && status_code != 206) {
return this->upload_end_(false);
@@ -255,7 +265,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
// If it fails for any reason a power cycle of the display will be needed
sprintf(command, "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate);
snprintf(command, sizeof(command), "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate);
// Clear serial receive buffer
ESP_LOGV(TAG, "Clear RX buffer");
@@ -300,10 +310,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
return this->upload_end_(false);
}
ESP_LOGD(TAG, "Uploading TFT:");
ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str());
ESP_LOGD(TAG, " Size: %" PRIu32 " bytes", this->content_length_);
ESP_LOGD(TAG, " Heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGD(TAG,
"Uploading TFT:\n"
" URL: %s\n"
" Size: %" PRIu32 " bytes\n"
" Heap: %" PRIu32,
this->tft_url_.c_str(), this->content_length_, esp_get_free_heap_size());
// Proceed with the content download as before
@@ -324,9 +336,8 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, esp_get_free_heap_size(), this->content_length_);
}
ESP_LOGD(TAG, "TFT upload complete");
ESP_LOGD(TAG, "Close HTTP");
ESP_LOGD(TAG, "TFT upload complete\n"
"Close HTTP");
esp_http_client_close(http_client);
esp_http_client_cleanup(http_client);
ESP_LOGV(TAG, "Connection closed");
@@ -336,5 +347,5 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
} // namespace nextion
} // namespace esphome
#endif // USE_ESP_IDF
#endif // USE_ESP32
#endif // USE_NEXTION_TFT_UPLOAD

View File

@@ -91,7 +91,7 @@ def set_sdkconfig_options(config):
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True)
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
# TODO: Add suport for synchronized sleepy end devices (SSED)
# TODO: Add support for synchronized sleepy end devices (SSED)
add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
@@ -102,7 +102,7 @@ OpenThreadSrpComponent = openthread_ns.class_("OpenThreadSrpComponent", cg.Compo
_CONNECTION_SCHEMA = cv.Schema(
{
cv.Optional(CONF_PAN_ID): cv.hex_int,
cv.Optional(CONF_CHANNEL): cv.int_,
cv.Optional(CONF_CHANNEL): cv.int_range(min=11, max=26),
cv.Optional(CONF_NETWORK_KEY): cv.hex_int,
cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,

View File

@@ -21,8 +21,7 @@
static const char *const TAG = "openthread";
namespace esphome {
namespace openthread {
namespace esphome::openthread {
OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -275,7 +274,5 @@ const char *OpenThreadComponent::get_use_address() const { return this->use_addr
void OpenThreadComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
} // namespace openthread
} // namespace esphome
} // namespace esphome::openthread
#endif

View File

@@ -13,8 +13,7 @@
#include <optional>
#include <vector>
namespace esphome {
namespace openthread {
namespace esphome::openthread {
class InstanceLock;
@@ -91,6 +90,5 @@ class InstanceLock {
InstanceLock() {}
};
} // namespace openthread
} // namespace esphome
} // namespace esphome::openthread
#endif

View File

@@ -24,8 +24,7 @@
static const char *const TAG = "openthread";
namespace esphome {
namespace openthread {
namespace esphome::openthread {
void OpenThreadComponent::setup() {
// Used eventfds:
@@ -209,6 +208,5 @@ otInstance *InstanceLock::get_instance() { return esp_openthread_get_instance();
InstanceLock::~InstanceLock() { esp_openthread_lock_release(); }
} // namespace openthread
} // namespace esphome
} // namespace esphome::openthread
#endif

View File

@@ -3,8 +3,7 @@
#ifdef USE_OPENTHREAD
#include "esphome/core/log.h"
namespace esphome {
namespace openthread_info {
namespace esphome::openthread_info {
static const char *const TAG = "openthread_info";
@@ -19,6 +18,5 @@ void NetworkKeyOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Network Key"
void PanIdOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "PAN ID", this); }
void ExtPanIdOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Extended PAN ID", this); }
} // namespace openthread_info
} // namespace esphome
} // namespace esphome::openthread_info
#endif

View File

@@ -5,8 +5,7 @@
#include "esphome/core/component.h"
#ifdef USE_OPENTHREAD
namespace esphome {
namespace openthread_info {
namespace esphome::openthread_info {
using esphome::openthread::InstanceLock;
@@ -213,6 +212,5 @@ class ExtPanIdOpenThreadInfo : public DatasetOpenThreadInfo, public text_sensor:
std::array<uint8_t, 8> last_extpanid_{};
};
} // namespace openthread_info
} // namespace esphome
} // namespace esphome::openthread_info
#endif

View File

@@ -38,7 +38,7 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(PCA9685Output),
cv.Optional(CONF_FREQUENCY): cv.All(
cv.frequency, cv.Range(min=23.84, max=1525.88)
cv.frequency, cv.float_range(min=23.84, max=1525.88)
),
cv.Optional(CONF_EXTERNAL_CLOCK_INPUT, default=False): cv.boolean,
cv.Optional(CONF_PHASE_BALANCER, default="linear"): cv.enum(

View File

@@ -7,10 +7,10 @@ from esphome.const import (
CONF_UPDATE_INTERVAL,
DEVICE_CLASS_PM25,
ICON_BLUR,
SCHEDULER_DONT_RUN,
STATE_CLASS_MEASUREMENT,
UNIT_MICROGRAMS_PER_CUBIC_METER,
)
from esphome.core import TimePeriodMilliseconds
CODEOWNERS = ["@habbie"]
DEPENDENCIES = ["uart"]
@@ -41,16 +41,12 @@ CONFIG_SCHEMA = cv.All(
def validate_interval_uart(config):
require_tx = False
interval = config.get(CONF_UPDATE_INTERVAL)
if isinstance(interval, TimePeriodMilliseconds):
# 'never' is encoded as a very large int, not as a TimePeriodMilliseconds objects
require_tx = True
uart.final_validate_device_schema(
"pm1006", baud_rate=9600, require_rx=True, require_tx=require_tx
"pm1006",
baud_rate=9600,
require_rx=True,
require_tx=interval.total_milliseconds != SCHEDULER_DONT_RUN,
)(config)

View File

@@ -63,9 +63,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(
CONF_BUFFER_DURATION, default="100ms"
): cv.positive_time_period_milliseconds,
cv.SplitDefault(CONF_TASK_STACK_IN_PSRAM, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
),
cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean,
cv.Optional(CONF_FILTERS, default=16): cv.int_range(min=2, max=1024),
cv.Optional(CONF_TAPS, default=16): _validate_taps,
}

View File

@@ -11,7 +11,7 @@ DEPENDENCIES = ["rp2040"]
rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm")
RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component)
SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action)
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6))
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{

View File

@@ -9,6 +9,10 @@
#include <cinttypes>
#include <cstdio>
#ifdef USE_OTA_ROLLBACK
#include <esp_ota_ops.h>
#endif
namespace esphome {
namespace safe_mode {
@@ -32,6 +36,14 @@ void SafeModeComponent::dump_config() {
ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
}
}
#ifdef USE_OTA_ROLLBACK
const esp_partition_t *last_invalid = esp_ota_get_last_invalid_partition();
if (last_invalid != nullptr) {
ESP_LOGW(TAG, "OTA rollback detected! Rolled back from partition '%s'", last_invalid->label);
ESP_LOGW(TAG, "The device reset before the boot was marked successful");
}
#endif
}
float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
@@ -42,6 +54,10 @@ void SafeModeComponent::loop() {
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
this->clean_rtc();
this->boot_successful_ = true;
#ifdef USE_OTA_ROLLBACK
// Mark OTA partition as valid to prevent rollback
esp_ota_mark_app_valid_cancel_rollback();
#endif
// Disable loop since we no longer need to check
this->disable_loop();
}

View File

@@ -8,9 +8,13 @@ namespace esphome::select {
class SelectStateTrigger : public Trigger<std::string, size_t> {
public:
explicit SelectStateTrigger(Select *parent) {
parent->add_on_state_callback([this](const std::string &value, size_t index) { this->trigger(value, index); });
explicit SelectStateTrigger(Select *parent) : parent_(parent) {
parent->add_on_state_callback(
[this](size_t index) { this->trigger(std::string(this->parent_->option_at(index)), index); });
}
protected:
Select *parent_;
};
template<typename... Ts> class SelectSetAction : public Action<Ts...> {

View File

@@ -32,8 +32,7 @@ void Select::publish_state(size_t index) {
this->state = option; // Update deprecated member for backward compatibility
#pragma GCC diagnostic pop
ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", this->get_name().c_str(), option, index);
// Callback signature requires std::string, create temporary for compatibility
this->state_callback_.call(std::string(option), index);
this->state_callback_.call(index);
#if defined(USE_SELECT) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_select_update(this);
#endif
@@ -41,7 +40,7 @@ void Select::publish_state(size_t index) {
const char *Select::current_option() const { return this->has_state() ? this->option_at(this->active_index_) : ""; }
void Select::add_on_state_callback(std::function<void(std::string, size_t)> &&callback) {
void Select::add_on_state_callback(std::function<void(size_t)> &&callback) {
this->state_callback_.add(std::move(callback));
}

View File

@@ -75,7 +75,7 @@ class Select : public EntityBase {
/// Return the option value at the provided index offset (as const char* from flash).
const char *option_at(size_t index) const;
void add_on_state_callback(std::function<void(std::string, size_t)> &&callback);
void add_on_state_callback(std::function<void(size_t)> &&callback);
protected:
friend class SelectCall;
@@ -111,7 +111,7 @@ class Select : public EntityBase {
}
}
CallbackManager<void(std::string, size_t)> state_callback_;
CallbackManager<void(size_t)> state_callback_;
};
} // namespace esphome::select

View File

@@ -76,9 +76,9 @@ StateClass Sensor::get_state_class() {
void Sensor::publish_state(float state) {
this->raw_state = state;
if (this->raw_callback_) {
this->raw_callback_->call(state);
}
// Call raw callbacks (before filters)
this->callbacks_.call_first(this->raw_count_, state);
ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state);
@@ -89,12 +89,12 @@ void Sensor::publish_state(float state) {
}
}
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) { this->callback_.add(std::move(callback)); }
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) {
this->callbacks_.add_second(std::move(callback));
}
void Sensor::add_on_raw_state_callback(std::function<void(float)> &&callback) {
if (!this->raw_callback_) {
this->raw_callback_ = make_unique<CallbackManager<void(float)>>();
}
this->raw_callback_->add(std::move(callback));
this->callbacks_.add_first(std::move(callback), &this->raw_count_);
}
void Sensor::add_filter(Filter *filter) {
@@ -134,7 +134,10 @@ void Sensor::internal_send_state_to_frontend(float state) {
this->state = state;
ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state,
this->get_unit_of_measurement_ref().c_str(), this->get_accuracy_decimals());
this->callback_.call(state);
// Call filtered callbacks (after filters)
this->callbacks_.call_second(this->raw_count_, state);
#if defined(USE_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_sensor_update(this);
#endif

View File

@@ -125,8 +125,7 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
void internal_send_state_to_frontend(float state);
protected:
std::unique_ptr<CallbackManager<void(float)>> raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
CallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
PartitionedCallbackManager<void(float)> callbacks_;
Filter *filter_list_{nullptr}; ///< Store all active filters.
@@ -141,6 +140,8 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
uint8_t force_update : 1;
uint8_t reserved : 5; // Reserved for future use
} sensor_flags_{};
uint8_t raw_count_{0}; ///< Number of raw callbacks (partition point in callbacks_ vector)
};
} // namespace sensor

View File

@@ -12,6 +12,8 @@ CONFIG_SCHEMA = cv.Schema({})
async def to_code(config: ConfigType) -> None:
cg.add_define("USE_SHA256")
# Add OpenSSL library for host platform
if not CORE.is_host:
return

View File

@@ -272,10 +272,11 @@ def validate_spi_config(config):
# Given an SPI index, convert to a string that represents the C++ object for it.
def get_spi_interface(index):
if CORE.using_esp_idf:
platform = get_target_platform()
if platform == PLATFORM_ESP32:
# ESP32 uses ESP-IDF SPI driver for both Arduino and IDF frameworks
return ["SPI2_HOST", "SPI3_HOST"][index]
# Arduino code follows
platform = get_target_platform()
if platform == PLATFORM_RP2040:
return ["&SPI", "&SPI1"][index]
if index == 0:
@@ -356,7 +357,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(configs):
cg.add_define("USE_SPI")
cg.add_global(spi_ns.using)
if CORE.using_arduino:
if CORE.using_arduino and not CORE.is_esp32:
cg.add_library("SPI", None)
for spi in configs:
var = cg.new_Pvariable(spi[CONF_ID])
@@ -447,13 +448,15 @@ def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso:
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"spi_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"spi_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
"spi_esp_idf.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
}
)

View File

@@ -2,8 +2,7 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace spi {
namespace esphome::spi {
const char *const TAG = "spi";
@@ -119,5 +118,4 @@ uint16_t SPIDelegateBitBash::transfer_(uint16_t data, size_t num_bits) {
return out_data;
}
} // namespace spi
} // namespace esphome
} // namespace esphome::spi

View File

@@ -7,7 +7,13 @@
#include <utility>
#include <vector>
#ifdef USE_ARDUINO
#ifdef USE_ESP32
#include "driver/spi_master.h"
using SPIInterface = spi_host_device_t;
#elif defined(USE_ARDUINO)
#include <SPI.h>
@@ -17,26 +23,16 @@ using SPIInterface = SPIClassRP2040 *;
using SPIInterface = SPIClass *;
#endif
#endif
#elif defined(CLANG_TIDY)
#ifdef USE_ESP_IDF
using SPIInterface = void *; // Stub for platforms without SPI (e.g., Zephyr)
#include "driver/spi_master.h"
using SPIInterface = spi_host_device_t;
#endif // USE_ESP_IDF
#ifdef USE_ZEPHYR
// TODO supprse clang-tidy. Remove after SPI driver for nrf52 is added.
using SPIInterface = void *;
#endif
#endif // USE_ESP32 / USE_ARDUINO
/**
* Implementation of SPI Controller mode.
*/
namespace esphome {
namespace spi {
namespace esphome::spi {
/// The bit-order for SPI devices. This defines how the data read from and written to the device is interpreted.
enum SPIBitOrder {
@@ -509,5 +505,4 @@ class SPIDevice : public SPIClient {
template<size_t N> void transfer_array(std::array<uint8_t, N> &data) { this->transfer_array(data.data(), N); }
};
} // namespace spi
} // namespace esphome
} // namespace esphome::spi

View File

@@ -1,9 +1,8 @@
#include "spi.h"
#include <vector>
namespace esphome {
namespace spi {
#ifdef USE_ARDUINO
namespace esphome::spi {
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
static const char *const TAG = "spi-esp-arduino";
class SPIDelegateHw : public SPIDelegate {
@@ -101,6 +100,5 @@ SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo
return new SPIBusHw(clk, sdo, sdi, interface);
}
#endif // USE_ARDUINO
} // namespace spi
} // namespace esphome
#endif // USE_ARDUINO && !USE_ESP32
} // namespace esphome::spi

View File

@@ -1,10 +1,9 @@
#include "spi.h"
#include <vector>
namespace esphome {
namespace spi {
namespace esphome::spi {
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
static const char *const TAG = "spi-esp-idf";
static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API.
@@ -266,6 +265,5 @@ SPIBus *SPIComponent::get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo
return new SPIBusHw(clk, sdo, sdi, interface, data_pins);
}
#endif
} // namespace spi
} // namespace esphome
#endif // USE_ESP32
} // namespace esphome::spi

View File

@@ -199,9 +199,13 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_CRC_INITIAL, default=0x1D0F): cv.All(
cv.hex_int, cv.Range(min=0, max=0xFFFF)
),
cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000),
cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All(
cv.frequency, cv.float_range(min=0, max=100000)
),
cv.Required(CONF_DIO1_PIN): pins.gpio_input_pin_schema,
cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000),
cv.Required(CONF_FREQUENCY): cv.All(
cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6)
),
cv.Required(CONF_HW_VERSION): cv.one_of(
"sx1261", "sx1262", "sx1268", "llcc68", lower=True
),

View File

@@ -196,9 +196,13 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_BITSYNC): cv.boolean,
cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE),
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000),
cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All(
cv.frequency, cv.float_range(min=0, max=100000)
),
cv.Optional(CONF_DIO0_PIN): pins.internal_gpio_input_pin_schema,
cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000),
cv.Required(CONF_FREQUENCY): cv.All(
cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6)
),
cv.Required(CONF_MODULATION): cv.enum(MOD),
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
cv.Optional(CONF_PA_PIN, default="BOOST"): cv.enum(PA_PIN),

View File

@@ -23,7 +23,7 @@ void Text::publish_state(const std::string &state) {
#endif
}
void Text::add_on_state_callback(std::function<void(std::string)> &&callback) {
void Text::add_on_state_callback(std::function<void(const std::string &)> &&callback) {
this->state_callback_.add(std::move(callback));
}

View File

@@ -31,7 +31,7 @@ class Text : public EntityBase {
/// Instantiate a TextCall object to modify this text component's state.
TextCall make_call() { return TextCall(this); }
void add_on_state_callback(std::function<void(std::string)> &&callback);
void add_on_state_callback(std::function<void(const std::string &)> &&callback);
protected:
friend class TextCall;
@@ -44,7 +44,7 @@ class Text : public EntityBase {
*/
virtual void control(const std::string &value) = 0;
CallbackManager<void(std::string)> state_callback_;
CallbackManager<void(const std::string &)> state_callback_;
};
} // namespace text

View File

@@ -30,9 +30,9 @@ void TextSensor::publish_state(const std::string &state) {
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
this->raw_state = state;
#pragma GCC diagnostic pop
if (this->raw_callback_) {
this->raw_callback_->call(state);
}
// Call raw callbacks (before filters)
this->callbacks_.call_first(this->raw_count_, state);
ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str());
@@ -74,13 +74,11 @@ void TextSensor::clear_filters() {
}
void TextSensor::add_on_state_callback(std::function<void(std::string)> callback) {
this->callback_.add(std::move(callback));
this->callbacks_.add_second(std::move(callback));
}
void TextSensor::add_on_raw_state_callback(std::function<void(std::string)> callback) {
if (!this->raw_callback_) {
this->raw_callback_ = make_unique<CallbackManager<void(std::string)>>();
}
this->raw_callback_->add(std::move(callback));
this->callbacks_.add_first(std::move(callback), &this->raw_count_);
}
std::string TextSensor::get_state() const { return this->state; }
@@ -95,7 +93,10 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) {
this->state = state;
this->set_has_state(true);
ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str());
this->callback_.call(state);
// Call filtered callbacks (after filters)
this->callbacks_.call_second(this->raw_count_, state);
#if defined(USE_TEXT_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_text_sensor_update(this);
#endif

View File

@@ -65,11 +65,11 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
void internal_send_state_to_frontend(const std::string &state);
protected:
std::unique_ptr<CallbackManager<void(std::string)>>
raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
CallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
PartitionedCallbackManager<void(std::string)> callbacks_;
Filter *filter_list_{nullptr}; ///< Store all active filters.
uint8_t raw_count_{0}; ///< Number of raw callbacks (partition point in callbacks_ vector)
};
} // namespace text_sensor

View File

@@ -4,8 +4,7 @@
#include <cinttypes>
namespace esphome {
namespace time {
namespace esphome::time {
static const char *const TAG = "automation";
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
@@ -92,5 +91,4 @@ SyncTrigger::SyncTrigger(RealTimeClock *rtc) : rtc_(rtc) {
rtc->add_on_time_sync_callback([this]() { this->trigger(); });
}
} // namespace time
} // namespace esphome
} // namespace esphome::time

View File

@@ -8,8 +8,7 @@
#include <vector>
namespace esphome {
namespace time {
namespace esphome::time {
class CronTrigger : public Trigger<>, public Component {
public:
@@ -48,5 +47,4 @@ class SyncTrigger : public Trigger<>, public Component {
protected:
RealTimeClock *rtc_;
};
} // namespace time
} // namespace esphome
} // namespace esphome::time

View File

@@ -17,8 +17,7 @@
#include <cinttypes>
namespace esphome {
namespace time {
namespace esphome::time {
static const char *const TAG = "time";
@@ -78,5 +77,4 @@ void RealTimeClock::apply_timezone_() {
}
#endif
} // namespace time
} // namespace esphome
} // namespace esphome::time

View File

@@ -7,8 +7,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/time.h"
namespace esphome {
namespace time {
namespace esphome::time {
/// The RealTimeClock class exposes common timekeeping functions via the device's local real-time clock.
///
@@ -75,5 +74,4 @@ template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> {
RealTimeClock *parent_;
};
} // namespace time
} // namespace esphome
} // namespace esphome::time

View File

@@ -29,6 +29,9 @@ UpdateInfo = update_ns.struct("UpdateInfo")
PerformAction = update_ns.class_(
"PerformAction", automation.Action, cg.Parented.template(UpdateEntity)
)
CheckAction = update_ns.class_(
"CheckAction", automation.Action, cg.Parented.template(UpdateEntity)
)
IsAvailableCondition = update_ns.class_(
"IsAvailableCondition", automation.Condition, cg.Parented.template(UpdateEntity)
)
@@ -143,6 +146,21 @@ async def update_perform_action_to_code(config, action_id, template_arg, args):
return var
@automation.register_action(
"update.check",
CheckAction,
automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(UpdateEntity),
}
),
)
async def update_check_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_condition(
"update.is_available",
IsAvailableCondition,

View File

@@ -14,6 +14,11 @@ template<typename... Ts> class PerformAction : public Action<Ts...>, public Pare
void play(const Ts &...x) override { this->parent_->perform(this->force_.value(x...)); }
};
template<typename... Ts> class CheckAction : public Action<Ts...>, public Parented<UpdateEntity> {
public:
void play(const Ts &...x) override { this->parent_->check(); }
};
template<typename... Ts> class IsAvailableCondition : public Condition<Ts...>, public Parented<UpdateEntity> {
public:
bool check(const Ts &...x) override { return this->parent_->state == UPDATE_STATE_AVAILABLE; }

View File

@@ -4,7 +4,7 @@ from enum import Enum
from esphome.enum import StrEnum
__version__ = "2025.12.0"
__version__ = "2026.1.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (

Some files were not shown because too many files have changed in this diff Show More