Compare commits

..
Author SHA1 Message Date
J. Nick Koston 38ab0805a0 [logger] Add optional boot log buffer replayed over the API 2026-07-17 23:02:43 -10:00
tomaszduda23andGitHub 017040ec8e [zigbee] add radio power off stats (#17521) 2026-07-18 09:09:52 +02:00
TomandGitHub 52fe461e9c [ds248x] Add OneWireBus platform for DS248x I2C-to-1Wire bridges (#12717) 2026-07-17 22:01:15 -07:00
esphome[bot]andGitHub a7dad14449 Bump bundled esphome-device-builder to 1.6.4 (#17662) 2026-07-17 17:44:52 -10:00
Jesse HillsandGitHub 1493a09521 [nrf52] Install PlatformIO toolchain Python packages into a dedicated venv (#17635) 2026-07-17 23:22:23 -04:00
J. Nick KostonandGitHub 2a59c0ad9e [logs] Cap the logs reconnect backoff for deep-sleep devices (#17656) 2026-07-17 17:16:14 -10:00
Jonathan SwobodaandGitHub 70421fb14b [espidf] Prune tool download cache after install to shrink the ESP-IDF cache (#17661) 2026-07-17 22:49:50 -04:00
J. Nick KostonandGitHub bacc223360 Ship component requirements.txt files in the sdist and wheel (#17660) 2026-07-17 15:59:12 -10:00
J. Nick KostonandGitHub 94be1da893 Pin cryptography to 48.0.1 on Intel macOS (#17658) 2026-07-17 15:46:16 -10:00
Brandon HarveyandGitHub cb66dc01ab [core] Fix srcFilter exclusions being silently ignored on Windows (#17648) 2026-07-17 21:45:13 -04:00
Jonathan SwobodaandGitHub ebaf32c82a [ci] Add LibreTiny clang-tidy environments (#17491) 2026-07-17 21:38:55 -04:00
Jonathan SwobodaandGitHub f918c299b1 [espidf] Suggest installing missing system libraries when the tools install fails (#17619) 2026-07-17 19:46:45 -04:00
dependabot[bot]andGitHub 328a2018d2 Bump aioesphomeapi from 45.6.1 to 45.6.2 (#17654)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 13:32:47 -10:00
dependabot[bot]andGitHub a4a9feac16 Bump esphome/workflows/.github/workflows/lock.yml from 2026.4.1 to 2026.7.0 (#17652)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 12:26:04 -10:00
esphome[bot]andGitHub 6f27c7f3fe Bump bundled esphome-device-builder to 1.6.3 (#17651) 2026-07-17 12:25:18 -10:00
dependabot[bot]andGitHub 300ab1be35 Bump aioesphomeapi from 45.6.0 to 45.6.1 (#17653)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 12:00:54 -10:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
7aed5fb94b Bump bundled esphome-device-builder to 1.6.2 (#17640)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-17 08:08:28 -04:00
Keith BurzinskiandGitHub 2132cae1c9 [nextion] Fix RP2040 clang-tidy failure from TFT upload guard mismatch (#17638) 2026-07-17 08:07:38 -04:00
Jesse HillsandGitHub 735f8d607d [nrf52] Set ZEPHYR_SDK_INSTALL_DIR for Zephyr SDK discovery (#17633) 2026-07-18 00:04:35 +12:00
Jonathan SwobodaandGitHub cc6392785f [runtime_image] Prevent integer overflow in image buffer size calculation (#17624) 2026-07-16 18:36:18 -04:00
Jonathan SwobodaandGitHub 7a2d13da90 [web_server_idf] Use core format_hex_to helper for digest auth (fixes Arduino build) (#17608) 2026-07-16 15:59:53 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e2df9fb554 Bump ruff from 0.15.21 to 0.15.22 (#17628)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:43:43 -04:00
Jonathan SwobodaandGitHub 4507d05822 [http_request] Fix use-after-return of header collection state in IDF backend (#17627) 2026-07-16 15:34:49 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3caae3031a Bump github/codeql-action/init from 4.37.0 to 4.37.1 (#17629)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:33:38 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a624659856 Bump github/codeql-action/analyze from 4.37.0 to 4.37.1 (#17630)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 15:33:30 -04:00
Jonathan SwobodaandGitHub 6c8d0050ab [esp32_ble_server] Validate descriptor write length before copying (#17626) 2026-07-16 15:32:44 -04:00
Jonathan SwobodaandGitHub 09da2766ed [bluetooth_proxy] Bound GATT characteristic/descriptor enumeration to allocated size (#17625) 2026-07-16 15:32:09 -04:00
72bd45538e [opentherm] Rename OpenthermData accessors that collide with vendor SDK type macros (#17611)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-16 15:25:45 -04:00
Jonathan SwobodaandGitHub 5b37049dff [midea] Restrict to platforms where MideaUART actually builds (#17613) 2026-07-16 15:23:46 -04:00
Jonathan SwobodaandGitHub c0e78a5574 [core] Improve framework mirror selection, download errors, and version parsing (#17615) 2026-07-16 15:22:19 -04:00
Jonathan SwobodaandGitHub f53394e46f [mlx90393] Compile out on BK72xx where the bundled library cannot build (#17614) 2026-07-16 15:14:18 -04:00
J. Nick KostonandGitHub 2218edf20c [web_server] Switch entity id to the new format and drop name_id (#17586) 2026-07-16 08:49:43 -10:00
J. Nick KostonandGitHub dcdd044234 [web_server] Use alarm_control_panel as the domain in JSON (#17594) 2026-07-16 08:48:47 -10:00
J. Nick KostonandGitHub bc8510c6d8 [micro_wake_word] Include the local model file in bundles (#17604) 2026-07-16 08:09:30 -04:00
J. Nick KostonandGitHub 5b4ae22f58 [api] Don't spam tracebacks when decoding a crash without a local build (#17597) 2026-07-16 08:08:28 -04:00
J. Nick KostonandGitHub 14e71e190c [ci] Restore memory impact detail for ESP-IDF builds (#17587) 2026-07-16 08:00:19 -04:00
Guanzhong ChenandGitHub 2333e6eef5 [zephyr] implement ISRInternalGPIOPin::digital_write (#17601) 2026-07-16 07:57:30 -04:00
Jonathan SwobodaandGitHub d748c04b28 [libretiny] Code fixes for upcoming LibreTiny clang-tidy scans (#17596) 2026-07-16 07:25:46 -04:00
Jonathan SwobodaandGitHub 8d1a0446a2 [haier][teleinfo][hlk_fm22x][rp2040_ble] Rename enum members that collide with vendor SDK macros (#17595) 2026-07-16 07:25:37 -04:00
0887e01828 [improv_base] Bump Improv library to 1.2.6 (#17599)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 05:17:01 +00:00
J. Nick KostonandGitHub f88621f2e1 [network] Remove deprecated IPAddress::str() (#17589) 2026-07-16 05:12:06 +00:00
J. Nick KostonandGitHub ec3f7ec16e [api] Remove outdated API version warning (#17593) 2026-07-16 05:07:26 +00:00
J. Nick KostonandGitHub 880cb1db43 [voice_assistant] Remove deprecated Timer::to_string() (#17591) 2026-07-16 05:06:49 +00:00
J. Nick KostonandGitHub f336c45177 [water_heater] Remove deprecated WaterHeaterCall::get_state() (#17592) 2026-07-16 05:06:38 +00:00
J. Nick KostonandGitHub b12d392e02 [esp32_ble] Remove deprecated ESPBTUUID::to_string() (#17590) 2026-07-16 05:06:30 +00:00
49a1d2bb1b [veml3235] Overhaul auto-gain and fix latent config bugs (#17551)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 23:08:14 -05:00
J. Nick KostonandGitHub e327a7f52f [scheduler] Remove deprecated set_retry/cancel_retry (#17585) 2026-07-15 17:58:41 -10:00
Jonathan SwobodaandGitHub 90e838d327 [ci] Extend variant clang-tidy scans to USB logger, tsens and LP peripheral code (#17580) 2026-07-15 23:07:34 -04:00
Jonathan SwobodaandGitHub f91aa7aa13 [as3935_i2c] Use repeated start when reading registers (#17584) 2026-07-16 14:26:23 +12:00
lsellensandGitHub be3b27f378 [rc522_i2c] Change default address to match whats in the docs (#17566) 2026-07-15 21:20:07 -04:00
Jesse HillsandGitHub 3439a08b68 [deep_sleep] Add on_wake automation triggers (#17569) 2026-07-15 21:19:41 -04:00
dedca344f9 [aqi] Add extended_range option for over-range AQI values (#17570)
Co-authored-by: jas <jas@asspa.in>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:17:58 -04:00
Hajo NoerenbergandGitHub c4975e1870 [cc1101] Add FOCCFG and BSCFG config options (#17577) 2026-07-15 21:17:39 -04:00
Jonathan SwobodaandGitHub 5a3c2f4d11 [ci] Add rp2 clang-tidy environment (#17486) 2026-07-15 21:17:21 -04:00
Jonathan SwobodaandGitHub e700b01406 [haier] Fix outdoor defrost temperature reporting the coil temperature (#17492) 2026-07-15 21:16:38 -04:00
Jonathan SwobodaandGitHub d53d4c5b58 [emc2101] Fix negative external temperatures reported as large positives (#17494) 2026-07-15 21:16:24 -04:00
Jesse Hills 3c34fc950e Merge branch 'release' into dev 2026-07-16 12:59:58 +12:00
Jesse Hills b0489e21b2 Merge branch 'beta' into dev 2026-07-16 09:45:16 +12:00
Jesse HillsandGitHub a3ab7961e0 [core] Fix wait_until crash when re-entered from its own continuation (#17571) 2026-07-16 08:14:55 +12:00
Hajo NoerenbergandGitHub 54987d7d23 [cc1101] Export CC1101Listener to Python (#17576) 2026-07-15 12:38:08 -04:00
esphome[bot]GitHubesphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
053ce39fc6 Bump bundled esphome-device-builder to 1.6.1 (#17575)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
2026-07-15 08:51:50 -04:00
Jesse HillsandGitHub b295b8d5a2 [api] Warn when Home Assistant actions are sent with no subscribed client (#17560) 2026-07-15 23:11:47 +12:00
esphome[bot]andGitHub 4d3d06959b Bump bundled esphome-device-builder to 1.6.0 (#17573) 2026-07-14 23:59:19 -10:00
Jesse HillsandGitHub a5583dcba6 [tests] Add test_display component to free touchscreen tests from display pins (#17540) 2026-07-15 11:22:47 +12:00
luar123andGitHub e2b62bcd00 [zigbee] bump esp-zigbee-sdk to 2.0.3 (#17564) 2026-07-14 17:27:17 -04:00
Jonathan SwobodaandGitHub 4275343231 [atc_mithermometer] Make duplicate-packet counter per-instance (#17496) 2026-07-15 09:23:37 +12:00
dependabot[bot]andGitHub 4ebe49b141 Bump clang-tidy from 22.1.7 to 22.1.8 (#17565)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-14 09:54:02 -10:00
Jesse Hills d3adb6fb81 Merge branch 'beta' into dev 2026-07-14 16:46:31 +12:00
esphome[bot]andGitHub 2753ab1f45 Bump bundled esphome-device-builder to 1.5.0 (#17558) 2026-07-13 16:50:08 -10:00
b6b5b61640 [mcp23017] reset IPOL registers to 0x00 on setup (#17177)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-07-14 02:38:07 +00:00
Jesse HillsandGitHub db9a09d05a [script] Recursively expand nested packages when merging component tests (#17557) 2026-07-14 14:11:03 +12:00
Keith BurzinskiandGitHub ca77cc585c [nextion] Fix unbounded queue growth and OOM crash when display sends no data (#17553) 2026-07-14 11:56:03 +12:00
Jonathan SwobodaandGitHub f1e4726f4e [veml7700][as7341][ltr501] Fix device class on raw-count sensors (#17549) 2026-07-14 11:53:31 +12:00
J. Nick KostonandGitHub 49bbceb1da [libretiny] Keep renamed board generic-ln882hki validating against generic-ln882h (#17542) 2026-07-14 11:51:54 +12:00
Jesse HillsandGitHub 65d6c028ce [web_server] Add HTTP digest authentication with selectable scheme (#17541) 2026-07-14 10:33:44 +12:00
Clyde StubbsandGitHub 5e3e2f82c9 [script] Fix duplicate import in build_codeowners.py (#17543) 2026-07-13 10:16:53 -04:00
Jesse HillsandGitHub d78cb09b17 [web_server] Use dict-style packages in tests so they can be batch-grouped (#17544) 2026-07-13 23:23:55 +12:00
Keith BurzinskiandGitHub bcfb438a81 [esp32] Do not require verification_key with Secure Boot V2 signing schemes (#17497) 2026-07-13 16:23:40 +12:00
Jesse HillsandGitHub a8dfd00cc6 [web_server] Add CORS origin checking with allowed_origins (#17530) 2026-07-13 15:38:48 +12:00
Jesse HillsandGitHub 4a82b10783 [ci] Group component test output into collapsible CI log sections (#17536) 2026-07-13 15:29:35 +12:00
Jesse HillsandGitHub 07460ebee4 [gsl3670] Fix i2c package variant in esp32-s3-idf test (#17535) 2026-07-13 15:28:55 +12:00
Jesse Hills cdc42e5e55 Merge branch 'beta' into dev 2026-07-13 13:25:21 +12:00
Jonathan SwobodaandGitHub b3e03868b3 [mipi_rgb] Test in isolation to avoid bus/pin merge conflicts (#17534) 2026-07-13 12:00:22 +12:00
Jesse HillsandGitHub 0ff11674ef [mipi_rgb] Use dict-style packages in test so it can be batch-grouped (#17533) 2026-07-12 18:38:38 -04:00
Keith BurzinskiandGitHub 434cffb745 [zwave_proxy] Fix parser gaps and harden frame and subscription handling (#17461) 2026-07-13 08:58:17 +12:00
J. Nick KostonandGitHub 2b3027a7fd [api] Provision encryption keys over an encrypted zero-PSK noise connection (#17482) 2026-07-13 08:55:30 +12:00
Jesse HillsandGitHub e27a14ec70 [core] Classify entity metadata visibility for the visual editor (#17503) 2026-07-12 08:33:29 -04:00
luar123andGitHub 91c42381f6 [zigbee] prevent task watchdog trigger with large configs. (#17506) 2026-07-12 08:32:01 -04:00
luar123andGitHub a39607476a [zigbee] Fix merge endpoint (#17511) 2026-07-12 08:31:39 -04:00
J. Nick KostonandGitHub b098571a6f [web_server] Fix unused function warning for json_state_str (#17524) 2026-07-11 14:31:14 -10:00
J. Nick KostonandGitHub 614fd88829 [mdns] Fix missing device info TXT records when native API is not enabled (#17520) 2026-07-11 14:31:01 -10:00
J. Nick KostonandGitHub c0636e2bf7 [core] Make config-hash independent of machine-local paths (#17523) 2026-07-11 14:30:47 -10:00
esphome[bot]andGitHub 65353006c8 Bump bundled esphome-device-builder to 1.4.3 (#17522) 2026-07-11 13:32:45 -10:00
tomaszduda23andGitHub 35a99f478e [deep_sleep] feed watchdog in deep sleep (#17516) 2026-07-11 09:48:11 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b6a4dd237e Update tzdata requirement from >=2026.2 to >=2026.3 (#17510)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:42 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0ef85783dc Bump actions/stale from 10.3.0 to 10.4.0 (#17509)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:23 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5020179210 Bump ruff from 0.15.20 to 0.15.21 (#17508)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-11 09:45:02 -04:00
54529412dc [mipi_dsi] New model for M5Stack Tab5 (#17500)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 09:38:01 -04:00
Clyde StubbsGitHubpre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
e6525b5d93 [mipi][mipi_spi] SWRESET handling improved (#17504)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-07-11 09:36:26 -04:00
RaymondandGitHub 9f62cf9243 [mipi_dsi] Add JC8012P4A1-V2 (#17457) 2026-07-11 21:24:00 +10:00
esphome[bot]andGitHub 478bca026c Bump bundled esphome-device-builder to 1.4.2 (#17512) 2026-07-10 16:37:00 -10:00
e1719cd85d [mipi][mipi_spi][mipi_dsi][mipi_rgb] Transform cleanup (#17405)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:06:40 -04:00
esphome[bot]andGitHub 83aaed71e1 Bump bundled esphome-device-builder to 1.4.1 (#17507) 2026-07-10 10:30:19 -10:00
Jesse HillsandGitHub 88ca0d44e0 [docs] Document web server as an open HTTP API by design in threat model (#17465) 2026-07-10 16:38:23 +12:00
esphome[bot]andGitHub 7a1e0bbbec Bump bundled esphome-device-builder to 1.4.0 (#17495) 2026-07-09 15:36:42 -10:00
Jonathan SwobodaandGitHub b2226b91ff [web_server] Serialize entity state strings without a copy buffer (#17488) 2026-07-09 16:51:04 -04:00
dependabot[bot]andGitHub ba84f2ec55 Bump aioesphomeapi from 45.5.2 to 45.6.0 (#17490)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-09 19:53:42 +00:00
dependabot[bot]andGitHub 4b19de0c1a Bump CodSpeedHQ/action from 4.18.4 to 4.18.5 (#17489)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-09 19:35:16 +00:00
Jonathan SwobodaandGitHub 4292e7988e [mcp4461] Fix wiper increment/decrement write length (#17487) 2026-07-09 15:27:05 -04:00
Jonathan SwobodaandGitHub 9f21fd0b55 [usb_uart] Fix output chunk length truncated to 8 bits (#17480) 2026-07-09 15:04:42 -04:00
Jesse Hills 9327d011fc Merge branch 'beta' into dev 2026-07-09 16:54:49 +12:00
Jonathan SwobodaandGitHub 435dde67d0 [ci] Stop per-PR cache copies from crowding the 10GB Actions cache quota (#17463) 2026-07-08 23:29:23 -04:00
Jonathan SwobodaandGitHub 9c92ab63fb [gsl3670] Reference the test display explicitly so grouped CI builds validate (#17462) 2026-07-09 13:43:56 +12:00
Elvin LuffandGitHub acc8381cbb [epaper_spi] Remove noop deep sleep command (#15595) 2026-07-09 11:29:39 +10:00
Jesse Hills 0a8a7e22d2 Bump version to 2026.8.0-dev 2026-07-09 12:30:54 +12:00
266 changed files with 10105 additions and 14430 deletions
+26 -5
View File
@@ -465,7 +465,7 @@ jobs:
echo "binary=$BINARY" >> $GITHUB_OUTPUT
- name: Run CodSpeed benchmarks
uses: CodSpeedHQ/action@9f3a37ece7abc84992501a7fcd54d1704f3458fa # v4.18.4
uses: CodSpeedHQ/action@f99becdce5e5d51fd556489ebef684f4ecfd6286 # v4.18.5
with:
run: |
. venv/bin/activate
@@ -504,6 +504,15 @@ jobs:
options: --environment nrf52-tidy --grep USE_ZEPHYR --grep USE_NRF52
cache_sdk_nrf: true
ignore_errors: false
- id: clang-tidy
name: Run script/clang-tidy for RP2
options: --environment rp2-tidy --grep USE_RP2
pio_cache_key: tidyrp2
- id: clang-tidy
name: Run script/clang-tidy for LibreTiny
environments: bk72xx-tidy ln882h-tidy rtl87xxb-tidy rtl87xxc-tidy
options: --grep USE_LIBRETINY --grep USE_BK72XX --grep USE_RTL87XX --grep USE_LN882X
pio_cache_key: tidylibretiny
steps:
- name: Check out code from GitHub
@@ -567,10 +576,21 @@ jobs:
. venv/bin/activate
if [ "${{ steps.check_full_scan.outputs.full_scan }}" = "true" ]; then
echo "Running FULL clang-tidy scan (reason: ${{ steps.check_full_scan.outputs.reason }})"
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
changed=""
else
echo "Running clang-tidy on changed files only"
script/clang-tidy --all-headers --fix --changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
changed="--changed"
fi
if [ -n "${{ matrix.environments }}" ]; then
rc=0
for env in ${{ matrix.environments }}; do
echo "::group::clang-tidy $env"
script/clang-tidy --all-headers --fix $changed --environment "$env" ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }} || rc=1
echo "::endgroup::"
done
exit $rc
else
script/clang-tidy --all-headers --fix $changed ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
fi
env:
# Also cache libdeps, store them in a ~/.platformio subfolder
@@ -744,7 +764,8 @@ jobs:
include:
- id: clang-tidy
name: Run script/clang-tidy for ESP32 S3
options: --environment esp32s3-idf-tidy --grep USE_ESP32_VARIANT_ESP32S3
# yamllint disable-line rule:line-length
options: --environment esp32s3-idf-tidy --grep SOC_TEMP_SENSOR_SUPPORTED --grep USE_ESP32_VARIANT_ESP32S3 --grep USE_LOGGER_USB_CDC
- id: clang-tidy
name: Run script/clang-tidy for ESP32 P4
# P4 has no native Wi-Fi/BLE; those run over the hosted co-processor,
@@ -754,7 +775,7 @@ jobs:
- id: clang-tidy
name: Run script/clang-tidy for ESP32 C6
# yamllint disable-line rule:line-length
options: --environment esp32c6-idf-tidy --grep USE_ESP32_VARIANT_ESP32C6 --grep USE_OPENTHREAD --grep USE_ZIGBEE
options: --environment esp32c6-idf-tidy --grep SOC_LP_I2C_SUPPORTED --grep USE_ESP32_VARIANT_ESP32C6 --grep USE_OPENTHREAD --grep USE_ZIGBEE
steps:
- name: Check out code from GitHub
+2 -2
View File
@@ -56,7 +56,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -84,6 +84,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
category: "/language:${{matrix.language}}"
+1 -1
View File
@@ -14,4 +14,4 @@ jobs:
permissions:
issues: write # issues.lock on closed issues
pull-requests: write # issues.lock on closed pull requests
uses: esphome/workflows/.github/workflows/lock.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1
uses: esphome/workflows/.github/workflows/lock.yml@9f6577fd37b5cf773ab1b9be929714a0dcd15661 # 2026.7.0
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Stale
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
with:
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
remove-stale-when-updated: true
+1
View File
@@ -145,6 +145,7 @@ esphome/components/dlms_meter/* @latonita @PolarGoose @SimonFischer04 @Tomer27cz
esphome/components/dps310/* @kbx81
esphome/components/ds1307/* @badbadc0ffee
esphome/components/ds2484/* @mrk-its
esphome/components/ds248x/* @tomwellnitz
esphome/components/dsmr/* @glmnet @PolarGoose
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
+1 -1
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 = 2026.7.4
PROJECT_NUMBER = 2026.8.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+1 -1
View File
@@ -22,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.9.2
RUN uv pip install --no-cache-dir esphome-device-builder==1.6.4
RUN \
platformio settings set enable_telemetry No \
+2 -17
View File
@@ -776,13 +776,6 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int:
check_placeholder_credentials(config)
# Keep this here, NOT in codegen: config-hash and --only-generate must keep
# working on machines that cannot run the toolchain.
if CORE.is_esp8266:
from esphome.components.esp8266 import check_rosetta
check_rosetta()
# NOTE: "Build path:" format is parsed by script/ci_memory_impact_extract.py
# If you change this format, update the regex in that script as well
_LOGGER.info("Compiling app... Build path: %s", CORE.build_path)
@@ -1517,18 +1510,10 @@ def _redact_with_legacy_fallback(output: str) -> str:
m = _LEGACY_REDACTION_RE.search(line)
if m is None:
continue
key = m.group("key")
if not in_substitutions:
# Public keys (e.g. wireguard's peer_public_key) are not secret;
# redacting them and telling maintainers to mark them cv.sensitive
# would be wrong on both counts. Substitution keys are user-named
# with no schema behind them, so anything secret-shaped there
# (public or not) stays conservatively redacted.
if "public" in key.split("_"):
continue
unmarked.add(key)
unmarked.add(m.group("key"))
lines[i] = (
f"{line[: m.start()]}{key}: "
f"{line[: m.start()]}{m.group('key')}: "
f"\\033[8m{m.group('val')}\\033[28m{line[m.end() :]}"
)
output = "\n".join(lines)
+8 -27
View File
@@ -20,6 +20,7 @@ from . import (
RAM_SECTIONS,
MemoryAnalyzer,
)
from .toolchain import find_elf_path, find_idedata_path, idedata_candidates
if TYPE_CHECKING:
from . import ComponentMemory
@@ -759,45 +760,25 @@ def main():
print(f"Error: {build_path} is not a directory", file=sys.stderr)
sys.exit(1)
# Find firmware.elf
elf_file = None
for elf_candidate in [
build_path / "firmware.elf",
build_path / ".pioenvs" / build_path.name / "firmware.elf",
]:
if elf_candidate.exists():
elf_file = str(elf_candidate)
break
if not elf_file:
print(f"Error: firmware.elf not found in {build_dir}", file=sys.stderr)
elf_path = find_elf_path(build_path)
if not elf_path:
print(f"Error: no firmware ELF found in {build_dir}", file=sys.stderr)
sys.exit(1)
# Find idedata.json - check current directory first, then home
device_name = build_path.name
idedata_candidates = [
Path.cwd() / ".esphome" / "idedata" / f"{device_name}.json",
Path.home() / ".esphome" / "idedata" / f"{device_name}.json",
]
elf_file = str(elf_path)
idedata = None
for idedata_path in idedata_candidates:
if not idedata_path.exists():
continue
if idedata_path := find_idedata_path(build_path):
try:
with idedata_path.open(encoding="utf-8") as f:
raw_data = json.load(f)
idedata = IDEData(raw_data)
print(f"Loaded idedata from: {idedata_path}", file=sys.stderr)
break
except (json.JSONDecodeError, OSError) as e:
print(f"Warning: Failed to load idedata: {e}", file=sys.stderr)
if not idedata:
print(
f"Warning: idedata not found (searched {idedata_candidates[0]} and {idedata_candidates[1]})",
file=sys.stderr,
)
searched = "\n ".join(str(p) for p in idedata_candidates(build_path))
print(f"Warning: idedata not found, searched:\n {searched}", file=sys.stderr)
analyzer = MemoryAnalyzerCLI(elf_file, idedata=idedata)
analyzer.analyze()
+72
View File
@@ -23,6 +23,78 @@ TOOLCHAIN_PREFIXES = [
]
def find_elf_path(build_path: Path) -> Path | None:
"""Locate the firmware ELF inside an ESPHome build directory.
The layout depends on the toolchain that produced the build, so try each
known one in turn.
Args:
build_path: Path to an ESPHome build directory
Returns:
Path to the ELF file, or None if no known layout matches
"""
name = build_path.name
for candidate in (
# Native ESP-IDF: idf.py writes build/<name>.elf, which ESPHome copies
# to build/firmware.elf (see espidf.toolchain.create_elf_copy)
build_path / "build" / "firmware.elf",
# PlatformIO
build_path / "firmware.elf",
build_path / ".pioenvs" / name / "firmware.elf",
# LibreTiny uses raw_firmware.elf
build_path / "raw_firmware.elf",
build_path / ".pioenvs" / name / "raw_firmware.elf",
# Zephyr (nRF52); the SDK nests the artifacts one level deeper from 2.9.2
build_path / ".pioenvs" / name / "zephyr" / "zephyr" / "zephyr.elf",
build_path / ".pioenvs" / name / "zephyr" / "zephyr.elf",
):
if candidate.is_file():
return candidate
return None
def idedata_candidates(build_path: Path) -> list[Path]:
"""Return the idedata locations searched for a build directory, in order.
Exposed so a caller reporting "not found" can name the paths it tried
without keeping its own copy of the list.
Args:
build_path: Path to an ESPHome build directory
Returns:
The candidate idedata JSON paths, most specific first
"""
name = build_path.name
return [
# In .pioenvs for test builds
build_path / ".pioenvs" / name / "idedata.json",
# Both toolchains cache it in the data dir, which holds this build dir:
# <data_dir>/idedata/<name>.json next to <data_dir>/build/<name>
build_path.parent.parent / "idedata" / f"{name}.json",
# Regular builds, invoked from the config dir or from anywhere
Path.cwd() / ".esphome" / "idedata" / f"{name}.json",
Path.home() / ".esphome" / "idedata" / f"{name}.json",
]
def find_idedata_path(build_path: Path) -> Path | None:
"""Locate the idedata JSON belonging to an ESPHome build directory.
Args:
build_path: Path to an ESPHome build directory
Returns:
Path to the idedata JSON, or None if it was not found
"""
for candidate in idedata_candidates(build_path):
if candidate.is_file():
return candidate
return None
def _find_in_platformio_packages(tool_name: str) -> str | None:
"""Search for a tool in PlatformIO package directories.
-12
View File
@@ -210,27 +210,15 @@ def get_component_cmakelists() -> str:
if(CMAKE_SCRIPT_MODE_FILE)
file(GLOB_RECURSE app_sources
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cc"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cxx"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c++"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cc"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cxx"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c++"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
)
else()
file(GLOB_RECURSE app_sources CONFIGURE_DEPENDS
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cpp"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cc"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.cxx"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c++"
"${{CMAKE_CURRENT_SOURCE_DIR}}/*.c"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cpp"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cc"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.cxx"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c++"
"${{CMAKE_CURRENT_SOURCE_DIR}}/esphome/*.c"
)
endif()
+1 -100
View File
@@ -12,7 +12,7 @@ from enum import StrEnum
import io
import json
import logging
from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath
from pathlib import Path
import re
import shutil
import tarfile
@@ -51,7 +51,6 @@ class ManifestKey(StrEnum):
MANIFEST_VERSION = "manifest_version"
ESPHOME_VERSION = "esphome_version"
CONFIG_FILENAME = "config_filename"
CONFIG_DIR = "config_dir"
FILES = "files"
HAS_SECRETS = "has_secrets"
@@ -128,12 +127,6 @@ class BundleData:
"""Files components asked to include, keyed under DOMAIN in CORE.data."""
extra_files: list[Path] = field(default_factory=list)
# Original config dir parsed from an extracted bundle's manifest.json,
# kept in the path flavor of the machine the bundle was created on.
# The checked flag makes the manifest lookup happen at most once per run;
# CORE.data is cleared between runs.
original_config_dir: PurePath | None = None
original_config_dir_checked: bool = False
def _get_data() -> BundleData:
@@ -155,94 +148,6 @@ def add_bundle_file(path: Path) -> None:
_get_data().extra_files.append(CORE.relative_config_path(path))
# Windows paths start with a drive letter or contain backslashes; POSIX
# paths do neither in practice, so this is how the flavor of a recorded
# path string is recognized on any host.
_WINDOWS_DRIVE_RE = re.compile(r"^[A-Za-z]:")
def _path_flavor(value: str) -> type[PurePath]:
"""Pick the pure path class matching the flavor ``value`` was written in."""
if "\\" in value or _WINDOWS_DRIVE_RE.match(value):
return PureWindowsPath
return PurePosixPath
def _load_original_config_dir() -> PurePath | None:
"""Read the original config dir from an extracted bundle's manifest.
Returns None when the current config dir is not an extracted bundle or
the manifest does not record the original config dir.
"""
manifest_path = CORE.config_dir / MANIFEST_FILENAME
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except FileNotFoundError:
# The common case: this config dir is not an extracted bundle.
return None
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as err:
# A manifest.json is present but unreadable or malformed. Say so
# instead of letting it look identical to "not a bundle".
_LOGGER.warning("Bundle: ignoring unreadable %s: %s", manifest_path, err)
return None
if not isinstance(manifest, dict):
return None
# A manifest.json in the config dir does not have to be ours. Only trust
# one that looks like a bundle manifest for exactly this config file.
version = manifest.get(ManifestKey.MANIFEST_VERSION)
if not isinstance(version, int) or version < 1:
return None
if manifest.get(ManifestKey.CONFIG_FILENAME) != CORE.config_path.name:
return None
config_dir = manifest.get(ManifestKey.CONFIG_DIR)
if not isinstance(config_dir, str) or not config_dir:
return None
return _path_flavor(config_dir)(config_dir)
def remap_bundle_path(value: str) -> Path | None:
"""Remap an absolute path from the machine a bundle was created on.
A bundled config may reference files by absolute path. The referenced
files ship inside the bundle at their config-relative locations, but the
YAML text is copied verbatim, so after extraction on another machine the
absolute reference points at a path that only existed on the creating
machine. The bundle manifest records that machine's config dir; when
``value`` names a path that lived under it, return the corresponding
file next to the extracted config.
``value`` is the raw path string from the config. It is parsed with the
original machine's path flavor, so a bundle created on Windows remaps on
a POSIX build server and vice versa.
Returns None when not compiling an extracted bundle, when ``value`` was
not under the original config dir, or when the bundle does not contain
the file.
"""
data = _get_data()
if not data.original_config_dir_checked:
data.original_config_dir_checked = True
data.original_config_dir = _load_original_config_dir()
original_dir = data.original_config_dir
if original_dir is None:
return None
path = type(original_dir)(value)
if not path.is_absolute():
return None
try:
rel = path.relative_to(original_dir)
except ValueError:
return None
# relative_to is lexical, so ".." segments survive it. Refuse them: the
# remapped file must land strictly inside the extracted config tree.
if ".." in rel.parts:
return None
remapped = CORE.relative_config_path(Path(*rel.parts))
if not remapped.exists():
return None
return remapped
@dataclass
class BundleFile:
"""A file to include in the bundle."""
@@ -269,7 +174,6 @@ class BundleManifest:
config_filename: str
files: list[str]
has_secrets: bool
config_dir: str | None = None
class ConfigBundleCreator:
@@ -534,7 +438,6 @@ class ConfigBundleCreator:
ManifestKey.MANIFEST_VERSION: CURRENT_MANIFEST_VERSION,
ManifestKey.ESPHOME_VERSION: const.__version__,
ManifestKey.CONFIG_FILENAME: self._config_path.name,
ManifestKey.CONFIG_DIR: str(self._config_dir),
ManifestKey.FILES: [f.path for f in files],
ManifestKey.HAS_SECRETS: has_secrets,
}
@@ -619,14 +522,12 @@ def read_bundle_manifest(bundle_path: Path) -> BundleManifest:
except tarfile.TarError as err:
raise EsphomeError(f"Failed to read bundle: {err}") from err
config_dir = manifest.get(ManifestKey.CONFIG_DIR)
return BundleManifest(
manifest_version=manifest[ManifestKey.MANIFEST_VERSION],
esphome_version=manifest.get(ManifestKey.ESPHOME_VERSION, "unknown"),
config_filename=manifest[ManifestKey.CONFIG_FILENAME],
files=manifest.get(ManifestKey.FILES, []),
has_secrets=manifest.get(ManifestKey.HAS_SECRETS, False),
config_dir=config_dir if isinstance(config_dir, str) else None,
)
@@ -1746,12 +1746,6 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu16 ".%" PRIu16, this->helper_->get_client_name(),
this->helper_->get_peername_to(peername), this->client_api_version_major_, this->client_api_version_minor_);
// TODO: Remove before 2026.8.0 (one version after get_object_id backward compat removal)
if (!this->client_supports_api_version(1, 14)) {
ESP_LOGW(TAG, "'%s' using outdated API %" PRIu16 ".%" PRIu16 ", update to 1.14+", this->helper_->get_client_name(),
this->client_api_version_major_, this->client_api_version_minor_);
}
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 14;
+2 -19
View File
@@ -12,22 +12,6 @@ APIOverflowBuffer::~APIOverflowBuffer() {
}
ssize_t APIOverflowBuffer::try_drain(socket::Socket *socket) {
// socket->write() can re-enter this function: a log message emitted from an
// lwip callback during the write goes out over the API and lands back in the
// frame helper's write/drain path. If a nested drain ran here it would send
// and free the entry the outer drain is still holding, causing a double free.
// Report "no progress" instead; the outer drain keeps draining, and the
// nested send is enqueued behind the existing backlog.
if (this->draining_)
return 0;
// RAII so the flag is cleared on every return path
struct DrainGuard {
explicit DrainGuard(bool &flag) : flag_(flag) { flag_ = true; }
~DrainGuard() { this->flag_ = false; }
bool &flag_;
} guard(this->draining_);
while (this->count_ > 0) {
Entry *front = this->queue_[this->head_];
@@ -45,12 +29,11 @@ ssize_t APIOverflowBuffer::try_drain(socket::Socket *socket) {
return sent;
}
// Entry fully sent — unlink it before freeing so a freed pointer is never
// reachable from the queue
// Entry fully sent — free it and advance
Entry::destroy(front);
this->queue_[this->head_] = nullptr;
this->head_ = (this->head_ + 1) % API_MAX_SEND_QUEUE;
this->count_--;
Entry::destroy(front);
}
return 0; // All drained
@@ -69,10 +69,6 @@ class APIOverflowBuffer {
uint8_t head_{0};
uint8_t tail_{0};
uint8_t count_{0};
// Guards against re-entrant drains: socket->write() can re-enter the API
// send path (e.g. a log message emitted from an lwip callback), and a nested
// drain would free the entry the outer drain is still holding.
bool draining_{false};
};
} // namespace esphome::api
+14 -11
View File
@@ -18,7 +18,7 @@ with warnings.catch_warnings():
import contextlib
from esphome.const import CONF_KEY, CONF_PORT, __version__
from esphome.core import CORE, EsphomeError
from esphome.core import CORE
from esphome.util import safe_print
from . import CONF_ENCRYPTION
@@ -36,15 +36,17 @@ class _LogLineProcessor:
"""Feeds incoming log lines to the stack-trace decoder.
Two responsibilities beyond just calling the decoder:
1. Catch EsphomeError. on_log runs inside an asyncio protocol
callback; if an exception escapes, the loop tears the transport
down with "Fatal error: protocol.data_received() call failed."
and ReconnectLogic immediately reconnects, the device replays
the same crash trace, and we loop forever.
2. Disable decoding after the first failure. _decode_pc shells out
to PlatformIO via _run_idedata, which is expensive; a single
crash dump can contain many PC/BT lines and we don't want to
retry the failing subprocess for each one.
1. Catch everything the decoder can raise. aioesphomeapi isolates
exceptions raised by log handlers, so an escaping one no longer
kills the session, but it does log a full traceback per line. A
crash dump carries a PC line plus one per backtrace frame, so the
tracebacks bury the dump the user is trying to read. Decoding is a
diagnostic nicety; nothing it raises is worth that noise.
2. Disable decoding after the first failure. _decode_pc shells out to
the toolchain to resolve addr2line, which is expensive; a single
crash dump can contain many PC/BT lines and we don't want to retry
the failing subprocess for each one. This only works if every
failure is caught, which is why 1 is not narrowed to EsphomeError.
"""
def __init__(self, config: dict[str, Any], platform_handler: Any | None) -> None:
@@ -61,12 +63,13 @@ class _LogLineProcessor:
self.backtrace_state = self._platform_handler(
self._config, raw_line, self.backtrace_state
)
except EsphomeError as exc:
except Exception as exc: # noqa: BLE001 # pylint: disable=broad-except
self._decode_enabled = False
self.backtrace_state = False
# _run_idedata raises EsphomeError with no message; fall back
# to a generic explanation when str(exc) is empty.
detail = str(exc) or "build artifacts not found locally"
_LOGGER.debug("Stack-trace decoding failed", exc_info=True)
_LOGGER.warning(
"Crash trace decoding unavailable: %s. "
"Run 'esphome compile' for this device to enable PC decoding.",
+1
View File
@@ -7,6 +7,7 @@ AQICalculatorType = aqi_ns.enum("AQICalculatorType")
CONF_AQI = "aqi"
CONF_CALCULATION_TYPE = "calculation_type"
CONF_EXTENDED_RANGE = "extended_range"
AQI_CALCULATION_TYPE = {
"CAQI": AQICalculatorType.CAQI_TYPE,
@@ -6,7 +6,7 @@ namespace esphome::aqi {
class AbstractAQICalculator {
public:
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0;
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool extended_range) = 0;
};
} // namespace esphome::aqi
+20 -10
View File
@@ -11,10 +11,12 @@ namespace esphome::aqi {
class AQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool extended_range) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID, extended_range);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID, extended_range);
float aqi = std::max({pm2_5_index, pm10_0_index, 0.0f});
// extended_range lets the index run past the standard maximum, so clamp to the sensor's range.
aqi = std::min(aqi, static_cast<float>(std::numeric_limits<uint16_t>::max()));
return static_cast<uint16_t>(std::lround(aqi));
}
@@ -30,7 +32,7 @@ class AQICalculator : public AbstractAQICalculator {
{35.5f, 55.5f},
{55.5f, 125.5f},
{125.5f, 225.5f},
{225.5f, std::numeric_limits<float>::max()}
{225.5f, 500.4f} // EPA 2024: AQI 301-500 maps to PM2.5 225.5-500.4 ug/m3
// clang-format on
};
@@ -41,11 +43,11 @@ class AQICalculator : public AbstractAQICalculator {
{155.0f, 255.0f},
{255.0f, 355.0f},
{355.0f, 425.0f},
{425.0f, std::numeric_limits<float>::max()}
{425.0f, 604.0f} // EPA: AQI 301-500 maps to PM10 425-604 ug/m3 (top of the 401-500 band)
// clang-format on
};
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
static float calculate_index(float value, const float array[NUM_LEVELS][2], bool extended_range) {
int grid_index = get_grid_index(value, array);
if (grid_index == -1) {
return -1.0f;
@@ -55,14 +57,22 @@ class AQICalculator : public AbstractAQICalculator {
float conc_lo = array[grid_index][0];
float conc_hi = array[grid_index][1];
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
float index = (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
// Concentrations above the highest breakpoint run the linear fit past aqi_hi. By default we
// clamp to the standard maximum; with extended_range we keep the extrapolated "over-range"
// value so heavy pollution reports numbers beyond what the standard defines.
if (grid_index == NUM_LEVELS - 1 && !extended_range && index > aqi_hi) {
return aqi_hi;
}
return index;
}
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
const bool in_range =
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
: (value < array[i][1])); // others exclusive on hi
// The top band is open-ended: any value at or above its lower breakpoint falls into it,
// and calculate_index() decides whether to clamp or extrapolate.
const bool in_range = (value >= array[i][0]) && (i == NUM_LEVELS - 1 || value < array[i][1]);
if (in_range) {
return i;
}
+2 -1
View File
@@ -24,6 +24,7 @@ void AQISensor::setup() {
void AQISensor::dump_config() {
ESP_LOGCONFIG(TAG, "AQI Sensor:");
ESP_LOGCONFIG(TAG, " Calculation Type: %s", this->aqi_calc_type_ == AQI_TYPE ? "AQI" : "CAQI");
ESP_LOGCONFIG(TAG, " Extended Range: %s", this->extended_range_ ? "enabled" : "disabled");
if (this->pm_2_5_sensor_ != nullptr) {
ESP_LOGCONFIG(TAG, " PM2.5 Sensor: '%s'", this->pm_2_5_sensor_->get_name().c_str());
}
@@ -44,7 +45,7 @@ void AQISensor::calculate_aqi_() {
return;
}
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_);
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_, this->extended_range_);
this->publish_state(aqi);
}
+2
View File
@@ -14,6 +14,7 @@ class AQISensor final : public sensor::Sensor, public Component {
void set_pm_2_5_sensor(sensor::Sensor *sensor) { this->pm_2_5_sensor_ = sensor; }
void set_pm_10_0_sensor(sensor::Sensor *sensor) { this->pm_10_0_sensor_ = sensor; }
void set_aqi_calculation_type(AQICalculatorType type) { this->aqi_calc_type_ = type; }
void set_extended_range(bool extended_range) { this->extended_range_ = extended_range; }
protected:
void calculate_aqi_();
@@ -21,6 +22,7 @@ class AQISensor final : public sensor::Sensor, public Component {
sensor::Sensor *pm_2_5_sensor_{nullptr};
sensor::Sensor *pm_10_0_sensor_{nullptr};
AQICalculatorType aqi_calc_type_{AQI_TYPE};
bool extended_range_{false};
AQICalculatorFactory aqi_calculator_factory_;
float pm_2_5_value_{NAN};
+13 -10
View File
@@ -9,25 +9,28 @@ namespace esphome::aqi {
class CAQICalculator : public AbstractAQICalculator {
public:
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
// The CAQI (CITEAIR) scale defines no maximum: its top "Very high" class is simply ">100". We
// therefore always extrapolate the top band past 100 without limit, so the extended_range flag
// (which lifts the AQI calculator's fixed 500 cap) has no meaning here and is ignored.
uint16_t get_aqi(float pm2_5_value, float pm10_0_value, bool /*extended_range*/) override {
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
float aqi = std::max({pm2_5_index, pm10_0_index, 0.0f});
aqi = std::min(aqi, static_cast<float>(std::numeric_limits<uint16_t>::max()));
return static_cast<uint16_t>(std::lround(aqi));
}
protected:
static constexpr int NUM_LEVELS = 5;
static constexpr int NUM_LEVELS = 4;
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}};
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
// clang-format off
{0.0f, 15.1f},
{15.1f, 30.1f},
{30.1f, 55.1f},
{55.1f, 110.1f},
{110.1f, std::numeric_limits<float>::max()}
{55.1f, 110.1f}
// clang-format on
};
@@ -36,8 +39,7 @@ class CAQICalculator : public AbstractAQICalculator {
{0.0f, 25.1f},
{25.1f, 50.1f},
{50.1f, 90.1f},
{90.1f, 180.1f},
{180.1f, std::numeric_limits<float>::max()}
{90.1f, 180.1f}
// clang-format on
};
@@ -52,14 +54,15 @@ class CAQICalculator : public AbstractAQICalculator {
float conc_lo = array[grid_index][0];
float conc_hi = array[grid_index][1];
// The top band is open-ended (see get_grid_index), so for concentrations above the last
// breakpoint this linear fit extrapolates past 100 unbounded, matching CAQI's open ">100" class.
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
}
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
for (int i = 0; i < NUM_LEVELS; i++) {
const bool in_range =
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
: (value < array[i][1])); // others exclusive on hi
// The top band is open-ended: any value at or above its lower breakpoint falls into it.
const bool in_range = (value >= array[i][0]) && (i == NUM_LEVELS - 1 || value < array[i][1]);
if (in_range) {
return i;
}
+17 -3
View File
@@ -8,14 +8,25 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
)
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, aqi_ns
from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, CONF_EXTENDED_RANGE, aqi_ns
CODEOWNERS = ["@jasstrong"]
DEPENDENCIES = ["sensor"]
AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component)
CONFIG_SCHEMA = (
def _validate_extended_range(config):
if CONF_EXTENDED_RANGE in config and config[CONF_CALCULATION_TYPE] == "CAQI":
raise cv.Invalid(
f"'{CONF_EXTENDED_RANGE}' is not supported with 'calculation_type: CAQI'. "
"CAQI has no maximum value by specification, so it is always reported unbounded.",
[CONF_EXTENDED_RANGE],
)
return config
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
AQISensor,
accuracy_decimals=0,
@@ -29,9 +40,11 @@ CONFIG_SCHEMA = (
cv.Required(CONF_CALCULATION_TYPE): cv.enum(
AQI_CALCULATION_TYPE, upper=True
),
cv.Optional(CONF_EXTENDED_RANGE): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
_validate_extended_range,
)
@@ -46,3 +59,4 @@ async def to_code(config):
cg.add(var.set_pm_10_0_sensor(pm_10_0_sensor))
cg.add(var.set_aqi_calculation_type(config[CONF_CALCULATION_TYPE]))
cg.add(var.set_extended_range(config.get(CONF_EXTENDED_RANGE, False)))
@@ -37,15 +37,15 @@ namespace esphome::beken_spi_led_strip {
static const char *const TAG = "beken_spi_led_strip";
struct spi_data_t {
struct SpiData {
SemaphoreHandle_t dma_tx_semaphore;
volatile bool tx_in_progress;
bool first_run;
};
static spi_data_t *spi_data = nullptr;
static SpiData *spi_data = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static void set_spi_ctrl_register(unsigned long bit, bool val) {
static void set_spi_ctrl_register(uint32_t bit, bool val) {
uint32_t value = REG_READ(SPI_CTRL);
if (val == 0) {
value &= ~bit;
@@ -55,7 +55,7 @@ static void set_spi_ctrl_register(unsigned long bit, bool val) {
REG_WRITE(SPI_CTRL, value);
}
static void set_spi_config_register(unsigned long bit, bool val) {
static void set_spi_config_register(uint32_t bit, bool val) {
uint32_t value = REG_READ(SPI_CONFIG);
if (val == 0) {
value &= ~bit;
@@ -67,7 +67,7 @@ static void set_spi_config_register(unsigned long bit, bool val) {
void spi_dma_tx_enable(bool enable) {
GDMA_CFG_ST en_cfg;
set_spi_config_register(SPI_TX_EN, enable ? 1 : 0);
set_spi_config_register(SPI_TX_EN, enable);
en_cfg.channel = SPI_TX_DMA_CHANNEL;
en_cfg.param = enable ? 1 : 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg);
@@ -110,13 +110,13 @@ static void spi_set_clock(uint32_t max_hz) {
param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
param |= (div << SPI_CKR_POSI);
REG_WRITE(SPI_CTRL, param);
ESP_LOGD(TAG, "target frequency: %d, actual frequency: %d", max_hz, source_clk / 2 / div);
ESP_LOGD(TAG, "target frequency: %" PRIu32 ", actual frequency: %d", max_hz, source_clk / 2 / div);
}
void spi_dma_tx_finish_callback(unsigned int param) {
spi_data->tx_in_progress = false;
xSemaphoreGive(spi_data->dma_tx_semaphore);
spi_dma_tx_enable(0);
spi_dma_tx_enable(false);
}
void BekenSPILEDStripLightOutput::setup() {
@@ -161,7 +161,7 @@ void BekenSPILEDStripLightOutput::setup() {
return;
}
spi_data = (spi_data_t *) calloc(1, sizeof(spi_data_t));
spi_data = (SpiData *) calloc(1, sizeof(SpiData)); // NOLINT(cppcoreguidelines-no-malloc)
if (spi_data == nullptr) {
ESP_LOGE(TAG, "Cannot allocate spi_data!");
this->mark_failed();
@@ -177,20 +177,20 @@ void BekenSPILEDStripLightOutput::setup() {
spi_data->first_run = true;
set_spi_ctrl_register(MSTEN, 0);
set_spi_ctrl_register(BIT_WDTH, 0);
set_spi_ctrl_register(MSTEN, false);
set_spi_ctrl_register(BIT_WDTH, false);
spi_set_clock(this->spi_frequency_);
set_spi_ctrl_register(CKPOL, 0);
set_spi_ctrl_register(CKPHA, 0);
set_spi_ctrl_register(MSTEN, 1);
set_spi_ctrl_register(SPIEN, 1);
set_spi_ctrl_register(CKPOL, false);
set_spi_ctrl_register(CKPHA, false);
set_spi_ctrl_register(MSTEN, true);
set_spi_ctrl_register(SPIEN, true);
set_spi_ctrl_register(TXINT_EN, 0);
set_spi_ctrl_register(RXINT_EN, 0);
set_spi_config_register(SPI_TX_FINISH_EN, 1);
set_spi_config_register(SPI_RX_FINISH_EN, 1);
set_spi_ctrl_register(RXOVR_EN, 0);
set_spi_ctrl_register(TXOVR_EN, 0);
set_spi_ctrl_register(TXINT_EN, false);
set_spi_ctrl_register(RXINT_EN, false);
set_spi_config_register(SPI_TX_FINISH_EN, true);
set_spi_config_register(SPI_RX_FINISH_EN, true);
set_spi_ctrl_register(RXOVR_EN, false);
set_spi_ctrl_register(TXOVR_EN, false);
value = REG_READ(SPI_CTRL);
value &= ~CTRL_NSSMD_3;
@@ -199,7 +199,7 @@ void BekenSPILEDStripLightOutput::setup() {
value = GFUNC_MODE_SPI_DMA;
sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value);
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, 0);
set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, false);
GDMA_CFG_ST en_cfg;
GDMACFG_TPYES_ST init_cfg;
@@ -210,7 +210,7 @@ void BekenSPILEDStripLightOutput::setup() {
init_cfg.dstptr_incr = 0;
init_cfg.srcptr_incr = 1;
init_cfg.src_start_addr = this->dma_buf_;
init_cfg.dst_start_addr = (void *) SPI_DAT; // SPI_DMA_REG4_TXFIFO
init_cfg.dst_start_addr = (void *) SPI_DAT; // NOLINT(performance-no-int-to-ptr) SPI_DMA_REG4_TXFIFO
init_cfg.channel = SPI_TX_DMA_CHANNEL;
init_cfg.prio = 0; // 10
init_cfg.u.type4.src_loop_start_addr = this->dma_buf_;
@@ -230,7 +230,7 @@ void BekenSPILEDStripLightOutput::setup() {
en_cfg.param = 0;
sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_SRCADDR_LOOP, &en_cfg);
spi_dma_tx_enable(0);
spi_dma_tx_enable(false);
value = REG_READ(SPI_CONFIG);
value &= ~(0xFFF << 8);
@@ -247,7 +247,8 @@ void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uin
void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
// protect from refreshing too often
uint32_t now = micros();
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
if (this->max_refresh_rate_.has_value() && *this->max_refresh_rate_ != 0 &&
(now - this->last_refresh_) < *this->max_refresh_rate_) {
// try again next loop iteration, so that this change won't get lost
this->schedule_show();
return;
@@ -293,7 +294,7 @@ void BekenSPILEDStripLightOutput::write_state(light::LightState *state) {
}
spi_data->first_run = false;
spi_dma_tx_enable(1);
spi_dma_tx_enable(true);
this->status_clear_warning();
}
@@ -376,7 +377,7 @@ void BekenSPILEDStripLightOutput::dump_config() {
" RGB Order: %s\n"
" Max refresh rate: %" PRIu32 "\n"
" Number of LEDs: %u",
rgb_order, *this->max_refresh_rate_, this->num_leds_);
rgb_order, this->max_refresh_rate_.value_or(0), this->num_leds_);
}
float BekenSPILEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
@@ -245,7 +245,9 @@ void BluetoothConnection::send_service_for_discovery_() {
service_resp.characteristics.init(total_char_count);
uint16_t char_offset = 0;
esp_gattc_char_elem_t char_result;
while (true) { // characteristics
// Bound by total_char_count: the vector is sized for it, and a malicious peripheral
// can make enumeration return more entries than the count query reported
while (char_offset < total_char_count) { // characteristics
uint16_t char_count = 1;
esp_gatt_status_t char_status =
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
@@ -287,7 +289,7 @@ void BluetoothConnection::send_service_for_discovery_() {
characteristic_resp.descriptors.init(total_desc_count);
uint16_t desc_offset = 0;
esp_gattc_descr_elem_t desc_result;
while (true) { // descriptors
while (desc_offset < total_desc_count) { // descriptors
uint16_t desc_count = 1;
esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr(
this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset);
@@ -1,7 +1,7 @@
import logging
import esphome.codegen as cg
from esphome.components import web_server_base, wifi
from esphome.components import web_server_base
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
@@ -101,9 +101,6 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], paren)
await cg.register_component(var, config)
cg.add_define("USE_CAPTIVE_PORTAL")
# The portal reads wifi scan results from the web server task; this makes the
# wifi component guard them with a lock on multi-threaded platforms.
wifi.request_wifi_scan_results_lock()
if config[CONF_COMPRESSION] == "gzip":
cg.add_define("USE_CAPTIVE_PORTAL_GZIP")
+136 -137
View File
@@ -7,146 +7,145 @@ namespace esphome::captive_portal {
#ifdef USE_CAPTIVE_PORTAL_GZIP
constexpr uint8_t INDEX_GZ[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7f,
0x05, 0x8f, 0x4d, 0x1b, 0xa9, 0xb1, 0xa8, 0x87, 0xd7, 0xde, 0x44, 0x96, 0x54, 0xa4, 0x7b, 0x2d, 0x5a, 0xa0, 0x69,
0x03, 0xec, 0x36, 0xf7, 0x21, 0x08, 0xb0, 0x34, 0x39, 0xb2, 0x98, 0xa5, 0x48, 0x1d, 0x49, 0xbf, 0x62, 0xf8, 0x7e,
0xfb, 0x81, 0x92, 0xec, 0xf5, 0x2e, 0x9a, 0x03, 0x0e, 0x86, 0x85, 0x19, 0xce, 0x7b, 0x38, 0x0f, 0x16, 0xff, 0xe0,
0x9a, 0xb9, 0x7d, 0x07, 0xa8, 0x71, 0xad, 0xac, 0x0a, 0xff, 0x45, 0x92, 0xaa, 0x55, 0x09, 0xaa, 0x2a, 0x1a, 0xa0,
0xbc, 0x2a, 0x5a, 0x70, 0x14, 0xb1, 0x86, 0x1a, 0x0b, 0xae, 0xfc, 0xeb, 0xee, 0x97, 0xe8, 0x75, 0x55, 0x48, 0xa1,
0x1e, 0x90, 0x01, 0x59, 0x0a, 0xa6, 0x15, 0x6a, 0x0c, 0xd4, 0x25, 0xa7, 0x8e, 0xe6, 0xa2, 0xa5, 0x2b, 0x18, 0x45,
0x14, 0x6d, 0xa1, 0xdc, 0x08, 0xd8, 0x76, 0xda, 0x38, 0xc4, 0xb4, 0x72, 0xa0, 0x5c, 0x89, 0xb7, 0x82, 0xbb, 0xa6,
0xe4, 0xb0, 0x11, 0x0c, 0xa2, 0x1e, 0x99, 0x08, 0x25, 0x9c, 0xa0, 0x32, 0xb2, 0x8c, 0x4a, 0x28, 0xd3, 0xc9, 0xda,
0x82, 0xe9, 0x11, 0xba, 0x94, 0x50, 0x2a, 0x8d, 0xab, 0xc2, 0x32, 0x23, 0x3a, 0x87, 0xbc, 0xab, 0x65, 0xab, 0xf9,
0x5a, 0x42, 0x15, 0xc7, 0xd4, 0x5a, 0x70, 0x36, 0x16, 0x8a, 0xc3, 0x8e, 0xd0, 0x6b, 0xb8, 0xa6, 0x2c, 0x4d, 0xc8,
0x67, 0xfb, 0x0d, 0xd7, 0x6c, 0xdd, 0x82, 0x72, 0x44, 0x6a, 0x46, 0x9d, 0xd0, 0x8a, 0x58, 0xa0, 0x86, 0x35, 0x65,
0x59, 0xe2, 0x1f, 0x2d, 0xdd, 0x00, 0xfe, 0xfe, 0xfb, 0xe0, 0xcc, 0xb4, 0x02, 0xf7, 0xb3, 0x04, 0x0f, 0xda, 0x9f,
0xf6, 0x77, 0x74, 0xf5, 0x07, 0x6d, 0x21, 0xc0, 0xd4, 0x0a, 0x0e, 0x38, 0xfc, 0x98, 0x7c, 0x22, 0xd6, 0xed, 0x25,
0x10, 0x2e, 0x6c, 0x27, 0xe9, 0xbe, 0xc4, 0x4b, 0xa9, 0xd9, 0x03, 0x0e, 0x17, 0xf5, 0x5a, 0x31, 0xaf, 0x1c, 0xe9,
0x00, 0xc2, 0x83, 0x04, 0x87, 0x5c, 0xf9, 0x8e, 0xba, 0x86, 0xb4, 0x74, 0x17, 0x0c, 0x80, 0x50, 0x41, 0xf6, 0x43,
0x00, 0xaf, 0xd2, 0x24, 0x09, 0x27, 0xfd, 0x27, 0x09, 0xe3, 0x34, 0x49, 0x16, 0x06, 0xdc, 0xda, 0x28, 0x44, 0x83,
0xfb, 0xa2, 0xa3, 0xae, 0x41, 0xbc, 0xc4, 0xef, 0xd2, 0x0c, 0xa5, 0x6f, 0x48, 0x36, 0xfb, 0x9d, 0x5c, 0xa3, 0x2b,
0x92, 0xcd, 0xd8, 0x75, 0x34, 0x43, 0xe9, 0x55, 0x34, 0x43, 0x59, 0x46, 0x66, 0x28, 0xf9, 0x82, 0x51, 0x2d, 0xa4,
0x2c, 0xb1, 0xd2, 0x0a, 0x30, 0xb2, 0xce, 0xe8, 0x07, 0x28, 0x31, 0x5b, 0x1b, 0x03, 0xca, 0xdd, 0x68, 0xa9, 0x0d,
0x8e, 0xab, 0x6f, 0xfe, 0x2f, 0x85, 0xce, 0x50, 0x65, 0x6b, 0x6d, 0xda, 0x12, 0xf7, 0xd9, 0x0f, 0x5e, 0x1c, 0xdc,
0x11, 0xf9, 0x4f, 0x78, 0x41, 0x8c, 0xb4, 0x11, 0x2b, 0xa1, 0x4a, 0xec, 0x35, 0xbe, 0xc6, 0x71, 0x75, 0x1f, 0x1e,
0xcf, 0xd1, 0x53, 0x1f, 0xfd, 0x18, 0x0f, 0x0f, 0x3e, 0xde, 0x17, 0x76, 0xb3, 0x42, 0xbb, 0x56, 0x2a, 0x5b, 0xe2,
0xc6, 0xb9, 0x2e, 0x8f, 0xe3, 0xed, 0x76, 0x4b, 0xb6, 0x53, 0xa2, 0xcd, 0x2a, 0xce, 0x92, 0x24, 0x89, 0xed, 0x66,
0x85, 0xd1, 0x50, 0x08, 0x38, 0xbb, 0xc2, 0xa8, 0x01, 0xb1, 0x6a, 0x5c, 0x0f, 0x57, 0x2f, 0x0e, 0x70, 0x2c, 0x3c,
0x47, 0x75, 0xff, 0xe9, 0xc2, 0x8a, 0xb9, 0xb0, 0x02, 0x3f, 0xd2, 0x00, 0x9f, 0xc2, 0x7c, 0xd9, 0x87, 0x79, 0x4d,
0x33, 0x94, 0xa1, 0xa4, 0xff, 0x65, 0x91, 0x87, 0x47, 0x2c, 0x7a, 0x86, 0xa1, 0x0b, 0xcc, 0x43, 0xed, 0x3c, 0x7a,
0x73, 0x96, 0x4d, 0xfd, 0xc9, 0x26, 0x4d, 0x1e, 0x0f, 0xbc, 0xc0, 0xaf, 0xf3, 0x4b, 0x3c, 0xca, 0x3e, 0x5c, 0x32,
0x78, 0x6b, 0x4d, 0xfa, 0x61, 0x4e, 0x67, 0x68, 0x36, 0x9e, 0xcc, 0x22, 0x0f, 0x9f, 0x31, 0x34, 0xdb, 0x64, 0x4d,
0xda, 0x46, 0xf3, 0x68, 0x46, 0xa7, 0x68, 0x3a, 0x3a, 0x32, 0x45, 0xd3, 0x4d, 0xd6, 0xcc, 0x3f, 0xcc, 0x2f, 0xcf,
0xa2, 0xe9, 0x97, 0x97, 0x71, 0x85, 0xc3, 0x1c, 0xe3, 0xc7, 0xc8, 0xf9, 0x65, 0xe4, 0xe4, 0xb3, 0x16, 0x2a, 0xc0,
0x38, 0x3c, 0xd6, 0xe0, 0x58, 0x13, 0xe0, 0x98, 0x69, 0x55, 0x8b, 0x15, 0xf9, 0x6c, 0xb5, 0xc2, 0x21, 0x71, 0x0d,
0xa8, 0xe0, 0x24, 0xea, 0x05, 0xa1, 0xa7, 0x04, 0xcf, 0x29, 0x2e, 0x3c, 0x9c, 0xeb, 0xdf, 0x09, 0x27, 0xa1, 0x74,
0xc4, 0x37, 0xec, 0xe4, 0x6f, 0xba, 0xe2, 0xa7, 0xfd, 0x6f, 0x3c, 0xc0, 0x2d, 0x65, 0x38, 0x24, 0x42, 0x29, 0x30,
0x77, 0xb0, 0x73, 0x25, 0x7e, 0xf7, 0xf6, 0x06, 0xbd, 0xe5, 0xdc, 0x80, 0xb5, 0x39, 0xc2, 0xaf, 0x1c, 0x69, 0x29,
0xfb, 0xba, 0x78, 0x93, 0x3e, 0x95, 0xfe, 0x97, 0xf8, 0x45, 0xa0, 0x3f, 0xc0, 0x6d, 0xb5, 0x79, 0x18, 0xe5, 0xbd,
0xfd, 0x85, 0x6f, 0x23, 0x56, 0x7e, 0x55, 0x8d, 0x02, 0x87, 0xc3, 0x89, 0xf8, 0x3a, 0x83, 0xb5, 0x82, 0xe3, 0x70,
0x22, 0xbf, 0xce, 0xd1, 0x59, 0xdf, 0xbc, 0x8e, 0xd0, 0xce, 0x12, 0x2b, 0x05, 0x83, 0x20, 0x0d, 0x49, 0xad, 0xcd,
0xcf, 0x94, 0x35, 0x8f, 0x09, 0xb2, 0x43, 0x47, 0xab, 0x47, 0x3d, 0xcc, 0x00, 0x75, 0x30, 0xaa, 0x0a, 0x30, 0x17,
0x1b, 0x1c, 0x2e, 0x14, 0x61, 0x92, 0x5a, 0xeb, 0x47, 0x46, 0xe9, 0x9d, 0xf3, 0xe1, 0xe0, 0x89, 0x1a, 0x22, 0xfd,
0xf5, 0xee, 0xdd, 0xef, 0xe5, 0x7d, 0x41, 0x87, 0x01, 0x89, 0xbf, 0xc5, 0xa8, 0x67, 0x3e, 0x33, 0x46, 0x12, 0x6a,
0xe7, 0x2b, 0x5e, 0x07, 0x96, 0x18, 0x6b, 0x45, 0x78, 0x2c, 0x6c, 0x47, 0xd5, 0x73, 0xb6, 0x3e, 0xa6, 0xaa, 0x88,
0x3d, 0xad, 0x2a, 0x62, 0x5a, 0xbd, 0x38, 0x98, 0xc0, 0xfa, 0xe1, 0xf6, 0x10, 0x1e, 0xef, 0x27, 0x8a, 0xfc, 0x7b,
0x0d, 0x66, 0x7f, 0x0b, 0x12, 0x98, 0xd3, 0x26, 0xc0, 0xe4, 0x89, 0x60, 0x48, 0x1c, 0xec, 0xdc, 0xcd, 0x38, 0x7f,
0x2d, 0xf1, 0x87, 0x13, 0x45, 0xb4, 0x62, 0x52, 0xb0, 0x87, 0xf2, 0x1c, 0x71, 0x78, 0x10, 0x64, 0x43, 0xe5, 0x1a,
0x4e, 0x3c, 0x92, 0xd4, 0x9a, 0xad, 0x6d, 0x10, 0x1e, 0x27, 0x8c, 0xd0, 0xae, 0x03, 0xc5, 0x6f, 0x1a, 0x21, 0x79,
0xa0, 0xc2, 0x63, 0xf8, 0x78, 0xd3, 0xcf, 0x8c, 0xfb, 0xd5, 0xf0, 0xd1, 0x80, 0xfc, 0x4f, 0xf9, 0xd2, 0x2f, 0x87,
0x97, 0x9f, 0x70, 0x48, 0xfa, 0xf8, 0xef, 0x1f, 0x37, 0x84, 0x6f, 0xef, 0x57, 0xbb, 0x56, 0x4e, 0x7c, 0xe8, 0xd1,
0x7c, 0x16, 0x1e, 0xef, 0x8f, 0xe1, 0x31, 0x5c, 0x14, 0xf1, 0x30, 0xe7, 0xab, 0xa2, 0x1f, 0xb9, 0xd5, 0x0f, 0x87,
0xa5, 0xde, 0x45, 0x56, 0x7c, 0x11, 0x6a, 0x95, 0x0b, 0xd5, 0x80, 0x11, 0xee, 0xc8, 0xc5, 0x66, 0x22, 0x54, 0xb7,
0x76, 0x87, 0x8e, 0x72, 0xee, 0x29, 0xb3, 0x6e, 0xb7, 0xa8, 0xb5, 0x72, 0x9e, 0x13, 0xf2, 0x14, 0xda, 0xe3, 0x40,
0xef, 0x27, 0x4c, 0xfe, 0x66, 0xf6, 0xdd, 0x71, 0xa9, 0xf9, 0xfe, 0xe0, 0xd3, 0x10, 0x51, 0x29, 0x56, 0x2a, 0x67,
0xa0, 0x1c, 0x98, 0x41, 0xa8, 0xa6, 0xad, 0x90, 0xfb, 0xdc, 0x52, 0x65, 0x23, 0x0b, 0x46, 0xd4, 0xc7, 0xe5, 0xda,
0x39, 0xad, 0x0e, 0x4b, 0x6d, 0x38, 0x98, 0x3c, 0x59, 0x0c, 0x40, 0x64, 0x28, 0x17, 0x6b, 0x9b, 0x93, 0xa9, 0x81,
0x76, 0xb1, 0xa4, 0xec, 0x61, 0x65, 0xf4, 0x5a, 0xf1, 0x88, 0xf9, 0xc9, 0x9b, 0x7f, 0x9b, 0xd6, 0x74, 0x0a, 0x6c,
0x31, 0x62, 0x75, 0x5d, 0x2f, 0xa4, 0x50, 0x10, 0x0d, 0xb3, 0x2d, 0xcf, 0xc8, 0x95, 0x17, 0xbb, 0x70, 0x93, 0x64,
0xfe, 0x60, 0xf0, 0x31, 0x4d, 0x92, 0xef, 0x16, 0xa7, 0x70, 0x92, 0x05, 0x5b, 0x1b, 0xab, 0x4d, 0xde, 0x69, 0xe1,
0xdd, 0x3c, 0xb6, 0x54, 0xa8, 0x4b, 0xef, 0x7d, 0xd9, 0x2c, 0xc6, 0x75, 0x94, 0x0b, 0xd5, 0x9b, 0xe9, 0x97, 0xd2,
0xa2, 0x15, 0x6a, 0xd8, 0xa9, 0x79, 0x36, 0x4f, 0xba, 0xdd, 0xf1, 0x54, 0x09, 0x87, 0x13, 0x77, 0x2d, 0x61, 0xb7,
0xf8, 0xbc, 0xb6, 0x4e, 0xd4, 0xfb, 0x68, 0xdc, 0xc9, 0xb9, 0xed, 0x28, 0x83, 0x68, 0x09, 0x6e, 0x0b, 0xa0, 0x16,
0xbd, 0x8d, 0x48, 0x38, 0x68, 0xed, 0x98, 0xa7, 0xb3, 0x9a, 0xbe, 0x60, 0x9f, 0xea, 0xfa, 0x5f, 0xdc, 0xbe, 0x8a,
0x0e, 0x2d, 0x35, 0x2b, 0xa1, 0xa2, 0xa5, 0x76, 0x4e, 0xb7, 0x79, 0x74, 0xdd, 0xed, 0x16, 0xe3, 0x91, 0x57, 0x96,
0xa7, 0xde, 0xcd, 0x7e, 0xd7, 0x9e, 0xf2, 0x9d, 0x76, 0x3b, 0x64, 0xb5, 0x14, 0x7c, 0xe4, 0xeb, 0x59, 0x50, 0x72,
0x4e, 0x4f, 0x3a, 0xeb, 0x76, 0xc8, 0x9f, 0x9d, 0x52, 0x7d, 0x55, 0xbf, 0xa6, 0x69, 0xf2, 0x37, 0x37, 0xc2, 0xeb,
0x3a, 0x5b, 0xd6, 0xe7, 0x4c, 0xf9, 0xb5, 0xe9, 0x57, 0x4b, 0x5f, 0x5a, 0x45, 0x3c, 0xbc, 0x6e, 0x7c, 0x65, 0x54,
0x85, 0xcf, 0x70, 0x55, 0x34, 0x29, 0x12, 0xbc, 0x6c, 0x29, 0xab, 0x2e, 0x66, 0x5b, 0x11, 0x37, 0xe9, 0x89, 0xd4,
0xa4, 0xd5, 0x93, 0xb9, 0x35, 0xd0, 0x7a, 0xef, 0xab, 0x1b, 0xad, 0x14, 0x30, 0x27, 0xd4, 0x0a, 0x39, 0x8d, 0xc6,
0x14, 0x10, 0x42, 0x8a, 0xa5, 0xa9, 0xde, 0x4b, 0xa0, 0x16, 0xd0, 0x96, 0x0a, 0x47, 0x8a, 0x78, 0xe0, 0x1f, 0x3a,
0x5d, 0xf0, 0x52, 0x81, 0x3b, 0xf7, 0x76, 0x33, 0x1d, 0x0c, 0xdc, 0x82, 0xf3, 0x9a, 0xbc, 0x81, 0x69, 0x55, 0xf8,
0x15, 0x8c, 0x68, 0xdf, 0xa5, 0x65, 0xbc, 0x15, 0xb5, 0xf0, 0x4f, 0x98, 0xaa, 0xe8, 0x8b, 0xdc, 0x6b, 0xf0, 0x79,
0x1e, 0x9e, 0x5b, 0x3d, 0x24, 0x41, 0xad, 0x5c, 0x53, 0x4e, 0x33, 0xd4, 0x49, 0xca, 0xa0, 0xd1, 0x92, 0x83, 0x29,
0x6f, 0x6f, 0x7f, 0xfb, 0x67, 0xe5, 0x9d, 0x79, 0x94, 0xeb, 0xec, 0xc3, 0x20, 0xe6, 0x81, 0x51, 0x6a, 0x7e, 0x35,
0x3c, 0xb2, 0x3a, 0x6a, 0xed, 0x56, 0x1b, 0xfe, 0x44, 0xc7, 0xfb, 0xf1, 0x70, 0xd0, 0xd3, 0xff, 0xfb, 0x56, 0xa9,
0x6e, 0xe9, 0x06, 0x8a, 0x78, 0x44, 0x8a, 0xd8, 0x3b, 0x3c, 0xd0, 0x9b, 0x91, 0xaf, 0x49, 0xab, 0x3f, 0xef, 0xde,
0xa2, 0xbf, 0x3a, 0x4e, 0x1d, 0x0c, 0x69, 0xeb, 0xa3, 0x6a, 0xc1, 0x35, 0x9a, 0x97, 0xef, 0xff, 0xbc, 0xbd, 0x3b,
0x47, 0xb8, 0xee, 0x99, 0x10, 0x28, 0x36, 0x3c, 0xf7, 0xd6, 0xd2, 0x89, 0x8e, 0x1a, 0xd7, 0xab, 0x8d, 0xfc, 0x14,
0x39, 0xc5, 0xd0, 0xd3, 0x6b, 0x21, 0x61, 0x08, 0x63, 0x10, 0xac, 0xd0, 0xc9, 0xab, 0x93, 0xb5, 0x67, 0x7e, 0xc5,
0xc3, 0x6d, 0xc7, 0xc3, 0xd5, 0xc7, 0xfd, 0xcb, 0xf7, 0xbf, 0x81, 0xdb, 0x13, 0xb5, 0x09, 0x0b, 0x00, 0x00};
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e,
0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36,
0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf,
0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a,
0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68,
0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5,
0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22,
0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52,
0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06,
0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a,
0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0,
0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84,
0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7,
0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05,
0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6,
0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0,
0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7,
0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b,
0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e,
0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34,
0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b,
0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1,
0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37,
0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac,
0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3,
0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68,
0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc,
0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c,
0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93,
0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c,
0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18,
0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06,
0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c,
0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef,
0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2,
0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9,
0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8,
0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc,
0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca,
0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f,
0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0,
0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f,
0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c,
0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d,
0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf,
0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d,
0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6,
0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5,
0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b,
0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3,
0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69,
0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95,
0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9,
0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e,
0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62,
0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7,
0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97,
0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee,
0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11,
0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b,
0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9,
0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93,
0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97,
0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19,
0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc,
0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2,
0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc,
0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e,
0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e,
0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9,
0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3,
0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5,
0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37,
0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f,
0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22,
0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68,
0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
#else // Brotli (default, smaller)
constexpr uint8_t INDEX_BR[] PROGMEM = {
0x1b, 0x08, 0x0b, 0x00, 0xe4, 0x7f, 0x9b, 0xad, 0xbb, 0x97, 0x53, 0xde, 0xb7, 0x25, 0x0e, 0x69, 0xd4, 0x69, 0x89,
0xba, 0xa5, 0x55, 0x22, 0x04, 0x27, 0xeb, 0x00, 0x52, 0xac, 0xbc, 0xec, 0x5f, 0xfb, 0xb5, 0x7a, 0x12, 0x0a, 0xd6,
0x48, 0x84, 0x4a, 0x48, 0x5a, 0xcb, 0xbd, 0xb7, 0x72, 0x66, 0x4e, 0x62, 0xd8, 0xbd, 0x7f, 0x88, 0x68, 0x86, 0x28,
0xd6, 0xf1, 0xda, 0x2c, 0xa2, 0x32, 0x5c, 0xdd, 0xab, 0xb2, 0x15, 0xbc, 0x24, 0x13, 0xe6, 0xbd, 0x0c, 0x52, 0x63,
0x82, 0x88, 0x6d, 0x74, 0x71, 0x51, 0x9c, 0xe2, 0x40, 0x56, 0xea, 0xb0, 0x5c, 0xb7, 0x1d, 0x81, 0x3a, 0x0c, 0xf2,
0xd7, 0xa8, 0x5c, 0x35, 0x2d, 0x43, 0xb3, 0x42, 0x37, 0x7c, 0x60, 0xd8, 0xc1, 0x95, 0x91, 0x94, 0x3c, 0x29, 0x20,
0x96, 0x20, 0xf6, 0x24, 0xb7, 0x83, 0x4a, 0x06, 0xf1, 0xc3, 0x2f, 0x88, 0x50, 0x55, 0x35, 0x2d, 0xe8, 0xb6, 0x21,
0x38, 0x61, 0x8e, 0x44, 0x2a, 0xac, 0x39, 0xcf, 0x74, 0xaa, 0x31, 0x7f, 0x62, 0x32, 0x9b, 0x99, 0x42, 0x0a, 0xf6,
0x6f, 0xd8, 0x89, 0x3f, 0x49, 0xb1, 0xa2, 0x52, 0x0a, 0x8e, 0xb3, 0x27, 0x0b, 0x07, 0x07, 0x58, 0xb0, 0x8f, 0xaa,
0xdc, 0x68, 0x12, 0x0f, 0x95, 0xbf, 0xc7, 0x36, 0xd5, 0x60, 0x5a, 0xc7, 0x80, 0xac, 0x52, 0xf7, 0xa7, 0x2d, 0xb6,
0x64, 0xda, 0xda, 0x11, 0x8d, 0x6a, 0xe5, 0xba, 0xe9, 0xda, 0xdc, 0x62, 0xc3, 0xcb, 0xae, 0xc1, 0xe1, 0x11, 0xb6,
0x33, 0x29, 0x04, 0x09, 0xfe, 0x09, 0x08, 0xc2, 0x1f, 0x98, 0x16, 0x26, 0x0d, 0xce, 0xd7, 0x75, 0xfb, 0xd7, 0xa5,
0x82, 0xd7, 0x32, 0x44, 0x72, 0xc1, 0xc2, 0xe4, 0x15, 0xcb, 0x50, 0xfc, 0xd3, 0x58, 0x64, 0x34, 0x41, 0x32, 0xbe,
0x1f, 0x08, 0x43, 0x16, 0x14, 0xf7, 0x70, 0x2c, 0x1c, 0x61, 0x09, 0x78, 0x73, 0xd1, 0x30, 0xf6, 0xed, 0x85, 0x55,
0x50, 0x02, 0xf0, 0x68, 0x50, 0x5c, 0x1a, 0xd7, 0x3b, 0x8e, 0xf2, 0x23, 0xc8, 0x88, 0x39, 0xd0, 0x84, 0xf7, 0xa6,
0xd1, 0xa3, 0xfb, 0xe5, 0x44, 0xc0, 0x4c, 0x2f, 0x6f, 0x19, 0x70, 0x75, 0xf6, 0x1c, 0xb8, 0xce, 0x89, 0x4f, 0x01,
0x8d, 0xa1, 0x71, 0xf2, 0x97, 0xf8, 0xe7, 0xd3, 0xf1, 0xe1, 0xfa, 0xfc, 0xc8, 0x03, 0x78, 0x14, 0xa0, 0x3d, 0x28,
0xb7, 0xeb, 0x26, 0x52, 0x59, 0xa8, 0xb5, 0x0d, 0x5c, 0x8a, 0x6b, 0xe5, 0xf6, 0x30, 0xd6, 0xf7, 0x71, 0x31, 0xe8,
0xbd, 0xc9, 0xfa, 0xf5, 0x71, 0x9d, 0xff, 0xf6, 0x69, 0x62, 0x5f, 0xb5, 0xc7, 0x06, 0x43, 0x54, 0xb5, 0x87, 0xf1,
0xcc, 0x84, 0xe8, 0xb7, 0x56, 0x38, 0x43, 0x6a, 0xa6, 0x2f, 0x53, 0xd1, 0x89, 0x48, 0x8d, 0x72, 0x75, 0x4a, 0x17,
0xf6, 0x8d, 0xb2, 0xeb, 0xb1, 0x6b, 0x29, 0x1e, 0xd5, 0x74, 0xc7, 0xb3, 0xf4, 0xf1, 0x04, 0x0d, 0xbf, 0x08, 0x81,
0x8f, 0xfe, 0x8d, 0xfc, 0xf2, 0x35, 0x92, 0xa0, 0x24, 0x88, 0x12, 0x6a, 0xe6, 0x2f, 0x01, 0x94, 0x5c, 0x4b, 0xf9,
0x6b, 0x9a, 0xf6, 0x1a, 0x35, 0x11, 0x8a, 0xc6, 0x04, 0x8d, 0x50, 0x64, 0x48, 0x2d, 0x8b, 0xdf, 0xdf, 0xa1, 0xd1,
0xfd, 0x21, 0xd7, 0x40, 0x96, 0x00, 0xb1, 0x9f, 0x54, 0xc2, 0xe1, 0x00, 0x79, 0x15, 0x80, 0xf8, 0xca, 0x8e, 0xc5,
0x06, 0x03, 0xdf, 0x72, 0x49, 0xc0, 0x08, 0x27, 0x90, 0xe3, 0x14, 0xc2, 0xac, 0xc3, 0x83, 0xc9, 0xf6, 0x9d, 0x12,
0xab, 0x46, 0xae, 0x03, 0x74, 0x1d, 0x27, 0xce, 0x91, 0x1d, 0x4e, 0xb4, 0xbe, 0x80, 0xe7, 0x6a, 0x6d, 0x0a, 0x20,
0x47, 0x6b, 0x00, 0x4e, 0x25, 0x52, 0xe6, 0xf5, 0xe9, 0x43, 0x84, 0xc1, 0x0d, 0x4b, 0x04, 0xb3, 0x91, 0x49, 0xf5,
0xe9, 0xc4, 0x2e, 0x1b, 0xf9, 0x98, 0xf9, 0xea, 0x9e, 0xb8, 0xf6, 0x53, 0xf8, 0x42, 0xc2, 0x60, 0x5c, 0xa9, 0xd2,
0xfe, 0x0a, 0xe5, 0xd4, 0x7d, 0x36, 0x76, 0x04, 0x12, 0x38, 0xa1, 0xc4, 0x30, 0xb8, 0xf2, 0x69, 0x86, 0x5b, 0xe7,
0xe5, 0xa0, 0xc0, 0xfe, 0x91, 0x19, 0x03, 0x1e, 0xa9, 0x93, 0x26, 0x49, 0x58, 0xd5, 0xf6, 0x33, 0x4e, 0x0a, 0x89,
0x64, 0x1e, 0xb4, 0x7a, 0x5d, 0x0d, 0x12, 0xcf, 0x2b, 0xdd, 0x35, 0x90, 0x55, 0xc3, 0x0e, 0xcd, 0x0e, 0xad, 0x6b,
0x8f, 0x43, 0xd0, 0xb4, 0x6a, 0xdb, 0x4b, 0xe5, 0xa4, 0x9b, 0x09, 0x23, 0x1a, 0x43, 0xa9, 0xff, 0x61, 0x8b, 0x07,
0xd6, 0x0f, 0x83, 0x23, 0xbe, 0x8a, 0x66, 0xa2, 0x10, 0x2f, 0x11, 0x01, 0x0d, 0xc6, 0x96, 0xba, 0x87, 0xaa, 0xa8,
0x44, 0x7c, 0x1e, 0x34, 0xdb, 0xba, 0x41, 0x90, 0xf0, 0x7a, 0xdb, 0x63, 0x60, 0x96, 0x8d, 0x64, 0xcb, 0x2f, 0x28,
0x96, 0x04, 0xd5, 0xc0, 0x7a, 0xe8, 0xec, 0xd1, 0x61, 0x1b, 0x09, 0x4b, 0x4c, 0x6e, 0x1b, 0x31, 0x98, 0x9c, 0x6d,
0xdb, 0xd0, 0xec, 0xa4, 0x0f, 0x0a, 0xe0, 0xc8, 0x35, 0xc4, 0x93, 0xdc, 0xee, 0x19, 0x00, 0x80, 0x07, 0xf5, 0xcf,
0xe0, 0x7f, 0x75, 0xf8, 0x52, 0x3a, 0xfc, 0x0d, 0x84, 0xa5, 0xc1, 0xb2, 0x1c, 0x25, 0x40, 0xc5, 0x8b, 0xb3, 0xdb,
0x7a, 0x1b, 0x44, 0xff, 0x79, 0x9a, 0x26, 0xc4, 0xe7, 0x9e, 0x78, 0x4c, 0xa5, 0x94, 0xab, 0x78, 0x34, 0x9d, 0xb5,
0xb7, 0x24, 0x26, 0xe7, 0x9a, 0xf3, 0xe5, 0x2a, 0x35, 0x4b, 0xbe, 0x74, 0xd7, 0x01, 0xbc, 0x71, 0x72, 0x03, 0x5c,
0x40, 0x24, 0x17, 0x61, 0x5b, 0x7b, 0x19, 0xc2, 0x7f, 0x4e, 0x5b, 0x24, 0xfb, 0x8b, 0xc3, 0x51, 0x00, 0x3d, 0x09,
0x04, 0x05, 0x72, 0x64, 0xf6, 0xf0, 0x6b, 0x99, 0x38, 0x93, 0x5b, 0xac, 0x0c, 0xff, 0x40, 0x7b, 0x53, 0xba, 0xab,
0x61, 0xc9, 0xa2, 0xde, 0xd6, 0x2b, 0x0c, 0xbd, 0x85, 0xac, 0x4c, 0x64, 0x8b, 0xe6, 0x69, 0x2b, 0x56, 0x10, 0x1b,
0x08, 0x59, 0x6c, 0x55, 0x0a, 0xaa, 0x93, 0x85, 0x1d, 0x45, 0xda, 0xc6, 0x39, 0xe6, 0x6a, 0xab, 0x71, 0x73, 0x46,
0x0f, 0xf7, 0xab, 0x4e, 0x88, 0xae, 0x8c, 0xe0, 0x91, 0xda, 0x35, 0x8c, 0xd3, 0x18, 0x92, 0x3d, 0xab, 0x2f, 0x0d,
0xd6, 0xc9, 0x06, 0xdb, 0xc2, 0x62, 0xcb, 0x8a, 0x23, 0x5b, 0x28, 0x2e, 0x9b, 0x96, 0xc4, 0xc1, 0x42, 0xa9, 0x8d,
0x69, 0xe5, 0x8f, 0x89, 0x12, 0x11, 0x9a, 0x56, 0x3b, 0x3c, 0x2b, 0xb4, 0xb2, 0x7b, 0xfd, 0xdb, 0x80, 0x92, 0xb4,
0xca, 0x44, 0x37, 0x8c, 0x94, 0x2f, 0x4a, 0xb4, 0xc4, 0x28, 0x83, 0x8a, 0x78, 0xcb, 0xd2, 0x5c, 0x5c, 0x35, 0x29,
0x95, 0x35, 0x2f, 0x99, 0x28, 0x4f, 0x22, 0x18, 0x70, 0x85, 0xee, 0x2c, 0xb9, 0xef, 0x14, 0x57, 0x73, 0x23, 0x45,
0xae, 0xdc, 0x54, 0x56, 0x55, 0x78, 0x56, 0xad, 0x48, 0x26, 0x27, 0xbf, 0xcb, 0xd7, 0x54, 0xa9, 0xa8, 0xd7, 0xb5,
0x71, 0x9c, 0xe7, 0x79, 0x89, 0x5c, 0xa9, 0x6a, 0x53, 0xe8, 0xfa, 0x0d, 0x69, 0x36, 0xed, 0xad, 0x33, 0xc9, 0x75,
0x17, 0xe9, 0x8f, 0x31, 0x58, 0xa6, 0x27, 0xfc, 0x79, 0xc6, 0xb6, 0xd5, 0x65, 0x90, 0x31, 0xc6, 0x9d, 0x29, 0x95,
0x83, 0xe5, 0x4e, 0xbb, 0xd7, 0xdc, 0x8e, 0xd0, 0x3a, 0x54, 0x27, 0xce, 0xf5, 0xdb, 0xbd, 0x89, 0x3c, 0x61, 0xb3,
0x71, 0x23, 0xc7, 0x55, 0x9e, 0xea, 0x50, 0xe4, 0x37, 0xae, 0x72, 0x34, 0x66, 0xb9, 0x6e, 0x2c, 0x0a, 0x69, 0x8d,
0x94, 0x8b, 0x98, 0x08, 0x05, 0x3c, 0x45, 0x45, 0xe1, 0x6c, 0x22, 0xc5, 0x8f, 0x1f, 0x9f, 0x3f, 0xd2, 0x01, 0x12,
0xec, 0x86, 0x2f, 0x87, 0x8b, 0x47, 0x30, 0xd0, 0x77, 0x77, 0x1a, 0x13, 0x2d, 0xc6, 0x31, 0x65, 0x77, 0xd8, 0x8c,
0xe7, 0xe0, 0x16, 0xf9, 0x4b, 0xd4, 0xc5, 0xa8, 0x67, 0x79, 0xe7, 0xc3, 0x07, 0x94, 0x46, 0xa3, 0x8c, 0x67, 0xd3,
0xff, 0x5f, 0x96, 0xfa, 0xed, 0xa7, 0xd3, 0xdd, 0x4f, 0x27, 0x49, 0x27, 0xcf, 0xa4, 0x02, 0xc3, 0x27, 0x3c, 0x96,
0x64, 0x24, 0x55, 0xc8, 0x36, 0x45, 0x68, 0x90, 0xea, 0x03, 0x0b, 0x46, 0xa7, 0x8d, 0xb4, 0x26, 0x91, 0x07, 0x4c,
0x80, 0x7b, 0x93, 0xa8, 0x10, 0xcb, 0x5e, 0x8d, 0x42, 0x86, 0x3e, 0x2a, 0x7c, 0x99, 0x79, 0x8e, 0xcb, 0x43, 0x28};
0x1b, 0xf8, 0x0a, 0x00, 0x64, 0x5a, 0xd3, 0xfa, 0xe7, 0xf3, 0x62, 0xd8, 0x06, 0x1b, 0xe9, 0x6a, 0x8a, 0x81, 0x2b,
0xb5, 0x49, 0x14, 0x37, 0xdc, 0x9e, 0x1a, 0xcb, 0x56, 0x87, 0xfb, 0xff, 0xf7, 0x73, 0x75, 0x12, 0x0a, 0xd6, 0x48,
0x84, 0xc6, 0x21, 0xa4, 0x6d, 0xb5, 0x71, 0xef, 0x13, 0xbe, 0x4e, 0x54, 0xf1, 0x64, 0x8f, 0x3f, 0xcc, 0x9a, 0x78,
0xa5, 0x89, 0x25, 0xb3, 0xda, 0x2c, 0xa2, 0x32, 0x9c, 0x57, 0x07, 0x56, 0xbc, 0x34, 0x13, 0xff, 0x5c, 0x0a, 0xa1,
0x67, 0x82, 0xb8, 0x6b, 0x4c, 0x76, 0x31, 0x6c, 0xe3, 0x40, 0x46, 0xea, 0xb0, 0xd4, 0xf4, 0x3b, 0x02, 0x65, 0x18,
0xa4, 0xaf, 0xac, 0x6d, 0x55, 0xd6, 0xbe, 0x59, 0x66, 0x7a, 0x7c, 0x60, 0xb2, 0x83, 0x33, 0x23, 0xc9, 0x79, 0x82,
0x47, 0xb4, 0x28, 0xf4, 0x24, 0xb5, 0x23, 0x5a, 0x44, 0xe1, 0xc3, 0x27, 0x04, 0xe8, 0x0c, 0xdd, 0xb4, 0xd0, 0x8c,
0xfb, 0x10, 0x39, 0x93, 0x04, 0x2a, 0x66, 0x18, 0x4b, 0x74, 0xca, 0x31, 0x7f, 0xb2, 0xe5, 0x45, 0xc1, 0xdd, 0x72,
0x49, 0xff, 0x0e, 0xb3, 0xf0, 0x93, 0x18, 0xab, 0x68, 0xad, 0xe1, 0x9d, 0xe4, 0x29, 0xc0, 0xe3, 0x63, 0x54, 0x61,
0x1b, 0x45, 0xb9, 0x6c, 0x23, 0x0f, 0x99, 0x7f, 0x8e, 0x69, 0xaa, 0xc1, 0xb8, 0x4e, 0x42, 0x9c, 0xc5, 0x6e, 0x69,
0x40, 0x0e, 0x4f, 0x97, 0xd3, 0x23, 0x18, 0xf5, 0xc8, 0x75, 0x73, 0xb5, 0xbd, 0x46, 0x8a, 0x97, 0x7d, 0x83, 0xe4,
0x29, 0x72, 0x73, 0xc1, 0x39, 0x8e, 0x7e, 0x84, 0x39, 0x66, 0x57, 0xc6, 0x85, 0x19, 0x8b, 0xf2, 0x4d, 0xd9, 0xfe,
0x75, 0xa9, 0xe1, 0x2b, 0x21, 0x81, 0x58, 0x51, 0x99, 0xbc, 0xa4, 0x0b, 0x10, 0x6f, 0x86, 0x17, 0x0b, 0x92, 0x00,
0x11, 0x6f, 0x3b, 0xa4, 0xa4, 0x11, 0x7e, 0x0b, 0x97, 0x85, 0x23, 0x0c, 0x01, 0x6f, 0x2a, 0x18, 0xc6, 0xbe, 0x3d,
0x77, 0x1a, 0xe6, 0x00, 0x5c, 0x1a, 0x14, 0x47, 0xc6, 0xcc, 0xcc, 0x52, 0xbe, 0x04, 0x19, 0x31, 0x05, 0x46, 0xa0,
0xc3, 0x69, 0x0c, 0x60, 0xb7, 0x14, 0x57, 0xa0, 0x92, 0xbf, 0xb7, 0x0c, 0xd8, 0x3a, 0x79, 0x09, 0x99, 0xc9, 0x71,
0x88, 0x01, 0x8b, 0xa5, 0x61, 0x0a, 0xb5, 0xe8, 0xc7, 0x71, 0xe7, 0x70, 0x79, 0xb6, 0xe4, 0x01, 0xfc, 0x1a, 0x4a,
0x7b, 0x60, 0x6e, 0xef, 0x95, 0x62, 0x59, 0x28, 0xb5, 0x25, 0x56, 0x15, 0xe7, 0xca, 0xad, 0x32, 0xe6, 0xf7, 0x01,
0x31, 0x34, 0x87, 0x93, 0x0b, 0x9b, 0x9d, 0x26, 0xff, 0xe5, 0x92, 0xad, 0x6f, 0xb8, 0x3b, 0x16, 0xc1, 0xa0, 0x5a,
0x4f, 0x52, 0x0b, 0x2b, 0xc1, 0xa7, 0x95, 0x7b, 0x24, 0x51, 0xd3, 0xb3, 0x23, 0x62, 0x0b, 0xcc, 0xa0, 0x58, 0xa7,
0x64, 0x45, 0x2f, 0x0b, 0xdd, 0x1d, 0x97, 0x82, 0x1f, 0xcc, 0x64, 0xdb, 0xd3, 0xf4, 0xb0, 0x8b, 0xc8, 0xcf, 0x15,
0x81, 0x8b, 0xa1, 0x9d, 0xf8, 0xfc, 0xec, 0x49, 0x40, 0x12, 0x01, 0x09, 0x51, 0xf3, 0x73, 0x18, 0x24, 0x97, 0x55,
0x85, 0x6a, 0x92, 0x1a, 0xf5, 0x5a, 0x05, 0x54, 0x1f, 0x27, 0x0a, 0xa8, 0xa1, 0x94, 0x58, 0x78, 0x7d, 0x87, 0xa8,
0xdb, 0x13, 0x66, 0x20, 0x5e, 0x43, 0x18, 0x7a, 0xbb, 0x16, 0x16, 0x07, 0xc8, 0xab, 0x10, 0xe2, 0x50, 0xb9, 0xb1,
0xd8, 0x21, 0xc8, 0x4a, 0x2e, 0x99, 0x0e, 0x23, 0x52, 0xc6, 0xcb, 0x29, 0x84, 0x91, 0x03, 0xb1, 0xe2, 0x4c, 0x1d,
0x22, 0xd3, 0xc8, 0x79, 0x00, 0x8b, 0x8b, 0x88, 0x1e, 0x29, 0xd3, 0xae, 0x10, 0x15, 0x22, 0x6d, 0xb0, 0x87, 0x6f,
0x27, 0x2e, 0x7c, 0xc2, 0x7a, 0x61, 0xbd, 0x22, 0xe5, 0x5f, 0xdd, 0x7b, 0x00, 0x04, 0xf2, 0x7d, 0x5a, 0x03, 0x38,
0x1f, 0x69, 0x6d, 0x0b, 0xfb, 0xec, 0x45, 0xfe, 0x8b, 0x7f, 0xec, 0x7b, 0xad, 0xc2, 0x33, 0xf1, 0x9e, 0x9c, 0x71,
0xd9, 0xe8, 0x5e, 0x8f, 0xd4, 0xee, 0x87, 0x45, 0x6c, 0xe2, 0x12, 0xf8, 0xb8, 0xc5, 0xee, 0x43, 0xa6, 0x37, 0x91,
0xb5, 0x2c, 0x2f, 0xe9, 0xe8, 0x24, 0xd0, 0x45, 0xc1, 0x0c, 0x7c, 0xf0, 0xb2, 0xb5, 0x2d, 0x10, 0x36, 0x7e, 0x18,
0x7c, 0x79, 0x82, 0x69, 0x3d, 0x35, 0xca, 0x52, 0xee, 0xc9, 0xb5, 0x65, 0xa4, 0xa1, 0xfd, 0x70, 0x7e, 0xe0, 0x7d,
0x67, 0xf9, 0xa1, 0x71, 0xd2, 0x08, 0x74, 0x33, 0x5f, 0x69, 0xa4, 0x59, 0x03, 0xfd, 0xf8, 0xf0, 0x70, 0x1a, 0x50,
0x43, 0xfb, 0x61, 0xf0, 0x38, 0x18, 0x88, 0x85, 0x36, 0x23, 0x06, 0x4f, 0x02, 0xbb, 0x78, 0x1a, 0xaa, 0xd2, 0x02,
0x5e, 0xa0, 0x74, 0x30, 0xc8, 0x7a, 0x66, 0xab, 0xd9, 0x43, 0x99, 0x45, 0xb7, 0x0c, 0x5c, 0xec, 0xc8, 0x03, 0x0e,
0x0b, 0xca, 0x4a, 0x22, 0x48, 0xfb, 0xb7, 0x3d, 0x82, 0x07, 0x8d, 0x1b, 0x21, 0x87, 0x4d, 0x57, 0xa4, 0x5b, 0xd4,
0xe3, 0x88, 0x02, 0xc4, 0x81, 0xf9, 0x47, 0xe4, 0xbf, 0x3e, 0x39, 0xbb, 0x4f, 0x7e, 0x91, 0x63, 0x98, 0x97, 0xe4,
0x52, 0x01, 0x58, 0xba, 0x32, 0xbf, 0xae, 0xff, 0x45, 0xa1, 0xbc, 0x9b, 0xa4, 0x09, 0x0e, 0x79, 0xc0, 0x41, 0x86,
0x52, 0x88, 0x55, 0x39, 0x9d, 0xb6, 0xed, 0x35, 0x68, 0x29, 0xfa, 0xe6, 0x6c, 0x3d, 0x0a, 0xcd, 0x6a, 0x28, 0xfd,
0x65, 0x24, 0xce, 0x38, 0x98, 0x01, 0xd9, 0x3f, 0x1b, 0x4c, 0xc4, 0x5c, 0x1d, 0xaa, 0x21, 0x78, 0x67, 0xaf, 0x55,
0x72, 0x34, 0xf8, 0x1b, 0x03, 0x21, 0x27, 0x08, 0xbd, 0x59, 0x60, 0x48, 0x0d, 0xe2, 0x56, 0x9b, 0x30, 0x92, 0x8f,
0x67, 0x8a, 0x7f, 0x20, 0xbd, 0x2d, 0xfd, 0xc5, 0xb0, 0xa6, 0xaa, 0x77, 0x75, 0x26, 0x33, 0x2f, 0x20, 0x2a, 0xab,
0x5c, 0xd1, 0x3b, 0xda, 0xb2, 0x4c, 0xa4, 0x86, 0x25, 0x8d, 0x49, 0x05, 0xaf, 0x7a, 0xa8, 0xd4, 0x9c, 0x0d, 0xd3,
0x38, 0xa6, 0x5c, 0x29, 0x6b, 0x16, 0x27, 0x07, 0xf1, 0xbe, 0xe2, 0x24, 0xc1, 0x8d, 0x25, 0x76, 0xbc, 0xf6, 0x0d,
0xc2, 0x94, 0x25, 0xb8, 0xf3, 0x07, 0x9a, 0x49, 0xf4, 0x89, 0x82, 0x4d, 0x51, 0xb1, 0x96, 0x61, 0x62, 0x8d, 0xc8,
0x61, 0x65, 0x0d, 0x14, 0x34, 0x02, 0x65, 0x94, 0xcc, 0x1d, 0x85, 0x00, 0x0f, 0x1a, 0x57, 0x68, 0x15, 0xcf, 0xa4,
0xa2, 0x7d, 0x6d, 0x53, 0x60, 0xce, 0x5c, 0x61, 0x82, 0x17, 0x32, 0xc1, 0x87, 0x02, 0x0c, 0x91, 0x85, 0x57, 0x51,
0xbe, 0xb2, 0x38, 0x9f, 0x3d, 0x2a, 0x52, 0x5a, 0xad, 0xba, 0x46, 0x9e, 0x3c, 0x8a, 0xa0, 0x46, 0x15, 0xf4, 0x59,
0x74, 0x5f, 0x2a, 0xae, 0x96, 0x56, 0xf0, 0x54, 0x39, 0xaf, 0xac, 0x2a, 0xb9, 0xad, 0x32, 0x50, 0xc9, 0xc1, 0xee,
0xd2, 0x0d, 0x34, 0xaa, 0x98, 0x4d, 0x6d, 0x3d, 0xc6, 0xb9, 0x5b, 0x00, 0x5f, 0xea, 0xda, 0x16, 0xa6, 0x08, 0x43,
0x58, 0x4d, 0x8d, 0x07, 0x55, 0x62, 0x81, 0x44, 0xcc, 0x31, 0x04, 0x4b, 0x4c, 0x8b, 0x3e, 0xff, 0xd8, 0xf6, 0x65,
0x19, 0xa1, 0x94, 0x62, 0x65, 0x0a, 0xdd, 0x60, 0x38, 0xd3, 0xbe, 0x0d, 0xa3, 0x99, 0xd5, 0x37, 0x68, 0xa1, 0x71,
0xa3, 0x41, 0xe7, 0xbe, 0x9d, 0x72, 0x84, 0x75, 0xb6, 0x8d, 0x98, 0xd6, 0xb8, 0x2d, 0x43, 0x85, 0x5d, 0xf9, 0xca,
0xc3, 0x96, 0xa5, 0xa6, 0xe7, 0x50, 0x88, 0x6b, 0x84, 0x58, 0x44, 0x45, 0x20, 0xdf, 0x1e, 0x5a, 0xc9, 0xce, 0x42,
0x2a, 0x1f, 0x3e, 0x3c, 0x7b, 0x68, 0x3c, 0x34, 0x8b, 0x36, 0xba, 0x1f, 0xce, 0x0f, 0xa0, 0x60, 0x37, 0x5f, 0x1a,
0x03, 0x2b, 0x86, 0x29, 0x45, 0x7b, 0xb4, 0xb7, 0x06, 0x68, 0x17, 0x7e, 0x13, 0x76, 0x91, 0x4d, 0x27, 0xee, 0xbc,
0x7e, 0x80, 0xc2, 0x66, 0xac, 0xc6, 0xbf, 0xeb, 0x7f, 0xd7, 0x84, 0x79, 0xf3, 0xf1, 0xde, 0xec, 0xa6, 0x93, 0xa8,
0x13, 0x3b, 0x4a, 0x81, 0xfa, 0x11, 0x1e, 0x4a, 0xd2, 0x50, 0x2a, 0xea, 0x9a, 0xc2, 0x37, 0x08, 0xed, 0x01, 0xf5,
0xa2, 0xd5, 0x32, 0x29, 0x49, 0xc4, 0x1a, 0x11, 0xc0, 0xda, 0x24, 0x28, 0x84, 0x38, 0x60, 0x80, 0xcf, 0xd0, 0x45,
0x83, 0xa7, 0xca, 0x52, 0x5c, 0xac, 0x23, 0x01};
// Backwards compatibility alias
#define INDEX_GZ INDEX_BR
@@ -4,7 +4,6 @@
#include "esphome/core/application.h"
#include "esphome/components/wifi/wifi_component.h"
#include "captive_index.h"
#include "json_escape.h"
namespace esphome::captive_portal {
@@ -25,30 +24,23 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", mac_str, App.get_name().c_str());
#endif
// An SSID can contain a " or \ that would break the JSON, so escape it before writing it out. An SSID is at most
// 32 bytes (IEEE 802.11), so this is large enough that nothing is ever dropped. Reused for every scan result.
char escaped_ssid[32 * JSON_ESCAPE_MAX_EXPANSION + 1];
{
// Invariant: only bounded in-memory work under the lock; the network send
// happens later in request->send()
wifi::ScanResultsLock lock(wifi::global_wifi_component);
for (const auto &scan : wifi::global_wifi_component->get_scan_result()) {
if (scan.get_is_hidden())
continue;
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
if (scan.get_is_hidden())
continue;
json_escape_into_buffer(escaped_ssid, scan.get_ssid());
// Assumes no " in ssid, possible unicode isses?
#ifdef USE_ESP8266
stream->print(ESPHOME_F(",{\"ssid\":\""));
stream->print(escaped_ssid);
stream->print(ESPHOME_F("\",\"rssi\":"));
stream->print(scan.get_rssi());
stream->print(ESPHOME_F(",\"lock\":"));
stream->print(scan.get_with_auth());
stream->print(ESPHOME_F("}"));
stream->print(ESPHOME_F(",{\"ssid\":\""));
stream->print(scan.get_ssid().c_str());
stream->print(ESPHOME_F("\",\"rssi\":"));
stream->print(scan.get_rssi());
stream->print(ESPHOME_F(",\"lock\":"));
stream->print(scan.get_with_auth());
stream->print(ESPHOME_F("}"));
#else
stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", escaped_ssid, scan.get_rssi(), scan.get_with_auth());
stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(),
scan.get_with_auth());
#endif
}
}
stream->print(ESPHOME_F("]}"));
request->send(stream);
@@ -1,85 +0,0 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <span>
#include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
namespace esphome::captive_portal {
/// Largest number of output bytes a single input byte can expand to (a \u00XX sequence).
static constexpr size_t JSON_ESCAPE_MAX_EXPANSION = 6;
/// Copy value into buf, escaping the characters that cannot appear raw inside a JSON string literal.
///
/// Escapes " and \ along with the control characters below 0x20, using the short forms where JSON defines one and
/// \u00XX otherwise. Bytes >= 0x20 are copied verbatim, so text containing valid UTF-8 survives intact. The result is
/// always null terminated; anything that would not fit is dropped rather than written partially. Returns buf so the
/// call can be used directly as an argument.
///
/// To size buf so that no input is ever dropped, allow JSON_ESCAPE_MAX_EXPANSION bytes per input byte plus one for
/// the null terminator.
inline const char *json_escape_into_buffer(std::span<char> buf, StringRef value) {
if (buf.empty())
return "";
// Reserve one byte for the null terminator.
const size_t limit = buf.size() - 1;
size_t pos = 0;
for (char ch : value) {
auto c = static_cast<unsigned char>(ch);
// Every short form is a backslash followed by a single character, so only that character is needed here. Keeping
// it a char rather than a string avoids putting the sequences in read only data, which is RAM on the ESP8266.
char escape = '\0';
switch (c) {
case '"':
escape = '"';
break;
case '\\':
escape = '\\';
break;
case '\n':
escape = 'n';
break;
case '\r':
escape = 'r';
break;
case '\t':
escape = 't';
break;
case '\b':
escape = 'b';
break;
case '\f':
escape = 'f';
break;
default:
break;
}
if (escape != '\0') {
if (pos + 2 > limit)
break;
buf[pos++] = '\\';
buf[pos++] = escape;
} else if (c < 0x20) {
// Remaining control characters have no short form and must be written as \u00XX. The value is below 0x20, so
// the two high hex digits are always zero.
if (pos + JSON_ESCAPE_MAX_EXPANSION > limit)
break;
buf[pos++] = '\\';
buf[pos++] = 'u';
buf[pos++] = '0';
buf[pos++] = '0';
buf[pos++] = format_hex_char(static_cast<uint8_t>(c >> 4));
buf[pos++] = format_hex_char(static_cast<uint8_t>(c & 0x0F));
} else {
if (pos + 1 > limit)
break;
buf[pos++] = static_cast<char>(c);
}
}
buf[pos] = '\0';
return buf.data();
}
} // namespace esphome::captive_portal
+77
View File
@@ -21,6 +21,7 @@ MULTI_CONF = True
ns = cg.esphome_ns.namespace("cc1101")
CC1101Component = ns.class_("CC1101Component", cg.Component, spi.SPIDevice)
CC1101Listener = ns.class_("CC1101Listener")
# Config keys
CONF_RX_ATTENUATION = "rx_attenuation"
@@ -48,6 +49,15 @@ CONF_FILTER_LENGTH_FSK_MSK = "filter_length_fsk_msk"
CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook"
CONF_FREEZE = "freeze"
CONF_HYST_LEVEL = "hyst_level"
CONF_FOC_BS_CS_GATE = "foc_bs_cs_gate"
CONF_FOC_LIMIT = "foc_limit"
CONF_FOC_PRE_K = "foc_pre_k"
CONF_FOC_POST_K = "foc_post_k"
CONF_BS_LIMIT = "bs_limit"
CONF_BS_PRE_KI = "bs_pre_ki"
CONF_BS_PRE_KP = "bs_pre_kp"
CONF_BS_POST_KI = "bs_post_ki"
CONF_BS_POST_KP = "bs_post_kp"
# Packet mode config keys
CONF_PACKET_MODE = "packet_mode"
@@ -161,6 +171,64 @@ HYST_LEVEL = {
"High": HystLevel.HYST_LEVEL_HIGH,
}
FocLimit = ns.enum("FocLimit", True)
FOC_LIMIT = {
"Disabled": FocLimit.FOC_LIMIT_DISABLED,
"BW/8": FocLimit.FOC_LIMIT_BW_8,
"BW/4": FocLimit.FOC_LIMIT_BW_4,
"BW/2": FocLimit.FOC_LIMIT_BW_2,
}
FocPreK = ns.enum("FocPreK", True)
FOC_PRE_K = {
"K": FocPreK.FOC_PRE_K_K,
"2K": FocPreK.FOC_PRE_K_2K,
"3K": FocPreK.FOC_PRE_K_3K,
"4K": FocPreK.FOC_PRE_K_4K,
}
FocPostK = ns.enum("FocPostK", True)
FOC_POST_K = {
"Same": FocPostK.FOC_POST_K_SAME,
"K/2": FocPostK.FOC_POST_K_K_2,
}
BsLimit = ns.enum("BsLimit", True)
BS_LIMIT = {
"Disabled": BsLimit.BS_LIMIT_DISABLED,
"3.125%": BsLimit.BS_LIMIT_3P125_PERCENT,
"6.25%": BsLimit.BS_LIMIT_6P25_PERCENT,
"12.5%": BsLimit.BS_LIMIT_12P5_PERCENT,
}
BsPreKi = ns.enum("BsPreKi", True)
BS_PRE_KI = {
"KI": BsPreKi.BS_PRE_KI_KI,
"2KI": BsPreKi.BS_PRE_KI_2KI,
"3KI": BsPreKi.BS_PRE_KI_3KI,
"4KI": BsPreKi.BS_PRE_KI_4KI,
}
BsPreKp = ns.enum("BsPreKp", True)
BS_PRE_KP = {
"KP": BsPreKp.BS_PRE_KP_KP,
"2KP": BsPreKp.BS_PRE_KP_2KP,
"3KP": BsPreKp.BS_PRE_KP_3KP,
"4KP": BsPreKp.BS_PRE_KP_4KP,
}
BsPostKi = ns.enum("BsPostKi", True)
BS_POST_KI = {
"Same": BsPostKi.BS_POST_KI_SAME,
"KI/2": BsPostKi.BS_POST_KI_KI_2,
}
BsPostKp = ns.enum("BsPostKp", True)
BS_POST_KP = {
"Same": BsPostKp.BS_POST_KP_SAME,
"KP": BsPostKp.BS_POST_KP_KP,
}
# Optional settings to generate setter calls for
CONFIG_MAP = {
cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0),
@@ -214,6 +282,15 @@ CONFIG_MAP = {
cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False),
cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False),
cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False),
cv.Optional(CONF_FOC_BS_CS_GATE): cv.boolean,
cv.Optional(CONF_FOC_LIMIT): cv.enum(FOC_LIMIT, upper=False),
cv.Optional(CONF_FOC_PRE_K): cv.enum(FOC_PRE_K, upper=False),
cv.Optional(CONF_FOC_POST_K): cv.enum(FOC_POST_K, upper=False),
cv.Optional(CONF_BS_LIMIT): cv.enum(BS_LIMIT, upper=False),
cv.Optional(CONF_BS_PRE_KI): cv.enum(BS_PRE_KI, upper=False),
cv.Optional(CONF_BS_PRE_KP): cv.enum(BS_PRE_KP, upper=False),
cv.Optional(CONF_BS_POST_KI): cv.enum(BS_POST_KI, upper=False),
cv.Optional(CONF_BS_POST_KP): cv.enum(BS_POST_KP, upper=False),
cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean,
cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t,
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
+63
View File
@@ -672,6 +672,69 @@ void CC1101Component::set_hyst_level(HystLevel value) {
}
}
void CC1101Component::set_foc_bs_cs_gate(bool value) {
this->state_.FOC_BS_CS_GATE = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::FOCCFG);
}
}
void CC1101Component::set_foc_limit(FocLimit value) {
this->state_.FOC_LIMIT = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::FOCCFG);
}
}
void CC1101Component::set_foc_pre_k(FocPreK value) {
this->state_.FOC_PRE_K = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::FOCCFG);
}
}
void CC1101Component::set_foc_post_k(FocPostK value) {
this->state_.FOC_POST_K = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::FOCCFG);
}
}
void CC1101Component::set_bs_limit(BsLimit value) {
this->state_.BS_LIMIT = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_bs_pre_ki(BsPreKi value) {
this->state_.BS_PRE_KI = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_bs_pre_kp(BsPreKp value) {
this->state_.BS_PRE_KP = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_bs_post_ki(BsPostKi value) {
this->state_.BS_POST_KI = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_bs_post_kp(BsPostKp value) {
this->state_.BS_POST_KP = static_cast<uint8_t>(value);
if (this->initialized_) {
this->write_(Register::BSCFG);
}
}
void CC1101Component::set_packet_mode(bool value) {
this->state_.PKT_FORMAT =
static_cast<uint8_t>(value ? PacketFormat::PACKET_FORMAT_FIFO : PacketFormat::PACKET_FORMAT_ASYNC_SERIAL);
+11
View File
@@ -71,6 +71,17 @@ class CC1101Component final : public Component,
void set_wait_time(WaitTime value);
void set_hyst_level(HystLevel value);
// Frequency offset compensation and bit synchronization settings
void set_foc_bs_cs_gate(bool value);
void set_foc_limit(FocLimit value);
void set_foc_pre_k(FocPreK value);
void set_foc_post_k(FocPostK value);
void set_bs_limit(BsLimit value);
void set_bs_pre_ki(BsPreKi value);
void set_bs_pre_kp(BsPreKp value);
void set_bs_post_ki(BsPostKi value);
void set_bs_post_kp(BsPostKp value);
// Packet mode settings
void set_packet_mode(bool value);
void set_packet_length(uint8_t value);
+50
View File
@@ -231,6 +231,56 @@ enum class HystLevel : uint8_t {
HYST_LEVEL_HIGH,
};
enum class FocLimit : uint8_t {
FOC_LIMIT_DISABLED,
FOC_LIMIT_BW_8,
FOC_LIMIT_BW_4,
FOC_LIMIT_BW_2,
};
enum class FocPreK : uint8_t {
FOC_PRE_K_K,
FOC_PRE_K_2K,
FOC_PRE_K_3K,
FOC_PRE_K_4K,
};
enum class FocPostK : uint8_t {
FOC_POST_K_SAME,
FOC_POST_K_K_2,
};
enum class BsLimit : uint8_t {
BS_LIMIT_DISABLED,
BS_LIMIT_3P125_PERCENT,
BS_LIMIT_6P25_PERCENT,
BS_LIMIT_12P5_PERCENT,
};
enum class BsPreKi : uint8_t {
BS_PRE_KI_KI,
BS_PRE_KI_2KI,
BS_PRE_KI_3KI,
BS_PRE_KI_4KI,
};
enum class BsPreKp : uint8_t {
BS_PRE_KP_KP,
BS_PRE_KP_2KP,
BS_PRE_KP_3KP,
BS_PRE_KP_4KP,
};
enum class BsPostKi : uint8_t {
BS_POST_KI_SAME,
BS_POST_KI_KI_2,
};
enum class BsPostKp : uint8_t {
BS_POST_KP_SAME,
BS_POST_KP_KP,
};
enum class PacketFormat : uint8_t {
PACKET_FORMAT_FIFO,
PACKET_FORMAT_SYNC_SERIAL,
+2 -2
View File
@@ -28,7 +28,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
ESP_LOGD(TAG,
"LibreTiny debug info:\n"
" Version: %s\n"
" Chip: %s (%04x) @ %u MHz\n"
" Chip: %s (%04x) @ %" PRIu32 " MHz\n"
" Chip ID: 0x%06" PRIX32 "\n"
" Board: %s\n"
" Flash: %" PRIu32 " KiB\n"
@@ -38,7 +38,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
pos = buf_append_str(buf, size, pos, "|Version: ");
pos = buf_append_str(buf, size, pos, LT_BANNER_STR + 10);
pos = buf_append_str(buf, size, pos, &LT_BANNER_STR[10]);
pos = buf_append_str(buf, size, pos, "|Reset Reason: ");
pos = buf_append_str(buf, size, pos, reset_reason);
pos = buf_append_str(buf, size, pos, "|Chip Name: ");
+1 -1
View File
@@ -74,7 +74,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
char *buf = buffer.data();
uint32_t cpu_freq = ::rp2040.f_cpu();
uint32_t cpu_freq = RP2040::f_cpu();
ESP_LOGD(TAG, "CPU Frequency: %" PRIu32, cpu_freq);
pos = buf_append_printf(buf, size, pos, "|CPU Frequency: %" PRIu32, cpu_freq);
+50 -8
View File
@@ -30,6 +30,7 @@ from esphome.const import (
CONF_SECOND,
CONF_SLEEP_DURATION,
CONF_TIME_ID,
CONF_TRIGGER_ID,
CONF_WAKEUP_PIN,
PLATFORM_BK72XX,
PLATFORM_ESP32,
@@ -234,6 +235,15 @@ EXT1_WAKEUP_MODES = {
}
WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration")
WakeupCause = deep_sleep_ns.enum("WakeupCause")
WakeTrigger = deep_sleep_ns.class_(
"WakeTrigger", automation.Trigger.template(WakeupCause), cg.Component
)
Ext1WakeTrigger = deep_sleep_ns.class_(
"Ext1WakeTrigger", automation.Trigger.template(), cg.Component
)
CONF_ON_WAKE = "on_wake"
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
CONF_TOUCH_WAKEUP = "touch_wakeup"
@@ -256,6 +266,22 @@ WAKEUP_PIN_SCHEMA = cv.Schema(
}
)
EXT1_WAKEUP_PIN_SCHEMA = cv.Schema(
{
cv.Required(CONF_PIN): cv.All(
pins.internal_gpio_input_pin_schema, validate_pin_number_esp32
),
cv.Optional(CONF_ON_WAKE): automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Ext1WakeTrigger)}
),
}
)
# Entries that are not in the {pin: ..., on_wake: ...} form are treated as a
# bare pin config (the original syntax, e.g. a plain "GPIO5" or {number: 5}).
validate_ext1_wakeup_pin = cv.maybe_simple_value(EXT1_WAKEUP_PIN_SCHEMA, key=CONF_PIN)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -282,8 +308,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_PINS): cv.ensure_list(
pins.internal_gpio_input_pin_schema,
validate_pin_number_esp32,
validate_ext1_wakeup_pin,
),
cv.Required(CONF_MODE): cv.All(
cv.enum(EXT1_WAKEUP_MODES, upper=True),
@@ -292,6 +317,12 @@ CONFIG_SCHEMA = cv.All(
}
),
),
cv.Optional(CONF_ON_WAKE): cv.All(
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger)}
),
),
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(
cv.only_on_esp32,
esp32.only_on_variant(
@@ -323,7 +354,7 @@ async def to_code(config):
if CONF_WAKEUP_PIN in config:
pins_as_list = config.get(CONF_WAKEUP_PIN, [])
if CORE.is_bk72xx:
cg.add(var.init_wakeup_pins_(len(pins_as_list)))
cg.add(var.init_wakeup_pins(len(pins_as_list)))
for item in pins_as_list:
cg.add(
var.add_wakeup_pin(
@@ -362,16 +393,27 @@ async def to_code(config):
)
cg.add(var.set_run_duration(wakeup_cause_to_run_duration))
if CONF_ESP32_EXT1_WAKEUP in config:
conf = config[CONF_ESP32_EXT1_WAKEUP]
if (ext1_conf := config.get(CONF_ESP32_EXT1_WAKEUP)) is not None:
mask = 0
for pin in conf[CONF_PINS]:
mask |= 1 << pin[CONF_NUMBER]
for pin_conf in ext1_conf[CONF_PINS]:
number = pin_conf[CONF_PIN][CONF_NUMBER]
mask |= 1 << number
for wake_conf in pin_conf.get(CONF_ON_WAKE, []):
trigger = cg.new_Pvariable(wake_conf[CONF_TRIGGER_ID], number)
await cg.register_component(trigger, wake_conf)
await automation.build_automation(trigger, [], wake_conf)
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
struct = cg.StructInitializer(
Ext1Wakeup, ("mask", mask), ("wakeup_mode", conf[CONF_MODE])
Ext1Wakeup, ("mask", mask), ("wakeup_mode", ext1_conf[CONF_MODE])
)
cg.add(var.set_ext1_wakeup(struct))
for wake_conf in config.get(CONF_ON_WAKE, []):
trigger = cg.new_Pvariable(wake_conf[CONF_TRIGGER_ID])
await cg.register_component(trigger, wake_conf)
await automation.build_automation(trigger, [(WakeupCause, "cause")], wake_conf)
cg.add_define("USE_DEEP_SLEEP_ON_WAKE")
if CONF_TOUCH_WAKEUP in config:
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
if CORE.using_zephyr and "zigbee" not in CORE.loaded_integrations:
@@ -7,6 +7,21 @@ namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep.bk72xx";
#ifdef USE_DEEP_SLEEP_ON_WAKE
WakeupCause get_wakeup_cause() {
switch (lt_get_reboot_reason()) {
case REBOOT_REASON_SLEEP_GPIO:
return WAKEUP_CAUSE_GPIO;
case REBOOT_REASON_SLEEP_RTC:
return WAKEUP_CAUSE_TIMER;
case REBOOT_REASON_SLEEP_USB:
return WAKEUP_CAUSE_UNKNOWN;
default:
return WAKEUP_CAUSE_NONE;
}
}
#endif // USE_DEEP_SLEEP_ON_WAKE
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {
@@ -15,15 +30,15 @@ void DeepSleepComponent::dump_config_platform_() {
}
}
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::pin_prevents_sleep_(WakeUpPinItem &pin_item) const {
return (pin_item.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pin_item.wakeup_pin != nullptr &&
!this->sleep_duration_.has_value() && (pin_item.wakeup_level == get_real_pin_state_(*pin_item.wakeup_pin)));
}
bool DeepSleepComponent::prepare_to_sleep_() {
if (wakeup_pins_.size() > 0) {
if (!this->wakeup_pins_.empty()) {
for (WakeUpPinItem &item : this->wakeup_pins_) {
if (pin_prevents_sleep_(item)) {
if (this->pin_prevents_sleep_(item)) {
// Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) {
this->status_set_warning();
@@ -44,7 +59,7 @@ void DeepSleepComponent::deep_sleep_() {
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",
ESP_LOGI(TAG, "Wake-up on P%u %s (%" PRId32 ")", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
static_cast<int32_t>(item.wakeup_pin_mode));
}
@@ -60,6 +60,65 @@ struct WakeupCauseToRunDuration {
#endif // USE_ESP32
#ifdef USE_DEEP_SLEEP_ON_WAKE
/// Why the device woke from deep sleep. Passed to on_wake automations.
enum WakeupCause : uint8_t {
/// The device did not wake from deep sleep (for example a cold boot, reset or OTA restart).
WAKEUP_CAUSE_NONE = 0,
/// The device woke from deep sleep, but the source could not be identified.
WAKEUP_CAUSE_UNKNOWN,
/// The device was woken by the sleep timer.
WAKEUP_CAUSE_TIMER,
/// The device was woken by a GPIO pin (wakeup_pin or esp32_ext1_wakeup).
WAKEUP_CAUSE_GPIO,
/// The device was woken by a touch pad.
WAKEUP_CAUSE_TOUCH,
};
/// Return why the device woke from deep sleep. Implemented per platform.
WakeupCause get_wakeup_cause();
/** Setup priority of on_wake triggers.
*
* Between restoring global variables (setup_priority::HARDWARE, 800) and on_boot automations at
* their default priority (600), so on_wake automations can update state (e.g. globals) that
* on_boot automations then use.
*/
inline constexpr float ON_WAKE_TRIGGER_SETUP_PRIORITY = 700.0f;
/// Fires once on boot when the device woke from deep sleep, with the wakeup cause.
class WakeTrigger : public Trigger<WakeupCause>, public Component {
public:
void setup() override {
const WakeupCause cause = get_wakeup_cause();
if (cause != WAKEUP_CAUSE_NONE) {
this->trigger(cause);
}
}
float get_setup_priority() const override { return ON_WAKE_TRIGGER_SETUP_PRIORITY; }
};
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
/// Fires once on boot when the device was woken from deep sleep by the given ext1 pin.
class Ext1WakeTrigger : public Trigger<>, public Component {
public:
explicit Ext1WakeTrigger(uint8_t pin) : pin_(pin) {}
void setup() override {
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1 &&
(esp_sleep_get_ext1_wakeup_status() & (1ULL << this->pin_))) {
this->trigger();
}
}
float get_setup_priority() const override { return ON_WAKE_TRIGGER_SETUP_PRIORITY; }
protected:
uint8_t pin_;
};
#endif
#endif // USE_DEEP_SLEEP_ON_WAKE
template<typename... Ts> class EnterDeepSleepAction;
template<typename... Ts> class PreventDeepSleepAction;
@@ -84,7 +143,7 @@ class DeepSleepComponent final : public Component {
#endif // USE_ESP32
#if defined(USE_BK72XX)
void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); }
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()});
}
@@ -132,7 +191,7 @@ class DeepSleepComponent final : public Component {
bool should_teardown_();
#ifdef USE_BK72XX
bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const;
bool pin_prevents_sleep_(WakeUpPinItem &pin_item) const;
bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); }
#endif // USE_BK72XX
@@ -30,6 +30,25 @@ namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep";
#ifdef USE_DEEP_SLEEP_ON_WAKE
WakeupCause get_wakeup_cause() {
switch (esp_sleep_get_wakeup_cause()) {
case ESP_SLEEP_WAKEUP_EXT0:
case ESP_SLEEP_WAKEUP_EXT1:
case ESP_SLEEP_WAKEUP_GPIO:
return WAKEUP_CAUSE_GPIO;
case ESP_SLEEP_WAKEUP_TIMER:
return WAKEUP_CAUSE_TIMER;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
return WAKEUP_CAUSE_TOUCH;
case ESP_SLEEP_WAKEUP_UNDEFINED:
return WAKEUP_CAUSE_NONE;
default:
return WAKEUP_CAUSE_UNKNOWN;
}
}
#endif // USE_DEEP_SLEEP_ON_WAKE
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
if (this->wakeup_cause_to_run_duration_.has_value()) {
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
@@ -3,10 +3,27 @@
#include <Esp.h>
#ifdef USE_DEEP_SLEEP_ON_WAKE
extern "C" {
#include <user_interface.h>
}
#endif
namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep";
#ifdef USE_DEEP_SLEEP_ON_WAKE
WakeupCause get_wakeup_cause() {
// The ESP8266 can only wake from deep sleep through the RTC timer (via GPIO16 -> RST).
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
if (ESP.getResetInfoPtr()->reason == REASON_DEEP_SLEEP_AWAKE) {
return WAKEUP_CAUSE_TIMER;
}
return WAKEUP_CAUSE_NONE;
}
#endif // USE_DEEP_SLEEP_ON_WAKE
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {}
+112
View File
@@ -0,0 +1,112 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_SLEEP_PIN, CONF_TYPE
CODEOWNERS = ["@tomwellnitz"]
MULTI_CONF = True
DEPENDENCIES = ["i2c"]
CONF_DS248X_ID = "ds248x_id"
CONF_BUS_SLEEP = "bus_sleep"
CONF_HUB_SLEEP = "hub_sleep"
CONF_ACTIVE_PULLUP = "active_pullup"
CONF_RESET_LOW_TIME = "reset_low_time"
CONF_MASTER_SAMPLE_TIME = "master_sample_time"
CONF_WRITE_0_LOW_TIME = "write_0_low_time"
CONF_RECOVERY_TIME = "recovery_time"
CONF_ACTIVE_PULLUP_RESISTANCE = "active_pullup_resistance"
TYPE_DS2482_100 = "ds2482-100"
TYPE_DS2482_101 = "ds2482-101"
TYPE_DS2482_800 = "ds2482-800"
TYPE_DS2484 = "ds2484"
CHANNEL_COUNTS = {
TYPE_DS2482_100: 1,
TYPE_DS2482_101: 1,
TYPE_DS2482_800: 8,
TYPE_DS2484: 1,
}
ds248x_ns = cg.esphome_ns.namespace("ds248x")
DS248xComponent = ds248x_ns.class_("DS248xComponent", cg.Component, i2c.I2CDevice)
def _component_schema(*extras):
schema = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DS248xComponent),
cv.Optional(CONF_ACTIVE_PULLUP, default=False): cv.boolean,
}
)
for extra in extras:
schema = schema.extend(extra)
return schema.extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x18))
SLEEP_SCHEMA = {
cv.Optional(CONF_SLEEP_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_BUS_SLEEP, default=False): cv.boolean,
cv.Optional(CONF_HUB_SLEEP, default=False): cv.boolean,
}
DS2484_SCHEMA = {
cv.Optional(CONF_RESET_LOW_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_MASTER_SAMPLE_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_WRITE_0_LOW_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_RECOVERY_TIME): cv.int_range(min=0, max=15),
cv.Optional(CONF_ACTIVE_PULLUP_RESISTANCE): cv.enum(
{
# DS2484 Table 7: value codes 0-5 map to 500 ohm, 6-15 map to 1000 ohm.
"500ohm": 0,
"1000ohm": 6,
}
),
}
CONFIG_SCHEMA = cv.typed_schema(
{
TYPE_DS2482_100: _component_schema(),
TYPE_DS2482_101: _component_schema(SLEEP_SCHEMA),
TYPE_DS2482_800: _component_schema(),
TYPE_DS2484: _component_schema(SLEEP_SCHEMA, DS2484_SCHEMA),
},
key=CONF_TYPE,
lower=True,
)
def get_channel_count(config):
return CHANNEL_COUNTS[config[CONF_TYPE]]
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_active_pullup(config[CONF_ACTIVE_PULLUP]))
cg.add(var.set_channel_count(get_channel_count(config)))
if CONF_BUS_SLEEP in config:
cg.add(var.set_bus_sleep(config[CONF_BUS_SLEEP]))
if CONF_HUB_SLEEP in config:
cg.add(var.set_hub_sleep(config[CONF_HUB_SLEEP]))
if CONF_RESET_LOW_TIME in config:
cg.add(var.set_val_trstl(config[CONF_RESET_LOW_TIME]))
if CONF_MASTER_SAMPLE_TIME in config:
cg.add(var.set_val_tmsp(config[CONF_MASTER_SAMPLE_TIME]))
if CONF_WRITE_0_LOW_TIME in config:
cg.add(var.set_val_tw0l(config[CONF_WRITE_0_LOW_TIME]))
if CONF_RECOVERY_TIME in config:
cg.add(var.set_val_trec0(config[CONF_RECOVERY_TIME]))
if CONF_ACTIVE_PULLUP_RESISTANCE in config:
cg.add(var.set_val_rwpu(config[CONF_ACTIVE_PULLUP_RESISTANCE]))
if CONF_SLEEP_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN])
cg.add(var.set_sleep_pin(pin))
+320
View File
@@ -0,0 +1,320 @@
#include "ds248x.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome::ds248x {
static const char *const TAG = "ds248x";
void DS248xComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up DS248x...");
// Wake up device if sleep pin is configured
if (this->sleep_pin_) {
this->sleep_pin_->setup();
this->sleep_pin_->pin_mode(esphome::gpio::FLAG_OUTPUT);
this->sleep_pin_->digital_write(true); // Wake up
delay(1); // DS2482-101 Datasheet: tOSCWUP = 100μs (using 10x margin)
}
// Probe device
ESP_LOGD(TAG, "Probing DS248x...");
uint8_t status = 0;
if (this->read(&status, 1) == i2c::ERROR_OK) {
ESP_LOGD(TAG, "Device responded! Status: 0x%02x", status);
} else {
ESP_LOGW(TAG, "Device did not respond. Trying reset anyway...");
}
if (!this->device_reset_()) {
ESP_LOGW(TAG, "DS248x reset failed during setup!");
}
// Configure device
if (!this->device_configure_()) {
ESP_LOGE(TAG, "DS248x configuration failed!");
this->mark_failed();
return;
}
// Reset to Channel 0
this->select_channel(0);
ESP_LOGI(TAG, "DS248x initialized successfully.");
}
void DS248xComponent::on_shutdown() {
if (this->sleep_pin_ && (this->hub_sleep_ || this->bus_sleep_)) {
this->sleep_pin_->digital_write(false); // Sleep
}
}
void DS248xComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DS248x:");
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Channel Count: %d", this->channel_count_);
ESP_LOGCONFIG(TAG, " Active Pullup: %s", YESNO(this->active_pullup_));
if (this->ds2484_mode_) {
ESP_LOGCONFIG(TAG, " DS2484 Mode: enabled");
}
}
// --- Internal Helpers ---
// Datasheet command durations are sub-2ms; allow a little margin before forcing recovery.
static constexpr uint32_t BUSY_TIMEOUT_MS = 5;
bool DS248xComponent::set_read_pointer_(uint8_t ptr) { return this->write_byte(DS248X_COMMAND_SETREADPTR, ptr); }
bool DS248xComponent::wait_busy_() {
uint32_t start = millis();
do {
uint8_t status;
if (this->read(&status, 1) == i2c::ERROR_OK && !(status & DS248X_STATUS_BUSY))
return true;
delayMicroseconds(100);
} while (millis() - start < BUSY_TIMEOUT_MS);
ESP_LOGW(TAG, "DS248x busy timeout");
bool recovered = this->device_reset_() && this->device_configure_();
this->current_channel_ = -1;
if (!recovered) {
ESP_LOGE(TAG, "DS248x recovery failed after busy timeout");
this->mark_failed();
}
return false;
}
bool DS248xComponent::device_reset_() {
ESP_LOGD(TAG, "Resetting device...");
uint8_t cmd = DS248X_COMMAND_RESET;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
uint8_t status;
if (this->read(&status, 1) != i2c::ERROR_OK)
return false;
if (!(status & DS248X_STATUS_RST)) {
ESP_LOGW(TAG, "Device reset failed (RST bit not set)");
return false;
}
this->current_channel_ = -1;
return true;
}
bool DS248xComponent::device_configure_() {
ESP_LOGD(TAG, "Configuring device...");
if (!this->write_config_()) {
ESP_LOGW(TAG, "Config write/verify failed");
return false;
}
ESP_LOGD(TAG, "Configured successfully");
// DS2484 Configuration
if (this->ds2484_mode_) {
if (this->ds2484_trstl_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TRSTL, this->ds2484_trstl_))
return false;
if (this->ds2484_tmsp_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TMSP, this->ds2484_tmsp_))
return false;
if (this->ds2484_tw0l_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TW0L, this->ds2484_tw0l_))
return false;
if (this->ds2484_trec0_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_TREC0, this->ds2484_trec0_))
return false;
if (this->ds2484_rwpu_ != DS2484_PARAM_UNSET &&
!this->configure_ds2484_port_(DS2484_PORT_PARAM_RWPU, this->ds2484_rwpu_))
return false;
}
return true;
}
bool DS248xComponent::configure_ds2484_port_(uint8_t param, uint8_t val) {
uint8_t cmd = DS2484_COMMAND_ADJUSTPORT;
// Control Byte format (DS2484 Table 6): P[2:0] in bits 7:5, OD in bit 4, VAL[3:0] in bits 3:0
uint8_t data = ((param & 0x07) << 5) | (val & 0x0F);
// The DS2484 always acknowledges the Adjust 1-Wire Port control byte (datasheet "Adjust
// 1-Wire Port"), so a successful write confirms the update. We deliberately do not read
// back to verify: a single read of the Port Configuration register always returns the
// fixed 8-byte report starting at Byte 1 (tRSTL standard speed), not the parameter that
// was just written, so a per-parameter readback comparison would spuriously fail for
// tMSP/tW0L/tREC0/RWPU.
if (!this->write_byte(cmd, data)) {
ESP_LOGW(TAG, "DS2484 port config failed (param %d)", param);
return false;
}
return this->set_read_pointer_(DS248X_POINTER_STATUS);
}
bool DS248xComponent::write_config_() {
uint8_t config = 0;
if (this->active_pullup_)
config |= DS248X_CONFIG_ACTIVE_PULLUP;
// The DS248x only accepts the config byte if the upper nibble is the one's-complement of the lower nibble.
uint8_t config_byte = (config & 0x0F) | ((~config & 0x0F) << 4);
if (!this->write_byte(DS248X_COMMAND_WRITECONFIG, config_byte)) {
ESP_LOGW(TAG, "Failed to write config byte");
return false;
}
if (!this->set_read_pointer_(DS248X_POINTER_CONFIG)) {
return false;
}
uint8_t read_config;
if (this->read(&read_config, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Failed to read back config byte");
return false;
}
if ((read_config & 0x0F) != (config_byte & 0x0F)) {
ESP_LOGW(TAG, "Config mismatch! Wrote 0x%02x, Read 0x%02x", config_byte, read_config);
return false;
}
return this->set_read_pointer_(DS248X_POINTER_STATUS);
}
// --- Channel Selection ---
// Channel select codes: write code -> expected read code
static constexpr uint8_t CHANNEL_WRITE_CODES[8] = {0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87};
static constexpr uint8_t CHANNEL_READ_CODES[8] = {0xB8, 0xB1, 0xAA, 0xA3, 0x9C, 0x95, 0x8E, 0x87};
bool DS248xComponent::select_channel(uint8_t channel) {
if (this->channel_count_ <= 1)
return true;
if (channel >= this->channel_count_)
return false;
if (this->current_channel_ == channel)
return true;
if (!this->write_byte(DS248X_COMMAND_CHANNELSELECT, CHANNEL_WRITE_CODES[channel])) {
this->current_channel_ = -1;
return false;
}
uint8_t read_code;
if (this->read(&read_code, 1) != i2c::ERROR_OK) {
this->current_channel_ = -1;
return false;
}
if (read_code != CHANNEL_READ_CODES[channel]) {
ESP_LOGW(TAG, "Channel select failed! Expected 0x%02x, got 0x%02x", CHANNEL_READ_CODES[channel], read_code);
this->current_channel_ = -1;
return false;
}
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
this->current_channel_ = channel;
return true;
}
// --- 1-Wire Bus Operations ---
bool DS248xComponent::ow_reset(bool &presence) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
uint8_t cmd = DS248X_COMMAND_RESETWIRE;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "ow_reset: wait busy failed");
return false;
}
uint8_t status;
if (this->read(&status, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "ow_reset: read status failed");
return false;
}
if (status & DS248X_STATUS_SD) {
ESP_LOGW(TAG, "Short detected on 1-Wire bus!");
return false;
}
presence = (status & DS248X_STATUS_PPD);
return true;
}
bool DS248xComponent::ow_write_byte(uint8_t byte) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "Device busy before writing byte 0x%02x", byte);
return false;
}
uint8_t cmd[2] = {DS248X_COMMAND_WRITEBYTE, byte};
if (this->write(cmd, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "I2C write failed for byte 0x%02x", byte);
return false;
}
if (!this->wait_busy_()) {
ESP_LOGW(TAG, "Timeout waiting for write byte to complete!");
return false;
}
return true;
}
bool DS248xComponent::ow_read_byte(uint8_t &byte) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
uint8_t cmd = DS248X_COMMAND_READBYTE;
if (this->write(&cmd, 1) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_())
return false;
if (!this->set_read_pointer_(DS248X_POINTER_DATA))
return false;
if (this->read(&byte, 1) != i2c::ERROR_OK)
return false;
return true;
}
bool DS248xComponent::search_triplet(bool search_direction, uint8_t &status) {
if (!this->set_read_pointer_(DS248X_POINTER_STATUS))
return false;
// DS248x Datasheet: 1-Wire Triplet command requires 2 bytes:
// Byte 1: Command code 0x78
// Byte 2: Direction byte (bit 7 = V, search direction if discrepancy)
uint8_t buffer[2] = {DS248X_COMMAND_TRIPLET, static_cast<uint8_t>(search_direction ? 0x80 : 0x00)};
if (this->write(buffer, 2) != i2c::ERROR_OK)
return false;
if (!this->wait_busy_())
return false;
if (this->read(&status, 1) != i2c::ERROR_OK)
return false;
return true;
}
} // namespace esphome::ds248x
+133
View File
@@ -0,0 +1,133 @@
#pragma once
// DS248x I2C-to-1-Wire Bridge Family
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2482-100.pdf
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2482-800.pdf
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds2484.pdf
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome::ds248x {
// DS248x I2C Commands
static constexpr uint8_t DS248X_COMMAND_RESET = 0xF0;
static constexpr uint8_t DS248X_COMMAND_SETREADPTR = 0xE1;
static constexpr uint8_t DS248X_COMMAND_WRITECONFIG = 0xD2;
static constexpr uint8_t DS248X_COMMAND_CHANNELSELECT = 0xC3;
static constexpr uint8_t DS248X_COMMAND_RESETWIRE = 0xB4;
static constexpr uint8_t DS248X_COMMAND_WRITEBYTE = 0xA5;
static constexpr uint8_t DS248X_COMMAND_READBYTE = 0x96;
static constexpr uint8_t DS248X_COMMAND_TRIPLET = 0x78;
static constexpr uint8_t DS2484_COMMAND_ADJUSTPORT = 0xC3;
// DS2484 "Adjust 1-Wire Port" parameter codes (datasheet Table 6, control byte P[2:0])
static constexpr uint8_t DS2484_PORT_PARAM_TRSTL = 0x0;
static constexpr uint8_t DS2484_PORT_PARAM_TMSP = 0x1;
static constexpr uint8_t DS2484_PORT_PARAM_TW0L = 0x2;
static constexpr uint8_t DS2484_PORT_PARAM_TREC0 = 0x3;
static constexpr uint8_t DS2484_PORT_PARAM_RWPU = 0x4;
// DS248x Status Register Bits
static constexpr uint8_t DS248X_STATUS_BUSY = 0x01;
static constexpr uint8_t DS248X_STATUS_PPD = 0x02;
static constexpr uint8_t DS248X_STATUS_SD = 0x04;
static constexpr uint8_t DS248X_STATUS_RST = 0x10;
static constexpr uint8_t DS248X_STATUS_SBR = 0x20;
static constexpr uint8_t DS248X_STATUS_TSB = 0x40;
static constexpr uint8_t DS248X_STATUS_DIR = 0x80;
// DS248x Register Pointers
static constexpr uint8_t DS248X_POINTER_STATUS = 0xF0;
static constexpr uint8_t DS248X_POINTER_DATA = 0xE1;
static constexpr uint8_t DS248X_POINTER_CONFIG = 0xC3;
// DS248x Configuration Bits
static constexpr uint8_t DS248X_CONFIG_ACTIVE_PULLUP = 0x01;
/**
* @brief DS248x I2C-to-1-Wire Bridge Component.
*
* This component manages the DS248x chip (DS2482-100, DS2482-800, DS2484).
* It provides low-level 1-Wire bus operations via I2C.
*
* Usage: Configure DS248xOneWireBus instances for each channel.
* These buses implement the one_wire::OneWireBus interface for compatibility
* with all existing 1-Wire device components (dallas_temp, etc.).
*/
class DS248xComponent : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void on_shutdown() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_sleep_pin(InternalGPIOPin *pin) { this->sleep_pin_ = pin; }
void set_bus_sleep(bool enabled) { this->bus_sleep_ = enabled; }
void set_hub_sleep(bool enabled) { this->hub_sleep_ = enabled; }
void set_channel_count(uint8_t count) { this->channel_count_ = count; }
void set_active_pullup(bool enabled) { this->active_pullup_ = enabled; }
// DS2484 Timing Parameters
void set_val_trstl(uint8_t val) {
this->ds2484_trstl_ = val;
this->ds2484_mode_ = true;
}
void set_val_tmsp(uint8_t val) {
this->ds2484_tmsp_ = val;
this->ds2484_mode_ = true;
}
void set_val_tw0l(uint8_t val) {
this->ds2484_tw0l_ = val;
this->ds2484_mode_ = true;
}
void set_val_trec0(uint8_t val) {
this->ds2484_trec0_ = val;
this->ds2484_mode_ = true;
}
void set_val_rwpu(uint8_t val) {
this->ds2484_rwpu_ = val;
this->ds2484_mode_ = true;
}
/// Get the channel count (1 for DS2482-100/DS2484, 8 for DS2482-800)
uint8_t get_channel_count() const { return this->channel_count_; }
// --- Core 1-Wire API (used by DS248xOneWireBus) ---
bool select_channel(uint8_t channel);
bool ow_reset(bool &presence);
bool ow_write_byte(uint8_t byte);
bool ow_read_byte(uint8_t &byte);
// --- Search support (used by DS248xOneWireBus) ---
bool search_triplet(bool search_direction, uint8_t &status);
protected:
InternalGPIOPin *sleep_pin_{nullptr};
uint8_t channel_count_ = 1;
bool bus_sleep_{false};
bool hub_sleep_{false};
bool active_pullup_ = false;
// DS2484 Config
bool ds2484_mode_ = false;
static constexpr uint8_t DS2484_PARAM_UNSET = 0xFF;
uint8_t ds2484_trstl_{DS2484_PARAM_UNSET};
uint8_t ds2484_tmsp_{DS2484_PARAM_UNSET};
uint8_t ds2484_tw0l_{DS2484_PARAM_UNSET};
uint8_t ds2484_trec0_{DS2484_PARAM_UNSET};
uint8_t ds2484_rwpu_{DS2484_PARAM_UNSET};
int8_t current_channel_{-1};
// Internal helpers
bool set_read_pointer_(uint8_t ptr);
bool wait_busy_();
bool device_reset_();
bool device_configure_();
bool configure_ds2484_port_(uint8_t param, uint8_t val);
bool write_config_();
};
} // namespace esphome::ds248x
@@ -0,0 +1,171 @@
#include "ds248x_one_wire_bus.h"
#include "ds248x.h"
#include "esphome/core/log.h"
namespace esphome::ds248x {
static const char *const TAG = "ds248x.one_wire";
void DS248xOneWireBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up DS248x 1-Wire Bus (Channel %d)...", this->channel_);
// Parent setup happens in DS248xComponent::setup()
// We just need to scan for devices on this channel
if (!this->ensure_channel_()) {
ESP_LOGE(TAG, "Failed to select channel %d during setup", this->channel_);
this->mark_failed();
return;
}
// Perform device search on this channel
this->search();
ESP_LOGCONFIG(TAG, "Found %zu devices on channel %d", this->devices_.size(), this->channel_);
}
void DS248xOneWireBus::dump_config() {
ESP_LOGCONFIG(TAG, "DS248x 1-Wire Bus (Channel %d):", this->channel_);
this->dump_devices_(TAG);
}
bool DS248xOneWireBus::ensure_channel_() {
if (this->parent_ == nullptr) {
ESP_LOGE(TAG, "Parent not set!");
return false;
}
return this->parent_->select_channel(this->channel_);
}
int DS248xOneWireBus::reset_int() {
if (!this->ensure_channel_()) {
return -1;
}
bool presence = false;
if (!this->parent_->ow_reset(presence)) {
return -1;
}
return presence ? 1 : 0;
}
void DS248xOneWireBus::write8(uint8_t val) {
if (!this->ensure_channel_()) {
return;
}
if (!this->parent_->ow_write_byte(val)) {
ESP_LOGE(TAG, "Failed to write byte 0x%02X on channel %d", val, this->channel_);
}
}
void DS248xOneWireBus::write64(uint64_t val) {
if (!this->ensure_channel_()) {
return;
}
for (uint8_t i = 0; i < 8; i++) {
uint8_t byte = static_cast<uint8_t>(val >> (i * 8));
if (!this->parent_->ow_write_byte(byte)) {
ESP_LOGE(TAG, "Failed to write byte %d/8 (0x%02X) on channel %d - aborting write64", i + 1, byte, this->channel_);
return; // Stop writing to prevent sending corrupted data
}
}
}
uint8_t DS248xOneWireBus::read8() {
if (!this->ensure_channel_()) {
return 0;
}
uint8_t value = 0;
if (!this->parent_->ow_read_byte(value)) {
ESP_LOGE(TAG, "Failed to read byte on channel %d", this->channel_);
}
return value;
}
uint64_t DS248xOneWireBus::read64() {
if (!this->ensure_channel_()) {
return 0;
}
uint64_t value = 0;
for (uint8_t i = 0; i < 8; i++) {
uint8_t byte = 0;
if (!this->parent_->ow_read_byte(byte)) {
ESP_LOGE(TAG, "Failed to read byte %d/8 on channel %d - returning partial data", i + 1, this->channel_);
return value; // Return partial data to avoid blocking, caller should validate
}
value |= (static_cast<uint64_t>(byte) << (i * 8));
}
return value;
}
void DS248xOneWireBus::reset_search() {
this->search_last_discrepancy_ = 0;
this->search_last_device_flag_ = false;
this->search_address_ = 0;
}
uint64_t DS248xOneWireBus::search_int() {
if (!this->ensure_channel_()) {
return 0;
}
if (this->search_last_device_flag_) {
return 0;
}
uint8_t last_zero = 0;
uint64_t address = this->search_address_;
// Iterate through all 64 bits
for (uint8_t bit_number = 1; bit_number <= 64; bit_number++) {
uint64_t bit_mask = 1ULL << (bit_number - 1);
// Determine search direction
bool search_direction;
if (bit_number < this->search_last_discrepancy_) {
search_direction = (address & bit_mask) != 0;
} else {
search_direction = (bit_number == this->search_last_discrepancy_);
}
// Perform triplet operation
uint8_t status = 0;
if (!this->parent_->search_triplet(search_direction, status)) {
ESP_LOGW(TAG, "1-Wire triplet failed at bit %d on channel %d - aborting search", bit_number, this->channel_);
this->reset_search();
return 0;
}
bool id_bit = (status & DS248X_STATUS_SBR) != 0;
bool cmp_id_bit = (status & DS248X_STATUS_TSB) != 0;
bool dir_taken = (status & DS248X_STATUS_DIR) != 0;
if (id_bit && cmp_id_bit) {
// No devices participating
this->reset_search();
return 0;
}
if (!id_bit && !cmp_id_bit && !dir_taken) {
// Discrepancy, went 0 - record position
last_zero = bit_number;
}
// Update address based on direction taken
if (dir_taken) {
address |= bit_mask;
} else {
address &= ~bit_mask;
}
}
// Search successful
this->search_last_discrepancy_ = last_zero;
if (last_zero == 0) {
this->search_last_device_flag_ = true;
}
this->search_address_ = address;
return address;
}
} // namespace esphome::ds248x
@@ -0,0 +1,57 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/one_wire/one_wire_bus.h"
namespace esphome::ds248x {
class DS248xComponent;
/**
* @brief OneWireBus implementation for DS248x I2C-to-1-Wire bridges.
*
* This class wraps the DS248xComponent to provide the one_wire::OneWireBus interface,
* enabling compatibility with all existing 1-Wire device components (dallas_temp, etc.).
*
* For DS2482-800, multiple instances of this class can be created (one per channel).
* For DS2482-100/DS2484, a single instance is used.
*/
class DS248xOneWireBus : public one_wire::OneWireBus, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::BUS - 1.0f; }
/// Set the parent DS248x component
void set_parent(DS248xComponent *parent) { this->parent_ = parent; }
/// Set the 1-Wire channel (0-7, only relevant for DS2482-800)
void set_channel(uint8_t channel) { this->channel_ = channel; }
/// Get the channel number
uint8_t get_channel() const { return this->channel_; }
// OneWireBus interface implementation
int reset_int() override;
void write8(uint8_t val) override;
void write64(uint64_t val) override;
uint8_t read8() override;
uint64_t read64() override;
protected:
void reset_search() override;
uint64_t search_int() override;
/// Select the channel on the DS248x before any 1-Wire operation
bool ensure_channel_();
DS248xComponent *parent_{nullptr};
uint8_t channel_{0};
// Search state
uint64_t search_address_{0};
uint8_t search_last_discrepancy_{0};
bool search_last_device_flag_{false};
};
} // namespace esphome::ds248x
+56
View File
@@ -0,0 +1,56 @@
"""DS248x 1-Wire Bus Platform.
This platform creates one_wire bus instances backed by a DS248x I2C-to-1-Wire bridge.
It supports DS2482-100/101 (single channel), DS2482-800 (8 channels), and DS2484 (single channel).
For multi-channel devices (DS2482-800), create one platform entry per channel.
Each entry becomes a separate one_wire bus that can be used by dallas_temp and other 1-Wire devices.
"""
from esphome import final_validate as fv
import esphome.codegen as cg
from esphome.components.one_wire import OneWireBus
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID
from . import CONF_DS248X_ID, DS248xComponent, ds248x_ns, get_channel_count
CODEOWNERS = ["@tomwellnitz"]
DEPENDENCIES = ["ds248x"]
DS248xOneWireBus = ds248x_ns.class_("DS248xOneWireBus", OneWireBus, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DS248xOneWireBus),
cv.GenerateID(CONF_DS248X_ID): cv.use_id(DS248xComponent),
cv.Optional(CONF_CHANNEL, default=0): cv.int_range(min=0, max=7),
}
).extend(cv.COMPONENT_SCHEMA)
def _final_validate(config):
"""Validate that the channel is within the parent's channel count."""
fconf = fv.full_config.get()
path = fconf.get_path_for_id(config[CONF_DS248X_ID])[:-1]
parent_config = fconf.get_config_for_path(path)
channel_count = get_channel_count(parent_config)
channel = config[CONF_CHANNEL]
if channel >= channel_count:
raise cv.Invalid(
f"Channel {channel} is invalid for DS248x with {channel_count} channel(s). "
f"Valid range: 0-{channel_count - 1}"
)
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
parent = await cg.get_variable(config[CONF_DS248X_ID])
cg.add(var.set_parent(parent))
cg.add(var.set_channel(config[CONF_CHANNEL]))
@@ -14,10 +14,9 @@ void EPaperMono::refresh_screen(bool partial) {
}
void EPaperMono::deep_sleep() {
ESP_LOGV(TAG, "Deep sleep");
if (this->is_using_partial_update_()) {
this->cmd_data(0x10, {0x00}); // sleep in power on mode
} else {
// Deep sleep loses RAM so cannot be used with partial update
if (!this->is_using_partial_update_()) {
ESP_LOGV(TAG, "Deep sleep");
this->cmd_data(0x10, {0x03}); // deep sleep
}
}
@@ -15,7 +15,7 @@ class EpaperModel:
self,
name: str,
class_name: str,
initsequence=(),
initsequence=None,
**defaults,
):
name = name.upper()
+26 -24
View File
@@ -628,6 +628,7 @@ class NetworkSdkconfigData:
wifi_ap: bool = False # WiFi AP mode configured
ethernet: bool = False # Ethernet component active
bluetooth: bool = False # any BLE component active
ble_42: bool = False # BLE 4.2 features needed
software_coexistence: bool = False # WiFi/BT software coexistence requested
# esp32 advanced enable_lwip_dhcp_server option (True/False/None=unset)
enable_lwip_dhcp_server: bool | None = None
@@ -653,10 +654,12 @@ def request_ethernet() -> None:
_network_sdkconfig().ethernet = True
def request_bluetooth() -> None:
"""Request the Bluetooth controller."""
def request_bluetooth(ble_42: bool = False) -> None:
"""Request the Bluetooth controller. Pass ble_42=True for 4.2 features."""
net = _network_sdkconfig()
net.bluetooth = True
if ble_42:
net.ble_42 = True
def request_software_coexistence() -> None:
@@ -811,15 +814,14 @@ def _is_framework_url(source: str) -> bool:
# The default/recommended arduino framework version
# - https://github.com/espressif/arduino-esp32/releases
ARDUINO_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(3, 3, 10),
"latest": cv.Version(3, 3, 10),
"dev": cv.Version(3, 3, 10),
"recommended": cv.Version(3, 3, 9),
"latest": cv.Version(3, 3, 9),
"dev": cv.Version(3, 3, 9),
}
ARDUINO_PLATFORM_VERSION_LOOKUP = {
cv.Version(
4, 0, 0, "alpha1"
): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6",
cv.Version(3, 3, 10): cv.Version(55, 3, 39),
cv.Version(3, 3, 9): cv.Version(55, 3, 39),
cv.Version(3, 3, 8): cv.Version(55, 3, 38, "1"),
cv.Version(3, 3, 7): cv.Version(55, 3, 37),
@@ -842,7 +844,6 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = {
# See: https://github.com/pioarduino/esp-idf/releases
ARDUINO_IDF_VERSION_LOOKUP = {
cv.Version(4, 0, 0, "alpha1"): cv.Version(6, 0, 1),
cv.Version(3, 3, 10): cv.Version(5, 5, 5),
cv.Version(3, 3, 9): cv.Version(5, 5, 4),
cv.Version(3, 3, 8): cv.Version(5, 5, 4),
cv.Version(3, 3, 7): cv.Version(5, 5, 3, "1"),
@@ -864,9 +865,9 @@ ARDUINO_IDF_VERSION_LOOKUP = {
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(5, 5, 5),
"latest": cv.Version(5, 5, 5),
"dev": cv.Version(5, 5, 5),
"recommended": cv.Version(5, 5, 4),
"latest": cv.Version(5, 5, 4),
"dev": cv.Version(5, 5, 4),
}
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
@@ -876,7 +877,6 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(
6, 0, 0
): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6",
cv.Version(5, 5, 5): cv.Version(55, 3, 39),
cv.Version(5, 5, 4): cv.Version(55, 3, 39),
cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37),
cv.Version(5, 5, 3): cv.Version(55, 3, 37),
@@ -1026,9 +1026,7 @@ def _check_esp_idf_versions(config: ConfigType) -> ConfigType:
def _validate_toolchain(value) -> Toolchain:
return Toolchain(
cv.one_of(Toolchain.PLATFORMIO, Toolchain.ESP_IDF, lower=True)(value)
)
return Toolchain(cv.one_of(*(t.value for t in Toolchain), lower=True)(value))
def _resolve_toolchain(value: ConfigType) -> ConfigType:
@@ -2043,12 +2041,12 @@ async def _reconcile_network_sdkconfig() -> None:
if name not in opts:
add_idf_sdkconfig_option(name, value)
# Bluetooth: only ever enable when requested. The IDF default is off.
# According to the IDF docs, only one of 4.2 or 5.0 should be enabled.
# Bluetooth: only ever enable when requested. The IDF default is off and
# nothing sets these False today, so never write False here.
if net.bluetooth:
set_opt("CONFIG_BT_ENABLED", True)
set_opt("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
set_opt("CONFIG_BT_BLE_50_FEATURES_SUPPORTED", False)
if net.ble_42:
set_opt("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
# WiFi stack: disable only when Ethernet is present and WiFi is not. WiFi
# relies on the IDF default (enabled), so it is never written True here.
@@ -3060,19 +3058,23 @@ def copy_files():
def _decode_pc(config, addr):
# _decode_pc runs from the api log processor's asyncio callback, which
# only catches EsphomeError. Any other exception escaping here tears down
# the protocol and triggers an infinite reconnect/replay loop. Convert
# toolchain-resolution errors (e.g. missing build dir / cmake cache) into
# EsphomeError so the caller can disable decoding cleanly.
# Convert toolchain-resolution errors (e.g. missing build dir / cmake
# cache) into EsphomeError. The api log processor stops decoding on any
# exception, so this is about the message it reports rather than about
# catching it at all: EsphomeError carries an explanation worth showing
# the user, where a raw OSError repr does not.
if CORE.using_toolchain_esp_idf:
from esphome.espidf import toolchain as idf_toolchain
try:
addr2line_path = idf_toolchain.get_addr2line_path()
firmware_elf_path = idf_toolchain.get_elf_path()
except RuntimeError as err:
except (RuntimeError, OSError) as err:
# OSError covers a missing build directory or a cmake that isn't
# on PATH; both surface from the subprocess call, not as RuntimeError.
raise EsphomeError(f"ESP-IDF toolchain not available: {err}") from err
if not firmware_elf_path.is_file():
raise EsphomeError(f"Firmware ELF not found: {firmware_elf_path}")
else:
from esphome.platformio import toolchain
+1 -1
View File
@@ -604,7 +604,7 @@ async def to_code(config):
max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS)
cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections)
request_bluetooth()
request_bluetooth(ble_42=True)
# When PSRAM and BT are used together, Bluedroid should prefer SPIRAM for
# heap allocations and use dynamic (heap-based) environment memory tables
-1
View File
@@ -58,7 +58,6 @@ static constexpr uint32_t HOSTED_BT_WDT_TIMEOUT_MS = 60000;
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: \
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: \
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: \
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: \
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: \
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT
+1 -4
View File
@@ -207,7 +207,7 @@ class BLEEvent {
StatusOnlyData scan_complete; // 1 byte
// Advertising complete events all have same structure
// Used by: esp32_ble_beacon, esp32_ble server components
// ADV_DATA_SET, SCAN_RSP_DATA_SET, ADV_DATA_RAW_SET, SCAN_RSP_DATA_RAW_SET, ADV_START, ADV_STOP
// ADV_DATA_SET, SCAN_RSP_DATA_SET, ADV_DATA_RAW_SET, ADV_START, ADV_STOP
StatusOnlyData adv_complete; // 1 byte
// RSSI complete event
// Used by: ble_client (ble_rssi_sensor component)
@@ -324,9 +324,6 @@ class BLEEvent {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: // Used by: esp32_ble_beacon
this->event_.gap.adv_complete.status = p->adv_data_raw_cmpl.status;
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: // Used by: raw advertisers with scan response
this->event_.gap.adv_complete.status = p->scan_rsp_data_raw_cmpl.status;
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: // Used by: esp32_ble_beacon
this->event_.gap.adv_complete.status = p->adv_start_cmpl.status;
break;
@@ -181,12 +181,6 @@ const char *ESPBTUUID::to_str(std::span<char, UUID_STR_LEN> output) const {
return output.data();
}
}
std::string ESPBTUUID::to_string() const {
char buf[UUID_STR_LEN];
this->to_str(buf);
return std::string(buf);
}
} // namespace esphome::esp32_ble
#endif // USE_ESP32_BLE_UUID
-3
View File
@@ -46,9 +46,6 @@ class ESPBTUUID {
esp_bt_uuid_t get_uuid() const;
// Remove before 2026.8.0
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
std::string to_string() const; // NOLINT
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
protected:
@@ -86,4 +86,4 @@ async def to_code(config):
cg.add_define("USE_ESP32_BLE_ADVERTISING")
request_bluetooth()
request_bluetooth(ble_42=True)
@@ -77,6 +77,10 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_
case ESP_GATTS_WRITE_EVT: {
if (this->handle_ != param->write.handle)
break;
if (param->write.len > this->value_.attr_max_len) {
ESP_LOGE(TAG, "Size %d too large, must be no bigger than %d", param->write.len, this->value_.attr_max_len);
break;
}
this->value_.attr_len = param->write.len;
memcpy(this->value_.attr_value, param->write.value, param->write.len);
if (this->on_write_callback_) {
+2 -35
View File
@@ -1,6 +1,5 @@
import logging
from pathlib import Path
import platform
import re
import subprocess
@@ -21,15 +20,9 @@ from esphome.const import (
PLATFORM_ESP8266,
ThreadModel,
)
from esphome.core import (
CORE,
CoroPriority,
EsphomeError,
Lambda,
coroutine_with_priority,
)
from esphome.core import CORE, CoroPriority, Lambda, coroutine_with_priority
from esphome.core.config import BOARD_MAX_LENGTH
from esphome.helpers import IS_MACOS, copy_file_if_changed
from esphome.helpers import copy_file_if_changed
from esphome.types import ConfigType
from .boards import BOARDS, ESP8266_LD_SCRIPTS
@@ -244,32 +237,6 @@ CONFIG_SCHEMA = cv.All(
)
def check_rosetta() -> None:
"""Fail fast when the x86_64 ESP8266 toolchain cannot run on this Mac.
There is no native arm64 build of the xtensa-lx106 toolchain; on Apple
Silicon it runs under Rosetta 2, which macOS updates can remove.
"""
if not IS_MACOS or platform.machine() != "arm64":
return
try:
result = subprocess.run(
["/usr/bin/arch", "-x86_64", "/usr/bin/true"],
capture_output=True,
close_fds=False,
check=False,
)
except OSError:
return # arch(1) unavailable; let the build proceed
if result.returncode != 0:
raise EsphomeError(
"ESP8266 builds on Apple Silicon Macs use an Intel (x86_64) "
"compiler that requires Rosetta 2, which is not installed on "
"this system. Install it with:\n"
" softwareupdate --install-rosetta --agree-to-license"
)
@coroutine_with_priority(CoroPriority.PLATFORM)
async def to_code(config):
cg.add(esp8266_ns.setup_preferences())
+31 -42
View File
@@ -433,48 +433,37 @@ GENERIC_SCHEMA = cv.All(
cv.only_on([Platform.ESP32]),
)
def _spi_schema(default_clock: str = "26.67MHz", max_clock: int = int(80e6)):
return cv.All(
BASE_SCHEMA.extend(
cv.Schema(
{
cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number,
cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(
CONF_INTERRUPT_PIN
): pins.internal_gpio_input_pin_number,
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number,
cv.SplitDefault(CONF_CLOCK_SPEED, esp32=default_clock): cv.All(
cv.only_on_esp32,
cv.frequency,
cv.int_range(int(8e6), max_clock),
),
cv.Optional(CONF_INTERFACE): cv.All(
cv.only_on_esp32,
cv.one_of(*SPI_INTERFACE_MAP.keys(), lower=True),
),
# Set default value (SPI_ETHERNET_DEFAULT_POLLING_INTERVAL) at _validate()
cv.Optional(CONF_POLLING_INTERVAL): cv.All(
cv.only_on_esp32,
cv.positive_time_period_milliseconds,
cv.Range(min=TimePeriodMilliseconds(milliseconds=1)),
),
}
),
SPI_SCHEMA = cv.All(
BASE_SCHEMA.extend(
cv.Schema(
{
cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number,
cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number,
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number,
cv.SplitDefault(CONF_CLOCK_SPEED, esp32="26.67MHz"): cv.All(
cv.only_on_esp32,
cv.frequency,
cv.int_range(int(8e6), int(80e6)),
),
cv.Optional(CONF_INTERFACE): cv.All(
cv.only_on_esp32,
cv.one_of(*SPI_INTERFACE_MAP.keys(), lower=True),
),
# Set default value (SPI_ETHERNET_DEFAULT_POLLING_INTERVAL) at _validate()
cv.Optional(CONF_POLLING_INTERVAL): cv.All(
cv.only_on_esp32,
cv.positive_time_period_milliseconds,
cv.Range(min=TimePeriodMilliseconds(milliseconds=1)),
),
}
),
cv.only_on([Platform.ESP32, Platform.RP2]),
_validate_spi_interface,
)
SPI_SCHEMA = _spi_schema()
# The ENC28J60's SCK maximum is 20 MHz, so the shared 26.67 MHz default is out
# of spec for it and makes the driver's CS hold time helper compute no hold
SPI_SCHEMA_ENC28J60 = _spi_schema(default_clock="20MHz", max_clock=int(20e6))
),
cv.only_on([Platform.ESP32, Platform.RP2]),
_validate_spi_interface,
)
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
@@ -490,7 +479,7 @@ CONFIG_SCHEMA = cv.All(
"W5500": SPI_SCHEMA,
"OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])),
"DM9051": SPI_SCHEMA,
"ENC28J60": SPI_SCHEMA_ENC28J60,
"ENC28J60": SPI_SCHEMA,
"W6100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
"W6300": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
"LAN8670": RMII_SCHEMA,
@@ -112,8 +112,10 @@ enum class EthernetComponentState : uint8_t {
// Platform-neutral duplex/speed types
#ifndef USE_ESP32
// NOLINTBEGIN(readability-identifier-naming)
enum eth_duplex_t { ETH_DUPLEX_HALF, ETH_DUPLEX_FULL };
enum eth_speed_t { ETH_SPEED_10M, ETH_SPEED_100M };
// NOLINTEND(readability-identifier-naming)
#endif
class EthernetComponent final : public Component {
@@ -232,10 +232,8 @@ void EthernetComponent::ethernet_lazy_init_() {
dm9051_config.poll_period_ms = this->polling_interval_;
#endif
#elif defined(USE_ETHERNET_ENC28J60)
// ENC28J60 does not support poll_period_ms. CS must stay asserted for the chip's CS hold
// time (t10, 210 ns) after the last clock or MAC/MII register reads fail ("wrong chip ID")
enc28j60_config.spi_devcfg->cs_ena_posttrans = enc28j60_cal_spi_cs_hold_time((this->clock_speed_ + 999999) / 1000000);
enc28j60_config.int_gpio_num = this->interrupt_pin_;
// ENC28J60 does not support poll_period_ms
#endif
phy_config.phy_addr = this->phy_addr_spi_;
@@ -187,17 +187,18 @@ void EthernetComponent::loop() {
}
void EthernetComponent::dump_config() {
const char *type_str = "Unknown";
#if defined(USE_ETHERNET_W5500)
type_str = "W5500";
const char *type_str = "W5500";
#elif defined(USE_ETHERNET_W5100)
type_str = "W5100";
const char *type_str = "W5100";
#elif defined(USE_ETHERNET_W6100)
type_str = "W6100";
const char *type_str = "W6100";
#elif defined(USE_ETHERNET_W6300)
type_str = "W6300";
const char *type_str = "W6300";
#elif defined(USE_ETHERNET_ENC28J60)
type_str = "ENC28J60";
const char *type_str = "ENC28J60";
#else
const char *type_str = "Unknown";
#endif
#if defined(USE_ETHERNET_W6300)
// W6300 uses PIO QSPI with hardcoded pins — SPI pin fields are not used
@@ -1,4 +1,4 @@
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "fastled_light.h"
#include "esphome/core/log.h"
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
+2 -2
View File
@@ -132,7 +132,7 @@ void HaierClimateBase::save_settings() {
}
bool HaierClimateBase::get_display_state() const {
return (this->display_status_ == SwitchState::ON) || (this->display_status_ == SwitchState::PENDING_ON);
return (this->display_status_ == SwitchState::SWITCH_ON) || (this->display_status_ == SwitchState::PENDING_ON);
}
void HaierClimateBase::set_display_state(bool state) {
@@ -144,7 +144,7 @@ void HaierClimateBase::set_display_state(bool state) {
}
bool HaierClimateBase::get_health_mode() const {
return (this->health_mode_ == SwitchState::ON) || (this->health_mode_ == SwitchState::PENDING_ON);
return (this->health_mode_ == SwitchState::SWITCH_ON) || (this->health_mode_ == SwitchState::PENDING_ON);
}
void HaierClimateBase::set_health_mode(bool state) {
+4 -4
View File
@@ -147,8 +147,8 @@ class HaierClimateBase : public esphome::Component,
esphome::optional<haier_protocol::HaierMessage> message;
};
enum class SwitchState {
OFF = 0b00,
ON = 0b01,
SWITCH_OFF = 0b00,
SWITCH_ON = 0b01,
PENDING_OFF = 0b10,
PENDING_ON = 0b11,
};
@@ -157,8 +157,8 @@ class HaierClimateBase : public esphome::Component,
esphome::optional<PendingAction> action_request_;
uint8_t fan_mode_speed_;
uint8_t other_modes_fan_speed_;
SwitchState display_status_{SwitchState::ON};
SwitchState health_mode_{SwitchState::OFF};
SwitchState display_status_{SwitchState::SWITCH_ON};
SwitchState health_mode_{SwitchState::SWITCH_OFF};
bool force_send_control_;
bool forced_request_status_;
bool reset_protocol_request_;
+6 -6
View File
@@ -50,7 +50,7 @@ void HonClimate::set_quiet_mode_state(bool state) {
this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->force_send_control_ = true;
} else {
this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF;
this->quiet_mode_state_ = state ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
}
this->settings_.quiet_mode_state = state;
#ifdef USE_SWITCH
@@ -63,7 +63,7 @@ void HonClimate::set_quiet_mode_state(bool state) {
}
bool HonClimate::get_quiet_mode_state() const {
return (this->quiet_mode_state_ == SwitchState::ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON);
return (this->quiet_mode_state_ == SwitchState::SWITCH_ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON);
}
esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
@@ -513,7 +513,7 @@ void HonClimate::initialization() {
}
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
}
haier_protocol::HaierMessage HonClimate::get_control_message() {
@@ -939,14 +939,14 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
// AC just turned on from remote need to turn off display
this->force_send_control_ = true;
} else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
this->display_status_ = disp_status ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
}
}
}
// Health mode
if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
bool old_health_mode = this->get_health_mode();
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
should_publish = should_publish || (old_health_mode != this->get_health_mode());
}
{
@@ -1008,7 +1008,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
// In proper mode and not in pending state
bool new_quiet_mode = packet.control.quiet_mode != 0;
if (new_quiet_mode != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
this->settings_.quiet_mode_state = new_quiet_mode;
#ifdef USE_SWITCH
if (this->quiet_mode_switch_ != nullptr) {
+1 -1
View File
@@ -197,7 +197,7 @@ class HonClimate final : public HaierClimateBase {
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
HonSettings settings_{};
ESPPreferenceObject hon_rtc_;
SwitchState quiet_mode_state_{SwitchState::OFF};
SwitchState quiet_mode_state_{SwitchState::SWITCH_OFF};
};
} // namespace esphome::haier
@@ -464,14 +464,14 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
// AC just turned on from remote need to turn off display
this->force_send_control_ = true;
} else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
this->display_status_ = disp_status ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
}
}
}
// Health mode
if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
bool old_health_mode = this->get_health_mode();
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
should_publish = should_publish || (old_health_mode != this->get_health_mode());
}
{
+1 -1
View File
@@ -242,7 +242,7 @@ void HlkFm22xComponent::handle_reply_(const uint8_t *data, size_t length) {
return;
}
if (data[1] != HlkFm22xResult::SUCCESS) {
if (data[1] != HlkFm22xResult::SUCCEEDED) {
ESP_LOGE(TAG, "Command <0x%.2X> failed. Error: 0x%.2X", data[0], data[1]);
switch (expected) {
case HlkFm22xCommand::ENROLL:
+1 -1
View File
@@ -41,7 +41,7 @@ enum HlkFm22xNoteType {
};
enum HlkFm22xResult {
SUCCESS = 0x00,
SUCCEEDED = 0x00,
REJECTED = 0x01,
ABORTED = 0x02,
FAILED4_CAMERA = 0x04,
+1 -1
View File
@@ -61,7 +61,7 @@ void HM3301Component::update() {
int16_t aqi_value = -1;
if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) {
aqi::AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value);
aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value, /*extended_range=*/false);
}
if (pm_1_0_value != -1) {
@@ -488,10 +488,10 @@ template<typename... Ts> class HttpRequestSendAction final : public Action<Ts...
body = this->body_.value(x...);
}
if (!this->json_.empty()) {
body = json::build_json([this, x...](JsonObject root) mutable { this->encode_json_(x..., root); });
body = json::build_json([this, x...](JsonObject root) { this->encode_json_(x..., root); });
}
if (this->json_func_ != nullptr) {
body = json::build_json([this, x...](JsonObject root) mutable { this->json_func_(x..., root); });
body = json::build_json([this, x...](JsonObject root) { this->json_func_(x..., root); });
}
std::vector<Header> request_headers;
request_headers.reserve(this->request_headers_.size());
@@ -1,6 +1,6 @@
#include "http_request_arduino.h"
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
#include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
@@ -2,7 +2,7 @@
#include "http_request.h"
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
#if defined(USE_RP2)
#include <HTTPClient.h>
+1 -1
View File
@@ -1,4 +1,4 @@
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
#include "i2c_bus_arduino.h"
#include <Arduino.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ARDUINO) && !defined(USE_ESP32) && !defined(USE_LIBRETINY)
#include <Wire.h>
#include "esphome/core/component.h"
+1 -1
View File
@@ -42,4 +42,4 @@ async def setup_improv_core(var: MockObj, config: ConfigType, component: str):
cg.add(var.set_next_url(_process_next_url(next_url)))
cg.add_define(f"USE_{component.upper()}_NEXT_URL")
cg.add_library("improv/Improv", "1.2.4")
cg.add_library("improv/Improv", "1.2.6")
+1 -1
View File
@@ -44,7 +44,7 @@ void arch_init() {
void arch_restart() {
lt_reboot();
while (1) {
while (true) {
}
}
+3
View File
@@ -44,6 +44,7 @@
// it is callable from Thumb code via interworking. The MRS CPSR instruction
// is ARM-only and user code here may be built in Thumb, so in_isr_context()
// defers to this port helper on BK72xx instead of reading CPSR inline.
// NOLINTNEXTLINE(readability-redundant-declaration)
extern "C" uint32_t platform_is_in_interrupt_context(void);
#endif
@@ -59,9 +60,11 @@ extern "C" void delayMicroseconds(unsigned int us);
// Forward decls from libretiny's <lt_api.h> family for the inline arch_*
// wrappers below. Pulling the full header would drag in the rest of the
// LibreTiny C API.
// NOLINTBEGIN(readability-redundant-declaration)
extern "C" void lt_wdt_feed(void);
extern "C" uint32_t lt_cpu_get_cycle_count(void);
extern "C" uint32_t lt_cpu_get_freq(void);
// NOLINTEND(readability-redundant-declaration)
namespace esphome::libretiny {}
@@ -13,14 +13,14 @@ void LTComponent::dump_config() {
"LibreTiny:\n"
" Version: %s\n"
" Loglevel: %u",
LT_BANNER_STR + 10, LT_LOGLEVEL);
&LT_BANNER_STR[10], LT_LOGLEVEL);
#if defined(__OPTIMIZE_SIZE__) && __OPTIMIZE_LEVEL__ > 0 && __OPTIMIZE_LEVEL__ <= 3
ESP_LOGCONFIG(TAG, " Optimization: -Os, SDK: -O" STRINGIFY_MACRO(__OPTIMIZE_LEVEL__));
#endif
#ifdef USE_TEXT_SENSOR
if (this->version_ != nullptr) {
this->version_->publish_state(LT_BANNER_STR + 10);
this->version_->publish_state(&LT_BANNER_STR[10]);
}
#endif // USE_TEXT_SENSOR
}
@@ -5,8 +5,10 @@
#include <cstdint>
// Forward declare FlashDB types to avoid pulling in flashdb.h
// NOLINTBEGIN(readability-identifier-naming)
struct fdb_kvdb;
struct fdb_blob;
// NOLINTEND(readability-identifier-naming)
namespace esphome::libretiny {
+8 -17
View File
@@ -219,23 +219,14 @@ LightColorValues LightCall::validate_() {
this->set_flag_(FLAG_HAS_STATE);
}
// A light without brightness control has no way to represent "on but dark", so zero
// brightness -- how effects encode their dark phase -- means the light is off. Clear the
// brightness as well, so a zero can't linger in remote_values and leave the light stuck
// off: a later turn-on can't heal it, because the capability check below drops any
// brightness this mode doesn't support. explicit_turn_off_request was captured above, so
// a running effect is not stopped by this.
if (this->has_brightness() && this->brightness_ == 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
this->state_ = false;
this->set_flag_(FLAG_HAS_STATE);
this->clear_flag_(FLAG_HAS_BRIGHTNESS);
}
// Make sure a simple (no specific brightness) turn-on makes the light visible
if (this->has_state() && this->state_ && (color_mode & ColorCapability::BRIGHTNESS) && !this->has_brightness() &&
this->parent_->remote_values.get_brightness() == 0.0f) {
this->brightness_ = 1.0f;
this->set_flag_(FLAG_HAS_BRIGHTNESS);
// Make sure a turn-on makes the light visible: if the resulting brightness would be zero
// (e.g. restored from a brightness=0 turn-off), reset it to full brightness.
if (this->has_state() && this->state_ && (color_mode & ColorCapability::BRIGHTNESS)) {
float brightness = this->has_brightness() ? this->brightness_ : this->parent_->remote_values.get_brightness();
if (brightness == 0.0f) {
this->brightness_ = 1.0f;
this->set_flag_(FLAG_HAS_BRIGHTNESS);
}
}
// Set color brightness to 100% if currently zero and a color is set.
-8
View File
@@ -71,14 +71,6 @@ void LightState::setup() {
break;
}
// A light coming up on boot must never end up on-but-invisible: if the resolved restore
// state is on but its brightness is zero (e.g. a stale/persisted value from before a
// forced-on restore mode, or an inverted restore flipping a dim-to-0 off state to on),
// reset it to full brightness.
if (recovered.state && recovered.brightness == 0.0f) {
recovered.brightness = 1.0f;
}
call.set_color_mode_if_supported(recovered.color_mode);
call.set_state(recovered.state);
call.set_brightness_if_supported(recovered.brightness);
+34
View File
@@ -101,6 +101,7 @@ USB_SERIAL_JTAG = "USB_SERIAL_JTAG"
USB_CDC = "USB_CDC"
DEFAULT = "DEFAULT"
CONF_BOOT_LOG_BUFFER_SIZE = "boot_log_buffer_size"
CONF_INITIAL_LEVEL = "initial_level"
CONF_LOGGER_ID = "logger_id"
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
@@ -240,6 +241,13 @@ CONFIG_SCHEMA = cv.All(
cv.validate_bytes, cv.int_range(min=160, max=65535)
),
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
cv.Optional(CONF_BOOT_LOG_BUFFER_SIZE, default=0): cv.All(
cv.validate_bytes,
cv.Any(
cv.int_(0), # Disabled
cv.int_range(min=512, max=16384),
),
),
cv.SplitDefault(
CONF_TASK_LOG_BUFFER_SIZE,
esp32=768, # Default: 768 bytes (~5-6 messages with 70-byte text plus thread names)
@@ -350,6 +358,11 @@ async def to_code(config: ConfigType) -> None:
if task_log_buffer_size > 0:
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
cg.add_define("ESPHOME_TASK_LOG_BUFFER_SIZE", task_log_buffer_size)
# Boot log buffer captures early WARN/ERROR lines for later replay
# (logger.replay_boot_logs action). Buffer is a direct member of Logger.
if (boot_log_buffer_size := config[CONF_BOOT_LOG_BUFFER_SIZE]) > 0:
cg.add_define("USE_LOGGER_BOOT_LOG_BUFFER")
cg.add_define("ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE", boot_log_buffer_size)
log = cg.new_Pvariable(
config[CONF_ID],
baud_rate,
@@ -593,6 +606,27 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
)
@automation.register_action(
"logger.replay_boot_logs",
LambdaAction,
automation.maybe_conf(
CONF_LOGGER_ID,
{
cv.GenerateID(CONF_LOGGER_ID): cv.use_id(Logger),
},
),
synchronous=True,
)
async def logger_replay_boot_logs_to_code(config, action_id, template_arg, args):
logger = await cg.get_variable(config[CONF_LOGGER_ID])
text = str(cg.statement(logger.replay_boot_logs()))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return automation.new_lambda_pvariable(
action_id, lambda_, StatelessLambdaAction, template_arg
)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"logger_esp32.cpp": {
+59
View File
@@ -1,5 +1,6 @@
#include "logger.h"
#include <cinttypes>
#include <cstring>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
@@ -202,6 +203,61 @@ void Logger::process_messages_() {
}
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
// Record layout in boot_log_buffer_: u16 text_len | u8 level | u32 millis | text
static constexpr uint16_t BOOT_LOG_RECORD_HEADER_SIZE = 7;
void Logger::boot_log_capture_(uint8_t level, const LogBuffer &buf) {
if (this->boot_log_done_ || level > ESPHOME_LOG_LEVEL_WARN || level == ESPHOME_LOG_LEVEL_NONE)
return;
// Strip the leading ANSI color prefix and trailing reset; the replay re-emits
// the text through the normal formatter, which colors it again.
const char *text = buf.data;
uint16_t len = buf.pos;
if (len > 0 && text[0] == '\033') {
const char *m = static_cast<const char *>(memchr(text, 'm', len));
if (m != nullptr) {
const uint16_t skip = static_cast<uint16_t>(m - text) + 1;
text += skip;
len -= skip;
}
}
if (len >= 4 && memcmp(text + len - 4, "\033[0m", 4) == 0)
len -= 4;
if (this->boot_log_pos_ + BOOT_LOG_RECORD_HEADER_SIZE + len > ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE) {
this->boot_log_dropped_++;
return;
}
char *p = this->boot_log_buffer_ + this->boot_log_pos_;
const uint32_t now = millis();
memcpy(p, &len, sizeof(len));
p[2] = static_cast<char>(level);
memcpy(p + 3, &now, sizeof(now));
memcpy(p + BOOT_LOG_RECORD_HEADER_SIZE, text, len);
this->boot_log_pos_ += BOOT_LOG_RECORD_HEADER_SIZE + len;
}
void Logger::replay_boot_logs() {
// Stop capturing first so the replay output is not buffered again.
this->boot_log_done_ = true;
ESP_LOGI(TAG, "Replaying boot logs (%u bytes used, %u lines dropped)", this->boot_log_pos_, this->boot_log_dropped_);
uint16_t pos = 0;
while (pos + BOOT_LOG_RECORD_HEADER_SIZE <= this->boot_log_pos_) {
const char *p = this->boot_log_buffer_ + pos;
uint16_t len;
memcpy(&len, p, sizeof(len));
uint32_t stamp;
memcpy(&stamp, p + 3, sizeof(stamp));
esp_log_printf_(static_cast<uint8_t>(p[2]), TAG, __LINE__, ESPHOME_LOG_FORMAT("@%" PRIu32 "ms %.*s"), stamp,
static_cast<int>(len), p + BOOT_LOG_RECORD_HEADER_SIZE);
pos += BOOT_LOG_RECORD_HEADER_SIZE + len;
}
ESP_LOGI(TAG, "Boot log replay done");
}
#else
void Logger::replay_boot_logs() { ESP_LOGW(TAG, "Boot log buffer not configured (set 'boot_log_buffer_size')"); }
#endif // USE_LOGGER_BOOT_LOG_BUFFER
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
#endif
@@ -239,6 +295,9 @@ void Logger::dump_config() {
ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast<unsigned int>(this->log_buffer_.size()));
#endif
#endif
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
ESP_LOGCONFIG(TAG, " Boot Log Buffer Size: %u bytes", ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE);
#endif
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
for (auto &it : this->log_levels_) {
+26
View File
@@ -174,6 +174,12 @@ class Logger final : public Component {
#endif
uint8_t get_log_level() { return this->current_level_; }
/// Re-emit WARN/ERROR messages captured during boot through the normal log
/// dispatch so connected listeners (API, MQTT, ...) can see them. Stops
/// further capture; safe to call multiple times. Warns when the logger was
/// built without 'boot_log_buffer_size'.
void replay_boot_logs();
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Set up this component.
@@ -291,6 +297,9 @@ class Logger final : public Component {
{
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf, thread_name);
}
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
this->boot_log_capture_(level, buf);
#endif
this->notify_listeners_(level, tag, buf);
this->write_log_buffer_to_console_(buf);
}
@@ -303,6 +312,9 @@ class Logger final : public Component {
LogBuffer &buf) {
buf.write_header(level, tag, line, thread_name);
buf.write_body(text, text_length);
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
this->boot_log_capture_(level, buf);
#endif
this->notify_listeners_(level, tag, buf);
}
#endif
@@ -311,6 +323,11 @@ class Logger final : public Component {
const LogString *get_uart_selection_();
#endif
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
/// Copy a formatted WARN/ERROR line (ANSI codes stripped) into the boot log buffer.
void boot_log_capture_(uint8_t level, const LogBuffer &buf);
#endif
// Group 4-byte aligned members first
uint32_t baud_rate_;
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
@@ -350,6 +367,11 @@ class Logger final : public Component {
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
#endif
// Group smaller types together at the end
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
uint16_t boot_log_pos_{0}; // Bytes used in boot_log_buffer_
uint16_t boot_log_dropped_{0}; // Lines that did not fit
bool boot_log_done_{false}; // Set on first replay; stops further capture
#endif
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2) || defined(USE_ZEPHYR)
UARTSelection uart_{UART_SELECTION_UART0};
@@ -368,6 +390,10 @@ class Logger final : public Component {
// Large buffers placed last to keep frequently-accessed member offsets small
char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE + 1]; // +1 for null terminator
#ifdef USE_LOGGER_BOOT_LOG_BUFFER
// Sequential records of early WARN/ERROR lines: u16 text_len | u8 level | u32 millis | text
char boot_log_buffer_[ESPHOME_LOGGER_BOOT_LOG_BUFFER_SIZE];
#endif
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
logger::TaskLogBuffer log_buffer_; // Embedded in Logger (no separate heap allocation)
#endif
@@ -20,7 +20,7 @@ TaskLogBuffer::~TaskLogBuffer() {
}
}
size_t TaskLogBuffer::available_contiguous_space() const {
size_t TaskLogBuffer::available_contiguous_space_() const {
if (this->head_ >= this->tail_) {
// head is ahead of or equal to tail
// Available space is from head to end, plus from start to tail
@@ -81,7 +81,7 @@ void TaskLogBuffer::release_message_main_loop() {
this->tail_ = 0;
}
this->message_count_--;
this->message_count_ = this->message_count_ - 1;
this->current_message_size_ = 0;
xSemaphoreGive(this->mutex_);
@@ -117,7 +117,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
}
// Check if we have enough contiguous space
size_t contiguous = this->available_contiguous_space();
size_t contiguous = this->available_contiguous_space_();
if (contiguous < total_size) {
// Not enough contiguous space at end
@@ -128,9 +128,9 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
}
// Need at least enough space to safely write padding marker (level field is at end of struct)
constexpr size_t PADDING_MARKER_MIN_SPACE = offsetof(LogMessage, level) + 1;
constexpr size_t padding_marker_min_space = offsetof(LogMessage, level) + 1;
if (space_at_start >= total_size && this->head_ > 0 && contiguous >= PADDING_MARKER_MIN_SPACE) {
if (space_at_start >= total_size && this->head_ > 0 && contiguous >= padding_marker_min_space) {
// Add padding marker (set level field to indicate this is padding, not a real message)
LogMessage *padding = reinterpret_cast<LogMessage *>(this->storage_ + this->head_);
padding->level = PADDING_MARKER_LEVEL;
@@ -180,7 +180,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
this->head_ = 0;
}
this->message_count_++;
this->message_count_ = this->message_count_ + 1;
xSemaphoreGive(this->mutex_);
return true;
@@ -84,7 +84,7 @@ class TaskLogBuffer {
static inline size_t message_total_size(size_t text_length) { return sizeof(LogMessage) + text_length + 1; }
// Calculate available contiguous space at write position
size_t available_contiguous_space() const;
size_t available_contiguous_space_() const;
uint8_t storage_[ESPHOME_TASK_LOG_BUFFER_SIZE]; // Embedded in Logger (no separate heap allocation)
size_t head_{0}; // Write position
+2 -2
View File
@@ -27,8 +27,8 @@ static void register_libretiny(MDNSComponent *, StaticVector<MDNSService, MDNS_S
while (*service_type == '_') {
service_type++;
}
uint16_t port_ = service.port.value();
MDNS.addService(service_type, proto, port_);
uint16_t port = service.port.value();
MDNS.addService(service_type, proto, port);
for (const auto &record : service.txt_records) {
MDNS.addServiceTxt(service_type, proto, MDNS_STR_ARG(record.key), MDNS_STR_ARG(record.value));
}
+26 -71
View File
@@ -2,7 +2,6 @@ import hashlib
import json
import logging
from pathlib import Path
import re
from urllib.parse import urljoin
from esphome import automation, external_files, git
@@ -10,7 +9,6 @@ from esphome.automation import register_action, register_condition
from esphome.bundle import add_bundle_file
import esphome.codegen as cg
from esphome.components import esp32, microphone, ota, psram
from esphome.components.http_request import validate_url
import esphome.config_validation as cv
from esphome.const import (
CONF_FILE,
@@ -211,13 +209,33 @@ def _validate_manifest_version(manifest_data):
raise cv.Invalid("Invalid manifest file, missing 'version' key")
HTTP_SCHEMA = cv.Schema(
def _process_http_source(config):
url = config[CONF_URL]
path = _compute_local_file_path(config)
json_path = path / "manifest.json"
json_contents = external_files.download_content(url, json_path)
manifest_data = json.loads(json_contents)
if not isinstance(manifest_data, dict):
raise cv.Invalid("Manifest file must contain a JSON object")
model = manifest_data[CONF_MODEL]
model_url = urljoin(url, model)
model_path = path / model
external_files.download_content(str(model_url), model_path)
return config
HTTP_SCHEMA = cv.All(
{
# validate_url only accepts http(s); the shorthand validator relies
# on this branch rejecting git shorthands ("github://...") so they
# fall through to the git branch.
cv.Required(CONF_URL): validate_url,
}
cv.Required(CONF_URL): cv.url,
},
_process_http_source,
)
@@ -262,13 +280,6 @@ LOCAL_SCHEMA = cv.All(
)
# Bare model names in the official model repository ("okay_nabu"). Must not
# overlap with local paths, http(s) urls, or git shorthands
# ("github://user/repo/file.json@ref"), which the shorthand validator tries
# next; anything containing "/", ":" or "@" is not a model name.
_MODEL_NAME_RE = re.compile(r"[A-Za-z0-9_.-]+")
def _validate_source_model_name(value):
if not isinstance(value, str):
raise cv.Invalid("Model name must be a string")
@@ -276,9 +287,6 @@ def _validate_source_model_name(value):
if value.endswith(".json"):
raise cv.Invalid("Model name must not end with .json")
if not _MODEL_NAME_RE.fullmatch(value):
raise cv.Invalid("Model name may only contain letters, numbers, . _ -")
return MODEL_SOURCE_SCHEMA(
{
CONF_TYPE: TYPE_HTTP,
@@ -368,58 +376,6 @@ def _maybe_empty_vad_schema(value):
return VAD_MODEL_SCHEMA(value)
def _download_http_models(config: ConfigType) -> ConfigType:
"""Download every http-sourced manifest and model file in two concurrent
batches (all manifests, then all model files).
The model file's URL only becomes known once its manifest has been
fetched and parsed, so the two stages cannot be merged into one batch.
"""
model_parameters = [*config[CONF_MODELS]]
if vad := config.get(CONF_VAD):
model_parameters.append(vad)
# Keyed by cache path so a URL referenced twice is fetched and parsed once
http_models: dict[Path, str] = {
_compute_local_file_path(model_config): model_config[CONF_URL]
for parameters in model_parameters
if (model_config := parameters.get(CONF_MODEL)) is not None
and model_config.get(CONF_TYPE) == TYPE_HTTP
}
if not http_models:
return config
external_files.download_content_many(
((url, path / "manifest.json") for path, url in http_models.items()),
description="wake word manifest(s)",
)
model_files: list[tuple[str, Path]] = []
errors: list[cv.Invalid] = []
for path, url in http_models.items():
try:
manifest_data = json.loads((path / "manifest.json").read_bytes())
except (OSError, ValueError) as e:
errors.append(cv.Invalid(f"Invalid manifest file at {url}: {e}"))
continue
if not isinstance(manifest_data, dict):
errors.append(
cv.Invalid(f"Manifest file at {url} must contain a JSON object")
)
continue
model = manifest_data.get(CONF_MODEL)
if not isinstance(model, str):
errors.append(
cv.Invalid(f"Manifest file at {url} is missing the 'model' key")
)
continue
model_files.append((urljoin(url, model), path / model))
if errors:
raise cv.MultipleInvalid(errors)
external_files.download_content_many(model_files, description="wake word model(s)")
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -453,7 +409,6 @@ CONFIG_SCHEMA = cv.All(
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_on_esp32,
_download_http_models,
)
+1 -1
View File
@@ -1,4 +1,4 @@
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "esphome/core/log.h"
#include "ac_adapter.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "esphome/core/automation.h"
#include "air_conditioner.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
// MideaUART
#include <Appliance/ApplianceBase.h>
+8
View File
@@ -25,6 +25,8 @@ from esphome.const import (
ICON_POWER,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
PLATFORM_ESP32,
PLATFORM_ESP8266,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -152,6 +154,12 @@ CONFIG_SCHEMA = cv.All(
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
]
),
)
# Actions
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2) && !defined(USE_LIBRETINY)
#ifdef USE_REMOTE_TRANSMITTER
#include "esphome/components/remote_base/midea_protocol.h"
@@ -318,4 +318,232 @@ DsiDriverChip(
(0xE0, 0x00),
]
)
# JC8012P4A1 V2 Driver Configuration (jd9365)
# Some units of this model have a different LCD panel but still use the same JD9365 driver chip.
# Using parameters from esp_lcd_jd9365.h and the working full init sequence
# ----------------------------------------------------------------------------------------------------------------------
# * Resolution: 800x1280
# * PCLK Frequency: 70 MHz
# * DSI Lane Bit Rate: 1.5 Gbps (using 2-Lane DSI configuration)
# * Horizontal Timing (hsync_pulse_width=20, hsync_back_porch=20, hsync_front_porch=40)
# * Vertical Timing (vsync_pulse_width=4, vsync_back_porch=10, vsync_front_porch=20)
# ----------------------------------------------------------------------------------------------------------------------
DsiDriverChip(
"JC8012P4A1-V2",
width=800,
height=1280,
hsync_back_porch=20,
hsync_pulse_width=20,
hsync_front_porch=40,
vsync_back_porch=10,
vsync_pulse_width=4,
vsync_front_porch=20,
pclk_frequency="70MHz",
lane_bit_rate="1500Mbps",
color_order="RGB",
reset_pin=27,
initsequence=[
(0xE0, 0x00),
(0xE1, 0x93),
(0xE2, 0x65),
(0xE3, 0xF8),
(0x80, 0x01),
(0xE0, 0x01),
(0x00, 0x00),
(0x01, 0x44),
(0x03, 0x10),
(0x04, 0x38),
(0x0C, 0x74),
(0x17, 0x00),
(0x18, 0xAF),
(0x19, 0x00),
(0x1A, 0x00),
(0x1B, 0xAF),
(0x1C, 0x00),
(0x35, 0x26),
(0x37, 0x09),
(0x38, 0x04),
(0x39, 0x00),
(0x3A, 0x01),
(0x3C, 0x78),
(0x3D, 0xFF),
(0x3E, 0xFF),
(0x3F, 0x7F),
(0x40, 0x06),
(0x41, 0xA0),
(0x42, 0x81),
(0x43, 0x1E),
(0x44, 0x0D),
(0x45, 0x28),
(0x55, 0x02),
(0x57, 0x69),
(0x59, 0x0A),
(0x5A, 0x2A),
(0x5B, 0x17),
(0x5D, 0x7F),
(0x5E, 0x6B),
(0x5F, 0x5C),
(0x60, 0x50),
(0x61, 0x4C),
(0x62, 0x3E),
(0x63, 0x41),
(0x64, 0x2B),
(0x65, 0x43),
(0x66, 0x42),
(0x67, 0x43),
(0x68, 0x62),
(0x69, 0x52),
(0x6A, 0x5A),
(0x6B, 0x4C),
(0x6C, 0x48),
(0x6D, 0x3A),
(0x6E, 0x28),
(0x6F, 0x10),
(0x70, 0x7F),
(0x71, 0x6B),
(0x72, 0x5C),
(0x73, 0x50),
(0x74, 0x4C),
(0x75, 0x3E),
(0x76, 0x41),
(0x77, 0x2B),
(0x78, 0x43),
(0x79, 0x42),
(0x7A, 0x43),
(0x7B, 0x62),
(0x7C, 0x52),
(0x7D, 0x5A),
(0x7E, 0x4C),
(0x7F, 0x48),
(0x80, 0x3A),
(0x81, 0x28),
(0x82, 0x10),
(0xE0, 0x02),
(0x00, 0x42),
(0x01, 0x42),
(0x02, 0x40),
(0x03, 0x40),
(0x04, 0x5E),
(0x05, 0x5E),
(0x06, 0x5F),
(0x07, 0x5F),
(0x08, 0x5F),
(0x09, 0x57),
(0x0A, 0x57),
(0x0B, 0x77),
(0x0C, 0x77),
(0x0D, 0x47),
(0x0E, 0x47),
(0x0F, 0x45),
(0x10, 0x45),
(0x11, 0x4B),
(0x12, 0x4B),
(0x13, 0x49),
(0x14, 0x49),
(0x15, 0x5F),
(0x16, 0x41),
(0x17, 0x41),
(0x18, 0x40),
(0x19, 0x40),
(0x1A, 0x5E),
(0x1B, 0x5E),
(0x1C, 0x5F),
(0x1D, 0x5F),
(0x1E, 0x5F),
(0x1F, 0x57),
(0x20, 0x57),
(0x21, 0x77),
(0x22, 0x77),
(0x23, 0x46),
(0x24, 0x46),
(0x25, 0x44),
(0x26, 0x44),
(0x27, 0x4A),
(0x28, 0x4A),
(0x29, 0x48),
(0x2A, 0x48),
(0x2B, 0x5F),
(0x2C, 0x01),
(0x2D, 0x01),
(0x2E, 0x00),
(0x2F, 0x00),
(0x30, 0x1F),
(0x31, 0x1F),
(0x32, 0x1E),
(0x33, 0x1E),
(0x34, 0x1F),
(0x35, 0x17),
(0x36, 0x17),
(0x37, 0x37),
(0x38, 0x37),
(0x39, 0x08),
(0x3A, 0x08),
(0x3B, 0x0A),
(0x3C, 0x0A),
(0x3D, 0x04),
(0x3E, 0x04),
(0x3F, 0x06),
(0x40, 0x06),
(0x41, 0x1F),
(0x42, 0x02),
(0x43, 0x02),
(0x44, 0x00),
(0x45, 0x00),
(0x46, 0x1F),
(0x47, 0x1F),
(0x48, 0x1E),
(0x49, 0x1E),
(0x4A, 0x1F),
(0x4B, 0x17),
(0x4C, 0x17),
(0x4D, 0x37),
(0x4E, 0x37),
(0x4F, 0x09),
(0x50, 0x09),
(0x51, 0x0B),
(0x52, 0x0B),
(0x53, 0x05),
(0x54, 0x05),
(0x55, 0x07),
(0x56, 0x07),
(0x57, 0x1F),
(0x58, 0x40),
(0x5B, 0x30),
(0x5C, 0x00),
(0x5D, 0x34),
(0x5E, 0x05),
(0x5F, 0x02),
(0x63, 0x00),
(0x64, 0x6A),
(0x67, 0x73),
(0x68, 0x07),
(0x69, 0x08),
(0x6A, 0x6A),
(0x6B, 0x08),
(0x6C, 0x00),
(0x6D, 0x00),
(0x6E, 0x00),
(0x6F, 0x88),
(0x75, 0xFF),
(0x77, 0xDD),
(0x78, 0x2C),
(0x79, 0x15),
(0x7A, 0x17),
(0x7D, 0x14),
(0x7E, 0x82),
(0xE0, 0x04),
(0x00, 0x0E),
(0x02, 0xB3),
(0x09, 0x60),
(0x0E, 0x48),
(0x37, 0x58),
(0x2B, 0x0F),
(0xE0, 0x05),
(0x15, 0x1D),
(0xE0, 0x00),
(0xE6, 0x02),
(0xE7, 0x0C)
]
)
# fmt: on
+7 -7
View File
@@ -385,10 +385,10 @@ class MipiSpi : public display::Display,
* @param ptr The pointer to the pixel data
* @param w Width of each line in bytes
* @param h Height of the buffer in rows
* @param stride Total length of each line in bytes, including any padding
* @param pad Padding in bytes after each line
*/
void write_display_data_(const uint8_t *ptr, size_t w, size_t h, size_t stride) {
if (stride == w) {
void write_display_data_(const uint8_t *ptr, size_t w, size_t h, size_t pad) {
if (pad == 0) {
if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) {
this->write_array(ptr, w * h);
} else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
@@ -405,7 +405,7 @@ class MipiSpi : public display::Display,
} else if constexpr (BUS_TYPE == BUS_TYPE_OCTAL) {
this->write_cmd_addr_data(0, 0, 0, 0, ptr, w, 8);
}
ptr += stride;
ptr += w + pad;
}
}
}
@@ -423,7 +423,7 @@ class MipiSpi : public display::Display,
ptr += y_offset * (x_offset + w + x_pad) + x_offset;
if constexpr (BUFFERPIXEL == DISPLAYPIXEL) {
this->write_display_data_(reinterpret_cast<const uint8_t *>(ptr), w * sizeof(BUFFERTYPE), h,
(x_offset + w + x_pad) * sizeof(BUFFERTYPE));
x_pad * sizeof(BUFFERTYPE));
} else {
// type conversion required, do it in chunks
uint8_t dbuffer[DISPLAYPIXEL * 48];
@@ -459,14 +459,14 @@ class MipiSpi : public display::Display,
}
// buffer full? Flush.
if (dptr == dbuffer + sizeof(dbuffer)) {
this->write_display_data_(dbuffer, sizeof(dbuffer), 1, sizeof(dbuffer));
this->write_display_data_(dbuffer, sizeof(dbuffer), 1, 0);
dptr = dbuffer;
}
}
}
// flush any remaining data
if (dptr != dbuffer) {
this->write_display_data_(dbuffer, dptr - dbuffer, 1, dptr - dbuffer);
this->write_display_data_(dbuffer, dptr - dbuffer, 1, 0);
}
}
this->disable();

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